一文學會如何在Keras中開發LSTMs(附程式碼)

AMiner學術頭條發表於2018-12-24

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模型的資料並進行序列預測?(附程式碼)

我們還將繼續推出一系列的文章來介紹裡面的詳細內容。本文有11000字左右,閱讀需11分鐘,建議收藏學習。

4.0 前言

4.0.1 課程目標

本課程的目標是理解如何使用Python中的Keras深度學習庫來定義、擬合和評價LSTM模型。完成這一課之後,你會知道:

  • 如何定義一個LSTM模型,包括怎麼樣將你的資料變型成為所需要的3D輸入;

  • 如何擬合和評估你的LSTM模型並且將其用來預測新的資料;

  • 如何對模型中的內部狀態以及當其重置時進行細粒度的控制。

4.0.2 課程概覽

本課程分為7個部分,它們是:

  1. 定義模型;

  2. 編譯模型;

  3. 擬合模型;

  4. 評價模型;

  5. 用模型做預測;

  6. LSTM狀態管理;

  7. 準備資料的樣例。

讓我們開始吧!

4.1 定義模型

第一步是定義你的網路。Keras中的神經網路被定義為一系列的層。這些層的容器是 Sequential類。第一步是建立 Sequential類的例項。然後你可以建立你的層,並按照它們應該的連線的順序新增它們。由儲存單元組成的LSTM迴圈層被稱為 LSTM()。一個全連線層經常跟著一個LSTM層,用於輸出一個預測的層被稱為 Dense()

例如,我們可以定義一個具有兩個儲存單元的LSTM隱藏層,連線著帶有一個神經元的一個全連線輸出層,如下:

  1. model = Sequential()

  2. model.add(LSTM(2))

  3. model.add(Dense(1))

表 4.1 定義一個LSTM模型的例子

但是我們也可以透過建立一個層的陣列,並將其傳遞給 Sequential類的建構函式來完成這一步。

  1. layers = [LSTM(2), Dense(1)]

  2. model = Sequential(layers)

表 4.2 第二個定義一個LSTM模型的例子

網路中的第一個隱藏層必須定義期望輸出的數量,例如輸入層的形狀。輸入必須是三維的,由樣本、時間步長和特徵組成。

  • 樣本。它們是你資料中的行。一個樣本可以是一個序列。

  • 時間步長。這些是對特徵的過去觀察值,例如滯後的變數。

  • 特徵。這些是資料中的列。

假設你的資料被載入為了一個NumPy陣列,你可以使用NumPy中的thereshape() 函式將1D或者2D的資料集轉換為3D資料集。您可以呼叫您的NumPy陣列中的NumPy函式,並傳遞給一個元組的維數給它,該維數就是你需要轉成多少維的值。假設我們在NumPy陣列中有兩個列的輸入資料(X),我們可以把這兩個列當做兩個時間步長來對待,將其改寫如下:

  1. data = data.reshape((data.shape[0], data.shape[1], 1))

表 4.3 變換有1個時間步長的NumPy陣列的例子

如果你想讓你的2D資料中的列成為一個時間步長的特徵,你可以變形,如下:

  1. data = data.reshape((data.shape[0], 1, data.shape[1]))

表 4.4 變換有1個時間步長的NumPy陣列的例子

你可以指定輸入形狀引數,該引數期望是一個包含時間步長和特徵數量的元祖。例如,如果我們有一個單變數序列的兩個時間步長和一個特徵,每行具有兩個之後的觀測值,則具體如下:

  1. model = Sequential()

  2. model.add(LSTM(5, input_shape=(2,1)))

  3. model.add(Dense(1))

表 4.5 定義LSTM模型的輸入的例子

樣本的數量不必是指定的。模型假定一個或者多個樣本,留給你定義的只有時間步長以及特徵。本課的章節提供了為LSTM模型準備輸入資料的附加示例。

Sequential模型想象成為一個管道,一端輸入原始資料,另外一端輸出預測結果。在Keras中這是一個很有用的容器,它傳統上是和一層相聯絡的,可以可以拆分並新增為一個單獨的層。這樣可以清楚地表明,它們在資料從輸入到預測的轉換中的作用。例如,啟用函式,即變換一個從來自於一層的每一個神經元的求和的訊號,可以被提取出來,並作為一個像層一樣的物件,叫做Activition,新增到 Sequential中。

  1. model = Sequential()

  2. model.add(LSTM(5, input_shape=(2,1)))

  3. model.add(Dense(1))

  4. model.add(Activation('sigmoid'))

表 4.6 在輸出層帶有sigmoid啟用函式LSTM模型的例子

啟用函式的選擇對於輸出層來說是最重要的,因為它將決定預測所採用的格式。例如,下面是一些常見的預測建模問題型別和結構以及在輸出層中使用的標準啟用函式

  • 迴歸。線性啟用函式,或者線性,神經元的數量和輸出的數量相匹配。這是用於全連線層(Dense)層的預設啟用函式

  • 二分類(2類)邏輯啟用函式,或者sigmoid,一個神經元的輸出層。

  • 多分類(大於2類)。Softmax啟用函式,或者softmax,假設是one hot 編碼輸出模式每個類值一個輸出神經元

4.2 編譯模型

一旦開發了我們的網路,我們就必須編譯它。編譯是一個有效的步驟。它將我們所定義的簡單層序列轉換成一系列高效的矩陣變換格式,以便在GPU或者CPU上執行,這取決於Keras是如何配置的。將編譯看做是一個網路的預計算步驟。一個模型建立之後總是需要編譯的。

編譯需要多個引數被指定,尤其是那些需要調整來訓練你網路的。具體來說,用於網路訓練的最佳化函式以及用於評價網路的損失函式,它被最佳化函式最小化了。

例如,下面是編譯指定梯度下降(sgd)和均方差(mse)的損失函式模型的情況,用於解決迴歸型別的問題。

  1. model.compile(optimizer= 'sgd' , loss= 'mse' )

表 4.7 編譯LSTM模型的例子

或者,在提供編譯不走的引數之前建立和配置 optimizer

  1. algorithm = SGD(lr=0.1, momentum=0.3)

  2. model.compile(optimizer=algorithm, loss= 'mse' )

表 4.8 用SGD最佳化演算法編譯一個LSTM模型的例子

預測建模問題的型別可對所使用的損失函式進行約束。例如,下面是不同的預測模型型別的一些標準損失函式

  • 迴歸:均方誤差,或者 mean squared error,簡稱 mse

  • 二分類(2類):對數損失,也叫做交叉熵或者 binary crossentropy

  • 多分類(大於2類):多分類的對數損失, categorical crossentropy

最常見的最佳化演算法是經典的隨機梯度下降演算法,但是Keras還支援一套其他擴充套件的經典最佳化演算法,這種演算法表現很好、配置很少。由於它們通常的效能更好,也許最常用的最佳化演算法是:

  • Stochastic Gradient Descent,或者sgd。

  • Adam,或者adam。

  • RMSprop,或者rmsprop。

最後,除了損失函式外,你也可以指定效能指標(Metrics)來收集擬合你模型時候的資訊。總的來說,最有用的附加效能指標(Metrics)來收集的是分類問題的準確性(例如‘accuracy’或者簡稱‘acc’ )。用來收集的效能指標(Metrics)可以透過效能指標(Metrics)陣列中的名稱或者損失函式的名字來指定。例如:

  1. model.compile(optimizer= 'sgd' , loss= 'mean_squared_error' , metrics=[ 'accuracy' ])

表 4.9 用一個效能指標(Metrics)編譯LSTM模型的例子

4.3 擬合模型

一旦網路被編譯,它就可以擬合,這意味著適應訓練資料集上的權重。擬合網路需要指定訓練資料集,包括輸入模式的效能指標(Metrics)X以及和輸出型別匹配的陣列 y。網路採用基於時間的反向傳播演算法進行訓練,並根據最佳化演算法和損失函式特性對模型進行最佳化。

反向傳播演算法要求對網路進行訓練資料集中的所有序列進行特定數量的週期或者暴露訓練集中的所有序列。每個週期(epoch)可以被分為成組的輸入-輸出模式對,這被稱為批次(batches)。這定義了網路在一個時代內更新權重之前所暴露的模式的數量。它也是一種有效的最佳化,確保在一段時間內沒有太多的輸入模式被載入到儲存器中。

  • Epoch。遍歷訓練資料集中的所有樣本,並更新網路權重。LSTM可以訓練及時、幾百或者數千個週期(epoch)。

  • Batch。透過訓練資料集中的樣本子集,然後更新網路權值。一個週期(epoch)是由一個或者多個批次(batch)組成的。

下面是batch size一些常用的配置:

  • batch_size=1。在每個樣本之後更新權重,並且該過程被稱為隨機梯度下降

  • batch_size=32。通常的值是32,64和128,調整來符合預期的效率和模型更新速率。如果批大小(batch size)不是一個週期(epoch)中一個樣本數量的因素,那麼在週期(epoch)的結尾,則在結束時執行剩餘樣本的一個額外的批大小(batch size)。

  • batch_size=n。其中,n是訓練資料集的樣本數。權重在每個週期(epoch)中被更新,這個過程被稱為批梯度下降(batch gradient descent)。

batch size為32的小批次梯度下降法(Mini-batch gradient descent)是LSTM的一個常見配置。擬合網路的一個例子如下:

  1. model.fit(X, y, batch_size=32, epochs=100)

表 4.10 擬合LSTM模型的例子

一旦擬合,返回一個歷史物件,該物件在訓練期間提供對模型姓名的總結。當編譯模型的時候,這包含損失和任何的額外的效能指標(Metrics)被指定,並在每個週期(epoch)被記錄。這些效能指標(Metrics)可以被記錄、繪製和分析,以瞭解網路在訓練資料集上是過擬合(overfitting)還是欠擬合(underfittting)。

根據網路規模和訓練資料的大小,訓練可能會花費很長的時間,從秒到數天。預設情況下,進度條顯示在每個epoch的命令列上。這可能會對你造成很多的噪音,或者會對你的環境造成問題,比如你在一個互動式的筆記本或者IDE中。你可以透過將 verbose引數設定為2來將顯示的資訊量減少到僅損失的每個週期(epoch)。你可以透過設定 verbose為0。例如:

  1. history = model.fit(X, y, batch_size=10, epochs=100, verbose=0)

表 4.11 擬合一個LSTM模型並檢索無 verbose輸出的歷史的例子

4.4 評價模型

一旦網路被訓練,它就可以被評估。網路可以對訓練資料進行評估,但是這不會作為預測模型提供網路效能的有用指示,因為它以前見過所有這些資料。我們可以在一個單獨的資料集上評估網路的效能,在測試期間看不見。在未來對於看不見的資料,這將提供網路效能的估計。

該模型評估了所有測試模式的損失,以及模型編譯時的任何其他度量,如分類精度。返回一個評估的度量表。如,對於使用精度度量編譯模型,我們可以在新的資料集上對其進行評估如下:

  1. loss, accuracy = model.evaluate(X, y)

表 4.12 衡量一個LSTM模型的例子

與網路一樣,提出 verbose輸出的概念是為了評價模型的進度。我們可以透過將 verbose引數設定為0來關閉這個。

  1. loss, accuracy = model.evaluate(X, y, verbose=0)

表 4.13 衡量一個沒有 verbose輸出的LSTM的例子

4.5 預測模型

一旦對我們的擬合模型的效能感到滿意,我們可以用它來預測新的資料。這就在帶有新輸入模式陣列的模型上呼叫 predict()函式一樣簡單。例如:

  1. predictions = model.predict(X)

表 4.14 在擬合LSTM模型上預測的例子

預測將以網路的輸出層提供的格式返回。在迴歸問題的情況下,這些預測可以直接和線性啟用函式提供問題的格式一樣。

對於一個二分類問題,預測可以使第一類的機率陣列,它可以透過舍入轉換為1或0。對於一個多分類問題,結果可能是一個機率陣列的形式(假設是一個one hot編碼輸出變數),它可能需要使用NumPy的argmax()函式轉換成單類輸出預測。另外,對於分類問題,我們可以使用 predict_classes()函式來自動地將不清晰的預測轉換為清晰的整數類值。

  1. predictions = model.predict_classes(X)

表 4.15 擬合LSTM模型上預測類別的例子

隨著對網路的擬合和評估, verbose被輸出以便用於反映模型預測的進度。我們可以透過將 verbose引數設定為0來關閉這個。

  1. predictions = model.predict(X, verbose=0)

表 4.16 沒有 verbose輸出時預測的例子

根據擬合LSTM模型來預測將會在第十三章中詳細的講述。

4.6 LSTM狀態管理

每個LSTM儲存單元都保持著積累的內部狀態。這種內部狀態可能需要在網路訓練和預測時對序列問題進行詳細的管理。預設情況下,網路中的所有LSTM儲存單元的內部狀態在每個批次之後被重置,例如,當網路權重被更新時。這意味著批次大小的配置在三個時期上施加了張力:

  • 學習的正確性,或更新前處理多少個樣本;

  • 學習的速度,或者權重的更新頻率;

  • 內部狀態,或內部狀態重置頻率。

透過頂一個LSTM層作為狀態,Keras提供了將內部狀態更新重置的很靈活的方式。這可以透過將LSTM層上的狀態引數設定為true來實現。當使用狀態LSTM層時,還必須透過設定輸入形狀引數來確定網路中輸入形狀的批大小(batch size),並且批處理大小必須是訓練資料集中樣本數量的一個因素。

批輸入(batch input)形狀引數需要一個定義為批處理大小、時間步長和特徵的三維陣列。

例如,我們可以定義一個狀態LSTM,在訓練資料集上訓練100個樣本,10個批次大小(batch size),1個特徵的5個時間步長,如下:

  1. model.add(LSTM(2, stateful=True, batch_input_shape=(10, 5, 1)))

表 4.17 定義一個帶狀態的LSTM層的例子

狀態LSTM不會在每個批次結束時重置內部傳狀態。相反,您可以透過呼叫 reset_states()函式對何時重置內部狀態進行細粒度控制。例如,我們可能希望在每個週期(epoch)結束時重置內部狀態,我們可以這樣做:

  1. for i in range(1000):

  2.    model.fit(X, y, epochs=1, batch_input_shape=(10, 5, 1))

  3.    model.reset_states()

表 4.18 一個狀態LSTM手動迭代訓練批次的例子

在進行預測時,也必須使用相同狀態的LSTM中的相同批次大小(batch size)。

  1. predictions = model.predict(X, batch_size=10)

例 4.19 用狀態LSTM預測的例子

LSTM層的內部狀態也在評估網路和進行預測時積累。因此,如果使用的是狀態LSTM,則必須在驗證資料集或者預測之後對網路進行重置狀態。

在預設情況下,一個週期(epoch)的狀態被洗牌。在使用神經網路的多層感知機進行工作的時候這是一個很好的做法。如果您嘗試在樣本間儲存狀態,那麼訓練資料集中的樣本順序可能是重要的並且必須保留。這可以透過設定 shuffle引數為False來完成,例如:

  1. for i in range(1000):

  2.    model.fit(X, y, epochs=1, shuffle=False, batch_input_shape=(10, 5, 1))

  3.    model.reset_states()

表 4.20 擬合一個狀態LSTM時使樣本不shuffle的例子

為了使這個更具體,下面是管理狀態的3個常見的例子:

  • 在每個序列結束的時候進行預測,並且序列是獨立的。狀態應該在每個序列之後重置,透過將批次大小設定為1。

  • 長序列被分割成多個子序列(每個樣本具有許多的時間步長)。狀態應該在網路暴露於整個序列之後透過LSTM狀態化,關閉子序列的洗牌(shuffling),並在每個週期(epoch)之後重置狀態之後被重置。

  • 一個非常長的子序列被分成多個子序列(每個樣本有許多時間步長)。訓練質量比長期內部狀態更重要,使用128個樣本的批次大小(batch size),然後更新網路權重和狀態重置。

我鼓勵你頭腦風暴你的序列預測問題和網路配置的許多不同的框架,測試和選擇那些在預測誤差方面最有希望的模型。

4.7 準備資料的例子

很難理解如何準備您的序列資料以輸入到LSTM模型。通常,圍繞如何定義LSTM模型的輸入層存在混淆。關於如何將您的序列資料轉換為一維或二維的數字矩陣到LSTM輸入層所需的3D格式也存在混淆。在這一節中,您將透過兩個示例來修改序列資料,並將輸入層改為LSTM模型。

4.7.1 單個輸入樣本的LSTM的例子

考慮一個具有多個時間步長和一個特徵的序列。例如,這可以是10個值的序列:

  1. 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0

表 4.21 序列的例子

我們可以將這個序列的數字定義為NumPy陣列。

  1. from numpy import array

  2. data = array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])

表 4.22 將一個序列定義為NumPy陣列的例子

我們可以使用NumPy的 reshape()函式來將這個一維陣列變成三維陣列,每個時間步長上有1個樣本、10個時間步長和一個特徵。在陣列上呼叫時, reshape()函式採用一個引數,它是陣列的新的形狀。我們不能傳遞任何一組數字,變換必須要均勻地重新排列陣列中的資料。

  1. data = data.reshape((1, 10, 1))

表 4.23 變換一個陣列的例子

一旦變換了,我們可以輸出陣列新的形狀。

  1. print(data.shape)

表 4.24 列印序列新形狀的例子

將所有的都放在一起,完整的例子展示如下:

  1. from numpy import array

  2. data = array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])

  3. data = data.reshape((1, 10, 1))

  4. print(data.shape)

  5. print(data)

表 4.25 變換一個樣本的例子

執行例子列印單個樣本的3D形狀。

  1. (1, 10, 1)

  2. [[[ 0.1]

  3.  [ 0.2]

  4.  [ 0.3]

  5.  [ 0.4]

  6.  [ 0.5]

  7.  [ 0.6]

  8.  [ 0.7]

  9.  [ 0.8]

  10.  [ 0.9]

  11.  [ 1. ]]]

表 4.26 單個樣本變型輸出的例子

這個資料透過設定 inpput_shape=(10,1)現在已經準備好被用於輸入(X)到LSTM模型中。

  1. model = Sequential()

  2. model.add(LSTM(32, input_shape=(10, 1)))

  3. ...

表 4.27 定義LSTM模型輸入層的例子

4.7.2 多輸入特徵的LSTM例子

考慮到你的模型可能有多個並行序列作為輸入的情況。例如,下面是有10個值的兩個並行的序列:

  1. series 1: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 series 2: 1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1

表 4.28 並行序列的例子

我們可以將這些資料定義為2列10行的資料:

  1. from numpy import array

  2. data = array([ [0.1, 1.0], [0.2, 0.9], [0.3, 0.8], [0.4, 0.7], [0.5, 0.6], [0.6, 0.5], [0.7, 0.4], [0.8, 0.3], [0.9, 0.2], [1.0, 0.1]])

表 4.29 將並行序列定義為一個NumPy陣列的例子

這個資料可以被化為具有10個時間步長和2個特徵的1個樣本,它可以變形為3D陣列如下:

  1. data = data.reshape(1, 10, 2)

表 4.30 變形一個序列的例子

將所有這些放在一起,完整的例子顯示如下:

  1. from numpy import array

  2. data = array([ [0.1, 1.0], [0.2, 0.9], [0.3, 0.8], [0.4, 0.7], [0.5, 0.6], [0.6, 0.5], [0.7, 0.4], [0.8, 0.3], [0.9, 0.2], [1.0, 0.1]])

  3. data = data.reshape(1, 10, 2)

  4. print(data.shape)

表 4.31 變形並行序列的例子

執行例子,列印單個樣本新的3D形狀如下:

  1. (1, 10, 2)

表 4.32 變換並行序列輸出的例子

這個資料現在已經透過設定 input_shape=(10,2)準備好被用作輸入(X)到LSTM模型中。

  1. model = Sequential()

  2. model.add(LSTM(32, input_shape=(10, 2)))

  3. ...

表 4.33 定義LSTM模型輸入層的例子

4.7.3 LSTM輸入的建議

本章節列出了一些幫助您準備LSTM輸入資料的提示:

  • LSTM輸入層必須是3D的;

  • 3個輸入維度的含義是:樣本、時間步長和特徵。

  • LSTM輸入層在輸入隱藏層上由輸入形狀引數決定。

  • 輸入形狀引數採用兩個值的元組,以減少時間步長和特徵的數量。

  • 假設樣本的數量是1個或者更多。

  • NumPy陣列中的 reshape()函式可以用來將1D或者2D資料變換為3D的。

  • reshape()函式將元組作為新的形狀的引數

4.8 擴充套件閱讀

本章節提供一些用於擴充套件閱讀的資料。

4.8.1 Keras APIs

  • Keras API for Sequential Models.

  • Keras API for LSTM Layers.

  • Keras API for optimization algorithms.

  • Keras API for loss functions.

4.8.2 其它APIs

  • NumPy reshape() API.

  • NumPy argmax() API.

4.9 擴充套件

你想深入瞭解Keras中LSTMs的生命週期嗎?這個章節列出了本課程中一些具有挑戰性的擴充套件。

  • 列出5個序列預測的問題並重點說明怎麼樣將資料分割成為樣本、時間步長以及特徵;

  • 列出5個序列預測問題並單獨指出每個輸出層的啟用函式

  • 研究Keras矩陣以及損失函式,並列出5個可以用於迴歸序列預測問題和分類序列預測問題的5個矩陣;

  • 研究Keras歷史物件並編寫示例程式碼來建立Matplotlib的線圖,該圖是從擬合一個LSTM模型捕獲的矩陣;

  • 列出5個序列預測問題,以及如何使得網路更好地管理每個節點的內部狀態。

在網上釋出你的擴充套件並與我分享連結。我很想知道你是怎麼樣想的!

4.10 總結

在本課程中,你發現了使用Keras庫的LSTM迴圈神經網路的5步生命週期。特別地,你學到了:

  • 怎麼樣定義一個LSTM模型,包括怎麼樣去將你的資料變型為所需要的3D輸入;

  • 怎麼樣定義和評價你的LSTM模型,以及怎麼樣使用它在新資料上做預測;

  • 怎麼樣對模型中的內部狀態以及當其重置時進行細粒度的控制。

相關文章