LSTM是一種時間遞迴神經網路,適合於處理和預測時間序列中間隔和延遲相對較長的重要事件。在自然語言處理、語言識別等一系列的應用上都取得了很好的效果。
《Long Short Term Memory Networks with Python》是澳大利亞機器學習專家Jason Brownlee的著作,裡面詳細介紹了LSTM模型的原理和使用。
該書總共分為十四個章節,具體如下:
第一章:什麼是LSTMs?
第二章:怎麼樣訓練LSTMs?
第三章:怎麼樣準備LSTMs的資料?
第四章:怎麼樣在Keras中開發LSTMs?
第五章:序列預測建模
第六章:如何開發一個Vanilla LSTM模型?
第七章:怎麼樣開發Stacked LSTMs?
第八章:開發CNN LSTM模型(本期內容)
第九章:開發Encoder-Decoder LSTMs(下週一發布)
第十章:開發Bidirectional LSTMs
第十一章:開發生成LSTMs
第十二章:診斷和除錯LSTMs
第十三章:怎麼樣用LSTMs做預測?
第十四章:更新LSTMs模型
本文的作者對此書進行了翻譯整理之後,分享給大家,本文是第八期內容。
第一期內容為:一萬字純乾貨|機器學習博士手把手教你入門LSTM(附程式碼資料)
第二期內容為:乾貨推薦|如何基於時間的反向傳播演算法來訓練LSTMs?
第三期內容為:乾貨推薦|如何準備用於LSTM模型的資料並進行序列預測?(附程式碼)
第四期內容為:機器學習博士帶你入門|一文學會如何在Keras中開發LSTMs(附程式碼)
第五期內容為:初學者如何避免在序列預測問題中遇到的陷阱?
第六期內容為:如何開發和評估Vanilla LSTM模型?
第七期內容為:博士帶你學LSTM|怎麼樣開發Stacked LSTMs?(附程式碼)
我們還將繼續推出一系列的文章來介紹裡面的詳細內容,和大家一起來共同學習。
8.0 前言
8.0.1 課程目標
本課程的目標是使用卷積神經網路作為前段開發一個LSTM模型。本課程結束之後,你將會學習到:
CNN LSTM的原始結構以及它適合什麼樣型別的問題;
怎麼樣在Keras中應用CNN LSTM結構;
怎麼樣為移動廣場影片預測問題開發一個CNN LSTM模型。
8.0.2 課程概覽
本課程分為7個部分,它們是:
CNN LSTM;
移動廣場影片預測問題;
定義和編譯模型;
擬合模型;
用模型進行預測;
完成例子。
讓我們開始吧!
8.1 CNN LSTM
8.1.1 結構
CNN LSTM結構涉及在輸入資料中使用卷積神經網路(CNN)層做特徵提取並結合LSTM來支援序列預測。CNN LSTMs開發用來視覺化序列預測問題和從影像序列生成文字描述的應用(例如:影片)。特別地,問題包括:
活動識別。對一個序列的圖片所顯示的活動生成一個文字的描述。
影像描述。對單個圖片生成一個文字的描述。
影片描述。對一個序列的圖片生成一個文字的描述。
CNN LSTMs是這樣一類模型,它在空間和時間上都很深,並具有適用於各種輸入任務和輸出的視覺任務的靈活性。
— Long-term Recurrent Convolutional Networks for Visual Recognition and Description, 2015
這種架構最初被稱為長期卷積神經網路(Long-term Recurrent Convolutional Network)或者LRCN模型。儘管我們將使用更通用的名為CNNLSTM來指代本課中使用的CNN作為前段的LSTM模型。該體系結構用於生成影像的文字描述的任務。關鍵是使用CNN,它在具有挑戰性的影像分類問題上被預訓練,該任務被重新用作字幕生成問題的特徵提取器。
...使用CNN作為影像的編碼器是很自然的,透過對影像分類任何進行預訓練,並將最後隱藏層作為輸入生成RNN解碼器。
— Show and Tell: A Neural Image Caption Generator, 2015
這種體系結構也被用於語音識別和自然語言處理問題,其中CNNs被用作語音和文字輸入資料上的LSTM的特徵提取器。該體系結構合適於以下的問題:
它們的輸入中具有空間結構,例如如下中的2D結構或畫素或句子、段落或者文件中的一維結構;
在其輸入中具有時間結構,或者影片中的影像順序或者文字中的單詞或者需要生產具有時間結構的輸出,例如文字描述中的單詞。
圖 8.1 CNN LSTM結構
8.1.2 實現
我們定義了一個CNN LSTM模型來在Keras中共同訓練。CNN LSTM可以透過在前端新增CNN層然後緊接著LSTM作為全連線層輸出來被定義。
這種體系結構可以被看做是兩個子模型:CNN模型做特徵提取,LSTM模型幫助教師跨時間步長的特徵。在假設輸入是影像的一系列的2D輸入情況下,讓我們來看看這兩個子模型的背景:
CNN模型
作為重新整理,我們可定義一個2D卷積網路,包括Conv2D和MaxPooling2D層,它們有序的排列在所需深度的堆疊中。Conv2D將解釋影像的快照(例如:小方塊),池化層將鞏固或抽象解釋。
例如,下面的片段期望以1個通道(例如:黑和白)讀取10X10畫素影像。Conv2D將讀取2X2快照中的影像並輸出一個新的10X10的影像解釋。MaxPooling2D將池化解釋為2X2的塊,將輸出減少到5X5合併。Flatten層將採用單個5X5對映,並將其轉換成25個元素的向量,以準備用於其他層處理,例如用於預測輸出的Dense。
cnn = Sequential()
cnn.add(Conv2D(1, (2,2), activation= 'relu' , padding= 'same' , input_shape=(10,10,1)))
cnn.add(MaxPooling2D(pool_size=(2, 2)))
cnn.add(Flatten())
表 8.1 CNN LSTM模型的部分CNN的例子
這在影像識別和其他的機器學習任務中是有效果的。
LSTM模型
上面的CNN網路只能處理單個影像,將其輸入畫素轉換為內部矩陣或者向量表示。我們需要在多個影像刪重複該操作,並允許LSTM透過輸入影像的內部向量表示的序列使用BPTT來建立內部狀態和更新權重。
CNN可以使用現有的預訓練模型如VGG進行影像特徵提取。CNN可能不被訓練,我們可能希望透過LSTM的錯誤從多個輸入影像反向傳播到CNN模型來訓練它。在這種情況下,概念上都有一個單一的CNN模型和一個LSTM序列,每個時間步長都有一個模型。我們希望將CNN模型應用於每個輸入影像,並將每個輸入影像的輸出作為單個時間步長傳遞給LSTM。
我們可以透過將整個CNN輸入模型(一層或多層)封裝在一個TimeDistributed層中來實現這一點。該層實現了多次應用相同層或層的期望結果。在這種情況下,將其多次應用於多個輸入時間步長,並依次向LSTM模型提供一系列影像解釋或者影像特徵。
model.add(TimeDistributed(...)) model.add(LSTM(...)) model.add(Dense(...))
表 8.2 CNN LSTM模型的LSTM部分的例子
我們有這個模型的兩個部分了,讓我們將它們放在一起吧!
CNN LSTM模型
我們可以在Keras中定義一個CNN LSTM模型的層,將它們包含在TimeDistributed層中,然後定義LSTM以及輸出層。我們有兩個方法來定義模型,這兩種定義的方法是等效的,只是在品位上有點區別。你可以首先定義CNN模型,然後透過將CNN層的整個序列包括在TimeDistributed層中的方法將其新增到LSTM模型中,例如:
# define CNN model
cnn = Sequential()
cnn.add(Conv2D(...))
cnn.add(MaxPooling2D(...))
cnn.add(Flatten())
# define CNN LSTM model
model = Sequential()
model.add(TimeDistributed(cnn, ...))
model.add(LSTM(..))
model.add(Dense(...))
表 8.3 CNN LSTM模型的兩個部分的例子
另一種,也許更容易閱讀的方法是將CNN模型中的每一層封裝在TimeDistributed中,然後將其新增到主模型中。
model = Sequential()
model.add(TimeDistributed(Conv2D(...))
model.add(TimeDistributed(MaxPooling2D(...)))
model.add(TimeDistributed(Flatten()))
model.add(LSTM(...))
model.add(Dense(...))
表 8.4 CNN LSTM模型一部分的例子
第二種方法的好處是所有的層都出現在模型的摘要中,因此這個是優選的。你可以選擇你喜歡的方法。
8.2 移動廣場影片預測問題
移動廣場影片預測問題是為了證明CNN LSTM。這個問題設計生產一系列的幀。在每副影像中,從左到右或者從右到左畫出一條線。每幀顯示一行畫素的擴充套件。任務是根據線在幀序列中向左或者向右移動為模型進行分類。技術上來說,這個問題是一個具有多對一預測模型的序列分類問題。
圖 8.2 採用many-to-many預測模型的移動廣場影片預測問題
測試問題可以分為下面的兩個步驟:
影像初始化;
新增步驟;
例項生成器。
8.2.1 影像初始化
我們可以透過定義一個填滿0值的2D的NumPy陣列來開始。我們可以使得影像對稱,在這種情況下,10個畫素高10個畫素寬。
from numpy import zeros
frame = zeros((10,10))
表 8.5 生成空的方塊影像的例子
下面,我們可以為行的第一步選擇行。我們將使用randint()函式來選擇一個0到9之間的統一隨機整數。
from random import randint
step = randint(0, 10-1)
表 8.6 選擇一個步長的例子
我們現在可以選擇是否在影像上畫左或右線。我們將使用random()函式來決定。如果是正確的,我們將從左邊或第0欄開始,如果離開,我們將從右邊開始,或者從第9欄開始。
from random import random
right = 1 if random() < 0.5 else 0
col = 0 if right else size-1
表 8.7 決定向左或者向右移動的例子
我們可以線的開始。
frame[step, col] = 1
表 8.8 標記線的開始的例子
8.2.2 新增步長
現在我們需要一個過程來增加步驟。下一步必須是前一步的函式。我們將榆樹它在下一列(左或者右),並在同一行中,上面的行或者下面的行。我們將進一步根據影像的邊界限制運動。我們可以使用上面相同的randint()函式來選擇下一個步驟,並將我們的運動約束價錢在上和下值上。上次選擇的不長值儲存在最後一步變數中。
lower = max(0, last_step-1)
upper = min(10-1, last_step+1)
step = randint(lower, upper)
表 8.9 選擇下一個步長的例子
下面,我們可以複製最後一幅影像並標記下一列的新位置。
column = i if right else size-1-i
frame = last_frame.copy()
frame[step, column] = 1
表 8.10 將步驟標記為新幀的示例
根據選定的方向,可以重複這個過程知道達到第一列或者最後一列。
8.2.3 示例生成器
我們可以在兩個小函式中鋪貨所有上述行為。build frames()函式使用一個引數來定義影像的大小,並返回一系列影像,以及該行是向右移動(1)還是向左移動(0)。這個函式呼叫另一個函式next frame()來建立當先在影像上移動時的每一個後續幀。
為了使問題具體化,我們可以繪製一個序列。我們將產生一個小序列與每個影像5X5畫素和5幀並繪製幀。
from numpy import zeros
from random import randint
from random import random
from matplotlib import pyplot
# generate the next frame in the sequence
def next_frame(last_step, last_frame, column):
# define the scope of the next step
lower = max(0, last_step-1)
upper = min(last_frame.shape[0]-1, last_step+1)
# choose the row index for the next step
step = randint(lower, upper)
# copy the prior frame
frame = last_frame.copy()
# add the new step
frame[step, column] = 1
return frame, step
# generate a sequence of frames of a dot moving across an image
def build_frames(size):
frames = list()
# create the first frame
frame = zeros((size,size))
step = randint(0, size-1)
# decide if we are heading left or right
right = 1 if random() < 0.5 else 0
col = 0 if right else size-1
frame[step, col] = 1
frames.append(frame)
# create all remaining frames
for i in range(1, size):
col = i if right else size-1-i
frame, step = next_frame(step, frame, col)
frames.append(frame)
return frames, right
# generate sequence of frames
size = 5
frames, right = build_frames(size)
# plot all feames
pyplot.figure()
for i in range(size):
# create a grayscale subplot for each frame
pyplot.subplot(1, size, i+1)
pyplot.imshow(frames[i], cmap= 'Greys')
# turn of the scale to make it cleaer
ax = pyplot.gca()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# show the plot
pyplot.show()
表 8.11 生成幀序列的示例
執行示例生成一個隨機序列並並排繪製幀。你可以看到,線從左到右繞著影像擺動,每一個時間步長一個畫素。
圖 8.3 線在影像中移動的的一系列幀的例子
8.2.4 準備模型輸入
最後,我們將準備一個函式來生成具有正確形狀的多個序列,以準備和評估LSTM模型。下面給出一個名為generate_examples()的函式,它將要生成的影像的大小和將要生成的序列的數量作為引數。
生成並儲存每個序列。重要的是,模型的輸入序列必須調整大小以適應2D CNN。通常情況下可以是:
[width, height, channels]
表 8.12 2D CNN模型預期輸入的例子
在我們的情況中,對於對稱的黑白影像來說,它是[size, size, 1]。這是不足夠的,因為我們也有多個影像,然後多個序列的影像。因此,對模型的輸入必須變型為:
[samples, timesteps, width, height, channels]
表 8.13 模型期望輸入形狀的例子
或者,在我們的函式中:
[n_patterns, size, size, size, 1]
表 8.14 使用來自問題定義的術語作為期望輸入形狀的例子
這個生成隨機影片的新的函式被列出如下:
# generate multiple sequences of frames and reshape for network input
def generate_examples(size, n_patterns):
X, y = list(), list()
for _ in range(n_patterns):
frames, right = build_frames(size)
X.append(frames)
y.append(right)
# resize as [samples, timesteps, width, height, channels]
X = array(X).reshape(n_patterns, size, size, size, 1)
y = array(y).reshape(n_patterns, 1)
return X, y
表 8.15 生成和變型模型的幀序列的例子
下面,讓我們定義並編譯模型。
8.3 定義和編譯模型
我們可以定義一個CNNLSTM來擬合模型。生成影像的大小決定了問題會多具有挑戰性。我們將透過配置影像到50X50畫素或者一共2500個二進位制值來使得問題不那麼具有挑戰。
# configure problem
size = 50
表 8.16 配置問題的例子
我們將會定義一個CNN模型中的每一層都包裹在一個單獨的TimeDistributed層中的模型。這是為了確保模型概要清楚地說明網路是如何連線在一起的。我們將定義一個Conv2D層作為輸出層,它具有兩個filter和一個2X2的核來掃描輸入影像。使用2個filter和常規情況下使用小的核是基於實驗的經驗的。Conv2D將會輸出2個49X49畫素的輸入印象。
卷積層經常直接跟著一個池化層。這裡我們使用一個pool大小為2X2的MaxPooling2D池化層,這將有效地減少前一層每一個輸出的大小,然後輸出2個24X24的對映。
池化層之後跟著的是Flatten層,它用於將來自於MaxPooling的2D層的[24,24,2] 3D輸出輸出轉換成一維具有1152個元素的向量。CNN模型是一個特徵提取的模型。希望的是,Flatten層的向量輸出是一個被壓縮的和/或更顯著的表示,而不是原始畫素值。
接下來,我們可以定義模型的LSTM模型。我們使用一個具有50個儲存單元的LSTM層,這是在一次次嘗試和錯誤之後配置的。在整個CNN模型中使用TimeDistribted wrapper意味著LSTM將會看到50個時間步長,每一個時間步長表示一個1152個元素的向量作為輸入。
這是一個二分類的問題,所以我們將使用一個具有一個單一神經元和sigmoid啟用函式的Dense輸出。該模型被編譯以將Adam用作梯度下降來最小化log損失(binary crossentropy),同時二分類的準確度將會被顯示出來。下面提供完整的程式碼列表:
# define the model
model = Sequential()
model.add(TimeDistributed(Conv2D(2, (2,2), activation= 'relu'), input_shape=(None,size,size,1)))
model.add(TimeDistributed(MaxPooling2D(pool_size=(2, 2))))
model.add(TimeDistributed(Flatten()))
model.add(LSTM(50))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
print(model.summary())
表 8.17 定義並編譯CNN LSTM模型的例子
執行例子,列印編譯模型的摘要。我們可以確定每個層輸出的期望形狀。
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
time_distributed_1 (TimeDist (None, None, 49, 49, 2) 10
_________________________________________________________________
time_distributed_2 (TimeDist (None, None, 24, 24, 2) 0
_________________________________________________________________
time_distributed_3 (TimeDist (None, None, 1152) 0
_________________________________________________________________
lstm_1 (LSTM) (None, 50) 240600
_________________________________________________________________
dense_1 (Dense) (None, 1) 51
=================================================================
Total params: 240,661
Trainable params: 240,661
Non-trainable params: 0
_________________________________________________________________
None
表 8.18 定義CNN LSTM模型的輸出的例子
8.4 擬合模型
我們現在準備好了在隨機生成的問題的例子上擬合模型。上面所定義的generate examples()函式準備了一些特定數量的隨機序列,這些隨機序列我們可以存在記憶體中,並用於有效地擬合模型。隨機生成的示例的數量是訓練週期(epoch)的數量的代理,因為我們更喜歡將模型訓練到特定的問題例項上,而不是一次又一次地重複同一個隨機例項。
在這裡,我們將在5000個隨機生成序列的單個週期(epoch)上訓練模型。理想情況下,LSTM的內部狀態將在每個序列的末尾被重置。我們可以透過將批大小(batch size)設定為1來實現這一點。我們將權衡模型的保真度和計算效率,並將批大小(batch size)設定為32。
# fit model
X, y = generate_examples(size, 5000)
model.fit(X, y, batch_size=32, epochs=1)
表 8.19 擬合編譯了的CNN LSTM模型的例子
執行例子將會顯示執行命令列時的進度條,它顯示每個批次(batch)結束時的損失和準確度。如果您在IDE或筆記本中執行該示例,可以透過設定verbose=0來開啟進度條。
5000/5000 [==============================] - 37s - loss: 0.1507 - acc: 0.9208
表 8.20 擬合CNN LSTM模型輸出的例子
我們可以透過亞牛股不同數量的樣本、週期(epoch)和批大小(batch size)來實驗。你可以開發一個學習能力更好並且整體訓練更少的模型嗎?
8.5 評價模型
現在模型被擬合了,我們可以在一個新的隨機序列上估計模型的學習能力。這裡,我們可以生成100個新的隨機序列,並估計模型的準確度。
# evaluate model
X, y = generate_examples(size, 100)
loss, acc = model.evaluate(X, y, verbose=0)
print('loss: %f, acc: %f' % (loss, acc*100))
表 8.21 衡量擬合了的CNN LSTM模型的例子
執行例子列印擬合模型的損失和精確度。這裡,我們可以看到模型達到了100%的準確度。你的結果可能不一樣,但是你如果沒有看到100%的準確度,嘗試著多執行例子幾次。
loss: 0.001120, acc: 100.000000
8.6 用模型進行預測
為了完整性,我們可以開發一個模型來在新的序列上做出預測。這裡我們生成了一個新的單個隨機序列,並預測線將會向左或者是向右移動。
# prediction on new data
X, y = generate_examples(size, 1)
yhat = model.predict_classes(X, verbose=0)
expected = "Right" if y[0]==1 else "Left"
predicted = "Right" if yhat[0]==1 else "Left"
print('Expected: %s, Predicted: %s' % (expected, predicted))
表 8.23 使用擬合了的CNN LSTM模型進行預測的例子
執行例子,列印出期望的編碼以及預測的值。
Expected: Right, Predicted: Right
表 8.24 用擬合了的CNN LSTM模型做出預測的輸出的例子
8.7 完整例子
為了完整性,下面提供了完整的程式碼列表供您參考。
from random import random
from random import randint
from numpy import array
from numpy import zeros
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import TimeDistributed
# generate the next frame in the sequence
def next_frame(last_step, last_frame, column):
# define the scope of the next step
lower = max(0, last_step-1)
upper = min(last_frame.shape[0]-1, last_step+1)
# choose the row index for the next step
step = randint(lower, upper)
# copy the prior frame
frame = last_frame.copy()
# add the new step
frame[step, column] = 1
return frame, step
# generate a sequence of frames of a dot moving across an image
def build_frames(size):
frames = list()
# create the first frame
frame = zeros((size,size))
step = randint(0, size-1)
# decide if we are heading left or right
right = 1 if random() < 0.5 else 0
col = 0 if right else size-1
frame[step, col] = 1
frames.append(frame)
# create all remaining frames
for i in range(1, size):
col = i if right else size-1-i
frame, step = next_frame(step, frame, col)
frames.append(frame)
return frames, right
# generate multiple sequences of frames and reshape for network input
def generate_examples(size, n_patterns):
X, y = list(), list()
for _ in range(n_patterns):
frames, right = build_frames(size)
X.append(frames)
y.append(right)
# resize as [samples, timesteps, width, height, channels]
X = array(X).reshape(n_patterns, size, size, size, 1)
y = array(y).reshape(n_patterns, 1)
return X, y
# configure problem
size = 50
# define the model
model = Sequential()
model.add(TimeDistributed(Conv2D(2, (2,2), activation= 'relu'), input_shape=(None,size,size,1)))
model.add(TimeDistributed(MaxPooling2D(pool_size=(2, 2))))
model.add(TimeDistributed(Flatten()))
model.add(LSTM(50))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
print(model.summary())
# fit model
X, y = generate_examples(size, 5000)
model.fit(X, y, batch_size=32, epochs=1)
# evaluate model
X, y = generate_examples(size, 100)
loss, acc = model.evaluate(X, y, verbose=0)
print('loss: %f, acc: %f' % (loss, acc*100))
# prediction on new data
X, y = generate_examples(size, 1)
yhat = model.predict_classes(X, verbose=0)
expected = "Right" if y[0]==1 else "Left"
predicted = "Right" if yhat[0]==1 else "Left"
print('Expected: %s, Predicted: %s' % (expected, predicted))
表 8.25 移動廣場預測問題中CNN LSTM模型的完整例子
8.8 擴充套件閱讀
本章節提供了進行擴充套件閱讀的一些資源。
8.8.1 CNN LSTM論文
Long-term Recurrent Convolutional Networks for Visual Recognition and Description, 2015.
Show and Tell: A Neural Image Caption Generator, 2015.
Convolutional, Long Short-Term Memory, fully connected Deep Neural Networks, 2015.
Character-Aware Neural Language Models, 2015.
Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting, 2015.](https://arxiv.org/abs/1506.04214)
8.8.2 Keras API
Conv2D Keras API. https://keras.io/layers/convolutional/#conv2d MaxPooling2D Keras API.
Flatten Keras API.
TimeDistributed Keras API.
8.9 總結
在本課程中,你發現了怎麼樣去開發一個CNN LSTM模型。特別地,你學到了:
CNN LSTM的原始結構以及它適合什麼樣型別的問題;
怎麼樣在Keras中應用CNN LSTM結構;
怎麼樣為移動廣場影片預測問題開發一個CNN LSTM模型。
在下一課中,你將會學習到怎麼樣開發一個Encoder-Decoder LSTM模型。