Introduction
Problemy z predykcją sekwencji istnieją od dawna. Są one uważane za jedne z najtrudniejszych problemów do rozwiązania w branży data science. Obejmują one szeroki zakres problemów; od przewidywania sprzedaży do znajdowania wzorców w danych giełdowych, od rozumienia fabuł filmowych do rozpoznawania sposobu mówienia, od tłumaczeń językowych do przewidywania następnego słowa na klawiaturze iPhone’a.
Z ostatnimi przełomami, które miały miejsce w data science, okazuje się, że dla prawie wszystkich tych problemów przewidywania sekwencji, sieci Long Short Term Memory, a.k.a LSTMs zostały zaobserwowane jako najbardziej skuteczne rozwiązanie.
LSTMs mają przewagę nad konwencjonalnymi sieciami neuronowymi feed-forward i RNN na wiele sposobów. Wynika to z ich właściwości selektywnego zapamiętywania wzorców na długie okresy czasu. Celem tego artykułu jest wyjaśnienie działania LSTM i umożliwienie wykorzystania ich w rzeczywistych problemach. Rzućmy okiem!
Uwaga: Aby przejść przez ten artykuł, musisz mieć podstawową wiedzę na temat sieci neuronowych i tego, jak działa Keras (biblioteka głębokiego uczenia). Możesz odnieść się do wspomnianych artykułów, aby zrozumieć te koncepcje:
- Understanding Neural Network From Scratch
- Fundamentals of Deep Learning – Introduction to Recurrent Neural Networks
- Tutorial: Optimizing Neural Networks using Keras (with Image recognition case study)
Table of Contents
- Flashback: A look into Recurrent Neural Networks (RNN)
- Limitations of RNNs
- Improvement over RNN : Long Short Term Memory (LSTM)
- Architektura LSTM
- Bramka zapomnienia
- Bramka wejściowa
- Bramka wyjściowa
- Generowanie tekstu przy użyciu LSTM.
Flashback: A look into Recurrent Neural Networks (RNN)
Przyjmij przykład danych sekwencyjnych, którymi mogą być dane giełdowe dla konkretnego waloru. Prosty model uczenia maszynowego lub sztucznej sieci neuronowej może nauczyć się przewidywać ceny akcji w oparciu o szereg cech: wolumen akcji, wartość otwarcia itp. Podczas gdy cena akcji zależy od tych cech, jest ona również w dużej mierze zależna od wartości akcji w poprzednich dniach. W rzeczywistości dla przedsiębiorcy, te wartości w poprzednich dniach (lub trend) jest jednym z głównych czynników decydujących o predictions.
W konwencjonalnych sieci neuronowych feed-forward, wszystkie przypadki testowe są uważane za niezależne. Oznacza to, że podczas dopasowywania modelu na konkretny dzień, nie bierze się pod uwagę cen akcji w poprzednich dniach.
Ta zależność od czasu jest osiągana przez rekurencyjne sieci neuronowe. Typowy RNN wygląda jak:
To może być onieśmielające na pierwszy rzut oka, ale po rozwinięciu, to wygląda dużo prostsze:
Teraz jest to łatwiejsze dla nas do wizualizacji, jak te sieci są biorąc pod uwagę trend cen akcji, przed przewidywaniem cen akcji na dziś. Tutaj każde przewidywanie w czasie t (h_t) jest zależne od wszystkich poprzednich przewidywań i informacji wyciągniętych z nich.
RNNs może rozwiązać nasz cel obsługi sekwencji w dużym stopniu, ale nie całkowicie. Chcemy, aby nasze komputery były wystarczająco dobre, aby pisać sonety Szekspira. Teraz RNN są świetne, jeśli chodzi o krótkie konteksty, ale aby móc zbudować historię i ją zapamiętać, potrzebujemy, aby nasze modele były w stanie zrozumieć i zapamiętać kontekst stojący za sekwencjami, tak jak ludzki mózg. Nie jest to możliwe w przypadku prostej RNN.
Dlaczego? Przyjrzyjmy się temu.
Ograniczenia RNN
Recurrent Neural Networks działają dobrze, gdy mamy do czynienia z krótkoterminowymi zależnościami. To znaczy, gdy zastosujemy je do problemów takich jak:
RNNs okazują się być całkiem skuteczne. Dzieje się tak dlatego, że problem ten nie ma nic wspólnego z kontekstem wypowiedzi. RNN nie muszą pamiętać, co było powiedziane przed tym, lub jakie było jego znaczenie, wszystko, co muszą wiedzieć, to że w większości przypadków niebo jest niebieskie. Zatem przewidywanie byłoby następujące:
Jednakże waniliowe RNN nie rozumieją kontekstu stojącego za danymi wejściowymi. Coś, co zostało powiedziane dawno temu, nie może być przywołane podczas tworzenia przewidywań w teraźniejszości. Zrozummy to na przykładzie:
Możemy zrozumieć, że skoro autor pracował w Hiszpanii przez 20 lat, jest bardzo prawdopodobne, że może posiadać dobrą znajomość języka hiszpańskiego. Jednak, aby dokonać właściwej predykcji, RNN musi pamiętać ten kontekst. Istotna informacja może być oddzielona od punktu, w którym jest potrzebna, przez ogromny ładunek nieistotnych danych. W tym miejscu sieć neuronowa rekurencyjna zawodzi!
Powodem tego jest problem zanikającego gradientu. Aby to zrozumieć, musisz mieć trochę wiedzy o tym, jak uczy się sieć neuronowa typu feed-forward. Wiemy, że dla konwencjonalnej sieci neuronowej typu feed-forward, aktualizacja wagi, która jest stosowana w danej warstwie jest wielokrotnością współczynnika uczenia, składnika błędu z poprzedniej warstwy i wejścia do tej warstwy. Tak więc, składnik błędu dla danej warstwy jest gdzieś iloczynem błędów wszystkich poprzednich warstw. Gdy mamy do czynienia z funkcjami aktywacji takimi jak funkcja sigmoidalna, małe wartości jej pochodnych (występujące w funkcji błędu) mnożą się wielokrotnie w miarę przesuwania się w kierunku warstw początkowych. W wyniku tego gradient prawie zanika w miarę przesuwania się w kierunku warstw początkowych, a trenowanie tych warstw staje się trudne.
Podobny przypadek obserwuje się w rekurencyjnych sieciach neuronowych. RNN zapamiętują rzeczy tylko dla małych okresów czasu, tzn. jeśli potrzebujemy informacji po małym czasie, może być ona odtwarzalna, ale gdy podamy wiele słów, informacja ta gdzieś się gubi. Problem ten można rozwiązać stosując nieco podrasowaną wersję RNN – Long Short-Term Memory Networks.
Ulepszenie w stosunku do RNN: LSTM (Long Short-Term Memory) Networks
Kiedy układamy nasz kalendarz na dany dzień, nadajemy priorytety naszym spotkaniom, prawda? Jeśli w przypadku, gdy musimy zrobić trochę miejsca na coś ważnego, wiemy, które spotkanie może być odwołane, aby pomieścić ewentualne spotkanie.
Okazuje się, że RNN tego nie robi. Aby dodać nową informację, całkowicie przekształca istniejącą informację poprzez zastosowanie funkcji. W związku z tym, cała informacja jest modyfikowana, w całości, tzn. nie są brane pod uwagę informacje „ważne” i „nie tak ważne”.
LSTM z drugiej strony, dokonują małych modyfikacji informacji poprzez mnożenie i dodawanie. W przypadku LSTM-ów informacja przepływa przez mechanizm znany jako stany komórek. W ten sposób LSTM-y mogą selektywnie zapamiętywać lub zapominać pewne rzeczy. Informacja w danym stanie komórki ma trzy różne zależności.
Wyobrazimy to na przykładzie. Weźmy przykład przewidywania cen akcji dla konkretnego waloru. Dzisiejsza cena akcji będzie zależała od:
- Tendencji, jaką podąża akcja w poprzednich dniach, może to być trend spadkowy lub trend wzrostowy.
- Ceny akcji w poprzednim dniu, ponieważ wielu handlowców porównuje cenę akcji z poprzedniego dnia przed jej zakupem.
- Czynników, które mogą wpłynąć na cenę akcji na dziś. To może być nowa polityka firmy, która jest powszechnie krytykowany, lub spadek zysku firmy, a może nieoczekiwana zmiana w starszym przywództwa firmy.
Zależności te można uogólnić na dowolny problem jako:
- Poprzedni stan komórki (tj. informacja, która była obecna w pamięci po poprzednim kroku czasowym)
- Poprzedni stan ukryty (tj. jest to to samo, co wyjście poprzedniej komórki)
- Wejście w bieżącym kroku czasowym (tj. nowa informacja, która jest wprowadzana w tym momencie)
Kolejną ważną cechą LSTM jest jej analogia do przenośników taśmowych!
To prawda!
Przemysł używa ich do przemieszczania produktów w różnych procesach. LSTMy używają tego mechanizmu do przenoszenia informacji.
Możemy mieć pewne dodawanie, modyfikowanie lub usuwanie informacji, gdy przepływa ona przez różne warstwy, tak jak produkt może być formowany, malowany lub pakowany, gdy znajduje się na przenośniku taśmowym.
Następujący diagram wyjaśnia ścisły związek LSTM i przenośników taśmowych.
Źródło
Pomimo, że ten diagram nie jest nawet bliski rzeczywistej architekturze LSTM, na razie rozwiązuje nasz cel.
Właśnie z powodu tej właściwości LSTM, gdzie nie manipulują one całą informacją, ale raczej lekko ją modyfikują, są w stanie selektywnie zapominać i zapamiętywać rzeczy. W jaki sposób to robią, tego dowiemy się w następnej sekcji?
Architektura LSTM
Funkcjonowanie LSTM można zwizualizować poprzez zrozumienie funkcjonowania zespołu kanału informacyjnego zajmującego się historią morderstwa. Wiadomości są zbudowane wokół faktów, dowodów i wypowiedzi wielu osób. Ilekroć nowe zdarzenie występuje podejmujesz jeden z trzech kroków.
Powiedzmy, że zakładaliśmy, że morderstwo zostało dokonane przez „zatrucie” ofiary, ale raport z autopsji, który właśnie przyszedł powiedział, że przyczyną śmierci było „uderzenie w głowę”. Będąc częścią tego zespołu informacyjnego, co robisz? Natychmiast zapominasz o poprzedniej przyczynie śmierci i wszystkich historiach, które były osnute wokół tego faktu.
Co, jeśli do obrazu zostaje wprowadzony zupełnie nowy podejrzany. Osoba, która miała pretensje do ofiary i mogła być mordercą? Wprowadzasz tę informację do swojego kanału informacyjnego, prawda?
Teraz wszystkie te zepsute kawałki informacji nie mogą być serwowane przez media głównego nurtu. Tak więc, po pewnym odstępie czasu, trzeba podsumować te informacje i wyjść istotne rzeczy do swoich odbiorców. Może w formie „XYZ okazuje się być głównym podejrzanym.”.
Teraz zajmijmy się szczegółami architektury sieci LSTM:
Źródło
Teraz nie jest to nigdzie blisko uproszczonej wersji, którą widzieliśmy wcześniej, ale pozwólcie, że was przez nią przeprowadzę. Typowa sieć LSTM składa się z różnych bloków pamięci zwanych komórkami
(prostokąty, które widzimy na obrazku). Istnieją dwa stany, które są przekazywane do następnej komórki; stan komórki i stan ukryty. Bloki pamięci są odpowiedzialne za zapamiętywanie rzeczy i manipulacje do tej pamięci odbywa się poprzez trzy główne mechanizmy, zwane bramkami. Każdy z nich jest omówiony poniżej.
4.1 Bramka zapomnienia
Przykładem może być problem predykcji tekstu. Załóżmy, że LSTM otrzymuje następujące zdanie:
Jak tylko napotka pierwszą kropkę po „osobie”, brama zapomnienia zdaje sobie sprawę, że w następnym zdaniu może nastąpić zmiana kontekstu. W wyniku tego podmiot zdania zostaje zapomniany, a miejsce dla podmiotu zostaje zwolnione. I kiedy zaczynamy mówić o „Danu”, ta pozycja podmiotu zostaje przypisana „Danowi”. Ten proces zapominania podmiotu jest wywoływany przez bramkę zapominania.
Bramka zapominania jest odpowiedzialna za usuwanie informacji ze stanu komórki. Informacje, które nie są już potrzebne LSTM do zrozumienia rzeczy lub informacje, które są mniej ważne są usuwane poprzez mnożenie filtra. Jest to wymagane do optymalizacji wydajności sieci LSTM.
Ta bramka przyjmuje dwa wejścia: h_t-1 i x_t.
h_t-1 jest stanem ukrytym z poprzedniej komórki lub wyjściem z poprzedniej komórki, a x_t jest wejściem w tym konkretnym kroku czasowym. Dane wejściowe mnożone są przez macierze wag i dodawany jest bias. Następnie na tę wartość nakładana jest funkcja sigmoidalna. Funkcja sigmoidalna wyprowadza wektor o wartościach z zakresu od 0 do 1, odpowiadający każdej liczbie w stanie komórki. Zasadniczo funkcja sigmoidalna jest odpowiedzialna za podejmowanie decyzji, które wartości zachować, a które odrzucić. Jeśli dla danej wartości w stanie komórki zostanie wyświetlone '0′, oznacza to, że bramka zapomnienia chce, aby stan komórki całkowicie zapomniał tę informację. Analogicznie, '1′ oznacza, że bramka zapomnienia chce zapamiętać całą tę informację. Ten wektor wyjściowy z funkcji sigmoidalnej jest mnożony do stanu komórki.
4.2 Bramka wejściowa
Dobrze, weźmy inny przykład, gdzie LSTM analizuje zdanie:
Teraz ważną informacją jest to, że „Bob” umie pływać i że służył w Marynarce Wojennej przez cztery lata. To może być dodane do stanu komórki, jednak fakt, że powiedział to wszystko przez telefon jest mniej ważnym faktem i może być zignorowany. Ten proces dodawania nowych informacji może być przeprowadzony przez bramkę wejściową.
Oto jej struktura:
Bramka wejściowa jest odpowiedzialna za dodawanie informacji do stanu komórki. To dodawanie informacji jest w zasadzie trzystopniowym procesem jak widać na powyższym diagramie.
- Regulowanie jakie wartości muszą być dodane do stanu komórki poprzez zaangażowanie funkcji sigmoidalnej. Jest to w zasadzie bardzo podobne do bramki zapomnienia i działa jak filtr dla wszystkich informacji z h_t-1 i x_t.
- Tworzenie wektora zawierającego wszystkie możliwe wartości, które mogą być dodane (jako postrzegane z h_t-1 i x_t) do stanu komórki. Służy do tego funkcja tanh, która wyprowadza wartości od -1 do +1.
- Multiplikacja wartości filtra regulacyjnego (bramka sigmoidalna) do utworzonego wektora (funkcja tanh), a następnie dodanie tej użytecznej informacji do stanu komórki poprzez operację dodawania.
Po wykonaniu tego trzyetapowego procesu zapewniamy, że do stanu komórki dodawane są tylko te informacje, które są ważne i nie są zbędne.
4.3 Bramka wyjściowa
Nie wszystkie informacje, które biegną wzdłuż stanu komórki, nadają się do wyprowadzenia w określonym czasie. Zobrazujemy to na przykładzie:
W tym zdaniu, może być wiele opcji dla pustego miejsca. Ale wiemy, że obecne wejście 'odważny’, jest przymiotnikiem, który jest używany do opisania rzeczownika. Tak więc, jakiekolwiek słowo następuje, ma silną tendencję do bycia rzeczownikiem. A zatem Bob może być trafnym wyjściem.
Ta praca polegająca na wybieraniu użytecznych informacji z bieżącego stanu komórki i pokazywaniu ich jako wyjście jest wykonywana przez bramkę wyjściową. Oto jej struktura:
Działanie bramki wyjściowej można ponownie rozłożyć na trzy kroki:
- Utworzenie wektora po zastosowaniu funkcji tanh do stanu komórki, a tym samym przeskalowanie wartości do zakresu -1 do +1.
- Utworzenie filtra wykorzystującego wartości h_t-1 i x_t, tak aby mógł regulować wartości, które muszą być wyprowadzone z wektora utworzonego powyżej. Ten filtr ponownie wykorzystuje funkcję sigmoidalną.
- Multiplikacja wartości tego filtra regulacyjnego do wektora utworzonego w kroku 1, i wysłanie go jako wyjście, a także do stanu ukrytego następnej komórki.
Filtr w powyższym przykładzie upewni się, że zmniejsza wszystkie inne wartości oprócz 'Bob’. Tak więc filtr musi być zbudowany na wartościach stanu wejściowego i ukrytego i być zastosowany na wektorze stanu komórki.
Generowanie tekstu przy użyciu LSTM
Mamy już dość teoretycznych koncepcji i działania LSTM. Teraz próbowalibyśmy zbudować model, który będzie w stanie przewidzieć jakąś n liczbę znaków po oryginalnym tekście Makbeta. Większość klasycznych tekstów nie jest już chroniona prawem autorskim i można je znaleźć tutaj. Zaktualizowaną wersję pliku .txt można znaleźć tutaj.
Będziemy korzystać z biblioteki Keras, która jest wysokopoziomowym API dla sieci neuronowych i działa na szczycie TensorFlow lub Theano. Upewnij się więc, że przed zanurzeniem się w tym kodzie masz zainstalowane i funkcjonalne Keras.
Okay, więc wygenerujmy trochę tekstu!
-
Importowanie zależności
# Importing dependencies numpy and kerasimport numpyfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.utils import np_utils
Importujemy wszystkie wymagane zależności i jest to całkiem zrozumiałe.
-
Wczytanie pliku tekstowego i tworzenie mapowania znaków na liczby całkowite
# load textfilename = "/macbeth.txt"text = (open(filename).read()).lower()# mapping characters with integersunique_chars = sorted(list(set(text)))char_to_int = {}int_to_char = {}for i, c in enumerate (unique_chars): char_to_int.update({c: i}) int_to_char.update({i: c})
Plik tekstowy jest otwarty, a wszystkie znaki są konwertowane na małe litery. Aby ułatwić następujące kroki, będziemy mapować każdy znak do odpowiedniej liczby. Robimy to, by ułatwić część obliczeniową LSTM.
-
Przygotowanie zbioru danych
# preparing input and output datasetX = Y = for i in range(0, len(text) - 50, 1): sequence = text label =text X.append( for char in sequence]) Y.append(char_to_int)
Dane są przygotowane w takim formacie, że jeśli chcemy, by LSTM przewidział 'O’ w 'HELLO’, podamy je jako wejście i jako oczekiwane wyjście. Podobnie tutaj, ustalamy długość sekwencji, którą chcemy uzyskać (w przykładzie ustawioną na 50), a następnie zapisujemy kodowanie pierwszych 49 znaków w X oraz oczekiwane wyjście, czyli 50-ty znak w Y.
-
Kształtowanie X
# reshaping, normalizing and one hot encodingX_modified = numpy.reshape(X, (len(X), 50, 1))X_modified = X_modified / float(len(unique_chars))Y_modified = np_utils.to_categorical(Y)
Sieć LSTM oczekuje, że dane wejściowe będą miały postać, gdzie próbki to liczba punktów danych, które mamy, kroki czasowe to liczba kroków zależnych od czasu, które są w pojedynczym punkcie danych, cechy odnoszą się do liczby zmiennych, które mamy dla odpowiadającej prawdziwej wartości w Y. Następnie skalujemy wartości w X_modified pomiędzy 0 a 1 i jedną gorącą kodujemy nasze prawdziwe wartości w Y_modified.
-
Definiowanie modelu LSTM
# defining the LSTM modelmodel = Sequential()model.add(LSTM(300, input_shape=(X_modified.shape, X_modified.shape), return_sequences=True))model.add(Dropout(0.2))model.add(LSTM(300))model.add(Dropout(0.2))model.add(Dense(Y_modified.shape, activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam')
Używamy modelu sekwencyjnego, który jest liniowym stosem warstw. Pierwszą warstwą jest warstwa LSTM z 300 jednostkami pamięci i zwraca ona sekwencje. Ma to na celu zapewnienie, że następna warstwa LSTM otrzymuje sekwencje, a nie tylko losowo rozrzucone dane. Po każdej warstwie LSTM stosowana jest warstwa dropout, aby uniknąć przepasowania modelu. Wreszcie, mamy ostatnią warstwę jako w pełni połączoną warstwę z aktywacją 'softmax’ i neuronami równymi liczbie unikalnych znaków, ponieważ musimy wyprowadzić jeden gorący zakodowany wynik.
-
Dopasowanie modelu i generowanie znaków
# fitting the modelmodel.fit(X_modified, Y_modified, epochs=1, batch_size=30)# picking a random seedstart_index = numpy.random.randint(0, len(X)-1)new_string = X# generating charactersfor i in range(50): x = numpy.reshape(new_string, (1, len(new_string), 1)) x = x / float(len(unique_chars)) #predicting pred_index = numpy.argmax(model.predict(x, verbose=0)) char_out = int_to_char seq_in = for value in new_string] print(char_out) new_string.append(pred_index) new_string = new_string
Model jest dopasowywany przez 100 epok, z rozmiarem serii 30. Następnie ustalamy losowy seed (dla łatwej powtarzalności) i rozpoczynamy generowanie znaków. Przewidywanie z modelu daje kodowanie znaku przewidywanego znaku, jest on następnie dekodowany z powrotem do wartości znaku i dołączany do wzorca.
Tak wyglądałoby wyjście sieci
W końcu, po wystarczającej ilości epok treningowych, będzie ona dawać coraz lepsze wyniki w czasie. W ten sposób można użyć LSTM do rozwiązania zadania przewidywania sekwencji.
Uwagi końcowe
LSTM są bardzo obiecującym rozwiązaniem dla problemów związanych z sekwencjami i szeregami czasowymi. Jednak jedyną wadą, jaką w nich znajduję, jest trudność w ich trenowaniu. Dużo czasu i zasobów systemowych idzie na szkolenie nawet prostego modelu. Ale to jest tylko ograniczenie sprzętowe! Mam nadzieję, że udało mi się przybliżyć Ci podstawowe pojęcie o tych sieciach. W przypadku jakichkolwiek problemów lub kwestii związanych z blogiem, zapraszam do komentowania poniżej.