自然语言处理机器翻译

自然语言处理机器翻译本次实验通过构建和训练机器翻译模型 探索了其在自然语言处理领域中的应用效果 并深入理解了机器翻译的工作原理和优化方法

大家好,欢迎来到IT知识分享网。

 一、机器翻译

机器翻译,又称为自动翻译,简称“机翻、机译”,是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程。它是计算语言学的一个分支,也是人工智能的重要方向之一。

机器翻译的过程涉及原语(源语言)的输入和目的语(目标语言)的输出,其本质是人类思维和语言活动的模拟。机器翻译技术经历了从早期的词典匹配、词典结合语言学专家知识的规则翻译,到基于语料库的统计机器翻译,再到现在的神经网络机器翻译等多个阶段的发展。

机器翻译的应用场景广泛,包括但不限于跨语言交流、文档翻译、多语言内容处理、跨境电商、在线内容翻译以及语言学习和教育等领域。机器翻译技术可以实现不同国家不同语言间的低成本交流,具有成本低、易把控和速度快等优点。

机器翻译的发展一直与计算机技术、信息论、语言学等学科的发展紧密相随,是人工智能领域的重要研究方向之一。

机器翻译是指将一段文本从一种语言自动翻译到另一种语言。因为一段文本序列在不同语言中的长度不一定相同,所以我们使用机器翻译为例来介绍编码器—解码器和注意力机制的应用。

二、实验原理及目的

实验原理

机器翻译是自然语言处理(NLP)的一个核心领域,其目标是实现计算机自动将一种语言翻译成另一种语言,而不需要人类的参与。机器翻译的原理主要基于以下三种技术:

  1. 基于规则的机器翻译(RBMT):利用语言学规则将源语言文本转换为目标语言文本。这些规则通常由语言学家手工编写,覆盖语法、词汇和其他语言相关的特性。
  2. 基于统计的机器翻译(SMT):利用统计模型从大量双语文本数据中学习如何将源语言翻译为目标语言。与RBMT不同,SMT自动从数据中学习翻译规则和模式。
  3. 基于神经网络的机器翻译(NMT):使用深度学习技术,特别是递归神经网络(RNN)、长短时记忆网络(LSTM)或Transformer结构,以端到端的方式进行翻译。NMT直接从源语言到目标语言的句子或序列进行映射,不需要复杂的特性工程或中间步骤。其中,Transformer模型是近年来最受欢迎的神经机器翻译模型,它通过自注意力机制(Self-Attention)来处理输入序列中的关联信息,能够更好地捕捉长距离依赖关系。

实验目的

自然语言处理中的机器翻译实验目的主要包括以下几点:

  1. 探究翻译质量:通过构建和训练翻译模型,验证其在不同语言对之间的翻译效果,并评估其翻译质量,如准确率、流畅度等。
  2. 优化翻译算法:通过实验,发现现有翻译算法的不足,并尝试改进和优化算法,以提高翻译质量和效率。
  3. 促进跨语言交流:机器翻译有助于消除语言障碍,促进不同语言之间的交流和合作,对于全球化背景下的国际交流具有重要意义。
  4. 推动NLP技术发展:机器翻译作为NLP的重要应用领域之一,其研究成果和技术进步将推动整个NLP领域的发展。

三、代码实现

我们将使用含注意力机制的编码器—解码器来将一段简短的法语翻译成英语。下面我们来介绍模型的实现。

1. 编码器

在编码器中,我们将输入语言的词索引通过词嵌入层得到词的表征,然后输入到一个多层门控循环单元中。正如我们在6.5节(循环神经网络的简洁实现)中提到的,PyTorch的nn.GRU实例在前向计算后也会分别返回输出和最终时间步的多层隐藏状态。其中的输出指的是最后一层的隐藏层在各个时间步的隐藏状态,并不涉及输出层计算。注意力机制将这些输出作为键项和值项。

class Encoder(nn.Module): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, drop_prob=0, kwargs): super(Encoder, self).__init__(kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=drop_prob) def forward(self, inputs, state): # 输入形状是(批量大小, 时间步数)。将输出互换样本维和时间步维 embedding = self.embedding(inputs.long()).permute(1, 0, 2) # (seq_len, batch, input_size) return self.rnn(embedding, state) def begin_state(self): return None 

下面我们来创建一个批量大小为4、时间步数为7的小批量序列输入。设门控循环单元的隐藏层个数为2,隐藏单元个数为16。编码器对该输入执行前向计算后返回的输出形状为(时间步数, 批量大小, 隐藏单元个数)。门控循环单元在最终时间步的多层隐藏状态的形状为(隐藏层个数, 批量大小, 隐藏单元个数)。对于门控循环单元来说,state就是一个元素,即隐藏状态;如果使用长短期记忆,state是一个元组,包含两个元素即隐藏状态和记忆细胞。

In [8]:

encoder = Encoder(vocab_size=10, embed_size=8, num_hiddens=16, num_layers=2) output, state = encoder(torch.zeros((4, 7)), encoder.begin_state()) output.shape, state.shape # GRU的state是h, 而LSTM的是一个元组(h, c) 

Out[8]:

(torch.Size([7, 4, 16]), torch.Size([2, 4, 16]))

2. 注意力机制

我们将实现10.11节(注意力机制)中定义的函数a𝑎:将输入连结后通过含单隐藏层的多层感知机变换。其中隐藏层的输入是解码器的隐藏状态与编码器在所有时间步上隐藏状态的一一连结,且使用tanh函数作为激活函数。输出层的输出个数为1。两个Linear实例均不使用偏差。其中函数a𝑎定义里向量v𝑣的长度是一个超参数,即attention_size

def attention_model(input_size, attention_size): model = nn.Sequential(nn.Linear(input_size, attention_size, bias=False), nn.Tanh(), nn.Linear(attention_size, 1, bias=False)) return model 

注意力机制的输入包括查询项、键项和值项。设编码器和解码器的隐藏单元个数相同。这里的查询项为解码器在上一时间步的隐藏状态,形状为(批量大小, 隐藏单元个数);键项和值项均为编码器在所有时间步的隐藏状态,形状为(时间步数, 批量大小, 隐藏单元个数)。注意力机制返回当前时间步的背景变量,形状为(批量大小, 隐藏单元个数)。

def attention_forward(model, enc_states, dec_state): """ enc_states: (时间步数, 批量大小, 隐藏单元个数) dec_state: (批量大小, 隐藏单元个数) """ # 将解码器隐藏状态广播到和编码器隐藏状态形状相同后进行连结 dec_states = dec_state.unsqueeze(dim=0).expand_as(enc_states) enc_and_dec_states = torch.cat((enc_states, dec_states), dim=2) e = model(enc_and_dec_states) # 形状为(时间步数, 批量大小, 1) alpha = F.softmax(e, dim=0) # 在时间步维度做softmax运算 return (alpha * enc_states).sum(dim=0) # 返回背景变量 

在下面的例子中,编码器的时间步数为10,批量大小为4,编码器和解码器的隐藏单元个数均为8。注意力机制返回一个小批量的背景向量,每个背景向量的长度等于编码器的隐藏单元个数。因此输出的形状为(4, 8)。

seq_len, batch_size, num_hiddens = 10, 4, 8 model = attention_model(2*num_hiddens, 10) enc_states = torch.zeros((seq_len, batch_size, num_hiddens)) dec_state = torch.zeros((batch_size, num_hiddens)) attention_forward(model, enc_states, dec_state).shape 
torch.Size([4, 8])

3 .含注意力机制的解码器

我们直接将编码器在最终时间步的隐藏状态作为解码器的初始隐藏状态。这要求编码器和解码器的循环神经网络使用相同的隐藏层个数和隐藏单元个数。

在解码器的前向计算中,我们先通过刚刚介绍的注意力机制计算得到当前时间步的背景向量。由于解码器的输入来自输出语言的词索引,我们将输入通过词嵌入层得到表征,然后和背景向量在特征维连结。我们将连结后的结果与上一时间步的隐藏状态通过门控循环单元计算出当前时间步的输出与隐藏状态。最后,我们将输出通过全连接层变换为有关各个输出词的预测,形状为(批量大小, 输出词典大小)。

In [12]:

class Decoder(nn.Module): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, attention_size, drop_prob=0): super(Decoder, self).__init__() self.embedding = nn.Embedding(vocab_size, embed_size) self.attention = attention_model(2*num_hiddens, attention_size) # GRU的输入包含attention输出的c和实际输入, 所以尺寸是 num_hiddens+embed_size self.rnn = nn.GRU(num_hiddens + embed_size, num_hiddens, num_layers, dropout=drop_prob) self.out = nn.Linear(num_hiddens, vocab_size) def forward(self, cur_input, state, enc_states): """ cur_input shape: (batch, ) state shape: (num_layers, batch, num_hiddens) """ # 使用注意力机制计算背景向量 c = attention_forward(self.attention, enc_states, state[-1]) # 将嵌入后的输入和背景向量在特征维连结, (批量大小, num_hiddens+embed_size) input_and_c = torch.cat((self.embedding(cur_input), c), dim=1) # 为输入和背景向量的连结增加时间步维,时间步个数为1 output, state = self.rnn(input_and_c.unsqueeze(0), state) # 移除时间步维,输出形状为(批量大小, 输出词典大小) output = self.out(output).squeeze(dim=0) return output, state def begin_state(self, enc_state): # 直接将编码器最终时间步的隐藏状态作为解码器的初始隐藏状态 return enc_states 

4.训练模型

我们先实现batch_loss函数计算一个小批量的损失。解码器在最初时间步的输入是特殊字符BOS。之后,解码器在某时间步的输入为样本输出序列在上一时间步的词,即强制教学。此外,同10.3节(word2vec的实现)中的实现一样,我们在这里也使用掩码变量避免填充项对损失函数计算的影响。

In [13]:

def batch_loss(encoder, decoder, X, Y, loss): batch_size = X.shape[0] enc_state = encoder.begin_state() enc_outputs, enc_state = encoder(X, enc_state) # 初始化解码器的隐藏状态 dec_state = decoder.begin_state(enc_state) # 解码器在最初时间步的输入是BOS dec_input = torch.tensor([out_vocab.stoi[BOS]] * batch_size) # 我们将使用掩码变量mask来忽略掉标签为填充项PAD的损失, 初始全1 mask, num_not_pad_tokens = torch.ones(batch_size,), 0 l = torch.tensor([0.0]) for y in Y.permute(1,0): # Y shape: (batch, seq_len) dec_output, dec_state = decoder(dec_input, dec_state, enc_outputs) l = l + (mask * loss(dec_output, y)).sum() dec_input = y # 使用强制教学 num_not_pad_tokens += mask.sum().item() # EOS后面全是PAD. 下面一行保证一旦遇到EOS接下来的循环中mask就一直是0 mask = mask * (y != out_vocab.stoi[EOS]).float() return l / num_not_pad_tokens 

在训练函数中,我们需要同时迭代编码器和解码器的模型参数。

In [23]:

def train(encoder, decoder, dataset, lr, batch_size, num_epochs): # 为编码器和解码器分别创建Adam优化器,并设置学习率为lr enc_optimizer = torch.optim.Adam(encoder.parameters(), lr=lr) dec_optimizer = torch.optim.Adam(decoder.parameters(), lr=lr) # 创建一个交叉熵损失函数,设置reduction为'none',意味着不对mini-batch的损失进行平均或求和 loss = nn.CrossEntropyLoss(reduction='none') # 使用PyTorch的DataLoader来加载数据集,并设置batch_size和shuffle=True(在每个epoch开始时打乱数据) data_iter = Data.DataLoader(dataset, batch_size, shuffle=True) # 开始训练循环 for epoch in range(num_epochs): l_sum = 0.0 # 用于记录当前epoch的总损失 # 开始一个epoch的数据迭代 for X, Y in data_iter: # 在每次迭代开始前,重置优化器的梯度 enc_optimizer.zero_grad() dec_optimizer.zero_grad() # 调用batch_loss函数(该函数未在给出的代码段中定义)来计算当前batch的损失 # 假设这个函数接收编码器、解码器、输入X、目标Y和损失函数作为参数 l = batch_loss(encoder, decoder, X, Y, loss) # 反向传播损失,计算梯度 l.backward() # 使用优化器更新编码器和解码器的参数 enc_optimizer.step() dec_optimizer.step() # 累加当前batch的损失值 l_sum += l.item() # 如果当前epoch是10的倍数,则打印出当前的epoch数和平均损失 if (epoch + 1) % 10 == 0: print("epoch %d, loss %.3f" % (epoch + 1, l_sum / len(data_iter))) 

接下来,创建模型实例并设置超参数。然后,我们就可以训练模型了。

In [16]:

embed_size, num_hiddens, num_layers = 64, 64, 2 attention_size, drop_prob, lr, batch_size, num_epochs = 10, 0.5, 0.01, 2, 50 encoder = Encoder(len(in_vocab), embed_size, num_hiddens, num_layers, drop_prob) decoder = Decoder(len(out_vocab), embed_size, num_hiddens, num_layers, attention_size, drop_prob) train(encoder, decoder, dataset, lr, batch_size, num_epochs) 
epoch 10, loss 0.463 epoch 20, loss 0.217 epoch 30, loss 0.105 epoch 40, loss 0.061 epoch 50, loss 0.038 

5.预测不定长的序列

在10.10节(束搜索)中我们介绍了3种方法来生成解码器在每个时间步的输出。这里我们实现最简单的贪婪搜索。

In [17]:

def translate(encoder, decoder, input_seq, max_seq_len): # 将输入的序列字符串按照空格分割成token列表 in_tokens = input_seq.split(' ') # 在token列表的末尾添加EOS(End of Sentence)token,并在其后填充PAD(Padding)token到最大序列长度 # 这样做是为了确保输入序列具有统一的长度,以便能够作为张量(tensor)进行处理 in_tokens += [EOS] + [PAD] * (max_seq_len - len(in_tokens) - 1) # 将token列表转换为张量,使用词汇表(vocabulary)中的token到索引(stoi)的映射 # 这里假设in_vocab是一个包含stoi映射的Vocabulary对象 # 注意这里batch_size=1,因为我们一次只处理一个句子 enc_input = torch.tensor([[in_vocab.stoi[tk] for tk in in_tokens]]) # batch=1 # 获取编码器的初始状态 enc_state = encoder.begin_state() # 通过编码器处理输入序列,得到编码器的输出和最终状态 enc_output, enc_state = encoder(enc_input, enc_state) # 设置解码器的初始输入为BOS(Begin of Sentence)token # 这里假设out_vocab是解码器输出词汇表,包含stoi映射 dec_input = torch.tensor([out_vocab.stoi[BOS]]) # 根据编码器的最终状态初始化解码器的状态 # 注意这里假设decoder.begin_state()接受编码器的最终状态作为参数 dec_state = decoder.begin_state(enc_state) # 初始化一个空列表来存储解码器输出的token output_tokens = [] # 开始解码过程,最大迭代次数为max_seq_len for _ in range(max_seq_len): # 通过解码器处理当前的输入和解码器的状态,得到输出和解码器的下一个状态 dec_output, dec_state = decoder(dec_input, dec_state, enc_output) # 取出当前输出中概率最大的索引 pred = dec_output.argmax(dim=1) # 将索引转换回token pred_token = out_vocab.itos[int(pred.item())] # 如果预测出的token是EOS,则结束解码过程 if pred_token == EOS: # 当任一时间步搜索出EOS时,输出序列即完成 break else: # 否则,将预测的token添加到输出列表中 output_tokens.append(pred_token) # 将预测的token作为下一个时间步的输入 dec_input = pred # 返回解码器输出的token列表 return output_tokens 

简单测试一下模型。输入法语句子“ils regardent.”,翻译后的英语句子应该是“they are watching.”。

In [18]:

input_seq = 'ils regardent .' translate(encoder, decoder, input_seq, max_seq_len) 

Out[18]:

['they', 'are', 'watching', '.']

6.评价翻译结果

评价机器翻译结果通常使用BLEU(Bilingual Evaluation Understudy)[1]。对于模型预测序列中任意的子序列,BLEU考察这个子序列是否出现在标签序列中。

具体来说,设词数为n𝑛的子序列的精度为pn𝑝𝑛。它是预测序列与标签序列匹配词数为n𝑛的子序列的数量与预测序列中词数为n𝑛的子序列的数量之比。举个例子,假设标签序列为A𝐴、B𝐵、C𝐶、D𝐷、E𝐸、F𝐹,预测序列为A𝐴、B𝐵、B𝐵、C𝐶、D𝐷,那么p1=4/5,p2=3/4,p3=1/3,p4=0𝑝1=4/5,𝑝2=3/4,𝑝3=1/3,𝑝4=0。设lenlabel𝑙𝑒𝑛label和lenpred𝑙𝑒𝑛pred分别为标签序列和预测序列的词数,那么,BLEU的定义为

exp(min(0,1−lenlabellenpred))∏n=1kp1/2nn,

其中k𝑘是我们希望匹配的子序列的最大词数。可以看到当预测序列和标签序列完全一致时,BLEU为1。

因为匹配较长子序列比匹配较短子序列更难,BLEU对匹配较长子序列的精度赋予了更大权重。例如,当pn𝑝𝑛固定在0.5时,随着n𝑛的增大,0.51/2≈0.7,0.51/4≈0.84,0.51/8≈0.92,0.51/16≈0.960.51/2≈0.7,0.51/4≈0.84,0.51/8≈0.92,0.51/16≈0.96。另外,模型预测较短序列往往会得到较高pn𝑝𝑛值。因此,上式中连乘项前面的系数是为了惩罚较短的输出而设的。举个例子,当k=2𝑘=2时,假设标签序列为A𝐴、B𝐵、C𝐶、D𝐷、E𝐸、F𝐹,而预测序列为A𝐴、B𝐵。虽然p1=p2=1𝑝1=𝑝2=1,但惩罚系数exp(1−6/2)≈0.14exp⁡(1−6/2)≈0.14,因此BLEU也接近0.14。

下面来实现BLEU的计算。

In [24]:

import math import collections def bleu(pred_tokens, label_tokens, k): # 获取预测序列和标签序列的长度 len_pred, len_label = len(pred_tokens), len(label_tokens) # 初始BLEU分数的简短惩罚(brevity penalty)部分 # 如果预测序列比标签序列短,则分数会受到惩罚 score = math.exp(min(0, 1 - len_label / len_pred)) # 遍历从1到k的所有n-gram长度 for n in range(1, k + 1): # 初始化n-gram匹配的数量和标签序列中n-gram的出现次数 num_matches, label_subs = 0, collections.defaultdict(int) # 遍历标签序列,统计每个n-gram的出现次数 for i in range(len_label - n + 1): label_subs[''.join(label_tokens[i: i + n])] += 1 # 遍历预测序列,检查每个n-gram是否出现在标签序列中 for i in range(len_pred - n + 1): # 如果预测序列中的n-gram在标签序列中出现过 if label_subs[''.join(pred_tokens[i: i + n])] > 0: # 增加匹配的n-gram数量 num_matches += 1 # 减少该n-gram在标签序列中的计数(确保不重复计算) label_subs[''.join(pred_tokens[i: i + n])] -= 1 # 计算n-gram的精度并应用到BLEU分数中 # 注意这里使用了指数加权平均,其中权重与n的大小成反比 score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n)) # 返回最终的BLEU分数 return score 

接下来,定义一个辅助打印函数。

In [25]:

def score(input_seq, label_seq, k): pred_tokens = translate(encoder, decoder, input_seq, max_seq_len) label_tokens = label_seq.split(' ') print('bleu %.3f, predict: %s' % (bleu(pred_tokens, label_tokens, k), ' '.join(pred_tokens))) 

预测正确则分数为1。

In [21]:

score('ils regardent .', 'they are watching .', k=2) 
bleu 1.000, predict: they are watching . 

In [22]:

score('ils sont canadienne .', 'they are canadian .', k=2) 
bleu 0.658, predict: they are actors . 

五、实验小结

1.总结

2.实验意义与展望

本次实验通过构建和训练机器翻译模型,探索了其在自然语言处理领域中的应用效果,并深入理解了机器翻译的工作原理和优化方法。实验结果表明,基于神经网络的机器翻译模型具有强大的学习能力和翻译能力,在自然语言处理领域具有广泛的应用前景。

未来,随着技术的不断进步和研究的深入,我们可以进一步探索和改进机器翻译模型,如引入更复杂的模型结构、利用多模态信息、融合知识图谱等,以提高模型的翻译质量和泛化能力。此外,我们还可以研究机器翻译模型在其他自然语言处理任务中的应用,如问答系统、对话系统等,为自然语言处理领域的发展做出更大的贡献。

  • 神经网络模型在机器翻译任务中取得了显著的成效,能够有效地从源语言中捕获并传达关键信息。
  • 模型的设计和选择对于翻译质量至关重要。适当的模型结构和参数设置能够显著提高模型的翻译性能。
  • 优化技术的应用对于提高模型的训练效率和翻译质量具有积极作用。在实际应用中,需要综合考虑各种优化技术的特点和适用场景,选择最适合当前任务的优化方法。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128423.html

(0)
上一篇 2025-08-31 18:33
下一篇 2025-08-31 18:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信