本篇文章參考《Packt.Deep.Learning.with.Keras》 電子版網(wǎng)上一搜就能找到,主要關(guān)注于使用RNN進行文本相關(guān)處理的流程。
實現(xiàn)效果
以《愛麗絲夢游仙境》的英文版為例,給出i am alice的開頭,網(wǎng)絡(luò)能實現(xiàn)續(xù)寫故事的效果,如下:
i am alice , and the project gutenberg-tm electronic work in a low the white rabbit again the gryphon as she co
不過畢竟只是個字符級的生成Demo,效果一般,我們會在后續(xù)討論如何改進效果。
預(yù)處理
文本讀取
首先從http://www.gutenberg.org/下載alice_in_wonderland.txt,讀出所有文本,消除空行
with open('alice_in_wonderland.txt','r') as fin:
lines=[]
for line in fin:
line=line.strip().lower()
#空行
if(len(line)==0):
continue
lines.append(line)
text=" ".join(lines)
字符編碼
這一步是為了把字符轉(zhuǎn)換成網(wǎng)絡(luò)能看懂的形式
#所有字符的集合
chars=set(c for c in text)
nb_chars=len(chars)
#構(gòu)建映射表
char2index=dict((c,i) for i,c in enumerate(chars))
index2char=dict((i,c) for i,c in enumerate(chars))
訓(xùn)練集生成
我們文本生成的方式是通過前10個字符生成第11個,然后以第2個到第11個字符再來生成第12個,以此往復(fù)。所以訓(xùn)練集的X就是原小說中的10個字符,Y就是第11個。代碼如下
SEQLEN=10
STEP=1
input_chars=[]
label_chars=[]
for i in range(0,len(text)-SEQLEN,STEP):
input_chars.append(text[i:i+SEQLEN])
label_chars.append(text[i+SEQLEN])
打印一下看下效果
for i in range(5):
print(input_chars[i],"->",label_chars[i])
anyone an -> y
anyone any -> w
nyone anyw -> h
yone anywh -> e
one anywhe -> r
字符串有了,再用上面生成的映射表轉(zhuǎn)為one-hot編碼。以26個字母為例,X的形狀即為(data_size,10,26),其中的data_size為有多少條X數(shù)據(jù),10指每條數(shù)據(jù)由10個字符組成,26為one-hot編碼長度。
這里是手工進行one-hot編碼,采用keras.utils.to_categorical也是可以的。
import numpy as np
# 轉(zhuǎn)Index
# Input:(batch_size=len(input_chars),dim1=SEQLEN, dim2=nb_chars)
X=np.zeros((len(input_chars),SEQLEN,nb_chars),dtype=np.bool)
y=np.zeros((len(label_chars),nb_chars),dtype=np.bool)
for i, input_char in enumerate(input_chars):
for j,ch in enumerate(input_char):
X[i,j,char2index[ch]]=1
y[i,char2index[label_chars[i]]]=1
網(wǎng)絡(luò)構(gòu)建
書上寫的是用字符串的形式去描述誤差,比如loss='categorical_crossentropy',不過這樣手打容易出錯,如果是Jupyter notebook環(huán)境的話,可以從keras.losses里面import一下,也是一樣的效果。
from keras import Sequential
from keras.layers import SimpleRNN,Dense,Activation
from keras.activations import softmax
from keras.losses import categorical_crossentropy
from keras.optimizers import RMSprop
HIDDEN_SIZE=128
BATCH_SIZE=128
NUM_ITERATIONS=25
NUM_EPOCHS_PER_ITERATION=1
NUM_PREDS_PER_EPOCH=100
model=Sequential()
model.add(SimpleRNN(HIDDEN_SIZE,return_sequences=False,
input_shape=(SEQLEN,nb_chars),
unroll=True))
model.add(Dense(nb_chars))
model.add(Activation(softmax))
#這里的RMSprop優(yōu)化器需要構(gòu)造,所以要加上括號
model.compile(loss=categorical_crossentropy,optimizer=RMSprop())
網(wǎng)絡(luò)結(jié)構(gòu)如下。需要注意書上對RNN進行講解的時候會把RNN按時間展開

這里的一個圓圈表示RNN的一層(而不只是一個Cell),包含HIDDEN_SIZE個RNN Cell(可以理解成抽取HIDDEN_SIZE個特征)
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_1 (SimpleRNN) (None, 128) 24192
_________________________________________________________________
dense_1 (Dense) (None, 60) 7740
_________________________________________________________________
activation_1 (Activation) (None, 60) 0
=================================================================
文本生成
for iteration in range(NUM_ITERATIONS):
print("_"*50)
print("Iteratipn #:%d"%iteration)
model.fit(X,y,batch_size=BATCH_SIZE,epochs=NUM_EPOCHS_PER_ITERATION)
next_chars="i am alice"
for i in range(NUM_PREDS_PER_EPOCH):
Xtest=np.zeros((1,SEQLEN,nb_chars),dtype=np.bool)
index_list=[char2index[c] for c in next_chars]
for i,ci in enumerate(index_list):
Xtest[0][i][ci]=True
pred=model.predict(Xtest)
pred_char=index2char[np.argmax(pred)]
print(pred_char,end="")
next_chars=next_chars[1:]+pred_char
這里預(yù)測出來的字符直接取了概率最大的,但如果概率是0.51,0.49這樣,對0.49就不是很公平,也可以采用如下的sample函數(shù),按概率取出字符以增加文本的多樣性。

最后幾次迭代效果如下:
Iteratipn #:22
Epoch 1/1
161794/161794 [==============================] - 8s 49us/step - loss: 1.4239
, and the project gutenberg-tm electronic work in a low the white rabbit again the gryphon as she co__________________________________________________
Iteratipn #:23
Epoch 1/1
161794/161794 [==============================] - 8s 51us/step - loss: 1.4177
was a little to see while the this agreement in the door and she was a little to see while the this__________________________________________________
Iteratipn #:24
Epoch 1/1
161794/161794 [==============================] - 8s 50us/step - loss: 1.4101
was a little course were and donations to the queen was a little course were and donations to the q
改進與思考
首先網(wǎng)絡(luò)從前幾個字符預(yù)測下一個字符比較類似n-gram語言模型,不過由于是按照字符級別構(gòu)造的,生成效果當然沒有按照單詞自然,下篇文章我們采用LSTM來對句子進行單詞級別的處理,用于分析情感;另外從結(jié)果中可以看到,可能是因為原文中下劃線_經(jīng)常連續(xù)出現(xiàn),導(dǎo)致最后生成的文本也是連續(xù)的下劃線,可以在一開始的文本處理中選擇剔除掉除字母之外的字符。
應(yīng)用角度上來看,除了文本的預(yù)測,還可以是語音的生成,圖像的補全(PixelRNN)等等。
RNN倒是能夠簡單地模擬大腦對時間序列的記憶模式,但是對記憶比較久遠的事情就無能為力了,但是人類卻可以做到對很久遠的、例如童年時期事情的回憶,這樣的模式如何在網(wǎng)絡(luò)上復(fù)現(xiàn)?這樣的記憶似乎可以看成是重要程度(對這個人人生觀價值觀的沖擊大?。┑挠绊?,對于網(wǎng)絡(luò)如何衡量?有點像Attention機制。再有就是人類看到一個單詞往往會聯(lián)想到其他與之相關(guān)的單詞,這些單詞組成了一張知識網(wǎng)絡(luò),只需要記住其中一個知識就能推導(dǎo)出其他知識,這樣的聯(lián)想相似性倒是可以由Word2Vec推導(dǎo)出來,在網(wǎng)絡(luò)上可以怎樣應(yīng)用呢?圖像可以根據(jù)特征相似度提取出相似的圖片,例如從一個圓就可以聯(lián)想到籃球、太陽、胖頭魚什么的,這樣的聯(lián)想對人類來說有什么用可能就是網(wǎng)絡(luò)應(yīng)用這些機制所需要思考的。
對RNN來說,每次計算除開X之外還加上了H(t-1),將各個時間點都聯(lián)系在了一起。人類的記憶似乎也是這樣,僅僅從大腦的上一個狀態(tài)轉(zhuǎn)移到當前狀態(tài)就行、然而RNN模式對于大腦來說相當于把每個時刻的信息都儲存到同一個神經(jīng)元里面然后再更新,而真實情況應(yīng)該是不同時間的信息存儲在不同細胞里面,然后互相調(diào)用。從這個角度上來說感覺Wavenet的模式更像一些。(雖然調(diào)用的時候是固定的幾個信息結(jié)合在一起,但是更深的層能夠決定是哪些記憶需要被提取出來)

關(guān)于記憶還可以搜索Memory Network,網(wǎng)絡(luò)起到的作用相當于CPU,外部的儲存器相當于RAM,CPU起到找出RAM地址的作用。詳細信息可以參考李宏毅課程的Attention-based Model。

以閱讀理解為例,為了回答一個問題Query,我們需要從文章中找出與這個問題相關(guān)的回答句子。下圖中的
q就是我們要查詢的RAM的“地址”,也可以說這個q是對文章中句子的編碼組合,通過這個組合找到哪些句子是相關(guān)的。假如把文章中的每個句子都用x來表示,a就是每個句子的重要程度了,也就是Attention。
Attention機制
Ref: Deep Learning基礎(chǔ)--理解LSTM/RNN中的Attention機制
目前采用編碼器-解碼器 (Encode-Decode) 結(jié)構(gòu)的模型非常熱門,是因為它在許多領(lǐng)域較其他的傳統(tǒng)模型方法都取得了更好的結(jié)果。這種結(jié)構(gòu)的模型通常將輸入序列編碼成一個固定長度的向量表示,對于長度較短的輸入序列而言,該模型能夠?qū)W習(xí)出對應(yīng)合理的向量表示。然而,這種模型存在的問題在于:當輸入序列非常長時,模型難以學(xué)到合理的向量表示。
類似于人類的記憶,一大堆記憶涌入的時候是記不住的,只會記住關(guān)鍵的一些信息,Attention機制做的就是找出哪些記憶是需要重點關(guān)注的。
“在文本翻譯任務(wù)上,使用attention機制的模型每生成一個詞時都會在輸入序列中找出一個與之最相關(guān)的詞集合。之后模型根據(jù)當前的上下文向量 (context vectors) 和所有之前生成出的詞來預(yù)測下一個目標詞。
… 它將輸入序列轉(zhuǎn)化為一堆向量的序列并自適應(yīng)地從中選擇一個子集來解碼出目標翻譯文本。這感覺上像是用于文本翻譯的神經(jīng)網(wǎng)絡(luò)模型需要“壓縮”輸入文本中的所有信息為一個固定長度的向量,不論輸入文本的長短。”
也就是說,它不再使用固定長度的Encode Vector(上下文向量c)作為記憶,而使用整個Input序列,Memory不再是被編碼的形態(tài),而是類似于人腦多個神經(jīng)元的形態(tài)。每個被翻譯出來的單詞yi對于Input序列來說都有一個獨立的需要關(guān)注的上下文向量ci,相當于從Memory中找到了和文本翻譯最相關(guān)的Memory出來(對齊)作為結(jié)果。
舉個例子,在翻譯出machine的時候不再使用整個機器學(xué)習(xí)的被壓縮后的上下文向量c,而是使用其獨有的c0,即重點關(guān)注機器這兩個字的上下文向量。

除開文字到文字以外,圖像到文字也有相同原理的論文。

這樣的Attention有什么用處呢?我們將在后續(xù)文章的Encoder-Decoder機制中見到他的威力。