Introduction
Sequence prediction problems bestaan al een lange tijd. Ze worden beschouwd als een van de moeilijkste problemen om op te lossen in de data science industrie. Ze omvatten een breed scala aan problemen; van het voorspellen van verkopen tot het vinden van patronen in gegevens van aandelenmarkten, van het begrijpen van filmplots tot het herkennen van uw manier van spreken, van taalvertalingen tot het voorspellen van uw volgende woord op het toetsenbord van uw iPhone.
Met de recente doorbraken die hebben plaatsgevonden in de gegevenswetenschap, is gebleken dat voor bijna al deze sequentievoorspellingsproblemen Long Short Term Memory-netwerken, ook bekend als LSTM’s, zijn waargenomen als de meest effectieve oplossing.
LSTM’s hebben een voorsprong op conventionele feed-forward neurale netwerken en RNN op vele manieren. Dit komt door hun eigenschap om selectief patronen te onthouden gedurende lange perioden. Het doel van dit artikel is om LSTM’s uit te leggen en je in staat te stellen ze te gebruiken in problemen uit het echte leven. Laten we eens kijken!
Note: Om dit artikel door te nemen, moet je basiskennis hebben van neurale netwerken en hoe Keras (een deep learning-bibliotheek) werkt. U kunt de genoemde artikelen raadplegen om deze concepten te begrijpen:
- Understanding Neural Network From Scratch
- Fundamentals of Deep Learning – Introduction to Recurrent Neural Networks
- Tutorial: Optimaliseren van Neurale Netwerken met behulp van Keras (met casus Beeldherkenning)
Table of Contents
- Flashback: Een blik op Recurrente Neurale Netwerken (RNN)
- Beperkingen van RNNs
- Verbetering ten opzichte van RNN : Long Short Term Memory (LSTM)
- Architectuur van LSTM
- Vergeet Gate
- Input Gate
- Output Gate
- Tekstgeneratie met behulp van LSTM’s.
Flashback: Een blik op Recurrente Neurale Netwerken (RNN)
Neem een voorbeeld van sequentiële gegevens, dat kunnen de beursgegevens zijn van een bepaald aandeel. Een eenvoudig machine-leermodel of een kunstmatig neuraal netwerk kan leren de aandelenkoersen te voorspellen op basis van een aantal kenmerken: het volume van het aandeel, de openingswaarde, enz. Hoewel de prijs van het aandeel afhangt van deze kenmerken, is het ook grotendeels afhankelijk van de aandelenkoersen van de voorgaande dagen. In feite zijn voor een handelaar deze waarden in de voorgaande dagen (of de trend) een belangrijke beslissende factor voor voorspellingen.
In de conventionele feed-forward neurale netwerken worden alle testgevallen als onafhankelijk beschouwd. Dat wil zeggen dat bij de aanpassing van het model voor een bepaalde dag geen rekening wordt gehouden met de aandelenkoersen van de voorgaande dagen.
Deze afhankelijkheid van de tijd wordt bereikt via Recurrente Neurale Netwerken. Een typisch RNN ziet er als volgt uit:
Dit kan op het eerste gezicht intimiderend zijn, maar eenmaal ontvouwd ziet het er een stuk eenvoudiger uit:
Nu is het gemakkelijker voor ons om te visualiseren hoe deze netwerken rekening houden met de trend van de aandelenkoersen, alvorens de aandelenkoersen voor vandaag te voorspellen. Hier is elke voorspelling op tijdstip t (h_t) afhankelijk van alle voorgaande voorspellingen en de daaruit geleerde informatie.
RNN’s kunnen ons doel van sequentieverwerking voor een groot deel oplossen, maar niet helemaal. We willen dat onze computers goed genoeg zijn om Shakespeareaanse sonnetten te schrijven. Nu zijn RNN’s geweldig als het gaat om korte contexten, maar om in staat te zijn een verhaal op te bouwen en het te onthouden, moeten onze modellen in staat zijn de context achter de sequenties te begrijpen en te onthouden, net als een menselijk brein. Dit is niet mogelijk met een eenvoudig RNN.
Waarom? Laten we eens kijken.
Beperkingen van RNNs
Recurrente Neurale Netwerken werken prima wanneer we te maken hebben met korte-termijn afhankelijkheden. Dat is wanneer toegepast op problemen als:
RNNs blijken heel effectief te zijn. Dit komt omdat dit probleem niets te maken heeft met de context van de uitspraak. Het RNN hoeft zich niet te herinneren wat er eerder is gezegd, of wat de betekenis ervan was, het enige wat ze hoeven te weten is dat in de meeste gevallen de lucht blauw is. De voorspelling zou dus zijn:
Echter, vanilla RNNs begrijpen de context achter een input niet. Iets dat lang geleden is gezegd, kan niet worden herinnerd bij het maken van voorspellingen in het heden. Laten we dit als voorbeeld nemen:
Hieruit kunnen we opmaken dat, aangezien de auteur 20 jaar in Spanje heeft gewerkt, het zeer waarschijnlijk is dat hij een goede beheersing van het Spaans heeft. Maar om een goede voorspelling te kunnen doen, moet het RNN deze context onthouden. De relevante informatie kan worden gescheiden van het punt waar zij nodig is, door een enorme lading irrelevante gegevens. Dit is waar een Recurrent Neural Network faalt!
De reden hierachter is het probleem van de Vanishing Gradient. Om dit te begrijpen, moet je enige kennis hebben over hoe een feed-forward neuraal netwerk leert. We weten dat voor een conventioneel feed-forward neuraal netwerk, de gewichtsaanpassing die wordt toegepast op een bepaalde laag een veelvoud is van de leersnelheid, de foutterm van de vorige laag en de input voor die laag. De foutterm voor een bepaalde laag is dus ergens een product van de fouten van alle voorgaande lagen. Wanneer we te maken hebben met activeringsfuncties zoals de sigmoïde functie, worden de kleine waarden van de afgeleiden (die in de foutfunctie voorkomen) meerdere malen vermenigvuldigd naarmate we naar de beginlagen gaan. Als gevolg hiervan verdwijnt de gradiënt bijna als we naar de beginlagen gaan, en wordt het moeilijk om deze lagen te trainen.
Een soortgelijk geval wordt waargenomen bij Recurrente Neurale Netwerken. RNN onthoudt dingen slechts voor kleine tijdsduren, d.w.z. als wij de informatie na een kleine tijd nodig hebben, kan het reproduceerbaar zijn, maar zodra een heleboel woorden worden ingevoerd, gaat deze informatie ergens verloren. Dit probleem kan worden opgelost door een enigszins aangepaste versie van RNN’s toe te passen – de Long Short-Term Memory Networks.
Verbetering ten opzichte van RNN: LSTM (Long Short-Term Memory) Networks
Wanneer we onze agenda voor de dag indelen, prioriteren we onze afspraken toch? Als we ruimte moeten maken voor iets belangrijks, weten we welke afspraak geannuleerd kan worden voor een eventuele vergadering.
Het blijkt dat een RNN dat niet doet. Om een nieuwe informatie toe te voegen, transformeert het de bestaande informatie volledig door een functie toe te passen. Hierdoor wordt de informatie in zijn geheel gewijzigd, d.w.z. dat er geen rekening wordt gehouden met “belangrijke” informatie en “niet zo belangrijke” informatie.
LSTM’s daarentegen brengen kleine wijzigingen aan in de informatie door vermenigvuldigingen en toevoegingen. Bij LSTM’s stroomt de informatie door een mechanisme dat bekend staat als celtoestanden. Op deze manier kunnen LSTM’s selectief dingen onthouden of vergeten. De informatie in een bepaalde celtoestand heeft drie verschillende afhankelijkheden.
We zullen dit met een voorbeeld visualiseren. Laten we het voorbeeld nemen van het voorspellen van aandelenkoersen voor een bepaald aandeel. De koers van vandaag zal afhangen van:
- De trend die het aandeel in de voorgaande dagen heeft gevolgd, misschien een downtrend of een uptrend.
- De prijs van het aandeel op de vorige dag, omdat veel handelaren de prijs van het aandeel op de vorige dag vergelijken voordat ze het kopen.
- De factoren die van invloed kunnen zijn op de prijs van het aandeel voor vandaag. Dit kan een nieuw bedrijfsbeleid zijn dat breed wordt bekritiseerd, of een daling van de winst van het bedrijf, of misschien een onverwachte verandering in de hogere leiding van het bedrijf.
Deze afhankelijkheden kunnen voor elk probleem worden gegeneraliseerd als:
- De vorige celtoestand (d.w.z. de informatie die in het geheugen aanwezig was na de vorige tijdstap)
- De vorige verborgen toestand (d.w.z. dit is hetzelfde als de output van de vorige cel)
- De input bij de huidige tijdstap (d.w.z. de nieuwe informatie die op dat moment wordt ingevoerd)
Een ander belangrijk kenmerk van LSTM is de analogie met transportbanden!
Dat klopt!
Industrieën gebruiken ze om producten te verplaatsen voor verschillende processen. LSTM’s gebruiken dit mechanisme om informatie te verplaatsen.
Er kan informatie worden toegevoegd, gewijzigd of verwijderd terwijl het door de verschillende lagen stroomt, net zoals een product kan worden gegoten, geverfd of verpakt terwijl het op een transportband ligt.
Het volgende diagram verklaart de nauwe relatie tussen LSTM’s en lopende banden.
Bron
Hoewel dit diagram niet eens in de buurt komt van de werkelijke architectuur van een LSTM, lost het ons doel voor nu op.
Dankzij deze eigenschap van LSTM’s, waarbij zij niet de gehele informatie manipuleren maar deze eerder enigszins wijzigen, zijn zij in staat om dingen selectief te vergeten en te onthouden. Hoe ze dat doen, zullen we in het volgende hoofdstuk leren
Architectuur van LSTM’s
De werking van LSTM’s kan worden gevisualiseerd door het functioneren te begrijpen van het team van een nieuwszender dat een moordverhaal verslaat. Een nieuwsverhaal is opgebouwd uit feiten, bewijzen en verklaringen van veel mensen. Telkens wanneer zich een nieuwe gebeurtenis voordoet, neem je een van de drie stappen.
Laten we zeggen dat we ervan uitgingen dat de moord werd gepleegd door het slachtoffer te ‘vergiftigen’, maar het autopsierapport dat net binnenkwam, zei dat de doodsoorzaak ‘een klap op het hoofd’ was. Als lid van dit nieuwsteam, wat doe je dan? Je vergeet onmiddellijk de vorige doodsoorzaak en alle verhalen die rond dit feit waren geweven.
Wat, als er een geheel nieuwe verdachte in beeld wordt gebracht. Een persoon die wrok koesterde tegen het slachtoffer en de moordenaar zou kunnen zijn? Je zet deze informatie in je nieuwsfeed, toch?
Nu kunnen al deze gebroken stukjes informatie niet worden afgeserveerd op de reguliere media. Dus, na een bepaald tijdsinterval, moet u deze informatie samenvatten en de relevante dingen aan uw publiek geven. Misschien in de vorm van “XYZ blijkt de hoofdverdachte te zijn.”.
Nu gaan we in op de details van de architectuur van het LSTM netwerk:
Bron
Nu, dit komt in de verste verte niet in de buurt van de vereenvoudigde versie die we eerder zagen, maar laat me je er even doorheen leiden. Een typisch LSTM netwerk bestaat uit verschillende geheugenblokken die cellen
worden genoemd (de rechthoeken die we in de afbeelding zien). Er zijn twee toestanden die worden doorgegeven aan de volgende cel; de cel toestand en de verborgen toestand. De geheugenblokken zijn verantwoordelijk voor het onthouden van dingen en manipulaties aan dit geheugen worden gedaan door middel van drie belangrijke mechanismen, poorten genaamd. Elk van hen wordt hieronder besproken.
4.1 Forget Gate
Nemen we het voorbeeld van een tekstvoorspellingsprobleem. Laten we aannemen dat een LSTM wordt gevoed met de volgende zin:
Zodra de eerste punt na “persoon” wordt tegengekomen, realiseert de vergeetpoort zich dat er in de volgende zin sprake kan zijn van een verandering van context. Als gevolg daarvan wordt het onderwerp van de zin vergeten en wordt de plaats voor het onderwerp vrijgemaakt. En wanneer we gaan spreken over “Dan” wordt deze plaats van het onderwerp toegewezen aan “Dan”. Dit proces van het vergeten van het onderwerp wordt bewerkstelligd door de vergeetpoort.
Een vergeetpoort is verantwoordelijk voor het verwijderen van informatie uit de celtoestand. De informatie die niet langer nodig is voor het LSTM om dingen te begrijpen of de informatie die van minder belang is, wordt verwijderd via vermenigvuldiging van een filter. Dit is nodig voor het optimaliseren van de prestaties van het LSTM-netwerk.
Deze poort ontvangt twee ingangen; h_t-1 en x_t.
h_t-1 is de verborgen toestand van de vorige cel of de output van de vorige cel en x_t is de input op die bepaalde tijdstap. De gegeven inputs worden vermenigvuldigd met de gewichtsmatrices en er wordt een bias toegevoegd. Vervolgens wordt de sigmoïdfunctie op deze waarde toegepast. De sigmoïdfunctie levert een vector, met waarden van 0 tot 1, die overeenkomt met elk getal in de celtoestand. In principe is de sigmoid-functie verantwoordelijk voor de beslissing welke waarden worden behouden en welke worden weggegooid. Als een ‘0’ wordt uitgegeven voor een bepaalde waarde in de celtoestand, betekent dit dat de vergeetpoort wil dat de celtoestand dat stukje informatie volledig vergeet. Evenzo betekent een ‘1’ dat de vergeetpoort dat hele stukje informatie wil onthouden. Deze vector-output van de sigmoid-functie wordt vermenigvuldigd met de celtoestand.
4.2 Input Gate
Okee, laten we een ander voorbeeld nemen waarin de LSTM een zin analyseert:
Nu is de belangrijke informatie hier dat “Bob” kan zwemmen en dat hij vier jaar bij de marine heeft gediend. Dit kan worden toegevoegd aan de celstaat, maar het feit dat hij dit alles over de telefoon heeft verteld is een minder belangrijk feit en kan worden genegeerd. Dit proces van toevoegen van nieuwe informatie kan worden gedaan via de input gate.
Hier is de structuur:
De input gate is verantwoordelijk voor het toevoegen van informatie aan de celtoestand. Dit toevoegen van informatie gebeurt in principe in drie stappen, zoals te zien is in bovenstaand diagram.
- Reguleren welke waarden aan de celtoestand moeten worden toegevoegd door er een sigmoïde functie bij te betrekken. Dit lijkt in wezen sterk op de vergeetpoort en fungeert als een filter voor alle informatie uit h_t-1 en x_t.
- Het maken van een vector met alle mogelijke waarden die kunnen worden toegevoegd (zoals waargenomen uit h_t-1 en x_t) aan de celtoestand. Dit wordt gedaan met behulp van de tanh-functie, die waarden van -1 tot +1 uitvoert.
- Multiplicatie van de waarde van het reguleringsfilter (de sigmoid-gate) op de gecreëerde vector (de tanh-functie) en vervolgens het toevoegen van deze nuttige informatie aan de celtoestand via opteloperatie.
Als dit drie-stappen-proces is gedaan met, zorgen we ervoor dat alleen die informatie wordt toegevoegd aan de celtoestand die belangrijk is en niet overbodig is.
4.3 Uitvoerpoort
Niet alle informatie die langs de celtoestand loopt, is geschikt om op een bepaald moment te worden uitgevoerd. We zullen dit met een voorbeeld visualiseren:
In deze zin zouden er een aantal mogelijkheden kunnen zijn voor de lege ruimte. Maar we weten dat de huidige input van ‘moedig’, een bijvoeglijk naamwoord is dat wordt gebruikt om een zelfstandig naamwoord te beschrijven. Dus, welk woord ook volgt, heeft een sterke neiging om een zelfstandig naamwoord te zijn. En dus zou Bob een geschikte uitgang kunnen zijn.
Deze taak om nuttige informatie uit de huidige celtoestand te selecteren en die als uitgang weer te geven, wordt uitgevoerd via de uitgangspoort. Hier is de structuur ervan:
De werking van een output gate kan weer worden onderverdeeld in drie stappen:
- Het maken van een vector na toepassing van de tanh-functie op de celtoestand, waardoor de waarden worden geschaald naar het bereik -1 tot +1.
- Maken van een filter met gebruikmaking van de waarden van h_t-1 en x_t, zodanig dat het de waarden kan regelen die moeten worden uitgevoerd uit de hierboven gemaakte vector. Ook dit filter maakt gebruik van een sigmoid-functie.
- De waarde van dit reguleringsfilter vermenigvuldigen met de in stap 1 gecreëerde vector, en deze als output en tevens naar de verborgen toestand van de volgende cel sturen.
Het filter in bovenstaand voorbeeld zal er voor zorgen dat het alle andere waarden behalve ‘Bob’ vermindert. Het filter moet dus worden gebouwd op de invoer- en verborgen toestandswaarden en worden toegepast op de celtoestandsvector.
Tekstgeneratie met behulp van LSTM’s
We hebben genoeg gehad aan de theoretische concepten en de werking van LSTM’s. Nu zouden we proberen een model te bouwen dat een aantal n tekens kan voorspellen na de oorspronkelijke tekst van Macbeth. De meeste klassieke teksten zijn niet langer beschermd door copyright en kunnen hier gevonden worden. Een bijgewerkte versie van het .txt bestand is hier te vinden.
We zullen de bibliotheek Keras gebruiken, dat is een high-level API voor neurale netwerken en werkt bovenop TensorFlow of Theano. Zorg er dus voor dat je Keras geïnstalleerd en functioneel hebt voordat je in deze code duikt.
Okee, laten we wat tekst genereren!
-
Importeren van afhankelijkheden
# 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
We importeren alle vereiste afhankelijkheden en dit spreekt voor zich.
-
Tekstbestand laden en toewijzingen van tekens aan gehele getallen maken
# 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})
Het tekstbestand wordt geopend, en alle tekens worden omgezet in kleine letters. Om de volgende stappen te vergemakkelijken, wordt elk teken in een getal omgezet. Dit wordt gedaan om het rekengedeelte van de LSTM te vergemakkelijken.
-
Voorbereiding dataset
# 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)
De gegevens worden in een dusdanig formaat voorbereid dat als we willen dat de LSTM de ‘O’ in ‘HELLO’ voorspelt, we dit invoeren als invoer en als verwachte uitvoer. Op dezelfde manier bepalen we hier de lengte van de reeks die we willen (in het voorbeeld gesteld op 50) en slaan dan de coderingen op van de eerste 49 tekens in X en de verwachte uitvoer, d.w.z. het 50e teken in Y.
-
Herschalen van 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)
Een LSTM-netwerk verwacht dat de invoer de vorm heeft waarbij samples het aantal datapunten is dat we hebben, time steps het aantal tijdsafhankelijke stappen dat er in een enkel datapunt zit, features verwijst naar het aantal variabelen dat we hebben voor de corresponderende ware waarde in Y. Vervolgens schalen we de waarden in X_modified tussen 0 en 1 en één hete coderen onze ware waarden in Y_modified.
-
Het definiëren van het LSTM-model
# 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')
Een sequentieel model dat een lineaire stapeling van lagen is, wordt gebruikt. De eerste laag is een LSTM-laag met 300 geheugeneenheden en deze geeft sequenties terug. Dit wordt gedaan om ervoor te zorgen dat de volgende LSTM-laag sequenties ontvangt en niet slechts willekeurig verspreide gegevens. Na elke LSTM-laag wordt een dropout-laag toegepast om overfitting van het model te voorkomen. Tenslotte hebben we de laatste laag als een volledig aangesloten laag met een ‘softmax’-activering en neuronen gelijk aan het aantal unieke tekens, omdat we één warm gecodeerd resultaat moeten uitvoeren.
-
Passen van het model en genereren van tekens
# 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
Het model wordt gepast over 100 epochs, met een batchgrootte van 30. Vervolgens leggen we een willekeurig zaadje vast (voor gemakkelijke reproduceerbaarheid) en beginnen we met het genereren van tekens. De voorspelling van het model geeft de tekencodering van het voorspelde teken, het wordt dan terug gedecodeerd naar de tekenwaarde en bij het patroon gevoegd.
Zo zou de output van het netwerk eruit zien
Uiteindelijk, na genoeg trainingsepochs, zal het na verloop van tijd steeds betere resultaten geven. Dit is hoe u LSTM’s zou gebruiken om een sequentievoorspellingstaak op te lossen.
End Notes
LSTM’s zijn een veelbelovende oplossing voor sequentie- en tijdreeksgerelateerde problemen. Echter, het enige nadeel dat ik eraan vind, is de moeilijkheid om ze te trainen. Er gaat veel tijd en systeembronnen zitten in het trainen van zelfs een eenvoudig model. Maar dat is slechts een hardwarebeperking! Ik hoop dat ik erin geslaagd ben u een basiskennis van deze netwerken te geven. Als er problemen zijn met deze blog, kunt u hieronder reageren.