第10章 使用Keras搭建人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)
第12章 使用TensorFlow自定義模型并訓(xùn)練
第13章 使用TensorFlow加載和預(yù)處理數(shù)據(jù)
第14章 使用卷積神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)深度計(jì)算機(jī)視覺(jué)
第15章 使用RNN和CNN處理序列
第16章 使用RNN和注意力機(jī)制進(jìn)行自然語(yǔ)言處理
第17章 使用自編碼器和GAN做表征學(xué)習(xí)和生成式學(xué)習(xí)
第18章 強(qiáng)化學(xué)習(xí)
第19章 規(guī)模化訓(xùn)練和部署TensorFlow模型
擊球手擊出壘球,外場(chǎng)手會(huì)立即開(kāi)始奔跑,并預(yù)測(cè)球的軌跡。外場(chǎng)手追蹤球,不斷調(diào)整移動(dòng)步伐,最終在觀眾的掌聲中抓到它。無(wú)論是在聽(tīng)完朋友的話還是早餐時(shí)預(yù)測(cè)咖啡的味道,你時(shí)刻在做的事就是在預(yù)測(cè)未來(lái)。在本章中,我們將討論循環(huán)神經(jīng)網(wǎng)絡(luò),一類(lèi)可以預(yù)測(cè)未來(lái)的網(wǎng)絡(luò)(當(dāng)然,是到某一點(diǎn)為止)。它們可以分析時(shí)間序列數(shù)據(jù),比如股票價(jià)格,并告訴你什么時(shí)候買(mǎi)入和賣(mài)出。在自動(dòng)駕駛系統(tǒng)中,他們可以預(yù)測(cè)行車(chē)軌跡,避免發(fā)生事故。更一般地說(shuō),它們可在任意長(zhǎng)度的序列上工作,而不是截止目前我們討論的只能在固定長(zhǎng)度的輸入上工作的網(wǎng)絡(luò)。舉個(gè)例子,它們可以將語(yǔ)句,文件,以及語(yǔ)音范本作為輸入,應(yīng)用在在自動(dòng)翻譯,語(yǔ)音到文本的自然語(yǔ)言處理應(yīng)用中。
在本章中,我們將學(xué)習(xí)循環(huán)神經(jīng)網(wǎng)絡(luò)的基本概念,如何使用時(shí)間反向傳播訓(xùn)練網(wǎng)絡(luò),然后用來(lái)預(yù)測(cè)時(shí)間序列。然后,會(huì)討論RNN面對(duì)的兩大難點(diǎn):
不穩(wěn)定梯度(換句話說(shuō),在第11章中討論的梯度消失/爆炸),可以使用多種方法緩解,包括循環(huán)dropout和循環(huán)層歸一化。
有限的短期記憶,可以通過(guò)LSTM 和 GRU 單元延長(zhǎng)。
RNN不是唯一能處理序列數(shù)據(jù)的神經(jīng)網(wǎng)絡(luò):對(duì)于小序列,常規(guī)緊密網(wǎng)絡(luò)也可以;對(duì)于長(zhǎng)序列,比如音頻或文本,卷積神經(jīng)網(wǎng)絡(luò)也可以。我們會(huì)討論這兩種方法,本章最后會(huì)實(shí)現(xiàn)一個(gè)WaveNet:這是一種CNN架構(gòu),可以處理上萬(wàn)個(gè)時(shí)間步的序列。在第16章,還會(huì)繼續(xù)學(xué)習(xí)RNN,如何使用RNN來(lái)做自然語(yǔ)言處理,和基于注意力機(jī)制的新架構(gòu)。
循環(huán)神經(jīng)元和層
到目前為止,我們主要關(guān)注的是前饋神經(jīng)網(wǎng)絡(luò),激活僅從輸入層到輸出層的一個(gè)方向流動(dòng)(附錄 E 中的幾個(gè)網(wǎng)絡(luò)除外)。 循環(huán)神經(jīng)網(wǎng)絡(luò)看起來(lái)非常像一個(gè)前饋神經(jīng)網(wǎng)絡(luò),除了它也有連接指向后方。 讓我們看一下最簡(jiǎn)單的 RNN,由一個(gè)神經(jīng)元接收輸入,產(chǎn)生一個(gè)輸出,并將輸出發(fā)送回自己,如圖 15-1(左)所示。 在每個(gè)時(shí)間步t
(也稱(chēng)為一個(gè)幀),這個(gè)循環(huán)神經(jīng)元接收輸入x(t)以及它自己的前一時(shí)間步長(zhǎng) y(t-1) 的輸出。 因?yàn)榈谝粋€(gè)時(shí)間步驟沒(méi)有上一次的輸出,所以是0。可以用時(shí)間軸來(lái)表示這個(gè)微小的網(wǎng)絡(luò),如圖 15-1(右)所示。 這被稱(chēng)為隨時(shí)間展開(kāi)網(wǎng)絡(luò)。
你可以輕松創(chuàng)建一個(gè)循環(huán)神經(jīng)元層。 在每個(gè)時(shí)間步t,每個(gè)神經(jīng)元都接收輸入矢量x(t) 和前一個(gè)時(shí)間步 y(t-1) 的輸出矢量,如圖 15-2 所示。 注意,輸入和輸出都是矢量(當(dāng)只有一個(gè)神經(jīng)元時(shí),輸出是一個(gè)標(biāo)量)。
每個(gè)循環(huán)神經(jīng)元有兩組權(quán)重:一組用于輸入x(t),另一組用于前一時(shí)間步長(zhǎng) y(t-1) 的輸出。 我們稱(chēng)這些權(quán)重向量為 wx 和 wy。如果考慮的是整個(gè)循環(huán)神經(jīng)元層,可以將所有權(quán)重矢量放到兩個(gè)權(quán)重矩陣中,Wx 和 Wy。整個(gè)循環(huán)神經(jīng)元層的輸出可以用公式 15-1 表示(b
是偏差項(xiàng),φ(·)
是激活函數(shù),例如 ReLU)。
就像前饋神經(jīng)網(wǎng)絡(luò)一樣,可以將所有輸入和時(shí)間步t
放到輸入矩陣X(t)中,一次計(jì)算出整個(gè)小批次的輸出:(見(jiàn)公式 15-2)。
在這個(gè)公式中:
- Y(t) 是 m × nneurons 矩陣,包含在小批次中每個(gè)實(shí)例在時(shí)間步
t
的層輸出(m
是小批次中的實(shí)例數(shù),nneurons 是神經(jīng)元數(shù))。 - X(t) 是 m × ninputs 矩陣,包含所有實(shí)例的輸入 (ninputs 是輸入特征的數(shù)量)。
- Wx 是 ninputs × nneurons 矩陣,包含當(dāng)前時(shí)間步的輸入的連接權(quán)重。
- Wy 是 nneurons × nneurons 矩陣,包含上一個(gè)時(shí)間步的輸出的連接權(quán)重。
-
b
是大小為 nneurons 的矢量,包含每個(gè)神經(jīng)元的偏置項(xiàng)。 - 權(quán)重矩陣 Wx 和 Wy 通常縱向連接成一個(gè)權(quán)重矩陣
W
,形狀為(ninputs + nneurons) × nneurons(見(jiàn)公式 15-2 的第二行)
注意,Y(t) 是 X(t) 和 Y(t-1) 的函數(shù),Y(t-1)是 X(t-1)和 Y(t-2) 的函數(shù),以此類(lèi)推。這使得 Y(t) 是從時(shí)間t = 0
開(kāi)始的所有輸入(即 X(0),X(1),...,X(t))的函數(shù)。 在第一個(gè)時(shí)間步,t = 0
,沒(méi)有以前的輸出,所以它們通常被假定為全零。
記憶單元
由于時(shí)間t
的循環(huán)神經(jīng)元的輸出,是由所有先前時(shí)間步驟計(jì)算出來(lái)的的函數(shù),你可以說(shuō)它有一種記憶形式。神經(jīng)網(wǎng)絡(luò)的一部分,保留一些跨越時(shí)間步長(zhǎng)的狀態(tài),稱(chēng)為存儲(chǔ)單元(或簡(jiǎn)稱(chēng)為單元)。單個(gè)循環(huán)神經(jīng)元或循環(huán)神經(jīng)元層是非常基本的單元,只能學(xué)習(xí)短期規(guī)律(取決于具體任務(wù),通常是10個(gè)時(shí)間步)。本章后面我們將介紹一些更為復(fù)雜和強(qiáng)大的單元,可以學(xué)習(xí)更長(zhǎng)時(shí)間步的規(guī)律(也取決于具體任務(wù),大概是100個(gè)時(shí)間步)。
一般情況下,時(shí)間步t
的單元狀態(tài),記為 h(t)(h
代表“隱藏”),是該時(shí)間步的某些輸入和前一時(shí)間步狀態(tài)的函數(shù):h(t) = f(h(t–1), x(t))。 其在時(shí)間步t
的輸出,表示為 y(t),也和前一狀態(tài)和當(dāng)前輸入的函數(shù)有關(guān)。 我們已經(jīng)討論過(guò)的基本單元,輸出等于單元狀態(tài),但是在更復(fù)雜的單元中并不總是如此,如圖 15-3 所示。
輸入和輸出序列
RNN 可以同時(shí)輸入序列并輸出序列(見(jiàn)圖 15-4,左上角的網(wǎng)絡(luò))。這種序列到序列的網(wǎng)絡(luò)可以有效預(yù)測(cè)時(shí)間序列(如股票價(jià)格):輸入過(guò)去N
天價(jià)格,則輸出向未來(lái)移動(dòng)一天的價(jià)格(即,從N - 1
天前到明天)。
或者,你可以向網(wǎng)絡(luò)輸入一個(gè)序列,忽略除最后一項(xiàng)之外的所有輸出(圖15-4右上角的網(wǎng)絡(luò))。 換句話說(shuō),這是一個(gè)序列到矢量的網(wǎng)絡(luò)。 例如,你可以向網(wǎng)絡(luò)輸入與電影評(píng)論相對(duì)應(yīng)的單詞序列,網(wǎng)絡(luò)輸出情感評(píng)分(例如,從-1 [討厭]
到+1 [喜歡]
)。
相反,可以向網(wǎng)絡(luò)一遍又一遍輸入相同的矢量(見(jiàn)圖15-4的左下角),輸出一個(gè)序列。這是一個(gè)矢量到序列的網(wǎng)絡(luò)。 例如,輸入可以是圖像(或是CNN的結(jié)果),輸出是該圖像的標(biāo)題。
最后,可以有一個(gè)序列到矢量的網(wǎng)絡(luò),稱(chēng)為編碼器,后面跟著一個(gè)稱(chēng)為解碼器的矢量到序列的網(wǎng)絡(luò)(見(jiàn)圖15-4右下角)。 例如,這可以用于將句子從一種語(yǔ)言翻譯成另一種語(yǔ)言。 給網(wǎng)絡(luò)輸入一種語(yǔ)言的一句話,編碼器會(huì)把這個(gè)句子轉(zhuǎn)換成單一的矢量表征,然后解碼器將這個(gè)矢量解碼成另一種語(yǔ)言的句子。 這種稱(chēng)為編碼器 - 解碼器的兩步模型,比用單個(gè)序列到序列的 RNN實(shí)時(shí)地進(jìn)行翻譯要好得多,因?yàn)榫渥拥淖詈笠粋€(gè)單詞可以影響翻譯的第一句話,所以你需要等到聽(tīng)完整個(gè)句子才能翻譯。第16章還會(huì)介紹如何實(shí)現(xiàn)編碼器-解碼器(會(huì)比圖15-4中復(fù)雜)
訓(xùn)練RNN
訓(xùn)練RNN訣竅是在時(shí)間上展開(kāi)(就像我們剛剛做的那樣),然后只要使用常規(guī)反向傳播(見(jiàn)圖 15-5)。 這個(gè)策略被稱(chēng)為時(shí)間上的反向傳播(BPTT)。
就像在正常的反向傳播中一樣,展開(kāi)的網(wǎng)絡(luò)(用虛線箭頭表示)中先有一個(gè)正向傳播(虛線)。然后使用損失函數(shù) C(Y(0), Y(1), …Y(T)) 評(píng)估輸出序列(其中T是最大時(shí)間步)。這個(gè)損失函數(shù)會(huì)忽略一些輸出,見(jiàn)圖15-5(例如,在序列到矢量的RNN中,除了最后一項(xiàng),其它的都被忽略了)。損失函數(shù)的梯度通過(guò)展開(kāi)的網(wǎng)絡(luò)反向傳播(實(shí)線箭頭)。最后使用在 BPTT 期間計(jì)算的梯度來(lái)更新模型參數(shù)。注意,梯度在損失函數(shù)所使用的所有輸出中反向流動(dòng),而不僅僅通過(guò)最終輸出(例如,在圖 15-5 中,損失函數(shù)使用網(wǎng)絡(luò)的最后三個(gè)輸出 Y(2),Y(3) 和 Y(4),所以梯度流經(jīng)這三個(gè)輸出,但不通過(guò) Y(0) 和 Y(1)。而且,由于在每個(gè)時(shí)間步驟使用相同的參數(shù)W
和b
,所以反向傳播將做正確的事情并對(duì)所有時(shí)間步求和。
幸好,tf.keras處理了這些麻煩。
預(yù)測(cè)時(shí)間序列
假設(shè)你在研究網(wǎng)站每小時(shí)的活躍用戶數(shù),或是所在城市的每日氣溫,或公司的財(cái)務(wù)狀況,用多種指標(biāo)做季度衡量。在這些任務(wù)中,數(shù)據(jù)都是一個(gè)序列,每步有一個(gè)或多個(gè)值。這被稱(chēng)為時(shí)間序列。在前兩個(gè)任務(wù)中,每個(gè)時(shí)間步只有一個(gè)值,它們是單變量時(shí)間序列。在財(cái)務(wù)狀況的任務(wù)中,每個(gè)時(shí)間步有多個(gè)值(利潤(rùn)、欠賬,等等),所以是多變量時(shí)間序列。典型的任務(wù)是預(yù)測(cè)未來(lái)值,稱(chēng)為“預(yù)測(cè)”。另一個(gè)任務(wù)是填空:預(yù)測(cè)(或“后測(cè)”)過(guò)去的缺失值,這被稱(chēng)為“填充”。例如,圖15-6展示了3個(gè)單變量時(shí)間序列,每個(gè)都有50個(gè)時(shí)間步,目標(biāo)是預(yù)測(cè)下一個(gè)時(shí)間步的值(用X表示)。
簡(jiǎn)單起見(jiàn),使用函數(shù)generate_time_series()
生成的時(shí)間序列,如下:
def generate_time_series(batch_size, n_steps):
freq1, freq2, offsets1, offsets2 = np.random.rand(4, batch_size, 1)
time = np.linspace(0, 1, n_steps)
series = 0.5 * np.sin((time - offsets1) * (freq1 * 10 + 10)) # wave 1
series += 0.2 * np.sin((time - offsets2) * (freq2 * 20 + 20)) # + wave 2
series += 0.1 * (np.random.rand(batch_size, n_steps) - 0.5) # + noise
return series[..., np.newaxis].astype(np.float32)
這個(gè)函數(shù)可以根據(jù)要求創(chuàng)建出時(shí)間序列(通過(guò)batch_size
參數(shù)),長(zhǎng)度為n_steps
,每個(gè)時(shí)間步只有1個(gè)值。函數(shù)返回NumPy數(shù)組,形狀是[批次大小, 時(shí)間步數(shù), 1],每個(gè)序列是兩個(gè)正弦波之和(固定強(qiáng)度+隨機(jī)頻率和相位),加一點(diǎn)噪音。
筆記:當(dāng)處理時(shí)間序列時(shí)(和其它類(lèi)型的時(shí)間序列),輸入特征通常用3D數(shù)組來(lái)表示,其形狀是 [批次大小, 時(shí)間步數(shù), 維度],對(duì)于單變量時(shí)間序列,其維度是1,多變量時(shí)間序列的維度是其維度數(shù)。
用這個(gè)函數(shù)來(lái)創(chuàng)建訓(xùn)練集、驗(yàn)證集和測(cè)試集:
n_steps = 50
series = generate_time_series(10000, n_steps + 1)
X_train, y_train = series[:7000, :n_steps], series[:7000, -1]
X_valid, y_valid = series[7000:9000, :n_steps], series[7000:9000, -1]
X_test, y_test = series[9000:, :n_steps], series[9000:, -1]
X_train
包含7000個(gè)時(shí)間序列(即,形狀是[7000, 50, 1]),X_valid
有2000個(gè),X_test
有1000個(gè)。因?yàn)轭A(yù)測(cè)的是單一值,目標(biāo)值是列矢量(y_train
的形狀是[7000, 1])。
基線模型
使用RNN之前,最好有基線指標(biāo),否則做出來(lái)的模型可能比基線模型還糟。例如,最簡(jiǎn)單的方法,是預(yù)測(cè)每個(gè)序列的最后一個(gè)值。這個(gè)方法被稱(chēng)為樸素預(yù)測(cè),有時(shí)很難被超越。在這個(gè)例子中,它的均方誤差為0.020:
>>> y_pred = X_valid[:, -1]
>>> np.mean(keras.losses.mean_squared_error(y_valid, y_pred))
0.020211367
另一個(gè)簡(jiǎn)單的方法是使用全連接網(wǎng)絡(luò)。因?yàn)榻Y(jié)果要是打平的特征列表,需要加一個(gè)Flatten
層。使用簡(jiǎn)單線性回歸模型,使預(yù)測(cè)值是時(shí)間序列中每個(gè)值的線性組合:
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[50, 1]),
keras.layers.Dense(1)
])
使用MSE損失、Adam優(yōu)化器編譯模型,在訓(xùn)練集上訓(xùn)練20個(gè)周期,用驗(yàn)證集評(píng)估,最終得到的MSE值為0.004。比樸素預(yù)測(cè)強(qiáng)多了!
實(shí)現(xiàn)一個(gè)簡(jiǎn)單RNN
搭建一個(gè)簡(jiǎn)單RNN模型:
model = keras.models.Sequential([
keras.layers.SimpleRNN(1, input_shape=[None, 1])
])
這是能實(shí)現(xiàn)的最簡(jiǎn)單的RNN。只有1個(gè)層,1個(gè)神經(jīng)元,如圖15-1。不用指定輸入序列的長(zhǎng)度(和之前的模型不同),因?yàn)檠h(huán)神經(jīng)網(wǎng)絡(luò)可以處理任意的時(shí)間步(這就是為什么將第一個(gè)輸入維度設(shè)為None
)。默認(rèn)時(shí),SimpleRNN
使用雙曲正切激活函數(shù)。和之前看到的一樣:初始狀態(tài)h(init)設(shè)為0,和時(shí)間序列的第一個(gè)值x(0)一起傳遞給神經(jīng)元。神經(jīng)元計(jì)算這兩個(gè)值的加權(quán)和,對(duì)結(jié)果使用雙曲正切激活函數(shù),得到第一個(gè)輸出y(0)。在簡(jiǎn)單RNN中,這個(gè)輸出也是新?tīng)顟B(tài)h(0)。這個(gè)新?tīng)顟B(tài)和下一個(gè)輸入值x(1),按照這個(gè)流程,直到輸出最后一個(gè)值,y49。所有這些都是同時(shí)對(duì)每個(gè)時(shí)間序列進(jìn)行的。
筆記:默認(rèn)時(shí),Keras的循環(huán)層只返回最后一個(gè)輸出。要讓其返回每個(gè)時(shí)間步的輸出,必須設(shè)置
return_sequences=True
。
用這個(gè)模型編譯、訓(xùn)練、評(píng)估(和之前一樣,用Adam訓(xùn)練20個(gè)周期),你會(huì)發(fā)現(xiàn)它的MSE只有0.014。擊敗了樸素預(yù)測(cè),但不如簡(jiǎn)單線性模型。對(duì)于每個(gè)神經(jīng)元,線性簡(jiǎn)單模型中每個(gè)時(shí)間步驟每個(gè)輸入就有一個(gè)參數(shù)(前面用過(guò)的簡(jiǎn)單線性模型一共有51個(gè)參數(shù))。相反,對(duì)于簡(jiǎn)單RNN中每個(gè)循環(huán)神經(jīng)元,每個(gè)輸入每個(gè)隱藏狀態(tài)只有一個(gè)參數(shù)(在簡(jiǎn)單RNN中,就是每層循環(huán)神經(jīng)元的數(shù)量),加上一個(gè)偏置項(xiàng)。在這個(gè)簡(jiǎn)單RNN中,只有三個(gè)參數(shù)。
趨勢(shì)和季節(jié)性
還有其它預(yù)測(cè)時(shí)間序列的模型,比如權(quán)重移動(dòng)平均模型或自動(dòng)回歸集成移動(dòng)平均(ARIMA)模型。某些模型需要先移出趨勢(shì)和季節(jié)性。例如,如果要研究網(wǎng)站的活躍用戶數(shù),它每月會(huì)增長(zhǎng)10%,就需要去掉這個(gè)趨勢(shì)。訓(xùn)練好模型之后,在做預(yù)測(cè)時(shí),你可以將趨勢(shì)加回來(lái)做最終的預(yù)測(cè)。相似的,如果要預(yù)測(cè)防曬霜的每月銷(xiāo)量,會(huì)觀察到明顯的季節(jié)性:每年夏天賣(mài)的多。需要將季節(jié)性從時(shí)間序列去除,比如計(jì)算每個(gè)時(shí)間步和前一年的差值(這個(gè)方法被稱(chēng)為差分)。然后,當(dāng)訓(xùn)練好模型,做預(yù)測(cè)時(shí),可以將季節(jié)性加回來(lái),來(lái)得到最終結(jié)果。
使用RNN時(shí),一般不需要做這些,但在有些任務(wù)中可以提高性能,因?yàn)槟P筒皇欠且獙W(xué)習(xí)這些趨勢(shì)或季節(jié)性。
很顯然,這個(gè)簡(jiǎn)單RNN過(guò)于簡(jiǎn)單了,性能不成。下面就來(lái)添加更多的循環(huán)層!
深度RNN
將多個(gè)神經(jīng)元的層堆起來(lái),見(jiàn)圖15-7。就形成了深度RNN。
用tf.keras實(shí)現(xiàn)深度RNN相當(dāng)容易:將循環(huán)層堆起來(lái)就成。在這個(gè)例子中,我們使用三個(gè)SimpleRNN
層(也可以添加其它類(lèi)型的循環(huán)層,比如LSTM或GRU):
model = keras.models.Sequential([
keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
keras.layers.SimpleRNN(20, return_sequences=True),
keras.layers.SimpleRNN(1)
])
警告:所有循環(huán)層一定要設(shè)置
return_sequences=True
(除了最后一層,因?yàn)樽詈笠粚又魂P(guān)心輸出)。如果沒(méi)有設(shè)置,輸出的是2D數(shù)組(只有最終時(shí)間步的輸出),而不是3D數(shù)組(包含所有時(shí)間步的輸出),下一個(gè)循環(huán)層就接收不到3D格式的序列數(shù)據(jù)。
如果對(duì)這個(gè)模型做編譯,訓(xùn)練和評(píng)估,其MSE值可以達(dá)到0.003。總算打敗了線性模型!
最后一層不夠理想:因?yàn)橐A(yù)測(cè)單一值,每個(gè)時(shí)間步只能有一個(gè)輸出值,最終層只能有一個(gè)神經(jīng)元。但是一個(gè)神經(jīng)元意味著隱藏態(tài)只有一個(gè)值。RNN大部分使用其他循環(huán)層的隱藏態(tài)的所有信息,最后一層的隱藏態(tài)不怎么用到。另外,因?yàn)?code>SimpleRNN層默認(rèn)使用tanh激活函數(shù),預(yù)測(cè)值位于-1和1之間。想使用另一個(gè)激活函數(shù)該怎么辦呢?出于這些原因,最好使用緊密層:運(yùn)行更快,準(zhǔn)確率差不多,可以選擇任何激活函數(shù)。如果做了替換,要將第二個(gè)循環(huán)層的return_sequences=True
刪掉:
model = keras.models.Sequential([
keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
keras.layers.SimpleRNN(20),
keras.layers.Dense(1)
])
如果訓(xùn)練這個(gè)模型,會(huì)發(fā)現(xiàn)它收斂更快,效果也不錯(cuò)。
提前預(yù)測(cè)幾個(gè)時(shí)間步
目前為止我們只是預(yù)測(cè)下一個(gè)時(shí)間步的值,但也可以輕易地提前預(yù)測(cè)幾步,只要改變目標(biāo)就成(例如,要提前預(yù)測(cè)10步,只要將目標(biāo)變?yōu)?0步就成)。但如果想預(yù)測(cè)后面的10個(gè)值呢?
第一種方法是使用訓(xùn)練好的模型,預(yù)測(cè)出下一個(gè)值,然后將這個(gè)值添加到輸入中(假設(shè)這個(gè)預(yù)測(cè)值真實(shí)發(fā)生了),使用這個(gè)模型再次預(yù)測(cè)下一個(gè)值,依次類(lèi)推,見(jiàn)如下代碼:
series = generate_time_series(1, n_steps + 10)
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):
y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :]
X = np.concatenate([X, y_pred_one], axis=1)
Y_pred = X[:, n_steps:]
想象的到,第一個(gè)預(yù)測(cè)值比后面的更準(zhǔn),因?yàn)殄e(cuò)誤可能會(huì)累積(見(jiàn)圖15-8)。如果在驗(yàn)證集上評(píng)估這個(gè)方法,MSE值為0.029。MSE比之前高多了,但因?yàn)槿蝿?wù)本身難,這個(gè)對(duì)比意義不大。將其余樸素預(yù)測(cè)(預(yù)測(cè)時(shí)間序列可以恒定10個(gè)步驟)或簡(jiǎn)單線性模型對(duì)比的意義更大。樸素方法效果很差(MSE值為0.223),線性簡(jiǎn)單模型的MSE值為0.0188:比RNN的預(yù)測(cè)效果好,并且還快。如果只想在復(fù)雜任務(wù)上提前預(yù)測(cè)幾步的話,這個(gè)方法就夠了。
第二種方法是訓(xùn)練一個(gè)RNN,一次性預(yù)測(cè)出10個(gè)值。還可以使用序列到矢量模型,但輸出的是10個(gè)值。但是,我們先需要修改矢量,時(shí)期含有10個(gè)值:
series = generate_time_series(10000, n_steps + 10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:, 0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:, 0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:, 0]
然后使輸出層有10個(gè)神經(jīng)元:
model = keras.models.Sequential([
keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
keras.layers.SimpleRNN(20),
keras.layers.Dense(10)
])
訓(xùn)練好這個(gè)模型之后,就可以一次預(yù)測(cè)出后面的10個(gè)值了:
Y_pred = model.predict(X_new)
這個(gè)模型的效果不錯(cuò):預(yù)測(cè)10個(gè)值的MSE值為0.008。比線性模型強(qiáng)多了。但還有繼續(xù)改善的空間,除了在最后的時(shí)間步用訓(xùn)練模型預(yù)測(cè)接下來(lái)的10個(gè)值,還可以在每個(gè)時(shí)間步預(yù)測(cè)接下來(lái)的10個(gè)值。換句話說(shuō),可以將這個(gè)序列到矢量的RNN變成序列到序列的RNN。這種方法的優(yōu)勢(shì),是損失會(huì)包含RNN的每個(gè)時(shí)間步的輸出項(xiàng),不僅是最后時(shí)間步的輸出。這意味著模型中會(huì)流動(dòng)著更多的誤差梯度,梯度不必只通過(guò)時(shí)間流動(dòng);還可以從輸出流動(dòng)。這樣可以穩(wěn)定和加速訓(xùn)練。
更加清楚一點(diǎn),在時(shí)間步0,模型輸出一個(gè)包含時(shí)間步1到10的預(yù)測(cè)矢量,在時(shí)間步1,模型輸出一個(gè)包含時(shí)間步2到11的預(yù)測(cè)矢量,以此類(lèi)推。因此每個(gè)目標(biāo)必須是一個(gè)序列,其長(zhǎng)度和輸入序列長(zhǎng)度相同,每個(gè)時(shí)間步包含一個(gè)10維矢量。先準(zhǔn)備目標(biāo)序列:
Y = np.empty((10000, n_steps, 10)) # each target is a sequence of 10D vectors
for step_ahead in range(1, 10 + 1):
Y[:, :, step_ahead - 1] = series[:, step_ahead:step_ahead + n_steps, 0]
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]
筆記:目標(biāo)要包含出現(xiàn)在輸入中的值(
X_train
和Y_train
有許多重復(fù)),聽(tīng)起來(lái)很奇怪。這不是作弊嗎?其實(shí)不是:在每個(gè)時(shí)間步,模型只知道過(guò)去的時(shí)間步,不能向前看。這個(gè)模型被稱(chēng)為因果模型。
要將模型變成序列到序列的模型,必須給所有循環(huán)層(包括最后一個(gè))設(shè)置return_sequences=True
,還必須在每個(gè)時(shí)間步添加緊密輸出層。出于這個(gè)目的,Keras提供了TimeDistributed
層:它將任意層(比如,緊密層)包裝起來(lái),然后在輸入序列的每個(gè)時(shí)間步上使用。通過(guò)變形輸入,將每個(gè)時(shí)間步處理為獨(dú)立實(shí)例(即,將輸入從 [批次大小, 時(shí)間步數(shù), 輸入維度] 變形為 [批次大小 × 時(shí)間步數(shù), 輸入維度] ;在這個(gè)例子中,因?yàn)榍耙?code>SimpleRNN有20個(gè)神經(jīng)元,輸入的維度數(shù)是20),這個(gè)層的效率很高。然后運(yùn)行緊密層,最后將輸出變形為序列(即,將輸出從 [批次大小 × 時(shí)間步數(shù), 輸出維度] 變形為 [批次大小, 時(shí)間步數(shù), 輸出維度] ;在這個(gè)例子中,輸出維度數(shù)是10,因?yàn)榫o密層有10個(gè)神經(jīng)元)。下面是更新后的模型:
model = keras.models.Sequential([
keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
keras.layers.SimpleRNN(20, return_sequences=True),
keras.layers.TimeDistributed(keras.layers.Dense(10))
])
緊密層實(shí)際上是支持序列(和更高維度的輸入)作為輸入的:如同TimeDistributed(Dense(…))
一樣處理序列,意味著只應(yīng)用在最后的輸入維度上(所有時(shí)間步獨(dú)立)。因此,因此可以將最后一層替換為Dense(10)
。但為了能夠清晰,我們還是使用TimeDistributed(Dense(10))
,因?yàn)榍宄恼故玖司o密層獨(dú)立應(yīng)用在了每個(gè)時(shí)間上,并且模型會(huì)輸出一個(gè)序列,不僅僅是一個(gè)單矢量。
訓(xùn)練時(shí)需要所有輸出,但預(yù)測(cè)和評(píng)估時(shí),只需最后時(shí)間步的輸出。因此盡管訓(xùn)練時(shí)依賴(lài)所有輸出的MSE,評(píng)估需要一個(gè)自定義指標(biāo),只計(jì)算最后一個(gè)時(shí)間步輸出值的MSE:
def last_time_step_mse(Y_true, Y_pred):
return keras.metrics.mean_squared_error(Y_true[:, -1], Y_pred[:, -1])
optimizer = keras.optimizers.Adam(lr=0.01)
model.compile(loss="mse", optimizer=optimizer, metrics=[last_time_step_mse])
得到的MSE值為0.006,比前面的模型提高了25%。可以將這個(gè)方法和第一個(gè)結(jié)合起來(lái):先用這個(gè)RNN預(yù)測(cè)接下來(lái)的10個(gè)值,然后將結(jié)果和輸入序列連起來(lái),再用模型預(yù)測(cè)接下來(lái)的10個(gè)值,以此類(lèi)推。使用這個(gè)方法,可以預(yù)測(cè)任意長(zhǎng)度的序列。對(duì)長(zhǎng)期預(yù)測(cè)可能不那么準(zhǔn)確,但用來(lái)生成音樂(lè)和文字是足夠的,第16章有例子。
提示:當(dāng)預(yù)測(cè)時(shí)間序列時(shí),最好給預(yù)測(cè)加上誤差條。要這么做,一個(gè)高效的方法是用MC Dropout,第11章介紹過(guò):給每個(gè)記憶單元添加一個(gè)MC Dropout層丟失部分輸入和隱藏狀態(tài)。訓(xùn)練之后,要預(yù)測(cè)新的時(shí)間序列,可以多次使用模型計(jì)算每一步預(yù)測(cè)值的平均值和標(biāo)準(zhǔn)差。
簡(jiǎn)單RNN在預(yù)測(cè)時(shí)間序列或處理其它類(lèi)型序列時(shí)表現(xiàn)很好,但在長(zhǎng)序列上表現(xiàn)不佳。接下來(lái)就探究其原因和解決方法。
處理長(zhǎng)序列
在訓(xùn)練長(zhǎng)序列的 RNN 模型時(shí),必須運(yùn)行許多時(shí)間步,展開(kāi)的RNN變成了一個(gè)很深的網(wǎng)絡(luò)。正如任何深度神經(jīng)網(wǎng)絡(luò)一樣,它面臨不穩(wěn)定梯度問(wèn)題(第11章討論過(guò)),使訓(xùn)練無(wú)法停止,或訓(xùn)練不穩(wěn)定。另外,當(dāng)RNN處理長(zhǎng)序列時(shí),RNN會(huì)逐漸忘掉序列的第一個(gè)輸入。下面就來(lái)看看這兩個(gè)問(wèn)題,先是第一個(gè)問(wèn)題。
應(yīng)對(duì)不穩(wěn)定梯度
很多之前討論過(guò)的緩解不穩(wěn)定梯度的技巧都可以應(yīng)用在RNN中:好的參數(shù)初始化方式,更快的優(yōu)化器,dropout,等等。但是非飽和激活函數(shù)(如 ReLU)的幫助不大;事實(shí)上,它會(huì)導(dǎo)致RNN更加不穩(wěn)定。為什么呢?假設(shè)梯度下降更新了權(quán)重,可以令第一個(gè)時(shí)間步的輸出提高。因?yàn)槊總€(gè)時(shí)間步使用的權(quán)重相同,第二個(gè)時(shí)間步的輸出也會(huì)提高,這樣就會(huì)導(dǎo)致輸出爆炸 —— 不飽和激活函數(shù)不能阻止這個(gè)問(wèn)題。要降低爆炸風(fēng)險(xiǎn),可以使用更小的學(xué)習(xí)率,更簡(jiǎn)單的方法是使用一個(gè)飽和激活函數(shù),比如雙曲正切函數(shù)(這就解釋了為什么tanh是默認(rèn)選項(xiàng))。同樣的道理,梯度本身也可能爆炸。如果觀察到訓(xùn)練不穩(wěn)定,可以監(jiān)督梯度的大小(例如,使用TensorBoard),看情況使用梯度裁剪。
另外,批歸一化也沒(méi)什么幫助。事實(shí)上,不能在時(shí)間步驟之間使用批歸一化,只能在循環(huán)層之間使用。更加準(zhǔn)確點(diǎn),技術(shù)上可以將BN層添加到記憶單元上(后面會(huì)看到),這樣就可以應(yīng)用在每個(gè)時(shí)間步上了(既對(duì)輸入使用,也對(duì)前一步的隱藏態(tài)使用)。但是,每個(gè)時(shí)間步用BN層相同,參數(shù)也相同,與輸入和隱藏態(tài)的大小和偏移無(wú)關(guān)。在實(shí)踐中,César Laurent等人在2015年的一篇論文展示,這么做的效果不好:作者發(fā)現(xiàn)BN層只對(duì)輸入有用,而對(duì)隱藏態(tài)沒(méi)用。換句話說(shuō),在循環(huán)層之間使用BN層時(shí),效果只有一點(diǎn)(即在圖15-7中垂直使用),在循環(huán)層之內(nèi)使用,效果不大(即,水平使用)。在Keras中,可以在每個(gè)循環(huán)層之前添加BatchNormalization
層,但不要期待太高。
另一種歸一化的形式效果好些:層歸一化。它是由Jimmy Lei Ba等人在2016年的一篇論文中提出的:它跟批歸一化很像,但不是在批次維度上做歸一化,而是在特征維度上歸一化。這么做的一個(gè)優(yōu)勢(shì)是可以獨(dú)立對(duì)每個(gè)實(shí)例,實(shí)時(shí)計(jì)算所需的統(tǒng)計(jì)量。這還意味著訓(xùn)練和測(cè)試中的行為是一致的(這點(diǎn)和BN相反),且不需要使用指數(shù)移動(dòng)平均來(lái)估計(jì)訓(xùn)練集中所有實(shí)例的特征統(tǒng)計(jì)。和BN一樣,層歸一化會(huì)學(xué)習(xí)每個(gè)輸入的比例和偏移參數(shù)。在RNN中,層歸一化通常用在輸入和隱藏態(tài)的線型組合之后。
使用tf.keras在一個(gè)簡(jiǎn)單記憶單元中實(shí)現(xiàn)層歸一化。要這么做,需要定義一個(gè)自定義記憶單元。就像一個(gè)常規(guī)層一樣,call()
接收兩個(gè)參數(shù):當(dāng)前時(shí)間步的inputs
和上一時(shí)間步的隱藏states
。states
是一個(gè)包含一個(gè)或多個(gè)張量的列表。在簡(jiǎn)單RNN單元中,states
包含一個(gè)等于上一時(shí)間步輸出的張量,但其它單元可能包含多個(gè)狀態(tài)張量(比如LSTMCell
有長(zhǎng)期狀態(tài)和短期狀態(tài))。單元還必須有一個(gè)state_size
屬性和一個(gè)output_size
屬性。在簡(jiǎn)單RNN中,這兩個(gè)屬性等于神經(jīng)元的數(shù)量。下面的代碼實(shí)現(xiàn)了一個(gè)自定義記憶單元,作用類(lèi)似于SimpleRNNCell
,但會(huì)在每個(gè)時(shí)間步做層歸一化:
class LNSimpleRNNCell(keras.layers.Layer):
def __init__(self, units, activation="tanh", **kwargs):
super().__init__(**kwargs)
self.state_size = units
self.output_size = units
self.simple_rnn_cell = keras.layers.SimpleRNNCell(units,
activation=None)
self.layer_norm = keras.layers.LayerNormalization()
self.activation = keras.activations.get(activation)
def call(self, inputs, states):
outputs, new_states = self.simple_rnn_cell(inputs, states)
norm_outputs = self.activation(self.layer_norm(outputs))
return norm_outputs, [norm_outputs]
代碼不難。和其它自定義類(lèi)一樣,LNSimpleRNNCell
繼承自keras.layers.Layer
。構(gòu)造器接收unit的數(shù)量、激活函數(shù)、設(shè)置state_size
和output_size
屬性,創(chuàng)建一個(gè)沒(méi)有激活函數(shù)的SimpleRNNCell
(因?yàn)橐诰€性運(yùn)算之后、激活函數(shù)之前運(yùn)行層歸一化)。然后構(gòu)造器創(chuàng)建LayerNormalization
層,最終拿到激活函數(shù)。call()
方法先應(yīng)用簡(jiǎn)單RNN單元,計(jì)算當(dāng)前輸入和上一隱藏態(tài)的線性組合,然后返回結(jié)果兩次(事實(shí)上,在SimpleRNNCell
中,輸入等于隱藏狀態(tài):換句話說(shuō),new_states[0]
等于outputs
,因此可以放心地在剩下的call()
中忽略new_states
)。然后,call()
應(yīng)用層歸一化,然后使用激活函數(shù)。最后,返回去輸出兩次(一次作為輸出,一次作為新的隱藏態(tài))。要使用這個(gè)自定義單元,需要做的是創(chuàng)建一個(gè)keras.layers.RNN
層,傳給其單元實(shí)例:
model = keras.models.Sequential([
keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True,
input_shape=[None, 1]),
keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True),
keras.layers.TimeDistributed(keras.layers.Dense(10))
])
相似地,可以創(chuàng)建一個(gè)自定義單元,在時(shí)間步之間應(yīng)用dropout。但有一個(gè)更簡(jiǎn)單的方法:Keras提供的所有循環(huán)層(除了keras.layers.RNN
)和單元都有一個(gè)dropout
超參數(shù)和一個(gè)recurrent_dropout
超參數(shù):前者定義dropout率,應(yīng)用到所有輸入上(每個(gè)時(shí)間步),后者定義dropout率,應(yīng)用到隱藏態(tài)上(也是每個(gè)時(shí)間步)。無(wú)需在RNN中創(chuàng)建自定義單元來(lái)應(yīng)用dropout。
有了這些方法,就可以減輕不穩(wěn)定梯度問(wèn)題,高效訓(xùn)練RNN了。下面來(lái)看如何處理短期記憶問(wèn)題。
處理短期記憶問(wèn)題
由于數(shù)據(jù)在RNN中流動(dòng)時(shí)會(huì)經(jīng)歷轉(zhuǎn)換,每個(gè)時(shí)間步都損失了一定信息。一定時(shí)間后,第一個(gè)輸入實(shí)際上會(huì)在 RNN 的狀態(tài)中消失。就像一個(gè)攪局者。比如《尋找尼莫》中的多莉想翻譯一個(gè)長(zhǎng)句:當(dāng)她讀完這句話時(shí),就把開(kāi)頭忘了。為了解決這個(gè)問(wèn)題,涌現(xiàn)出了各種帶有長(zhǎng)期記憶的單元。首先了解一下最流行的一種:長(zhǎng)短時(shí)記憶神經(jīng)單元 LSTM。
LSTM 單元
長(zhǎng)短時(shí)記憶單元在 1997 年由 Sepp Hochreiter 和 Jürgen Schmidhuber 首次提出,并在接下來(lái)的幾年內(nèi)經(jīng)過(guò) Alex Graves、Ha?im Sak、Wojciech Zaremba 等人的改進(jìn),逐漸完善。如果把 LSTM 單元看作一個(gè)黑盒,可以將其當(dāng)做基本單元一樣來(lái)使用,但 LSTM 單元比基本單元性能更好:收斂更快,能夠感知數(shù)據(jù)的長(zhǎng)時(shí)依賴(lài)。在Keras中,可以將SimpleRNN
層,替換為LSTM
層:
model = keras.models.Sequential([
keras.layers.LSTM(20, return_sequences=True, input_shape=[None, 1]),
keras.layers.LSTM(20, return_sequences=True),
keras.layers.TimeDistributed(keras.layers.Dense(10))
])
或者,可以使用通用的keras.layers.RNN layer
,設(shè)置LSTMCell
參數(shù):
model = keras.models.Sequential([
keras.layers.RNN(keras.layers.LSTMCell(20), return_sequences=True,
input_shape=[None, 1]),
keras.layers.RNN(keras.layers.LSTMCell(20), return_sequences=True),
keras.layers.TimeDistributed(keras.layers.Dense(10))
])
但是,當(dāng)在GPU運(yùn)行時(shí),LSTM層使用了優(yōu)化的實(shí)現(xiàn)(見(jiàn)第19章),所以更應(yīng)該使用LSTM層(RNN
大多用來(lái)自定義層)。
LSTM 單元的工作機(jī)制是什么呢?圖 15-9 展示了 LSTM 單元的結(jié)構(gòu)。
如果不觀察黑箱的內(nèi)部,LSTM單元跟常規(guī)單元看起來(lái)差不多,除了LSTM單元的狀態(tài)分成了兩個(gè)矢量:h(t) 和 c(t)(c
代表 cell)。可以認(rèn)為 h(t) 是短期記憶狀態(tài),c(t) 是長(zhǎng)期記憶狀態(tài)。
現(xiàn)在打開(kāi)黑箱。LSTM 單元的核心思想是它能從長(zhǎng)期狀態(tài)中學(xué)習(xí)該存儲(chǔ)什么、丟掉什么、讀取什么。當(dāng)長(zhǎng)期狀態(tài) c(t-1) 從左向右在網(wǎng)絡(luò)中傳播,它先經(jīng)過(guò)遺忘門(mén)(forget gate),丟棄一些記憶,之后通過(guò)添加操作增加一些記憶(從輸入門(mén)中選擇一些記憶)。結(jié)果c(t) 不經(jīng)任何轉(zhuǎn)換直接輸出。因此,在每個(gè)時(shí)間步,都有一些記憶被拋棄,也有新的記憶添加進(jìn)來(lái)。另外,添加操作之后,長(zhǎng)時(shí)狀態(tài)復(fù)制后經(jīng)過(guò) tanh 激活函數(shù),然后結(jié)果被輸出門(mén)過(guò)濾。得到短時(shí)狀態(tài)h(t)(它等于這一時(shí)間步的單元輸出, y(t)。接下來(lái)討論新的記憶如何產(chǎn)生,門(mén)是如何工作的。
首先,當(dāng)前的輸入矢量 x(t) 和前一時(shí)刻的短時(shí)狀態(tài) h(t-1) 作為輸入,傳給四個(gè)不同的全連接層,這四個(gè)全連接層有不同的目的:
輸出 g(t)的層是主要層。它的常規(guī)任務(wù)是分析當(dāng)前的輸入 x(t) 和前一時(shí)刻的短時(shí)狀態(tài) h(t-1)。基本單元中與這種結(jié)構(gòu)一樣,直接輸出了 h(t) 和 y(t) 。相反的,LSTM 單元中的該層的輸出不會(huì)直接出去,兒是將最重要的部分保存在長(zhǎng)期狀態(tài)中(其余部分丟掉)。
-
其它三個(gè)全連接層被是門(mén)控制器(gate controller)。其采用 Logistic 作為激活函數(shù),輸出范圍在 0 到 1 之間。可以看到,這三個(gè)層的輸出提供給了逐元素乘法操作,當(dāng)輸入為 0 時(shí)門(mén)關(guān)閉,輸出為 1 時(shí)門(mén)打開(kāi)。具體講:
遺忘門(mén)(由 f(t) 控制)決定哪些長(zhǎng)期記憶需要被刪除;
輸入門(mén)(由 i(t) 控制) 決定哪部分 g(t) 應(yīng)該被添加到長(zhǎng)時(shí)狀態(tài)中。
輸出門(mén)(由 o(t) 控制)決定長(zhǎng)時(shí)狀態(tài)的哪些部分要讀取和輸出為 h(t) 和y(t)。
總而言之,LSTM 單元能夠?qū)W習(xí)識(shí)別重要輸入(輸入門(mén)的作用),存儲(chǔ)進(jìn)長(zhǎng)時(shí)狀態(tài),并保存必要的時(shí)間(遺忘門(mén)功能),并在需要時(shí)提取出來(lái)。這解釋了為什么LSTM 單元能夠如此成功地獲取時(shí)間序列、長(zhǎng)文本、錄音等數(shù)據(jù)中的長(zhǎng)期模式。
公式 15-3 總結(jié)了如何計(jì)算單元的長(zhǎng)時(shí)狀態(tài),短時(shí)狀態(tài),和單個(gè)實(shí)例的在每個(gè)時(shí)間步的輸出(小批次的公式和這個(gè)公式很像)。
在這個(gè)公式中,
Wxi,Wxf,Wxo,Wxg 是四個(gè)全連接層連接輸入向量 x(t) 的權(quán)重。
Whi,Whf,Who,Whg 是四個(gè)全連接層連接上一時(shí)刻的短時(shí)狀態(tài) h(t-1) 的權(quán)重。
bi,bf,bo,bg是全連接層的四個(gè)偏置項(xiàng)。需要注意的是 TensorFlow 將bf初始化為全 1 向量,而非全 0。這樣可以保證在訓(xùn)練狀態(tài)開(kāi)始時(shí),防止忘掉所有東西。
窺孔連接
在基本 LSTM 單元中,門(mén)控制器只能觀察當(dāng)前輸入 x(t) 和前一時(shí)刻的短時(shí)狀態(tài) h(t-1)。不妨讓各個(gè)門(mén)控制器窺視一下長(zhǎng)時(shí)狀態(tài),獲取一些上下文信息。該想法由 Felix Gers 和 Jürgen Schmidhuber 在 2000 年提出。他們提出了一個(gè) LSTM 的變體,帶有叫做窺孔連接的額外連接:把前一時(shí)刻的長(zhǎng)時(shí)狀態(tài) c(t-1) 輸入給遺忘門(mén)和輸入門(mén),當(dāng)前時(shí)刻的長(zhǎng)時(shí)狀態(tài)c(t)輸入給輸出門(mén)。這么做時(shí)常可以提高性能,但不一定每次都能有效,也沒(méi)有清晰的規(guī)律顯示哪種任務(wù)適合添加窺孔連接。
Keras中,LSTM
層基于keras.layers.LSTMCell
單元,后者目前還不支持窺孔。但是,試驗(yàn)性的tf.keras.experimental.PeepholeLSTMCell
支持,所以可以創(chuàng)建一個(gè)keras.layers.RNN
層,向構(gòu)造器傳入PeepholeLSTMCell
。
LSTM有多種其它變體,其中特別流行的是GRU單元。
GRU 單元
門(mén)控循環(huán)單元(圖 15-10)在 2014 年的 Kyunghyun Cho 的論文中提出,并且此文也引入了前文所述的編碼器-解碼器網(wǎng)絡(luò)。
GRU單元是 LSTM 單元的簡(jiǎn)化版本,能實(shí)現(xiàn)同樣的性能(這也說(shuō)明了為什么它能越來(lái)越流行)。簡(jiǎn)化主要在一下幾個(gè)方面:
長(zhǎng)時(shí)狀態(tài)和短時(shí)狀態(tài)合并為一個(gè)矢量 h(t)。
用一個(gè)門(mén)控制器z(t)控制遺忘門(mén)和輸入門(mén)。如果門(mén)控制器輸出 1,則遺忘門(mén)打開(kāi)(=1),輸入門(mén)關(guān)閉(1 - 1 = 0)。如果輸出0,則相反。換句話說(shuō),如果當(dāng)有記憶要存儲(chǔ),那么就必須先在其存儲(chǔ)位置刪掉該處記憶。這構(gòu)成了 LSTM 本身的常見(jiàn)變體。
GRU 單元取消了輸出門(mén),每個(gè)時(shí)間步輸出全態(tài)矢量。但是,增加了一個(gè)控制門(mén) r(t) 來(lái)控制前一狀態(tài)的哪些部分呈現(xiàn)給主層g(t)。
公式 15-4 總結(jié)了如何計(jì)算單元對(duì)單個(gè)實(shí)例在每個(gè)時(shí)間步的狀態(tài)。
Keras提供了keras.layers.GRU
層(基于keras.layers.GRUCell
記憶單元);使用時(shí),只需將SimpleRNN
或LSTM
替換為GRU
。
LSTM和GRU是RNN取得成功的主要原因之一。盡管它們相比于簡(jiǎn)單RNN可以處理更長(zhǎng)的序列了,還是有一定程度的短時(shí)記憶,序列超過(guò)100時(shí),比如音頻、長(zhǎng)時(shí)間序列或長(zhǎng)序列,學(xué)習(xí)長(zhǎng)時(shí)模式就很困難。應(yīng)對(duì)的方法之一,是使用縮短輸入序列,例如使用1D卷積層。
使用1D卷積層處理序列
在第14章中,我們使用2D卷積層,通過(guò)在圖片上滑動(dòng)幾個(gè)小核(或過(guò)濾器),來(lái)產(chǎn)生多個(gè)2D特征映射(每個(gè)核產(chǎn)生一個(gè))。相似的,1D軍幾層在序列上滑動(dòng)幾個(gè)核,每個(gè)核可以產(chǎn)生一個(gè)1D特征映射。每個(gè)核能學(xué)到一個(gè)非常短序列模式(不會(huì)超過(guò)核的大小)。如果你是用10個(gè)核,則輸出會(huì)包括10個(gè)1維的序列(長(zhǎng)度相同),或者可以將輸出當(dāng)做一個(gè)10維的序列。這意味著,可以搭建一個(gè)由循環(huán)層和1D卷積層(或1維池化層)混合組成的神經(jīng)網(wǎng)絡(luò)。如果1D卷積層的步長(zhǎng)是1,填充為零,則輸出序列的長(zhǎng)度和輸入序列相同。但如果使用"valid"
填充,或大于1的步長(zhǎng),則輸出序列會(huì)比輸入序列短,所以一定要按照目標(biāo)作出調(diào)整。例如,下面的模型和之前的一樣,除了開(kāi)頭是一個(gè)步長(zhǎng)為2的1D卷積層,用因子2對(duì)輸入序列降采樣。核大小比步長(zhǎng)大,所以所有輸入會(huì)用來(lái)計(jì)算層的輸出,所以模型可以學(xué)到保存有用的信息、丟棄不重要信息。通過(guò)縮短序列,卷積層可以幫助GRU檢測(cè)長(zhǎng)模式。注意,必須裁剪目標(biāo)中的前三個(gè)時(shí)間步(因?yàn)楹舜笮∈?,卷積層的第一個(gè)輸出是基于輸入時(shí)間步0到3),并用因子2對(duì)目標(biāo)做降采樣:
model = keras.models.Sequential([
keras.layers.Conv1D(filters=20, kernel_size=4, strides=2, padding="valid",
input_shape=[None, 1]),
keras.layers.GRU(20, return_sequences=True),
keras.layers.GRU(20, return_sequences=True),
keras.layers.TimeDistributed(keras.layers.Dense(10))
])
model.compile(loss="mse", optimizer="adam", metrics=[last_time_step_mse])
history = model.fit(X_train, Y_train[:, 3::2], epochs=20,
validation_data=(X_valid, Y_valid[:, 3::2]))
如果訓(xùn)練并評(píng)估這個(gè)模型,你會(huì)發(fā)現(xiàn)它是目前最好的模型。卷積層確實(shí)發(fā)揮了作用。事實(shí)上,可以只使用1D卷積層,不用循環(huán)層!
WaveNet
在一篇2016年的論文中,Aaron van den Oord和其它DeepMind的研究者,提出了一個(gè)名為WaveNet的架構(gòu)。他們將1D卷積層疊起來(lái),每一層膨脹率(如何將每個(gè)神經(jīng)元的輸入分開(kāi))變?yōu)?倍:第一個(gè)卷積層一次只觀察兩個(gè)時(shí)間步,,接下來(lái)的一層觀察四個(gè)時(shí)間步(感受野是4個(gè)時(shí)間步的長(zhǎng)度),下一層觀察八個(gè)時(shí)間步,以此類(lèi)推(見(jiàn)圖15-11)。用這種方式,底下的層學(xué)習(xí)短時(shí)模式,上面的層學(xué)習(xí)長(zhǎng)時(shí)模式。得益于翻倍的膨脹率,這個(gè)網(wǎng)絡(luò)可以非常高效地處理極長(zhǎng)的序列。
在WaveNet論文中,作者疊了10個(gè)卷積層,膨脹率為1, 2, 4, 8, …, 256, 512,然后又疊了一組10個(gè)相同的層(膨脹率還是1, 2, 4, 8, …, 256, 512),然后又是10個(gè)相同的層。作者解釋到,一摞這樣的10個(gè)卷積層,就像一個(gè)超高效的核大小為1024的卷積層(只是更快、更強(qiáng)、參數(shù)更少),所以同樣的結(jié)構(gòu)疊了三次。他們還給輸入序列左填充了一些0,以滿足每層的膨脹率,使序列長(zhǎng)度不變。下面的代碼實(shí)現(xiàn)了簡(jiǎn)化的WaveNet,來(lái)處理前面的序列:
model = keras.models.Sequential()
model.add(keras.layers.InputLayer(input_shape=[None, 1]))
for rate in (1, 2, 4, 8) * 2:
model.add(keras.layers.Conv1D(filters=20, kernel_size=2, padding="causal",
activation="relu", dilation_rate=rate))
model.add(keras.layers.Conv1D(filters=10, kernel_size=1))
model.compile(loss="mse", optimizer="adam", metrics=[last_time_step_mse])
history = model.fit(X_train, Y_train, epochs=20,
validation_data=(X_valid, Y_valid))
Sequential
模型開(kāi)頭是一個(gè)輸入層(比只在第一個(gè)層上設(shè)定input_shape
簡(jiǎn)單的多);然后是一個(gè)1D卷積層,使用"causal"
填充:這可以保證卷積層在做預(yù)測(cè)時(shí),不會(huì)窺視到未來(lái)值(等價(jià)于在輸入序列的左邊用零填充填充合適數(shù)量的0)。然后添加相似的成對(duì)的層,膨脹率為1、2、4、8,接著又是1、2、4、8。最后,添加輸出層:一個(gè)有10個(gè)大小為1的過(guò)濾器的卷積層,沒(méi)有激活函數(shù)。得益于填充層,每個(gè)卷積層輸出的序列長(zhǎng)度都和輸入序列一樣,所以訓(xùn)練時(shí)的目標(biāo)可以是完整序列:無(wú)需裁剪或降采樣。
最后兩個(gè)模型的序列預(yù)測(cè)結(jié)果最好!在WaveNet論文中,作者在多種音頻任務(wù)(WaveNet名字正是源于此)中,包括文本轉(zhuǎn)語(yǔ)音任務(wù)(可以輸出多種語(yǔ)言極為真實(shí)的語(yǔ)音),達(dá)到了頂尖的表現(xiàn)。他們還用這個(gè)模型生成音樂(lè),每次生成一段音頻。每段音頻包含上萬(wàn)個(gè)時(shí)間步(LSTM和GRU無(wú)法處理如此長(zhǎng)的序列),這是相當(dāng)了不起的。
第16章,我們會(huì)繼續(xù)探索RNN,會(huì)看到如何用RNN處理各種NLP任務(wù)。
練習(xí)
你能說(shuō)出序列到序列RNN 的幾個(gè)應(yīng)用嗎?序列到矢量的應(yīng)用?矢量到序列的應(yīng)用?
RNN層的輸入要有多少維?每一維表示什么?輸出呢?
如果搭建深度序列到序列RNN,哪些RNN層要設(shè)置
return_sequences=True
?序列到矢量RNN又如何?假如有一個(gè)每日單變量時(shí)間序列,想預(yù)測(cè)接下來(lái)的七天。要使用什么RNN架構(gòu)?
訓(xùn)練RNN的困難是什么?如何應(yīng)對(duì)?
畫(huà)出LSTM單元的架構(gòu)圖?
為什么在RNN中使用1D卷積層?
哪種神經(jīng)網(wǎng)絡(luò)架構(gòu)可以用來(lái)分類(lèi)視頻?
為SketchRNN數(shù)據(jù)集(TensorFlow Datasets中有),訓(xùn)練一個(gè)分類(lèi)模型。
下載Bach chorales數(shù)據(jù)集,并解壓。它含有382首巴赫作曲的贊美歌。每首的長(zhǎng)度是100到640時(shí)間步,每個(gè)時(shí)間步包含4個(gè)整數(shù),每個(gè)整數(shù)對(duì)應(yīng)一個(gè)鋼琴音符索引(除了0,表示沒(méi)有音符)。訓(xùn)練一個(gè)可以預(yù)測(cè)下一個(gè)時(shí)間步(四個(gè)音符)的模型,循環(huán)、卷積、或混合架構(gòu)。然后使用這個(gè)模型來(lái)生成類(lèi)似巴赫的音樂(lè),每個(gè)時(shí)間一個(gè)音符:可以給模型一首贊美歌的開(kāi)頭,然后讓其預(yù)測(cè)接下來(lái)的時(shí)間步,然后將輸出加到輸入上,再讓模型繼續(xù)預(yù)測(cè)。或者查看Google的 Coconet 模型,它是Google來(lái)做巴赫曲子的。
參考答案見(jiàn)附錄A。
第10章 使用Keras搭建人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)
第12章 使用TensorFlow自定義模型并訓(xùn)練
第13章 使用TensorFlow加載和預(yù)處理數(shù)據(jù)
第14章 使用卷積神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)深度計(jì)算機(jī)視覺(jué)
第15章 使用RNN和CNN處理序列
第16章 使用RNN和注意力機(jī)制進(jìn)行自然語(yǔ)言處理
第17章 使用自編碼器和GAN做表征學(xué)習(xí)和生成式學(xué)習(xí)
第18章 強(qiáng)化學(xué)習(xí)
第19章 規(guī)模化訓(xùn)練和部署TensorFlow模型