作者:Maël Fabie

机器之心编译

参与:Panda

很多研究者和开拓者都认为,初学神经网络时,最好的方法莫过于先自己动手演习一个模型。
机器之心也曾推举过很多不同开拓者写的上手教程。
本文同样是个中之一,数据科学家 Maël Fabien 先容了如何利用自己的博客文章演习一个和自己风格一样的大略措辞天生模型。

用自己的风格教AI措辞措辞生成模型可以这样学

在过去几个月的课程中,我在我的个人博客上写了 100 多篇文章:https://maelfabien.github.io/。
数量还是很可不雅观的。
然后我有了一个想法:

演习一个说话办法与我类似的措辞天生模型。

更详细而言,是书写风格像我。
这种办法能完美地阐释措辞天生的紧张观点、利用 Keras 的实现以及我的模型的局限性。

本文的完全代码见这个代码库:

https://github.com/maelfabien/Machine_Learning_Tutorials

我们开始之前,我推举一个我创造的很有用的 Kaggle Kernel 资源,可以帮助理解措辞天生算法的构造:https://www.kaggle.com/shivamb/beginners-guide-to-text-generation-using-lstms

措辞天生

自然措辞天生(NLG)是一个以天生故意义的自然措辞为目标的领域。

大多数情形下,内容因此单个词的序列的形式天生的。
这是一个很宽泛的思想,大致事情办法如下:

演习一个模型来预测一个序列的下一个词为演习好的模型供应一个输入迭代 N 次,使其天生后面的 N 个词

序列预测过程

1.创建数据集

第一步是构建一个数据集,让我们之后可以基于其构建网络,因此这个数据集须要构建成可被该网络理解的形式。
首先导入以下软件包:

a.载入数据

我写的每篇文章的文件头都利用了以下模板:

这是我们常日不肯望涌如今我们的终极数据集中的内容。
我们想要关注的是文本本身。

每一篇文章都是一个单独的 Markdown 文件。
文件头基本上包含的是标题、标题图片等信息。

首先,我们须要定位到包含文章的文件夹。
在我的目录中,这个文件夹名为「maelfabien.github.io」。

b. 句子 token 化

然后,打开每篇文章,将每篇文章的内容都附加到一个列表中。
但是,由于我们的目标是天生句子,而非整篇文章,以是我们须要将每篇文章都分割成句子列表,并将每个句子附加到列表「all_sentences」。

all_sentences= []for file in glob.glob(\"大众.md\"大众): f = open(file,'r') txt = f.read().replace(\"大众\n\"大众, \"大众 \公众) try: sent_text = nltk.sent_tokenize(''.join(txt.split(\"大众---\"大众)[2]).strip()) for k in sent_text : all_sentences.append(k) except : pass

总体而言,我们得到了略多于 6800 个演习句子。
到目前为止的过程如下:

句子分割

c. 创建 n-gram

然后,创建一起涌现的词的 n-gram。
为了实现这一目标,我们须要:

在语料库上利用一个 token 化程序,为每个 token 都关联一个索引将语料库中的每个句子都分解为一个 token 序列将一起涌现的 token 序列保存起来

下图展示了这个过程:

创建 N-gram

我们来实现它吧。
我们首先须要利用 token 化程序:

tokenizer = Tokenizer()tokenizer.fit_on_texts(all_sentences)total_words = len(tokenizer.word_index) + 1

变量 total_words 包含利用过的不同词的总数。
这里是 8976。
然后,对付每个句子,获取对应的 token 并天生 n-gram:

token_list 变量包含以 token 序列形式存在的句子:

[656, 6, 3, 2284, 6, 3, 86, 1283, 640, 1193, 319][33, 6, 3345, 1007, 7, 388, 5, 2128, 1194, 62, 2731][7, 17, 152, 97, 1165, 1, 762, 1095, 1343, 4, 656]

然后,n_gram_sequences 创建 n-gram。
它从前两个词开始,然后逐渐添加词:

[656, 6][656, 6, 3][656, 6, 3, 2284][656, 6, 3, 2284, 6][656, 6, 3, 2284, 6, 3]...

d. 添补

现在我们面临着这样一个问题:并非所有序列都一样长!
我们如何办理这个问题呢?

我们将利用添补(padding)。
添补是在变量 input_sequences 的每一行之前添加 0 构成的序列,这样每一行的长度便与最长行一样了。

添补的图示

为了将所有句子都添补到句子的最大长度,我们必须先找到最长的句子:

max_sequence_len = max([len(x) for x in input_sequences])

我的情形是最大序列长度为 792。
好吧,对付单句话来说,这一句确实相称长!
由于我的博客包含一些代码和教程,以是我估计这一句实际上是 Python 代码。
我们绘制一个序列长度的直方图来看看:

序列长度

确实仅有非常少的样本的单个序列超过 200 个词。
那么将最大序列长度设置为 200 如何?

max_sequence_len = 200input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))

这会返回类似这样的结果:

array([[ 0, 0, 0, ..., 0, 656, 6], [ 0, 0, 0, ..., 656, 6, 3], [ 0, 0, 0, ..., 6, 3, 2284], ...,

e. 分割 X 和 y

现在我们有固定长度的数组了,个中大多数在实际的序列之前都添补了 0。
那么,我们如何将其转换成一个演习集?我们须要分割 X 和 y!
要记住,我们的目标是预测序列的下一个词。
因此,我们必须将最新的 token 之外的所有 token 都视为 X,而将那个最新的 token 视为 y。

分割 X 和 y

用 Python 实行这个操作非常大略:

X, y = input_sequences[:,:-1],input_sequences[:,-1]

现在我们可以把这个问题视为一个多类分类任务。
首先,我们必须对 y 进行 one-hot 编码,得到一个稀疏矩阵,该矩阵在对应于该 token 的一列包含一个 1,其它地方则都是 0。

在 Python 中,利用 Keras Utils 的 to_categorial:

y = ku.to_categorical(y, num_classes=total_words)

现在,X 的形状为 (164496, 199),y 的形状为 (164496, 8976)。

现在我们有大约 165000 个演习样本。
X 的列宽为 199,由于其对应于我们许可的最长序列长度(200-1,减去的 1 是要预测的标签)。
y 有 8976 列,对应于词汇表所有词的一个稀疏矩阵。
现在,数据集就准备好了!

2. 构建模型

我们将利用是非期影象网络(LSTM)。
LSTM 有一个主要上风,即能够理解在全体序列上的依赖情形,因此,句子的起始部分可能会影响到所要预测的第 15 个词。
另一方面,循环神经网络(RNN)仅涉及对网络之前状态的依赖,且仅有前一个词有助于预测下一个词。
如果选用 RNN,我们很快就会失落去高下文语境,因此选择 LSTM 彷佛是精确的。

a. 模型架构

由于演习须要非常非常非常非常非常的韶光(不是开玩笑),以是我们就创建一个大略的「1 嵌入层+1 LSTM 层+1 密集层」的网络:

def create_model(max_sequence_len, total_words): input_len = max_sequence_len - 1 model = Sequential() # Add Input Embedding Layer model.add(Embedding(total_words, 10, input_length=input_len)) # Add Hidden Layer 1 - LSTM Layer model.add(LSTM(100)) model.add(Dropout(0.1)) # Add Output Layer model.add(Dense(total_words, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam') return modelmodel = create_model(max_sequence_len, total_words)model.summary()

首先,我们添加一个嵌入层。
我们将其通报给一个有 100 个神经元的 LSTM,添加一个 dropout 来掌握神经元共适应(neuron co-adaptation),末了添加一个密集层(dense layer)扫尾。
把稳,我们仅在末了一层上运用一个 softmax 激活函数,以得到输出属于每个类别的概率。
这里利用的丢失是种别交叉熵,由于这是一个多类分类问题。

下面汇总了该模型的情形:

模型情形总览

b. 演习模型

现在我们终于准备好演习模型了!

model.fit(X, y, batch_size=256, epochs=100, verbose=True)

然后模型的演习就开始了:

Epoch 1/10164496/164496 [==============================] - 471s 3ms/step - loss: 7.0687Epoch 2/1073216/164496 [============>.................] - ETA: 5:12 - loss: 7.0513

在一个 CPU 上,单个 epoch 耗时大约 8 分钟。
在 GPU 上(比如 Colab),你该当修正所利用的 Keras LSTM 网络,由于它不能被用在 GPU 上。
你须要的是这个:

# Modify Importfrom keras.layers import Embedding, LSTM, Dense, Dropout, CuDNNLSTM# In the Moddel... model.add(CuDNNLSTM(100))...

我在演习几步之后就会停一下,以便采样预测结果,以及根据交叉熵的不同值来掌握模型的质量。

下面是我不雅观察到的结果:

3. 天生句子

读到这里,下一步就可以预见了:天生新句子!
要天生新句子,我们须要将同样的变换运用到输入文本上。
我们构建一个循环来迭代天生下一个词一定次数:

input_txt = \公众Machine\公众for _ in range(10): # Get tokens token_list = tokenizer.texts_to_sequences([input_txt])[0] # Pad the sequence token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre') # Predict the class predicted = model.predict_classes(token_list, verbose=0) output_word = \"大众\"大众 # Get the corresponding work for word,index in tokenizer.word_index.items(): if index == predicted: output_word = word break input_txt += \"大众 \公众+output_word

当丢失大约为 3.1 时,下面是利用「Google」作为输入而天生的句子:

Google is a large amount of data produced worldwide

这没什么真正的含义,但它成功地将 Google 与大量数据的观点关联到了一起。
这是非常了不起的,由于这只依赖词的共现,并没有整合任何语法观点。

如果模型的演习韶光更长一些,将丢失降到了 2.5,那么给定输入「Random Forest」,会得到:

Random Forest is a fully managed service distributed designed to support a large amount of startups vision infrastructure

同样,天生的东西没什么意义,但其语法构造是相称精确的。

丢失在大约 50 epoch 后就收敛了,且从未低于 2.5。

我认为这是由于这里开拓的方法的局限性:

模型仍旧非常大略演习数据没有理应的那样整洁数据量非常有限

话虽如此,我认为结果还是挺故意思的,比如演习好的模型可以轻松地支配到 Flask WebApp 上。

总结

我希望这篇文章是有用的。
我考试测验阐释了措辞天生的紧张观点、难题和局限。
比较于本文中谈论的方法,更大型的网络和更好的数据肯定有助于改进结果。

原文链接:https://towardsdatascience.com/i-trained-a-network-to-speak-like-me-9552c16e2396