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)