如何理解Keras中的TimeDistributed層並在LSTM中使用

    老規矩,主要框架譯自How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python~,中間加了一點點自己的理解。


    長短時記憶網絡(LSTMs)是一種流行且功能強大的循環神經網絡(RNN)。它們很難配置和應用於任意序列預測問題,即使使用定義良好且“易於使用”的接口(如Python中的Keras深度學習庫中提供的接口)也是如此。Keras中出現這種困難的一個原因是使用了TimeDistributed包裝器層,並且需要一些LSTM層返回序列而不是單個值。

    在本教程中,您將發現爲序列預測配置LSTM網絡的不同方法、TimeDistributed層所扮演的角色以及如何正確使用它。

    完成本教程後,您將知道:

  •     如何設計一個用於序列預測的一對一LSTM。
  •     如何在沒有TimeDistributed層的情況下設計多對一LSTM進行序列預測。
  •     如何利用TimeDistributed層設計多對多LSTM進行序列預測。

    讓我們開始吧。

教程概述

    本教程分爲五個部分;它們是:

  1. TimeDistributed層
  2. 序列學習問題
  3. 用於序列預測的一對一LSTM
  4. 用於序列預測的多對一LSTM(沒有TimeDistributed)
  5. 用於序列預測的多對多LSTM (TimeDistributed)

TimeDistributed層

    LSTMs功能強大,但是很難使用和配置,尤其是對於初學者。附加的複雜性是TimeDistributed層(和前TimedistributedDense層),它被神祕地描述爲一個層包裝器:

    這個包裝器允許我們對輸入的每個時間片應用一個層。

     您應該如何以及何時在LSTMs中使用這個包裝器?

    當您搜索關於Keras GitHub問題和StackOverflow上的包裝器層的討論時,這種混淆就更加複雜了。

    例如,在“When and How to use TimeDistributedDense”一文中,fchollet (Keras的作者)解釋道:

    TimeDistributedDese對三維張量的每個時間步長應用相同的Dense(全連接)操作。

    如果您已經瞭解TimeDistributed層的用途以及何時使用它,那麼這是非常有意義的,但是對初學者毫無幫助。

    本教程的目的是通過一些工作示例來澄清使用帶有LSTMs的TimeDistributed包裝器的困惑,您可以查看、運行和使用這些示例來幫助您具體理解。

序列學習問題

    我們將使用一個簡單的序列學習問題來演示TimeDistributed層。

    在這個問題中,序列[0.0,0.2,0.4,0.6,0.8]一次作爲一個輸入,然後必須作爲輸出返回,一次作爲一個項。把它看作是學習一個簡單的回聲程序。我們給出0.0作爲輸入,我們希望看到0.0作爲輸出,對序列中的每一項重複執行。我們可以直接生成這個序列如下:

from numpy import array
length = 5
seq = array([i/float(length) for i in range(length)])
print(seq)

     運行此示例將打印生成的序列:

    該示例是可配置的,如果您願意,稍後還可以自己處理較長/較短的序列。請在評論中告訴我你的結果。

用於序列預測的一對一LSTM

    在我們深入研究之前,重要的是證明這個序列學習問題可以分段學習。也就是說,我們可以將問題重新構造爲序列中每個項的輸入-輸出對的數據集。給定0,網絡應該輸出0,給定0.2,網絡必須輸出0.2,以此類推。

    這是該問題最簡單的公式,它要求將序列分解爲輸入-輸出對,並對序列進行一次一步預測,然後在網絡外部收集序列。輸入-輸出對如下:

    LSTMs的輸入必須是三維的(三維的結構是[樣本批大小,滑窗大小,特徵數量],詳細介紹可以參考博客lstm數據格式與老鼠屎的舊博文Keras實戰:基於LSTM的股價預測方法)。

è¿éåå¾çæè¿°                                                                           圖1 RNN結構圖(摘抄自lstm數據格式

è¿éåå¾çæè¿°

                                                                    圖2 batch結構圖(摘抄自lstm數據格式

è¿éåå¾çæè¿°

                                                                          圖3 LSTM輸入訓練過程(摘抄自lstm數據格式

    因此,其輸入格式爲[batch,n_steps,n_input]。

    我們可以用5個樣本,1個時間步長,1個特徵將二維序列重新塑造成三維序列。我們將輸出定義爲5個帶有1個特性的示例。

X = seq.reshape(5, 1, 1)
y = seq.reshape(5, 1)

     我們將網絡模型定義爲具有1個輸入和1個時間步長。第一個隱藏層將是一個具有5個單元的LSTM。輸出層爲1輸出的全連接層。該模型將採用有效的Adam優化算法和均方誤差損失函數進行擬合。批處理大小被設置爲epoch中的樣本數量,以避免必須使LSTM有狀態並手動管理狀態重置,儘管在每個樣本顯示到網絡後更新權重也可以輕鬆地做到這一點。

    完整的代碼清單如下:

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(len(seq), 1, 1)
y = seq.reshape(len(seq), 1)
# define LSTM configuration
n_neurons = length
n_batch = length
n_epoch = 1000
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(1, 1)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result:
	print('%.1f' % value)

    運行示例首先打印已配置網絡的結構。我們可以看到LSTM層有140個參數。根據輸入數(1)和輸出數(隱層5個單元5個)計算,如下(這個地方可以參考老鼠屎的舊博文Keras實戰:基於LSTM的股價預測方法):

n = 4 * ((inputs + 1) * outputs + outputs^2)
n = 4 * ((1 + 1) * 5 + 5^2)
n = 4 * 35
n = 140

     我們還可以看到,全連接層只有6個參數表示輸入的數量(前一層的5個輸入爲5個)、輸出的數量(層中1個神經元爲1個)和偏置。

n = inputs * outputs + outputs
n = 5 * 1 + 1
n = 6

 

    網絡正確地學習預測問題。 

用於序列預測的多對一LSTM(沒有TimeDistributed) 

    在本節中,我們將開發一個LSTM來一次性輸出序列,儘管沒有TimeDistributed包裝層。LSTMs的輸入必須是三維的。我們可以用1個樣本,5個時間步長,1個特徵將二維序列重新塑造成三維序列。我們將把輸出定義爲一個包含5個特性的示例。

X = seq.reshape(1, 5, 1)
y = seq.reshape(1, 5)

     您馬上就可以看到,必須稍微調整問題定義,以支持在沒有TimeDistributed包裝器的情況下進行序列預測的網絡。具體來說,輸出一個向量,而不是一步一步地構建一個輸出序列。這種差別聽起來可能很細微,但對於理解TimeDistributed包裝器的角色很重要。

    我們將定義模型爲一個輸入,包含5個時間步驟。第一個隱藏層將是一個具有5個單元的LSTM。輸出層爲5個神經元的全連接層。

# create LSTM
model = Sequential()
model.add(LSTM(5, input_shape=(5, 1)))
model.add(Dense(length))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())

     接下來,我們將模型只適用於500個epoch,對於訓練數據集中的單個樣本,批處理大小爲1。

# train LSTM
model.fit(X, y, epochs=500, batch_size=1, verbose=2)

     綜上所述,完整的代碼清單如下所示。

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(1, length, 1)
y = seq.reshape(1, length)
# define LSTM configuration
n_neurons = length
n_batch = 1
n_epoch = 500
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(length, 1)))
model.add(Dense(length))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result[0,:]:
	print('%.1f' % value)

     運行示例首先打印已配置網絡的summary。我們可以看到LSTM層有140個參數,如上一節所示。


    LSTM單元已經損壞,每個輸出一個值,提供一個5個值的向量作爲全連接層的輸入。時間維度或序列信息已被丟棄並分解爲一個包含5個值的向量。

    我們可以看到,全連接的輸出層有5個輸入,預計輸出5個值。我們可以將需要學習的30個權重解釋如下:

n = inputs * outputs + outputs
n = 5 * 5 + 5
n = 30

    模型擬合良好,打印出預測序列前的打印損耗信息。

    序列被正確地複製,但是作爲單個塊而不是逐步地通過輸入數據。我們可能使用了一個Dense層作爲第一個隱藏層,而不是LSTMs,因爲這種LSTMs的使用並沒有充分利用它們在序列學習和處理方面的全部能力。

 

用於序列預測的多對多LSTM (TimeDistributed) 

    在本節中,我們將使用TimeDistributed層來處理LSTM隱藏層的輸出。

    當使用TimeDistributed包裝層時,有兩個關鍵點需要記住:

  1. 輸入必須(至少)是3D的。這通常意味着您需要在TimeDistributed包裝Dense層之前配置最後一個LSTM層來返回序列(例如,將“return_sequences”參數設置爲“True”)。
  2. 輸出將是3D的。這意味着,如果您的TimeDistributed包裝Dense層是您的輸出層,並且您正在預測一個序列,那麼您將需要將y數組調整爲一個3D向量。

    那麼究竟什麼是TimeDistributed層呢?原諒我念了很久才懂一點點~這裏引用一下When and How to use TimeDistributedDenseEdwardRaff的回答(TimeDistributed層的意義):

    RNNs are capable of a number of different types of input / output combinations, as seen below.

RNN types

    The TimeDistributedDense layer allows you to build models that do the one-to-many and many-to-many architectures. This is because the output function for each of the "many" outputs must be the same function applied to each timestep. The TimeDistributedDense layers allows you to apply that Dense function across every output over time. This is important because it needs to be the same dense function applied at every time step.

    If you didn't not use this, you would only have one final output - and so you use a normal dense layer. This means you are doing either a one-to-one or a many-to-one network, since there will only be one dense layer for the output.

     關於其作用,Keras官方 API解釋地更爲簡單粗暴:

keras.layers.TimeDistributed(layer)

    這個封裝器將一個層應用於輸入的每個時間片。

    輸入至少爲 3D,且第一個維度應該是時間所表示的維度。

    考慮 32 個樣本的一個 batch, 其中每個樣本是 10 個 16 維向量的序列。 那麼這個 batch 的輸入尺寸爲 (32, 10, 16), 而 input_shape 不包含樣本數量的維度,爲 (10, 16)

    你可以使用 TimeDistributed 來將 Dense 層獨立地應用到 這 10 個時間步的每一個:

# 作爲模型第一層
model = Sequential()
model.add(TimeDistributed(Dense(8), input_shape=(10, 16)))
# 現在 model.output_shape == (None, 10, 8)

    輸出的尺寸爲 (32, 10, 8)

    在後續的層中,將不再需要 input_shape

model.add(TimeDistributed(Dense(32)))
# 現在 model.output_shape == (None, 10, 32)

    輸出的尺寸爲 (32, 10, 32)

 TimeDistributed 可以應用於任意層,不僅僅是 Dense, 例如運用於 Conv2D 層:

model = Sequential()
model.add(TimeDistributed(Conv2D(64, (3, 3)),
                          input_shape=(10, 299, 299, 3)))

 

    這裏可以結合上面的圖3進行理解。相信我,讀10遍以上,意思慢慢地就瞭解了。 

    下面繼續我們的教程。我們可以定義輸出的形狀爲1個樣本,5個時間步長,1個特徵,就像輸入序列一樣,如下:

y = seq.reshape(1, length, 1)

    通過將“return_sequences”參數設置爲true,我們可以定義LSTM隱藏層來返回序列,而不是單個值。 

model.add(LSTM(n_neurons, input_shape=(length, 1), return_sequences=True))

     這使得每個LSTM單元返回一個由5個輸出組成的序列,在輸入數據中的每個時間步對應一個輸出,而不是像前面示例中那樣只返回一個輸出值。

    我們還可以使用輸出層上的TimeDistributed將一個完全連接的Dense層封裝爲一個輸出。

model.add(TimeDistributed(Dense(1)))

     輸出層中的單個輸出值是關鍵。它強調,我們打算爲輸入中的每個時間步從序列中輸出一個時間步。碰巧我們一次要處理輸入序列的5個時間步長。

    TimeDistributed通過在LSTMs輸出上應用相同的Dense層(相同的權重),每次執行一個步驟來實現這個技巧。這樣,輸出層只需要一個到每個LSTM單元的連接(加上一個偏置)。

    因此,需要增加培訓期的數目,以應付較小的網絡能力。爲了匹配第一個一對一的例子,我將它從500加倍到1000。

    綜上所述,下面提供了完整的代碼清單。

from numpy import array
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import TimeDistributed
from keras.layers import LSTM
# prepare sequence
length = 5
seq = array([i/float(length) for i in range(length)])
X = seq.reshape(1, length, 1)
y = seq.reshape(1, length, 1)
# define LSTM configuration
n_neurons = length
n_batch = 1
n_epoch = 1000
# create LSTM
model = Sequential()
model.add(LSTM(n_neurons, input_shape=(length, 1), return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(loss='mean_squared_error', optimizer='adam')
print(model.summary())
# train LSTM
model.fit(X, y, epochs=n_epoch, batch_size=n_batch, verbose=2)
# evaluate
result = model.predict(X, batch_size=n_batch, verbose=0)
for value in result[0,:,0]:
	print('%.1f' % value)

    運行這個示例,我們可以看到所配置網絡的結構。我們可以看到,在前面的示例中,LSTM隱藏層中有140個參數。

    全連接的輸出層則完全不同。事實上,它與一對一的例子完全匹配。對於前一層的每個LSTM單元,一個神經元有一個權值,加上一個偏置輸入。

    這做了兩件重要的事:

  1. 允許按照定義問題的方式構造和學習問題,即一個輸入對應一個輸出,將每個時間步驟的內部流程分開。
  2. 通過要求更少的權重來簡化網絡,這樣一次只處理一個時間步長。

    一個簡單的全連接層應用於前一層提供的序列中的每個時間步長,以構建輸出序列。

     我們可以用時間步長和TimeDistributed層作爲第一個示例中實現一對一網絡的更緊湊的方法來考慮問題的框架。它甚至可能在更大的範圍內更有效(空間或時間方面)。

進一步的閱讀

    下面是一些關於TimeDistributed層的資源和討論,您可能想深入其中。

參考資料:

    文中已標明,但是再次感謝!

How to Use the TimeDistributed Layer for Long Short-Term Memory Networks in Python

lstm數據格式

Keras實戰:基於LSTM的股價預測方法

When and How to use TimeDistributedDense on GitHub

TimeDistributed Layer in the Keras API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章