今天我们来解析下Tensorflow的Seq2Seq的demo。继上篇博客的PTM模型之后,Tensorflow官方也开放了名为translate的demo,这个demo对比之前的PTM要大了很多(首先,空间上就会需要大约20个G,另外差点把我的硬盘给运行死),但是也实用了很多。模型采用了encoder-decoder的框架结果,佐以attention机制来实现论文中的英语法语翻译功能。同时,模型的基础却来自之前的PTM模型。下面,让我们来一起来了解一下这个神奇的系统吧!

论文介绍及基础描写:

这个英语法语翻译器融合了多篇论文的核心内容,所以在学习的过程中其实我们可以变相的了解这些技巧。首先,Cho在论文Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation一文中指出可以通过encoder-decoder这种类似于编码-解码的框架来做机器翻译。这个框架在后续论文中掀起了不小的波澜,很多作品延续了这种sequence-to-sequence的框架在其他的一些领域,比如image caption。该框架说白了就是两个RNN,一个作为编码器,一个作为解码器。至于RNN cell的结构,虽然本项目支持使用LSTM, default的模型为GRU(Gated Recurrent Uint),一个LSTM的简化版模型。另外,为了使翻译的效果达到最好,2014年Bahdanau在论文Neural Machine Translation By Jointy Learning to Align and Translate中提出的attention机制也被运用了起来。背景介绍到此,下面我们赶快来看看代码吧!

代码:

较之PTM模型,机器翻译的demo代码有足足个file之多,可见其难度。(其实也没那么难,都是些犹如纸老虎般的存在,充其量也就在数量上吓唬吓唬人罢了。)项目源代码可以在Tensorflow官方github中找到,地址点击这里。打开项目后我们发现了3个文件,分别是data_utils.py, seq2seq_model.py以及translate.py。这三个文件里,data_utils.py是类似于helper function般的存在,如其名,是一个集合了对输入raw的数据做处理的一个file。file本身不是用来运行的,只是被translate.py所使用而已。这个文件本身也可以在你自己的Tensorflow框架的本地库内找到,所需要做的只是输入from tensorflow.models.rnn.translate import data_utils,就可以取得库里的函数了。为方便用户,这个data_utils文件里的函数还自带检测数据库是否存在于本地路径的功能,如果你没来得及或不知道如何下载数据库,只需要指定路径后运行库里的函数即可,是不是很方便?

Encoder-decoder模型存在于第二个文件中,即seq2seq_model.py文件。文件作为一个类包含了组建机器翻译所需要的神经网络图框架。与data_utils文件一样,他们都是被我们的“main”文件,translate.py使用的。

了解了三个文件的关系后,我们也了解了系统运行的过程以及解读顺序,那么,我们先来了解一下程序运行的大体顺序。运行该程序的方式为cd到文件所在的目录下后在terminal里面输入“python translate.py --data_dir /tmp/ --train_dir /tmp/”就可以了。 其中tmp为默认的tmp文件夹,如果你希望长期保留运行结果,建议在别的文件夹里进行实验。另外,你可以改变一些模型的参数,如把模型改为两层,每层改为256个神经元等。方法为在刚才的comment后面加上两横杠,更改参数名,等于号及更改后参数量。如改变模型为两层的框架的方法是“--num_layers=2”。那么在输入参数后系统是怎么运行的呢?我们在translate.py里找到了main函数的代码如下:

def main(_):
if FLAGS.self_test:
self_test()
elif FLAGS.decode:
decode()
else:
train()

该代码显示,一般情况下如果你没表明要运行decode模式或者测试模式,训练模式将自动开始。那么很明显,我们的主程序在训练模式,也就train函数里。那么我们就顺藤摸瓜来看看train函数吧。

首先,train函数的开始为运行data_utils库的prepare_wmt_data函数,这里的输入为我们在之前手动输入的data_dir外还有英语和法语的单词数。这里除了data_dir是我们之前手动输入的外,英法语的单词数默认都是4万。通过这个神奇的prepare_wmt_data函数,我们可以得到英语,法语两种语言的训练以及测试资料外,还可以获得英语及法语单词的单词表存放路径。那么这个函数是如何工作的呢?我们这就来一探究竟。

def prepare_wmt_data(data_dir, en_vocabulary_size, fr_vocabulary_size, tokenizer=None):
"""Get WMT data into data_dir, create vocabularies and tokenize data.
Args:
data_dir: directory in which the data sets will be stored.
en_vocabulary_size: size of the English vocabulary to create and use.
fr_vocabulary_size: size of the French vocabulary to create and use.
tokenizer: a function to use to tokenize each data sentence;
if None, basic_tokenizer will be used.
Returns:
A tuple of 6 elements:
(1) path to the token-ids for English training data-set,
(2) path to the token-ids for French training data-set,
(3) path to the token-ids for English development data-set,
(4) path to the token-ids for French development data-set,
(5) path to the English vocabulary file,
(6) path to the French vocabulary file.
"""
# Get wmt data to the specified directory.
# 建立训练集并取得他们的路径
train_path = get_wmt_enfr_train_set(data_dir)
dev_path = get_wmt_enfr_dev_set(data_dir) # Create vocabularies of the appropriate sizes.
# 建立英语及法语的单词表
fr_vocab_path = os.path.join(data_dir, "vocab%d.fr" % fr_vocabulary_size)
en_vocab_path = os.path.join(data_dir, "vocab%d.en" % en_vocabulary_size)
create_vocabulary(fr_vocab_path, train_path + ".fr", fr_vocabulary_size, tokenizer)
create_vocabulary(en_vocab_path, train_path + ".en", en_vocabulary_size, tokenizer) # Create token ids for the training data.
# 将输入数据里的单词数字化以方便运算
fr_train_ids_path = train_path + (".ids%d.fr" % fr_vocabulary_size)
en_train_ids_path = train_path + (".ids%d.en" % en_vocabulary_size)
data_to_token_ids(train_path + ".fr", fr_train_ids_path, fr_vocab_path, tokenizer)
data_to_token_ids(train_path + ".en", en_train_ids_path, en_vocab_path, tokenizer) # Create token ids for the development data.
# 将测试数据里的单词数字化以方便运算
fr_dev_ids_path = dev_path + (".ids%d.fr" % fr_vocabulary_size)
en_dev_ids_path = dev_path + (".ids%d.en" % en_vocabulary_size)
data_to_token_ids(dev_path + ".fr", fr_dev_ids_path, fr_vocab_path, tokenizer)
data_to_token_ids(dev_path + ".en", en_dev_ids_path, en_vocab_path, tokenizer) return (en_train_ids_path, fr_train_ids_path,
en_dev_ids_path, fr_dev_ids_path,
en_vocab_path, fr_vocab_path)

在data_utils库里,prepare_wmt_data整合了其他的helper函数,其中,get_wmt_enfr_train_set和get_wmt_enfr_dev_set两个函数测试了训练集的路径是否存在后,视情况下载训练集(如果训练集还未下载),之后便是解压了训练包中所用的具体集并提供其路径。虽然对于测试集有不同的应对方法,其逻辑大同小异,具体实现过程大家可以阅读这两个函数去一探究竟,这里将不做细说。

之后函数建立了两个不同的单词表,即英语单词表及法语单词表。在建立表的过程中,逻辑近似于之前讨论过的Word2Vec的create_dataset函数,即从海量数据里建立一个对应的字典来统计输入词。其中,值得注意的是这里有一个tokenizer输入的选项,默认的tokenizer是在符号处截断句子,其代码如下:

_WORD_SPLIT = re.compile(b"([.,!?\"':;)(])")

def basic_tokenizer(sentence):
"""Very basic tokenizer: split the sentence into a list of tokens."""
words = []
for space_separated_fragment in sentence.strip().split():
words.extend(re.split(_WORD_SPLIT, space_separated_fragment))
return [w for w in words if w]

该项目鼓励读者们去采用更好的tokenizer去取得更好的结果。最后,不同于Word2Vec模型的最大地方在于我们将这个单词集保存后备用,也就是说,在第一次运算耗时可能很长后,之后在运行将会比较方便。

在建立完词典后,通过运用data_to_token_ids函数,我们可以将输入转化为数字序列并将其保存,这样可以方便我们系统的运用。其原理也可以在Word2Vec的demo代码里找到逻辑,即运用单词在词典里的位置来代替单词。具体内容请参考博客Python Tensorflow下的Word2Vec代码解释。所得的结果在保存在各自的文件中已备后用后,我们可以直接运用这些资料来训练我们的系统了。接下来,让我们重新回到translation.py文件的train()函数来继续了解它的机制。在得到了输入及训练资料后,我们看到了熟悉的with tf.Session() as sess,这里大家都明白怎么回事了吧,我们进入session了,可以开始建立模型并运行了。那么很明显,我们现在的任务就是建立模型。这里,我们看到了model = create_model(sess, False), 这个函数的具体内容就在train()函数之上,很好找。那么,它是怎么建立模型的呢?走进该函数后我们发现它其实就是个包装盒子,运用了系统的另一个文件,即seq2seq_model.py库里Seq2SeqModel类来得到模型,之后便是取得模型目前的状态。如果模型已经训练并保存,我们即呼唤之前训练的模型并返回。反之则初始化所有参数并返回模型。

现在,让我们来看看这个模型本身。在seq2seq_model.py文件里有三个函数:init函数,step函数及get_batch函数。目前,我们在制造模型阶段,所以先来看看这个init函数。

def __init__(self, source_vocab_size, target_vocab_size, buckets, size,
num_layers, max_gradient_norm, batch_size, learning_rate,
learning_rate_decay_factor, use_lstm=False,
num_samples=512, forward_only=False):

这个函数的parameter列很长,有源语言和目标语言各自的单词数量,框架的层数,每层的神经元数,用于clipped的梯度的最大数值,训练batch的大小,learning rate,learning rate减少的比例及sampled softmax所接收的sample数量。这些都是常见的训练神经网络的参数。参数use_lstm也比较好理解,默认的false表明我们将会运用到GRU,即简易版本的lstm。设置为True时便是使用传统的LSTM cell了。现在,有两个参数我没讲解到,buckets参数和forward_only参数。这两个参数挺有意思。bucket参数的存在是针对机器翻译的,他的格式为一个充满(I,O)的list,I代表这最大输入长度,O代表着输出的最大长度。当输入或输出超出这个距离后,我们将超出的部分放入下一个batch。至于forward_only参数,其存在是因为两种不同的训练方式。一种方式为在训练中根据两种语言各自训练input及output,这是默认方式,即该参数设为Flase。如果我们设为True后,将会在训练output语言时运用output目标的开头后由输入语言取得剩下的数据。两种方法在官方的document里有详细讲解,这里附上链接供有兴趣的读者加深了解。

之后,在assign了变量后,我们又见到了熟悉的RNN模型框架,即

    # If we use sampled softmax, we need an output projection.
output_projection = None
softmax_loss_function = None
# Sampled softmax only makes sense if we sample less than vocabulary size.
if num_samples > 0 and num_samples < self.target_vocab_size:
w = tf.get_variable("proj_w", [size, self.target_vocab_size])
w_t = tf.transpose(w)
b = tf.get_variable("proj_b", [self.target_vocab_size])
output_projection = (w, b) def sampled_loss(inputs, labels):
labels = tf.reshape(labels, [-1, 1])
return tf.nn.sampled_softmax_loss(w_t, b, inputs, labels, num_samples,
self.target_vocab_size)
softmax_loss_function = sampled_loss # Create the internal multi-layer cell for our RNN.
single_cell = tf.nn.rnn_cell.GRUCell(size)
if use_lstm:
single_cell = tf.nn.rnn_cell.BasicLSTMCell(size)
cell = single_cell
if num_layers > 1:
cell = tf.nn.rnn_cell.MultiRNNCell([single_cell] * num_layers) # The seq2seq function: we use embedding for the input and attention.
def seq2seq_f(encoder_inputs, decoder_inputs, do_decode):
return tf.nn.seq2seq.embedding_attention_seq2seq(
encoder_inputs, decoder_inputs, cell,
num_encoder_symbols=source_vocab_size,
num_decoder_symbols=target_vocab_size,
embedding_size=size,
output_projection=output_projection,
feed_previous=do_decode)

这里,我们先了解到如果我们的参数num_samples大于0但小于目标单词量时,我们再次如同PTB模型那样运用一个projection layer来减少空间的占据。之后,我们默认celll类型是GRU,但是如果用户设定了用lstm,我们即更新single_cell变量到lstm cell框架。当网络层大于1时(默认为3层),我们的cell变成了一个多层RNN框架,由单层cell累积组成。这个逻辑已经在之前关于PTM模型的博客里有过介绍,如果你不熟悉这个设定,请看之前PTB模型的博客。在之后,我们发现设计了一个seq2seq_f的内部函数,这个函数为模型加入attention机制,之后我们会用到。

如同往常,我们为源语言,目标语言的训练输入以及目标权重建立placeholder,因为训练目标是输入目标的下一句,我们设定目标为目标语言输入的现在位置+1。之后便是建立训练输出及计算loss的时刻了。基本方法如下:

self.outputs, self.losses = tf.nn.seq2seq.model_with_buckets(
self.encoder_inputs, self.decoder_inputs, targets,
self.target_weights, buckets,
lambda x, y: seq2seq_f(x, y, False),
softmax_loss_function=softmax_loss_function)

我们将会运用Tensorflow库seq2seq里的model_with_buckets函数来完成。这个函数是什么呢?让我们来一探究竟。该函数的目标是建立一个bucket版本的seq2seq模型。模型取得encoder, decoder的输入,目标和权重的Tensor,输入和输出大小配对列表叫做buckets的参数后,要求一个sequence to sequence模型,softmax_loss_function函数(default是None),每例子的loss(default None)及名字(default None)。输出是(output, losses)tuple,output指的是每一个bucket的输出,losses这该bucket的loss数值。了解了这些后,我们发掘这里的sequence to sequence函数输入我们运用了lambda匿名函数,核心是我们的attention模型。这是我们训练的基础,但是当forward_only被设为True时,训练后,我们又多了一步,及重新编写buckets的output为揉合输出及之前projection的output。代码如下:

      if output_projection is not None:
for b in xrange(len(buckets)):
self.outputs[b] = [
tf.matmul(output, output_projection[0]) + output_projection[1]
for output in self.outputs[b]
]

之后便是传统的RNN训练方法,即运用GradientDecentOptimizer,并运用clip_by_global_norm及appy_gradient函数。这些知识点在之前的PTB模型里以详细介绍,这里将不再重复。

建立模型之后,在train()函数里,我们读入测试和训练的数据,计算训练的bucket并选择输入的句子属于哪个bucket (= [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in xrange(len(train_bucket_sizes))]),之后便是具体训练的循环步骤。

步骤里,系统在运用seq2seq_model类里的get_batch和step两个函数,在取得了一个batch的数据后训练一个步骤的数据,并在一定的步骤后展出结果。代码的逻辑还是很清晰的,只是值得注意的是第205行的sys.stdout.flush()代码,这代码的存在是为了实时把运算结果展示在terminal的。除此之外,大家可以仔细阅读代码来加深了解,这里将不再细说。

就此,系统训练的步骤讲完了。但当我们准备测试我们的训练结果时,我们该怎么办呢?这里,我们就要讲讲这个decode()函数了。根据官方的说明,我们在训练模型后模型参数等全部资料全部都是有好好的保存的,所以我们不需要再次训练了,我们只需要运行“python translate.py --decode --data_dir /tmp/ --train_dir /tmp/"即可,一个interactive session将会打开供我们运作。

这个decode函数本身是用来测试系统的,所以在初始化英语法语的单词表后,系统读取我们输入的一行句子后,运行一下逻辑:

    # 读取一行话
sentence = sys.stdin.readline()
while sentence:
# Get token-ids for the input sentence.
# 把输入转换成token_ids
token_ids = data_utils.sentence_to_token_ids(tf.compat.as_bytes(sentence), en_vocab)
# Which bucket does it belong to?
# 选择输入所对应的bucket大小
bucket_id = min([b for b in xrange(len(_buckets))
if _buckets[b][0] > len(token_ids)])
# Get a 1-element batch to feed the sentence to the model.
# 取得一个一个element的batch并通过step函数来取得运行结果
encoder_inputs, decoder_inputs, target_weights = model.get_batch(
{bucket_id: [(token_ids, [])]}, bucket_id)
# Get output logits for the sentence.
_, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs,
target_weights, bucket_id, True)
# This is a greedy decoder - outputs are just argmaxes of output_logits.
outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]
# If there is an EOS symbol in outputs, cut them at that point.
if data_utils.EOS_ID in outputs:
outputs = outputs[:outputs.index(data_utils.EOS_ID)]
# Print out French sentence corresponding to outputs.
print(" ".join([tf.compat.as_str(rev_fr_vocab[output]) for output in outputs]))
print("> ", end="")
sys.stdout.flush()
sentence = sys.stdin.readline()

通过这种方式,我们可以验证我们系统的好坏。可惜的是第一,我不懂法语。第二,系统在读取第1770000行句子时系统卡死了,差点废了我的硬盘,我没有得到测试结果,可能是模型过大我的电脑承受不起的缘故吧。对于这个错误我会研究一下,不过如果读者们有类似的情况并知道为何如此,请务必让我知道!谢谢大家!

解析Tensorflow官方English-Franch翻译器demo的更多相关文章

  1. 解析Tensorflow官方PTB模型的demo

    RNN 模型作为一个可以学习时间序列的模型被认为是深度学习中比较重要的一类模型.在Tensorflow的官方教程中,有两个与之相关的模型被实现出来.第一个模型是围绕着Zaremba的论文Recurre ...

  2. 微信小程序导航:官方工具+精品教程+DEMO集合(1月7更新)

    1:官方工具:https://mp.weixin.qq.com/debug/w ... tml?t=14764346784612:简易教程:https://mp.weixin.qq.com/debug ...

  3. [源码解析] TensorFlow 分布式环境(1) --- 总体架构

    [源码解析] TensorFlow 分布式环境(1) --- 总体架构 目录 [源码解析] TensorFlow 分布式环境(1) --- 总体架构 1. 总体架构 1.1 集群角度 1.1.1 概念 ...

  4. [源码解析] TensorFlow 分布式之 ParameterServerStrategy V2

    [源码解析] TensorFlow 分布式之 ParameterServerStrategy V2 目录 [源码解析] TensorFlow 分布式之 ParameterServerStrategy ...

  5. Tensorflow 官方版教程中文版

    2015年11月9日,Google发布人工智能系统TensorFlow并宣布开源,同日,极客学院组织在线TensorFlow中文文档翻译.一个月后,30章文档全部翻译校对完成,上线并提供电子书下载,该 ...

  6. TensorFlow 在android上的Demo(1)

    转载时请注明出处: 修雨轩陈 系统环境说明: ------------------------------------ 操作系统 : ubunt 14.03 _ x86_64 操作系统 内存: 8GB ...

  7. TensorFlow官方发布剪枝优化工具:参数减少80%,精度几乎不变

    去年TensorFlow官方推出了模型优化工具,最多能将模型尺寸减小4倍,运行速度提高3倍. 最近现又有一款新工具加入模型优化"豪华套餐",这就是基于Keras的剪枝优化工具. 训 ...

  8. [源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑

    [源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑 目录 [源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑 1. 总述 2. 接口 2.1 ...

  9. [源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑

    [源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑 目录 [源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑 1. 继承关系 1.1 角 ...

随机推荐

  1. Java三大特征之多态(三)

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...

  2. IE9以下通过css让html页面背景图片铺满整个屏幕

    第一种方法不设为背景图片,通过css来控制样式,可兼容到IE6,代码如下: <!DOCTYPE html> <html lang="en"> <hea ...

  3. windows中path环境变量即时生效

    修改PATH后,打来CMD命令行,输入 “set PATH=C” (不会真的改变PATH变量值,但会重新读取一次PATH值),关掉CMD窗口再打开.OK 不放心可以 echo %PATH% 检视一下.

  4. yii2不用composer使用redis

    1.下载redis https://github.com/yiisoft/yii2-redis 2.下载解压放到 basic\vendor\yiisoft\yii2-redis 3.编辑文件: bas ...

  5. android-support关联源码

    http://blog.csdn.net/xiaanming/article/details/9031141 http://www.cnblogs.com/androidez/archive/2013 ...

  6. zookeeper数据弱一致性

    zookeeper本身支持单机部署和集群部署,生产环境建议使用集群部署,因为集群部署不存在单点故障问题,并且zookeeper建议部署的节点个数为奇数个,只有超过一半的机器不可用整个zk集群才不可用. ...

  7. 体验下Xcode5与ios7

    IOS7的最新更新. 1.自主选择默认地图应用苹果在iOS 6中移除了谷歌地图,使用了自家研发的苹果地图.不得不说,这款自带的地图应用可以说烂到了一定的程度,自发布之后便问题不断.正因如此,苹果应该允 ...

  8. relay 2015-02-05 21:00 27人阅读 评论(0) 收藏

    scanf函数是以在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔,则可用空格,TAB或回车作间隔. C编译在碰到空格,TAB,回车或非法数据(如对"%d"输 ...

  9. 网易云课堂_C++开发入门到精通_章节3: 类、对象和封装

    课时12构造函数与析构函数-2 构造函数 构造函数可以有多个 构造函数可以重载 构造函数用于隐式类型转换 class Student { public: explicit Student(int ss ...

  10. docker学习笔记(1)

    (1)Docker介绍 关于Docker的介绍,我就不列举出来了.到百度.谷歌搜索.非常多介绍文章.以下我给出官网的介绍:https://www.docker.com/whatisdocker/ (2 ...