自然语言处理(Natural Language Processing, NLP)是人工智能领域中最重要的分支之一。本案例使用深度学习的方法,利用常见的机器翻译模型,构建了一个智能对联生成的微信小程序。通过对该案例的学习,读者能够了解NLP领域的基础理论,包括词嵌入(Word Embedding)、编码解码模型(Encoder-Decoder)、注意力机制(Attention)等重要知识点,并具备应用、实践能力。通过阅读详实的步骤介绍和参考代码,读者也能了解RNN、LSTM、GRU、Transformer等流行算法,以及Tensor2Tensor等模型库的使用,从而在应用场景中将NLP的理论知识转变成相应的实战技能。
该案例面向对象广泛,扩展性强,不但覆盖了NLP的几大主要知识点,还进一步结合了计算机视觉(Computer Vision)中目标检测、图像分割、图像标注等知识点。初学者可通过复现此案例来学习NLP相关的理论知识,在实践中加深对理论的理解、提高动手能力;对于进阶者,可以通过研究优化对联生成模型等方面,对该案例应用做进一步的扩展,从而提高分析、研究能力。
机器翻译的发展经历了以下几个阶段:
由最初的基于规则的方法,发展到统计机器翻译,再到现在的神经网络机器翻译。
基于规则的方法是由人来提供翻译规则,从词语到词语的对应,都由人来提供。由于该方法需要人类语言专家来设计规则,且规则复杂,因此开发周期长,成本高。由于基于规则的方法,通常是字字对应的转化,没有考虑上下文,因此翻译质量通常会由于使用场景的不同而产生较大的差异。
在基于统计的机器翻译中,规则是由机器自动从大规模的语料中学习得到的,而非由人主动提供完整的规则。这种方法的成本较低,因为机器可以利用大量数据自动学习对应的规则,而无需人的参与。由于统计机器翻译基于大量的语料库,因此翻译质量易受语料库的多寡影响。
微软亚洲研究院周明老师团队早在十几年前就已经使用基于短语的统计机器学习的方法,实现了电脑自动对联系统,效果非常好,很好的展现了中国经典文化的魅力,收获了非常多的赞誉。在线体验地址是?这里。
近年来,深度神经网络学习的发展为机器翻译提供了新的思路。通常情况下,神经机器翻译使用编码器-解码器框架。编码阶段将整个源序列编码成一个(或一组)向量,解码阶段通过最大化预测序列概率,从中解码出整个目标序列,完成翻译过程。
编码器、解码器通常使用 RNN、LSTM 来实现,也有的用 CNN 来实现,达到了比较好的性能和结果。
在本次案例中,我们将使用深度学习的方法,实现一个对联自动生成的应用。
当前,常见的深度学习算法有?RNN、?LSTM、GRU、?Transformer。
由于前馈神经网络的输入都是一批静态数据,无法处理对于随着时间变化的连续数据,或者说无法捕捉时间序列的关系,因此科学家提出了循环神经网络(RNN,Recurrent Neural Network),通过连接多个前馈神经网络的隐藏层,从而获取每个相邻时间步之间的联系。在RNN的基础上,科学家又引入了大量优化理论并从此衍生出许多改进算法,如长短期记忆网络(Long Short-Term Memory networks, LSTM)、门控循环单元网络(Gated Recurrent Unit networks, GRU)等。
LSTM主要解决了RNN中容易出现的梯度爆炸和梯度消失的问题,而GRU在LSTM的基础上,做了进一步的简化,但它们始终是基于RNN的算法,十分地消耗计算资源。Transformer算法则基于全新的Attention机制,放弃了循环和卷积,采用了编码器和解码器的结构,在翻译任务上的表现也更优。因此,在这里我们选择使用transformer模型来实现我们的任务。
此案例特色显明,生动有趣,可以激发学生们对深度学习的兴趣。在技术层面,此案例使学生对深度学习的时序模型有直观的了解。该案例面向对象广泛,扩展性强。对初学者,可重复案例的过程;对于进阶者,不论在模型选择上,还是在模型推理上,都可以有更多的扩展,可提高学生们的探索研究能力。
了解RNN的基本概念和原理
参考链接:?循环神经网络
Seq2Seq模型的基本概念
参考链接:序列到序列
了解主流深度学习框架
参考链接:tensorflow,?Pytorch
熟悉github的使用
参考链接:GitHub入门
本案例运行在Azure虚拟机上,虚拟机的系统为Ubuntu 16.04
需要的软件环境如下:
在开始之前,请确保安装好以下依赖:
安装示例:
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:#1f2328"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>pip3 install -r train_requirements.txt
</code></span></span></span></span>
序号 | 内容 | 关键知识点 | 收获实战技能 |
---|---|---|---|
1 | 图片信息提取 | 认知服务 | 使用Cognitive Service提取图像内容 |
2 | 文本表征 | 词嵌入与词向量 | 掌握常用词嵌入的方法; 用向量表征文本; 词向量库的使用 |
3 | 语言模型简介 | 传统语言模型; 基于神经网络的语言模型 | 了解语言模型发展历程及基本原理 |
4 | Seq2Seq序列模型 | Seq2Seq, 编码器-解码器, 注意, 变压器 | 掌握Seq2Seq模型原理 |
5 | 模型库的使用 | Tensor2Tensor、Fairseq | 使用模型库构建并训练语言模型;使用训练好的模型进行推理 |
6 | 应用开发 | 后端开发 | HTTP服务搭建 |
本案例的基本程序结构如下图所示:
由于在该结构中,NLP的核心内容在于上联生成下联,因此我们将会在案例中关注此部分的实现,并搭建一个简单的web应用将模型封装成api。
有了模型,还需要数据。巧妇难为无米之炊,没有数据,什么都是浮云。数据从哪里来呢?GitHub 上有很多开源贡献者收集和整理了对联数据,可以进行下载使用。
本案例从下面几个渠道获取对联数据:
想要完成一个自动生成对联的小程序,想法十分美好,但想要达到这个目标,光拍拍脑袋想想是不够的,需要训练出一个能完成对联生成的自然语言理解模型。于是乎,就有两个选择:
Tensor2Tensor(以下简称T2T)是由 Google Brain 团队使用和维护的开源深度学习模型库,支持多种数据集和模型。T2T 在 github 上有完整的介绍和用法,可以访问这里了解详细信息。
在本案例中,我们将演示如何使用T2T工具包进行模型训练。
Fairseq?是 Facebook 推出的一个序列建模工具包,这个工具包允许研究和开发人员自定义训练翻译、摘要、语言模型等文本生成任务。这里是它的 PyTorch 实现。
除了下面的使用T2T训练的版本外,我们也提供了使用fairseq训练模型的教程。
网上提供的对联数据形式各异,需要整理成我们需要的格式。我们创建两个文本文件,命名为 train.txt.up 和 train.txt.down,存放上联和下联数据。每个上联/下联为一行,用换行符 '\n' 分隔。
接下来我们要统计上下联中出现多少不同的字,用于后续的模型推理。
将上下联数据每个字以"空格"分隔,合并成一个文件。
a. 分隔数据的python代码 (split_data.py):
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>import sys
filename = sys.argv[1]
with open(filename, 'r', encoding='utf-8') as infile:
with open(filename + '.clean', 'w', encoding='utf-8') as outfile:
lines = infile.readlines()
for line in lines:
out = ""
for i in line.strip():
out += i + (' ')
out = out[:-1]
out += '\n'
outfile.write(out)
</code></span></span></span>
b. 执行如下命令完成文件分隔
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>python3 split_data.py train.txt.up
python3 split_data.py train.txt.down
</code></span></span></span>
分隔后生成两个文件:train.txt.up.clean 和 train.txt.down.clean
c. 合并文件为 merge.txt
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>cat train.txt.up.clean train.txt.down.clean > merge.txt
</code></span></span></span>
统计文件中出现的不同字和每个字的出现次数。
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>subword-nmt get-vocab --input merge.txt --output merge.txt.vocab
</code></span></span></span>
去掉出现次数,只保留字,并统计字数
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>cat merge.txt.vocab | awk '{print $1}' > merge.txt.vocab.clean
wc -l merge.txt.vocab.clean
</code></span></span></span>
生成测试集。
取训练集中前 100 个数据作为测试集。(在实际训练过程中,没有用到测试集)
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>head -n 100 train.txt.up.clean > dev.txt.up.clean
head -n 100 train.txt.down.clean > dev.txt.down.clean
</code></span></span></span>
完成以上预处理以后,我们最终得到以下文件:
将上述文件放到目录(新建目录)。usr_dir
新建目录。usr_dir
在merge_vocab.py?文件中编写下联生成模型的问题定义。
修改如下参数:
SRC_TRAIN_DATA
为训练集上联数据文件TGT_TRAIN_DATA
为训练集下联数据文件SRC_DEV_DATA
为测试集上联数据文件TGT_DEV_DATA
为测试集下联数据文件MERGE_VOCAB
为最终字表文件VOCAB_SIZE
为字表文件中字的个数该文件注册了问题类?,用于指出如何进行上下联翻译。其中?函数用于处理词表、编码、创建完成时序任务的生成器的工作。TranslateUp2down
generate_encoded_samples
添加一个?文件,导入。__init__.py
merge_vocab.py
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>from . import merge_vocab
</code></span></span></span>
完成上述步骤后,请确保你的文件按如下的目录结构放置。
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:#1f2328"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>usr_dir \
__init__.py
merge_vocab.py
train.txt.up.clean
train.txt.down.clean
dev.txt.up.clean
dev.txt.down.clean
merge.txt.vocab.clean
</code></span></span></span></span>
在本案例中,若要使用 T2T 工具包进行训练,需要把数据转换成T2T认可的二进制文件形式。
使用如下命令生成训练数据:
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:#1f2328"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>USR_DIR=./usr_dir
DATA_DIR=./data_dir
PROBLEM=translate_up2down
t2t-datagen \
--t2t_usr_dir=${USR_DIR} \
--data_dir=${DATA_DIR} \
--problem=${PROBLEM}
</code></span></span></span></span>
其中,
t2t_usr_dir
:指定了一个目录,该目录中包含 __init__.py 文件,并可以导入处理对联问题的 python 模块。在该目录中,编写 merge_vocab.py 文件,注册对联问题。
data_dir
:数据目录。存放生成的训练数据文件。
problem
:定义问题名称,本案例中问题名称为 translate_up2down。
当命令执行完毕,将会在 data 目录下生成两个文件:
<span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><span style="color:#1f2328"><span style="color:var(--fgColor-default, var(--color-fg-default))"><span style="background-color:var(--bgColor-muted, var(--color-canvas-subtle))"><code>translate_up2down-train-00000-of-00001
translate_up2down-dev-00000-of-00001
</code></span></span></span></span>
这便是我们需要的训练数据文件。