入门NLP竞赛

一、Transformer 介绍
基于循环或卷积神经网络的序列到序列建模方法是现存机器翻译任务中的经典方法。然而,它们在建模文本长程依赖方面都存在一定的局限性。

对于卷积神经网络来说,受限的上下文窗口在建模长文本方面天然地存在不足。如果要对长距离依赖进行描述,需要多层卷积操作,而且不同层之间信息传递也可能有损失,这些都限制了模型的能力。

而对于循环神经网络来说,上下文的语义依赖是通过维护循环单元中的隐状态实现的。在编码过程中,每一个时间步的输入建模都涉及到对隐藏状态的修改。随着序列长度的增加,编码在隐藏状态中的序列早期的上下文信息被逐渐遗忘。尽管注意力机制的引入在一定程度上缓解了这个问题,但循环网络在编码效率方面仍存在很大的不足之处。由于编码端和解码端的每一个时间步的隐藏状态都依赖于前一时间步的计算结果,这就造成了在训练和推断阶段的低效。

二、基于 task2 的 baseline 修改代码
# 位置编码
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)

# Transformer
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)

 

 

对于Task3代码对比分析
在新的代码中,修改和添加的部分主要集中在PositionalEncoding类和TransformerModel类的实现。以下是详细的对比和分析:
1. PositionalEncoding类
旧代码:
假设旧代码没有实现PositionalEncoding类,或其实现比较简单,可能使用了一些默认或简化的位置编码方法。
新代码:

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)


优势:
细致的实现:新的PositionalEncoding类实现了基于sin和cos函数的位置编码,并使用了logarithmic scaling来确保不同维度上的编码具有不同的频率。这种方法被Transformer模型广泛使用,能够更好地捕捉序列中的位置信息。
Dropout正则化:在位置编码之后添加了Dropout层,有助于防止过拟合。
灵活性:允许用户指定最大长度和d_model维度,使得位置编码的实现更加灵活和可定制。
2. TransformerModel类
旧代码:
假设旧代码中的Transformer模型实现较为简化,可能缺少一些关键功能或优化。
新代码:

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实现:新的TransformerModel类完整实现了基于PyTorch的Transformer模型,包括编码器和解码器的构建、嵌入层、位置编码、以及输出层。
位置编码:使用了自定义的PositionalEncoding类,为输入序列添加位置信息,增强模型的学习能力。
Masking机制:实现了src_mask和tgt_mask的生成,确保模型在训练和推理时正确处理序列数据的遮蔽(masking),避免信息泄漏。
Embedding层:为源语言和目标语言分别定义了Embedding层,确保输入序列能够正确转换为高维向量表示。
正则化和防止过拟合:在嵌入层之后应用位置编码和Dropout,进一步提高了模型的泛化能力。
总结:
新的代码相较于旧代码,在实现细节和功能上有了显著提升。它引入了更复杂和有效的位置编码方法,完整实现了基于PyTorch的Transformer模型,包括Masking机制、正则化技术等。这些改进使得模型在处理序列数据时更为准确和高效,同时也提高了模型的灵活性和可定制性。通过详细的注释,新的代码更易于理解和修改,适合在实际的机器翻译任务中使用。

 

三、其他上分技巧
最简单的就是调参,将 epochs 调大一点,使用全部训练集,以及调整模型的参数,如head、layers等。如果数据量允许,增加模型的深度(更多的编码器/解码器层)或宽度(更大的隐藏层尺寸),这通常可以提高模型的表达能力和翻译质量,尤其是在处理复杂或专业内容时。

加入术语词典,这是在此竞赛中比较有效的方法,加入术语词典的方法策略也有很多,如:

在模型生成的翻译输出中替换术语,这是最简单的方法

整合到数据预处理流程,确保它们在翻译中保持一致

在模型内部动态地调整术语的嵌入,这涉及到在模型中加入一个额外的层,该层负责查找术语词典中的术语,并为其生成专门的嵌入向量,然后将这些向量与常规的词嵌入结合使用

认真做数据清洗,我们在 Task2 已经提到过当前训练集存在脏数据的问题,会影响我们的模型训练

数据扩增:

回译(back-translation):将源语言文本先翻译成目标语言,再将目标语言文本翻译回源语言,生成的新文本作为额外的训练数据

同义词替换:随机选择句子中的词,并用其同义词替换

使用句法分析和语义解析技术重新表述句子,保持原意不变

将文本翻译成多种语言后再翻译回原语言,以获得多样化翻译

采用更精细的学习率调度策略(baseline我们使用的是固定学习率):

Noam Scheduler:结合了warmup阶段和衰减阶段

Step Decay:最简单的一种学习率衰减策略,每隔一定数量的epoch,学习率按固定比例衰减

Cosine Annealing:学习率随周期性变化,通常从初始值下降到接近零,然后再逐渐上升

自己训练一个小的预训练模型,尽量选择 1B 以下小模型,对 GPU 资源要求比较高,仅仅使用魔搭平台可能就满足不了

将训练集上训练出来的模型拿到开发集(dev dataset)上 finetune 可以提高测试集(test dataset)的得分,因为开发集与测试集的分布比较相近

在开发集和测试集上训一个语言模型,用这个语言模型给训练集中的句子打分,选出一些高分句子

集成学习:训练多个不同初始化或架构的模型,并使用集成方法(如投票或平均)来产生最终翻译。这可以减少单一模型的过拟合风险,提高翻译的稳定性。

改进模型技巧(摘录)
最简单的就是调参,将 epochs 调大一点,使用全部训练集,以及调整模型的参数,如head、layers等。如果数据量允许,增加模型的深度(更多的编码器/解码器层)或宽度(更大的隐藏层尺寸),这通常可以提高模型的表达能力和翻译质量,尤其是在处理复杂或专业内容时。

加入术语词典,这是在此竞赛中比较有效的方法,加入术语词典的方法策略也有很多,如:

在模型生成的翻译输出中替换术语,这是最简单的方法

整合到数据预处理流程,确保它们在翻译中保持一致

在模型内部动态地调整术语的嵌入,这涉及到在模型中加入一个额外的层,该层负责查找术语词典中的术语,并为其生成专门的嵌入向量,然后将这些向量与常规的词嵌入结合使用

认真做数据清洗,我们在 Task2 已经提到过当前训练集存在脏数据的问题,会影响我们的模型训练

数据扩增:

回译(back-translation):将源语言文本先翻译成目标语言,再将目标语言文本翻译回源语言,生成的新文本作为额外的训练数据

同义词替换:随机选择句子中的词,并用其同义词替换

使用句法分析和语义解析技术重新表述句子,保持原意不变

将文本翻译成多种语言后再翻译回原语言,以获得多样化翻译

采用更精细的学习率调度策略(baseline我们使用的是固定学习率):

Noam Scheduler:结合了warmup(预热)阶段和衰减阶段

Step Decay:最简单的一种学习率衰减策略,每隔一定数量的epoch,学习率按固定比例衰减

Cosine Annealing:学习率随周期性变化,通常从初始值下降到接近零,然后再逐渐上升

自己训练一个小的预训练模型,尽量选择 1B 以下小模型,对 GPU 资源要求比较高,仅仅使用魔搭平台可能就满足不了

将训练集上训练出来的模型拿到开发集(dev dataset)上 finetune 可以提高测试集(test dataset)的得分,因为开发集与测试集的分布比较相近

在开发集和测试集上训一个语言模型,用这个语言模型给训练集中的句子打分,选出一些高分句子

集成学习:训练多个不同初始化或架构的模型,并使用集成方法(如投票或平均)来产生最终翻译。这可以减少单一模型的过拟合风险,提高翻译的稳定性。

posted @ 2024-07-20 23:56  叶谨之  阅读(79)  评论(0)    收藏  举报