概述
更新进度:■■■■■■■■■■■■■■■■■■■■■■■|100%
理论上一周更一个经典论文
刚刚开始学习,写的不好,有错误麻烦大家留言给我啦
这位博主的笔记短小精炼,爱了爱了:点击跳转
目录
- 准备
- 理论知识
- 代码实现(英文翻中文)
- 1. 数据预处理
- step 1,2: 分词,统计
- step 3: 分配ID
- step 4: 生成词汇表
- step 5: 编号表示
- step 6: 预料填充(分批次处理,每一批次语句长度一样)
- 2. 模型搭建
- 3. 训练
- 4. 测试
- 代码可学习技巧
准备
文章标题:
Neural Machine Translation by Jointly Learning to Align and Translate
基于联合学习对齐和翻译的神经机器翻译
作者:
Dzmitry Bahdanau1, KyungHyun Cho2, Yoshua Bengio*2
单位:
- 不来梅雅克布大学Jacobs University Bremen, Germany
- 蒙特利尔大学Universite de Montr ´ eal
发表会议及时间:
ICLR 2015综合性会议
最早将注意力机制引入机器翻译的论文
理论知识
模型:
代码实现(英文翻中文)
数据集:IWLST TED数据集、PTB数据集
1. 数据预处理
step 1,2: 分词,统计
import codecs
import collections
counter = collections.Counter() # 建立一个空的Counter
with codecs.open(RAW_DATA, "r", "utf-8") as f: # RAW_DATA 是 ptb.train.txt
for line in f:
for word in line.strip().split():
counter[word] += 1
输出:按词频排列的词典
- collections.Counter()是Python的计数器,快速的统计一个字典里面每个元素出现的次数,参数必须是list类型的,不能是二维的,或者其他维度的。
可以创建一个空的Counter,对空的Counter进行操作。
参考文章 - line.strip().split():
strip()表示删除掉数据中的换行符,split()表示数据中遇到空格就隔开。 - 字典(Dictionary).items() 函数以列表(list)形式返回可遍历的(键, 值) 元组数组。
- 列表的排列
例:list1 = [(3, 8, 2), (1, 5, 8), (2, 5, 7)]
方法1:利用list自带sort()方法
list1.sort(key=lambda t: t[0])
print(list1) # [(1, 5, 8), (2, 5, 7), (3, 8, 2)]
方法2:利用集合公共函数sorted()–>排序后的集合
ret = sorted(list1, key=lambda t: t[0])
print(ret) # [(1, 5, 8), (2, 5, 7), (3, 8, 2)]
step 3: 分配ID
# 按词频顺序对单词进行排序。
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True)
sorted_words = [x[0] for x in sorted_word_to_cnt]
# 稍后我们需要在文本换行处加入句子结束符"<eos>",这里预先将其加入词汇表。
sorted_words = ["<eos>"] + sorted_words
输出:词汇vocabe
with codecs.open(VOCAB_OUTPUT, 'w', 'utf-8') as file_output:
for word in sorted_words:
file_output.write(word + "n")
step 4: 生成词汇表
with codecs.open("./ptb.vocab", "r", "utf-8") as f_vocab:
vocab = [w.strip() for w in f_vocab.readlines()]
word_to_id = {k: v for (k, v) in zip(vocab, range(len(vocab)))}
将词汇频率对应vocab
输出:生成词汇表(字典)
如果出现了不在词汇表内的低频词,则替换为"unk"
def get_id(word):
return word_to_id[word] if word in word_to_id else word_to_id["<unk>"]
- zip(x,y)
x = [1, 2, 3]
y = [4, 5, 6]
zipped = zip(x, y)
list(zipped)
[(1, 4), (2, 5), (3, 6)]
step 5: 编号表示
fin = codecs.open(RAW_DATA, "r", "utf-8")
fout = codecs.open(OUTPUT_DATA, 'w', 'utf-8')
for line in fin:
words = line.strip().split() + ["<eos>"] # 读取单词并添加<eos>结束符
# 将每个单词替换为词汇表中的编号
out_line = ' '.join([str(get_id(w)) for w in words]) + 'n'
fout.write(out_line)
step 6: 预料填充(分批次处理,每一批次语句长度一样)
输入:已经用空格隔开并转换为编码的数据集
def MakeDataset(file_path):
dataset = tf.data.TextLineDataset(file_path)
# 根据空格将单词编号切分开并放入一个一维向量。
dataset = dataset.map(lambda string: tf.string_split([string]).values)
# 将字符串形式的单词编号转化为整数。
dataset = dataset.map(lambda string: tf.string_to_number(string, tf.int32))
# 统计每个句子的单词数量,并与句子内容一起放入Dataset中。
dataset = dataset.map(lambda x: (x, tf.size(x)))
return dataset
输出:单词编号转换为整数,补充了句子长度的数据集
# 从源语言文件src_path和目标语言文件trg_path中分别读取数据,并进行填充和
# batching操作。
def MakeSrcTrgDataset(src_path, trg_path, batch_size):
# 首先分别读取源语言数据和目标语言数据。
src_data = MakeDataset(src_path) # SRC_TRAIN_DATA = "./train.en"
trg_data = MakeDataset(trg_path) # TRG_TRAIN_DATA = "./train.zh"
# 通过zip操作将两个Dataset合并为一个Dataset。现在每个Dataset中每一项数据ds
# 由4个张量组成:
# ds[0][0]是源句子
# ds[0][1]是源句子长度
# ds[1][0]是目标句子
# ds[1][1]是目标句子长度
dataset = tf.data.Dataset.zip((src_data, trg_data))
# 删除内容为空(只包含<EOS>,句子结束符)的句子和长度过长的句子。
def FilterLength(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
src_len_ok = tf.logical_and(tf.greater(src_len, 1), tf.less_equal(src_len, MAX_LEN))
trg_len_ok = tf.logical_and(tf.greater(trg_len, 1), tf.less_equal(trg_len, MAX_LEN))
return tf.logical_and(src_len_ok, trg_len_ok)
dataset = dataset.filter(FilterLength)
# 从图9-5可知,解码器需要两种格式的目标句子:
# 1.解码器的输入(trg_input),形式如同"<sos> X Y Z"
# 2.解码器的目标输出(trg_label),形式如同"X Y Z <eos>"
# 上面从文件中读到的目标句子是"X Y Z <eos>"的形式,我们需要从中生成"<sos> X Y Z"
# 形式并加入到Dataset中。
def MakeTrgInput(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
trg_input = tf.concat([[SOS_ID], trg_label[:-1]], axis=0)
return ((src_input, src_len), (trg_input, trg_label, trg_len))
dataset = dataset.map(MakeTrgInput)
# 随机打乱训练数据。
dataset = dataset.shuffle(10000)
# 规定填充后输出的数据维度。
padded_shapes = (
(tf.TensorShape([None]), # 源句子是长度未知的向量
tf.TensorShape([])), # 源句子长度是单个数字
(tf.TensorShape([None]), # 目标句子(解码器输入)是长度未知的向量
tf.TensorShape([None]), # 目标句子(解码器目标输出)是长度未知的向量
tf.TensorShape([]))) # 目标句子长度是单个数字
# 调用padded_batch方法进行batching操作。
batched_dataset = dataset.padded_batch(batch_size, padded_shapes) # 填充句子到这一轮训练句子的最长长度,让每个句子同等长度
return batched_dataset
- 填充调用函数为:
tf. data. Dataset.padded_batch(batch_size, padded_shapes)
- lambda+map函数的使用
第一个问题: lambda如何用?
lambda叫做匿名函数:不需要提前定义函数就可以使用的函数
如果不使用lambda函数:
输入:def func(x):
… return x+1输出:
func(1)
使用lambda函数:
输入:a = lambda x:x+1
输出:
a(1)
第二个问题: map如何用?
例:有一个元组列表[(‘a’,1),(‘b’,2),(‘c’,3),(‘d’,4)],需将字母项提取为新的列表。
vartuple = [(‘a’,1),(‘b’,2),(‘c’,3),(‘d’,4)]
list(map(lambda x:x[0],vartuple))
输出:[‘a’, ‘b’, ‘c’, ‘d’]
2. 模型搭建
熟悉模型流程+输入输出
# 定义NMTModel类来描述模型。
class NMTModel(object):
# 在模型的初始化函数中定义模型要用到的变量。
def __init__(self):
# 定义编码器和解码器所使用的LSTM结构。
self.enc_cell = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
# 为源语言和目标语言分别定义词向量。
self.src_embedding = tf.get_variable(
"src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE]) # 源语言词向量
self.trg_embedding = tf.get_variable(
"trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE]) # 目标语言词向量
# 定义softmax层的变量
if SHARE_EMB_AND_SOFTMAX:
self.softmax_weight = tf.transpose(self.trg_embedding)
else:
self.softmax_weight = tf.get_variable(
"weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
self.softmax_bias = tf.get_variable(
"softmax_bias", [TRG_VOCAB_SIZE])
# 在forward函数中定义模型的前向计算图。
# src_input, src_size, trg_input, trg_label, trg_size分别是上面
# MakeSrcTrgDataset函数产生的五种张量。
def forward(self, src_input, src_size, trg_input, trg_label, trg_size):
batch_size = tf.shape(src_input)[0]
# 将输入和输出单词编号转为词向量。
src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
trg_emb = tf.nn.embedding_lookup(self.trg_embedding, trg_input)
# 在词向量上进行dropout。
src_emb = tf.nn.dropout(src_emb, KEEP_PROB)
trg_emb = tf.nn.dropout(trg_emb, KEEP_PROB)
# 使用dynamic_rnn构造编码器。
# 编码器读取源句子每个位置的词向量,输出最后一步的隐藏状态enc_state。
# 因为编码器是一个双层LSTM,因此enc_state是一个包含两个LSTMStateTuple类
# 张量的tuple,每个LSTMStateTuple对应编码器中的一层。
# enc_outputs是顶层LSTM在每一步的输出,它的维度是[batch_size,
# max_time, HIDDEN_SIZE]。Seq2Seq模型中不需要用到enc_outputs,而
# 后面介绍的attention模型会用到它。
with tf.variable_scope("encoder"):
enc_outputs, enc_state = tf.nn.dynamic_rnn(
self.enc_cell, src_emb, src_size, dtype=tf.float32)
# 使用dyanmic_rnn构造解码器。
# 解码器读取目标句子每个位置的词向量,输出的dec_outputs为每一步
# 顶层LSTM的输出。dec_outputs的维度是 [batch_size, max_time,
# HIDDEN_SIZE]。
# initial_state=enc_state表示用编码器的输出来初始化第一步的隐藏状态。
with tf.variable_scope("decoder"):
dec_outputs, _ = tf.nn.dynamic_rnn(
self.dec_cell, trg_emb, trg_size, initial_state=enc_state)
# 计算解码器每一步的log perplexity。这一步与语言模型代码相同。
output = tf.reshape(dec_outputs, [-1, HIDDEN_SIZE])
logits = tf.matmul(output, self.softmax_weight) + self.softmax_bias
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=tf.reshape(trg_label, [-1]), logits=logits)
# 在计算平均损失时,需要将填充位置的权重设置为0,以避免无效位置的预测干扰
# 模型的训练。
label_weights = tf.sequence_mask(trg_size, maxlen=tf.shape(trg_label)[1], dtype=tf.float32)
label_weights = tf.reshape(label_weights, [-1])
cost = tf.reduce_sum(loss * label_weights)
cost_per_token = cost / tf.reduce_sum(label_weights)
# 定义反向传播操作。反向操作的实现与语言模型代码相同。
trainable_variables = tf.trainable_variables()
# 控制梯度大小,定义优化方法和训练步骤。
grads = tf.gradients(cost / tf.to_float(batch_size),trainable_variables)
grads, _ = tf.clip_by_global_norm(grads, MAX_GRAD_NORM)
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
train_op = optimizer.apply_gradients(zip(grads, trainable_variables))
return cost_per_token, train_op
# 使用给定的模型model上训练一个epoch,并返回全局步数。
# 每训练200步便保存一个checkpoint。
def run_epoch(session, cost_op, train_op, saver, step):
# 训练一个epoch。
# 重复训练步骤直至遍历完Dataset中所有数据。
while True:
try:
# 运行train_op并计算损失值。训练数据在main()函数中以Dataset方式提供。
cost, _ = session.run([cost_op, train_op])
if step % 10 == 0:
print("After %d steps, per token cost is %.3f" % (step, cost))
# 每200步保存一个checkpoint。
if step % 200 == 0:
saver.save(session, CHECKPOINT_PATH, global_step=step)
step += 1
except tf.errors.OutOfRangeError:
break
return step
3. 训练
'''
训练步骤:
1.初始化
2.定义模型
3.定义输入数据
4.定义前向图
5.训练模型
'''
def main():
# 定义初始化函数。
initializer = tf.random_uniform_initializer(-0.05, 0.05) # 均匀分布
# 定义训练用的循环神经网络模型。
with tf.variable_scope("nmt_model", reuse=None, initializer=initializer):
train_model = NMTModel()
# 定义输入数据。
data = MakeSrcTrgDataset(SRC_TRAIN_DATA, TRG_TRAIN_DATA, BATCH_SIZE)
iterator = data.make_initializable_iterator()
(src, src_size), (trg_input, trg_label, trg_size) = iterator.get_next()
# 定义前向计算图。输入数据以张量形式提供给forward函数。
cost_op, train_op = train_model.forward(src, src_size, trg_input, trg_label, trg_size)
# 训练模型。
saver = tf.train.Saver()
step = 0
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(NUM_EPOCH):
print("In iteration: %d" % (i + 1))
sess.run(iterator.initializer)
step = run_epoch(sess, cost_op, train_op, saver, step)
4. 测试
import tensorflow as tf
import codecs
import sys
# 读取checkpoint的路径。9000表示是训练程序在第9000步保存的checkpoint。
CHECKPOINT_PATH = "./seq2seq_ckpt-200"
# 模型参数。必须与训练时的模型参数保持一致。
HIDDEN_SIZE = 1024 # LSTM的隐藏层规模。
NUM_LAYERS = 2 # 深层循环神经网络中LSTM结构的层数。
SRC_VOCAB_SIZE = 10000 # 源语言词汇表大小。
TRG_VOCAB_SIZE = 4000 # 目标语言词汇表大小。
SHARE_EMB_AND_SOFTMAX = True # 在Softmax层和词向量层之间共享参数。
# 词汇表文件
SRC_VOCAB = "./en.vocab"
TRG_VOCAB = "./zh.vocab"
# 词汇表中<sos>和<eos>的ID。在解码过程中需要用<sos>作为第一步的输入,并将检查
# 是否是<eos>,因此需要知道这两个符号的ID。
SOS_ID = 1
EOS_ID = 2
# 定义NMTModel类来描述模型。
class NMTModel(object):
# 在模型的初始化函数中定义模型要用到的变量。
def __init__(self):
# 定义编码器和解码器所使用的LSTM结构。
self.enc_cell = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
# 为源语言和目标语言分别定义词向量。
self.src_embedding = tf.get_variable(
"src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
self.trg_embedding = tf.get_variable(
"trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])
# 定义softmax层的变量
if SHARE_EMB_AND_SOFTMAX:
self.softmax_weight = tf.transpose(self.trg_embedding)
else:
self.softmax_weight = tf.get_variable(
"weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
self.softmax_bias = tf.get_variable(
"softmax_bias", [TRG_VOCAB_SIZE])
def inference(self, src_input):
# 虽然输入只有一个句子,但因为dynamic_rnn要求输入是batch的形式,因此这里
# 将输入句子整理为大小为1的batch。
src_size = tf.convert_to_tensor([len(src_input)], dtype=tf.int32)
src_input = tf.convert_to_tensor([src_input], dtype=tf.int32)
src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
# 使用dynamic_rnn构造编码器。这一步与训练时相同。
with tf.variable_scope("encoder"):
enc_outputs, enc_state = tf.nn.dynamic_rnn(
self.enc_cell, src_emb, src_size, dtype=tf.float32)
# 设置解码的最大步数。这是为了避免在极端情况出现无限循环的问题。
MAX_DEC_LEN=100
with tf.variable_scope("decoder/rnn/multi_rnn_cell"):
# 使用一个变长的TensorArray来存储生成的句子。
init_array = tf.TensorArray(dtype=tf.int32, size=0,
dynamic_size = True, clear_after_read=False)
# 填入第一个单词<sos>作为解码器的输入。
init_array = init_array.write(0, SOS_ID)
# 构建初始的循环状态。循环状态包含循环神经网络的隐藏状态,保存生成句子的
# TensorArray,以及记录解码步数的一个整数step。
init_loop_var = (enc_state, init_array, 0)
# tf.while_loop的循环条件:
# 循环直到解码器输出<eos>,或者达到最大步数为止。
def continue_loop_condition(state, trg_ids, step):
return tf.reduce_all(tf.logical_and(
tf.not_equal(trg_ids.read(step), EOS_ID),
tf.less(step, MAX_DEC_LEN-1)))
def loop_body(state, trg_ids, step):
# 读取最后一步输出的单词,并读取其词向量。
trg_input = [trg_ids.read(step)]
trg_emb = tf.nn.embedding_lookup(self.trg_embedding,
trg_input)
# 这里不使用dynamic_rnn,而是直接调用dec_cell向前计算一步。
dec_outputs, next_state = self.dec_cell.call(
state=state, inputs=trg_emb)
# 计算每个可能的输出单词对应的logit,并选取logit值最大的单词作为
# 这一步的而输出。
output = tf.reshape(dec_outputs, [-1, HIDDEN_SIZE])
logits = (tf.matmul(output, self.softmax_weight)
+ self.softmax_bias)
next_id = tf.argmax(logits, axis=1, output_type=tf.int32)
# 将这一步输出的单词写入循环状态的trg_ids中。
trg_ids = trg_ids.write(step+1, next_id[0])
return next_state, trg_ids, step+1
# 执行tf.while_loop,返回最终状态。
state, trg_ids, step = tf.while_loop(
continue_loop_condition, loop_body, init_loop_var)
return trg_ids.stack()
def main():
# 定义训练用的循环神经网络模型。
with tf.variable_scope("nmt_model", reuse=None):
model = NMTModel()
# 定义个测试句子。
test_en_text = "This is a test . <eos>"
print(test_en_text)
# 根据英文词汇表,将测试句子转为单词ID。
with codecs.open(SRC_VOCAB, "r", "utf-8") as f_vocab:
src_vocab = [w.strip() for w in f_vocab.readlines()]
src_id_dict = dict((src_vocab[x], x) for x in range(len(src_vocab)))
test_en_ids = [(src_id_dict[token] if token in src_id_dict else src_id_dict['<unk>'])
for token in test_en_text.split()]
print(test_en_ids)
# 建立解码所需的计算图。
output_op = model.inference(test_en_ids)
sess = tf.Session()
saver = tf.train.Saver()
saver.restore(sess, CHECKPOINT_PATH)
# 读取翻译结果。
output_ids = sess.run(output_op)
print(output_ids)
# 根据中文词汇表,将翻译结果转换为中文文字。
with codecs.open(TRG_VOCAB, "r", "utf-8") as f_vocab:
trg_vocab = [w.strip() for w in f_vocab.readlines()]
output_text = ''.join([trg_vocab[x] for x in output_ids])
# 输出翻译结果。
print(output_text.encode('utf8').decode(sys.stdout.encoding))
sess.close()
if __name__ == "__main__":
main()
代码可学习技巧
- checkpoint保存每一步权重文件
- 使用argparse.ArgumentParser(),argparse 模块可以让人轻松编写用户友好的命令行接口,将变量通过命令行赋值
最后
以上就是机灵小笼包为你收集整理的【论文泛读】4. 机器翻译:Neural Machine Translation by Jointly Learning to Align and Translate准备理论知识代码实现(英文翻中文)代码可学习技巧的全部内容,希望文章能够帮你解决【论文泛读】4. 机器翻译:Neural Machine Translation by Jointly Learning to Align and Translate准备理论知识代码实现(英文翻中文)代码可学习技巧所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复