1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 机器翻译——英译中

机器翻译——英译中

时间:2024-04-20 21:38:21

相关推荐

机器翻译——英译中

1. 前言

本文使用飞桨(PaddlePaddle)训练机器翻译模型,实现将英文翻译成中文的神经网络翻译机。

本人全部文章请参见:博客文章导航目录

本文归属于:自然语言处理系列

本系列实践代码请参见:我的GitHub

前文:BERT与ERNIE

2. 训练机器翻译模型

本文使用Sequence-to-Sequence模型原理一文中所述Seq2Seq模型实现机器翻译。其中RNN使用长短期记忆网络(LSTM)原理与实战一文中所述LSTM模型结构,同时结合注意力机制(Attention):Seq2Seq模型的改进一文中所述注意力机制改进Seq2Seq模型,提升机器翻译效果。

本文代码部分参考了PaddlePaddle官方应用实践教程,对代码结构进行了修改,并调整了部分处理逻辑,使得该深度学习实践与本系列讲解的方法原理一致。

2.1 数据处理

本文讲解的机器翻译实践使用/anki/提供的中英文句子对作为训练数据集。对于英文,先将其全部变成小写,然后仅保留英文单词及句子末尾标点符号。对于中文,直接按字进行切分,不采用分词操作。同时设定MAX_LEN = 12,得到一个包含19702个中英句子对的训练数据集。

对数据集进行进一步处理,生成中英文词表。在英文句子后添加<eos>符号,在中文句子前后分别添加<bos><eos>符号,

并使用<pad>符号将短句子填充成统一长度。最后根据中文句子创建Decoder每个时刻标签对应的句子,并将训练句子转换成词的ID构成的序列。具体代码如下:

# -*- coding: utf-8 -*-# @Time : /8/1 19:16# @Author : He Ruizhi# @File : machine_translation.py# @Software: PyCharmimport paddleimport paddle.nn.functional as Fimport reimport numpy as npimport timeimport warningswarnings.filterwarnings('ignore')print(paddle.__version__) # 2.1.0# 设置训练句子最大长度,用于筛选数据集中的部分数据MAX_LEN = 12def create_dataset(file_path):"""构建机器翻译训练数据集:param file_path: 训练数据路径:return:train_en_sents:由数字ID组成的英文句子train_cn_sents:由数字ID组成的中文句子train_cn_label_sents:由数字ID组成的中文词汇标签en_vocab:英文词表cn_vocab:中文词表"""with open(file_path, 'rt', encoding='utf-8') as f:lines = f.read().strip().split('\n')# 设置正则匹配模板,用于从英文句子中提取单词words_re = pile(r'\w+')# 将训练数据文件中的中文和英文句子全部提取出来pairs = []for line in lines:en_sent, cn_sent, _ = line.split('\t')pairs.append((words_re.findall(en_sent.lower())+[en_sent[-1]], list(cn_sent)))# 从原始训练数据中筛选出一部分数据用来训练模型# 实际训练神经网络翻译机时数据量肯定是越多越好,不过本文只选取长度小于10的句子filtered_pairs = []for pair in pairs:if len(pair[0]) < MAX_LEN and len(pair[1]) < MAX_LEN:filtered_pairs.append(pair)# 创建中英文词表,将中文和因为句子转换成词的ID构成的序列# 此外须在词表中添加三个特殊词:<pad>用来对短句子进行填充;<bos>表示解码时的起始符号;<eos>表示解码时的终止符号# 在实际任务中,一般还会需要指定<unk>符号表示在词表中未出现过的词,并在构造训练集时有意识地添加<unk>符号,使模型能够处理相应情况en_vocab = {}cn_vocab = {}en_vocab['<pad>'], en_vocab['<bos>'], en_vocab['<eos>'] = 0, 1, 2cn_vocab['<pad>'], cn_vocab['<bos>'], cn_vocab['<eos>'] = 0, 1, 2en_idx, cn_idx = 3, 3for en, cn in filtered_pairs:for w in en:if w not in en_vocab:en_vocab[w] = en_idxen_idx += 1for w in cn:if w not in cn_vocab:cn_vocab[w] = cn_idxcn_idx += 1# 使用<pad>符号将短句子填充成长度一致的句子,便于使用批量数据训练模型# 同时根据词表,创建一份实际的用于训练的用numpy array组织起来的数据集padded_en_sents = []padded_cn_sents = []# 训练过程中的预测的目标,即每个中文的当前词去预测下一个词是什么词padded_cn_label_sents = []for en, cn in filtered_pairs:padded_en_sent = en + ['<eos>'] + ['<pad>'] * (MAX_LEN - len(en))padded_cn_sent = ['<bos>'] + cn + ['<eos>'] + ['<pad>'] * (MAX_LEN - len(cn))padded_cn_label_sent = cn + ['<eos>'] + ['<pad>'] * (MAX_LEN - len(cn) + 1)# 根据词表,将相应的单词转换成数字IDpadded_en_sents.append([en_vocab[w] for w in padded_en_sent])padded_cn_sents.append([cn_vocab[w] for w in padded_cn_sent])padded_cn_label_sents.append([cn_vocab[w] for w in padded_cn_label_sent])# 将训练数据用numpy array组织起来train_en_sents = np.array(padded_en_sents, dtype='int64')train_cn_sents = np.array(padded_cn_sents, dtype='int64')train_cn_label_sents = np.array(padded_cn_label_sents, dtype='int64')return train_en_sents, train_cn_sents, train_cn_label_sents, en_vocab, cn_vocab

2.2 网络构建

本文讲解的机器翻译实践采用Encoder-AttentionDecoder架构,Encoder采用LSTM模型,Decoder采用Attention+LSTM模型。虽然在注意力机制(Attention):Seq2Seq模型的改进一文中为了讲解简便,使用的是SimpleRNN+Attention,而不是LSTM+Attention。但是使用不同的RNN结构,本质均为Decoder RNN初始状态等于Encoder RNN最后时刻状态,Decoder RNN ttt时刻输入为xt′x_t^\primext′​和Context Vector ct−1c_{t-1}ct−1​拼接而成的向量。

2.2.1 构建Encoder

在Encoder部分,输入英文句子,通过查找完Embedding之后接一个LSTM的方式构建一个对英文句子编码的网络。具体代码如下:

class Encoder(paddle.nn.Layer):"""Seq2Seq模型Encoder,采用LSTM结构"""def __init__(self, en_vocab_size, en_embedding_dim, lstm_hidden_size, lstm_num_layers):super(Encoder, self).__init__()self.emb = paddle.nn.Embedding(num_embeddings=en_vocab_size, embedding_dim=en_embedding_dim)self.lstm = paddle.nn.LSTM(input_size=en_embedding_dim, hidden_size=lstm_hidden_size,num_layers=lstm_num_layers)def forward(self, x):x = self.emb(x)x, (h, c) = self.lstm(x)return x, h, c

2.2.2 构建AttentionDecoder

在Decoder部分,通过一个带有Attention的LSTM来完成解码。

Decoder的forward函数每次调用只让LSTM往前计算一次,即对只生成当前时刻的输出,而不是生成整个输出序列。整体的recurrent部分,是在训练循环内完成的。

本文讲解的机器翻译实践Attention中权重计算采用注意力机制(Attention):Seq2Seq模型的改进一文3.2部分所述方法一。即将Decoder ttt时刻输出状态向量sts_tst​分别与Encoder各个时刻状态向量h1h_1h1​至hmh_mhm​拼接,依次通过一个tanh函数激活的全连接层,和一个线性加和层(Linear Layer)。

具体代码如下:

class AttentionDecoder(paddle.nn.Layer):"""Seq2Seq模型解码器,采用带有注意力机制的LSTM结构"""def __init__(self, cn_vocab_size, cn_embedding_dim, lstm_hidden_size, v_dim):super(AttentionDecoder, self).__init__()self.emb = paddle.nn.Embedding(num_embeddings=cn_vocab_size, embedding_dim=cn_embedding_dim)# lstm层输入为x'_t和Context Vector拼接而成的向量,Context Vector的维度与lstm_hidden_size一致self.lstm = paddle.nn.LSTM(input_size=cn_embedding_dim + lstm_hidden_size,hidden_size=lstm_hidden_size)# 用于计算Attention权重self.attention_linear1 = paddle.nn.Linear(lstm_hidden_size * 2, v_dim)self.attention_linear2 = paddle.nn.Linear(v_dim, 1)# 用于根据lstm状态计算输出self.out_linear = paddle.nn.Linear(lstm_hidden_size, cn_vocab_size)# forward函数每次往前计算一次。整体的recurrent部分,是在训练循环内完成的。def forward(self, x, previous_hidden, previous_cell, encoder_outputs):x = self.emb(x)# 对previous_hidden进行数据重排hidden_transpose = paddle.transpose(previous_hidden, [1, 0, 2])# attention输入:Decoder当前状态和Encoder所有状态# 总共需计算MAX_LEN + 1个权重(这是因为会在输入句子后面加上一个<eos>符号)attention_inputs = paddle.concat((encoder_outputs,paddle.tile(hidden_transpose, repeat_times=[1, MAX_LEN + 1, 1])), axis=-1)attention_hidden = self.attention_linear1(attention_inputs)attention_hidden = F.tanh(attention_hidden)attention_logits = self.attention_linear2(attention_hidden)attention_logits = paddle.squeeze(attention_logits)# 计算得到所有MAX_LEN + 1个权重attention_weights = F.softmax(attention_logits)attention_weights = paddle.expand_as(paddle.unsqueeze(attention_weights, -1),encoder_outputs)# 计算Context Vectorcontext_vector = paddle.multiply(encoder_outputs, attention_weights)context_vector = paddle.sum(context_vector, 1)context_vector = paddle.unsqueeze(context_vector, 1)lstm_input = paddle.concat((x, context_vector), axis=-1)x, (hidden, cell) = self.lstm(lstm_input, (previous_hidden, previous_cell))output = self.out_linear(hidden)output = paddle.squeeze(output)return output, (hidden, cell)

2.3 训练模型

定义trian函数,创建EncoderAttentionDecoder对象,创建Adam优化器,设定学习率和优化参数。

在每个epoch内,随机打乱训练数据,在每个batch内,通过多次调用atten_decoder,实现解码时的recurrent循环。在每次解码下一个词时,给定了训练数据当中的真实词作为了预测下一个词时的输入。

具体代码如下:

def train(train_en_sents, train_cn_sents, train_cn_label_sents, epochs, learning_rate, batch_size,en_vocab_size, en_embedding_dim, lstm_hidden_size, lstm_num_layers,cn_vocab_size, cn_embedding_dim, v_dim):encoder = Encoder(en_vocab_size, en_embedding_dim, lstm_hidden_size, lstm_num_layers)atten_decoder = AttentionDecoder(cn_vocab_size, cn_embedding_dim, lstm_hidden_size, v_dim)opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=encoder.parameters()+atten_decoder.parameters())for epoch in range(epochs):print("epoch:{}".format(epoch))# 将训练数据集打乱perm = np.random.permutation(len(train_en_sents))train_en_sents_shuffled = train_en_sents[perm]train_cn_sents_shuffled = train_cn_sents[perm]train_cn_label_sents_shuffled = train_cn_label_sents[perm]for iteration in range(train_en_sents_shuffled.shape[0] // batch_size):# 获取一个batch的英文句子训练数据x_data = train_en_sents_shuffled[(batch_size * iteration):(batch_size * (iteration + 1))]# 将数据转换成paddle内置tensorsent = paddle.to_tensor(x_data)# 经过encoder得到对输入数据的编码en_repr, hidden, cell = encoder(sent)# 获取一个batch的对应的中文句子数据x_cn_data = train_cn_sents_shuffled[(batch_size * iteration):(batch_size * (iteration + 1))]x_cn_label_data = train_cn_label_sents_shuffled[(batch_size * iteration):(batch_size * (iteration + 1))]# 损失loss = paddle.zeros([1])# 解码器循环,计算总损失for i in range(MAX_LEN + 2):# 获得当前输入atten_decoder的输入元素及标签cn_word = paddle.to_tensor(x_cn_data[:, i:i + 1])cn_word_label = paddle.to_tensor(x_cn_label_data[:, i])logits, (hidden, cell) = atten_decoder(cn_word, hidden, cell, en_repr)step_loss = F.cross_entropy(logits, cn_word_label)loss += step_loss# 计算平均损失loss = loss / (MAX_LEN + 2)if iteration % 200 == 0:print("iter {}, loss:{}".format(iteration, loss.numpy()))# 后向传播loss.backward()# 参数更新opt.step()# 清除梯度opt.clear_grad()# 训练完成保存模型参数paddle.save(encoder.state_dict(), 'models/encoder.pdparams')paddle.save(atten_decoder.state_dict(), 'models/atten_decoder.pdparams')

调用create_dataset函数生成训练数据。设置超参数epochs = 20batch_size = 64learning_rate = 0.001en_embedding_dim = 128cn_embedding_dim = 128lstm_hidden_size = 256lstm_num_layers = 1以及v_dim = 256,调用train函数开启训练。代码如下:

if __name__ == '__main__':train_en_sents, train_cn_sents, train_cn_label_sents, en_vocab, cn_vocab = create_dataset('datasets/cmn.txt')# 设置超参数epochs = 20batch_size = 64learning_rate = 0.001en_vocab_size = len(en_vocab)cn_vocab_size = len(cn_vocab)en_embedding_dim = 128cn_embedding_dim = 128lstm_hidden_size = 256lstm_num_layers = 1v_dim = 256start_time = time.time()train(train_en_sents, train_cn_sents, train_cn_label_sents, epochs, learning_rate, batch_size,en_vocab_size, en_embedding_dim, lstm_hidden_size, lstm_num_layers,cn_vocab_size, cn_embedding_dim, v_dim)finish_time = time.time()print('训练用时:{:.2f}分钟'.format((finish_time-start_time)/60.0))

使用GPU训练模型,用时共7.59分钟。如果使用CPU,预计训练所用时间至少翻一倍。其中训练过程打印的信息如下:

2.1.0W0803 22:08:31.521724 16684 :404] Please NOTE: device: 0, GPU Compute Capability: 6.1, Driver API Version: 11.2, Runtime API Version: 10.1W0803 22:08:31.536396 16684 :422] device: 0, cuDNN Version: 7.6.epoch:0iter 0, loss:[8.066058]iter 200, loss:[3.3313792]epoch:1iter 0, loss:[3.0832756]iter 200, loss:[2.9235165]epoch:2iter 0, loss:[2.5026035]iter 200, loss:[2.6165113]epoch:3iter 0, loss:[2.511048]iter 200, loss:[2.173079]epoch:4iter 0, loss:[2.342031]iter 200, loss:[2.179213]epoch:5iter 0, loss:[1.9800943]iter 200, loss:[1.9754598]epoch:6iter 0, loss:[2.1824288]iter 200, loss:[2.0068507]epoch:7iter 0, loss:[1.8755927]iter 200, loss:[1.7782102]epoch:8iter 0, loss:[1.6492431]iter 200, loss:[1.7164807]epoch:9iter 0, loss:[1.5739967]iter 200, loss:[1.5312105]epoch:10iter 0, loss:[1.6726758]iter 200, loss:[1.5393445]epoch:11iter 0, loss:[1.391438]iter 200, loss:[1.3156104]epoch:12iter 0, loss:[1.328358]iter 200, loss:[1.0773911]epoch:13iter 0, loss:[1.1689429]iter 200, loss:[1.2644379]epoch:14iter 0, loss:[1.0560603]iter 200, loss:[1.1911696]epoch:15iter 0, loss:[1.1562344]iter 200, loss:[1.0733411]epoch:16iter 0, loss:[1.0015976]iter 200, loss:[0.9953356]epoch:17iter 0, loss:[0.78453755]iter 200, loss:[1.006]epoch:18iter 0, loss:[0.8454544]iter 200, loss:[1.0099177]epoch:19iter 0, loss:[0.8389757]iter 200, loss:[0.74120027]训练用时:7.59分钟

3. 使用模型进行机器翻译

训练完成后,从训练集中随机抽取部分句子演示机器翻译效果。定义translate函数,从训练集中随机抽取部分句子,经过encoder编码,再使用atten_decoder解码,并将结果输出。具体代码如下:

def translate(num_of_exampels_to_evaluate, encoder, atten_decoder, en_vocab, cn_vocab):"""展示机器翻译效果,用训练集中部分数据查看机器的翻译的结果:param num_of_exampels_to_evaluate: 指定从训练多少句子:param encoder: 训练好的encoder:param atten_decoder: 训练好的atten_decoder:param en_vocab: 英文词汇表:param cn_vocab: 中文词汇表:return: None"""# 将模型设置为eval模式encoder.eval()atten_decoder.eval()# 从训练数据中随机选择部分英语句子展示翻译效果indices = np.random.choice(len(train_en_sents), num_of_exampels_to_evaluate, replace=False)x_data = train_en_sents[indices]sent = paddle.to_tensor(x_data)en_repr, hidden, cell = encoder(sent)# 获取随机选择到的英语句子和对应的中文翻译en_vocab_list = list(en_vocab)cn_vocab_list = list(cn_vocab)en_sents = []cn_sents = []for i in range(num_of_exampels_to_evaluate):this_en_sents = []this_cn_sents = []for en_vocab_id in train_en_sents[indices[i]]:# 0,1,2是三个特殊符号的IDif en_vocab_id not in [0, 1, 2]:this_en_sents.append(en_vocab_list[en_vocab_id])for cn_vocab_id in train_cn_sents[indices[i]]:if cn_vocab_id not in [0, 1, 2]:this_cn_sents.append(cn_vocab_list[cn_vocab_id])en_sents.append(this_en_sents)cn_sents.append(this_cn_sents)# Decoder解码时输入的第一个符号为<bos>word = np.array([[cn_vocab['<bos>']]] * num_of_exampels_to_evaluate)word = paddle.to_tensor(word)decoded_sent = []for i in range(MAX_LEN + 2):logits, (hidden, cell) = atten_decoder(word, hidden, cell, en_repr)word = paddle.argmax(logits, axis=1)decoded_sent.append(word.numpy())word = paddle.unsqueeze(word, axis=-1)results = np.stack(decoded_sent, axis=1)for i in range(num_of_exampels_to_evaluate):en_input = " ".join(en_sents[i][:-1]) + en_sents[i][-1]ground_truth_translate = "".join(cn_sents[i][:-1]) + cn_sents[i][-1]model_translate = ""for k in results[i]:w = list(cn_vocab)[k]if w != '<pad>' and w != '<eos>':model_translate += wprint(en_input)print("true: {}".format(ground_truth_translate))print("pred: {}".format(model_translate))

创建encoderatten_decoder对象,并加载训练好的模型参数,调用translate函数查看机器翻译效果。具体代码如下:

# 用训练好的模型来预测# 首先创建encoder和atten_decoder对象,并加载训练好的参数encoder = Encoder(en_vocab_size, en_embedding_dim, lstm_hidden_size, lstm_num_layers)atten_decoder = AttentionDecoder(cn_vocab_size, cn_embedding_dim, lstm_hidden_size, v_dim)encoder_state_dict = paddle.load('models/encoder.pdparams')atten_decoder_state_dict = paddle.load('models/atten_decoder.pdparams')encoder.set_state_dict(encoder_state_dict)atten_decoder.set_state_dict(atten_decoder_state_dict)# 调用translate函数实现机器翻译——英译中translate(10, encoder, atten_decoder, en_vocab, cn_vocab)

运行上述代码,打印如下结果:

one hundred years is called a century.true: 一百年被叫做一个世纪。pred: 一个人都在10岁了。how long would it take?true: 要多长时间?pred: 要多久?i m waiting for this store to open.true: 我正等著這家店開門。pred: 我在等我去那裡。why do you need this money?true: 你為什麼需要這筆錢?pred: 你為什麼需要这种錢?i m free now.true: 我现在有空了。pred: 我现在有点了。he said that it was nine o clock.true: 他说九点了。pred: 他說他很快就了。you ve got a lot of willpower.true: 你的意志力很強。pred: 你的朋友很好。i ve got something i want to show you.true: 我有东西想给你看看。pred: 我有些事要我想要你。that hurts.true: 真疼。pred: 真疼。the top of mt fuji was covered with snow.true: 富士山顶盖满了雪。pred: 富士山被山顶盖了。

本文着重综合NLP模型原理与应用系列内容,讲述机器翻译基本原理和深度学习实践流程,离产业化机器翻译模型还比较遥远。在工业界,主流使用BERT+Transform模型,而不是文本所采用的模型。同时工业训练神经网络翻译机,训练数据量级不可同日而语。

4. 参考资料链接

.cn/documentation/docs/zh/tutorial/nlp_case/seq2seq_with_attention/seq2seq_with_attention.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。