水師提督速成指南:用Keras打造你的AI水軍。

我們之前曾有篇回答,講了美國芝加哥大學的研究人員可以用 AI 為 Yelp 上的餐館和酒店寫虛假評論,讓 AI 客串了一回水軍,而且效果足以以假亂真:

如何判斷淘寶上的商品評價是不是水軍的作品??

www.zhihu.com圖標

今天我們就分享一下如何用 Keras 打造 AI 水軍,寫出逼真的餐廳評論,速度升職為水師提督。

在閱讀本篇教程之後,你就能學會如何生成一條 5 星的 Yelp 餐廳評論。

下面是 AI 生成的一些評論示例(未編輯狀態):

我吃了牛排、貽貝,還有意式帕瑪森烤雞,全都很好吃,我們還會再來的。

飯菜、服務還有氛圍都很棒,我會給所有的朋友推薦這裡。

很好的氛圍,很棒的飯菜,服務也很好。值得一試!

<SOR>I had the steak, mussels with a side of chicken parmesan. All were very good. We will be back.<EOR>

<SOR>The food, service, atmosphere, and service are excellent. I would recommend it to all my friends<EOR>

<SOR>Good atmosphere, amazing food and great service.Service is also pretty good. Give them a try!<EOR>

下面會教你:

  • 獲取和準備訓練數據。
  • 搭建字元級語言模型。
  • 訓練模型時的 Tips。
  • 生成隨機評論。

在 GPU 上只花幾天就能很容易的訓練出一個模型。幸好,現在有不少預訓練模型權重,所以你也可以直接跳到最後的生成評論部分。

準備數據

我們在 Yelp 官網上,可以免費獲得 json 格式的 Yelp 數據集

Yelp Dataset?

www.yelp.com圖標

下載數據集並提取數據後,在數據集文件夾中,你會發現兩個需要的文件:

Review.json

Business.json

這裡提醒一下,這兩個文件都很大,特別是 review.json,(3.7 G)。

Review.json 文件的每一行都是一條 json 字元串格式的評論,這兩個文件夾中並沒有開始和結束括弧 [],因此 json 文件的內容整體來看並不是一個有效的 json 字元串。此外,將整個 review.json 文件放到內存中可能會很困難。因此我們首先用腳本將它們逐行轉為 CSV 格式的文件。

python json_converter.py ./dataset/review.jsonpython json_converter.py ./dataset/business.json

在這之後,你會發現數據集文件夾中有兩個文件,它們都是有效的 CSV 文件,可以用 Pandas 程序庫打開。

我們接下來會這麼做:只從類別中有「restaurant」標籤的業務中提取 5 星評論文本。

# Read thow two CSV files to pandas dataframesdf_business=pd.read_csv(../dataset/business.csv)df_review=pd.read_csv(../dataset/review.csv)# Filter Restaurants businessesrestaurants = df_business[df_business[categories].str.contains(Restaurants)]# Filter 5-stars reviewsfive_star=df_review[df_review[stars]==5]# merge the reviews with restaurants by key business_id# This keep only 5-star restaurants reviewscombo=pd.merge(restaurants_clean, five_star, on=business_id)# review texts columnrnn_fivestar_reviews_only=combo[[text]]

接下來,我們移除評論中的新行字元和重複的評論。

# remove new line charactersrnn_fivestar_reviews_only=rnn_fivestar_reviews_only.replace({r
+
: }, regex=True)# remove dupliated reviewsfinal=rnn_fivestar_reviews_only.drop_duplicates()

為了給模型展示評論的開始和結尾在哪裡,我們需要為評論文本添加特殊的標記。

那麼最終準備好的評論中會有一行達到預期,如下所示:

鷹嘴豆沙很好吃,也很新鮮!沙拉三明治也超棒。絕對值得再去!店老闆人很好,員工都很和藹。

"<SOR>Hummus is amazing and fresh! Loved the falafels. I will definitely be back. Great owner, friendly staff<EOR>"

搭建模型

我們所搭建的模型是一個字元級語言模型,意味著最小的可區分符號為字元。你也可以試試辭彙級模型,也就是輸入為單詞標記。

關於字元級語言模型,有表示贊同的,也有表示反對的。

贊同意見:

這樣就不必擔心未知辭彙的問題。

能夠學習較大的辭彙。

反對意見:

這樣會造成一個很長的序列,在獲取遠程依賴性方面(語句較早部分對後期部分的影響)不如辭彙級模型。

而且字元級模型在訓練時需要消耗的計算資源也更多。

模型和 lstm_text_generation.py demo code 很像

https://github.com/keras-team/keras/blob/master/examples/lstm_text_generation.py?

github.com

例外之處是我們會再堆疊幾個循環神經網路單元,從而讓隱藏層在輸入層和輸出層之間能存儲更多的信息。這樣能生成更加真實的 Yelp 評論。

在展示模型的代碼之前,我們先深究一下堆疊的 RNN 是如何工作的。

你可能在標準的神經網路中見過它(也就是 Keras 中的緻密層,Dense layer)。

第一個層會用輸入 X 計算激活值 a[1],它會堆疊第二個層來計算出下一個激活值 a[2]。

堆疊的 RNN 有點像標準的神經網路,而且能「及時展開」。

記號 a[I]<t> 表示層 I 的激活配置,其中 <t> 表示時步 t。

我們瞅一眼怎麼計算出一個激活值。

要想計算 a[2]<3>,需要兩個輸入,a[2]<2> 和 a[1]<3>。

g 是激活函數,wa[2] 和 ba[2] 是層 2 的參數。

我們可以看到,要想堆疊 RNN,之前的 RNN 需要將所有的時步 a<t>to 返回到下面的 RNN 中。

Keras 中默認一個 RNN 層,比如 LSTM,只返回最後的時步激活值 a<T>。為了返回所有時步的激活值,我們要將 return_sequences 參數設為 true。

這裡說說如何在 Keras 上搭建模型。每個輸入樣本都是一個 60 個字元的獨熱表示(one-hot representation),總共有 95 個可能的字元。

每個輸出就是每個字元的一列 95 個預測概率。

import kerasfrom keras import layersmodel = keras.models.Sequential()model.add(layers.LSTM(1024, input_shape=(60, 95),return_sequences=True))model.add(layers.LSTM(1024, input_shape=(60, 95)))model.add(layers.Dense(95, activation=softmax))

訓練模型

訓練模型的思路很簡單,我們會以輸入/輸出成對地訓練模型。每個輸入是 60 個字元,相應的輸出為緊跟在後面的字元。

在數據準備這一步,我們創建了一列乾淨的 5 星評論文本。總共有 1214016 行評論。為了讓訓練簡單一點,我們只訓練字元長度小於或等於 250 的評論,最後會得到 418955 行評論。

然後我們打亂評論的順序,這樣我們就不會用一行中同一家餐廳的 100 條評論來訓練模型。

我們會將所有的評論讀取為一個長文本字元串,然後創建一個 Python 目錄(比如一個散列表)將每個字元映射到 0-94(總共 95 個特別字元)之間的一個索引上。

# List of unique characters in the corpuschars = sorted(list(set(text)))print(Unique characters:, len(chars))# Dictionary mapping unique characters to their index in `chars`char_indices = dict((char, chars.index(char)) for char in chars)

文本庫一共有 72662807 個字元,很難將其作為一個整體處理。因此我們將它拆分為幾個文本塊,每塊有 90k 個字元。

對於拆分後的每個文本塊,我們會生成這部分的每對輸入和輸出。從頭到尾轉變文本塊的指針,如果時步設為 1 的話,每次轉變 1 個字元。

def getDataFromChunk(txtChunk, maxlen=60, step=1): sentences = [] next_chars = [] for i in range(0, len(txtChunk) - maxlen, step): sentences.append(txtChunk[i : i + maxlen]) next_chars.append(txtChunk[i + maxlen]) print(nb sequences:, len(sentences)) print(Vectorization...) X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool) y = np.zeros((len(sentences), len(chars)), dtype=np.bool) for i, sentence in enumerate(sentences): for t, char in enumerate(sentence): X[i, t, char_indices[char]] = 1 y[i, char_indices[next_chars[i]]] = 1return [X, y]

在 GPU(GTX1070)上訓練一個文本塊的話,每個 epoch 耗時 219 秒,那麼訓練完整個文本庫需要花費大約 2 天時間。

72662807 / 90000 * 219 /60 / 60/ 24 = 2.0 days

Keras 的兩個回調函數 ModelCheckpoint 和 ReduceLROnPlateau 用起來很方便。

ModelCheckpoint 能幫我們保存每一次優化時的權重。

當損失度量停止下降時,ReduceLROnPlateau 回調函數會自動減少學習率。它的主要益處是我們不需要再手動調整學習率,主要缺點是它的學習率會一直下降和衰退。

# this saves the weights everytime they improve so you can let it train. Also learning rate decayfilepath="Feb-22-all-{epoch:02d}-{loss:.4f}.hdf5"checkpoint = ModelCheckpoint(filepath, monitor=loss, verbose=1, save_best_only=True, mode=min)reduce_lr = ReduceLROnPlateau(monitor=loss, factor=0.5, patience=1, min_lr=0.00001)callbacks_list = [checkpoint, reduce_lr]

將模型訓練 20 個 epoch 的代碼如下:

for iteration in range(1, 20): print(Iteration, iteration) with open("../dataset/short_reviews_shuffle.txt") as f: for chunk in iter(lambda: f.read(90000), ""): X, y = getDataFromChunk(chunk) model.fit(X, y, batch_size=128, epochs=1, callbacks=callbacks_list)

如果全部訓練完,大概需要 1 個月的時間。但是對我們來說,訓練上 2 個小時就能生成很不錯的結果。

生成5星評論

有了預訓練模型的權重或者你自己訓練的模型,我們就可以生成有意思的 Yelp 評論了。

思路是這樣:我們用初始 60 個字元作為模型的「種子」,然後讓模型預測緊接下來的字元。

「索引抽樣」過程會根據給定預測生成一些隨機性,為最終結果添加一些變體。

如果 temperature 的值很小,它會一直選取有最高預測概率的索引。

def sample(preds, temperature=1.0): Generate some randomness with the given preds which is a list of numbers, if the temperature is very small, it will always pick the index with highest pred value preds = np.asarray(preds).astype(float64) preds = np.log(preds) / temperature exp_preds = np.exp(preds) preds = exp_preds / np.sum(exp_preds) probas = np.random.multinomial(1, preds, 1)return np.argmax(probas)

要想生成 300 個字元,代碼如下:

# We generate 300 charactersfor i in range(300): sampled = np.zeros((1, maxlen, len(chars))) # Turn each char to char index. for t, char in enumerate(generated_text): sampled[0, t, char_indices[char]] = 1. # Predict next char probabilities preds = model.predict(sampled, verbose=0)[0] # Add some randomness by sampling given probabilities. next_index = sample(preds, temperature) # Turn char index to char. next_char = chars[next_index] # Append char to generated text string generated_text += next_char # Pop the first char in generated text string. generated_text = generated_text[1:] # Print the new generated char. sys.stdout.write(next_char) sys.stdout.flush()print(generated_text)

結語

在本文,我們學習了如何用 Keras 搭建和訓練一個字元級文本生成模型。本項目的源代碼以及所用的預訓練模型,均可在 GitHub 上獲取:

Tony607/Yelp_review_generation?

github.com圖標

emmmmmm...來都來了,你還可以看看我站其它關於文本處理的教程與文章:

這評論有毒!——文本分類的一般套路 - 集智專欄?

jizhi.im圖標基於TensorFlow用卷積神經網路做文本分類 - 集智專欄?

jizhi.im圖標


推薦閱讀:

TAG:Keras | 景略集智 | 網路水軍 |