上一篇:《用谷歌經典ML方法方法來設計生成式人工智慧語言模型》
序言:市場上所謂的開源大語言模型並不完全開源,通常只提供權重和少量工具,而架構、訓練資料集、訓練方法及程式碼等關鍵內容並未公開。因此,要真正掌握人工智慧模型,仍需從基礎出發。本篇文章將透過傳統方法重新構建一個語言模型,以幫助大家理解語言模型的本質:它並不神秘,主要區別在於架構設計。目前主流架構是谷歌在論文《Attention Is All You Need》中提出的 Transformer,而本文選擇採用傳統的 RNN(LSTM)方法構建模型,其最大侷限在於不支援高效並行化,因而難以擴充套件。
建立模型
現在讓我們建立一個簡單的模型,用來訓練這些輸入資料。這個模型由一個嵌入層、一個 LSTM 層和一個全連線層組成。對於嵌入層,你需要為每個單詞生成一個向量,因此引數包括總單詞數和嵌入的維度數。在這個例子中,單詞數量不多,所以用八個維度就足夠了。
你可以讓 LSTM 成為雙向的,步數可以是序列的長度,也就是我們的最大長度減去 1(因為我們從末尾去掉了一個 token 用作標籤)。
最後,輸出層將是一個全連線層,其引數為總單詞數,並使用 softmax 啟用。該層的每個神經元表示下一個單詞匹配相應索引值單詞的機率:
model = Sequential()
model.add(Embedding(total_words, 8))
model.add(Bidirectional(LSTM(max_sequence_len-1)))
model.add(Dense(total_words, activation='softmax'))
使用分類損失函式(例如分類交叉熵)和最佳化器(例如 Adam)編譯模型。你還可以指定要捕獲的指標:
python
Copy code
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
這是一個非常簡單的模型,資料量也不大,因此可以訓練較長時間,比如 1,500 個 epoch:
history = model.fit(xs, ys, epochs=1500, verbose=1)
經過 1,500 個 epoch 後,你會發現模型已經達到了非常高的準確率(見圖 8-6)。
圖 8-6:訓練準確率
當模型的準確率達到 95% 左右時,我們可以確定,如果輸入一段它已經見過的文字,它預測下一個單詞的準確率約為 95%。然而請注意,當生成文字時,它會不斷遇到以前沒見過的單詞,因此儘管準確率看起來不錯,網路最終還是會迅速生成一些無意義的文字。我們將在下一節中探討這個問題。
文字生成
現在你已經訓練了一個可以預測序列中下一個單詞的網路,接下來的步驟是給它一段文字序列,讓它預測下一個單詞。我們來看看具體怎麼做。
預測下一個單詞
首先,你需要建立一個稱為種子文字(seed text)的短語。這是網路生成內容的基礎,它會透過預測下一個單詞來完成這一點。
從網路已經見過的短語開始,例如:
seed_text = "in the town of athy"
接下來,用 texts_to_sequences 對其進行標記化。即使結果只有一個值,這個方法也會返回一個陣列,因此你需要取這個陣列的第一個元素:
python
Copy code
token_list = tokenizer.texts_to_sequences([seed_text])[0]
然後,你需要對該序列進行填充,使其形狀與用於訓練的資料相同:
token_list = pad_sequences([token_list],
maxlen=max_sequence_len-1, padding='pre')
現在,你可以透過對該序列呼叫 model.predict 來預測下一個單詞。這將返回語料庫中每個單詞的機率,因此需要將結果傳遞給 np.argmax 來獲取最有可能的單詞:
predicted = np.argmax(model.predict(token_list), axis=-1)
print(predicted)
這應該會輸出值 68。如果檢視單詞索引表,你會發現這對應的單詞是 “one”:
'town': 66, 'athy': 67, 'one': 68, 'jeremy': 69, 'lanigan': 70,
你可以透過程式碼在單詞索引中搜尋這個值,並將其列印出來:
for word, index in tokenizer.word_index.items():
if index == predicted:
print(word)
break
因此,從文字 “in the town of athy” 開始,網路預測下一個單詞是 “one”。如果你檢視訓練資料,會發現這是正確的,因為這首歌的開頭是:
In the town of Athy one Jeremy Lanigan
Battered away til he hadn’t a pound
現在你已經確認模型可以正常工作,可以嘗試一些不同的種子文字。例如,當我使用種子文字 “sweet jeremy saw dublin” 時,模型預測的下一個單詞是 “then”。(這段文字的選擇是因為其中的所有單詞都在語料庫中。至少在開始時,如果種子文字中的單詞在語料庫中,你應該可以期待更準確的預測結果。)
透過遞迴預測生成文字
在上一節中,你已經學會了如何用模型根據種子文字預測下一個單詞。現在,為了讓神經網路生成新的文字,你只需重複預測的過程,每次新增新的單詞即可。
例如,之前我使用短語 “sweet jeremy saw dublin”,模型預測下一個單詞是 “then”。你可以在種子文字後新增 “then”,形成 “sweet jeremy saw dublin then”,然後進行下一次預測。重複這個過程,就可以生成一段由 AI 創造的文字。
以下是上一節程式碼的更新版,這段程式碼會迴圈執行多次,迴圈次數由 next_words 引數決定:
seed_text = "sweet jeremy saw dublin"
next_words = 10
for _ in range(next_words):
token_list = tokenizer.texts_to_sequences([seed_text])[0]
token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
predicted = model.predict_classes(token_list, verbose=0)
output_word = ""
for word, index in tokenizer.word_index.items():
if index == predicted:
output_word = word
break
seed_text += " " + output_word
print(seed_text)
執行這段程式碼後,可能會生成類似這樣的字串:
sweet jeremy saw dublin then got there as me me a call doing me
文字很快會變得無意義。為什麼會這樣?
-
訓練文字量太小:文字語料庫的規模非常有限,因此模型幾乎沒有足夠的上下文來生成合理的結果。
-
預測依賴性:序列中下一個單詞的預測高度依賴於之前的單詞。如果前幾個單詞的匹配度較差,即使最佳的“下一個”預測單詞的機率也會很低。當將這個單詞新增到序列後,再預測下一個單詞時,其匹配機率進一步降低,因此最終生成的單詞序列看起來會像隨機拼湊的結果。
例如,雖然短語 “sweet jeremy saw dublin” 中的每個單詞都在語料庫中,但這些單詞從未以這種順序出現過。在第一次預測時,模型選擇了機率最高的單詞 “then”,其機率高達 89%。當把 “then” 新增到種子文字中,形成 “sweet jeremy saw dublin then” 時,這個新短語也從未在訓練資料中出現過。因此,模型將機率最高的單詞 “got”(44%)作為預測結果。繼續向句子中新增單詞會使新短語與訓練資料的匹配度越來越低,從而導致預測的準確率下降,生成的單詞序列看起來越來越隨機。
這就是為什麼 AI 生成的內容隨著時間推移會變得越來越不連貫的原因。例如,可以參考優秀的科幻短片《Sunspring》。這部短片完全由一個基於 LSTM 的網路生成,類似於你正在構建的這個模型,它的訓練資料是科幻電影劇本。模型透過種子內容生成新的劇本。結果令人捧腹:儘管開頭的內容尚且可以理解,但隨著情節推進,生成的內容變得越來越不可理喻。
總結:本篇中,我們成功構建了一個語言模型,雖然未採用當前流行的 Transformer 架構,但這一實踐讓我們深刻理解了人工智慧模型的多樣性。即使是當下耗資巨大的 Transformer 模型,也可能很快被更新的架構取代。因此,我們的目標是緊跟技術前沿,掌握模型設計的核心理念。下一篇我們將詩歌如何透過擴充套件資料集來提高模型的準確度。