大模型发展与学习探索-2

222 阅读6分钟

前言

上一篇学习了解到大模型的发展迅速,同时很多文章提到了《Attention Is All You Need》,决定学习学习。


Attention Is All You Need

传统的模型通常都是编码器和解码器组成的卷积神经网络。在这篇文章中提出一种Transformer网络架构,抛弃递归和卷积,通过注意力机制进行模型训练。

经典模型简单介绍

简单说一下,之前传统的的机器翻译等任务是通过序列到序列架构处理的。

image.png

  • 编码器(Encoder) :由绿色方框表示,包含多个单元如( h1 ,h2,h3)。它接收输入序列(如 x1,x2,x3),通过一系列处理(如循环神经网络等),将输入序列转换为固定长度的上下文向量(通常是最后一个隐藏状态 h3及其对应的隐藏状态序列),这个过程是对输入信息进行编码,提取关键特征并压缩成向量形式,以便后续解码器使用。
  • 解码器(Decoder) :由蓝色方框表示,包含多个单元(如 s1,s 2)。它利用编码器产生的上下文向量作为初始状态,逐步生成输出序列如( y1,y2)。在生成每个输出元素时,会根据前一个输出和当前的上下文向量来预测下一个输出元素,通过循环的方式不断扩展输出序列,直到生成完整的序列。

工作流程:

  • 输入序列 x1,x2,x3按顺序输入编码器,编码器依次处理每个输入元素,更新隐藏状态,最终得到最后一个隐藏状态 h3及其对应的隐藏状态序列,对输入信息进行编码。
  • 解码器以编码器的输出 h3为初始状态,开始生成输出序列。先基于 h3生成第一个解码器隐藏状态 s1并得到输出 y1,然后根据 y1​和 s1生成下一个解码器隐藏状态 s2,进而得到输出 y2​,重复此过程直至输出序列生成完成。

之前的方法和不足:

  • 顺序计算限制,无并行计算
  • 没有利用上历史信息
  • 资源需要大,训练时间慢

Transformer模型

image.png

  • 输入处理:输入序列经过嵌入(Input Embedding)层转换为向量表示。然后加上位置编码(Positional Encoding),用于给模型引入位置信息。 词嵌入:用向量表示词;我们知道向量表,通过向量表把单词与句子进行查找,但是这个就像词典,不方便。这里采用的是词嵌入,向量表示词。 循环神经网络那样的顺序处理机制来感知位置,而Transformer需要位置编码。

  • 编码器(Encoder):编码器由多个相同的层(图中Nx表示重复多次)堆叠而成。

    • 多头自注意力(Multi - Head Attention)机制 :让输入序列中的每个词能够同时关注序列中的其他所有词,从而捕获词与词之间的关系,实现并行计算,并且多头注意力可以捕捉不同位置的词之间的多种关系。 通过查询向量、键和值三个数据衡量单词在句中的关注度,也就是查询向量与各个键向量的相关性,进行点积注意力计算。
    • 前馈神经网络(Feed Forward) :对每个位置的向量进行相同的线性变换和激活函数处理,增加模型的非线性表达能力。 处理注意力层的输出,更好适应下一个注意力层的输入 每个子层都使用残差连接(Add & Norm),即输入先通过子层处理,再与输入相加,然后进行归一化,这有助于缓解梯度消失问题,促进模型的训练。
  • 解码器(Decoder):解码器同样由多个相同的层堆叠而成。

    • 掩码多头自注意力(Masked Multi - Head Attention) :与编码器中的多头自注意力类似,但由于解码器在生成序列时不能看到未来的位置,所以使用掩码来屏蔽后续位置的信息,确保每个位置的预测只依赖于前面的位置。 掩码多头自注意力机制通过在计算注意力分数时引入掩码操作,注意力权重趋近于0,从而确保每个位置只能依赖于前面的位置信息。
    • 编码器 - 解码器注意力(Encoder - Decoder Attention) :将编码器的输出作为键(Key)和值(Value),解码器的输出作为查询(Query),使得解码器能够聚焦于编码器输出中的相关部分,实现信息的有效传递和交互。
    • 前馈神经网络(Feed Forward) :与编码器中的前馈神经网络功能相同,进一步处理信息。

同编码器一样,解码器的每个子层也采用残差连接和归一化。

  • 输出处理:经过解码器处理后的输出,先通过一个线性层(Linear),将维度转换为词汇表的大小。然后通过Softmax层,将输出转换为概率分布,用于预测下一个词的概率。

简单代码实现

利用Trae生成一个简单的实现代码,进行学习。

class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()

    def forward(self, query, key, value, mask=None):
        d_k = query.size(-1)
        scores = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))

        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)

        attn_weights = F.softmax(scores, dim=-1)
        output = torch.matmul(attn_weights, value)
        return output, attn_weights
  • torch.matmul(query, key.transpose(-2, -1)) 计算查询和键的点积
  • sqrt(d_k)缩放点积
  • scores = scores.masked_fill(mask == 0, -1e9)应用了掩码
  • F.softmax(scores, dim=-1)是使用到了softmax函数进行概率分布
class MultiHeadAttention(nn.Module):
    def __init__(self, num_heads, d_model):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"

        self.d_k = d_model // num_heads
        self.num_heads = 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 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, num_heads, seq_length, d_k = x.size()
        return x.transpose(1, 2).contiguous().view(batch_size, seq_length, -1)

    def forward(self, query, key, value, mask=None):
        Q = self.split_heads(self.W_q(query))
        K = self.split_heads(self.W_k(key))
        V = self.split_heads(self.W_v(value))

        output, attn_weights = ScaledDotProductAttention()(Q, K, V, mask)
        output = self.combine_heads(output)
        output = self.W_o(output)

        return output, attn_weights
  • W_q、W_k、W_v 是用于生成查询、键和值的线性变换层。
  • split_heads是在每个头中独立计算注意力。
  • combine_heads 方法将分割后的张量形状转换。
  • 总的来说就是拆分然后拼接。
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)))
  • 前馈网络的实现:两个全连接层和一个ReLU激活函数。
class EncoderLayer(nn.Module):
    def __init__(self, num_heads, d_model, d_ff, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(num_heads, d_model)
        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
  • EncoderLayer 类,实现了Transformer架构中的一个编码器层。编码器层是Transformer模型的核心组件之一,它结合了多头自注意力机制和位置前馈网络,并使用残差连接和层归一化来稳定训练。

引用

www.zhihu.com/zvideo/1723… blog.csdn.net/weixin_4333…

总结

在接下来的学习过程中,我将与大家分享更多关于大模型的学习,也欢迎大家积极参与讨论和研究。让我们一起深入探索大模型的奥秘,共同成长和进步!