RNN (Recurrent Neural Network) Handledning: TensorFlow Exempel
Varför behöver vi ett återkommande neuralt nätverk (RNN)?
Recurrent Neural Network (RNN) låter dig modellera minnesenheter för att bevara data och modellera kortsiktiga beroenden. Det används också i tidsserieprognoser för identifiering av datakorrelationer och mönster. Det hjälper också till att producera prediktiva resultat för sekventiell data genom att leverera liknande beteende som en mänsklig hjärna.
Strukturen för ett artificiellt neuralt nätverk är relativt enkel och handlar främst om matrismultiplikation. Under det första steget multipliceras indata med initialt slumpmässiga vikter, och bias, transformeras med en aktiveringsfunktion och utgångsvärdena används för att göra en förutsägelse. Detta steg ger en uppfattning om hur långt nätverket är från verkligheten.
Det mått som används är förlusten. Ju högre förlustfunktion, desto dummare är modellen. För att förbättra kunskapen om nätverket krävs viss optimering genom att justera nätets vikter. Den stokastiska gradientnedgången är metoden som används för att ändra vikternas värden i rätt riktning. När justeringen är gjord kan nätverket använda ytterligare en sats data för att testa sin nya kunskap.
Felet är lyckligtvis lägre än tidigare, men ändå inte tillräckligt litet. Optimeringssteget görs iterativt tills felet minimeras, dvs ingen mer information kan extraheras.
Problemet med denna typ av modell är att den inte har något minne. Det betyder att ingången och utgången är oberoende. Modellen bryr sig med andra ord inte om vad som kom innan. Det väcker vissa frågor när du behöver förutsäga tidsserier eller meningar eftersom nätverket behöver ha information om historiska data eller tidigare ord.
För att lösa detta problem har en ny typ av arkitektur utvecklats: Recurrent Neural Network (RNN härefter)
Vad är ett återkommande neuralt nätverk (RNN)?
A Återkommande neurala nätverk (RNN) är en klass av Artificiellt neuralt nätverk där kopplingen mellan olika noder bildar en riktad graf för att ge ett temporalt dynamiskt beteende. Det hjälper till att modellera sekventiell data som härrör från feedforward-nätverk. Det fungerar på samma sätt som mänskliga hjärnor för att leverera prediktiva resultat.
Ett återkommande neuralt nätverk ser ganska likt ett traditionellt neuralt nätverk förutom att ett minnestillstånd läggs till neuronerna. Beräkningen för att inkludera ett minne är enkel.
Föreställ dig en enkel modell med bara en neuron som matas av en mängd data. I ett traditionellt neuralt nät producerar modellen utsignalen genom att multiplicera inmatningen med vikten och aktiveringsfunktionen. Med en RNN skickas denna utdata tillbaka till sig själv ett antal gånger. Vi ringer tidssteg den tid som utgången blir indata för nästa matrismultiplikation.
På bilden nedan kan du till exempel se att nätverket består av en neuron. Nätverket beräknar matrismultiplikationen mellan ingången och vikten och lägger till icke-linjäritet med aktiveringsfunktionen. Det blir utgången vid t-1. Denna utsignal är ingången för den andra matrismultiplikationen.
Nedan kodar vi en enkel RNN i TensorFlow för att förstå steget och även formen på utdata.
Nätverket består av:
- Fyra ingångar
- Sex neuroner
- 2-gångssteg
Nätverket kommer att fortsätta som bilden nedan visar.
Nätverket kallas "återkommande" eftersom det utför samma operation i varje aktiveringsruta. Nätverket beräknade vikterna av ingångarna och den tidigare utsignalen innan för att använda en aktiveringsfunktion.
import numpy as np import tensorflow as tf n_inputs = 4 n_neurons = 6 n_timesteps = 2 The data is a sequence of a number from 0 to 9 and divided into three batches of data. ## Data X_batch = np.array([ [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1 [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2 [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3 ])
Vi kan bygga nätverket med en platshållare för data, det återkommande steget och utdata.
- Definiera platshållaren för data
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
Här:
- Ingen: Okänd och kommer att ta storleken på batchen
- n_timesteps: Antal gånger nätverket kommer att skicka utdata tillbaka till neuronen
- n_inputs: Antal input per batch
- Definiera det återkommande nätverket
Som nämnts i bilden ovan består nätverket av 6 neuroner. Nätverket kommer att beräkna produkt med två punkter:
- Indata med den första uppsättningen vikter (dvs 6: lika med antalet neuroner)
- Tidigare utdata med en andra uppsättning vikter (dvs 6: motsvarande antalet utdata)
Observera att, under den första feedforwarden, är värdena för föregående utdata lika med nollor eftersom vi inte har något tillgängligt värde.
Objektet för att bygga ett RNN är tf.contrib.rnn.BasicRNNCell med argumentet num_units för att definiera antalet indata
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
Nu när nätverket är definierat kan du beräkna utdata och tillstånd
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Detta objekt använder en intern loop för att multiplicera matriserna ett lämpligt antal gånger.
Observera att den återkommande neuronen är en funktion av alla ingångar från de tidigare tidsstegen. Så bygger nätverket sitt eget minne. Informationen från föregående tid kan spridas i framtida tid. Detta är magin med återkommande neurala nätverk
## Define the shape of the tensor X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs]) ## Define the network basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) init = tf.global_variables_initializer() init = tf.global_variables_initializer() with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) print(states.eval(feed_dict={X: X_batch})) [[ 0.38941205 -0.9980438 0.99750966 0.7892596 0.9978241 0.9999997 ] [ 0.61096436 0.7255889 0.82977575 -0.88226104 0.29261455 -0.15597084] [ 0.62091285 -0.87023467 0.99729395 -0.58261937 0.9811445 0.99969864]]
I förklaringssyfte skriver du ut värdena för det tidigare tillståndet. Utmatningen ovan visar utdata från det senaste tillståndet. Skriv nu ut alla utdata, du kan märka att tillstånden är den föregående utmatningen för varje batch. Det vill säga, den föregående utdatan innehåller informationen om hela sekvensen.e
print(outputs_val) print(outputs_val.shape) [[[-0.75934666 -0.99537754 0.9735819 -0.9722234 -0.14234993 -0.9984044 ] [ 0.99975264 -0.9983206 0.9999993 -1. -0.9997506 -1. ]] [[ 0.97486496 -0.98773265 0.9969686 -0.99950117 -0.7092863 -0.99998885] [ 0.9326837 0.2673438 0.2808514 -0.7535883 -0.43337247 0.5700631 ]] [[ 0.99628735 -0.9998728 0.99999213 -0.99999976 -0.9884324 -1. ] [ 0.99962527 -0.9467421 0.9997403 -0.99999714 -0.99929446 -0.9999795 ]]] (3, 2, 6)
Utgången har formen av (3, 2, 6):
- 3: Antal partier
- 2: Tidsstegets nummer
- 6: Antal neuroner
Optimeringen av ett återkommande neuralt nätverk är identiskt med ett traditionellt neuralt nätverk. Du kommer att se mer i detalj hur du kodar optimering i nästa del av denna självstudie för återkommande neurala nätverk.
Tillämpningar av RNN
RNN har flera användningsområden, särskilt när det gäller att förutsäga framtiden. Inom finansbranschen kan RNN vara till hjälp för att förutsäga aktiekurser eller tecken på börsriktningen (dvs. positiv eller negativ).
RNN är användbart för en autonom bil eftersom det kan undvika en bilolycka genom att förutse fordonets bana.
RNN används flitigt inom textanalys, bildtextning, sentimentanalys och maskinöversättning. Till exempel kan man använda en filmrecension för att förstå känslan åskådaren upplevde efter att ha sett filmen. Att automatisera denna uppgift är mycket användbart när filmbolaget inte har tillräckligt med tid att granska, märka, konsolidera och analysera recensionerna. Maskinen kan göra jobbet med högre noggrannhet.
Begränsningar för RNN
I teorin är RNN tänkt att bära informationen upp till tider. Det är dock ganska utmanande att sprida all denna information när tidssteget är för långt. När ett nätverk har för många djupa lager blir det oträningsbart. Detta problem kallas: försvinnande gradientproblem. Om du kommer ihåg uppdaterar det neurala nätverket vikten med algoritmen för gradientnedstigning. Gradienterna blir mindre när nätverket går ner till lägre lager.
Sammanfattningsvis förblir gradienterna konstanta vilket innebär att det inte finns något utrymme för förbättringar. Modellen lär sig av en förändring i gradienten; denna förändring påverkar nätverkets utdata. Men om skillnaden i gradienten är för liten (dvs vikterna ändras lite) kan nätverket inte lära sig någonting och så resultatet. Därför kan ett nätverk som står inför ett försvinnande gradientproblem inte konvergera mot en bra lösning.
Förbättring LSTM
För att övervinna den potentiella frågan om försvinnande gradient som RNN står inför, förbättrade tre forskare, Hochreiter, Schmidhuber och Bengio RNN med en arkitektur som kallas Long Short-Term Memory (LSTM). I korthet ger LSMT nätverket relevant tidigare information till nyare tid. Maskinen använder en bättre arkitektur för att välja och föra information tillbaka till senare tid.
LSTM-arkitektur är tillgänglig i TensorFlow, tf.contrib.rnn.LSTMCell. LSTM är utanför handledningens omfattning. Du kan hänvisa till tjänstemannen dokumentation för mer information
RNN i tidsserier
I den här TensorFlow RNN-handledningen kommer du att använda en RNN med tidsseriedata. Tidsserier är beroende av tidigare tid, vilket innebär att tidigare värden inkluderar relevant information som nätverket kan lära sig av. Tanken bakom tidsserieprediktion är att uppskatta det framtida värdet av en serie, låt oss säga aktiekurs, temperatur, BNP och så vidare.
Dataförberedelsen för Keras RNN och tidsserier kan vara lite knepig. Först och främst är målet att förutsäga nästa värde i serien, vilket innebär att du kommer att använda den tidigare informationen för att uppskatta värdet till t + 1. Etiketten är lika med inmatningssekvensen och flyttas en period framåt. För det andra sätts antalet inmatningar till 1, dvs en observation per gång. Slutligen är tidssteget lika med sekvensen av det numeriska värdet. Om du till exempel ställer in tidssteget till 10 kommer inmatningssekvensen tillbaka tio gånger i följd.
Titta på grafen nedan, vi har representerat tidsseriedata till vänster och en fiktiv inmatningssekvens till höger. Du skapar en funktion för att returnera en datauppsättning med slumpmässigt värde för varje dag från januari 2001 till december 2016
# To plot pretty figures %matplotlib inline import matplotlib import matplotlib.pyplot as plt import pandas as pd def create_ts(start = '2001', n = 201, freq = 'M'): rng = pd.date_range(start=start, periods=n, freq=freq) ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum() return ts ts= create_ts(start = '2001', n = 192, freq = 'M') ts.tail(5)
Produktion
2016-08-31 -93.459631 2016-09-30 -95.264791 2016-10-31 -95.551935 2016-11-30 -105.879611 2016-12-31 -123.729319 Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222) # Left plt.figure(figsize=(11,4)) plt.subplot(121) plt.plot(ts.index, ts) plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance") plt.title("A time series (generated)", fontsize=14) # Right plt.subplot(122) plt.title("A training instance", fontsize=14) plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance") plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red') plt.legend(loc="upper left") plt.xlabel("Time") plt.show()
Den högra delen av grafen visar alla serier. Det började från 2001 och slutar 2019. Det är ingen mening att mata all data i nätverket, istället måste du skapa ett parti data med en längd lika med tidssteget. Denna batch kommer att vara X-variabeln. Y-variabeln är densamma som X men förskjuten med en period (dvs du vill prognostisera t+1).
Båda vektorerna har samma längd. Du kan se det i den högra delen av grafen ovan. Linjen representerar de tio värdena för X-inmatningen, medan de röda prickarna är de tio värdena för etiketten, Y. Observera att etiketten börjar en period före X och slutar en period efter.
Bygg ett RNN för att förutsäga tidsserier i TensorFlow
Nu i denna RNN-utbildning är det dags att bygga ditt första RNN för att förutsäga serien ovan. Du måste ange några hyperparametrar (modellens parametrar, dvs antal neuroner, etc.) för modellen:
- Antal ingångar: 1
- Tidssteg (fönster i tidsserier): 10
- Antal neuroner: 120
- Antal utdata: 1
Ditt nätverk kommer att lära sig av en sekvens på 10 dagar och innehålla 120 återkommande neuroner. Du matar modellen med en ingång, dvs en dag. Ändra gärna värdena för att se om modellen förbättrats.
Innan du konstruerar modellen måste du dela upp datauppsättningen i en tåguppsättning och testuppsättning. Den fullständiga datamängden har 222 datapunkter; du kommer att använda de första 201 poängen för att träna modellen och de sista 21 poängen för att testa din modell.
När du har definierat ett tåg- och testset måste du skapa ett objekt som innehåller satserna. I dessa partier har du X-värden och Y-värden. Kom ihåg att X-värdena är en period fördröjd. Därför använder du de första 200 observationerna och tidssteget är lika med 10. X_batches-objektet ska innehålla 20 batcher av storleken 10*1. y_batches har samma form som X_batches-objektet men med en punkt framåt.
Steg 1) Skapa tåget och testa
Först och främst konverterar du serien till en numpy array; sedan definierar du fönstren (dvs. antalet gånger nätverket kommer att lära sig av), antalet ingångar, utdata och storleken på tågsetet som visas i TensorFlow RNN-exemplet nedan.
series = np.array(ts) n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Efter det delar du helt enkelt upp arrayen i två datamängder.
## Split data train = series[:size_train] test = series[size_train:] print(train.shape, test.shape) (201,) (21,)
Steg 2) Skapa funktionen för att returnera X_batches och y_batches
För att göra det enklare kan du skapa en funktion som returnerar två olika arrayer, en för X_batches och en för y_batches.
Låt oss skriva en RNN TensorFlow-funktion för att konstruera satserna.
Observera att X-satserna är fördröjda med en period (vi tar värdet t-1). Funktionens utdata ska ha tre dimensioner. De första måtten motsvarar antalet partier, den andra storleken på fönstren och den sista är antalet inmatade.
Det knepiga är att välja datapunkterna korrekt. För X-datapunkterna väljer du observationerna från t = 1 till t =200, medan för Y-datapunkten returnerar du observationerna från t = 2 till 201. När du väl har rätt datapunkter är det enkelt att omforma serien.
För att konstruera objektet med satserna måste du dela upp datasetet i tio satser av samma längd (dvs. 20). Du kan använda omformningsmetoden och skicka -1 så att serien liknar batchstorleken. Värdet 20 är antalet observationer per batch och 1 är antalet indata.
Du måste göra samma steg utom för etiketten.
Observera att du måste flytta data till det antal gånger du vill prognostisera. Om du till exempel vill förutsäga en tid framåt, flyttar du serien med 1. Om du vill prognostisera två dagar, flytta sedan data med 2.
x_data = train[:size_train-1]: Select all the training instance minus one day X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1) def create_batches(df, windows, input, output): ## Create X x_data = train[:size_train-1] # Select the data X_batches = x_data.reshape(-1, windows, input) # Reshape the data ## Create y y_data = train[n_output:size_train] y_batches = y_data.reshape(-1, windows, output) return X_batches, y_batches
Nu när funktionen är definierad kan du anropa den för att skapa batcherna som visas i nedanstående RNN-exempel.
X_batches, y_batches = create_batches(df = train, windows = n_windows, input = n_input, output = n_output)
Du kan skriva ut formen för att se till att måtten är korrekta.
print(X_batches.shape, y_batches.shape) (10, 20, 1) (10, 20, 1)
Du behöver skapa testsetet med endast en batch data och 20 observationer.
Observera att du prognostiserar dagar efter dagar betyder att det andra förutsagda värdet kommer att baseras på det sanna värdet för den första dagen (t+1) i testdatauppsättningen. Faktum är att det verkliga värdet kommer att vara känt.
Om du vill prognostisera t+2 (dvs två dagar framåt), måste du använda det förutsagda värdet t+1; om du ska förutsäga t+3 (tre dagar framåt), måste du använda det förutsagda värdet t+1 och t+2. Det är logiskt att det är svårt att förutsäga exakt t+n dagar framåt.
X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1) print(X_test.shape, y_test.shape) (10, 20, 1) (10, 20, 1)
Okej, din batchstorlek är klar, du kan bygga RNN-arkitekturen. Kom ihåg att du har 120 återkommande neuroner.
Steg 3) Bygg modellen
För att skapa modellen måste du definiera tre delar:
- Variabeln med tensorerna
- RNN
- Förlusten och optimeringen
Steg 3.1) variabler
Du måste ange X- och y-variablerna med lämplig form. Detta steg är trivialt. Tensorn har samma dimension som objekten X_batches och y_batches.
Tensor X är till exempel en platshållare (kolla handledningen om Introduktion till Tensorflöde för att fräscha upp ditt sinne om variabeldeklaration) har tre dimensioner:
- Obs: satsens storlek
- n_windows: Fönstrens längd. dvs hur många gånger modellen tittar bakåt
- n_input: Antal ingångar
Resultatet är:
tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output])
Steg 3.2) Skapa RNN
I den andra delen av detta RNN TensorFlow-exempel måste du definiera nätverkets arkitektur. Som tidigare använder du objektet BasicRNNCell och dynamic_rnn från TensorFlow estimator.
## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Nästa del är lite knepigare men möjliggör snabbare beräkning. Du måste omvandla körutdata till ett tätt lager och sedan konvertera det igen för att ha samma dimension som indata.
stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Steg 3.3) Skapa förlusten och optimering
Modelloptimeringen beror på vilken uppgift du utför. I föregående handledning på CNN, ditt mål var att klassificera bilder, i denna RNN-handledning är målet något annorlunda. Du ombeds göra en förutsägelse på en kontinuerlig variabel jämfört med en klass.
Denna skillnad är viktig eftersom den kommer att förändra optimeringsproblemet. Optimeringsproblemet för en kontinuerlig variabel är att minimera medelkvadratfelet. För att konstruera dessa mätvärden i TF kan du använda:
- tf.reduce_sum(tf.square(utgångar – y))
Resten av RNN-koden är densamma som tidigare; du använder en Adam-optimerare för att minska förlusten (dvs. MSE):
- tf.train.AdamOptimizer(learning_rate=learning_rate)
- optimizer.minimize(förlust)
Det är allt, du kan packa ihop allt och din modell är redo att träna.
tf.reset_default_graph() r_neuron = 120 ## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) ## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output]) ## 3. Loss + optimization learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer()
Du kommer att träna modellen med 1500 epoker och skriva ut förlusten var 150:e iteration. När modellen är tränad utvärderar du modellen på testsetet och skapar ett objekt som innehåller förutsägelserna som visas i exemplet nedan för återkommande neurala nätverk.
iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test}) 0 MSE: 502893.34 150 MSE: 13839.129 300 MSE: 3964.835 450 MSE: 2619.885 600 MSE: 2418.772 750 MSE: 2110.5923 900 MSE: 1887.9644 1050 MSE: 1747.1377 1200 MSE: 1556.3398 1350 MSE: 1384.6113
Äntligen i denna RNN Deep Learning-handledning kan du plotta det faktiska värdet av serien med det förutsagda värdet. Om din modell är korrigerad bör de förutsagda värdena läggas ovanpå de faktiska värdena.
Som du kan se har modellen utrymme för förbättringar. Det är upp till dig att ändra hyperparametrarna som windows, batchstorleken på antalet återkommande neuroner.
plt.title("Forecast vs Actual", fontsize=14) plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green') plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red') plt.legend(loc="lower left") plt.xlabel("Time") plt.show()
Sammanfattning
Ett återkommande neuralt nätverk är en robust arkitektur för att hantera tidsserier eller textanalys. Utsignalen från det tidigare tillståndet är återkoppling för att bevara nätverkets minne över tid eller sekvens av ord.
I TensorFlow kan du använda följande koder för att träna ett TensorFlow Recurrent Neural Network för tidsserier:
Modellens parametrar
n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Definiera modellen
X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Konstruera optimeringen
learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss)
Träna modellen
init = tf.global_variables_initializer() iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test})