Press "Enter" to skip to content

水师提督速成指南:用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.jsonpython json_converter.py ./dataset/business.json

在这之后,你会发现数据集文件夹中有两个文件,它们都是有效的 CSV 文件,可以用 Pandas 程序库打开。

我们接下来会这么做:只从类别中有“restaurant”标签的业务中提取 5 星评论文本。

# 将两个CSV 文件读取为pandas dataframesdf_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 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 个特别字符)之间的一个索引上。

# 语料库中的特殊字符列chars = sorted(list(set(text)))print('Unique characters:', len(chars))# 字典将特殊字符映射为它们在 `chars`中的索引char_indices = dict((char, chars.index(char)) for char in chars)

文本库一共有 72662807 个字符,很难将其作为一个整体处理。因此我们将它拆分为几个文本块,每块有 90k 个字符。< /p>

对于拆分后的每个文本块,我们会生成这部分的每对输入和输出。从头到尾转变文本块的指针,如果时步设为 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 回调函数会自动减少学习率。它的主要益处是我们不需要再手动调整学习率,主要缺点是它的学习率会一直下降和衰退。

# 这会保存模型每次优化的权重,所以让模型一直训练就行。同时学习率会降低。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 上获取。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注