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

机器翻译的发展历程:基于规则的机器翻译(1950s-1980s)->基于统计的机器翻译(1990s-2000s)->基于神经网络机器翻译(2010s-present)

数据划分

在机器学习和深度学习项目中,数据集通常被划分为三个部分:训练集(Training Set)、开发集(Development Set,也常被称为验证集,Validation Set)和测试集(Test Set)

task-1_terminology.ipynb解释

类 TranslationDataset

__init__ 方法
  1. 初始化数据和术语
  2. self.data = []:初始化一个空列表,用于存储从文件中读取的数据。
  3. with open(filename, 'r', encoding='utf-8') as f::打开文件读取数据,假设文件中的每一行包含一对中英文翻译,使用制表符(\t)分隔。
  4. self.data.append((en, zh)):将读取的中英文对添加到 self.data 列表中。
  5. self.terminology = terminology:存储传入的术语词典。
  6. 创建词汇表
  7. self.en_tokenizer = get_tokenizer('basic_english'):使用 torchtext 提供的基本英文分词器。
  8. self.zh_tokenizer = list:使用字符级分词器来处理中文。
  9. 构建词汇表
  10. en_vocab = Counter(self.terminology.keys()):初始化英文词汇表,确保术语词典中的词被包含。
  11. zh_vocab = Counter():初始化中文词汇表。
  12. 更新词汇表
  13. 遍历数据中的每一对句子,更新英文和中文的词汇表。
  14. 添加特殊符号和术语到词汇表
  15. self.en_vocab 和 self.zh_vocab:添加特殊符号(<pad><sos><eos>)和术语词典中的词,接着添加最常见的10000个词。
  16. self.en_word2idx 和 self.zh_word2idx:创建从词到索引的映射词典。
__len__ 方法
  • 返回数据集的大小。
__getitem__ 方法
  • 获取数据集中指定索引的中英文对,并将其转换为张量(tensor)。
  • 添加特殊符号 <eos> 到每个句子的末尾。
  • 使用词汇表将每个单词转换为索引,默认情况下,如果单词不在词汇表中,使用 <sos> 索引。

collate_fn 函数

  • 这个函数用于在 DataLoader 中进行批处理。
  • 批处理填充
  • en_batch 和 zh_batch:分别存储英文和中文句子的张量。
  • 使用 nn.utils.rnn.pad_sequence 对批次中的序列进行填充,确保每个序列的长度相同。填充值为0(即 <pad> 索引)。
# 定义数据集类
# 修改TranslationDataset类以处理术语
class TranslationDataset(Dataset):
    def __init__(self, filename, terminology):
        self.data = []
        with open(filename, 'r', encoding='utf-8') as f:
            for line in f:
                en, zh = line.strip().split('\t')
                self.data.append((en, zh))

        self.terminology = terminology

        # 创建词汇表,注意这里需要确保术语词典中的词也被包含在词汇表中
        self.en_tokenizer = get_tokenizer('basic_english')
        self.zh_tokenizer = list  # 使用字符级分词

        en_vocab = Counter(self.terminology.keys())  # 确保术语在词汇表中
        zh_vocab = Counter()

        for en, zh in self.data:
            en_vocab.update(self.en_tokenizer(en))
            zh_vocab.update(self.zh_tokenizer(zh))

        # 添加术语到词汇表
        self.en_vocab = ['<pad>', '<sos>', '<eos>'] + list(self.terminology.keys()) + [word for word, _ in en_vocab.most_common(10000)]
        self.zh_vocab = ['<pad>', '<sos>', '<eos>'] + [word for word, _ in zh_vocab.most_common(10000)]

        self.en_word2idx = {word: idx for idx, word in enumerate(self.en_vocab)}
        self.zh_word2idx = {word: idx for idx, word in enumerate(self.zh_vocab)}


    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        en, zh = self.data[idx]
        en_tensor = torch.tensor([self.en_word2idx.get(word, self.en_word2idx['<sos>']) for word in self.en_tokenizer(en)] + [self.en_word2idx['<eos>']])
        zh_tensor = torch.tensor([self.zh_word2idx.get(word, self.zh_word2idx['<sos>']) for word in self.zh_tokenizer(zh)] + [self.zh_word2idx['<eos>']])
        return en_tensor, zh_tensor

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

Encoder 类

__init__ 方法
  • 初始化编码器的各个层和参数:
  • self.embedding:定义一个嵌入层,将输入的索引转换为对应的嵌入向量。
  • self.rnn:定义一个带有多层和 dropout 的 GRU(门控循环单元)层,用于处理嵌入向量。
  • self.dropout:定义一个 dropout 层,用于正则化以防止过拟合。
forward 方法
  • 输入 src(形状为 [batch_size, src_len]),表示批次中的源序列。
  • 经过嵌入层和 dropout 处理,得到嵌入向量 embedded(形状为 [batch_size, src_len, emb_dim])。
  • 经过 GRU 层,得到 outputs(形状为 [batch_size, src_len, hid_dim])和 hidden(形状为 [n_layers, batch_size, hid_dim])。
  • 返回 outputs 和 hidden

Decoder 类

__init__ 方法
  • 初始化解码器的各个层和参数:
  • self.embedding:定义一个嵌入层,将输入的索引转换为对应的嵌入向量。
  • self.rnn:定义一个带有多层和 dropout 的 GRU 层,用于处理嵌入向量。
  • self.fc_out:定义一个全连接层,用于将 GRU 的输出映射到目标词汇表的大小。
  • self.dropout:定义一个 dropout 层,用于正则化。
forward 方法
  • 输入 input(形状为 [batch_size, 1]),表示批次中的目标序列的当前时间步的词索引。
  • 输入 hidden(形状为 [n_layers, batch_size, hid_dim]),表示编码器的隐藏状态。
  • 经过嵌入层和 dropout 处理,得到嵌入向量 embedded(形状为 [batch_size, 1, emb_dim])。
  • 经过 GRU 层,得到 output(形状为 [batch_size, 1, hid_dim])和 hidden(形状为 [n_layers, batch_size, hid_dim])。
  • 经过全连接层,得到预测 prediction(形状为 [batch_size, output_dim])。
  • 返回 prediction 和 hidden

Seq2Seq 类

__init__ 方法
  • 初始化 seq2seq 模型的编码器、解码器和设备(如 CPU 或 GPU)。
forward 方法
  • 输入 src(形状为 [batch_size, src_len])和 trg(形状为 [batch_size, trg_len]),表示批次中的源序列和目标序列。
  • 初始化 outputs 张量,用于存储每个时间步的解码器输出(形状为 [batch_size, trg_len, trg_vocab_size])。
  • 使用编码器对源序列进行编码,得到 hidden(形状为 [n_layers, batch_size, hid_dim])。
  • 初始化解码器的输入为目标序列的起始标记(trg[:, 0].unsqueeze(1))。
  • 对于目标序列的每个时间步,使用解码器生成输出和新的隐藏状态:
  • 使用 teacher_forcing_ratio 决定是否使用真实的目标序列词作为下一时间步的输入(教师强制),或使用解码器的预测。
  • 返回 outputs,即每个时间步的预测结果。
class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):
        super().__init__()
        self.embedding = nn.Embedding(input_dim, emb_dim)
        self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
        self.dropout = nn.Dropout(dropout)

    def forward(self, src):
        # src shape: [batch_size, src_len]
        embedded = self.dropout(self.embedding(src))
        # embedded shape: [batch_size, src_len, emb_dim]
        outputs, hidden = self.rnn(embedded)
        # outputs shape: [batch_size, src_len, hid_dim]
        # hidden shape: [n_layers, batch_size, hid_dim]
        return outputs, hidden

class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):
        super().__init__()
        self.output_dim = output_dim
        self.embedding = nn.Embedding(output_dim, emb_dim)
        self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
        self.fc_out = nn.Linear(hid_dim, output_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, input, hidden):
        # input shape: [batch_size, 1]
        # hidden shape: [n_layers, batch_size, hid_dim]

        embedded = self.dropout(self.embedding(input))
        # embedded shape: [batch_size, 1, emb_dim]

        output, hidden = self.rnn(embedded, hidden)
        # output shape: [batch_size, 1, hid_dim]
        # hidden shape: [n_layers, batch_size, hid_dim]

        prediction = self.fc_out(output.squeeze(1))
        # prediction shape: [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 shape: [batch_size, src_len]
        # trg shape: [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)

        _, hidden = self.encoder(src)

        input = trg[:, 0].unsqueeze(1)  # Start token

        for t in range(1, trg_len):
            output, hidden = self.decoder(input, hidden)
            outputs[:, t, :] = output
            teacher_force = random.random() < teacher_forcing_ratio
            top1 = output.argmax(1)
            input = trg[:, t].unsqueeze(1) if teacher_force else top1.unsqueeze(1)

        return outputs

load_terminology_dictionary

函数定义和参数
  • def load_terminology_dictionary(dict_file)::定义一个函数,接受一个参数 dict_file,表示包含术语词典的文件路径。
初始化术语词典
  • terminology = {}:初始化一个空字典 terminology,用于存储术语词典。
打开文件并读取内容
  • with open(dict_file, 'r', encoding='utf-8') as f::使用 utf-8 编码打开文件进行读取操作。
  • for line in f::逐行读取文件内容。
解析并存储术语对
  • en_term, ch_term = line.strip().split('\t'):去除每行首尾的空白字符,然后使用制表符(\t)将每行分割成英文术语和中文术语。
  • terminology[en_term] = ch_term:将英文术语作为键,中文术语作为值,存储到字典 terminology 中。
返回术语词典
  • return terminology:返回加载好的术语词典。

python # 新增术语词典加载部分 def load_terminology_dictionary(dict_file): terminology = {} with open(dict_file, 'r', encoding='utf-8') as f: for line in f: en_term, ch_term = line.strip().split('\t') terminology[en_term] = ch_term return terminology

主函数

这个函数通过批次数据训练 seq2seq 模型,并返回平均训练损失。主要步骤包括前向传播、损失计算、反向传播、梯度裁剪和参数更新。可以在每个训练轮次(epoch)中调用这个函数以训练模型。

train

参数
  • model:要训练的 seq2seq 模型。
  • iterator:数据加载器,用于迭代数据批次。
  • optimizer:优化器,用于更新模型参数。
  • criterion:损失函数,用于计算预测值和真实值之间的误差。
  • clip:梯度裁剪的阈值,用于防止梯度爆炸。
函数内容
  1. 设置模型为训练模式: python model.train() 这会启用 dropout 和 batch normalization。

  2. 初始化累积损失: python epoch_loss = 0

  3. 迭代数据批次: python for i, (src, trg) in enumerate(iterator): 使用数据加载器 iterator 获取每个批次的源序列 src 和目标序列 trg

  4. 将数据移到设备(CPU 或 GPU): python src, trg = src.to(device), trg.to(device)

  5. 梯度清零: python optimizer.zero_grad()

  6. 前向传播: python output = model(src, trg)

  7. 调整输出和目标形状: python output_dim = output.shape[-1] output = output[:, 1:].contiguous().view(-1, output_dim) trg = trg[:, 1:].contiguous().view(-1)

    • output[:, 1:]:移除 <sos> token。
    • contiguous().view(-1, output_dim):调整 output 的形状以适应损失计算。
    • trg[:, 1:]:移除 <sos> token。
    • contiguous().view(-1):调整 trg 的形状以适应损失计算。
  8. 计算损失: python loss = criterion(output, trg)

  9. 反向传播: python loss.backward()

  10. 梯度裁剪: python torch.nn.utils.clip_grad_norm_(model.parameters(), clip) 这可以防止梯度爆炸。

  11. 更新模型参数: python optimizer.step()

  12. 累积损失: python epoch_loss += loss.item()

返回平均损失
return epoch_loss / len(iterator)

将累积损失除以迭代次数,返回平均每批次的损失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值