Categories
坐骑幻化

一、概述

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

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

* source sentence: "hello how are you"

* target sentence: "salut comment ça-va"

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

* input tokens = [hello, how, are, you, ]

* label tokens = [salut, comment, ça, -, va, ]

当然,我们不会将这些标记作为文本提供给模型,而是以整数 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 => 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”后面的令牌是“”(意思是序列末尾或EOS令牌)。在这里,我们计算模型预测与目标标记(“”)之间的交叉熵损失。

在法典 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:每个样本的序列长度。

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

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

转换 logits 的形状:

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

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

logits = logits.view(-1, vocab_size)

转换 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 函数中,因此我们只需传入转换后的 logits 和 labels。

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

代码解析

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

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)

将 logits 和 labels 转换为合适的形状并计算交叉熵损失:

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()

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