用PyTorch构建Transformer模型实战

今天我们要聊一聊如何用PyTorch来搭建一个超酷的Transformer模型。Transformer不用多提,它在自然语言处理,比如语言翻译和文本摘要这些任务上,简直是翻天覆地的变化。而且,Transformer处理起长距离的依赖关系和并行计算来,那叫一个游刃有余,所以它已经把以前的LSTM网络给比下去了。

我们这篇文章就是想让大家彻底搞懂,用PyTorch来构建这种Transformer模型的每一个步骤。PyTorch 简单易用,功能强大,效率还高。它的动态计算图和海量的库支持,让它成为了机器学习和人工智能领域里,研究者和开发者们的最爱。现在,就是要带你一起,看看PyTorch的魔力,让你也能成为构建顶尖模型的高手!

构建PyTorch中的Transformer

设置PyTorch环境

在深入构建Transformer之前,正确设置工作环境至关重要。首先,需要安装PyTorch。PyTorch 可以通过pip或conda包管理器轻松安装。

使用pip安装的命令是:

pip3 install torch torchvision torchaudio

使用conda安装的命令是:

conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia

如果需要使用CPU运行PyTorch,请访问PyTorch文档。此外,了解深度学习概念也有助于理解Transformer的工作原理。 具体,可参考另外一篇文章《大模型Transformer架构详解》

使用PyTorch构建Transformer模型

构建Transformer模型需要以下步骤:

  1. 导入必要的库和模块

  2. 定义基本构建块 - 多头注意力、位置感知前馈网络、位置编码

  3. 构建编码器块

  4. 构建解码器块

  5. 将编码器和解码器层组合创建完整的Transformer网络

1. 导入必要的库和模块

我们将从导入PyTorch库的核心功能、用于创建神经网络的神经网络模块、用于训练网络的优化模块以及用于处理数据的数据实用函数开始。此外,我们还将导入标准Python数学模块以进行数学运算,以及用于创建复杂对象副本的copy模块。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy

2. 定义基本构建块:多头注意力、位置感知前馈网络、位置编码

多头注意力

多头注意力机制计算序列中每对位置之间的注意力。它由多个“注意力头”组成,这些头捕获输入序列的不同方面。

图1. 多头注意力

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        # 确保模型维度(d_model)可以被注意力头数整除
        assert d_model % num_heads == 0, "d_model必须能被num_heads整除"

        # 初始化维度
        self.d_model = d_model  # 模型的维度
        self.num_heads = num_heads  # 注意力头的数量
        self.d_k = d_model // num_heads  # 每个头的键、查询和值的维度

        # 用于转换输入的线性层
        self.W_q = nn.Linear(d_model, d_model)  # 查询转换
        self.W_k = nn.Linear(d_model, d_model)  # 键转换
        self.W_v = nn.Linear(d_model, d_model)  # 值转换
        self.W_o = nn.Linear(d_model, d_model)  # 输出转换

    def scaled_dot_product_attention(self, Q, K, V, mask=None):
        # 计算注意力分数
        attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

        # 如果提供了掩码,则应用它(有助于防止对某些部分如填充的注意力)
        if mask is not None:
            attn_scores = attn_scores.masked_fill(mask == 0, -1e9)

        # 应用softmax以获得注意力概率
        attn_probs = torch.softmax(attn_scores, dim=-1)

        # 乘以值以获得最终输出
        output = torch.matmul(attn_probs, V)
        return output

    def split_heads(self, x):
        # 重塑输入以进行多头注意力
        batch_size, seq_length, d_model = x.size()
        return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)

    def combine_heads(self, x):
        # 将多个头重新组合成原始形状
        batch_size, _, seq_length, d_k = x.size()
        return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)

    def forward(self, Q, K, V, mask=None):
        # 应用线性变换并分割头
        Q = self.split_heads(self.W_q(Q))
        K = self.split_heads(self.W_k(K))
        V = self.split_heads(self.W_v(V))

        # 执行缩放点积注意力
        attn_output = self.scaled_dot_product_attention(Q, K, V, mask)

        # 组合头并应用输出变换
        output = self.W_o(self.combine_heads(attn_output))
        return output

类定义和初始化:

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads)

该类定义为PyTorch的nn.Module的子类。

  1. d_model: 输入的维度。

  2. num_heads: 分割输入的注意力头数。

初始化检查d_model是否可以被num_heads整除,然后定义查询、键、值和输出的转换权重。

缩放点积注意力:

def scaled_dot_product_attention(self, Q, K, V, mask=None)
  1. 计算注意力分数:attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)。这里,注意力分数是通过取查询(Q)和键(K)的点积,然后通过键的维度(d_k)的平方根进行缩放来计算的。

  2. 应用掩码:如果提供了掩码,则将其应用于注意力分数以掩盖特定值。

  3. 计算注意力权重:注意力分数通过softmax函数传递,以将它们转换为总和为1的概率。

  4. 计算输出:注意力的最终输出是通过将注意力权重乘以值(V)来计算的。

分割头:

def split_heads(self, x)

这个方法将输入x重塑为形状(batch_size, num_heads, seq_length, d_k)。它使模型能够同时处理多个注意力头,允许并行计算。

组合头:

def combine_heads(self, x)

在分别对每个头应用注意力后,这个方法将结果组合回batch_size, seq_length, d_model形状的单个张量。这为进一步处理准备了结果。

前向方法:

def forward(self, Q, K, V, mask=None)

前向方法是实际计算发生的地方:

  1. 应用线性变换:首先使用初始化中定义的权重将查询(Q)、键(K)和值(V)通过线性变换。

  2. 分割头:使用split_heads方法将转换后的Q、K、V分割成多个头。

  3. 应用缩放点积注意力:在分割的头上调用scaled_dot_product_attention方法。

  4. 组合头:使用combine_heads方法将每个头的结果组合回单个张量。

  5. 应用输出变换:最后,组合的张量通过输出线性变换。

总结,MultiHeadAttention类封装了在Transformer模型中常用的多头注意力机制。它处理将输入分割成多个注意力头,对每个头应用注意力,然后将结果组合。通过这样做,模型可以在不同尺度上捕获输入数据中的各种关系,提高模型的表达能力。

位置感知前馈网络
class PositionWiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff):
        super(PositionWiseFeedForward, self).__init__()
        self.fc1 = nn.Linear(d_model, d_ff)
        self.fc2 = nn.Linear(d_ff, d_model)
        self.relu = nn.ReLU()

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

类定义:

class PositionWiseFeedForward(nn.Module):

该类是PyTorch的nn.Module的子类,意味着它将继承所有所需的功能,以与神经网络层一起工作。

初始化:

def __init__(self, d_model, d_ff):
    super(PositionWiseFeedForward, self).__init__()
    self.fc1 = nn.Linear(d_model, d_ff)
    self.fc2 = nn.Linear(d_ff, d_model)
    self.relu = nn.ReLU()
  1. d_model: 模型输入和输出的维度。

  2. d_ff: 前馈网络中内层的维度。

  3. self.fc1self.fc2: 两个全连接(线性)层,输入和输出维度由d_model和d_ff定义。

  4. self.relu: ReLU(修正线性单元)激活函数,在两个线性层之间引入非线性。

前向方法:

def forward(self, x):
    return self
.fc2(self.relu(self.fc1(x)))
  1. x: 前馈网络的输入。

  2. self.fc1(x): 输入首先通过第一个线性层(fc1)。

  3. self.relu(...): fc1的输出然后通过ReLU激活函数。ReLU将所有负值替换为零,为模型引入非线性。

  4. self.fc2(...): 激活的输出然后通过第二个线性层(fc2),产生最终输出。

总结,PositionWiseFeedForward类定义了一个位置感知的前馈神经网络,由两个带有ReLU激活函数的线性层组成。在Transformer模型的上下文中,这个前馈网络分别且相同地应用于每个位置。它有助于转换由注意力机制在Transformer中学习到的特征,作为注意力输出的额外处理步骤。

位置编码

位置编码用于注入输入序列中每个标记的位置信息。它使用不同频率的正弦和余弦函数生成位置编码。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_seq_length):
        super(PositionalEncoding, self).__init__()

        pe = torch.zeros(max_seq_length, d_model)
        position = torch.arange(0, max_seq_length, 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)

        self.register_buffer('pe', pe.unsqueeze(0))

    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

类定义:

class PositionalEncoding(nn.Module):

该类定义为PyTorch的nn.Module的子类,允许它作为标准的PyTorch层使用。

初始化:

def __init__(self, d_model, max_seq_length):
    super(PositionalEncoding, self).__init__()

    pe = torch.zeros(max_seq_length, d_model)
    position = torch.arange(0, max_seq_length, 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)

    self.register_buffer('pe', pe.unsqueeze(0))
  1. d_model: 模型输入的维度。

  2. max_seq_length: 预先计算位置编码的最大序列长度。

  3. pe: 用零填充的张量,将填充位置编码。

  4. position: 包含序列中每个位置的位置索引的张量。

  5. div_term: 用于以特定方式缩放位置索引的项。

  6. 正弦函数应用于pe的偶数索引,余弦函数应用于奇数索引。

  7. 最后,将pe注册为缓冲区,这意味着它将是模块状态的一部分,但不会被视为可训练参数。

前向方法:

def forward(self, x):
    return x + self.pe[:, :x.size(1)]

前向方法简单地将位置编码添加到输入x。

它使用pe的前x.size(1)个元素,以确保位置编码与x的实际序列长度相匹配。

总结PositionalEncoding类添加了有关序列中标记位置的信息。由于Transformer模型缺乏对令牌顺序的固有知识(由于其自注意力机制),此类帮助模型考虑序列中令牌的位置。所选的正弦函数允许模型轻松学习注意相对位置,因为它们为序列中的每个位置产生独特且平滑的编码。

构建编码器块

图2. Transformer网络的编码器部分

class EncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask):
        attn_output = self.self_attn(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))
        return x

类定义:

class EncoderLayer(nn.Module):

该类定义为PyTorch的nn.Module的子类,这意味着它可以作为PyTorch神经网络中的构建块使用。

初始化:

def __init__(self, d_model, num_heads, d_ff, dropout):
    super(EncoderLayer, self).__init__()
    self.self_attn = MultiHeadAttention(d_model, num_heads)
    self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
    self.norm1 = nn.LayerNorm(d_model)
    self.norm2 = nn.LayerNorm(d_model)
    self.dropout = nn.Dropout(dropout)

参数:

  1. d_model: 输入的维度。

  2. num_heads: 多头注意力中的注意力头数。

  3. d_ff: 位置感知前馈网络中内层的维度。

  4. dropout: 用于正则化的dropout率。

组件:

  1. self.self_attn: 多头自注意力机制。

  2. self.feed_forward: 位置感知前馈神经网络。

  3. self.norm1self.norm2: 层归一化,应用于平滑层的输入。

  4. self.dropout: Dropout层,用于通过在训练期间随机将一些激活设置为零来防止过拟合。

前向方法:

def forward(self, x, mask):
    attn_output = self.self_attn(x, x, x, mask)
    x = self.norm1(x + self.dropout(attn_output))
    ff_output = self.feed_forward(x)
    x = self.norm2(x + self.dropout(ff_output))
    return x

输入:

  1. x: 编码器层的输入。

  2. mask: 可选掩码,用于忽略输入的某些部分。

处理步骤:

  1. 自注意力:输入x通过多头自注意力机制传递。

  2. 加法 & 归一化(自注意力后):注意力输出添加到原始输入(残差连接),然后是dropout和使用norm1的归一化。

  3. 前馈网络:前一步的输出通过位置感知前馈网络传递。

  4. 加法 & 归一化(前馈后):与步骤2类似,前馈输出添加到该阶段的输入(残差连接),然后是dropout和使用norm2的归一化。

  5. 输出:处理后的张量作为编码器层的输出返回。

总结:EncoderLayer类定义了Transformer编码器的单层。它封装了多头自注意力机制,然后是位置感知前馈神经网络,带有残差连接、层归一化和dropout。这些组件共同允许编码器捕获输入数据中的复杂关系,并将它们转换为下游任务的有用表示。通常,多个这样的编码器层堆叠在一起形成完整的编码器部分。

构建解码器块

图3. Transformer网络的解码器部分

class DecoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(DecoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.cross_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, enc_output, src_mask, tgt_mask):
        attn_output = self.self_attn(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout(attn_output))
        attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
        x = self.norm2(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)

       x = self.norm3(x + self.dropout(ff_output))
        return x

类定义:

class DecoderLayer(nn.Module):

初始化:

def __init__(self, d_model, num_heads, d_ff, dropout):
    super(DecoderLayer, self).__init__()
    self.self_attn = MultiHeadAttention(d_model, num_heads)
    self.cross_attn = MultiHeadAttention(d_model, num_heads)
    self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
    self.norm1 = nn.LayerNorm(d_model)
    self.norm2 = nn.LayerNorm(d_model)
    self.norm3 = nn.LayerNorm(d_model)
    self.dropout = nn.Dropout(dropout)

参数:

  1. d_model: 输入的维度。

  2. num_heads: 多头注意力中的注意力头数。

  3. d_ff: 前馈网络中内层的维度。

  4. dropout: 用于正则化的dropout率。

组件:

  1. self.self_attn: 目标序列的多头自注意力机制。

  2. self.cross_attn: 多头注意力机制,用于关注编码器的输出。

  3. self.feed_forward: 位置感知前馈神经网络。

  4. self.norm1self.norm2self.norm3: 层归一化组件。

  5. self.dropout: 用于正则化的dropout层。

前向方法:

def forward(self, x, enc_output, src_mask, tgt_mask):
    attn_output = self.self_attn(x, x, x, tgt_mask)
    x = self.norm1(x + self.dropout(attn_output))
    attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
    x = self.norm2(x + self.dropout(attn_output))
    ff_output = self.feed_forward(x)
    x = self.norm3(x + self.dropout(ff_output))
    return x

输入:

  1. x: 解码器层的输入。

  2. enc_output: 对应编码器的输出(用于交叉注意力步骤)。

  3. src_mask: 源掩码,用于忽略编码器输出的某些部分。

  4. tgt_mask: 目标掩码,用于忽略解码器输入的某些部分。

处理步骤:

  1. 目标序列上的自注意力:输入x通过自注意力机制处理。

  2. 加法 & 归一化(自注意力后):自注意力的输出添加到原始x,然后是dropout和使用norm1的归一化。

  3. 编码器输出上的交叉注意力:前一步归一化输出通过交叉注意力机制处理,该机制关注编码器的输出enc_output。

  4. 加法 & 归一化(交叉注意力后):交叉注意力的输出添加到该阶段的输入,然后是dropout和使用norm2的归一化。

  5. 前馈网络:前一步的输出通过前馈网络传递。

  6. 加法 & 归一化(前馈后):前馈输出添加到该阶段的输入,然后是dropout和使用norm3的归一化。

  7. 输出:处理后的张量作为解码器层的输出返回。

总结:DecoderLayer类定义了Transformer解码器的单层。它由多头自注意力机制、多头交叉注意力机制(关注编码器的输出)、位置感知前馈神经网络以及相应的残差连接、层归一化和dropout层组成。这种组合使解码器能够根据编码器的表示生成有意义的输出,同时考虑目标序列和源序列。与编码器一样,通常会堆叠多个解码器层以形成完整的解码器部分。

将编码器和解码器层组合创建完整的Transformer网络

图4. Transformer网络

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
        super(Transformer, self).__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model, max_seq_length)

        self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
        self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])

        self.fc = nn.Linear(d_model, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def generate_mask(self, src, tgt):
        src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
        tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
        seq_length = tgt.size(1)
        nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
        tgt_mask = tgt_mask & nopeak_mask
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        src_mask, tgt_mask = self.generate_mask(src, tgt)
        src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))

        enc_output = src_embedded
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)

        dec_output = tgt_embedded
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)

        output = self.fc(dec_output)
        return output

类定义:

class Transformer(nn.Module):

初始化:

def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):

构造函数采用以下参数:

  1. src_vocab_size: 源词汇表大小。

  2. tgt_vocab_size: 目标词汇表大小。

  3. d_model: 模型嵌入的维度。

  4. num_heads: 多头注意力机制中的注意力头数。

  5. num_layers: 编码器和解码器的层数。

  6. d_ff: 前馈网络中内层的维度。

  7. max_seq_length: 位置编码的最大序列长度。

  8. dropout: 用于正则化的dropout率。

并定义了以下组件:

  1. self.encoder_embedding: 源序列的嵌入层。

  2. self.decoder_embedding: 目标序列的嵌入层。

  3. self.positional_encoding: 位置编码组件。

  4. self.encoder_layers: 编码器层的列表。

  5. self.decoder_layers: 解码器层的列表。

  6. self.fc: 将输出映射到目标词汇表大小的最终全连接(线性)层。

  7. self.dropout: Dropout层。

生成掩码方法:

def generate_mask(self, src, tgt):

此方法用于为源和目标序列创建掩码,确保在训练期间忽略填充标记,并且目标序列中的未来标记不可见。

前向方法:

def forward(self, src, tgt):

此方法定义了Transformer的前向传递,采用源和目标序列,并产生输出预测。

  1. 输入嵌入和位置编码:首先使用各自的嵌入层嵌入源和目标序列,然后添加它们的位置编码。

  2. 编码器层:源序列通过编码器层传递,最终编码器输出表示处理过的源序列。

  3. 解码器层:目标序列和编码器的输出通过解码器层传递,得到解码器的输出。

  4. 最终线性层:解码器的输出使用全连接(线性)层映射到目标词汇表大小。

输出:最终输出是一个张量,表示模型对目标序列的预测。

总结:Transformer类汇集了Transformer模型的各个组件,包括嵌入、位置编码、编码器层和解码器层。它为训练和推理提供了方便的接口,封装了多头注意力、前馈网络和层归一化的复杂性。

这个实现遵循标准的Transformer架构,适用于机器翻译、文本摘要等序列到序列任务。掩码的包含确保模型遵守序列内的因果依赖关系,忽略填充标记并防止来自未来标记的信息泄露。

这些连续的步骤使Transformer模型能够有效地处理输入序列并产生相应的输出序列。

训练PyTorch Transformer模型

样本数据准备

为了说明目的,本例将创建一个虚拟数据集。然而,在实际情况下,将使用更大的数据集,并且该过程将涉及文本预处理以及为源语言和目标语言创建词汇映射。

python
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1

transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)

# 生成随机样本数据
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)

超参数:这些值定义了Transformer模型的架构和行为:

  1. src_vocab_sizetgt_vocab_size: 源和目标序列的词汇表大小,均设置为5000。

  2. d_model: 模型嵌入的维度,设置为512。

  3. num_heads: 多头注意力机制中的注意力头数,设置为8。

  4. num_layers: 编码器和解码器的层数,设置为6。

  5. d_ff: 前馈网络中内层的维度,设置为2048。

  6. max_seq_length: 位置编码的最大序列长度,设置为100。

  7. dropout: 用于正则化的dropout率,设置为0.1。

创建Transformer实例:

transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)

这行代码创建了一个Transformer类的实例,用给定的超参数初始化它。实例将具有由这些超参数定义的架构和行为。

生成随机样本数据:下面的代码生成了随机的源和目标序列:

  1. src_data: 介于1和src_vocab_size之间的随机整数,表示一批源序列,形状为(64, max_seq_length)。

  2. tgt_data: 介于1和tgt_vocab_size之间的随机整数,表示一批目标序列,形状为(64, max_seq_length)。

  3. 这些随机序列可以用作输入到Transformer模型中,模拟了一批具有64个示例和长度为100的序列的数据。

总结:这段代码片段展示了如何初始化一个Transformer模型并生成可以输入到模型中的随机源和目标序列。选择的超参数决定了Transformer的具体结构和属性。这种设置可以是更大脚本的一部分,其中模型在实际的序列到序列任务上进行训练和评估,例如机器翻译或文本摘要。

训练模型

接下来,将使用上述样本数据训练模型。然而,在现实场景中,将使用一个更大的数据集,通常会被划分为训练和验证的独立集合。

criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)

transformer.train()

for epoch in range(100):
    optimizer.zero_grad()
    output = transformer(src_data, tgt_data[:, :-1])
    loss = criterion(output.contiguous().view(-1, tgt_vocab_size), tgt_data[:, 1:].contiguous().view(-1))
    loss.backward()
    optimizer.step()
    print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

损失函数和优化器:

  1. criterion = nn.CrossEntropyLoss(ignore_index=0): 定义损失函数为交叉熵损失。ignore_index参数设置为0,意味着损失将不考虑索引为0的目标(通常保留给填充标记)。

  2. optimizer = optim.Adam(...): 定义优化器为Adam,学习率为0.0001和特定的beta值。

模型训练模式:

  1. transformer.train(): 将Transformer模型设置为训练模式,启用仅在训练期间应用的行为,如dropout。

训练循环:这段代码片段使用典型的训练循环训练模型100个周期:

  1. for epoch in range(100): 遍历100个训练周期。

  2. optimizer.zero_grad(): 清除上一次迭代的梯度。

  3. output = transformer(src_data, tgt_data[:, :-1]): 将源数据和目标数据(每个序列中除去最后一个标记)传递通过Transformer。这在序列到序列任务中很常见,目标会逐个标记地移动。

  4. loss = criterion(...): 计算模型预测和目标数据(每个序列中除去第一个标记)之间的损失。损失通过将数据重塑为一维张量并使用交叉熵损失函数来计算。

  5. loss.backward(): 计算损失相对于模型参数的梯度。

  6. optimizer.step(): 使用计算出的梯度更新模型的参数。

  7. print(f"Epoch: {epoch+1}, Loss: {loss.item()}"): 打印当前周期号和该周期的损失值。

总结:这段代码片段在随机生成的源和目标序列上训练Transformer模型100个周期。它使用Adam优化器和交叉熵损失函数。每个周期的损失被打印出来,允许你监控训练进度。在现实场景中,你会用你的任务中的实际数据替换随机源和目标序列,例如机器翻译。

Transformer模型性能评估

训练模型后,可以在验证数据集或测试数据集上评估其性能。以下是一个如何执行此操作的示例:

transformer.eval()

# 生成随机样本验证数据
val_src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)
val_tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)

with torch.no_grad():
    val_output = transformer(val_src_data, val_tgt_data[:, :-1])
    val_loss = criterion(val_output.contiguous().view(-1, tgt_vocab_size), val_tgt_data[:, 1:].contiguous().view(-1))
    print(f"Validation Loss: {val_loss.item()}")

评估模式:

  1. transformer.eval(): 将Transformer模型置于评估模式。这很重要,因为它关闭了仅在训练期间使用的某些行为,如dropout。

生成随机验证数据:

  1. val_src_data: 介于1和src_vocab_size之间的随机整数,表示一批验证源序列,形状为(64, max_seq_length)。

  2. val_tgt_data: 介于1和tgt_vocab_size之间的随机整数,表示一批验证目标序列,形状为(64, max_seq_length)。

验证循环:

  1. with torch.no_grad(): 禁用梯度计算,因为在验证期间我们不需要计算梯度。这可以减少内存消耗并加速计算。

  2. val_output = transformer(val_src_data, val_tgt_data[:, :-1]): 将验证源数据和验证目标数据(每个序列中除去最后一个标记)传递通过Transformer。

  3. val_loss = criterion(...): 计算模型预测和验证目标数据(每个序列中除去第一个标记)之间的损失。损失通过将数据重塑为一维张量并使用之前定义的交叉熵损失函数来计算。

  4. print(f"Validation Loss: {val_loss.item()}"): 打印验证损失值。

总结:这段代码片段在随机生成的验证数据集上评估Transformer模型,计算验证损失,并打印它。在现实场景中,随机验证数据应替换为你正在处理的任务的实际验证数据。验证损失可以给你一个模型在未见过的数据上表现如何的指示,这是衡量模型泛化能力的关键指标。

结论

总之,本文展示了如何使用PyTorch构建Transformer模型,这是深度学习中最通用的工具之一。由于其并行化能力和捕捉数据中长期依赖关系的能力,Transformer在各种领域,特别是NLP任务如翻译、摘要和情感分析中具有巨大潜力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值