【Datawhale AI 夏令营】基于术语词典干预的机器翻译挑战赛

OovoOOnO 2024-07-31 10:01:02 阅读 59

赛事背景:

        目前神经机器翻译技术已经取得了很大的突破,但在特定领域或行业中,由于机器翻译难以保证术语的一致性,导致翻译效果还不够理想。对于术语名词、人名地名等机器翻译不准确的结果,可以通过术语词典进行纠正,避免了混淆或歧义,最大限度提高翻译质量。

赛事任务:

        基于术语词典干预的机器翻译挑战赛选择以英文为源语言,中文为目标语言的机器翻译。本次大赛除英文到中文的双语数据,还提供英中对照的术语词典。参赛队伍需要基于提供的训练数据样本从多语言机器翻译模型的构建与训练,并基于测试集以及术语词典,提供最终的翻译结果。

赛题数据:

赛题数据:

训练集:双语数据 - 中英14万余双语句对

开发集:英中1000双语句对

测试集:英中1000双语句对

术语词典:英中2226条

流程:

1.环境配置:

在魔搭平台上安装下列安装包:

torchtext :是一个用于自然语言处理(NLP)任务的库,它提供了丰富的功能,包括数据预处理、词汇构建、序列化和批处理等,特别适合于文本分类、情感分析、机器翻译等任务。

jieba是一个中文分词库,用于将中文文本切分成有意义的词语。

sacrebleu:用于评估机器翻译质量的工具,主要通过计算BLEU(Bilingual Evaluation Understudy)得分来衡量生成文本与参考译文之间的相似度。

!pip install torchtext

!pip install jieba

!pip install sacrebleu

spacy:是一个强大的自然语言处理库,支持70+语言的分词与训练

        需要注意的是,使用命令!python -m spacy download en_core_web_trf安装 en_core_web_sm 语言包非常的慢,经常会安装失败,这里我们可以离线安装。将en_core_web_trf下载到dataset文件夹中,生成压缩包再上传即可。

2.数据处理:

        1.分词。每个词素带有部分语句的信息,便于形成向量。

        2.构建词汇表和词向量。

                词汇表:从训练数据中收集所有出现过的词汇,构建词汇表,并为每个词分配一个唯一                                的索引。

                词向量:使用预训练的词向量或自己训练词向量,将词汇表中的词映射到高维空间中的                                向量,以捕捉语义信息。

        def build_vocab(data: List[Tuple[List[str], List[str]]]):

    en_vocab = build_vocab_from_iterator(

        (en for en, _ in data),

        specials=['<unk>', '<pad>', '<bos>', '<eos>']

    )

    zh_vocab = build_vocab_from_iterator(

        (zh for _, zh in data),

        specials=['<unk>', '<pad>', '<bos>', '<eos>']

    )

    en_vocab.set_default_index(en_vocab['<unk>'])

    zh_vocab.set_default_index(zh_vocab['<unk>'])

    return en_vocab, zh_vocab

 

        3.序列截断和填充,使每个句子长度相同,便于处理。

def collate_fn(batch):

    en_batch, zh_batch = [], []

    for en_item, zh_item in batch:

        en_batch.append(en_item)

        zh_batch.append(zh_item)

    

    en_batch = nn.utils.rnn.pad_sequence(en_batch, padding_value=0, batch_first=True)

    zh_batch = nn.utils.rnn.pad_sequence(zh_batch, padding_value=0, batch_first=True)

    

    return en_batch, zh_batch

3.模型构建:

        有两种模型可使用,分别是Sep2Sep模型和Transformer模型。

       1. Sep2Sep模型:

这是把编码器和解码器组合在一起的完整翻译模型。这个模型用编码器理解英语句子,将英语句子分词后形成包含句子全部信息的向量hm,再将向量Hm送到解码器,用解码器一个字一个字地生成中文翻译。

通过循环网络对源语言文本进行编码,并生成目标语言翻译结果的过程十分简单。然而,它仅仅使用一个定长的向量 hm 编码整个源语言序列。这对于较短的源语言文本没有什么问题,但随着文本序列长度的逐渐加长,单一的一个向量  hm可能不足以承载源语言序列当中的所有信息,导致处理长序列时效果不佳,因此需引入注意力机制。注意力机制允许解码器在生成每个输出词时,关注编码器产生的所有中间状态,从而更好地利用源序列的信息。具体来说,给定源语言序列经过编码器输出的向量序列 h1,h2,h3......hm,注意力机制旨在依据解码端翻译的需要,自适应地从这个向量序列中查找对应的信息。

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)

class Decoder(nn.Module):

    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout, attention):

        super().__init__()

        self.output_dim = output_dim

        self.hid_dim = hid_dim

        self.n_layers = n_layers

        self.attention = attention

        

        self.embedding = nn.Embedding(output_dim, emb_dim)

        self.gru = nn.GRU(hid_dim + emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)

        self.fc_out = nn.Linear(hid_dim * 2 + emb_dim, output_dim)

        self.dropout = nn.Dropout(dropout)

        

    def forward(self, input, hidden, encoder_outputs):

        # input = [batch size, 1]

        # hidden = [n layers, batch size, hid dim]

        # encoder_outputs = [batch size, src len, hid dim]

        

        input = input.unsqueeze(1)

        embedded = self.dropout(self.embedding(input))

        # embedded = [batch size, 1, emb dim]

        

        a = self.attention(hidden[-1:], encoder_outputs)

        # a = [batch size, src len]

        

        a = a.unsqueeze(1)

        # a = [batch size, 1, src len]

        

        weighted = torch.bmm(a, encoder_outputs)

        # weighted = [batch size, 1, hid dim]

        

        rnn_input = torch.cat((embedded, weighted), dim=2)

        # rnn_input = [batch size, 1, emb dim + hid dim]

        

        output, hidden = self.gru(rnn_input, hidden)

        # output = [batch size, 1, hid dim]

        # hidden = [n layers, batch size, hid dim]

        

        embedded = embedded.squeeze(1)

        output = output.squeeze(1)

        weighted = weighted.squeeze(1)

        

        prediction = self.fc_out(torch.cat((output, weighted, embedded), dim=1))

        # prediction = [batch size, output dim]

        

        return prediction, hidden

class Seq2Seq(nn.Module):

    def __init__(self, encoder, decoder, device):

        super().__init__()

        self.encoder = encoder

        self.decoder = decoder

        self.device = device

        

    def forward(self, src, trg, teacher_forcing_ratio=0.5):

        # src = [batch size, src len]

        # trg = [batch size, trg len]

        

        batch_size = src.shape[0]

        trg_len = trg.shape[1]

        trg_vocab_size = self.decoder.output_dim

        

        outputs = torch.zeros(batch_size, trg_len, trg_vocab_size).to(self.device)

        encoder_outputs, hidden = self.encoder(src)

        

        input = trg[:, 0]

        

        for t in range(1, trg_len):

            output, hidden = self.decoder(input, hidden, encoder_outputs)

            outputs[:, t] = output

            teacher_force = random.random() < teacher_forcing_ratio

            top1 = output.argmax(1)

            input = trg[:, t] if teacher_force else top1

        

        return outputs

        初始化Sep2Sep模型:

def initialize_model(input_dim, output_dim, emb_dim, hid_dim, n_layers, dropout, device):

    attn = Attention(hid_dim)

    enc = Encoder(input_dim, emb_dim, hid_dim, n_layers, dropout)

    dec = Decoder(output_dim, emb_dim, hid_dim, n_layers, dropout, attn)

    model = Seq2Seq(enc, dec, device).to(device)

    return model

        2.Transformer模型:

在Sep2Sep模型中,编码端和解码端的每一个时间步的隐藏状态都依赖于前一时间步的计算结果,导致计算效率低。Transformer模型摒弃了循环结构,并完全通过注意力机制完成对源语言序列和目标语言序列全局依赖的建模。在抽取每个单词的上下文特征时,Transformer 通过自注意力机制(self-attention)衡量上下文中每一个单词对当前单词的重要程度。两个不同模型的区别类似与串行进位加法器和超前进位加法器的区别。

自注意力实例:

Transformer模型:

由上图可知,Transfomer 模型不再使用基于循环的方式建模文本输入,序列中不再有任何信息能够提示模型单词之间的相对位置关系。在送入编码器端建模其上下文语义之前,一个非常重要的操作是在词嵌入中加入位置编码(Positional Encoding)这一特征。可以利用三角函数对位置进行编码,既不会破坏语义信息,也可以体现不同词语的相对位置关系。

class PositionalEncoding(nn.Module):

    def __init__(self, d_model, dropout=0.1, max_len=5000):

        super(PositionalEncoding, self).__init__()

        self.dropout = nn.Dropout(p=dropout)

        pe = torch.zeros(max_len, d_model)

        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)

        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

        pe[:, 0::2] = torch.sin(position * div_term)

        pe[:, 1::2] = torch.cos(position * div_term)

        pe = pe.unsqueeze(0).transpose(0, 1)

        self.register_buffer('pe', pe)

    def forward(self, x):

        x = x + self.pe[:x.size(0), :]

        return self.dropout(x)

class TransformerModel(nn.Module):

    def __init__(self, src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout):

        super(TransformerModel, self).__init__()

        self.transformer = nn.Transformer(d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)

        self.src_embedding = nn.Embedding(len(src_vocab), d_model)

        self.tgt_embedding = nn.Embedding(len(tgt_vocab), d_model)

        self.positional_encoding = PositionalEncoding(d_model, dropout)

        self.fc_out = nn.Linear(d_model, len(tgt_vocab))

        self.src_vocab = src_vocab

        self.tgt_vocab = tgt_vocab

        self.d_model = d_model

    def forward(self, src, tgt):

        # 调整src和tgt的维度

        src = src.transpose(0, 1)  # (seq_len, batch_size)

        tgt = tgt.transpose(0, 1)  # (seq_len, batch_size)

        src_mask = self.transformer.generate_square_subsequent_mask(src.size(0)).to(src.device)

        tgt_mask = self.transformer.generate_square_subsequent_mask(tgt.size(0)).to(tgt.device)

        src_padding_mask = (src == self.src_vocab['<pad>']).transpose(0, 1)

        tgt_padding_mask = (tgt == self.tgt_vocab['<pad>']).transpose(0, 1)

        src_embedded = self.positional_encoding(self.src_embedding(src) * math.sqrt(self.d_model))

        tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt) * math.sqrt(self.d_model))

        output = self.transformer(src_embedded, tgt_embedded,

                                  src_mask, tgt_mask, None, src_padding_mask, tgt_padding_mask, src_padding_mask)

        return self.fc_out(output).transpose(0, 1)

初始化Transformer模型:

def initialize_model(src_vocab, tgt_vocab, d_model=512, nhead=8, num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048, dropout=0.1):

    model = TransformerModel(src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)

    return model

4.评分:

BLEU(Bilingual Evaluation Understudy)是一种广泛使用的机器翻译评估方法。它通过比较机器翻译的结果与人工翻译的参考文本来评估翻译质量。

BLEU工作:

        1.精确度检查:它会看模型翻译中的词(或短语)有多少出现在了人工翻译中;

        2.完整性检查:它也会确保模型的翻译不会太短。不能只翻对了一个词就完事儿了;

        3.长度惩罚:如果机器翻译比人工翻译短太多或长太多,分数会降低;

        4.N-gram匹配:它不仅看单个词,还会看词组。比如"人工智能"这四个字在一起出现,比单独出现"人"、"工"、"智"、"能"要好。

最后,BLEU会给出一个0到100之间的分数。分数越高,说明机器翻译越接近人工翻译,质量越好。

通过这种评分方式,我们可以客观地评估我们的翻译模型性能,并且可以用它来比较不同模型或者跟踪同一个模型在训练过程中的进步。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。