【教程】从0开始搭建大语言模型:实现Attention机制

从0开始搭建大语言模型

  • 从0开始搭建大语言模型:实现Attention机制
    • 建模长序列存在的问题
    • 使用attention机制获得数据间的依赖
    • Self-attention
      • 介绍
      • 带有可训练权重的self-attention
        • 1.生成Q,K,V变量
        • 2.计算attention score
        • 3.attention weight的获得
        • 4.计算context vector
        • 5.对于query, key, value的理解
        • 6.masked attention的应用
        • 7.Dropout的使用
        • 8.多头注意力的应用

从0开始搭建大语言模型:实现Attention机制

接上文:【教程】从0开始搭建大语言模型:Word和位置Embedding

建模长序列存在的问题

思考一个问题,之前语言模型没有注意力机制,它有什么问题?

当将文本从一种语言翻译成另一种语言时,例如从德语翻译成英语,仅仅是逐字翻译是不可能的。相反,翻译过程需要上下文理解和语法对齐。

为了解决这个问题,通常使用具有两个子模块的深度神经网络,即所谓的编码器和解码器。编码器的工作首先是读入并处理整个文本,然后解码器生成翻译后的文本。

在transformer出现之前,循环神经网络(RNN)是语言翻译中最流行的编码器-解码器架构。RNN是一种神经网络,前一步的输出作为当前步骤的输入,使其非常适合于文本等顺序数据。

下图是RNN进行语言翻译的例子:
在这里插入图片描述
编码器将来自源语言的token序列作为输入,其中编码器的隐藏状态(中间神经网络层)对整个输入序列的压缩表示进行编码。然后,解码器使用其当前的隐藏状态开始一个词一个词的翻译。

上面这种结果存在不足:RNN不能在解码阶段直接从编码器获取早期的隐藏状态。因此,它仅依赖于当前隐藏状态,其中保存了所有相关信息。这可能会导致上下文的丢失,特别是在依赖关系可能跨越长距离的复杂句子中。

使用attention机制获得数据间的依赖

前面提到,RNN可以很好地翻译短句,但不能很好地翻译较长的文本,因为它们不能直接访问输入中之前的单词。

为了解决这个问题,研究人员为RNN开发了所谓的Bahdanau注意力机制,如下:
在这里插入图片描述
上图的网络的文本生成解码器部分可以有选择地访问所有输入token,因此在生成给定输出token时,某些输入token比其他标记更重要,类似于注意力权重。

受Bahdanau注意力的启发,自注意力被提出,它允许在计算序列表示时,输入序列中的每个位置都关注同一序列中的所有位置。

Self-attention

介绍

在自注意力中,“self”是指机制通过关联单个输入序列中的不同位置来计算注意力权重的能力。它评估和学习输入本身不同部分之间的关系和依赖关系,例如句子中的单词或图像中的像素。

自注意力的目标是为每个输入元素计算一个上下文向量,该向量结合了来自所有其他输入元素的信息,如下图所示:
在这里插入图片描述
在self-attention中,我们的目标是为输入序列中的每个元素x (i)计算上下文向量z(i)。上下文向量可以被解释为丰富的embedding向量。

在self-attention中,上下文向量起着至关重要的作用。它们的目的是通过合并来自序列中所有其他元素的信息,创建输入序列中每个元素(如句子)的丰富表示。

x(2)和其他元素的attention score的计算如下图所示,通过计算点积得到。
在这里插入图片描述
点积是一种相似性度量,因为它量化了两个向量之间的对齐程度:

  • 点积越高,表示向量之间的对齐程度或相似性越高。
  • 在自注意力机制的背景下,点积决定了序列中元素相互关注的程度
    • 点积越高,两个元素之间的相似性和注意力分数越高

得到attention score后,还需要归一化得到最终的attention weights,如下图:
在这里插入图片描述
归一化背后的主要目标是获得总和为1的注意力权重。这种归一化是一种约定,对于解释和保持LLM的训练稳定性很有用。

在实践中,使用softmax函数进行归一化更常见,也更可取。这种方法可以更好地管理极值,并在训练过程中提供更有利的梯度属性。此外,softmax函数确保注意力权重始终为正。这使得输出可以解释为概率或相对重要性,其中权重越大,说明重要性越高。

最后,将各个元素的信息通过attention weights结合其他,得到x(2)的上下文信息z(2):
在这里插入图片描述
如果要对所有token计算它与其他token的信息,可以使用矩阵乘法,而不是for循环,因为for循环计算效率低。

带有可训练权重的self-attention

在LLM中,self-attention中权重矩阵会在模型训练过程中更新。这些可训练的权重矩阵至关重要,以便模型(特别是模型中的注意力模块)可以学习产生“好的”上下文向量。

1.生成Q,K,V变量

引入三个可训练的权重矩阵Wq、Wk和Wv,这三个矩阵用于将embedding的输入标记x(i)投影为query、key和value,如下:
在这里插入图片描述
需要注意,权重参数(权重矩阵的值)是定义网络连接的基本的学习系数,而注意力权重是动态的、特定于上下文的值。

2.计算attention score

在这里插入图片描述
注意力分数计算是一种点积计算,需要注意的是:我们不直接计算输入元素之间的点积,而是使用通过各自的权重矩阵转换输入得到的query和key。

3.attention weight的获得

如下图,在计算出注意力分数ω之后,下一步是使用softmax函数对这些分数进行归一化,以获得注意力权重:
在这里插入图片描述
需要注意的是,我们通过除以key的embedding维度的平方根来缩放注意力分数。缩放注意力的原因:

  • 通过避免小梯度来提高训练性能。例如,当扩大嵌入维度时(对于类似gpt的LLM通常大于1000),由于对其应用了softmax函数,在反向传播过程中,较大的点积可能会导致非常小的梯度
  • 随着点积的增加,softmax函数的行为更像一个阶梯函数,导致梯度接近于零。这些小的梯度可能会大大减慢学习速度或导致训练停滞、

对embedding维度平方根的缩放是这种自注意力机制也被称为scaled-dot product attention的原因。

4.计算context vector

在这里插入图片描述
在自注意力计算的最后一步,我们通过注意力权重组合所有value向量来计算上下文向量。注意力权重作为一个权重因子,对每个值向量的重要性进行加权。

5.对于query, key, value的理解

注意力机制上下文中的术语“query”、“key”和“value”是从信息检索和数据库领域借用的。

“query”类似于数据库中的搜索查询。它表示模型关注或试图理解的当前项(例如,句子中的一个单词或标记)。query用于探测输入序列的其他部分,以确定对它们的关注程度。

“key”类似于数据库中用于索引和搜索的key。在注意力机制中,输入序列中的每个项目(例如,句子中的每个单词)都有一个关联的关键字。这些键用于匹配查询。

这里的“value”类似于数据库中键值对中的值。它表示输入项的实际内容或表示。一旦模型确定了哪些键(以及输入的哪些部分)与查询(当前焦点项)最相关,它就检索相应的值。

上面过程的总体流程为:
在这里插入图片描述
在self-attention中,我们用三个权重矩阵Wq、Wk和Wv对输入矩阵X中的输入向量进行变换。然后,我们根据结果查询(Q)和键(K)计算注意力权重矩阵。使用注意力权重和值(V),然后计算上下文向量(Z)。

使用权重矩阵的代码为:

import torch.nn as nn
class SelfAttention_v1(nn.Module):
    def __init__(self, d_in, d_out):
        super().__init__()
        self.d_out = d_out
        self.W_query = nn.Parameter(torch.rand(d_in, d_out))
        self.W_key = nn.Parameter(torch.rand(d_in, d_out))
        self.W_value = nn.Parameter(torch.rand(d_in, d_out))

    def forward(self, x):
        keys = x @ self.W_key
        queries = x @ self.W_query
        values = x @ self.W_value
        attn_scores = queries @ keys.T # omega
        attn_weights = torch.softmax(
        attn_scores / keys.shape[-1]**0.5, dim=-1)
        context_vec = attn_weights @ values
        return context_vec

import torch
inputs = torch.tensor(
    [[0.43, 0.15, 0.89], # Your (x^1)
    [0.55, 0.87, 0.66], # journey (x^2)
    [0.57, 0.85, 0.64], # starts (x^3)
    [0.22, 0.58, 0.33], # with (x^4)
    [0.77, 0.25, 0.10], # one (x^5)
    [0.05, 0.80, 0.55]] # step (x^6)
)
torch.manual_seed(123)
d_in = inputs.shape[1] #B
d_out = 2 #C
sa_v1 = SelfAttention_v1(d_in, d_out)
print(sa_v1(inputs))

我们也可以使用nn.Linear实现上述过程,代码为:

class SelfAttention_v2(nn.Module):
    def __init__(self, d_in, d_out, qkv_bias=False):
        super().__init__()
        self.d_out = d_out
        self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
    def forward(self, x):
        keys = self.W_key(x)
        queries = self.W_query(x)
        values = self.W_value(x)
        attn_scores = queries @ keys.T
        attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=1)
        context_vec = attn_weights @ values
        return context_vec
torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print(sa_v2(inputs))

需要注意的是,Linear将权重矩阵存储为转置形式。

6.masked attention的应用

masked attention限制模型在处理任何给定token时只考虑序列中以前和当前的输入。这与标准的自注意力机制不一样,后者允许一次访问整个输入序列。在计算注意力分数时,masked attention机制确保模型只考虑序列中出现在当前token之前或之前的token。

如下图,在masked attention中,我们屏蔽了对角线上的注意力权重,使得对于给定的输入,LLM在使用注意力权重计算上下文向量时无法访问未来的token。
在这里插入图片描述
在上图中,我们mask掉对角线上的注意力权重,并对未mask的注意力权重进行归一化,使每行的注意力权重之和为1。

获得masked attention weights的一种方式如下:
在这里插入图片描述
需要注意:

  • 当我们应用掩码,然后重新规范化注意力权重时,最初可能看起来来自未来token(打算mask)的信息仍然可能影响当前token,因为它们的值是softmax计算的一部分。
  • 当我们重新规范化掩码后的注意力权重时,我们本质上所做的是在较小的子集上重新计算softmax(因为掩码位置对softmax值没有贡献)

softmax的数学优雅之处在于,尽管最初在分母中包括所有位置,但在掩码和重归一化之后,掩码位置的影响被消除——它们不会以任何有意义的方式对softmax分数做出贡献。

另外一种更高效的实现方法如下:
在这里插入图片描述

7.Dropout的使用

深度学习中的Dropout是一种在训练过程中忽略随机选择的隐藏层单元的技术,有效地“丢弃”它们。这种方法通过确保模型不过度依赖任何特定的隐藏层单元集,有助于防止过拟合。需要强调的是,dropout只在训练过程中使用,并在训练结束后停用。

在transformer架构中,包括像GPT这样的模型,注意力机制中的dropout通常应用于两个特定领域:

  • 计算注意力分数后或
  • 注意力权重应用于value向量后

随机去掉一些attention weights的示例如下:
在这里插入图片描述
当将dropout应用于一个dropout率为50%的注意力权重矩阵时,矩阵中有一半元素随机设置为0。为了补偿活跃元素的减少,矩阵中剩余元素的值按比例放大1/0.5 =2。这种缩放对于保持注意力权重的整体平衡至关重要,确保注意力机制的平均影响在训练和推理阶段保持一致。

结合了mask attention和dropout的注意力代码为:

class CausalAttention(nn.Module):
    def __init__(self, d_in, d_out, context_length, dropout, qkv_bias=False):
        super().__init__()
        self.d_out = d_out
        self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.dropout = nn.Dropout(dropout) #A
        # 下三角,context_length为文本长度
        self.register_buffer('mask',torch.triu(torch.ones(context_length, context_length), diagonal=1)) #B
    def forward(self, x):
        b, num_tokens, d_in = x.shape #C
        # New batch dimension b
        keys = self.W_key(x)
        queries = self.W_query(x)
        values = self.W_value(x)
        attn_scores = queries @ keys.transpose(1, 2) #C
        # mask操作
        attn_scores.masked_fill_(self.mask.bool()[:num_tokens, :num_tokens], -torch.inf)
        attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
        attn_weights = self.dropout(attn_weights)
        context_vec = attn_weights @ values
        return context_vec
torch.manual_seed(123)
batch = torch.stack((inputs, inputs), dim=0)
print(batch.shape)
context_length = batch.shape[1]
ca = CausalAttention(d_in, d_out, context_length, 0.0)
context_vecs = ca(batch)
print("context_vecs.shape:", context_vecs.shape)
8.多头注意力的应用

“多头”是指将注意力机制划分为多个“头”,每个“头”独立操作。在这种情况下,单个注意力模块可以被认为是单头注意力,其中只有一组注意力权重按顺序处理输入,而多头是有多组权重处理输入。

实现多头可以通过堆叠多个attention模块来实现,如下图:

在这里插入图片描述
如上图,在一个有两个头的多头注意力模块中,query, key和value各有两个权重矩阵,该部分用代码实现为:

class MultiHeadAttentionWrapper(nn.Module):
    def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
        super().__init__()
        self.heads = nn.ModuleList([CausalAttention(d_in, d_out, context_length, dropout, qkv_bias) for _ in range(num_heads)]
        )
    def forward(self, x):
        return torch.cat([head(x) for head in self.heads], dim=-1)

需要注意,最后需要将每个头得到的context vector拼接,如下图:
在这里插入图片描述
上面实现多头的方法不能并行处理每一个头,事实上,可以通过矩阵乘法同时计算所有注意力头的输出。

具体来说,可以通过reshape投影的query、key和value张量,将输入分成多个头,然后在计算注意力后结合这些头的结果。

代码为:

class MultiHeadAttention(nn.Module):
    def __init__(self, d_in, d_out,
        context_length, dropout, num_heads, qkv_bias=False):
        super().__init__()
        assert d_out % num_heads == 0, "d_out must be divisible by num_heads"
        self.d_out = d_out
        self.num_heads = num_heads
        # 每个头中的输出维度
        self.head_dim = d_out // num_heads #A
        self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.out_proj = nn.Linear(d_out, d_out) #B
        self.dropout = nn.Dropout(dropout)
        self.register_buffer( 'mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)
    )
    
    def forward(self, x):
        b, num_tokens, d_in = x.shape
        keys = self.W_key(x) #C
        queries = self.W_query(x) #C
        values = self.W_value(x) #C
        # 将映射的变量reshape成多个头
        keys = keys.view(b, num_tokens, self.num_heads, self.head_dim) #D
        values = values.view(b, num_tokens, self.num_heads, self.head_dim) #D
        queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)#D
        # 交换矩阵的维度
        keys = keys.transpose(1, 2) #E
        queries = queries.transpose(1, 2) #E
        values = values.transpose(1, 2) #E
        attn_scores = queries @ keys.transpose(2, 3) #F
        mask_bool = self.mask.bool()[:num_tokens, :num_tokens] #G
        attn_scores.masked_fill_(mask_bool, -torch.inf) #H
        attn_weights = torch.softmax(
        attn_scores / keys.shape[-1]**0.5, dim=-1)
        attn_weights = self.dropout(attn_weights)
        context_vec = (attn_weights @ values).transpose(1, 2) #I
        #J
        context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
        context_vec = self.out_proj(context_vec) #K
        return context_vec

将query,key,value变成(b, num_heads,num_tokens, head_dim)是很重要的,矩阵乘法在最后2个维度(num_tokens, head_dim)之间进行,然后对每个头重复。

除此之外,代码中还加入了output projection layer,这在LLM的代码中经常能看到。

该方法比第一种方法更加高效,因为只需要一次矩阵乘法就可以计算出key,而在第一种方法中,我们需要为每个注意力头重复这个矩阵乘法,这是计算成本最高的步骤之一。它们两个的区别如下:
在这里插入图片描述
需要注意的是,最小的GPT-2模型(1.17亿个参数)具有12个注意力头和768个上下文向量embedding大小。最大的GPT-2模型(15亿个参数)有25个注意力头和1600个上下文向量embedding大小。token输入的embedding大小和上下文embedding在GPT模型中是相同的(d_in = d_out)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PS2045L-ASEMI低Low VF肖特基PS2045L

编辑:ll PS2045L-ASEMI低Low VF肖特基PS2045L 型号:PS2045L 品牌:ASEMI 封装:TO-277 最大平均正向电流(IF):20A 最大循环峰值反向电压(VRRM):45V 最大…

Armbian OS(基于ubuntu24) 源码编译mysql 5.7

最近弄了个S905X3的盒子刷完Armbian OS (基于ubuntu24),开始折腾Arm64之旅。第一站就遇到了MySQL的问题,由于MySQL没有提供Arm64版本,又不想塞Docker镜像,因此选择源码来编译MySQL5.7。下面记录详细过程和遇…

马斯克的战略选择:特斯拉的H100显卡转移风波及其影响

引言 最近,一则关于马斯克将特斯拉的H100显卡转给他的新公司xAI的消息引发了广泛关注。这一决定不仅导致特斯拉股价下跌,还引发了关于马斯克战略决策的激烈讨论。本文将深入探讨这一事件的背景、过程及其对特斯拉和整个科技行业的影响。 背景与事件回顾…

8.transformers量化

Transformers 核心设计Auto Classes Transformers Auto Classes 设计:统一接口、自动检索 AutoClasses 旨在通过全局统一的接口 from_pretrained() ,实现基于名称(路径)自动检索预训练权重(模 型)、配置文件、词汇表等所有与模型相关的抽象。 灵活扩展的配置AutoConfig…

本地GPT-window平台 搭建ChatGLM3-6B

一 ChatGLM-6B 介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,新一代开源模型 ChatGLM3-6B 已发布,拥有10B以下最强的基础模型,支持工具调用(Function Call)、代码执行(Code Interpreter&…

【Python】成功解决ModuleNotFoundError: No module named ‘PyQt5‘

【Python】成功解决ModuleNotFoundError: No module named ‘PyQt5’ 下滑即可查看博客内容 🌈 欢迎莅临我的个人主页 👈这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地!🎇 🎓 博主简介:985…

c语言回顾-函数递归

1.递归的介绍 1.1什么是递归 递归是指在一个函数的定义中调用自身的过程。简单来说,递归是一种通过重复调用自身来解决问题的方法。 递归包括两个关键要素:基本情况和递归情况。基本情况是指当问题达到某个特定条件时,不再需要递归调用&am…

SpringBoot整合SpringDataRedis

目录 1.导入Maven坐标 2.配置相关的数据源 3.编写配置类 4.通过RedisTemplate对象操作Redis SpringBoot整合Redis有很多种,这里使用的是Spring Data Redis。接下来就springboot整合springDataRedis步骤做一个详细介绍。 1.导入Maven坐标 首先,需要导…

LLM应用实战:当图谱问答(KBQA)集成大模型(三)

1. 背景 最近比较忙(也有点茫),本qiang~想切入多模态大模型领域,所以一直在潜心研读中... 本次的更新内容主要是响应图谱问答集成LLM项目中反馈问题的优化总结,对KBQA集成LLM不熟悉的客官可以翻翻之前的文章《LLM应用实战:当KBQ…

弘君资本:苹果股价暴涨,创历史新高!

当地时间6月11日,美股三大指数涨跌纷歧,标普500指数与纳指再创新高。 到收盘,道指跌0.31%,纳指涨0.88%,标普500指数涨0.27%。 苹果大涨逾7%创前史新高。美联储开端召开6月货币方针会议,周三发布利率决定。…

传神论文中心|第11期人工智能领域论文推荐

在人工智能领域的快速发展中,我们不断看到令人振奋的技术进步和创新。近期,开放传神(OpenCSG)社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自的论…

如何进行电子故障失效分析FA?

在电子主板生产的过程中,一般都会出现失效不良的主板,因为是因为各种各样的原因所导致的,比如短路,开路,本身元件的问题或者是认为操作不当等等所引起的。 所以在电子故障的分析中,需要考虑这些因素&#x…

5.5 业务流程和业务逻辑设计

一、引言 1.1 项目背景 经过上述的论述,我们讨论一下业务流程和业务逻辑设计,通过合理的业务流程设计和业务逻辑设计,可以提高用户的购物体验,降低用户的操作成本,并确保用户的购物行为符合平台的规则和要求。同时&a…

旅游网页(HTML+CSS+JS)

前言 本篇博客就不给大家讲解了,直接上代码 💓 个人主页:普通young man-CSDN博客 ⏩ 文章专栏:https://blog.csdn.net/2302_78381559/category_12644031.html?spm1001.2014.3001.5482https://blog.csdn.net/2302_78381559/catego…

Linux防火墙管理

计算机防火墙用于保护内部网络,主机和网络安全,有硬件防火墙和软件防火墙两种,软件主要是用对数据包进行分析过滤来保证软件层面安全。 此外还有根据对数据封包形式确定的分类方法, 如代理服务器,类似网关的形式监控整…

Mcgs 屏幕Modbus RTU通讯调试

目录 1. 设备窗口1.1 添加设备构件1.2 设备配置1.2.1 通用串口父设备配置1.2.2 设备0--ModbusRTU配置2. 设计用户窗口2.1 关联设备通道与实时数据库2.3 用户窗口3. 通信测试本文想要实现通过Modbus协议与Mcgs屏幕进行通信收发数据。在使用Mcgs屏幕进行Modbus通信时,一般Mcgs屏…

如何完美解决 sun.security.validator.ValidatorException: PKIX path building failed

如何完美解决 sun.security.validator.ValidatorException: PKIX path building failed 博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的…

一种改进盲解卷积算法在旋转机械故障诊断中的应用(MATLAB)

滚动轴承故障形成后,故障区与其他零部件表面接触将产生循环平稳的瞬态脉冲。由于受到系统传递函数、轴转频和环境噪声的干扰,故障脉冲特征受到大幅衰减,在测得信号中表现十分微弱甚至完全不可见。盲解卷积算法通过搜索一个最优的有限脉冲响应…

“面向绿色流域构建的生态处理技术创新与实践论坛”在成都召开

由中华环保联合会、福州大学、上海大学联合主办,中华环保联合会水环境治理专业委员会、福建省环境功能材料先进技术工程研究中心、上海大学环境与化学工程学院承办的“2024全国水科技大会暨技术装备成果展览会”于5月14日在成都世纪城国际会议中心隆重开幕。 期间&a…

Python 中 Selenium 的 send_keys() 函数

我们将介绍 Selenium Python 中的 send_keys() 函数并演示其用法。 任何应用程序在进入市场之前都需要经过一些测试。 应用程序应首先满足与其名称相关的所有要求。 我们应该全面测试应用程序,因为没有人能够预测给予应用程序的确切输入。 Python Selenium 可以帮…