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

咱們以前在知乎上曾有篇回答,講了美國芝加哥大學的研究人員能夠用 AI 爲 Yelp 上的餐館和酒店寫虛假評論,讓 AI 客串了一回水軍,並且效果足以以假亂真。python

今天咱們就分享一下如何用 Keras 打造 AI 水軍,寫出逼真的餐廳評論,速度升職爲水師提督。 在閱讀本篇教程以後,你就能學會如何生成一條 5 星的 Yelp 餐廳評論。git

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

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

飯菜、服務還有氛圍都很棒,我會給全部的朋友推薦這裏。bash

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

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

The food, service, atmosphere, and service are excellent. I would recommend it to all my friendsdom

Good atmosphere, amazing food and great service.Service is also pretty good. Give them a try!ide

下面會教你:函數

  • 獲取和準備訓練數據。

  • 搭建字符級語言模型。

  • 訓練模型時的 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'\n+': ''}, 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...來都來了,你還能夠看看我站其它關於文本處理的教程與文章:

這評論有毒!——文本分類的通常套路

基於TensorFlow用卷積神經網絡作文本分類

相關文章
相關標籤/搜索