学习目标:了解Transformer并基于baseline进行理解
一.介绍Transformer
1.概要:
Transformer的主要组件包括编码器(Encoder)、解码器(Decoder)和注意力层。其核心是利用多头自注意力机制(Multi-Head Self-Attention),使每个位置的表示不仅依赖于当前位置,还能够直接获取其他位置的表示。自从提出以来,Transformer模型在机器翻译、文本生成等自然语言处理任务中均取得了突破性进展,成为NLP领域新的主流模型。
2.模型框架
从宏观角度来看,Transformer的编码器是由多个相同的层叠加而成的,每个层都有两个子层(子层表示为sublayer)。第⼀个子层是多头自注意力(multi-head self-attention)汇聚;第二个子层是基于位置的前馈网络(positionwise feed-forward network)。
主要涉及到如下几个模块
1)嵌入表示层
对于输入文本序列,先通过一个输入嵌入层(Input Embedding)将每个单词转换为其相对应的向量表示。
2)注意力层
自注意力(Self-Attention)操作是基于 Transformer 的机器翻译模型的基本操作,在源语言的编码和目标语言的生成中频繁地被使用以建模源语言、目标语言任意两个单词之间的依赖关系。
3)前馈层
前馈层接受自注意力子层的输出作为输入,并通过一个带有 Relu 激活函数的两层全连接网络对输入进行更加复杂的非线性变换。实验证明,这一非线性变换会对模型最终的性能产生十分重要的影响。
增大前馈子层隐状态的维度有利于提升最终翻译结果的质量,因此,前馈子层隐状态的维度一般比自注意力子层要大。
4)残差连接与层归一化
残差连接主要是指使用一条直连通道直接将对应子层的输入连接到输出上去,从而避免由于网络过深在优化过程中潜在的梯度消失问题
层归一化技术可以有效地缓解优化过程中潜在的不稳定、收敛速度慢等问题。
5)编码器和解码器结构
这主要是因为在翻译的过程中,编码器端主要用于编码源语言序列的信息,而这个序列是完全已知的,因而编码器仅需要考虑如何融合上下文语义信息即可。而解码端则负责生成目标语言序列,这一生成过程是自回归的,即对于每一个单词的生成过程,仅有当前单词之前的目标语言序列是可以被观测的,因此这一额外增加的掩码是用来掩盖后续的文本信息,以防模型在训练阶段直接看到后续的文本序列进而无法得到有效地训练。
解码器端还额外增加了一个多头注意力(Multi-Head Attention)模块,使用交叉注意力(Cross-attention)方法,同时接收来自编码器端的输出以及当前 Transformer 块的前一个掩码注意力层的输出
二.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)
在主函数里定义 Transformer 模型调用:
model = TransformerModel(src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)
三.提分尝试
1.调整轮数
增大epoch,从5轮增大到15轮,分数明显提升,增到20轮就略微提升,20轮开始就有所下降,得分如图
2.清理数据
尝试使用正则匹配法加手动清洗训练样本中的脏数据,如“Joey. (掌声) (掌声) 乔伊”、“Thank you. (马嘶声) 谢谢你们”等这种声音词,
跑分略有所提高,翻译文本少了许多了(掌声),这种,但是多出来许多空白行
3.加入术语词典
术语词典提分不明显,因为测试集专有名词很少,对翻译影响极低
# # 存储成字典
def load_dictionary(dict_path):
term_dict = {}
with open(dict_path, 'r', encoding='utf-8') as f:
data = f.read()
data = data.strip().split('\n')
source_term = [line.split('\t')[0] for line in data]
target_term = [line.split('\t')[1] for line in data]
for i in range(len(source_term)):
term_dict[source_term[i]] = target_term[i]
return term_dict
def post_process_translation(translation, term_dict):
""" 使用术语词典进行后处理 """
translated_words = [term_dict.get(word, word) for word in translation]
return "".join(translated_words)
# # 加载你的术语词典
dict_path = '../dataset/en-zh.dic' # 这应该是你的术语词典文件路径
term_dict = load_dictionary(dict_path)
save_dir = '../results/submit_add_dict_wash.txt'
with open(save_dir, 'w') as f:
translated_sentences = []
for batch in test_loader: # 遍历所有数据
src, _ = batch
src = src.to(DEVICE)
translated = translate_sentence(src[0], en_vocab, zh_vocab, model, DEVICE) #翻译结果
results = post_process_translation(translated, term_dict)
results = "".join(results)
f.write(results + '\n') # 将结果写入文件
#break
print(f"翻译完成,结果已保存到{save_dir}")
4.调整参数
D_MODEL = 256 # 增加模型维度
NHEAD = 8
NUM_ENCODER_LAYERS = 7 # 增加编码器层数
NUM_DECODER_LAYERS = 7 # 增加解码器层数
DIM_FEEDFORWARD = 1024 # 增加前馈网络维度
DROPOUT = 0.1 # 调整Dropout率
这样的改动是,训练时间翻倍,得分得到提高