Keras詞級自然語言模型
更多深度文章,請關注雲計算頻道:https://yq.aliyun.com/cloud
語言模型是許多自然語言處理模型(如機器翻譯和語音識別)中的關鍵元素,它可以根據給出的單詞序列預測到序列中的下一個單詞。在選擇語言模型的框架時需要注意與語言模型的目的匹配。
本教程分為5個部分; 他們是:
1.語言建模框架。
2.模型1:單字輸入,單字輸出序列。
3.模型2:逐行序列。
4.模型3:雙字輸入,單字輸出序列。
1、語言建模框架
語言模型是挑戰自然語言處理問題(如機器翻譯和語音識別)較大模型中的關鍵組成部分。它們也可以作為獨立模型開發,用於生成與源文本具有相同統計屬性的新序列。
語言模型可以同時學習和預測一個單詞。網絡的訓練包括提供一係列的單詞作為輸入,在這個過程中,每一個輸入序列都可以進行預測和學習。
同樣地,在進行預測時,可以用一個或幾個單詞來表示這個過程,然後將預測得到的單詞進行收集,作為對後續預測輸入的源文本。
因此,每個模型都涉及將源文本分解為輸入和輸出序列,使得模型可以學習如何預測單詞。
在本教程中,我們將探索在深度學習庫Keras中開發基於單詞的語言模型的三種不同方式。
模型1:單字輸入,單字輸出序列
我們可以從一個非常簡單的模型開始。
給定一個單詞作為輸入,模型將學習預測序列中的下一個單詞。例如:
X, y
Jack, and
and, Jill
Jill, went
第一步是將文本編碼化為整數。
源文本中的每個小寫單詞都被賦予一個唯一的整數,我們可以將單詞序列轉換為整數序列。
Keras提供可用於執行此編碼的Tokenizer類。首先,Tokenizer適合源文本來開發從單詞到唯一整數的映射。然後,通過調用texts_to_sequences()函數將文本序列轉換為整數序列。
# integer encode text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]
我們需要通過訪問word_index類,從經過訓練的Tokenizer中檢索詞匯表的大小得出以後的詞匯的大小,以便在模型中定義詞的嵌入層,並且使用一個熱編碼來編碼輸出詞。
# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
在這個例子中,我們可以看到詞匯的大小是21個單詞。
因為我們需要將最大的編碼的整數指定為一個數組索引,例如編碼1到21的數組編號為0到21或22的位置,所以我們需要再添加一個編碼。
接下來,我們需要創建一個單詞序列,以一個單詞作為輸入,一個單詞作為輸出來匹配模型。
# create word -> word sequences
sequences = list()
for i in range(1, len(encoded)):
sequence = encoded[i-1:i+1]
sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
運行這一塊顯示,我們總共有24個輸入輸出來訓練網絡。
Total Sequences: 24
然後,我們可以將這些序列分解為輸入(X)和輸出元素(y)。這很簡單,因為我們隻有兩列數據。
# split into X and y elements
sequences = array(sequences)
X, y = sequences[:,0],sequences[:,1]
我們將使用我們的模型來預測詞匯表中所有單詞的概率分布。這意味著我們需要將輸出元素從單個整數轉換為一個熱門編碼,對於詞匯表中的每個單詞都有一個0,對於實際單詞來說,該值為1。這為網絡提供了一個基本事實,從中可以計算出錯誤的地方並更新模型。
Keras提供了to_categorical()函數,我們可以使用該函數將整數轉換為一個熱門編碼,同時指定類的數量作為詞匯量大小。
# one hot encode outputs
y = to_categorical(y, num_classes=vocab_size)
我們現在準備定義神經網絡模型。
該模型使用了一個在輸入層中的學習詞。這對詞匯表中的每個單詞都有一個實值向量,其中每個單詞向量都有一個指定的長度。在本例中,我們將使用10維投影。輸入序列包含一個單詞,因此input_length = 1。
該模型有一個隱藏的LSTM層,有50個單元。這遠遠超出了需要。在詞匯表中,輸出層由一個神經元組成,並使用一個softmax激活函數,以確保能夠標準輸出。
# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
網絡的結構可以概括如下:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, 1, 10) 220
_________________________________________________________________
lstm_1 (LSTM) (None, 50) 12200
_________________________________________________________________
dense_1 (Dense) (None, 22) 1122
=================================================================
Total params: 13,542
Trainable params: 13,542
Non-trainable params: 0
_________________________________________________________________
接下來,我們可以在編碼的文本數據上編譯和適配網絡。從技術上講,我們正在使用分類交叉熵損失函數構建一個解決多元分類問題(預測詞匯中的單詞)的模型。
由於網絡配置的原因, 我們選擇了超出規定的配置,以確保我們可以專注於語言模型的框架上。
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)
模型擬合後,我們通過從詞匯中傳遞給定的單詞來測試它,讓模型預測下一個單詞。在這裏,我們通過對“ Jack ”進行編碼並調用model.predict_classes()來獲取預測單詞的整數輸出。然後在詞匯映射中查找這個關聯詞。
# evaluate
in_text = 'Jack'
print(in_text)
encoded = tokenizer.texts_to_sequences([in_text])[0]
encoded = array(encoded)
yhat = model.predict_classes(encoded, verbose=0)
for word, index in tokenizer.word_index.items():
if index == yhat:
print(word)
這個過程可以重複幾次,以建立一個生成的單詞序列。
為了使這更容易,我們將這個行為包含在一個函數中,我們可以通過傳入我們的模型和種子詞來調用這個函數。
# generate a sequence from the model
def generate_seq(model, tokenizer, seed_text, n_words):
in_text, result = seed_text, seed_text
# generate a fixed number of words
for _ in range(n_words):
# encode the text as integer
encoded = tokenizer.texts_to_sequences([in_text])[0]
encoded = array(encoded)
# predict a word in the vocabulary
yhat = model.predict_classes(encoded, verbose=0)
# map predicted word index to word
out_word = ''
for word, index in tokenizer.word_index.items():
if index == yhat:
out_word = word
break
# append to input
in_text, result = out_word, result + ' ' + out_word
return result
運行該示例,打印出每個訓練時期的損失和準確性。
Epoch 496/500
0s - loss: 0.2358 - acc: 0.8750
Epoch 497/500
0s - loss: 0.2355 - acc: 0.8750
Epoch 498/500
0s - loss: 0.2352 - acc: 0.8750
Epoch 499/500
0s - loss: 0.2349 - acc: 0.8750
Epoch 500/500
0s - loss: 0.2346 - acc: 0.8750
我們可以看到,模型並不記住源序列,可能因為在輸入序列中有一些不明確的地方模型並沒有記住源序列。例如:
jack => and
jack => fell
在運行結束時,“ Jack ”被輸入並產生預測或新的序列。
我們基於一些元素的來源,會得到一個合理的序列作為輸出。
Jack and jill came tumbling after down
這是一個很好的語言模型,但是沒有充分利用LSTM處理輸入序列的能力問題,但通過使用更廣泛的上下文來消除一些不明確的成對序列。
模型2:逐行序列
另一種方法是逐行分解源文本,然後將每行分解為一係列構建的單詞。
例如:
X, y
_, _, _, _, _, Jack, and
_, _, _, _, Jack, and Jill
_, _, _, Jack, and, Jill, went
_, _, Jack, and, Jill, went, up
_, Jack, and, Jill, went, up, the
Jack, and, Jill, went, up, the, hill
這種方法可以允許模型使用每一行的上下文來幫助模型,在這種情況下,一個簡單的單詞進出模型會造成模煳性。
在這種情況下,這是以跨行預測單詞為代價的,如果我們隻對建模和生成文本行感興趣,現在可能會很好。
請注意,在這種表示中,我們將需要填充序列以確保它們符合固定長度的輸入。這是使用Keras時的要求。
首先,我們可以使用已經適合源文本的Tokenizer逐行地創建整數序列。
# create line-based sequences
sequences = list()
for line in data.split('\n'):
encoded = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(encoded)):
sequence = encoded[:i+1]
sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
接下來,我們可以填充準備好的序列。我們可以使用Keras中提供的pad_sequences()函數來做到這一點。首先涉及找到最長的序列,然後用它作為填充所有其他序列的長度。
# pad input sequences
max_length = max([len(seq) for seq in sequences])
sequences = pad_sequences(sequences, maxlen=max_length, padding='pre')
print('Max Sequence Length: %d' % max_length)
接下來,我們可以將序列分成輸入和輸出元素,就像以前一樣。
# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)
然後可以像以前那樣定義模型,除了現在輸入序列比單個單詞長。具體來說,它們的長度是max_length-1,因為當我們計算序列的最大長度時,它們包括輸入和輸出元素。
# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=max_length-1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)
我們可以像以前一樣使用該模型生成新的序列。將所述generate_seq()函數更新通過後加入預測,以輸入字每次迭代列表來建立一個輸入序列。
# generate a sequence from a language model
def generate_seq(model, tokenizer, max_length, seed_text, n_words):
in_text = seed_text
# generate a fixed number of words
for _ in range(n_words):
# encode the text as integer
encoded = tokenizer.texts_to_sequences([in_text])[0]
# pre-pad sequences to a fixed length
encoded = pad_sequences([encoded], maxlen=max_length, padding='pre')
# predict probabilities for each word
yhat = model.predict_classes(encoded, verbose=0)
# map predicted word index to word
out_word = ''
for word, index in tokenizer.word_index.items():
if index == yhat:
out_word = word
break
# append to input
in_text += ' ' + out_word
return in_text
附件中提供了完整的代碼示例。運行該示例可以更好地適應源數據。增加的上下文使模型能夠消除一些例子的歧義。仍然有兩行文字以“ Jack ” 開頭,可能仍然是網絡的問題。
Epoch 496/500
0s - loss: 0.1039 - acc: 0.9524
Epoch 497/500
0s - loss: 0.1037 - acc: 0.9524
Epoch 498/500
0s - loss: 0.1035 - acc: 0.9524
Epoch 499/500
0s - loss: 0.1033 - acc: 0.9524
Epoch 500/500
0s - loss: 0.1032 - acc: 0.9524
在運行結束時,我們會生成兩個不同種子詞的序列:“ Jack ”和“ Jill ”。
第一個生成的行看起來不錯,直接匹配源文本。第二個看著有點奇怪,那是因為網絡隻能在序列中看到“ Jill ”,為了最後一行押韻,它強製輸出單詞“ Jill ”。
Jack fell down and broke
Jill jill came tumbling after
這是例子恰當的表明了框架可能會生成更好的新行,但是它並不是一段好的輸入行。
模型3:雙字輸入,單字輸出序列
我們可以使用單詞輸入法和整個句子輸入法之間的媒介,輸入一個單詞的子序列。
這將提供兩個框架之間的權衡,從而允許生成新的線路。
我們將使用3個單詞作為輸入來預測一個字作為輸出。序列的製備與第一個例子非常相似,除了源序列陣列中的偏移量不同之外,如下所示:
# encode 2 words -> 1 word
sequences = list()
for i in range(2, len(encoded)):
sequence = encoded[i-2:i+1]
sequences.append(sequence)
完整的例子在附件中
再次運行這個例子得到合適的源文本的準確率在95%左右。
...
Epoch 496/500
Epoch 500/500
0s - loss: 0.0684 - acc: 0.9565
我們看看4代的例子,兩個開始的行的情形和兩個開始的中間行。
Jack and jill went up the hill
And Jill went up the
fell down and broke his crown and
pail of water jack fell down and
開始生成的第一個行是正確的,但是第二個不是。第二種情況是第四行的一個例子,與第一行的內容不一致。
我們可以看到,如何選擇語言模型的框架必須和如何使用模型的要求是匹配的。如何選擇語言模型的框架,以及如何使用模型的要求必須是兼容的。通常使用語言模型時需要仔細的設計,也許隨後需要通過序列生成的現場測試來確認模型要求是否得到滿足。
作者信息
Dr. Jason Brownlee 是一名機器學習從業者,學術研究人員,致力於幫助開發人員從入門到精通機器學習。
本文由北郵@愛可可-愛生活老師推薦,阿裏雲雲棲社區組織翻譯。
文章原標題《How to Develop Word-Based Neural Language Models in Python with Keras》
作者:Dr.Jason Brownlee譯者:烏拉烏拉,審閱:袁虎
文章為簡譯,更為詳細的內容,請查看原文
最後更新:2017-11-10 14:34:41