RNN(0)從文本生成到Attention機制

本篇文章參考《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é)合在一起,但是更深的層能夠決定是哪些記憶需要被提取出來)

wavenet

關(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機制中見到他的威力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容