(十三)T5是如何计算损失的

一、概述

T5 使用常规交叉熵损失(与任何语言模型一样)。

假设您正在微调 T5 以进行翻译,并且您有以下训练示例:

 
* source sentence: "hello how are you"
* target sentence: "salut comment ça-va"

首先,需要使用 对模型的句子进行标记。假设每个单词都被标记化为一个标记,并且我们还添加了 T5 的特殊标记(即 </s> - 它表示序列的结束),我们为模型提供以下输入:T5Tokenizer

 
* input tokens = [hello, how, are, you, </s>]
* label tokens = [salut, comment, ça, -, va, </s>]

当然,我们不会将这些标记作为文本提供给模型,而是以整数 ID 的形式提供,这些 ID 指的是嵌入矩阵中的行索引,因此实际输入将如下所示:

 
* input_ids = [21820, 149, 33, 25, 1]
* labels = [20239, 1670, 3664, 18, 900, 1]

在这种情况下,您首先向 T5 的编码器提供,该编码器会将其转换为形状的张量。接下来,T5 的解码器将为目标序列的每个标记预测正确的下一个标记。具体如下:input_ids(batch_size, seq_len, hidden_size)

 
      salut         comment      ça          -       va   </s>       => label tokens

      20239          1670        3664        18      900    1        => labels

----------------------------------------------------------------------------------------------                   
                                 DECODER 
----------------------------------------------------------------------------------------------   

         0            20239      1670      3664   18  900  => decoder_input_ids                         

decoder_start_token   salut     comment     ça    -    va  => decoder input tokens

换句话说,我们用一个特殊的标记(解码器开始标记 - 对于 T5 来说是填充标记,索引为 0)在解码器输入前面,然后解码器需要(并行)预测:

  • 解码器启动令牌后面的令牌是“Salut”。在这里,我们计算模型预测与目标标记(即“致敬”)之间的交叉熵损失。
  • “Salut”后面的标记是“comment”。在这里,我们计算模型预测和目标标记(即“注释”)之间的交叉熵损失。
  • “comment”后面的标记是“ça”。在这里,我们计算模型预测和目标标记(即“ça”)之间的交叉熵损失。
  • 等。
  • “va”后面的令牌是“</s>”(意思是序列末尾或EOS令牌)。在这里,我们计算模型预测与目标标记(“</s>”)之间的交叉熵损失。

法典 389,这是一次性完成的,即通过将模型的对数(形状为 batch_size、seq_len、vocab_size))与地面真值标签进行比较:

loss = loss_fct(lm_logits.view(-1, lm_logits.size(-1)), labels.view(-1))

引用: What is loss function for T5 - Models - Hugging Face Forums

二、详细讲解

import torch
from torch import nn
from transformers import T5ForConditionalGeneration, T5Tokenizer

# 修改后的自定义损失函数
class CustomLoss(nn.Module):
    def __init__(self):
        super(CustomLoss, self).__init__()
        self.loss_fn = nn.CrossEntropyLoss(reduction='none')

    def forward(self, outputs, labels):
        logits = outputs.logits
        batch_size, seq_len, vocab_size = logits.size()

        # 获取每个标签序列的实际长度(去掉pad)
        label_lengths = (labels != tokenizer.pad_token_id).sum(dim=1)

        # 计算权重
        weights = torch.zeros_like(labels, dtype=torch.float)
        for i, length in enumerate(label_lengths):
            length = length.item()
            weights[i, :length] = torch.arange(length + 1, 1, -1, dtype=torch.float)

        # 计算损失
        loss = self.loss_fn(logits.view(-1, vocab_size), labels.view(-1))
        loss = loss.view(batch_size, seq_len)
        
        # 应用权重
        weighted_loss = loss * weights
        
        # 计算平均损失
        weighted_loss = weighted_loss.sum() / weights.sum()
        
        return weighted_loss

# 加载模型和分词器
model_name = 't5-small'
model = T5ForConditionalGeneration.from_pretrained(model_name)
tokenizer = T5Tokenizer.from_pretrained(model_name)

# 示例训练数据
train_data = [
    {'input_text': 'translate English to French: Hello, how are you?', 'target_text': 'Bonjour, comment ça va?'},
    # 添加更多训练数据...
]

# 训练函数
def train(model, tokenizer, custom_loss_fn, train_data, epochs=3, lr=1e-4):
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    
    for epoch in range(epochs):
        model.train()
        total_loss = 0
        for batch in train_data:
            inputs = tokenizer(batch['input_text'], return_tensors='pt', padding=True, truncation=True).input_ids
            labels = tokenizer(batch['target_text'], return_tensors='pt', padding=True, truncation=True).input_ids

            outputs = model(input_ids=inputs, labels=labels)
            loss = custom_loss_fn(outputs, labels)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()

        avg_loss = total_loss / len(train_data)
        print(f'Epoch {epoch + 1}, Loss: {avg_loss}')

# 初始化自定义损失函数
custom_loss_fn = CustomLoss()

# 训练模型
train(model, tokenizer, custom_loss_fn, train_data)

模型输出 logits

在 T5 模型中,输出的 logits 张量通常有以下形状:

logits.shape -> (batch_size, seq_len, vocab_size)
  • batch_size:一个批次中的样本数量。
  • seq_len:每个样本的序列长度。
  • vocab_size:词汇表的大小,表示可能的词汇数。

真实标签 labels

真实标签 labels 张量的形状为:

labels.shape -> (batch_size, seq_len)
  • batch_size:一个批次中的样本数量。
  • seq_len:每个样本的序列长度。

计算交叉熵损失时的维度转换

为了计算交叉熵损失,我们需要将 logitslabels 转换为合适的形状。具体步骤如下:

  1. 转换 logits 的形状

    交叉熵损失函数期望 logits 的形状为 (N, C),其中 N 是样本数,C 是每个样本的类别数。对于序列任务,这里的 N 等于 batch_size * seq_lenC 等于 vocab_size

    因此,我们需要将 logits 的形状从 (batch_size, seq_len, vocab_size) 转换为 (batch_size * seq_len, vocab_size)

     
    logits = logits.view(-1, vocab_size)
  2. 转换 labels 的形状

    交叉熵损失函数期望 labels 的形状为 (N),其中 N 是样本数。对于序列任务,这里的 N 也是 batch_size * seq_len

    因此,我们需要将 labels 的形状从 (batch_size, seq_len) 转换为 (batch_size * seq_len)

     
    labels = labels.view(-1)

计算交叉熵损失

在转换形状后,我们可以计算交叉熵损失。nn.CrossEntropyLoss 函数会自动将 logits 传递到 softmax 函数中,因此我们只需传入转换后的 logitslabels

loss = self.loss_fn(logits.view(-1, vocab_size), labels.view(-1))

代码解析

  1. 提取模型输出的 logits 并获取其形状

    logits = outputs.logits batch_size, seq_len, vocab_size = logits.size()
  2. 计算每个标签序列的实际长度(去掉 pad

    label_lengths = (labels != tokenizer.pad_token_id).sum(dim=1)
  3. 初始化权重张量并为每个标签序列计算权重

    weights = torch.zeros_like(labels, dtype=torch.float) for i, length in enumerate(label_lengths): length = length.item() weights[i, :length] = torch.arange(length + 1, 1, -1, dtype=torch.float)
  4. logitslabels 转换为合适的形状并计算交叉熵损失

    loss = self.loss_fn(logits.view(-1, vocab_size), labels.view(-1)) loss = loss.view(batch_size, seq_len)
  5. 应用权重并计算加权平均损失

    weighted_loss = loss * weights weighted_loss = weighted_loss.sum() / weights.sum()

通过这种方式,我们可以为每个标签位置应用不同的权重,从而计算加权的交叉熵损失。

 
posted @ 2024-06-16 21:41  jasonzhangxianrong  阅读(62)  评论(0编辑  收藏  举报