集智专栏
资源加载中,请稍后...
集智专栏

水师提督速成指南:用Keras打造你的AI水军

集智小编

我们之前在知乎上曾有篇回答,讲了美国芝加哥大学的研究人员可以用 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 星评论文本。

# 将两个CSV 文件读取为pandas dataframes
df_business=pd.read_csv('../dataset/business.csv')
df_review=pd.read_csv('../dataset/review.csv')
# 过滤'Restaurants' 业务
restaurants = df_business[df_business['categories'].str.contains('Restaurants')]
# 过滤5星评论
five_star=df_review[df_review['stars']==5]
# 用关键词'business_id'将评论和餐厅融合
# 这只会保存餐厅的5星评论
combo=pd.merge(restaurants_clean, five_star, on='business_id')
# 评论文本列
rnn_fivestar_reviews_only=combo[['text']]

接下来,我们移除评论中的新行字符和重复的评论。

# 移除新行字符
rnn_fivestar_reviews_only=rnn_fivestar_reviews_only.replace({r'\n+': ''}, regex=True)
# 移除重复评论
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

堆叠的 RNN 有点像标准的神经网络,而且能“及时展开”。

记号 a[I] 表示层 I 的激活配置,其中 表示时步 t。

层I

我们瞅一眼怎么计算出一个激活值。

要想计算 a[2]<3>,需要两个输入,a[2]<2> 和 a[1]<3>。

g 是激活函数,wa[2] 和 ba[2] 是层 2 的参数。

a[2]<3>

我们可以看到,要想堆叠 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 个特别字符)之间的一个索引上。

# 语料库中的特殊字符列
chars = sorted(list(set(text)))
print('Unique characters:', len(chars))
# 字典将特殊字符映射为它们在 `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 回调函数会自动减少学习率。它的主要益处是我们不需要再手动调整学习率,主要缺点是它的学习率会一直下降和衰退。

# 这会保存模型每次优化的权重,所以让模型一直训练就行。同时学习率会降低。
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 个字符,代码如下:

# 生成300个字符
for i in range(300):
   sampled = np.zeros((1, maxlen, len(chars)))
   # 将每个字符变为字符索引.
   for t, char in enumerate(generated_text):
       sampled[0, t, char_indices[char]] = 1.
   # 预测下一个字符概率
   preds = model.predict(sampled, verbose=0)[0]
   # 通过对给定概率采样,增加随机数
   next_index = sample(preds, temperature)
   # 将字符索引变为字符.
   next_char = chars[next_index]
   # 添加字符,生成文本字符串
   generated_text += next_char
   # 在生成的文本字符串中打出第一个字符
   generated_text = generated_text[1:]
   # 打印新生成的字符
   sys.stdout.write(next_char)
   sys.stdout.flush()
print(generated_text)

结语

在本文,我们学习了如何用 Keras 搭建和训练一个字符级文本生成模型。本项目的源代码以及所用的预训练模型,均可在 GitHub 上获取

参考资料:How to generate realistic yelp restaurant reviews with Keras

您也许喜欢这些文章

集智专栏

教你用CPT算法解决序列预测问题

发表至数据科学
某宝怎么知道我要买这个?后天天气怎么预测?
集智专栏

机器翻译中的一些问题很棘手?Google表示山人自有妙计

发表至业界新闻
翻译一直是人类所面临的一大难题,人类使用的语言既多又繁,不少语句还存在很麻烦的歧义,这些问题对于机器翻译系统而言同样非常棘手。那么,机器翻译在面对有歧义的句子的时候会出现哪些问题,而业界大牛Google又是如何给出解决办法的呢?这篇文章将为你解答这一切。
集智专栏

如何用Keras打造出“风格迁移”的AI艺术作品

发表至趣味项目
教你用Python库Keras和卷积神经网络,将经典画作的艺术风格应用到自己的照片上,使用两张基本的图像素材,就能创造出惊艳的AI艺术作品。

文章评论(0)