DatawhaleAI夏令营第二期NLP方向Task3笔记

DatawhaleAI夏令营第二期NLP方向Task3笔记


前言

一、transformer模型相关

Transformer是一种机器学习模型架构,特别是在自然语言处理(NLP)领域中表现出色。它由Vaswani等人于2017年提出,是一种基于注意力机制(attention mechanism)的深度学习模型。Transformer 的设计主要解决了传统循环神经网络(RNN)在处理长距离依赖性时性能下降的问题,同时也提高了并行计算的效率。

Transformer 主要包含以下几个关键点和组件:

自注意力机制(Self-Attention):Transformer 使用自注意力机制来捕捉输入序列中各个位置之间的依赖关系,而不像RNN那样按顺序处理。自注意力机制允许每个位置的输出受到整个输入序列所有位置的加权组合影响,从而更好地建模长距离依赖性。

位置编码(Positional Encoding):为了保留输入序列的位置信息,Transformer 在输入嵌入向量中添加了位置编码,这些编码向量表示了输入序列中每个位置的绝对或相对位置。

多头注意力(Multi-Head Attention):为了增加模型的表达能力和学习多种不同的特征表示,Transformer 在每个注意力层中使用多个并行的注意力头来进行自注意力计算。

前馈神经网络(Feedforward Neural Networks):Transformer 的每个注意力子层后都连接了一个全连接的前馈神经网络,用于对特征进行进一步的非线性变换和组合。

残差连接和层归一化(Residual Connections and Layer Normalization):为了更好地训练深层网络并加速收敛,Transformer 在每个子层之间应用了残差连接,并使用层归一化来稳定训练过程。

编码器-解码器结构(Encoder-Decoder Architecture):Transformer 可以用于编码器-解码器任务,其中编码器用于处理输入序列并生成其表示,解码器则基于编码器的输出和先前生成的部分目标序列来生成目标序列。

Transformer 模型的优点包括其能够并行计算、处理长距离依赖性和灵活的模块化设计,使其成为许多自然语言处理任务(如机器翻译、语言建模、文本生成等)的首选模型架构。它摒弃了循环结构,并完全通过注意力机制完成对源语言序列和目标语言序列全局依赖的建模。在抽取每个单词的上下文特征时,Transformer 通过自注意力机制(self-attention)衡量上下文中每一个单词对当前单词的重要程度,在这个过程当中没有任何的循环单元参与计算。这种高度可并行化的编码过程使得模型的运行变得十分高效。

二、本次Task的目的

1、了解Transformer 模型的基本架构:

在这里插入图片描述
语句转换过程如下:
输入表示:
源语句和目标语句首先被转换为词嵌入(word embeddings)。这些词嵌入向量捕捉了单词的语义信息和上下文信息。

位置编码:
在词嵌入的基础上,加入位置编码(position embeddings)。这是因为Transformer没有循环结构,所以需要位置编码来表达单词在句子中的位置信息,通常使用正弦和余弦函数生成位置编码。
编码器堆叠:
编码器(Encoder):
源语句的词嵌入与位置编码作为输入,经过多个编码器层处理。每个编码器层包含两个子层:自注意力机制(Self-Attention):帮助模型理解输入句子中不同位置的单词之间的依赖关系,即每个单词与句子中其他单词的关联程度。前馈神经网络(Feedforward Neural Network):对每个位置的词向量进行独立的转换,增加模型的表达能力。
解码器堆叠:
解码器(Decoder):
目标语句的词嵌入与位置编码作为输入,经过多个解码器层处理。每个解码器层包含三个子层:自注意力机制(Self-Attention):帮助解码器理解已生成的部分目标语句之间的依赖关系。编码器-解码器注意力机制(Encoder-Decoder Attention):帮助解码器关注源语句中与当前生成单词相关的部分,确保翻译的准确性。前馈神经网络(Feedforward Neural Network):类似编码器中的作用,对每个位置的词向量进行转换。
输出层:
在解码器的最后一个位置,输出经过一个线性变换并应用softmax函数的结果,以生成最终的目标语句的词概率分布。每个位置的词概率表示模型对该位置生成哪个词的置信度。
训练过程:
在训练过程中,模型通过最小化目标语句的交叉熵损失来优化参数,使得模型能够生成与目标语句最接近的概率分布。
生成过程:

在生成过程中,模型逐步生成目标语句的每个单词,每次选择概率最高的单词作为当前位置的输出,然后将该单词作为下一个解码器层的输入,直到生成结束标记或达到最大长度。

2、应用Tranformer模型,训练所给数据集,并输出提交文件。

二、应用步骤

1.导入baseline

2.修改baseline

添加数据清洗函数

代码如下:

#数据清洗
def unicodeToAscii(text):
    return ''.join(c for c in unicodedata.normalize('NFD', text) if unicodedata.category(c) != 'Mn')

def preprocess_en(text):
    text = unicodeToAscii(text.strip())
    text = contractions.fix(text)
    text = re.sub(r'\([^)]*\)', '', text)
    text = re.sub(r"[^a-zA-Z0-9.!?]+", r" ", text)  # 保留数字
    return text
def preprocess_zh(text):
    # 去除(掌声)这些脏数据
    text = re.sub(r'\([^)]*\)', '', text)
    text = re.sub(r"[^\u4e00-\u9fa5,。!?0-9]", "", text)  # 保留数字
    return text

修改load_data函数,应用数据清洗

代码如下:

# 数据加载函数
def load_data(train_path: str, dev_en_path: str, dev_zh_path: str, test_en_path: str):
    # 读取训练数据
    #term=load_terminology_dictionary('../dataset/en-zh.dic')
    train_data = read_data(train_path)
    train_en=[]
    train_zh=[]
    for line in train_data:
        en, zh = line.strip().split('\t')
        #清洗英文
        en = preprocess_en(en)
        #print(en)
        #清洗中文
        zh = preprocess_zh(zh)
        #print(zh)
        train_en.append(en)
        train_zh.append(zh)
               
    
    # 读取开发集和测试集
    dev_en = read_data(dev_en_path)
    deven=[]
    for line in dev_en:
        en=line.strip()
        en=preprocess_en(en)
        deven.append(en)
    dev_zh = read_data(dev_zh_path)
    devzh=[]
    for line in dev_zh:
        zh=line.strip()
        zh=preprocess_zh(zh)
        devzh.append(zh)
    test_en = read_data(test_en_path)
    testen=[]
    for line in test_en:
        en=line.strip()
        en=preprocess_en(en)
        testen.append(en)
     
    # 预处理数据
    train_processed = preprocess_data(train_en, train_zh)
    #print(train_processed)
    dev_processed = preprocess_data(deven, devzh)
    #print(dev_processed)
    test_processed = [(en_tokenizer(en.lower())[:MAX_LENGTH], []) for en in testen if en.strip()]

    # 构建词汇表
    global en_vocab, zh_vocab,test_dataset,dev_dataset
    en_vocab, zh_vocab = build_vocab(train_processed)
    #print(en_vocab)
    #print(zh_vocab)
    # 创建数据集
    train_dataset = TranslationDataset(train_processed, en_vocab, zh_vocab)
    dev_dataset = TranslationDataset(dev_processed, en_vocab, zh_vocab)
    
    test_dataset = TranslationDataset(test_processed, en_vocab, zh_vocab)
    
    from torch.utils.data import Subset

    # 假设你有10000个样本,你只想用前1000个样本进行测试
    indices = list(range(148300))
    train_dataset = Subset(train_dataset, indices)

    # 创建数据加载器
    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_fn, drop_last=True)
    dev_loader = DataLoader(dev_dataset, batch_size=BATCH_SIZE, collate_fn=collate_fn, drop_last=True)
    test_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, drop_last=True)

    return train_loader, dev_loader, test_loader, en_vocab, zh_vocab

模型构建代码(与seq2seq不同)

代码如下:

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)

设置样本量和迭代次数

将样本量设为全集,迭代次数设置到20(不能过高),过高会过拟合,影响模型效果,这是经过多次训练后得出的结论。

3、运行代码进行训练

Epoch: 01 | Time: 4m 12s
	Train Loss: 6.321 | Train PPL: 556.265
	 Val. Loss: 6.186 |  Val. PPL: 485.970
Epoch: 02 | Time: 4m 11s
	Train Loss: 5.778 | Train PPL: 323.188
	 Val. Loss: 5.906 |  Val. PPL: 367.190
Epoch: 03 | Time: 4m 12s
	Train Loss: 5.546 | Train PPL: 256.152
	 Val. Loss: 5.697 |  Val. PPL: 298.002
Epoch: 04 | Time: 4m 11s
	Train Loss: 5.380 | Train PPL: 216.972
	 Val. Loss: 5.549 |  Val. PPL: 257.048
Epoch: 05 | Time: 4m 12s
	Train Loss: 5.252 | Train PPL: 190.932
	 Val. Loss: 5.438 |  Val. PPL: 229.951
Epoch: 06 | Time: 4m 11s
	Train Loss: 5.142 | Train PPL: 171.074
	 Val. Loss: 5.335 |  Val. PPL: 207.419
Epoch: 07 | Time: 4m 11s
	Train Loss: 5.049 | Train PPL: 155.794
	 Val. Loss: 5.248 |  Val. PPL: 190.134
Epoch: 08 | Time: 4m 11s
	Train Loss: 4.968 | Train PPL: 143.682
	 Val. Loss: 5.165 |  Val. PPL: 175.101
Epoch: 09 | Time: 4m 11s
	Train Loss: 4.897 | Train PPL: 133.836
	 Val. Loss: 5.126 |  Val. PPL: 168.421
Epoch: 10 | Time: 4m 12s
	Train Loss: 4.835 | Train PPL: 125.839
	 Val. Loss: 5.076 |  Val. PPL: 160.118
Epoch: 11 | Time: 4m 12s
	Train Loss: 4.781 | Train PPL: 119.213
	 Val. Loss: 5.004 |  Val. PPL: 148.980
Epoch: 12 | Time: 4m 12s
	Train Loss: 4.732 | Train PPL: 113.576
	 Val. Loss: 4.961 |  Val. PPL: 142.754
Epoch: 13 | Time: 4m 11s
	Train Loss: 4.687 | Train PPL: 108.548
	 Val. Loss: 4.926 |  Val. PPL: 137.807
Epoch: 14 | Time: 4m 12s
	Train Loss: 4.646 | Train PPL: 104.175
	 Val. Loss: 4.875 |  Val. PPL: 131.033
Epoch: 15 | Time: 4m 12s
	Train Loss: 4.608 | Train PPL: 100.298
	 Val. Loss: 4.849 |  Val. PPL: 127.647
Epoch: 16 | Time: 4m 12s
	Train Loss: 4.573 | Train PPL:  96.814
	 Val. Loss: 4.814 |  Val. PPL: 123.284
Epoch: 17 | Time: 4m 12s
	Train Loss: 4.542 | Train PPL:  93.847
	 Val. Loss: 4.788 |  Val. PPL: 120.017
Epoch: 18 | Time: 4m 11s
	Train Loss: 4.511 | Train PPL:  90.980
	 Val. Loss: 4.771 |  Val. PPL: 118.007
Epoch: 19 | Time: 4m 11s
	Train Loss: 4.484 | Train PPL:  88.587
	 Val. Loss: 4.751 |  Val. PPL: 115.680
Epoch: 20 | Time: 4m 12s
	Train Loss: 4.462 | Train PPL:  86.671
	 Val. Loss: 4.730 |  Val. PPL: 113.306
训练完成!模型已保存到:../model/best-model_transformer.pt

4.进行测试集的翻译并输出

代码如下:

# 加载最佳模型
model.load_state_dict(torch.load('../model/best-model_transformer.pt'))
save_dir = '../results/submit_task3.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 = "".join(translated)
        f.write(results + '\n')  # 将结果写入文件
    print(f"翻译完成,结果已保存到{save_dir}")

5. 提交评分

总结

经过以上步骤,也是将评分提高到10分台了,仍有许多提升空间。

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值