通過keras例子理解LSTM 循環神經網絡(RNN)


正文

一個強大而流行的循環神經網絡(RNN)的變種是長短期模型網絡(LSTM)。

它使用廣泛,因為它的架構克服了困擾着所有周期性的神經網絡梯度消失梯度爆炸的問題,允許創建非常大的、非常深的網絡。

與其他周期性的神經網絡一樣,LSTM網絡保持狀態,在keras框架中實現這一點的細節可能會令人困惑。

在這篇文章中,您將會確切地了解到在LSTM網絡中,如何在LSTM深度學習庫中維護狀態。

 本文目標:

  1. 怎么在keras上實現一個普通的lstm循環神經網絡
  2. 在lstm中怎樣小心的利用好時間狀態特征
  3. 怎樣在lstm上實現狀態的預測

本文在一個很簡單的例子上說明lstm的使用和lstm的特點,通過對這個簡化例子的理解,可以幫助我們對一般的序列預測問題和序列預測問題有更高的理解和使用。
用到的庫:Keras 2.0.2,TensorFlow 1.0.1Theano 0.9.0.

問題描述:學習字母

在本教程中,我們將開發和對比許多不同的LSTM循環神經網絡模型。

這些比較的背景是學習字母表的一個簡單的序列預測問題。也就是說,根據字母表的字母,可以預測字母表的下一個字母。

這是一個簡單的序列預測問題,一旦被理解,就可以被推廣到其他的序列預測問題,如時間序列預測和序列分類。

讓我們用一些python代碼來准備這個問題,我們可以從示例中重用這些代碼。

首先,讓我們導入本教程中計划使用的所有類和函數。

  1. import numpy
  2. from keras.models import Sequential
  3. from keras.layers import Dense
  4. from keras.layers import LSTM
  5. from keras.utils import np_utils

接下來,我們可以對隨機數生成器選定隨機數種子,以確保每次執行代碼時結果都是相同的。

  1. # fix random seed for reproducibility
  2. numpy.random.seed(7)

我們現在可以定義我們的數據集,字母表。為了便於閱讀,我們用大寫字母來定義字母表。

神經網絡是對數字建模,因此我們需要將字母表中的字母映射到整數值(把字母映射為數字)。我們可以很容易地通過創建字母索引的字典(map)到字符。我們還可以創建一個反向查找,以便將預測轉換回字符,以便稍后使用。

  1. # define the raw dataset
  2. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  3. # create mapping of characters to integers ( 0-25) and the reverse
  4. char_to_int = dict(( c, i) for i, c in enumerate(alphabet))
  5. int_to_char = dict((i, c) for i, c in enumerate(alphabet))

現在我們需要創建我們的輸入和輸出鍵值對來訓練我們的神經網絡。我們可以通過定義輸入序列長度,然后從輸入字母序列中讀取序列來實現這一點。

例如,我們使用的輸入長度是1。從原始輸入數據的開始,我們可以讀出第一個字母A和下一個字母“B”。我們沿着一個字符移動,直到我們到達一個“Z”的預測。

我們先創造這樣一個數據集,用一個字母,來預測下一個字母是什么。

  1. # prepare the dataset of input to output pairs encoded as integers
  2. seq_length = 1
  3. dataX = []
  4. dataY = []
  5. for i in range(0, len(alphabet) - seq_length, 1):
  6. seq_in = alphabet[i:i + seq_length]
  7. seq_out = alphabet[i + seq_length]
  8. dataX.append([char_to_int[ char] for char in seq_in])
  9. dataY.append(char_to_int[seq_out])
  10. print seq_in, '->', seq_out

我們運行上面的代碼,來觀察現在我們的input和output數據集是這樣一種情況

  1. A -> B
  2. B -> C
  3. C -> D
  4. D -> E
  5. E -> F
  6. F -> G
  7. G -> H
  8. H -> I
  9. I -> J
  10. J -> K
  11. K -> L
  12. L -> M
  13. M -> N
  14. N -> O
  15. O -> P
  16. P -> Q
  17. Q -> R
  18. R -> S
  19. S -> T
  20. T -> U
  21. U -> V
  22. V -> W
  23. W -> X
  24. X -> Y
  25. Y -> Z

input是一個一個字母的序列,output是一個一個的序列。
ok,就在這樣的數據集上來應用我們的lstm。看看會有什么結果?

這時候dataX是一個一個用字母組成的序列,但是還要轉換一下格式,才能用到keras上。我們需要將NumPy數組重新構造為LSTM網絡所期望的格式,即[samples示例, time steps時間步數, features特征]。

  1. # reshape X to be [samples, time steps, features]
  2. X = numpy.reshape(dataX, ( len(dataX), seq_length, 1))

然后我們需要把我們的整數值歸一化到0~1的區間上,這是LSTM網絡使用的s形激活函數(sigmoid)的范圍。

  1. # normalize
  2. X = X / float(len(alphabet))

最后,我們可以把這個問題看作是一個序列分類任務,其中26個字母代表一個不同的類。因此,我們用keras的內置的 to_categorical()函數把輸出output(y)進行 one-hot編碼(one-hot指n維單位向量a=(0,…,0,1,0,…,0))作為輸出層的結果。

  1. # one hot encode the output variable
  2. y = np_utils.to_categorical(dataY)

現在我們已經准備好去訓練不同的LSTM模型了。

 單字符——單字符的映射的簡單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用於上述問題的代碼如下:

  1. # create and fit the model
  2. model = Sequential()
  3. model. add(LSTM(32, input_shape=(X.shape[1], X.shape[2])))
  4. model. add(Dense(y.shape[1], activation='softmax'))
  5. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  6. model.fit(X, y, nb_epoch= 500, batch_size=1, verbose=2)

在我們訓練模型之后,我們可以對整個訓練集的性能進行評估和總結。

  1. # summarize performance of the model
  2. scores = model.evaluate(X, y, verbose= 0)
  3. print( "Model Accuracy: %.2f%%" % (scores[1]*100))

然后,我們可以通過網絡重新運行訓練數據,並生成預測,將輸入和輸出對轉換回原來的字符格式,以獲得關於網絡如何了解問題的視覺效果。

  1. # demonstrate some model predictions
  2. for pattern in dataX:
  3. x = numpy.reshape(pattern, ( 1, len(pattern), 1))
  4. x = x / float(len(alphabet))
  5. prediction = model.predict(x, verbose= 0)
  6. index = numpy.argmax(prediction)
  7. result = int_to_char[index]
  8. seq_in = [int_to_char[ value] for value in pattern]
  9. print seq_in, "->", result

 

我們可以看到,這個問題對於網絡來說確實是很困難的。
原因是可憐的lstm單元根本沒有可以利用的上下文章信息。
每個輸入輸出模式都以隨機的順序顯示在網絡中,並且網絡的狀態在每個模式之后被重置(每個批處理的每個批次包含一個模式)。

 

這是對LSTM網絡架構的濫用,因為我們把它當作了一個標准的多層感知器。

接下來,讓我們嘗試一個不同的問題框架,以便為網絡提供更多的序列來學習。

 

三字符特征——單字符的映射的簡單LSTM

在多層感知器中添加更多上下文最流行的方法是特征窗口方法(Feature Window method)。

即序列中的前面步驟的輸出被作為附加的輸入特性提供給網絡。我們可以用相同的技巧,為LSTM網絡提供更多的上下文。

在這里,我們將序列長度從1增加到3,例如:
我們把輸入從一個字符升到三個字符。

  1. # prepare the dataset of input to output pairs encoded as integers
  2. seq_length = 3

就像這樣:

  1. ABC -> D
  2. BCD -> E
  3. CDE -> F

然后將序列中的每個元素作為網絡的一個新輸入特性提供給它。這需要修改輸入序列在數據准備步驟中的reshape:

  1. # reshape X to be [samples, time steps, features]
  2. X = numpy.reshape(dataX, ( len(dataX), 1, seq_length))

還需要對示例模式的reshape進行修改,以展示模型的預測結果。

x = numpy.reshape(pattern, (1, 1, len(pattern)))

全部的代碼如下:

  1. # Naive LSTM to learn three-char window to one-char mapping
  2. import numpy
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.layers import LSTM
  6. from keras.utils import np_utils
  7. # fix random seed for reproducibility
  8. numpy.random.seed( 7)
  9. # define the raw dataset
  10. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  11. # create mapping of characters to integers (0-25) and the reverse
  12. char_to_int = dict((c, i) for i, c in enumerate(alphabet))
  13. int_to_char = dict((i, c) for i, c in enumerate(alphabet))
  14. # prepare the dataset of input to output pairs encoded as integers
  15. seq_length = 3
  16. dataX = []
  17. dataY = []
  18. for i in range(0, len(alphabet) - seq_length, 1):
  19. seq_in = alphabet[i:i + seq_length]
  20. seq_out = alphabet[i + seq_length]
  21. dataX.append([char_to_int[char] for char in seq_in])
  22. dataY.append(char_to_int[seq_out])
  23. print seq_in, '->', seq_out
  24. # reshape X to be [samples, time steps, features]
  25. X = numpy.reshape(dataX, (len(dataX), 1, seq_length))
  26. # normalize
  27. X = X / float(len(alphabet))
  28. # one hot encode the output variable
  29. y = np_utils.to_categorical(dataY)
  30. # create and fit the model
  31. model = Sequential()
  32. model.add(LSTM( 32, input_shape=(X.shape[1], X.shape[2])))
  33. model.add(Dense(y.shape[ 1], activation='softmax'))
  34. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  35. model.fit(X, y, epochs= 500, batch_size=1, verbose=2)
  36. # summarize performance of the model
  37. scores = model.evaluate(X, y, verbose= 0)
  38. print( "Model Accuracy: %.2f%%" % (scores[1]*100))
  39. # demonstrate some model predictions
  40. for pattern in dataX:
  41. x = numpy.reshape(pattern, ( 1, 1, len(pattern)))
  42. x = x / float(len(alphabet))
  43. prediction = model.predict(x, verbose= 0)
  44. index = numpy.argmax(prediction)
  45. result = int_to_char[index]
  46. seq_in = [int_to_char[value] for value in pattern]
  47. print seq_in, "->", result

運行結果如下:

  1. Model Accuracy: 86.96%
  2. [ 'A', 'B', 'C'] -> D
  3. [ 'B', 'C', 'D'] -> E
  4. [ 'C', 'D', 'E'] -> F
  5. [ 'D', 'E', 'F'] -> G
  6. [ 'E', 'F', 'G'] -> H
  7. [ 'F', 'G', 'H'] -> I
  8. [ 'G', 'H', 'I'] -> J
  9. [ 'H', 'I', 'J'] -> K
  10. [ 'I', 'J', 'K'] -> L
  11. [ 'J', 'K', 'L'] -> M
  12. [ 'K', 'L', 'M'] -> N
  13. [ 'L', 'M', 'N'] -> O
  14. [ 'M', 'N', 'O'] -> P
  15. [ 'N', 'O', 'P'] -> Q
  16. [ 'O', 'P', 'Q'] -> R
  17. [ 'P', 'Q', 'R'] -> S
  18. [ 'Q', 'R', 'S'] -> T
  19. [ 'R', 'S', 'T'] -> U
  20. [ 'S', 'T', 'U'] -> V
  21. [ 'T', 'U', 'V'] -> Y
  22. [ 'U', 'V', 'W'] -> Z
  23. [ 'V', 'W', 'X'] -> Z
  24. [ 'W', 'X', 'Y'] -> Z

我們發現有了一點點的提升,但是這一點點的提升未必是真的,梯度下降算法本來就是具有隨機性的。

也就是說我們再一次的錯誤使用了lstm循環神經網絡。
我們確實給了上下文,但是並不是合適的方式,
實際上,字母序列A-B-C才是一個特征的timesteps,而不是單獨ABC一個特征的timestep
我們已經給網絡提供了更多的上下文,但並沒有像預期的那樣有更多的順序。

在下一節中,我們將以timesteps的形式為網絡提供更多的上下文。

keras實踐循環的正確打開方式!

在keras中,利用lstm的關鍵是以時間序列(time steps)的方法來提供上下文,而不是像其他網絡結構(CNN)一樣,通過windowed features的方式。

這次我們還是采用這樣的訓練方式

seq_length = 3

輸入輸出對(input-output pairs)

  1. ABC -> D
  2. BCD -> E
  3. CDE -> F
  4. DEF -> G

我們這次唯一改變的地方是下面這里:

  1. # reshape X to be [samples, time steps, features]
  2. 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這樣一個訓練樣本。

完整代碼如下:

  1. # Naive LSTM to learn three-char time steps to one-char mapping
  2. import numpy
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.layers import LSTM
  6. from keras.utils import np_utils
  7. # fix random seed for reproducibility
  8. numpy.random.seed( 7)
  9. # define the raw dataset
  10. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  11. # create mapping of characters to integers (0-25) and the reverse
  12. char_to_int = dict((c, i) for i, c in enumerate(alphabet))
  13. int_to_char = dict((i, c) for i, c in enumerate(alphabet))
  14. # prepare the dataset of input to output pairs encoded as integers
  15. seq_length = 3
  16. dataX = []
  17. dataY = []
  18. for i in range(0, len(alphabet) - seq_length, 1):
  19. seq_in = alphabet[i:i + seq_length]
  20. seq_out = alphabet[i + seq_length]
  21. dataX.append([char_to_int[char] for char in seq_in])
  22. dataY.append(char_to_int[seq_out])
  23. print seq_in, '->', seq_out
  24. # reshape X to be [samples, time steps, features]
  25. X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
  26. # normalize
  27. X = X / float(len(alphabet))
  28. # one hot encode the output variable
  29. y = np_utils.to_categorical(dataY)
  30. # create and fit the model
  31. model = Sequential()
  32. model.add(LSTM( 32, input_shape=(X.shape[1], X.shape[2])))
  33. model.add(Dense(y.shape[ 1], activation='softmax'))
  34. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  35. model.fit(X, y, nb_epoch= 500, batch_size=1, verbose=2)
  36. # summarize performance of the model
  37. scores = model.evaluate(X, y, verbose= 0)
  38. print( "Model Accuracy: %.2f%%" % (scores[1]*100))
  39. # demonstrate some model predictions
  40. for pattern in dataX:
  41. x = numpy.reshape(pattern, ( 1, len(pattern), 1))
  42. x = x / float(len(alphabet))
  43. prediction = model.predict(x, verbose= 0)
  44. index = numpy.argmax(prediction)
  45. result = int_to_char[index]
  46. seq_in = [int_to_char[value] for value in pattern]
  47. print seq_in, "->", result

最終的訓練結果是

  1. Model Accuracy: 100.00%
  2. [ 'A', 'B', 'C'] -> D
  3. [ 'B', 'C', 'D'] -> E
  4. [ 'C', 'D', 'E'] -> F
  5. [ 'D', 'E', 'F'] -> G
  6. [ 'E', 'F', 'G'] -> H
  7. [ 'F', 'G', 'H'] -> I
  8. [ 'G', 'H', 'I'] -> J
  9. [ 'H', 'I', 'J'] -> K
  10. [ 'I', 'J', 'K'] -> L
  11. [ 'J', 'K', 'L'] -> M
  12. [ 'K', 'L', 'M'] -> N
  13. [ 'L', 'M', 'N'] -> O
  14. [ 'M', 'N', 'O'] -> P
  15. [ 'N', 'O', 'P'] -> Q
  16. [ 'O', 'P', 'Q'] -> R
  17. [ 'P', 'Q', 'R'] -> S
  18. [ 'Q', 'R', 'S'] -> T
  19. [ 'R', 'S', 'T'] -> U
  20. [ 'S', 'T', 'U'] -> V
  21. [ 'T', 'U', 'V'] -> W
  22. [ 'U', 'V', 'W'] -> X
  23. [ 'V', 'W', 'X'] -> Y
  24. [ 'W', 'X', 'Y'] -> Z

它已經學會了用字母表中的三個字母來預測下一個字母的順序。它可以顯示字母表中的任意三個字母的隨機序列,並預測下一個字母。

我們還沒有展示出循環神經網絡的強大之處,因為上面這個問題我們用多層感知器,足夠多的神經元,足夠多的迭代次數也可以很好的解決。(三層神經網絡擬合任意可以表示的函數)

 

LSTM網絡是有狀態的。它們應該能夠學習整個字母表序列,但是在默認情況下,keras在每次訓練之后重新設置網絡狀態。

 

那么接下來就是展示循環神經網絡的獨到之處!!

 

一個批處理中的LSTM狀態

keras實現的LSTM在每一個batch以后,都重置了LSTM的狀態。

這表明,如果我們的批處理大小足夠容納所有輸入模式,如果所有輸入模式都按順序排序,LSTM就可以使用序列中的序列上下文來更好地學習序列。

通過修改第一個示例來學習一對一映射,並將批處理大小從1增加到訓練數據集的大小,我們可以很容易地演示這一點。

此外,在每個epoch前,keras都重置了訓練數據集。為了確保訓練數據模式保持順序,我們可以禁用這種洗牌。

model.fit(X, y, epochs=5000, batch_size=len(dataX), verbose=2, shuffle=False)

該網絡將使用 within-batch批序列來學習字符的映射,但在進行預測時,這個上下文將無法用於網絡。我們可以對網絡進行評估,以確定網絡在隨機序列和順序序列的預測能力。

完整代碼如下:

  1. Naive LSTM to learn one-char to one-char mapping with all data in each batch
  2. import numpy
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.layers import LSTM
  6. from keras.utils import np_utils
  7. from keras.preprocessing.sequence import pad_sequences
  8. # fix random seed for reproducibility
  9. numpy.random.seed( 7)
  10. # define the raw dataset
  11. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  12. # create mapping of characters to integers (0-25) and the reverse
  13. char_to_int = dict((c, i) for i, c in enumerate(alphabet))
  14. int_to_char = dict((i, c) for i, c in enumerate(alphabet))
  15. # prepare the dataset of input to output pairs encoded as integers
  16. seq_length = 1
  17. dataX = []
  18. dataY = []
  19. for i in range(0, len(alphabet) - seq_length, 1):
  20. seq_in = alphabet[i:i + seq_length]
  21. seq_out = alphabet[i + seq_length]
  22. dataX.append([char_to_int[char] for char in seq_in])
  23. dataY.append(char_to_int[seq_out])
  24. print seq_in, '->', seq_out
  25. # convert list of lists to array and pad sequences if needed
  26. X = pad_sequences(dataX, maxlen=seq_length, dtype= 'float32')
  27. # reshape X to be [samples, time steps, features]
  28. X = numpy.reshape(dataX, (X.shape[ 0], seq_length, 1))
  29. # normalize
  30. X = X / float(len(alphabet))
  31. # one hot encode the output variable
  32. y = np_utils.to_categorical(dataY)
  33. # create and fit the model
  34. model = Sequential()
  35. model.add(LSTM( 16, input_shape=(X.shape[1], X.shape[2])))
  36. model.add(Dense(y.shape[ 1], activation='softmax'))
  37. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  38. model.fit(X, y, epochs= 5000, batch_size=len(dataX), verbose=2, shuffle=False)
  39. # summarize performance of the model
  40. scores = model.evaluate(X, y, verbose= 0)
  41. print( "Model Accuracy: %.2f%%" % (scores[1]*100))
  42. # demonstrate some model predictions
  43. for pattern in dataX:
  44. x = numpy.reshape(pattern, ( 1, len(pattern), 1))
  45. x = x / float(len(alphabet))
  46. prediction = model.predict(x, verbose= 0)
  47. index = numpy.argmax(prediction)
  48. result = int_to_char[index]
  49. seq_in = [int_to_char[value] for value in pattern]
  50. print seq_in, "->", result
  51. # demonstrate predicting random patterns
  52. print "Test a Random Pattern:"
  53. for i in range(0,20):
  54. pattern_index = numpy.random.randint(len(dataX))
  55. pattern = dataX[pattern_index]
  56. x = numpy.reshape(pattern, ( 1, len(pattern), 1))
  57. x = x / float(len(alphabet))
  58. prediction = model.predict(x, verbose= 0)
  59. index = numpy.argmax(prediction)
  60. result = int_to_char[index]
  61. seq_in = [int_to_char[value] for value in pattern]
  62. print seq_in, "->", result

結果:

  1. Model Accuracy: 100.00%
  2. ['A'] -> B
  3. ['B'] -> C
  4. ['C'] -> D
  5. ['D'] -> E
  6. ['E'] -> F
  7. ['F'] -> G
  8. ['G'] -> H
  9. ['H'] -> I
  10. ['I'] -> J
  11. ['J'] -> K
  12. ['K'] -> L
  13. ['L'] -> M
  14. ['M'] -> N
  15. ['N'] -> O
  16. ['O'] -> P
  17. ['P'] -> Q
  18. ['Q'] -> R
  19. ['R'] -> S
  20. ['S'] -> T
  21. ['T'] -> U
  22. ['U'] -> V
  23. ['V'] -> W
  24. ['W'] -> X
  25. ['X'] -> Y
  26. ['Y'] -> Z
  27. Test a Random Pattern:
  28. ['T'] -> U
  29. ['V'] -> W
  30. ['M'] -> N
  31. ['Q'] -> R
  32. ['D'] -> E
  33. ['V'] -> W
  34. ['T'] -> U
  35. ['U'] -> V
  36. ['J'] -> K
  37. ['F'] -> G
  38. ['N'] -> O
  39. ['B'] -> C
  40. ['M'] -> N
  41. ['F'] -> G
  42. ['F'] -> G
  43. ['P'] -> Q
  44. ['A'] -> B
  45. ['K'] -> L
  46. ['W'] -> X
  47. ['E'] -> F

正如我們所期望的那樣,網絡能夠使用 within-sequence的上下文來學習字母表,在訓練數據上達到100%的准確率。

重要的是,該網絡可以對隨機選擇的字符的下一個字母進行准確的預測。非常令人印象深刻。

單字符——單字符的映射的有狀態LSTM

我們已經看到,我們可以將原始數據拆分為固定大小的序列,並且這種表示可以由LSTM來學習,且只需要學習3個字符到1個字符的隨機映射。

我們也看到,我們可以對批量的大小進行限制,為網絡提供更多的序列,但只有在訓練期間才行。

理想情況下,我們希望將網絡公開給整個序列,並讓它學習相互依賴關系,而不是在問題的框架中明確地定義這些依賴關系。

我們可以在keras中做到這一點,通過使LSTM層擁有狀態,並在epoch結束時手動重新設置網絡的狀態,這時也結束了訓練整個序列的過程。

這才是LSTM網絡的真正用途。我們發現,如果允許網絡本身學習字符之間的依賴關系,我們只需要一個更小的網絡(一半的單位數量)和更少的訓練期(幾乎是一半)。

首先我們需要將LSTM層定義為有狀態的。這樣做的話,我們必須顯式地指定批大小作為輸入形狀的一個維度。這也意味着當我們評估網絡或用網絡進行預測時,我們也必須指定並遵守相同的批大小。現在這不是問題,因為我們使用的是批大小的1。這可能會在預測的時候帶來困難,因為當批大小不是1時,預測需要按批進行和按順序進行。

  1. batch_size = 1
  2. model.add(LSTM( 16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))

訓練有狀態的LSTM的一個重要區別是,我們每次都手動地訓練它,並且在每個時代之后重新設置狀態。我們可以在for循環中這樣做。同樣,我們不會對輸入進行洗牌,保留輸入訓練數據創建的順序。

  1. for i in range(300):
  2. model.fit(X, y, epochs= 1, batch_size=batch_size, verbose=2, shuffle=False)
  3. model.reset_states()

如前所述,在評估整個培訓數據集的網絡性能時,我們指定批處理大小。

  1. # summarize performance of the model
  2. scores = model.evaluate(X, y, batch_size=batch_size, verbose= 0)
  3. model.reset_states()
  4. print( "Model Accuracy: %.2f%%" % (scores[1]*100))

最后,我們可以證明網絡確實學會了整個字母表。我們可以用第一個字母A“A”來做輸入,獲得一個預測,把預測作為輸入反饋給它,然后把這個過程一直重復到“Z”。

  1. # demonstrate some model predictions
  2. seed = [char_to_int[alphabet[ 0]]]
  3. for i in range(0, len(alphabet)-1):
  4. x = numpy.reshape(seed, ( 1, len(seed), 1))
  5. x = x / float( len(alphabet))
  6. prediction = model.predict(x, verbose= 0)
  7. index = numpy.argmax(prediction)
  8. print int_to_char[seed[0]], "->", int_to_char[index]
  9. seed = [index]
  10. model.reset_states()

我們也可以看看這個網絡是否可以從任意的字母開始預測

  1. # demonstrate a random starting point
  2. letter = "K"
  3. seed = [char_to_int[letter]]
  4. print "New start: ", letter
  5. for i in range(0, 5):
  6. x = numpy.reshape(seed, (1, len(seed), 1))
  7. x = x / float(len(alphabet))
  8. prediction = model.predict( x, verbose=0)
  9. index = numpy.argmax(prediction)
  10. print int_to_char[seed[0]], "->", int_to_char[index]
  11. seed = [ index]
  12. model.reset_states()

完整代碼如下:

  1. # Stateful LSTM to learn one-char to one-char mapping
  2. import numpy
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.layers import LSTM
  6. from keras.utils import np_utils
  7. # fix random seed for reproducibility
  8. numpy.random.seed( 7)
  9. # define the raw dataset
  10. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  11. # create mapping of characters to integers (0-25) and the reverse
  12. char_to_int = dict((c, i) for i, c in enumerate(alphabet))
  13. int_to_char = dict((i, c) for i, c in enumerate(alphabet))
  14. # prepare the dataset of input to output pairs encoded as integers
  15. seq_length = 1
  16. dataX = []
  17. dataY = []
  18. for i in range(0, len(alphabet) - seq_length, 1):
  19. seq_in = alphabet[i:i + seq_length]
  20. seq_out = alphabet[i + seq_length]
  21. dataX.append([char_to_int[char] for char in seq_in])
  22. dataY.append(char_to_int[seq_out])
  23. print seq_in, '->', seq_out
  24. # reshape X to be [samples, time steps, features]
  25. X = numpy.reshape(dataX, (len(dataX), seq_length, 1))
  26. # normalize
  27. X = X / float(len(alphabet))
  28. # one hot encode the output variable
  29. y = np_utils.to_categorical(dataY)
  30. # create and fit the model
  31. batch_size = 1
  32. model = Sequential()
  33. model.add(LSTM( 16, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
  34. model.add(Dense(y.shape[ 1], activation='softmax'))
  35. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  36. for i in range(300):
  37. model.fit(X, y, epochs= 1, batch_size=batch_size, verbose=2, shuffle=False)
  38. model.reset_states()
  39. # summarize performance of the model
  40. scores = model.evaluate(X, y, batch_size=batch_size, verbose= 0)
  41. model.reset_states()
  42. print( "Model Accuracy: %.2f%%" % (scores[1]*100))
  43. # demonstrate some model predictions
  44. seed = [char_to_int[alphabet[ 0]]]
  45. for i in range(0, len(alphabet)-1):
  46. x = numpy.reshape(seed, ( 1, len(seed), 1))
  47. x = x / float(len(alphabet))
  48. prediction = model.predict(x, verbose= 0)
  49. index = numpy.argmax(prediction)
  50. print int_to_char[seed[0]], "->", int_to_char[index]
  51. seed = [index]
  52. model.reset_states()
  53. # demonstrate a random starting point
  54. letter = "K"
  55. seed = [char_to_int[letter]]
  56. print "New start: ", letter
  57. for i in range(0, 5):
  58. x = numpy.reshape(seed, ( 1, len(seed), 1))
  59. x = x / float(len(alphabet))
  60. prediction = model.predict(x, verbose= 0)
  61. index = numpy.argmax(prediction)
  62. print int_to_char[seed[0]], "->", int_to_char[index]
  63. seed = [index]
  64. model.reset_states()

output

  1. Model Accuracy: 100.00%
  2. A -> B
  3. B -> C
  4. C -> D
  5. D -> E
  6. E -> F
  7. F -> G
  8. G -> H
  9. H -> I
  10. I -> J
  11. J -> K
  12. K -> L
  13. L -> M
  14. M -> N
  15. N -> O
  16. O -> P
  17. P -> Q
  18. Q -> R
  19. R -> S
  20. S -> T
  21. T -> U
  22. U -> V
  23. V -> W
  24. W -> X
  25. X -> Y
  26. Y -> Z
  27. New start: K
  28. K -> B
  29. B -> C
  30. C -> D
  31. D -> E
  32. E -> F

我們可以看到,網絡已經完美地記住了整個字母表。它使用了樣本的上下文,並學習了預測序列中下一個字符所需要的依賴關系。

我們還可以看到,如果我們用第一個字母輸入網絡,它就能正確地對字母表的其他部分進行正確的理解。

我們還可以看到,它只是從一個冷啟動開始,就學會了完整的字母表順序。當要求預測“K”的下一個字母時,它會預測“B”,然后返回到整個字母表中。

為了真正地預測“K”,網絡的狀態需要被反復地從“A”到“J”的字母“加熱”。這告訴我們,我們也可以達到“無狀態”LSTM的效果,如果我們通過准備形如下面的訓練數據:

  1. ---a -> b
  2. --ab -> c
  3. -abc -> d
  4. abcd -> e

輸入序列固定在25(a-y,以預測z)的位置,並且模式以 zero-padding為前綴。

最后,這提出了另一個問題,即是否可以使用可變長度的輸入序列來訓練LSTM網絡,以預測下一個字符。

可變長度輸入——單字符輸出的LSTM

在上一節中,我們發現keras的“有狀態的”LSTM實際上只是重新播放第一個n序列的一個快捷方式,並沒有真正學習一個通用的字母表模型。

在這一節中,我們將探索一個“無狀態”LSTM的變體,它學習了字母表中的隨機子序列,並可以根據任意字母或字母序列去預測字母表中的下一個字母。

首先,我們改變問題的框架。為了簡化,我們定義一個最大的輸入序列長度(maximum input sequence length),並將其設置為5這樣的小值來加速訓練。這就定義了(用於訓練的字母表的)子序列的最大長度。在擴展中,如果我們允許循環回到序列的開始,這就可以設置為完整的字母表(26)或更長。

我們還需要定義要創建的隨機序列的數量,在本例中為1000。這也可能是更多或更少。我希望實際需要的模式更少。

  1. # prepare the dataset of input to output pairs encoded as integers
  2. num_inputs = 1000
  3. max_len = 5
  4. dataX = []
  5. dataY = []
  6. for i in range(num_inputs):
  7. start = numpy.random.randint(len(alphabet)- 2)
  8. end = numpy.random.randint(start, min(start+max_len,len(alphabet)-1))
  9. sequence_in = alphabet[start: end+1]
  10. sequence_out = alphabet[ end + 1]
  11. dataX.append([char_to_int[char] for char in sequence_in])
  12. dataY.append(char_to_int[sequence_out])
  13. print sequence_in, '->', sequence_out

輸入大概像這樣

  1. PQRST -> U
  2. W -> X
  3. O -> P
  4. OPQ -> R
  5. IJKLM -> N
  6. QRSTU -> V
  7. ABCD -> E
  8. X -> Y
  9. GHIJ -> K

輸入序列的長度在1和maxlen之間變化,因此需要zero padding(零填充)。在這里,我們使用了left-hand-side (prefix) padding和 keras自帶的pad_sequences()函數。

X = pad_sequences(dataX, maxlen=max_len, dtype='float32')

訓練模型在隨機選擇的輸入模式下進行評估。這可以很容易地成為新的隨機生成的字符序列。我認為,這也可以是一個線性序列,用“A”作為單個字符輸入的輸出。

  1. # LSTM with Variable Length Input Sequences to One Character Output
  2. import numpy
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.layers import LSTM
  6. from keras.utils import np_utils
  7. from keras.preprocessing.sequence import pad_sequences
  8. from theano.tensor.shared_randomstreams import RandomStreams
  9. # fix random seed for reproducibility
  10. numpy.random.seed( 7)
  11. # define the raw dataset
  12. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  13. # create mapping of characters to integers (0-25) and the reverse
  14. char_to_int = dict((c, i) for i, c in enumerate(alphabet))
  15. int_to_char = dict((i, c) for i, c in enumerate(alphabet))
  16. # prepare the dataset of input to output pairs encoded as integers
  17. num_inputs = 1000
  18. max_len = 5
  19. dataX = []
  20. dataY = []
  21. for i in range(num_inputs):
  22. start = numpy.random.randint(len(alphabet) -2)
  23. end = numpy.random.randint(start, min(start+max_len,len(alphabet) -1))
  24. sequence_in = alphabet[start:end+ 1]
  25. sequence_out = alphabet[end + 1]
  26. dataX.append([char_to_int[char] for char in sequence_in])
  27. dataY.append(char_to_int[sequence_out])
  28. print sequence_in, '->', sequence_out
  29. # convert list of lists to array and pad sequences if needed
  30. X = pad_sequences(dataX, maxlen=max_len, dtype= 'float32')
  31. # reshape X to be [samples, time steps, features]
  32. X = numpy.reshape(X, (X.shape[ 0], max_len, 1))
  33. # normalize
  34. X = X / float(len(alphabet))
  35. # one hot encode the output variable
  36. y = np_utils.to_categorical(dataY)
  37. # create and fit the model
  38. batch_size = 1
  39. model = Sequential()
  40. model.add(LSTM( 32, input_shape=(X.shape[1], 1)))
  41. model.add(Dense(y.shape[ 1], activation='softmax'))
  42. model.compile(loss= 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  43. model.fit(X, y, epochs= 500, batch_size=batch_size, verbose=2)
  44. # summarize performance of the model
  45. scores = model.evaluate(X, y, verbose= 0)
  46. print( "Model Accuracy: %.2f%%" % (scores[1]*100))
  47. # demonstrate some model predictions
  48. for i in range(20):
  49. pattern_index = numpy.random.randint(len(dataX))
  50. pattern = dataX[pattern_index]
  51. x = pad_sequences([pattern], maxlen=max_len, dtype= 'float32')
  52. x = numpy.reshape(x, ( 1, max_len, 1))
  53. x = x / float(len(alphabet))
  54. prediction = model.predict(x, verbose= 0)
  55. index = numpy.argmax(prediction)
  56. result = int_to_char[index]
  57. seq_in = [int_to_char[value] for value in pattern]
  58. print seq_in, "->", result

output

  1. Model Accuracy: 98.90%
  2. [ 'Q', 'R'] -> S
  3. [ 'W', 'X'] -> Y
  4. [ 'W', 'X'] -> Y
  5. [ 'C', 'D'] -> E
  6. [ 'E'] -> F
  7. [ 'S', 'T', 'U'] -> V
  8. [ 'G', 'H', 'I', 'J', 'K'] -> L
  9. [ 'O', 'P', 'Q', 'R', 'S'] -> T
  10. [ 'C', 'D'] -> E
  11. [ 'O'] -> P
  12. [ 'N', 'O', 'P'] -> Q
  13. [ 'D', 'E', 'F', 'G', 'H'] -> I
  14. [ 'X'] -> Y
  15. [ 'K'] -> L
  16. [ 'M'] -> N
  17. [ 'R'] -> T
  18. [ 'K'] -> L
  19. [ 'E', 'F', 'G'] -> H
  20. [ 'Q'] -> R
  21. [ 'Q', 'R', 'S'] -> T

我們可以看到,盡管這個模型沒有從隨機生成的子序列中完美地學習字母表,但它做得很好。該模型沒有進行調整,可能需要更多的訓練或更大的網絡,或者兩者都需要(為讀者提供一個練習)。

這是一個很好的自然擴展,對於“每個批處理中的所有順序輸入示例”,都可以在上面學到,它可以處理特殊的查詢,但是這一次是任意的序列長度(最多的是最大長度)。

總結

這篇文章你應該學會了:

  • 如何開發一個簡單的LSTM網絡,一個字符到一個字符的預測。
  • 如何配置一個簡單的LSTM,以在一個示例中跨時間步驟學習一個序列。
  • 如何配置LSTM來通過手動管理狀態來學習跨示例的序列。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM