百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

用自己的风格教AI说话,语言生成模型可以这样学

toyiye 2024-06-21 12:15 10 浏览 0 评论

选自 towardsdatascience

作者:Ma?l Fabie

机器之心编译

参与:Panda

很多研究者和开发者都认为,初学神经网络时,最好的方法莫过于先自己动手训练一个模型。机器之心也曾推荐过很多不同开发者写的上手教程。本文同样是其中之一,数据科学家 Ma?l Fabien 介绍了如何使用自己的博客文章训练一个和自己风格一样的简单语言生成模型。

在过去几个月的课程中,我在我的个人博客上写了 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 = 200
input_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 model
model = 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/10
164496/164496 [==============================] - 471s 3ms/step - loss: 7.0687
Epoch 2/10
73216/164496 [============>.................] - ETA: 5:12 - loss: 7.0513

在一个 CPU 上,单个 epoch 耗时大约 8 分钟。在 GPU 上(比如 Colab),你应该修改所使用的 Keras LSTM 网络,因为它不能被用在 GPU 上。你需要的是这个:

# Modify Import
from 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

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码