博文的翻譯和實踐:
python
一個強大而流行的循環神經網絡(RNN)的變種是長短時間模型網絡(LSTM)。算法
它使用普遍,由於它的架構克服了困擾着全部週期性的神經網絡梯度消失
和梯度爆炸
的問題,容許建立很是大的、很是深的網絡。sql
與其餘週期性的神經網絡同樣,LSTM網絡保持狀態,在keras框架中實現這一點的細節可能會使人困惑。數組
在這篇文章中,您將會確切地瞭解到在LSTM網絡中,如何在LSTM深度學習庫中維護狀態。ruby
本文在一個很簡單的例子上說明lstm的使用和lstm的特色,經過對這個簡化例子的理解,能夠幫助咱們對通常的序列預測問題和序列預測問題有更高的理解和使用。
用到的庫:Keras 2.0.2
,TensorFlow 1.0.1
和Theano 0.9.0.
bash
在本教程中,咱們將開發和對比許多不一樣的LSTM循環神經網絡模型。網絡
這些比較的背景是學習字母表的一個簡單的序列預測問題。也就是說,根據字母表的字母,能夠預測字母表的下一個字母。架構
這是一個簡單的序列預測問題,一旦被理解,就能夠被推廣到其餘的序列預測問題,如時間序列預測和序列分類。app
讓咱們用一些python代碼來準備這個問題,咱們能夠從示例中重用這些代碼。框架
首先,讓咱們導入本教程中計劃使用的全部類和函數。
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
接下來,咱們能夠對隨機數生成器選定隨機數種子,以確保每次執行代碼時結果都是相同的。
# fix random seed for reproducibility
numpy.random.seed(7)
咱們如今能夠定義咱們的數據集,字母表。爲了便於閱讀,咱們用大寫字母來定義字母表。
神經網絡是對數字建模,所以咱們須要將字母表中的字母映射到整數值(把字母映射爲數字)。咱們能夠很容易地經過建立字母索引的字典(map)到字符。咱們還能夠建立一個反向查找,以便將預測轉換回字符,以便稍後使用。
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
如今咱們須要建立咱們的輸入和輸出鍵值對來訓練咱們的神經網絡。咱們能夠經過定義輸入序列長度,而後從輸入字母序列中讀取序列來實現這一點。
例如,咱們使用的輸入長度是1。從原始輸入數據的開始,咱們能夠讀出第一個字母A和下一個字母「B」。咱們沿着一個字符移動,直到咱們到達一個「Z」的預測。
咱們先創造這樣一個數據集,用一個字母,來預測下一個字母是什麼。
# prepare the dataset of input to output pairs encoded as integers
seq_length = 1
dataX = []
dataY = []
for i in range(0, len(alphabet) - seq_length, 1):
seq_in = alphabet[i:i + seq_length]
seq_out = alphabet[i + seq_length]
dataX.append([char_to_int[char] for char in seq_in])
dataY.append(char_to_int[seq_out])
print seq_in, '->', seq_out
咱們運行上面的代碼,來觀察如今咱們的input和output數據集是這樣一種狀況
A -> B
B -> C
C -> D
D -> E
E -> F
F -> G
G -> H
H -> I
I -> J
J -> K
K -> L
L -> M
M -> N
N -> O
O -> P
P -> Q
Q -> R
R -> S
S -> T
T -> U
U -> V
V -> W
W -> X
X -> Y
Y -> Z
input是一個一個字母的序列,output是一個一個的序列。
ok,就在這樣的數據集上來應用咱們的lstm。看看會有什麼結果?
這時候dataX是一個一個用字母組成的序列,可是還要轉換一下格式,才能用到keras上。咱們須要將NumPy數組從新構造爲LSTM網絡所指望的格式,即[samples示例, time steps時間步數, features特徵]。
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
而後咱們須要把咱們的整數值歸一化到0~1的區間上,這是LSTM網絡使用的s形激活函數(sigmoid)的範圍。
# normalize
X = X / float(len(alphabet))
最後,咱們能夠把這個問題看做是一個序列分類任務,其中26個字母表明一個不一樣的類。所以,咱們用keras的內置的 to_categorical()函數把輸出output(y)進行 one-hot編碼(one-hot
指n維單位向量a=(0,…,0,1,0,…,0))做爲輸出層的結果。
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
如今咱們已經準備好去訓練不一樣的LSTM模型了。
讓咱們從設計一個簡單的LSTM開始,學習如何根據一個字符的上下文來預測字母表中的下一個字符。
咱們將定義這個問題爲:一些單字母的隨機集合做爲輸入,另外一些單字母做爲輸出,由輸入輸出對組成。正如咱們所看到的,這對於LSTM來講是一個很難用來學習的結構。
讓咱們定義一個LSTM網絡,它有32個單元(the LSTM units are the 「memory units」 or you can just call them the neurons.),一個輸出層,其中有一個softmax的激活函數來進行預測。因爲這是一個多類分類問題,因此咱們可使用在Keras中使用對數損失函數(稱爲「分類交叉熵」(categorical_crossentropy)),並使用ADAM優化函數對網絡進行優化。
該模型以500批次(epochs),每批次數據輸入大小(batch)爲1的形式訓練
咱們經過lstm在這個問題上的預測,會發現這對lstm循環網絡來講是很難解決的問題。
keras上LSTM用於上述問題的代碼以下:
# create and fit the model model = Sequential() model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2]))) model.add(Dense(y.shape[1], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(X, y, nb_epoch=500, batch_size=1, verbose=2)
在咱們訓練模型以後,咱們能夠對整個訓練集的性能進行評估和總結。
# summarize performance of the model
scores = model.evaluate(X, y, verbose=0)
print("Model Accuracy: %.2f%%" % (scores[1]*100))
而後,咱們能夠經過網絡從新運行訓練數據,並生成預測,將輸入和輸出對轉換回原來的字符格式,以得到關於網絡如何瞭解問題的視覺效果。
# demonstrate some model predictions
for pattern in dataX:
x = numpy.reshape(pattern, (1, len(pattern), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
咱們能夠看到,這個問題對於網絡來講確實是很困難的。
緣由是可憐的lstm單元根本沒有能夠利用的上下文章信息。
每一個輸入輸出模式都以隨機的順序顯示在網絡中,而且網絡的狀態在每一個模式以後被重置(每一個批處理的每一個批次包含一個模式)。
這是對LSTM網絡架構的濫用,由於咱們把它看成了一個標準的多層感知器。
接下來,讓咱們嘗試一個不一樣的問題框架,以便爲網絡提供更多的序列來學習。
在多層感知器中添加更多上下文最流行的方法是特徵窗口方法(Feature Window method)。
即序列中的前面步驟的輸出被做爲附加的輸入特性提供給網絡。咱們能夠用相同的技巧,爲LSTM網絡提供更多的上下文。
在這裏,咱們將序列長度從1增長到3,例如:
咱們把輸入從一個字符升到三個字符。
# prepare the dataset of input to output pairs encoded as integers
seq_length = 3
就像這樣:
ABC -> D
BCD -> E
CDE -> F
而後將序列中的每一個元素做爲網絡的一個新輸入特性提供給它。這須要修改輸入序列在數據準備步驟中的reshape:
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), 1, seq_length))
還須要對示例模式的reshape進行修改,以展現模型的預測結果。
x = numpy.reshape(pattern, (1, 1, len(pattern)))
所有的代碼以下:
# Naive LSTM to learn three-char window to one-char mapping
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
# fix random seed for reproducibility
numpy.random.seed(7)
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# prepare the dataset of input to output pairs encoded as integers
seq_length = 3
dataX = []
dataY = []
for i in range(0, len(alphabet) - seq_length, 1):
seq_in = alphabet[i:i + seq_length]
seq_out = alphabet[i + seq_length]
dataX.append([char_to_int[char] for char in seq_in])
dataY.append(char_to_int[seq_out])
print seq_in, '->', seq_out
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), 1, seq_length))
# normalize
X = X / float(len(alphabet))
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
# create and fit the model
model = Sequential()
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, epochs=500, batch_size=1, verbose=2)
# summarize performance of the model
scores = model.evaluate(X, y, verbose=0)
print("Model Accuracy: %.2f%%" % (scores[1]*100))
# demonstrate some model predictions
for pattern in dataX:
x = numpy.reshape(pattern, (1, 1, len(pattern)))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
運行結果以下:
Model Accuracy: 86.96%
['A', 'B', 'C'] -> D
['B', 'C', 'D'] -> E
['C', 'D', 'E'] -> F
['D', 'E', 'F'] -> G
['E', 'F', 'G'] -> H
['F', 'G', 'H'] -> I
['G', 'H', 'I'] -> J
['H', 'I', 'J'] -> K
['I', 'J', 'K'] -> L
['J', 'K', 'L'] -> M
['K', 'L', 'M'] -> N
['L', 'M', 'N'] -> O
['M', 'N', 'O'] -> P
['N', 'O', 'P'] -> Q
['O', 'P', 'Q'] -> R
['P', 'Q', 'R'] -> S
['Q', 'R', 'S'] -> T
['R', 'S', 'T'] -> U
['S', 'T', 'U'] -> V
['T', 'U', 'V'] -> Y
['U', 'V', 'W'] -> Z
['V', 'W', 'X'] -> Z
['W', 'X', 'Y'] -> Z
咱們發現有了一點點的提高,可是這一點點的提高未必是真的,梯度降低算法原本就是具備隨機性的。
也就是說咱們再一次的錯誤使用了lstm循環神經網絡。
咱們確實給了上下文,可是並非合適的方式,
實際上,字母序列A-B-C纔是一個特徵的timesteps,而不是單獨ABC一個特徵的timestep
咱們已經給網絡提供了更多的上下文,但並無像預期的那樣有更多的順序。
在下一節中,咱們將以timesteps的形式爲網絡提供更多的上下文。
在keras中,利用lstm的關鍵是以時間序列(time steps)的方法來提供上下文,而不是像其餘網絡結構(CNN)同樣,經過windowed features的方式。
此次咱們仍是採用這樣的訓練方式
seq_length = 3
輸入輸出對(input-output pairs)
ABC -> D
BCD -> E
CDE -> F
DEF -> G
咱們此次惟一改變的地方是下面這裏:
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
timesteps這個參數,咱們設置了3,而不是前面的1。
不一樣之處是,對輸入數據的reshape是將輸入序列做爲一個特性的time step序列,而不是多個特性的單一time step。
也就是說咱們把ABC 當作獨立的一個特徵組成的多個時間序列,而不是把ABC當作一個多個特徵組成一個時間序列。
這就是keras中LSTM循環神經網絡的正確打開的方式。
個人理解是,這樣在訓練 ABC——D的時候,BCD,CDE,均可以發揮做用。而最開始那種使用方法,只是利用了ABC——D這樣一個訓練樣本。
完整代碼以下:
# Naive LSTM to learn three-char time steps to one-char mapping
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
# fix random seed for reproducibility
numpy.random.seed(7)
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# prepare the dataset of input to output pairs encoded as integers
seq_length = 3
dataX = []
dataY = []
for i in range(0, len(alphabet) - seq_length, 1):
seq_in = alphabet[i:i + seq_length]
seq_out = alphabet[i + seq_length]
dataX.append([char_to_int[char] for char in seq_in])
dataY.append(char_to_int[seq_out])
print seq_in, '->', seq_out
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
# normalize
X = X / float(len(alphabet))
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
# create and fit the model
model = Sequential()
model.add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, nb_epoch=500, batch_size=1, verbose=2)
# summarize performance of the model
scores = model.evaluate(X, y, verbose=0)
print("Model Accuracy: %.2f%%" % (scores[1]*100))
# demonstrate some model predictions
for pattern in dataX:
x = numpy.reshape(pattern, (1, len(pattern), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
最終的訓練結果是
Model Accuracy: 100.00%
['A', 'B', 'C'] -> D
['B', 'C', 'D'] -> E
['C', 'D', 'E'] -> F
['D', 'E', 'F'] -> G
['E', 'F', 'G'] -> H
['F', 'G', 'H'] -> I
['G', 'H', 'I'] -> J
['H', 'I', 'J'] -> K
['I', 'J', 'K'] -> L
['J', 'K', 'L'] -> M
['K', 'L', 'M'] -> N
['L', 'M', 'N'] -> O
['M', 'N', 'O'] -> P
['N', 'O', 'P'] -> Q
['O', 'P', 'Q'] -> R
['P', 'Q', 'R'] -> S
['Q', 'R', 'S'] -> T
['R', 'S', 'T'] -> U
['S', 'T', 'U'] -> V
['T', 'U', 'V'] -> W
['U', 'V', 'W'] -> X
['V', 'W', 'X'] -> Y
['W', 'X', 'Y'] -> Z
它已經學會了用字母表中的三個字母來預測下一個字母的順序。它能夠顯示字母表中的任意三個字母的隨機序列,並預測下一個字母。
咱們尚未展現出循環神經網絡的強大之處,由於上面這個問題咱們用多層感知器,足夠多的神經元,足夠多的迭代次數也能夠很好的解決。(三層神經網絡擬合任意能夠表示的函數)
LSTM網絡是有狀態的。它們應該可以學習整個字母表序列,可是在默認狀況下,keras在每次訓練以後從新設置網絡狀態。
那麼接下來就是展現循環神經網絡的獨到之處!!
keras實現的LSTM在每個batch之後,都重置了LSTM的狀態。
這代表,若是咱們的批處理大小足夠容納全部輸入模式,若是全部輸入模式都按順序排序,LSTM就可使用序列中的序列上下文來更好地學習序列。
經過修改第一個示例來學習一對一映射,並將批處理大小從1增長到訓練數據集的大小,咱們能夠很容易地演示這一點。
此外,在每一個epoch前,keras都重置了訓練數據集。爲了確保訓練數據模式保持順序,咱們能夠禁用這種洗牌。
model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False)
該網絡將使用 within-batch批序列來學習字符的映射,但在進行預測時,這個上下文將沒法用於網絡。咱們能夠對網絡進行評估,以肯定網絡在隨機序列和順序序列的預測能力。
完整代碼以下:
Naive LSTM to learn one-char to one-char mapping with all data in each batch
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
from keras.preprocessing.sequence import pad_sequences
# fix random seed for reproducibility
numpy.random.seed(7)
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# prepare the dataset of input to output pairs encoded as integers
seq_length = 1
dataX = []
dataY = []
for i in range(0, len(alphabet) - seq_length, 1):
seq_in = alphabet[i:i + seq_length]
seq_out = alphabet[i + seq_length]
dataX.append([char_to_int[char] for char in seq_in])
dataY.append(char_to_int[seq_out])
print seq_in, '->', seq_out
# convert list of lists to array and pad sequences if needed
X = pad_sequences(dataX, maxlen=seq_length, dtype='float32')
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (X.shape[0], seq_length, 1))
# normalize
X = X / float(len(alphabet))
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
# create and fit the model
model = Sequential()
model.add(LSTM(16, input_shape=(X.shape[1], X.shape[2])))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False)
# summarize performance of the model
scores = model.evaluate(X, y, verbose=0)
print("Model Accuracy: %.2f%%" % (scores[1]*100))
# demonstrate some model predictions
for pattern in dataX:
x = numpy.reshape(pattern, (1, len(pattern), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
# demonstrate predicting random patterns
print "Test a Random Pattern:"
for i in range(0,20):
pattern_index = numpy.random.randint(len(dataX))
pattern = dataX[pattern_index]
x = numpy.reshape(pattern, (1, len(pattern), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
結果:
Model Accuracy: 100.00%
['A'] -> B
['B'] -> C
['C'] -> D
['D'] -> E
['E'] -> F
['F'] -> G
['G'] -> H
['H'] -> I
['I'] -> J
['J'] -> K
['K'] -> L
['L'] -> M
['M'] -> N
['N'] -> O
['O'] -> P
['P'] -> Q
['Q'] -> R
['R'] -> S
['S'] -> T
['T'] -> U
['U'] -> V
['V'] -> W
['W'] -> X
['X'] -> Y
['Y'] -> Z
Test a Random Pattern:
['T'] -> U
['V'] -> W
['M'] -> N
['Q'] -> R
['D'] -> E
['V'] -> W
['T'] -> U
['U'] -> V
['J'] -> K
['F'] -> G
['N'] -> O
['B'] -> C
['M'] -> N
['F'] -> G
['F'] -> G
['P'] -> Q
['A'] -> B
['K'] -> L
['W'] -> X
['E'] -> F
正如咱們所指望的那樣,網絡可以使用 within-sequence的上下文來學習字母表,在訓練數據上達到100%的準確率。
重要的是,該網絡能夠對隨機選擇的字符的下一個字母進行準確的預測。很是使人印象深入。
咱們已經看到,咱們能夠將原始數據拆分爲固定大小的序列,而且這種表示能夠由LSTM來學習,且只須要學習3個字符到1個字符的隨機映射。
咱們也看到,咱們能夠對批量的大小進行限制,爲網絡提供更多的序列,但只有在訓練期間才行。
理想狀況下,咱們但願將網絡公開給整個序列,並讓它學習相互依賴關係,而不是在問題的框架中明確地定義這些依賴關係。
咱們能夠在keras中作到這一點,經過使LSTM層擁有狀態,並在epoch結束時手動從新設置網絡的狀態,這時也結束了訓練整個序列的過程。
這纔是LSTM網絡的真正用途。咱們發現,若是容許網絡自己學習字符之間的依賴關係,咱們只須要一個更小的網絡(一半的單位數量)和更少的訓練期(幾乎是一半)。
首先咱們須要將LSTM層定義爲有狀態的。這樣作的話,咱們必須顯式地指定批大小做爲輸入形狀的一個維度。這也意味着當咱們評估網絡或用網絡進行預測時,咱們也必須指定並遵照相同的批大小。如今這不是問題,由於咱們使用的是批大小的1。這可能會在預測的時候帶來困難,由於當批大小不是1時,預測須要按批進行和按順序進行。
batch_size = 1
model.add(LSTM(16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
訓練有狀態的LSTM的一個重要區別是,咱們每次都手動地訓練它,而且在每一個時代以後從新設置狀態。咱們能夠在for循環中這樣作。一樣,咱們不會對輸入進行洗牌,保留輸入訓練數據建立的順序。
for i in range(300):
model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False)
model.reset_states()
如前所述,在評估整個培訓數據集的網絡性能時,咱們指定批處理大小。
# summarize performance of the model
scores = model.evaluate(X, y, batch_size=batch_size, verbose=0)
model.reset_states()
print("Model Accuracy: %.2f%%" % (scores[1]*100))
最後,咱們能夠證實網絡確實學會了整個字母表。咱們能夠用第一個字母A「A」來作輸入,得到一個預測,把預測做爲輸入反饋給它,而後把這個過程一直重複到「Z」。
# demonstrate some model predictions
seed = [char_to_int[alphabet[0]]]
for i in range(0, len(alphabet)-1):
x = numpy.reshape(seed, (1, len(seed), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
print int_to_char[seed[0]], "->", int_to_char[index]
seed = [index]
model.reset_states()
咱們也能夠看看這個網絡是否能夠從任意的字母開始預測
# demonstrate a random starting point
letter = "K"
seed = [char_to_int[letter]]
print "New start: ", letter
for i in range(0, 5):
x = numpy.reshape(seed, (1, len(seed), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
print int_to_char[seed[0]], "->", int_to_char[index]
seed = [index]
model.reset_states()
完整代碼以下:
# Stateful LSTM to learn one-char to one-char mapping
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
# fix random seed for reproducibility
numpy.random.seed(7)
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# prepare the dataset of input to output pairs encoded as integers
seq_length = 1
dataX = []
dataY = []
for i in range(0, len(alphabet) - seq_length, 1):
seq_in = alphabet[i:i + seq_length]
seq_out = alphabet[i + seq_length]
dataX.append([char_to_int[char] for char in seq_in])
dataY.append(char_to_int[seq_out])
print seq_in, '->', seq_out
# reshape X to be [samples, time steps, features]
X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
# normalize
X = X / float(len(alphabet))
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
# create and fit the model
batch_size = 1
model = Sequential()
model.add(LSTM(16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
for i in range(300):
model.fit(X, y, epochs=1, batch_size=batch_size, verbose=2, shuffle=False)
model.reset_states()
# summarize performance of the model
scores = model.evaluate(X, y, batch_size=batch_size, verbose=0)
model.reset_states()
print("Model Accuracy: %.2f%%" % (scores[1]*100))
# demonstrate some model predictions
seed = [char_to_int[alphabet[0]]]
for i in range(0, len(alphabet)-1):
x = numpy.reshape(seed, (1, len(seed), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
print int_to_char[seed[0]], "->", int_to_char[index]
seed = [index]
model.reset_states()
# demonstrate a random starting point
letter = "K"
seed = [char_to_int[letter]]
print "New start: ", letter
for i in range(0, 5):
x = numpy.reshape(seed, (1, len(seed), 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
print int_to_char[seed[0]], "->", int_to_char[index]
seed = [index]
model.reset_states()
output
Model Accuracy: 100.00%
A -> B
B -> C
C -> D
D -> E
E -> F
F -> G
G -> H
H -> I
I -> J
J -> K
K -> L
L -> M
M -> N
N -> O
O -> P
P -> Q
Q -> R
R -> S
S -> T
T -> U
U -> V
V -> W
W -> X
X -> Y
Y -> Z
New start: K
K -> B
B -> C
C -> D
D -> E
E -> F
咱們能夠看到,網絡已經完美地記住了整個字母表。它使用了樣本的上下文,並學習了預測序列中下一個字符所須要的依賴關係。
咱們還能夠看到,若是咱們用第一個字母輸入網絡,它就能正確地對字母表的其餘部分進行正確的理解。
咱們還能夠看到,它只是從一個冷啓動開始,就學會了完整的字母表順序。當要求預測「K」的下一個字母時,它會預測「B」,而後返回到整個字母表中。
爲了真正地預測「K」,網絡的狀態須要被反覆地從「A」到「J」的字母「加熱」。這告訴咱們,咱們也能夠達到「無狀態」LSTM的效果,若是咱們經過準備形以下面的訓練數據:
---a -> b --ab -> c -abc -> d abcd -> e
輸入序列固定在25(a-y,以預測z)的位置,而且模式以 zero-padding爲前綴。
最後,這提出了另外一個問題,便是否可使用可變長度的輸入序列來訓練LSTM網絡,以預測下一個字符。
在上一節中,咱們發現keras的「有狀態的」LSTM實際上只是從新播放第一個n序列的一個快捷方式,並無真正學習一個通用的字母表模型。
在這一節中,咱們將探索一個「無狀態」LSTM的變體,它學習了字母表中的隨機子序列,並能夠根據任意字母或字母序列去預測字母表中的下一個字母。
首先,咱們改變問題的框架。爲了簡化,咱們定義一個最大的輸入序列長度(maximum input sequence length),並將其設置爲5這樣的小值來加速訓練。這就定義了(用於訓練的字母表的)子序列的最大長度。在擴展中,若是咱們容許循環回到序列的開始,這就能夠設置爲完整的字母表(26)或更長。
咱們還須要定義要建立的隨機序列的數量,在本例中爲1000。這也多是更多或更少。我但願實際須要的模式更少。
# prepare the dataset of input to output pairs encoded as integers
num_inputs = 1000
max_len = 5
dataX = []
dataY = []
for i in range(num_inputs):
start = numpy.random.randint(len(alphabet)-2)
end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1))
sequence_in = alphabet[start:end+1]
sequence_out = alphabet[end + 1]
dataX.append([char_to_int[char] for char in sequence_in])
dataY.append(char_to_int[sequence_out])
print sequence_in, '->', sequence_out
輸入大概像這樣
PQRST -> U
W -> X
O -> P
OPQ -> R
IJKLM -> N
QRSTU -> V
ABCD -> E
X -> Y
GHIJ -> K
輸入序列的長度在1和maxlen之間變化,所以須要zero padding(零填充)。在這裏,咱們使用了left-hand-side (prefix) padding和 keras自帶的pad_sequences()函數。
X = pad_sequences(dataX, maxlen=max_len, dtype='float32')
訓練模型在隨機選擇的輸入模式下進行評估。這能夠很容易地成爲新的隨機生成的字符序列。我認爲,這也能夠是一個線性序列,用「A」做爲單個字符輸入的輸出。
# LSTM with Variable Length Input Sequences to One Character Output
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
from keras.preprocessing.sequence import pad_sequences
from theano.tensor.shared_randomstreams import RandomStreams
# fix random seed for reproducibility
numpy.random.seed(7)
# define the raw dataset
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# create mapping of characters to integers (0-25) and the reverse
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# prepare the dataset of input to output pairs encoded as integers
num_inputs = 1000
max_len = 5
dataX = []
dataY = []
for i in range(num_inputs):
start = numpy.random.randint(len(alphabet)-2)
end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1))
sequence_in = alphabet[start:end+1]
sequence_out = alphabet[end + 1]
dataX.append([char_to_int[char] for char in sequence_in])
dataY.append(char_to_int[sequence_out])
print sequence_in, '->', sequence_out
# convert list of lists to array and pad sequences if needed
X = pad_sequences(dataX, maxlen=max_len, dtype='float32')
# reshape X to be [samples, time steps, features]
X = numpy.reshape(X, (X.shape[0], max_len, 1))
# normalize
X = X / float(len(alphabet))
# one hot encode the output variable
y = np_utils.to_categorical(dataY)
# create and fit the model
batch_size = 1
model = Sequential()
model.add(LSTM(32, input_shape=(X.shape[1], 1)))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, epochs=500, batch_size=batch_size, verbose=2)
# summarize performance of the model
scores = model.evaluate(X, y, verbose=0)
print("Model Accuracy: %.2f%%" % (scores[1]*100))
# demonstrate some model predictions
for i in range(20):
pattern_index = numpy.random.randint(len(dataX))
pattern = dataX[pattern_index]
x = pad_sequences([pattern], maxlen=max_len, dtype='float32')
x = numpy.reshape(x, (1, max_len, 1))
x = x / float(len(alphabet))
prediction = model.predict(x, verbose=0)
index = numpy.argmax(prediction)
result = int_to_char[index]
seq_in = [int_to_char[value] for value in pattern]
print seq_in, "->", result
output
Model Accuracy: 98.90%
['Q', 'R'] -> S
['W', 'X'] -> Y
['W', 'X'] -> Y
['C', 'D'] -> E
['E'] -> F
['S', 'T', 'U'] -> V
['G', 'H', 'I', 'J', 'K'] -> L
['O', 'P', 'Q', 'R', 'S'] -> T
['C', 'D'] -> E
['O'] -> P
['N', 'O', 'P'] -> Q
['D', 'E', 'F', 'G', 'H'] -> I
['X'] -> Y
['K'] -> L
['M'] -> N
['R'] -> T
['K'] -> L
['E', 'F', 'G'] -> H
['Q'] -> R
['Q', 'R', 'S'] -> T
咱們能夠看到,儘管這個模型沒有從隨機生成的子序列中完美地學習字母表,但它作得很好。該模型沒有進行調整,可能須要更多的訓練或更大的網絡,或者二者都須要(爲讀者提供一個練習)。
這是一個很好的天然擴展,對於「每一個批處理中的全部順序輸入示例」,均可以在上面學到,它能夠處理特殊的查詢,可是這一次是任意的序列長度(最多的是最大長度)。
這篇文章你應該學會了: