Transform 和BERT、GPT 模型

目录

Transform的由来

Seq2seq 模型

Transform 的内部结构

语言模型

BERT 介绍

BERT 模型的组成

分词器

位置编码

Softmax

残差结构

BERT 模型总结


Transform的由来

传统的语⾔模型,⽐如RNN(循环神经⽹络),就像⼀个“短视”的学⽣。它在读⼀篇⽂章时,只能逐字逐句地读,并且每次只能记住前⼀个词的信息。当句⼦很⻓时,它就会“忘记” 前⾯读过的内容,导致对整句话的理解不完整。

Transformer 的出现,就像给这个学⽣配上了“全局视野”。它最⼤的创新是引⼊了⾃注意⼒机制(Self-Attention Mechanism)。简单来说,⾃注意⼒机制让模型在处理⼀个词时,能够 同时关注到句⼦中的所有其他词,并根据它们之间的关系来确定每个词的重要性

RNN(或者LSTM、GRU等)的计算限制为是顺序的,也就是RNN相关算法只能从左向右或从右向左依次计算:

  • 时间片t的计算依赖于t-1时刻的计算结果,限制了模型的并行能力;
  • 尽管LSTM等门机制的结构缓解了长期依赖的问题,但顺序计算的过程中信息会丢失,

Transformer的提出解决了上面两个问题,它使用了 Attention机制,将序列中的任意两个位置之间的距离缩小为一个常量;其次他不是类似于RNN的顺序结构,因此具有更好的并行性,符合现有的GPU框架。

  • 注意⼒机制(Attention Mechanism)

⼈类阅读⼀段⽂字时,⼤脑并不是平等地处理每⼀个字。相反,你会根据上下⽂,有意识或⽆意识地去关注那些对理解当前信息更重要的词语。

注意⼒机制的⼯作过程分解成三个简单的步骤:

  1. 打分(Scoring): 对于句⼦中的每⼀个词,模型都会计算它与当前正在处理的词之间的 “相关性分数”。分数越⾼,代表这两个词越相关。
  1. 归⼀化(Normalizing): 这些分数会被转换成权重,通常使⽤ Softmax 函数。这样做的 好处是,所有权重加起来等于1,⽽且越重要的词权重越⾼,不重要的词权重越低,这就像 给每个词分配了不同等级的关注度。
  1. 加权求和(Weighted Sum): 最后,模型会将所有词的原始信息(也叫“值”)与它们对 应的权重相乘,然后相加。这样得到的最终表⽰,就包含了所有词的信息,但重点突出了那 些权重⾼的、更重要的词。

注意⼒机制通过让模型能够“回头看”句⼦中的所有词,并给它们分配不同的权重,彻底解决了这个问题。它让模型在处理⻓句⼦时,不再“短视”,⽽是拥有了“全局视野”,能更好地理解整个句⼦的语境和含义

Seq2seq 模型

Seq2seq(Sequence-to-Sequence)将自然语言处理中的任务(如文本摘要、机器翻译、对话系统等)看作从一个输入序列到另外一个输出序列的映射,然后通过一个端到端的神经网络来直接学习序列的映射关系。

Seq2seq也是编码器-解码器结构的雏形。

  • 仅使用encoder的预训练模型: BERT (理解文本)
  • 仅使用decoder的预训练模型: GPT (生成文本)

Transform 的内部结构

Transformer是一种基于注意力机制的编码器-解码器结构,具有很好的并行性能,同时利用注意力机制很好地解决了长序列的长程依赖问题

多头注意力机制接受输入词嵌入与位置编码之和,并进行多头注意力的计算。注意力机制中的Q、K、V的来源不同。以机器翻译为例,Q来源于目标输出,而K和V来源于输入信息。与之相对,自注意力机制的Q、K、V均来源于同一个X。

多头注意力机制。多头注意力机制是多个自注意力机制的组合,目的是从多个不同角度提取交互信息,其中每个角度称为一个注意力头,多个注意力头可以独立并行计算。

Transformer编码器的输入是词嵌入与位置编码之和。将输入序列转化成词嵌入的方法是从一张査询表(Lookup Table)中获取每个词元(Token)对应的向量表示。但如果仅使用词嵌入作为Transformer的注意力机制的输入,则在计算词元之间的相关度时并未考虑它们的位置信息。原始的Tansformer采用了正余弦位置编码。

  • 通过计算得出各个位置每个维度上的信息,而非通过训练学习到
  • 输入长度不受最大长度的限制,可以计算到比训练数据更长的位置,具有一定的外推性;
  • 每个分量都是正弦或者余弦函数,并且整体的位置编码具有远程衰减性质,具备位置信息;

残差连接和层归一化

在多头注意力机制和前馈神经网络中都会进行相关逻辑处理

  • 残差连接的核心思想是在网络层的输出中直接添加该层的输入,形成"短路"连接
  • 层归一化对每个样本的所有特征维度进行归一化处理,使数据的均值为0,方差为1

残差连接其作用在于

  • 缓解梯度消失 :通过直接传递梯度,使得深层网络的梯度能够更有效地回传
  • 学习恒等映射 :允许网络层更容易学习恒等变换(identity mapping),促进模型收敛
  • 增强特征表达 :保留原始输入信息,同时引入新的特征表示

层归一化其作用在于

  • 稳定训练 :减少内部协变量偏移(Internal Covariate Shift),使网络训练更稳定
  • 加速收敛 :标准化的输入分布有助于优化器更快地找到最优解
  • 降低学习率敏感性 :减少对学习率选择的依赖,提高模型鲁棒性
  • 支持不同长度序列 :相比批归一化(Batch Normalization),层归一化不依赖于批次大小,更适合处理变长序列

在transformer 中,代码中的实现采用了"预归一化"(Pre-Normalization)设计,即层归一化应用在每个子层的输出之后,而不是输入之前。这种设计有以下优势:

  • 使残差连接更有效,因为归一化后的梯度更稳定
  • 简化了训练过程,不需要精心调整学习率和初始化
  • 允许构建更深的网络架构
self.layer_norm = nn.LayerNorm(d_embedding) # 一行代码实现残差连接+归一化# 将输出与残差连接相加,并进行层归一化
# 残差连接的作用是将原始输入与计算结果相加 ---> 绕过注意力机制相关的线条
# output 的维度: [batch_size, len_q, d_embedding]
output = self.layer_norm(output + residual)

transformer 结构代码示例

import numpy as np  # 导入 numpy 库,用于科学计算
import torch  # 导入 torch 库,用于构建神经网络
import torch.nn as nn  # 导入 torch.nn 库,包含了各种神经网络层# --- 全局参数 ---
d_k = 64  # Q 和 K 向量的维度
d_v = 64  # V 向量的维度
d_embedding = 128  # 词嵌入的维度
n_heads = 8  # 多头注意力机制中头的数量
n_layers = 6  # 编码器和解码器的层数
batch_size = 3  # 训练批次大小
epochs = 10  # 训练轮次# --- 注意力机制模块 ---class ScaledDotProductAttention(nn.Module):"""缩放点积注意力模块根据 Q、K、V 计算注意力分数和上下文向量包含掩码机制,通过 scores.masked_fill_(attn_mask.bool(), -1e9) 屏蔽填充位置或未来信息输入:Q: 查询向量,维度 [batch_size, n_heads, len_q, dim_q]K: 键向量,维度 [batch_size, n_heads, len_k, dim_k]V: 值向量,维度 [batch_size, n_heads, len_v, dim_v]attn_mask: 注意力掩码,维度 [batch_size, n_heads, len_q, len_k]输出:context: 上下文向量,维度 [batch_size, n_heads, len_q, dim_v]attn_weights: 注意力权重,维度 [batch_size, n_heads, len_q, len_k]输出:点积注意力的缩放操作解决了高维空间中点积值过大导致softmax梯度消失的问题,提高了训练稳定性缩放操作通过除以 sqrt(d_k) 来缩放点积,防止点积值过大导致 softmax 函数输出接近于 0 或 1,从而避免梯度消失问题"""def __init__(self):super(ScaledDotProductAttention, self).__init__()def forward(self, Q, K, V, attn_mask):# Q, K, V 的维度: [batch_size, n_heads, len_q/k/v, dim_q=k/v]# attn_mask 的维度: [batch_size, n_heads, len_q, len_k]# 1. 计算注意力分数# 将 Q 与 K 的转置相乘,并除以 sqrt(d_k) 进行缩放# scores 的维度: [batch_size, n_heads, len_q, len_k]scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)# 2. 应用注意力掩码# 将 attn_mask 中为 True 的位置的 scores 替换为一个极小值 (-1e9)# 这样在 softmax 之后,这些位置的权重将接近于0scores.masked_fill_(attn_mask, -1e9)# 3. 对分数进行 softmax 归一化# 沿着最后一个维度 (len_k) 进行 softmax# weights 的维度: [batch_size, n_heads, len_q, len_k]weights = nn.Softmax(dim=-1)(scores)# 4. 计算上下文向量# 将归一化后的权重与 V 相乘# context 的维度: [batch_size, n_heads, len_q, dim_v]context = torch.matmul(weights, V)# 返回上下文向量和注意力权重return context, weightsclass MultiHeadAttention(nn.Module):"""多头注意力机制模块将输入投影到多个子空间,并并行计算注意力,最后将结果拼接"""def __init__(self):super(MultiHeadAttention, self).__init__()# 线性投影层,用于生成 Q, K, Vself.W_Q = nn.Linear(d_embedding, d_k * n_heads)self.W_K = nn.Linear(d_embedding, d_k * n_heads)self.W_V = nn.Linear(d_embedding, d_v * n_heads)# 最后的线性层,将拼接后的多头输出投影回原始维度self.linear = nn.Linear(n_heads * d_v, d_embedding)# 层归一化(Layer Normalization),样本的特征进行归一下,使其均值为0 ,方差为1self.layer_norm = nn.LayerNorm(d_embedding)def forward(self, Q, K, V, attn_mask):# Q, K, V 的维度: [batch_size, len_q/k/v, d_embedding]residual, batch_size = Q, Q.size(0)# 1. 线性投影并重塑# 将输入 Q, K, V 投影到多头子空间,并调整维度以便并行计算# .view() 和 .transpose() 操作将维度变为: [batch_size, n_heads, len_q/k/v, d_q=k/v]q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2)k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1, 2)v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1, 2)# 2. 复制注意力掩码# 将 attn_mask 复制 n_heads 次,以适应多头注意力# attn_mask 的维度: [batch_size, n_heads, len_q, len_k]# unsqueeze(1) :在第1维(索引从0开始)插入一个新的维度,将掩码形状变为 [batch_size, 1, len_q, len_k]# repeat(1, n_heads, 1, 1) :在新增的维度上复制 n_heads 次(其他维度保持不变),最终掩码形状变为 [batch_size, n_heads, len_q, len_k]attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)# 3. 计算缩放点积注意力context, weights = ScaledDotProductAttention()(q_s, k_s, v_s,attn_mask)# context 的维度: [batch_size, n_heads, len_q, dim_v]# weights 的维度: [batch_size, n_heads, len_q, len_k]# 4. 拼接多头结果# 调整维度并使用 .contiguous() 确保内存连续,然后用 .view() 将多头结果拼接# context 的维度: [batch_size, len_q, n_heads * dim_v]context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v)# 5. 最终线性投影和层归一化# 将拼接后的结果通过线性层投影回 d_embedding 维度output = self.linear(context)# 将输出与残差连接相加,并进行层归一化# output 的维度: [batch_size, len_q, d_embedding]output = self.layer_norm(output + residual)# 返回最终输出和注意力权重return output, weights# --- 前馈网络模块 ---class PoswiseFeedForwardNet(nn.Module):"""逐位置前馈网络模块对序列中的每个位置独立地应用一个全连接前馈网络"""def __init__(self, d_ff=2048):super(PoswiseFeedForwardNet, self).__init__()# 两个一维卷积层,相当于两个全连接层self.conv1 = nn.Conv1d(in_channels=d_embedding, out_channels=d_ff, kernel_size=1)self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_embedding, kernel_size=1)# 层归一化self.layer_norm = nn.LayerNorm(d_embedding)def forward(self, inputs):# inputs 的维度: [batch_size, len_q, d_embedding]residual = inputs# 1. 维度转换并应用第一个卷积层# inputs.transpose(1, 2) 将维度变为 [batch_size, d_embedding, len_q]# 卷积操作后,output 的维度: [batch_size, d_ff, len_q]output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))# 2. 应用第二个卷积层并转换维度# 卷积操作后,output 的维度: [batch_size, d_embedding, len_q]# .transpose(1, 2) 将维度恢复为 [batch_size, len_q, d_embedding]output = self.conv2(output).transpose(1, 2)# 3. 残差连接和层归一化output = self.layer_norm(output + residual)return output# --- 位置编码和掩码函数 ---def get_sin_enc_table(n_position, embedding_dim):"""生成正弦位置编码表用于在序列中引入词语的绝对位置信息"""# 初始化正弦编码表sinusoid_table = np.zeros((n_position, embedding_dim))# 计算不同位置和维度的角度for pos_i in range(n_position):for hid_j in range(embedding_dim):angle = pos_i / np.power(10000, 2 * (hid_j // 2) / embedding_dim)sinusoid_table[pos_i, hid_j] = angle# 将偶数维应用 sin 函数,奇数维应用 cos 函数sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])# 转换为 PyTorch 张量return torch.FloatTensor(sinusoid_table)def get_attn_pad_mask(seq_q, seq_k):"""生成填充注意力掩码用于在注意力计算中忽略填充 <pad> 词语"""# seq_q 的维度: [batch_size, len_q]# seq_k 的维度: [batch_size, len_k]batch_size, len_q = seq_q.size()batch_size, len_k = seq_k.size()# 找到 seq_k 中所有值为 0 (<pad>) 的位置# pad_attn_mask 的维度: [batch_size, 1, len_k]pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)# 将掩码扩展到与注意力分数相同的形状# pad_attn_mask 的维度: [batch_size, len_q, len_k]pad_attn_mask = pad_attn_mask.expand(batch_size, len_q, len_k)return pad_attn_maskdef get_attn_subsequent_mask(seq):"""生成后续注意力掩码 (仅用于解码器)用于在注意力计算中忽略当前位置之后的信息,防止信息泄露"""# seq 的维度: [batch_size, seq_len]attn_shape = [seq.size(0), seq.size(1), seq.size(1)]# 创建一个上三角矩阵,k=1 表示主对角线上方的元素为 1# subsequent_mask 的维度: [batch_size, seq_len, seq_len]subsequent_mask = np.triu(np.ones(attn_shape), k=1)# 转换为 PyTorch 字节张量 (布尔类型)subsequent_mask = torch.from_numpy(subsequent_mask).byte()return subsequent_mask# --- 编码器和解码器模块 ---class EncoderLayer(nn.Module):"""编码器的一层包含一个多头自注意力层和一个位置前馈网络"""def __init__(self):super(EncoderLayer, self).__init__()self.enc_self_attn = MultiHeadAttention()self.pos_ffn = PoswiseFeedForwardNet()def forward(self, enc_inputs, enc_self_attn_mask):# enc_inputs 的维度: [batch_size, seq_len, d_embedding]# enc_self_attn_mask 的维度: [batch_size, seq_len, seq_len]# 将相同的 Q, K, V 输入多头自注意力层enc_outputs, attn_weights = self.enc_self_attn(enc_inputs, enc_inputs,enc_inputs, enc_self_attn_mask)# 将自注意力输出输入位置前馈网络enc_outputs = self.pos_ffn(enc_outputs)# 返回最终输出和注意力权重return enc_outputs, attn_weightsclass Encoder(nn.Module):"""Transformer 编码器由词嵌入层、位置嵌入层和多个编码器层组成"""def __init__(self, corpus):super(Encoder, self).__init__()self.src_emb = nn.Embedding(len(corpus.src_vocab), d_embedding)# 从预计算的位置编码表初始化位置嵌入层,并冻结参数self.pos_emb = nn.Embedding.from_pretrained(get_sin_enc_table(corpus.src_len + 1, d_embedding), freeze=True)# 堆叠 n_layers 个编码器层self.layers = nn.ModuleList(EncoderLayer() for _ in range(n_layers))def forward(self, enc_inputs):# enc_inputs 的维度: [batch_size, source_len]# 生成位置索引序列# orch.arange(1, enc_inputs.size(1) + 1) 创建一个从1开始到输入序列长度的整数序列 # unsqueeze(0) 在第0维(索引从0开始)插入一个新的维度,将序列长度转换为 [1, source_len]# to(enc_inputs) 将位置索引移动到与输入相同的设备上(如 GPU)pos_indices = torch.arange(1, enc_inputs.size(1) + 1).unsqueeze(0).to(enc_inputs)# 词嵌入和位置嵌入相加enc_outputs = self.src_emb(enc_inputs) + self.pos_emb(pos_indices)# 生成填充注意力掩码# get_attn_pad_mask 函数用于生成填充注意力掩码,忽略输入序列中的 <pad> 词语# enc_self_attn_mask 的维度: [batch_size, source_len, source_len]# True 和 False 分别表示填充位置和非填充位置,填充位置的注意力权重为 0,非填充位置的注意力权重为 1# True 表示该位置是填充标记,需要被忽略 # False 表示该位置不是填充标记,需要被考虑enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)# 存储所有层的注意力权重enc_self_attn_weights = []# 逐层通过编码器层for layer in self.layers:enc_outputs, enc_self_attn_weight = layer(enc_outputs, enc_self_attn_mask)enc_self_attn_weights.append(enc_self_attn_weight)# 返回最终输出和所有层的注意力权重return enc_outputs, enc_self_attn_weightsclass DecoderLayer(nn.Module):"""解码器的一层包含一个多头自注意力层、一个编码器-解码器注意力层和一个位置前馈网络"""def __init__(self):super(DecoderLayer, self).__init__()self.dec_self_attn = MultiHeadAttention()self.dec_enc_attn = MultiHeadAttention()self.pos_ffn = PoswiseFeedForwardNet()def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):# dec_inputs 的维度: [batch_size, target_len, d_embedding]# enc_outputs 的维度: [batch_size, source_len, d_embedding]# 1. 第一个注意力子层: 多头自注意力# Q, K, V 都来自解码器输入dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs,dec_inputs, dec_self_attn_mask)# 2. 第二个注意力子层: 编码器-解码器注意力# Q 来自解码器输出,K, V 来自编码器输出dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs,enc_outputs, dec_enc_attn_mask)# 3. 位置前馈网络dec_outputs = self.pos_ffn(dec_outputs)# 返回最终输出和两个注意力层的权重return dec_outputs, dec_self_attn, dec_enc_attnclass Decoder(nn.Module):"""Transformer 解码器由词嵌入层、位置嵌入层和多个解码器层组成"""def __init__(self, corpus):super(Decoder, self).__init__()self.tgt_emb = nn.Embedding(len(corpus.tgt_vocab), d_embedding)# 从预计算的位置编码表初始化位置嵌入层,并冻结参数# from_pretrained 方法用于从预计算的位置编码表中初始化位置嵌入层# get_sin_enc_table 函数用于生成正弦位置编码表# freeze=True 表示冻结位置嵌入层的参数,不进行训练self.pos_emb = nn.Embedding.from_pretrained(get_sin_enc_table(corpus.tgt_len + 1, d_embedding), freeze=True)# 堆叠 n_layers 个解码器层self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])def forward(self, dec_inputs, enc_inputs, enc_outputs):# dec_inputs 的维度: [batch_size, target_len]# enc_inputs 的维度: [batch_size, source_len]# enc_outputs 的维度: [batch_size, source_len, d_embedding]# 生成位置索引序列pos_indices = torch.arange(1, dec_inputs.size(1) + 1).unsqueeze(0).to(dec_inputs)# 词嵌入和位置嵌入相加dec_outputs = self.tgt_emb(dec_inputs) + self.pos_emb(pos_indices)# 1. 生成解码器自注意力掩码dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)# 将填充掩码和后续掩码相加,结果大于 0 的位置为 Truedec_self_attn_mask = torch.gt((dec_self_attn_pad_mask+ dec_self_attn_subsequent_mask), 0)# 2. 生成编码器-解码器注意力掩码 (仅考虑填充)dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)dec_self_attns, dec_enc_attns = [], []# 逐层通过解码器层for layer in self.layers:dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs,dec_self_attn_mask, dec_enc_attn_mask)dec_self_attns.append(dec_self_attn)dec_enc_attns.append(dec_enc_attn)# 返回最终输出和所有层的注意力权重return dec_outputs, dec_self_attns, dec_enc_attns# --- Transformer 模型 ---class Transformer(nn.Module):"""Transformer 模型的总框架由编码器、解码器和最后的线性投影层组成"""def __init__(self, corpus):super(Transformer, self).__init__()self.encoder = Encoder(corpus)self.decoder = Decoder(corpus)# 最后的线性层,将解码器输出映射到目标词汇表大小self.projection = nn.Linear(d_embedding, len(corpus.tgt_vocab), bias=False)def forward(self, enc_inputs, dec_inputs):# enc_inputs 的维度: [batch_size, source_seq_len]# dec_inputs 的维度: [batch_size, target_seq_len]# 1. 编码器前向传播enc_outputs, enc_self_attns = self.encoder(enc_inputs)# enc_outputs 的维度: [batch_size, source_len, d_embedding]# 2. 解码器前向传播dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)# dec_outputs 的维度: [batch_size, target_len, d_embedding]# 3. 线性投影dec_logits = self.projection(dec_outputs)# dec_logits 的维度: [batch_size, target_len, tgt_vocab_size]# 返回逻辑值和所有注意力权重return dec_logits, enc_self_attns, dec_self_attns, dec_enc_attns# --- 数据处理 ---from collections import Counterclass TranslationCorpus:"""数据处理类,用于管理语料库、词汇表和批次生成"""def __init__(self, sentences):self.sentences = sentencesself.src_len = max(len(sentence[0].split()) for sentence in sentences) + 1self.tgt_len = max(len(sentence[1].split()) for sentence in sentences) + 2self.src_vocab, self.tgt_vocab = self.create_vocabularies()self.src_idx2word = {v: k for k, v in self.src_vocab.items()}self.tgt_idx2word = {v: k for k, v in self.tgt_vocab.items()}def create_vocabularies(self):src_counter = Counter(word for sentence in self.sentences for word in sentence[0].split())tgt_counter = Counter(word for sentence in self.sentences for word in sentence[1].split())src_vocab = {'<pad>': 0, **{word: i + 1 for i, word in enumerate(src_counter)}}tgt_vocab = {'<pad>': 0, '<sos>': 1, '<eos>': 2,**{word: i + 3 for i, word in enumerate(tgt_counter)}}return src_vocab, tgt_vocabdef make_batch(self, batch_size, test_batch=False):input_batch, output_batch, target_batch = [], [], []# 随机选择句子索引# torch.randperm(len(self.sentences)) 生成一个长度为句子总数的随机排列整数序列# [:batch_size] 选择前 batch_size 个索引sentence_indices = torch.randperm(len(self.sentences))[:batch_size]for index in sentence_indices:src_sentence, tgt_sentence = self.sentences[index]# 将句子转换为索引序列src_seq = [self.src_vocab[word] for word in src_sentence.split()]tgt_seq = ([self.tgt_vocab['<sos>']] +[self.tgt_vocab[word] for word in tgt_sentence.split()] +[self.tgt_vocab['<eos>']])# 对序列进行填充src_seq += [self.src_vocab['<pad>']] * (self.src_len - len(src_seq))tgt_seq += [self.tgt_vocab['<pad>']] * (self.tgt_len - len(tgt_seq))input_batch.append(src_seq)# 在测试模式下,解码器输入只包含 <sos> 符号output_batch.append([self.tgt_vocab['<sos>']] +([self.tgt_vocab['<pad>']] * (self.tgt_len - 2)) if test_batch else tgt_seq[:-1])target_batch.append(tgt_seq[1:])return torch.LongTensor(input_batch), torch.LongTensor(output_batch), torch.LongTensor(target_batch)# --- 训练和推理 ---# 定义语料库
sentences = [['毛老师 喜欢 人工智能', 'TeacherMao likes AI'],['我 爱 学习 人工智能', 'I love studying AI'],['深度学习 改变 世界', ' DL changed the world'],['自然语言处理 很 强大', 'NLP is powerful'],['神经网络 非常 复杂', 'Neural-networks are complex']
]# 创建语料库实例
corpus = TranslationCorpus(sentences)# 实例化模型、损失函数和优化器
model = Transformer(corpus)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)# 模型训练
print("--- 开始训练 ---")
for epoch in range(epochs):optimizer.zero_grad()enc_inputs, dec_inputs, target_batch = corpus.make_batch(batch_size)outputs, _, _, _ = model(enc_inputs, dec_inputs)# 调整输出和目标张量维度以适应损失函数# - outputs.view(-1, len(corpus.tgt_vocab)) 将输出调整为 [batch_size * target_len, tgt_vocab_size]# - target_batch.view(-1) 将目标调整为 [batch_size * target_len]loss = criterion(outputs.view(-1, len(corpus.tgt_vocab)), target_batch.view(-1))if (epoch + 1) % 1 == 0:print(f"Epoch: {epoch + 1:04d} cost = {loss:.6f}")loss.backward()optimizer.step()# 模型推理 (翻译)
print("\n--- 开始翻译 ---")
# 创建一个大小为 1 的测试批次
enc_inputs, dec_inputs, target_batch = corpus.make_batch(batch_size=1, test_batch=True)# 打印输入数据
print("编码器输入 :", enc_inputs)
print("解码器输入 :", dec_inputs)
print("目标数据 :", target_batch)# 进行翻译预测
predict, enc_self_attns, dec_self_attns, dec_enc_attns = model(enc_inputs, dec_inputs)# 后处理预测结果
predict = predict.view(-1, len(corpus.tgt_vocab))
predict = torch.argmax(predict, dim=1, keepdim=True)  #  Greedy Encoding 贪婪解码,选取每个位置概率最大的词的索引# 将索引转换为单词
translated_sentence = [corpus.tgt_idx2word[idx.item()] for idx in predict.squeeze()]
input_sentence = ' '.join([corpus.src_idx2word[idx.item()] for idx in enc_inputs[0]])# 打印翻译结果
print(f"输入句子: '{input_sentence}'")
print(f"翻译结果: '{' '.join(translated_sentence)}'")

语言模型

现阶段所有的NLP模型都不能理解这个世界,只是依赖已有的数据集进行概率计算。而在目前的“猜概率”游戏环境下,基于大型语言模型(LLM, Large Language Model)演进出了最主流的两个方向:BERT 和GPT

BERT 是之前最流行的方向,统治了所有NLP领域的判别任务,并在自然语言理解类任务中发挥出色。而最初GPT则较为薄弱,在GPT3.0发布前,GPT方向一直是弱于BERT的。

BERT 介绍

掩码预训练语言模型BERT

2018年Devlin等人提出了掩码预训练语言型BERT(Bidirectional Encoder Representation from Transformers)。BERT利用掩码机制构造了基于上下文预测中间词的预训练任务,相较于传统的语言模型建模方法,BERT能进一步挖掘上下文所带来的丰富语义。

Bidirectional Encoder Representations fromTransformers,直译过来就是“来自Transformer的双向编码表示”。

  • 双向(Bidirectional):这是 BERT最大的特点。
  • 编码器(Encoder):表明它擅长理解文本。
  • 来自Transformer (from Transformers):说明它的底层架构是Transformer。

BERT 模型的组成

ERT由多层Transformer编码器组成,这意味着在编码过程中每个位置都能获得所有位置的信息,而不仅仅是历史位置的信息。BERT同样由输入层,编码层和输出层三部分组成。编码层由多层Transformer编码器组成。

在预训练时,模型的最后有两个输出层MLM和NSP,分别对应了两个不同的预训练任务:掩码语言模型(Masked LanguageModeling,MLM)和下一句预测(Next Sentence Prediction,NSP)

掩码语言模型的训练对于输入形式没有要求,可以是一句话也可以一段文本,甚至可以是整个篇章,但是下一句预测则需要输入为两个句子,因此BERT在预训练阶段的输入形式统一为两段文字的拼接,这与其他预训练模型相比有较大区别。

BERT的创新之处在于,它采用了双向的训练方式。它在训练时,会随机遮盖(mask)掉句子中一部分词,然后让模型根据被遮盖词的前后所有词来预测它。除了“完形填空”(MaskedLanguage Model)这个主要任务外,BERT还有另一个辅助任务:下一句预测(NextSentence Prediction)。

BERT首先在海量的文本数据上进行“预训练”,学习通用的语言知识。之后,我们可以用少量的特定任务数据对它进行“微调”,让它胜任具体的任务,如情感分析、问答系统、命名实体识别等。

分词器

tokenizer将原始文本转换为模型能够处理的数字序列。像 BERT和 GPT 这样的 Transformer模型,都只能处理数字,模型根本不认识这些汉字。分词器的作用,就是把这个句子拆解并转 换成一串数字,比如[101,23,45,67,102]。然后,这些数字才能被输入到模型中进行 计算。

1. 分词(Tokenization)分词器会把一句话分解成一个个独立的“词元”(Token)。

  • 按词分词:比如,句子“love cats”会被分成['I','love','cats']
  • 按字分词:对于中文,句子“我爱中国”会被分成['我','爱’,'中','国']

2. 特殊标记(Special Tokens)为了让模型更好地理解句子的结构,分词器会添加一些特殊的标22.记:

  •  [CLS] :通常放在句子的开头,它代表了整个句子的信息,常用于文0本分类任务。
  •  [SEP] :通常放在句子的结尾,用于分隔不同的句子。如果想让模型同时处理两个句子(比如问答任务),会在它们之间放一个[SEP]。
  •  [PAD] :当批次中的句子长度不一时,分词器会用 [PAD]来填充短句子,让所有句子的长度都相同,方便模型并行处理。

3. 映射到 ID(Token to lD)分词器内部有一个巨大的“词汇表”(Vocabulary),它将每个词元映射到一个唯一的整数 ID。

4. 生成注意力掩码(Attention Mask)在上面的步骤2中,我们用 [PAD]填充了句子。 但模型需要知道哪些是真正的词,哪些是填充物。

位置编码

Transformer 的位置编码不是简单的数字。它是一种与词嵌入(word embedding)相加的编码方式。在将词语输入模型之前,会将它的词嵌入向量和位置编码向量相加。位置编码有多种实现方式,但Transformer论文中介绍的是一种基于正弦(sine)和余弦(cosine)函数的编码方法。

Softmax

Softmax 主要作用是吧一串普通的数字,转换成一串所有数字加起来等于1的概率

残差结构

在深度神经网络中,信息需要一层一层地向前传递。当网络层数很深时,就像信息要走一条非常长的路。在这个过程中,可能会出现梯度消失的问题,导致信息越来越弱,甚至丢失。这会让模型很难训练,效果也会变差。残差结构的核心思想就是为信息传递提供一条捷径(shortcut)

  • 解决梯度消失问题:当梯度在反向传播时,可以通过这条捷径直接传递到前面的层,避免了信息在多层计算中逐渐衰减。这让训练更深的网络变得可能。
  • 保留原始信息:它确保了每一层的输出都包含了原始输入的信息。这就像在学习新知识时,不仅要吸收新的内容,还要不断回顾旧的知识,这样才能学得更扎实。

BERT 模型总结

预训练阶段: BERT采用了无监督的预训练策略,通过在大规模语料库上进行预训练,学习通用的语言表示。模型在两个任务上进行预训练:

  • Masked Language Model(MLM): 随机遮蔽输入文本中的一些词,然后预测这些被遮蔽的词。
  • Next Sentence Prediction(NSP): 预测两个相邻句子是否是原文中相邻的句子。

微调阶段: 在具体任务上进行有监督的微调,例如文本分类、命名实体识别等。

  • 优点:模型精度高,且泛化性较好
  • 缺点:模型复杂度较高 

GPT 介绍

生成式预训练语言模型GPT

OpenAI公司在2018年提出的GPT(Generative Pre-Training)模型是典型的生成式预训练语言模型之一。GPT-2由多层Transformer组成的单向语言模型,主要可以分为输入层,编码层和输出层三部分。

GPT 也预留了文本预测、文本分类的相关架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921905.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025高教社国赛数学建模A题参考论文35页(含代码和模型)

2025国赛数学建模竞赛A题完整参考论文 目录 摘要 1 问题背景与重述 2 问题分析 2.1 问题一分析 2.2 问题二分析 2.3 问题三分析 2.4 问题四分析 2.5 问题五分析 3 符号说明 4 模型假设 5 模型建立与求解 5.1 问题一 烟幕有效遮蔽时长…

【Linux】常用命令汇总

【Linux】常用命令【一】tar命令【1】可用参数【2】常用案例&#xff08;1&#xff09;创建归档&#xff08;打包&#xff09;&#xff08;2&#xff09;查看归档内容&#xff1a;​​&#xff08;3&#xff09;解包归档 (提取)&#xff1a;​​【二】日志查看命令【1】基础命令…

软考系统架构设计师之软件系统建模

一、软件系统建模 系统建模流程包括如下&#xff1a; 二、人机交互设计 黄金三法则&#xff1a; 1、置于用户控制之下以不强迫用户进入不必要的或不希望的动作的方式来定义交互方式 提供灵活的交互 允许用户交互可以被中断和撤销 当技能级别增加时可以使交互流水化并允许定制交…

Linux系统学习之注意事项及命令基本格式

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01;你要是翻过历史文章的话&#xff0c;肯定特别疑惑&#xff1a;这神经病搞得这个号到底是啥定位&#xff0c;究竟是分享啥类型的&#xff0c;咋乱七八糟的啥都有。真是个杂货铺啥都有&#xff0c;咋又开始分享Linux系统了…

0基础Java学习过程记录——枚举、注解

一、枚举1.基本介绍&#xff08;1&#xff09;枚举对应英文 enumeration&#xff0c;简写为 enum&#xff08;2&#xff09;枚举是一组常量的集合&#xff08;3&#xff09;可以理解为&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象2.实现方式&a…

高效计算的源泉:深入浅出冯诺依曼模型与操作系统的管理艺术 —— 构建稳定、高效的应用基石 【底层逻辑/性能优化】

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨个人…

性能测试-jmeter9-逻辑控制器、定时器压力并发

课程&#xff1a;B站大学 记录软件测试-性能测试学习历程、掌握前端性能测试、后端性能测试、服务端性能测试的你才是一个专业的软件测试工程师 性能测试-jmeter逻辑控制器、定时器妙用IF控制器**IF 控制器的作用**循环控制器循环控制器的作用ForEach控制器ForEach 控制器的作用…

T:线段树入门(无区间更新)

线段树.线段树介绍.线段树框架.理解线段树.图式整个过程.线段树代码逐层解析.代码汇总.leetcode练习.线段树介绍 线段树(SegmentTree)\;\;\;\;\;\;\;\;线段树(SegmentTree)线段树(SegmentTree) is 用于高效处理区间查询和单点修改的数据结构&#xff0c;和树状数组很像&#xf…

【ISP】Charlite工具实操

实习一周了&#xff0c;参与了客观拍摄和测试&#xff0c;复习一下nv工具 BLACK LEVEL&#xff08;黑电平&#xff09; eg&#xff1a; $ nv_ob 0 in_dir <input directory> out_name <ob file> nv_ob 0 in_dir D:\study\nvraw\ob1 out_name D:\study\nvraw\my_out…

普蓝机器人 AutoTrack-IR-DR200 外设配置全指南

为什么外设配置对机器人研究如此重要&#xff1f;在当今机器人技术飞速发展的时代&#xff0c;高校学生研究团队正成为创新的重要力量。无论是参加机器人竞赛、开展毕业设计&#xff0c;还是进行学术研究&#xff0c;正确配置和使用外设设备都是成功的关键。尤其学生组装一个服…

8、Python性能优化与代码工程化

学习目标&#xff1a;掌握Python程序性能分析和优化的通用方法&#xff0c;建立工程化开发的规范意识&#xff0c;为后续AI项目开发奠定坚实的编程基础在数据科学和AI开发中&#xff0c;代码性能往往决定了项目的可行性。一个处理时间从几小时缩短到几分钟的优化&#xff0c;可…

【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解

通俗算法讲解推荐阅读: 【算法–链表】83.删除排序链表中的重复元素–通俗讲解 【算法–链表】删除排序链表中的重复元素 II–通俗讲解 【算法–链表】86.分割链表–通俗讲解 【算法】92.翻转链表Ⅱ–通俗讲解 【算法–链表】109.有序链表转换二叉搜索树–通俗讲解 【算法–链…

分词器(Tokenizer)总结(89)

分词器(Tokenizer)总结 分词器(Tokenizer) 分词器的词表(vocabulary)长度通常短于模型嵌入层(embedding layer)的长度。 结束标记(EOS token)应仅用于标记文本结尾,不可用于其他用途。 填充标记(PAD token)通常未预先定义,但你仍可能需要用到它: 对于生成式模型…

19 webUI应用中 Controlnet精讲(05)-图像修复与编辑

前面的篇章已经详细讲解了线条约束、三维关系与空间深度、人体姿态等几类controlnet的功能与应用&#xff0c;本节内容将对通过controlnet对图像修复与编辑进行讲解。 通过controlnet也可以对图片进行编辑、重绘及放大等操作&#xff0c;具体包括Recolor、Inpaint、Tile等&…

消息推送的三种常见方式:轮询、SSE、WebSocket

摘要&#xff1a;本文介绍消息推送的三种常见方式&#xff1a;轮询&#xff08;定时请求&#xff0c;易增负担&#xff09;与长轮询&#xff08;阻塞请求至有数据 / 超时&#xff0c;减少请求&#xff09;、SSE&#xff08;HTTP 单向实时传输&#xff0c;纯文本、自动重连&…

论文阅读:ACL 2024 Stealthy Attack on Large Language Model based Recommendation

总目录 大模型相关研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 https://arxiv.org/pdf/2402.14836 https://www.doubao.com/chat/19815566713551106 文章目录速览攻击方法速览一、攻击核心目标与前提1. 核心目标2. 攻击前提二、模型无关的简单…

自动驾驶中的传感器技术43——Radar(4)

本文对目前毫米波雷达中的天线设计进行比较全面的罗列&#xff0c;并进行简单的设计评述 1、实际设计案例 图1 涵盖能宽窄覆盖的天线设计&#xff08;无俯仰分辨率&#xff09;图2 Bosch前雷达的天线设计&#xff08;有俯仰的分辨率但比较弱&#xff0c;也涵盖了扩展覆盖&…

使用反转法线材质球,实现切换天空盒相同的功能,优点:包体变小

切换天空盒第一步先把SKY 天空球资源导入到工程里&#xff0c; 第二步&#xff1a;天空球文件下的SKY预制件拖入到场景里 第三步 选着SKY材质球&#xff0c;拖入自己的全景图片(图片分辨率不能超过5000*5000&#xff0c;否则手机无法显示) 如果并没有效果&#xff0c;看看图…

真正有效的数据指标体系应该长什么样?

真正有效的数据指标体系应该长什么样&#xff1f;为什么大多数企业的指标体系都是"花架子"&#xff1f;真正有效的指标体系应该长什么样&#xff1f;从数据到洞察&#xff1a;让指标真正"活"起来结语在这个人人都在谈数字化转型的时代&#xff0c;企业就像…

分布式专题——6 Redis缓存设计与性能优化

1 多级缓存架构2 缓存设计 2.1 缓存穿透 2.1.1 简介缓存穿透是什么&#xff1f;当查询一个根本不存在的数据时&#xff0c;缓存层和存储层都不会命中。正常逻辑下&#xff0c;存储层查不到数据就不会写入缓存层。这会导致&#xff1a;每次请求这个不存在的数据&#xff0c;都要…