0
AI 開發(fā)者按,本文的作者是數(shù)據(jù)科學家 Ma?l Fabien。在過去的幾個月里,他在個人博客上寫了 100 多篇文章。這個內(nèi)容量相當可觀。他突然想到一個主意:訓練一個能像他一樣說話的語言生成模型。
為此,他寫了一篇文章分享了生成一個像人一樣說話的神經(jīng)網(wǎng)絡(luò)模型的過程和相關(guān)代碼,他的文章內(nèi)容如下:
我想訓練一個能像我一樣說話的語言生成模型,或者更具體地說,一個可以像我一樣寫作的模型。它可以完美的說明語言生成的主要概念、使用 keras 實現(xiàn)語言生成模型,以及我的模型的局限性。
本文的全部代碼都可以在這個 repo 中找到:
在我們開始之前,我想分享這個意外發(fā)現(xiàn)的資源—— Kaggle Kernel ,它對理解語言生成算法結(jié)構(gòu)來說是非常有用資源。
語言生成
自然語言生成的目的是生成有意義的自然語言。
大多數(shù)情況下,內(nèi)容是作為單個單詞的序列生成的。總的來說,它的工作原理如下:
你訓練一個模型來預測序列中的下一個單詞
你給經(jīng)過訓練的模型一個輸入
重復上面的步驟 n 次,生成接下來的 n 個單詞
序列預測的過程
1.創(chuàng)建數(shù)據(jù)集
第一步是構(gòu)建一個數(shù)據(jù)集,以便我們稍后將要構(gòu)建的網(wǎng)絡(luò)可以理解這個數(shù)據(jù)集。首先導入以下包:
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Embedding, LSTM, Dense, Dropout
from keras.preprocessing.text import Tokenizer
from keras.callbacks import EarlyStopping
from keras.models import Sequential
import keras.utils as ku
import pandas as pd
import numpy as np
import string, os
a.加載數(shù)據(jù)
我寫的每一篇文章的標題都遵循這個模板:
這是我們通常不希望在最終數(shù)據(jù)集中包含的內(nèi)容類型。相反,我們將關(guān)注文本本身。
所有文章都寫在一個單獨的 Markdown 文件中。標題基本上包含了標題、圖片標題等信息。
首先,我們需要指向包含文章的文件夾,在我的目錄中,名為「maelfabien.github.io」。
B.句子標記
然后,打開每一篇文章,并將每一篇文章的內(nèi)容添加到列表中。但是,由于我們的目標是生成句子,而不是生成整篇文章,因此我們將把每一篇文章拆分成一個句子列表,并將每個句子附加到「all_sentences」列表中:
all_sentences= []
for file in glob.glob("*.md"):
f = open(file,'r')
txt = f.read().replace("\n", " ")
try:
sent_text = nltk.sent_tokenize(''.join(txt.split("---")[2]).strip())
for k in sent_text :
all_sentences.append(k)
except :
pass
總的來說,我們有超過 6800 個訓練的句子。目前的過程如下:
句子拆分
c. N-gram 創(chuàng)建
然后,我的想法是根據(jù)一起出現(xiàn)的單詞創(chuàng)建 N-grams。為此,我們需要:
在語料庫上安裝一個標記器,將索引與每個標記相關(guān)聯(lián)
把語料庫中的每個句子分解成一系列的標記
存儲一起發(fā)生的標記序列
可通過下圖來理解這個過程:
N-gram 創(chuàng)建
接下來,讓我們來實現(xiàn)它。我們首先需要安裝標記器:
tokenizer = Tokenizer()
tokenizer.fit_on_texts(all_sentences)
total_words = len(tokenizer.word_index) + 1
變量「total_words」包含已使用的不同單詞的總數(shù),這里的數(shù)值是 8976。然后,對于每個句子,獲取相應(yīng)的標記并生成 N-grams:
input_sequences = []
# For each sentence
for sent in all_sentences:
# Get the corresponding token
token_list = tokenizer.texts_to_sequences([sent])[0]
# Create the corresponding n-grams
for i in range(1, len(token_list)):
n_gram_sequence = token_list[:i+1]
input_sequences.append(n_gram_sequence)
「token_list」變量將語句作為標記序列包含:
然后,'n_gram_sequences' 序列創(chuàng)建 n-grams。它從前兩個單詞開始,然后逐漸添加單詞:
d.Padding
我們現(xiàn)在面臨的問題是:不是所有的序列都有相同的長度!那么,如何解決這個問題?
我們將使用 Padding。Padding 在變量“input_sequences”的每一行之前添加 0 序列,這樣每一行的長度就與最長的行的長度相同了。
Padding 的解釋
為了將所有句子填充到句子的最大長度,我們必須首先找到最長的句子:
max_sequence_len = max([len(x) for x in input_sequences])
在我的例子里面它等于 792。好吧,對單個句子來說它已經(jīng)夠大了!由于我的博客包含了一些代碼和教程,我希望這一句話是由 python 代碼編寫的。讓我們繪制序列長度的直方圖:
import matplotlib.pyplot as plt
plt.figure(figsize=(12,8))
plt.hist([len(x) for x in input_sequences], bins=50)
plt.axvline(max_sequence_len, c="r")
plt.title("Sequence Length")
plt.show()
序列長度
在單個句子中,很少有例子會超過 200 個單詞。如果把最大序列長度設(shè)為 200 會如何?
max_sequence_len = 200
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))
其輸出結(jié)果為:
e.拆分 X 和 Y
現(xiàn)在我們有了固定長度的數(shù)組,其中大多數(shù)在實際序列之前填充了 0。好吧,我們怎么把它變成一個訓練集?我們需要拆分 X 和 Y!記住,我們的目標是預測序列中的下一個單詞。因此,我們必須將除最后一個標記外的所有標記作為 X,并將最后一個標記作為 Y。
拆分 X 和 Y
在 python 中,它就和下面的語句一樣簡單:
X, y = input_sequences[:,:-1],input_sequences[:,-1]
我們現(xiàn)在將這個問題看作一個多分類任務(wù)。像往常一樣,我們必須首先對 y 進行 one-hot 編碼,以獲得一個稀疏矩陣,該矩陣在對應(yīng)于該標記的列中包含 1,在其他位置包含 0:
在 python 中,使用「keras utils to_categorical」:
y = ku.to_categorical(y, num_classes=total_words)
X 的 shape 是 (164496, 199),Y 的 shape 是 (164496, 8976)。
我們有大約 165000 個訓練樣本。X 是 199 列寬,因為它對應(yīng)于我們允許的最長序列(200-1,要預測的標簽)。Y 有 8976 列,對應(yīng)于所有詞匯的稀疏矩陣。數(shù)據(jù)集現(xiàn)在準備好了!
2.建立模型
我們將使用 Long Short-Term Memory networks (LSTM)。LSTM 的一個重要優(yōu)點是能夠理解對整個序列的依賴性,因此,句子的開頭可能會對要預測的第 15 個單詞也產(chǎn)生影響。另一方面,遞歸神經(jīng)網(wǎng)絡(luò)(RNNs)只意味著依賴于網(wǎng)絡(luò)的前一個狀態(tài),只有前一個詞才能幫助預測下一個狀態(tài)。如果選擇 RNN,我們很快就會錯過上下文,因此,LSTM 應(yīng)該是目前的最佳選擇。
a.模型架構(gòu)
由于訓練可以非常(非常)(非常)(非常)(非常)(不開玩笑)長,我們將構(gòu)建一個簡單的 1 Embedding + 1 LSTM 層 + 1 密集網(wǎng)絡(luò):
def create_model(max_sequence_len, total_words):
input_len = max_sequence_len - 1
model = Sequential()
# Add Input Embedding Layer
model.add(Embedding(total_words, 10, input_length=input_len))
# Add Hidden Layer 1 - LSTM Layer
model.add(LSTM(100))
model.add(Dropout(0.1))
# Add Output Layer
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
return model
model = create_model(max_sequence_len, total_words)
model.summary()
首先,我們添加一個 embedding 層。我們將其傳遞到一個有 100 個神經(jīng)元的 LSTM 中,添加一個 dropout 來控制神經(jīng)元共適應(yīng),最后是一個稠密層。注意,我們在最后一層應(yīng)用一個 softmax 激活函數(shù)來獲得輸出屬于每個類的概率。由于損失是一個多分類問題,因此使用的損失是分類交叉熵。
模型大體情況如下:
模型概覽
b.訓練模型
我們終于可以開始訓練模型啦!
model.fit(X, y, batch_size=256, epochs=100, verbose=True)
然后模型的訓練就開始啦:
在 CPU上,一個 epoch 大約需要 8 分鐘。在 GPU 上(例如在 Colab 中),你應(yīng)該修改使用的 Keras LSTM 網(wǎng)絡(luò),因為它不能在 GPU 上使用。相反,你需要:
# Modify Import
from keras.layers import Embedding, LSTM, Dense, Dropout, CuDNNLSTM
# In the Moddel
...
model.add(CuDNNLSTM(100))
...
我傾向于在幾個步驟中停止訓練,以便進行樣本預測,并在給定交叉熵的幾個值時控制模型的質(zhì)量。
以下是我的結(jié)果:
3.生成序列
如果你讀到這里,接下來就是你所期望的了:生成新的句子!要生成句子,我們需要對輸入文本應(yīng)用相同的轉(zhuǎn)換。我們將構(gòu)建一個循環(huán),在給定的迭代次數(shù)內(nèi)生成下一個單詞:
input_txt = "Machine"
for _ in range(10):
# Get tokens
token_list = tokenizer.texts_to_sequences([input_txt])[0] # Pad the sequence
token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre') # Predict the class
predicted = model.predict_classes(token_list, verbose=0)
output_word = ""
# Get the corresponding work
for word,index in tokenizer.word_index.items():
if index == predicted:
output_word = word
break
input_txt += " "+output_word
當損失在 3.1 左右時,以「google」作為輸入生成的句子如下:
Google is a large amount of data produced worldwide!
這并沒有什么實際意義,但它成功地將谷歌與大數(shù)據(jù)的概念聯(lián)系起來。這是相當令人印象深刻的,因為它僅僅依賴于單詞的共現(xiàn),而沒有整合任何語法概念。
如果我們在訓練中稍等一段時間,讓損失減少到 2.5,并給它輸入「Random Forest」:
Random Forest is a fully managed service distributed designed to support a large amount of startups vision infrastructure。
同樣,生成的內(nèi)容沒有意義,但語法結(jié)構(gòu)相當正確。
損失在大約 50 個 epoch 后開始分化,并從未低于 2.5。
我想我們已經(jīng)達到了這個方法的極限:
模型仍然很簡單
訓練數(shù)據(jù)不夠清晰
數(shù)據(jù)量非常有限
也就是說,我發(fā)現(xiàn)結(jié)果非常有趣,例如,經(jīng)過訓練的模型可以很容易地部署在 Flask WebApp 上。
結(jié)論
我希望這篇文章對你有用。我試圖說明語言生成的主要概念、挑戰(zhàn)和限制。當然,與本文討論的方法相比,更大的網(wǎng)絡(luò)和更好的數(shù)據(jù)無疑是改進的源泉。
資料來源:
Kaggle Kernel : https://www.kaggle.com/shivamb/beginners-guide-to-text-generation-using-lstms
via:https://towardsdatascience.com/i-trained-a-network-to-speak-like-me-9552c16e2396
雷鋒網(wǎng)雷鋒網(wǎng)雷鋒網(wǎng)
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。