用自己的風格教AI說話,語言生成模型可以這樣學

(給機器學習算法與Python學習加星標,提升AI技能) 

選自 towardsdatascience 

作者:Maël Fabie

本文轉自“機器之心”(almosthuman2014)

本文的完整代碼見這個代碼庫:

https://github.com/maelfabien/Machine_Learning_Tutorials

在我們開始之前,我推薦一個我發現的很有用的 Kaggle Kernel 資源,可以幫助理解語言生成算法的結構:https://www.kaggle.com/shivamb/beginners-guide-to-text-generation-using-lstms

語言生成

自然語言生成(NLG)是一個以生成有意義的自然語言爲目標的領域。

大多數情況下,內容是以單個詞的序列的形式生成的。這是一個很寬泛的思想,大致工作方式如下:

  • 訓練一個模型來預測一個序列的下一個詞

  • 爲訓練好的模型提供一個輸入

  • 迭代 N 次,使其生成後面的 N 個詞

序列預測過程

1.創建數據集

第一步是構建一個數據集,讓我們之後可以基於其構建網絡,因此這個數據集需要構建成可被該網絡理解的形式。首先導入以下軟件包:

a.載入數據

我寫的每篇文章的文件頭都使用了以下模板:

 

這是我們通常不希望出現在我們的最終數據集中的內容。我們想要關注的是文本本身。

每一篇文章都是一個單獨的 Markdown 文件。文件頭基本上包含的是標題、標題圖片等信息。

首先,我們需要定位到包含文章的文件夾。在我的目錄中,這個文件夾名爲「maelfabien.github.io」。

b. 句子 token 化

然後,打開每篇文章,將每篇文章的內容都附加到一個列表中。但是,因爲我們的目標是生成句子,而非整篇文章,所以我們需要將每篇文章都分割成句子列表,並將每個句子附加到列表「all_sentences」。

all_sentences= []

for file in glob.glob("*.md"):
    f = open(file, r )
    txt = f.read().replace("
", " ")
    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

然後,創建一起出現的詞的 n-gram。爲了實現這一目標,我們需要:

  • 在語料庫上使用一個 token 化程序,爲每個 token 都關聯一個索引

  • 將語料庫中的每個句子都分解爲一個 token 序列

  • 將一起出現的 token 序列保存起來

下圖展示了這個過程:

創建 N-gram

我們來實現它吧。我們首先需要使用 token 化程序:

tokenizer = Tokenizer()
tokenizer.fit_on_texts(all_sentences)
total_words = len(tokenizer.word_index) + 1

變量 total_words 包含使用過的不同詞的總數。這裏是 8976。然後,對於每個句子,獲取對應的 token 並生成 n-gram:

token_list 變量包含以 token 序列形式存在的句子:

[656, 6, 3, 2284, 6, 3, 86, 1283, 640, 1193, 319]
[33, 6, 3345, 1007, 7, 388, 5, 2128, 1194, 62, 2731]
[7, 17, 152, 97, 1165, 1, 762, 1095, 1343, 4, 656]

然後,n_gram_sequences 創建 n-gram。它從前兩個詞開始,然後逐漸添加詞:

[656, 6]
[656, 6, 3]
[656, 6, 3, 2284]
[656, 6, 3, 2284, 6]
[656, 6, 3, 2284, 6, 3]
...

d. 填充

現在我們面臨着這樣一個問題:並非所有序列都一樣長!我們如何解決這個問題呢?

我們將使用填充(padding)。填充是在變量 input_sequences 的每一行之前添加 0 構成的序列,這樣每一行的長度便與最長行一樣了。

填充的圖示

爲了將所有句子都填充到句子的最大長度,我們必須先找到最長的句子:

max_sequence_len = max([len(x) for x in input_sequences])

我的情況是最大序列長度爲 792。好吧,對於單句話來說,這一句確實相當長!因爲我的博客包含一些代碼和教程,所以我估計這一句實際上是 Python 代碼。我們繪製一個序列長度的直方圖來看看:

序列長度

確實僅有非常少的樣本的單個序列超過 200 個詞。那麼將最大序列長度設置爲 200 如何?

max_sequence_len = 200
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding= pre ))

這會返回類似這樣的結果:

array([[   0,    0,    0, ...,    0,  656,    6],
       [   0,    0,    0, ...,  656,    6,    3],
       [   0,    0,    0, ...,    6,    3, 2284],
       ...,

e. 分割 X 和 y

現在我們有固定長度的數組了,其中大多數在實際的序列之前都填充了 0。那麼,我們如何將其轉換成一個訓練集?我們需要分割 X 和 y!要記住,我們的目標是預測序列的下一個詞。因此,我們必須將最新的 token 之外的所有 token 都視爲 X,而將那個最新的 token 視爲 y。

分割 X 和 y

用 Python 執行這個操作非常簡單:

X, y = input_sequences[:,:-1],input_sequences[:,-1]

現在我們可以把這個問題視爲一個多類分類任務。首先,我們必須對 y 進行 one-hot 編碼,得到一個稀疏矩陣,該矩陣在對應於該 token 的一列包含一個 1,其它地方則都是 0。

在 Python 中,使用 Keras Utils 的 to_categorial:

y = ku.to_categorical(y, num_classes=total_words)

現在,X 的形狀爲 (164496, 199),y 的形狀爲 (164496, 8976)。

現在我們有大約 165000 個訓練樣本。X 的列寬爲 199,因爲其對應於我們允許的最長序列長度(200-1,減去的 1 是要預測的標籤)。y 有 8976 列,對應於詞彙表所有詞的一個稀疏矩陣。現在,數據集就準備好了!

2. 構建模型

我們將使用長短期記憶網絡(LSTM)。LSTM 有一個重要優勢,即能夠理解在整個序列上的依賴情況,因此,句子的起始部分可能會影響到所要預測的第 15 個詞。另一方面,循環神經網絡(RNN)僅涉及對網絡之前狀態的依賴,且僅有前一個詞有助於預測下一個詞。如果選用 RNN,我們很快就會失去上下文語境,因此選擇 LSTM 似乎是正確的。

a. 模型架構

因爲訓練需要非常非常非常非常非常的時間(不是開玩笑),所以我們就創建一個簡單的「1 嵌入層+1 LSTM 層+1 密集層」的網絡:

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()

首先,我們添加一個嵌入層。我們將其傳遞給一個有 100 個神經元的 LSTM,添加一個 dropout 來控制神經元共適應(neuron co-adaptation),最後添加一個密集層(dense layer)收尾。注意,我們僅在最後一層上應用一個 softmax 激活函數,以獲得輸出屬於每個類別的概率。這裏使用的損失是類別交叉熵,因爲這是一個多類分類問題。

下面彙總了該模型的情況:

模型情況總覽

b. 訓練模型

現在我們終於準備好訓練模型了!

model.fit(X, y, batch_size=256, epochs=100, verbose=True)

然後模型的訓練就開始了:

Epoch 1/10
164496/164496 [==============================] - 471s 3ms/step - loss: 7.0687
Epoch 2/10
73216/164496 [============>.................] - ETA: 5:12 - loss: 7.0513

在一個 CPU 上,單個 epoch 耗時大約 8 分鐘。在 GPU 上(比如 Colab),你應該修改所使用的 Keras LSTM 網絡,因爲它不能被用在 GPU 上。你需要的是這個:

# Modify Import
from keras.layers import Embedding, LSTM, Dense, Dropout, CuDNNLSTM

# In the Moddel
...
    model.add(CuDNNLSTM(100))
...

我在訓練幾步之後就會停一下,以便採樣預測結果,以及根據交叉熵的不同值來控制模型的質量。

下面是我觀察到的結果:

3. 生成句子

讀到這裏,下一步就可以預料了:生成新句子!要生成新句子,我們需要將同樣的變換應用到輸入文本上。我們構建一個循環來迭代生成下一個詞一定次數:

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

這沒什麼真正的含義,但它成功地將 Google 與大量數據的概念關聯到了一起。這是非常了不起的,因爲這隻依靠詞的共現,並沒有整合任何語法概念。

如果模型的訓練時間更長一些,將損失降到了 2.5,那麼給定輸入「Random Forest」,會得到:

Random Forest is a fully managed service distributed designed to support a large amount of startups vision infrastructure

同樣,生成的東西沒什麼意義,但其語法結構是相當正確的。

損失在大約 50 epoch 後就收斂了,且從未低於 2.5。

我認爲這是由於這裏開發的方法的侷限性:

  • 模型仍然非常簡單

  • 訓練數據沒有理應的那樣整潔

  • 數據量非常有限

話雖如此,我認爲結果還是挺有意思的,比如訓練好的模型可以輕鬆地部署到 Flask WebApp 上。

總結

我希望這篇文章是有用的。我嘗試闡釋了語言生成的主要概念、難題和侷限。相比於本文中討論的方法,更大型的網絡和更好的數據肯定有助於改善結果。

原文鏈接:https://towardsdatascience.com/i-trained-a-network-to-speak-like-me-9552c16e2396

推薦閱讀
程序員必備的 Github 高級搜索技巧

圖解人工智能,這羣大學生做了個有趣的交互項目(中文版)

Linux拜拜!微軟給WSL加入GPU支持,Windows終於迎來命令行包管理工具

【讀懂系列】一文讀懂深度學習中的各種卷積
下載 | 5 本程序員成長必讀書籍


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章