我們之前在知乎上曾有篇回答,講了美國芝加哥大學的研究人員可以用 AI 為 Yelp 上的餐館和酒店寫虛假評論,讓 AI 客串了一回水軍,而且效果足以以假亂真。
今天我們就分享一下如何用 Keras 打造 AI 水軍,寫出逼真的餐廳評論,速度升職為水師提督。
在閱讀本篇教程之後,你就能學會如何生成一條 5 星的 Yelp 餐廳評論。
下面是 AI 生成的一些評論示例(未編輯狀態):
我吃了牛排、貽貝,還有意式帕瑪森烤雞,全都很好吃,我們還會再來的。
飯菜、服務還有氛圍都很棒,我會給所有的朋友推薦這裡。
很好的氛圍,很棒的飯菜,服務也很好。值得一試!
I had the steak, mussels with a side of chicken parmesan. All were very good. We will be back.
The food, service, atmosphere, and service are excellent. I would recommend it to all my friends
Good atmosphere, amazing food and great service.Service is also pretty good. Give them a try!
下面會教你:
-
獲取和準備訓練資料。
-
搭建字元級語言模型。
-
訓練模型時的 Tips。
-
生成隨機評論。
在 GPU 上只花幾天就能很容易的訓練出一個模型。幸好,現在有不少預訓練模型權重,所以你也可以直接跳到最後的生成評論部分。
準備資料
我們在 Yelp 官網上,可以免費獲得 json 格式的 Yelp 資料集 資料集。
下載資料集並提取資料後,在資料集資料夾中,你會發現兩個需要的檔案:
Review.json
Business.json
這裡提醒一下,這兩個檔案都很大,特別是 review.json,(3.7 G)。
Review.json 檔案的每一行都是一條 json 字串格式的評論,這兩個資料夾中並沒有開始和結束括號 [],因此 json 檔案的內容整體來看並不是一個有效的 json 字串。此外,將整個 review.json 檔案放到記憶體中可能會很困難。因此我們首先用指令碼將它們逐行轉為 CSV 格式的檔案。
python json_converter.py ./dataset/review.json
python json_converter.py ./dataset/business.json
複製程式碼
在這之後,你會發現資料集資料夾中有兩個檔案,它們都是有效的 CSV 檔案,可以用 Pandas 程式庫開啟。
我們接下來會這麼做:只從類別中有“restaurant”標籤的業務中提取 5 星評論文字。
# Read thow two CSV files to pandas dataframes
df_business=pd.read_csv(`../dataset/business.csv`)
df_review=pd.read_csv(`../dataset/review.csv`)
# Filter `Restaurants` businesses
restaurants = df_business[df_business[`categories`].str.contains(`Restaurants`)]
# Filter 5-stars reviews
five_star=df_review[df_review[`stars`]==5]
# merge the reviews with restaurants by key `business_id`
# This keep only 5-star restaurants reviews
combo=pd.merge(restaurants_clean, five_star, on=`business_id`)
# review texts column
rnn_fivestar_reviews_only=combo[[`text`]]
複製程式碼
接下來,我們移除評論中的新行字元和重複的評論。
# remove new line characters
rnn_fivestar_reviews_only=rnn_fivestar_reviews_only.replace({r`
+`: ``}, regex=True)
# remove dupliated reviews
final=rnn_fivestar_reviews_only.drop_duplicates()
複製程式碼
為了給模型展示評論的開始和結尾在哪裡,我們需要為評論文字新增特殊的標記。
那麼最終準備好的評論中會有一行達到預期,如下所示:
鷹嘴豆沙很好吃,也很新鮮!沙拉三明治也超棒。絕對值得再去!店老闆人很好,員工都很和藹。
“Hummus is amazing and fresh! Loved the falafels. I will definitely be back. Great owner, friendly staff”
搭建模型
我們所搭建的模型是一個字元級語言模型,意味著最小的可區分符號為字元。你也可以試試詞彙級模型,也就是輸入為單詞標記。
關於字元級語言模型,有優點也有缺點。
優點:
這樣就不必擔心未知詞彙的問題。
能夠學習較大的詞彙。
缺點:
這樣會造成一個很長的序列,在獲取遠端依賴性方面(語句較早部分對後期部分的影響)不如詞彙級模型。
而且字元級模型在訓練時需要消耗的計算資源也更多。
模型和 lstm_text_generation.py demo code 很像,例外之處是我們會再堆疊幾個迴圈神經網路單元,從而讓隱藏層在輸入層和輸出層之間能儲存更多的資訊。這樣能生成更加真實的 Yelp 評論。
在展示模型的程式碼之前,我們先深究一下堆疊的 RNN 是如何工作的。
你可能在標準的神經網路中見過它(也就是 Keras 中的緻密層,Dense layer)。
第一個層會用輸入 X 計算啟用值 a[1],它會堆疊第二個層來計算出下一個啟用值 a[2]。
堆疊的 RNN 有點像標準的神經網路,而且能“及時展開”。
記號 a[I] 表示層 I 的啟用配置,其中 表示時步 t。
我們瞅一眼怎麼計算出一個啟用值。
要想計算 a[2]<3>,需要兩個輸入,a[2]<2> 和 a[1]<3>。
g 是啟用函式,wa[2] 和 ba[2] 是層 2 的引數。
我們可以看到,要想堆疊 RNN,之前的 RNN 需要將所有的時步 ato 返回到下面的 RNN 中。
Keras 中預設一個 RNN 層,比如 LSTM,只返回最後的時步啟用值 a。為了返回所有時步的啟用值,我們要將 return_sequences 引數設為 true。
這裡說說如何在 Keras 上搭建模型。每個輸入樣本都是一個 60 個字元的獨熱表示(one-hot representation),總共有 95 個可能的字元。
每個輸出就是每個字元的一列 95 個預測概率。
import keras
from keras import layers
model = 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 corpus
chars = 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]]] = 1
return [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 decay
filepath="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 characters
for 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 上獲取獲取。
emmmmmm…來都來了,你還可以看看我站其它關於文字處理的教程與文章: