CNTK - rekurencyjna sieć neuronowa
Teraz zrozummy, jak zbudować cykliczną sieć neuronową (RNN) w CNTK.
Wprowadzenie
Dowiedzieliśmy się, jak klasyfikować obrazy za pomocą sieci neuronowej i jest to jedno z ikonicznych zawodów w głębokim uczeniu się. Ale innym obszarem, w którym sieć neuronowa przoduje i prowadzi się wiele badań, są rekurencyjne sieci neuronowe (RNN). Tutaj dowiemy się, czym jest RNN i jak można go wykorzystać w scenariuszach, w których musimy poradzić sobie z danymi szeregów czasowych.
Co to jest cykliczna sieć neuronowa?
Powtarzające się sieci neuronowe (RNN) można zdefiniować jako specjalny rodzaj sieci NN, które są zdolne do wnioskowania w czasie. Numery RNN są używane głównie w scenariuszach, w których musimy radzić sobie z wartościami zmieniającymi się w czasie, tj. Danymi szeregów czasowych. Aby lepiej to zrozumieć, zróbmy małe porównanie między zwykłymi sieciami neuronowymi a powtarzającymi się sieciami neuronowymi -
Jak wiemy, w zwykłej sieci neuronowej możemy podać tylko jedno wejście. Ogranicza to do wyników tylko jednej prognozy. Aby dać ci przykład, możemy wykonać zadanie tłumaczenia tekstu przy użyciu zwykłych sieci neuronowych.
Z drugiej strony, w powtarzających się sieciach neuronowych możemy podać sekwencję próbek, które skutkują pojedynczą prognozą. Innymi słowy, używając RNN możemy przewidzieć sekwencję wyjściową na podstawie sekwencji wejściowej. Na przykład było kilka udanych eksperymentów z RNN w zadaniach tłumaczeniowych.
Zastosowania powtarzających się sieci neuronowych
Numery RNN mogą być używane na kilka sposobów. Niektóre z nich są następujące -
Przewidywanie pojedynczego wyniku
Zanim zagłębimy się w kroki, w jaki sposób RNN może przewidzieć pojedynczy wynik na podstawie sekwencji, zobaczmy, jak wygląda podstawowy RNN.
Jak możemy na powyższym diagramie, RNN zawiera połączenie zwrotne do wejścia i zawsze, gdy podajemy sekwencję wartości, przetworzy każdy element w sekwencji jako kroki czasowe.
Co więcej, ze względu na połączenie zwrotne, RNN może łączyć wygenerowane dane wyjściowe z danymi wejściowymi dla następnego elementu w sekwencji. W ten sposób RNN zbuduje pamięć w całej sekwencji, która może być użyta do prognozowania.
Aby przewidzieć z RNN, możemy wykonać następujące kroki:
Po pierwsze, aby utworzyć początkowy stan ukryty, musimy podać pierwszy element sekwencji wejściowej.
Następnie, aby uzyskać zaktualizowany stan ukryty, musimy wziąć początkowy stan ukryty i połączyć go z drugim elementem w sekwencji wejściowej.
W końcu, aby uzyskać ostateczny stan ukryty i przewidzieć wynik dla RNN, musimy wziąć ostatni element sekwencji wejściowej.
W ten sposób za pomocą tego połączenia zwrotnego możemy nauczyć RNN rozpoznawać wzorce, które pojawiają się w czasie.
Przewidywanie sekwencji
Podstawowy model, omówiony powyżej, RNN można również rozszerzyć na inne przypadki użycia. Na przykład możemy go użyć do przewidywania sekwencji wartości na podstawie pojedynczego wejścia. W tym scenariuszu, aby prognozować z RNN, możemy wykonać następujące kroki -
Po pierwsze, aby stworzyć początkowy stan ukryty i przewidzieć pierwszy element w sekwencji wyjściowej, musimy wprowadzić próbkę wejściową do sieci neuronowej.
Następnie, aby uzyskać zaktualizowany stan ukryty i drugi element w sekwencji wyjściowej, musimy połączyć początkowy stan ukryty z tą samą próbką.
W końcu, aby zaktualizować stan ukryty jeszcze raz i przewidzieć końcowy element w sekwencji wyjściowej, podajemy próbkę innym razem.
Przewidywanie sekwencji
Jak widzieliśmy, jak przewidzieć pojedynczą wartość na podstawie sekwencji i jak przewidzieć sekwencję na podstawie pojedynczej wartości. Zobaczmy teraz, jak możemy przewidzieć sekwencje dla sekwencji. W tym scenariuszu, aby prognozować z RNN, możemy wykonać następujące kroki -
Po pierwsze, aby utworzyć początkowy stan ukryty i przewidzieć pierwszy element w sekwencji wyjściowej, musimy wziąć pierwszy element w sekwencji wejściowej.
Następnie, aby zaktualizować stan ukryty i przewidzieć drugi element w sekwencji wyjściowej, musimy przyjąć początkowy stan ukryty.
Na koniec, aby przewidzieć ostatni element w sekwencji wyjściowej, musimy wziąć zaktualizowany stan ukryty i ostatni element w sekwencji wejściowej.
Działanie RNN
Aby zrozumieć działanie rekurencyjnych sieci neuronowych (RNN), musimy najpierw zrozumieć, jak działają powtarzające się warstwy w sieci. Więc najpierw omówmy, w jaki sposób e można przewidzieć wynik za pomocą standardowej warstwy rekurencyjnej.
Przewidywanie wyniku ze standardową warstwą RNN
Jak omówiliśmy wcześniej, również podstawowa warstwa w RNN różni się znacznie od zwykłej warstwy w sieci neuronowej. W poprzedniej sekcji pokazaliśmy również na diagramie podstawową architekturę RNN. Aby zaktualizować stan ukryty dla sekwencji wkroczenia po raz pierwszy, możemy użyć następującego wzoru -
W powyższym równaniu obliczamy nowy stan ukryty, obliczając iloczyn skalarny między początkowym stanem ukrytym a zestawem wag.
Teraz, w następnym kroku, stan ukryty dla bieżącego kroku czasowego jest używany jako początkowy stan ukryty dla następnego kroku czasowego w sekwencji. Dlatego, aby zaktualizować stan ukryty po raz drugi, możemy powtórzyć obliczenia wykonane w pierwszym kroku w następujący sposób -
Następnie możemy powtórzyć proces aktualizacji stanu ukrytego dla trzeciego i ostatniego kroku w sekwencji, jak poniżej -
Po przetworzeniu wszystkich powyższych kroków w sekwencji możemy obliczyć wynik w następujący sposób -
W powyższym wzorze użyliśmy trzeciego zestawu wag i stanu ukrytego z ostatniego kroku czasowego.
Zaawansowane jednostki okresowe
Głównym problemem związanym z podstawową warstwą rekurencyjną jest problem znikającego gradientu, przez co nie jest ona zbyt dobra w uczeniu się długoterminowych korelacji. Krótko mówiąc, podstawowa warstwa powtarzalna nie radzi sobie zbyt dobrze z długimi sekwencjami. Z tego powodu niektóre inne powtarzające się typy warstw, które są znacznie bardziej odpowiednie do pracy z dłuższymi sekwencjami, są następujące:
Pamięć długoterminowa (LSTM)
Sieci pamięci długoterminowej (LSTM) zostały wprowadzone przez firmę Hochreiter & Schmidhuber. Rozwiązał problem uzyskania podstawowej powtarzającej się warstwy, aby pamiętać rzeczy przez długi czas. Architekturę LSTM przedstawiono powyżej na schemacie. Jak widzimy, ma neurony wejściowe, komórki pamięci i neurony wyjściowe. Aby zwalczyć problem znikającego gradientu, sieci pamięci długoterminowej używają jawnej komórki pamięci (przechowuje poprzednie wartości) i następujących bramek -
Forget gate- Jak sama nazwa wskazuje, mówi komórce pamięci, aby zapomniała poprzednich wartości. Komórka pamięci przechowuje wartości do momentu, gdy bramka, tj. „Zapomnij bramę”, powie jej, aby je zapomnieć.
Input gate- Jak sama nazwa wskazuje, dodaje nowe rzeczy do komórki.
Output gate- Jak sama nazwa wskazuje, bramka wyjściowa decyduje, kiedy przejść wzdłuż wektorów z komórki do następnego stanu ukrytego.
Zamknięte jednostki okresowe (GRU)
Gradient recurrent units(GRU) to niewielka odmiana sieci LSTM. Ma o jedną bramkę mniej i są okablowane nieco inaczej niż LSTM. Jego architekturę przedstawia powyższy diagram. Ma neurony wejściowe, bramkowane komórki pamięci i neurony wyjściowe. Sieć Gated Recurrent Units ma następujące dwie bramki -
Update gate- Określa następujące dwie rzeczy -
Jaka ilość informacji powinna być zachowana z ostatniego stanu?
Jaka ilość informacji powinna zostać wpuszczona z poprzedniej warstwy?
Reset gate- Funkcjonalność bramki resetowania jest podobna do funkcji zapomnienia bramki sieci LSTM. Jedyna różnica polega na tym, że znajduje się nieco inaczej.
W przeciwieństwie do sieci pamięci długoterminowej, sieci Gated Recurrent Unit są nieco szybsze i łatwiejsze w obsłudze.
Tworzenie struktury RNN
Zanim zaczniemy przewidywać dane wyjściowe z któregokolwiek z naszych źródeł danych, musimy najpierw skonstruować RNN, a konstruowanie RNN przebiega tak samo, jak przy budowaniu zwykłej sieci neuronowej w poprzednim rozdziale. Poniżej znajduje się kod do zbudowania jednego -
from cntk.losses import squared_error
from cntk.io import CTFDeserializer, MinibatchSource, INFINITELY_REPEAT, StreamDefs, StreamDef
from cntk.learners import adam
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
BATCH_SIZE = 14 * 10
EPOCH_SIZE = 12434
EPOCHS = 10
Tyczenie wielu warstw
W CNTK możemy również układać wiele powtarzających się warstw. Na przykład możemy użyć następującej kombinacji warstw -
from cntk import sequence, default_options, input_variable
from cntk.layers import Recurrence, LSTM, Dropout, Dense, Sequential, Fold
features = sequence.input_variable(1)
with default_options(initial_state = 0.1):
model = Sequential([
Fold(LSTM(15)),
Dense(1)
])(features)
target = input_variable(1, dynamic_axes=model.dynamic_axes)
Jak widać na powyższym kodzie, mamy dwa sposoby na zamodelowanie RNN w CNTK -
Po pierwsze, jeśli chcemy tylko końcowego wyniku warstwy powtarzającej się, możemy użyć Fold warstwa w połączeniu z powtarzającą się warstwą, taką jak GRU, LSTM lub nawet RNNStep.
Po drugie, jako alternatywny sposób możemy również użyć Recurrence blok.
Szkolenie RNN z danymi szeregów czasowych
Po zbudowaniu modelu zobaczmy, jak możemy wytrenować RNN w CNTK -
from cntk import Function
@Function
def criterion_factory(z, t):
loss = squared_error(z, t)
metric = squared_error(z, t)
return loss, metric
loss = criterion_factory(model, target)
learner = adam(model.parameters, lr=0.005, momentum=0.9)
Teraz, aby załadować dane do procesu uczenia, musimy deserializować sekwencje z zestawu plików CTF. Poniższy kod ma rozszerzeniecreate_datasource funkcja, która jest użyteczną funkcją narzędzia do tworzenia zarówno szkolenia, jak i testowego źródła danych.
target_stream = StreamDef(field='target', shape=1, is_sparse=False)
features_stream = StreamDef(field='features', shape=1, is_sparse=False)
deserializer = CTFDeserializer(filename, StreamDefs(features=features_stream, target=target_stream))
datasource = MinibatchSource(deserializer, randomize=True, max_sweeps=sweeps)
return datasource
train_datasource = create_datasource('Training data filename.ctf')#we need to provide the location of training file we created from our dataset.
test_datasource = create_datasource('Test filename.ctf', sweeps=1) #we need to provide the location of testing file we created from our dataset.
Teraz, po skonfigurowaniu źródeł danych, modelu i funkcji utraty, możemy rozpocząć proces uczenia. Jest to dość podobne, jak w poprzednich sekcjach z podstawowymi sieciami neuronowymi.
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
target: train_datasource.streams.target
}
history = loss.train(
train_datasource,
epoch_size=EPOCH_SIZE,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config],
minibatch_size=BATCH_SIZE,
max_epochs=EPOCHS
)
Otrzymamy dane wyjściowe w następujący sposób -
Wyjście -
average since average since examples
loss last metric last
------------------------------------------------------
Learning rate per minibatch: 0.005
0.4 0.4 0.4 0.4 19
0.4 0.4 0.4 0.4 59
0.452 0.495 0.452 0.495 129
[…]
Walidacja modelu
W rzeczywistości przeredagowanie za pomocą RNN jest bardzo podobne do tworzenia prognoz w każdym innym modelu CNK. Jedyna różnica polega na tym, że musimy zapewnić sekwencje, a nie pojedyncze próbki.
Teraz, gdy nasz RNN jest w końcu zakończony treningiem, możemy zweryfikować model, testując go przy użyciu sekwencji kilku próbek w następujący sposób -
import pickle
with open('test_samples.pkl', 'rb') as test_file:
test_samples = pickle.load(test_file)
model(test_samples) * NORMALIZE
Wyjście -
array([[ 8081.7905],
[16597.693 ],
[13335.17 ],
...,
[11275.804 ],
[15621.697 ],
[16875.555 ]], dtype=float32)