基于Seq2Seq的baseline详解
根据题意的知需要用基于神经网络进行相应的建模,将英文翻译成中文,基本流程如下:
- 配置环境
首先我们需要配制baseline的环境,基本环境还是魔搭平台,除此之外还用安装几个其他的安装包
torchtext :是一个用于自然语言处理(NLP)任务的库,它提供了丰富的功能,包括数据预处理、词汇构建、序列化和批处理等,特别适合于文本分类、情感分析、机器翻译等任务
jieba:是一个中文分词库,用于将中文文本切分成有意义的词语
sacrebleu:用于评估机器翻译质量的工具,主要通过计算BLEU(Bilingual Evaluation Understudy)得分来衡量生成文本与参考译文之间的相似度
spacy:是一个强大的自然语言处理库,支持70+语言的分词与训练
下图是spacy运作的示意图,先分词,进行词性标注再进行还原,获得每个词的具体属性再进行迭代
- 数据预处理
首先是清洗和规范化数据:去除掉数据中可能存在的特殊字符、非文本内容(拟声词)等,保持数据的整洁性,纯净性,还要统一文本中的大小写,统一日期,数字等标准化格式,最后就是将整篇文本分割成小句子和段落,便于后续去进行处理。
然后是分词处理,将第一步中文本拆解成的小句子和段落进一步进行分解成单词或词素,即包含着一部分语法信息,用jieba对中文进行分词处理,用spacy对英文进行分词处理
第三步是构建词汇表和词向量
首先从训练集给出的数据中收集到所有出现过的词汇,构建为索引,方便后续输出时进行查找,然后使用预训练的词向量对自己词汇表中的词向量进行训练,将词汇表中的词语映射到高维空间中的词向量来完成任务
然后是序列截断、填充和添加特殊标记,要限制输入序列的长度,提高效率,然后将所有序列填充至相同长度方便计算,填充为<PAD>,在序列的开始添加<SOS>与<EOS>进行标记,帮助模型更快识别序列的两端,对于不在词汇表中出现过的词汇添加<UNK>标记
最后就是数据的增强与分割,可以将部分同义词进行删除或替换,增加数据的鲁棒性和多样性。
- 模型训练以及模型验证
本文模型主要基于神经机器翻译,而其基础为编码器-解码器模型,描述的是输入与输出之间的关系,生活中经常会运到,将输入进来的信号转化成一种储存方式储存在某一空间内,输出时再根据转化规则将其对应输出。神经机器翻译也是这个原理,编码器先将某句话转化为一个实数向量,即话里的每个字都转化成不同的数字放在一个向量组里储存起来,然后通过解码器从向量中提取句子所包含的信息,将其输出出来。其中编码器有词嵌入层和中间网络层。词嵌入:输入单词时将每个单词映射到多维实数空间。中间层:对向量进行抽象处理得到中间表示。神经网络按信息输入是否反馈进行分类可以分为:前馈神经网络和反馈神经网络 前馈神经网络中无反馈,任何层的输出都不会影响同级层,包括 CNN,FCN,GAN,反馈神经网络中可以接受反馈信号,具有记忆功能在不同时刻都有不同的状态,信息传播可以是单向传播也可以是双向传播,不断迭代产生输出结果。本模型以RNN为主,本模型主要使用RNN,然而当用循环神经机制翻译序列时若序列长度较小,在20个单词以内,翻译效果较好,随着文本序列的增加,相关评价指标则会下降,所以引入注意力机制,好处在于注意力机制的引入使得不需要把原始文本中所有必要信息压缩到一个向量当中,可以分开储存,提高效率和准确度,传统的Seq2Seq模型解码时仅依赖于最后一个隐藏状态,在处理长序列时效果不佳。
- 评价最终的翻译质量
目前评估翻译质量的一大标准是BLEU,使评价变得快速,便捷,便于人们第一时间得到评价加快研发提升,目前机器评价一般将输出的结果与人工给出的数据库进行一对比,筛选符合的答案进行自动比对,成本较低,但得到结果快速,一目了然。
代码详解
- 环境配置
先把几个必须得库安装上去
进行数据的导入,并且做出标记,即标记数据的两端:开头和结尾,并且对文本中的个别输入词汇进行<UNK>标记,然后将数据进行读取,转化为向量
进行模型的构建,Enco和Deco,提取出文本中词与词之间的联系,并且引入注意力模型,提高模型在解读长文本序列的能力,将中英文进行分词处理
class Encoder(nn.Module):
def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):
super().__init__()
self.hid_dim = hid_dim
self.n_layers = n_layers
self.embedding = nn.Embedding(input_dim, emb_dim)
self.gru = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
self.dropout = nn.Dropout(dropout)
def forward(self, src):
# src = [batch size, src len]
embedded = self.dropout(self.embedding(src))
# embedded = [batch size, src len, emb dim]
outputs, hidden = self.gru(embedded)
# outputs = [batch size, src len, hid dim * n directions]
# hidden = [n layers * n directions, batch size, hid dim]
return outputs, hidden
class Attention(nn.Module):
def __init__(self, hid_dim):
super().__init__()
self.attn = nn.Linear(hid_dim * 2, hid_dim)
self.v = nn.Linear(hid_dim, 1, bias=False)
def forward(self, hidden, encoder_outputs):
# hidden = [1, batch size, hid dim]
# encoder_outputs = [batch size, src len, hid dim]
batch_size = encoder_outputs.shape[0]
src_len = encoder_outputs.shape[1]
hidden = hidden.repeat(src_len, 1, 1).transpose(0, 1)
# hidden = [batch size, src len, hid dim]
energy = torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim=2)))
# energy = [batch size, src len, hid dim]
attention = self.v(energy).squeeze(2)
# attention = [batch size, src len]
return F.softmax(attention, dim=1)
之下是翻译函数,将中间的词向量进行转化为对应的汉字,提高效率可以提高截断词的长度,增加采样训练集的长度,完成后在测试集进行对应翻译
def translate_sentence(sentence, src_vocab, trg_vocab, model, device, max_length=50):
model.eval()
#print(sentence) # 打印sentence的内容
if isinstance(sentence, str):
#tokens = [token.lower() for token in en_tokenizer(sentence)]
tokens = [token for token in en_tokenizer(sentence)]
else:
#tokens = [token.lower() for token in sentence]
tokens = [str(token) for token in sentence]
tokens = ['<bos>'] + tokens + ['<eos>']
src_indexes = [src_vocab[token] for token in tokens]
src_tensor = torch.LongTensor(src_indexes).unsqueeze(0).to(device)
with torch.no_grad():
encoder_outputs, hidden = model.encoder(src_tensor)
trg_indexes = [trg_vocab['<bos>']]
for i in range(max_length):
trg_tensor = torch.LongTensor([trg_indexes[-1]]).to(device)
with torch.no_grad():
output, hidden = model.decoder(trg_tensor, hidden, encoder_outputs)
pred_token = output.argmax(1).item()
trg_indexes.append(pred_token)
if pred_token == trg_vocab['<eos>']:
break
trg_tokens = [trg_vocab.get_itos()[i] for i in trg_indexes]
return trg_tokens[1:-1] # 移除 <bos> 和 <eos>
通过选定合适的截断长度和训练集数目可以有效提高效率,最终得出最终结果
目前存在的问题:翻译效果差,基本以逗号和的为主,很难连成一句话