【菜狗学深度学习】注意力机制手撕——20251201

目录

1、定义数据、参数、导包

2、多头注意力初始化

nn.Linear()全连接层:

Softmax归一化的原因:

[❌ 问题 1:无法表示"关注程度"](#❌ 问题 1:无法表示“关注程度”)

[❌ 问题 2:不能归一化,梯度可能爆炸或不稳定](#❌ 问题 2:不能归一化,梯度可能爆炸或不稳定)

3、前向传播

[1、 拿到输入的基本维度](#1、 拿到输入的基本维度)

2、输入分别线性映射到Q\K\V空间

3、切头+维度转换

4、注意力得分Q*K转置

[5、生成下三角mask让模型 不能看到未来的 token。](#5、生成下三角mask让模型 不能看到未来的 token。)

[6. softmax 得到注意力权重,并乘上 v](#6. softmax 得到注意力权重,并乘上 v)

[7. 把多头结果拼回去](#7. 把多头结果拼回去)

手撕代码

注意力机制允许模型在序列的不同位置之间建立直接联系,无论这些位置相距多远,都能够有效地捕捉到它们之间的依赖关系。

注意力分数是什么?注意力分数用来量化注意力机制中某一部分信息被关注的程度,反映了信息在注意力机制中的重要性。在注意力机制中,模型会根据注意力分数来决定对不同输入信息的关注程度。

注意力机制要做的事情是:

让模型决定:输入序列中每个 token 应该关注哪些其他 token,并关注多少。


1、定义数据、参数、导包

导包之后,生成测试数据的batch、时序、一个词多少维度:

定义两个参数:1、把QKV映射到多少维度,2、有几个头。


2、多头注意力初始化

  • 多头注意力(Multi-Head Attention):将查询(Query)、键(Key)、值(Value)投影到不同子空间,并行计算多个注意力头后拼接结果,增强模型捕捉多样依赖关系的能力。

    class multi_head_attention(nn.Module):
    def init(self,d_model,n_head) ->None:
    super(multi_head_attention).init()
    self.n_head=n_head
    self.d_model=d_model
    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_combine=nn.Linear(d_model,d_model)
    self.softmax=nn.Softmax(dim=-1)

nn.Linear()全连接层:

对输入的每个向量(维度 = d_model),用三套不一样的 nn.Linear,把它分别变成 Q / K / V。我们把输入叫做"句子中每个词的表示,对每个词,我们建立了三个变换:

变换层 作用
w_q 把输入变成 Query: 用于问"我应该关注谁?"
w_k 把输入变成 Key: 用于告诉别人"我是谁,我有什么特征"
w_v 把输入变成 Value: 被注意力加权后的值

Q = w_q(x);K = w_k(x);V = w_v(x);就是把同一个输入做了三次不同的线性变换。

Softmax归一化的原因:

注意力分数来自:

但是这个 score 本身没有范围限制,可能是任意大、任意小、负数、正数......

如果直接用这些数加权V:attention_output = score * V

❌ 问题 1:无法表示"关注程度"

没有 softmax 时,score 可能长这样:tensor([[ 12.3, -2.1, 8.7, 0.6]])

这根本不能看出:

  • 哪个是"重点"

  • 哪个是不重要

  • 总量也不固定

❌ 问题 2:不能归一化,梯度可能爆炸或不稳定

如果 score 很大,比如 100、200、500,

加权后 V 会被放大 100 倍、500 倍,训练会极不稳定。

而Softmax 会把每一行注意力分数变成一个 **0~1 的概率分布,**注意力机制就能明确:

我应该把主要注意力放在哪个位置。

combine是因为这里涉及到多头的信息结合。


3、前向传播

复制代码
def forward(self,q,k,v):
    #得到qkv的维度
    batch,time,dimension = q.shape
    #每一个token的qkv的维度n_d
    n_d = self.d_model//self.n_head
    #qkv放到上面定义的映射里面,得到在QKV空间的一个表示
    q,k,v=self.w_q(q),self.w_k(k),self.w_v(v)

    #permute维度转换,方便多头计算
    q=q.view(batch,time,self.n_head,n_d).permute(0,2,1,3)
    k=q.view(batch,time,self.n_head,n_d).permute(0,2,1,3)
    v=v.view(batch,time,self.n_head,n_d).permute(0,2,1,3)

    score=q@k.transpose(2,3)/math.sqrt(n_d)
    mask=torch.tril(torch.ones(time,time,dtype=bool))
    score=score.masked_fill(mask==0,float("-inf"))
    score=self.softmax(score)@v

    score=score.permute(0,2,1,3).contiguous().view(batch,time,dimension)

1、拿到输入的基本维度

多头注意力 = 把 d_model 切成 n_head 份,每个头自己算一小份,分到n_d维。这里d_model=512, n_head=8 → n_d = 64

2、输入分别线性映射到Q\K\V空间

三个线性层

  • w_q:负责让 q 进入 Query 空间

  • w_k:负责让 k 进入 Key 空间

  • w_v:负责让 v 进入 Value 空间

3、切头+维度转换

4、注意力得分Q*K转置

k.transpose(2,3) 就是交换张量的第 2 和第 3 个维度

  • q: (batch, head, time, n_d)

  • k: (batch, head, time, n_d)k.transpose(2,3)(batch, head, n_d, time)

点积 @ 衡量两个向量相似度。(每组对应元素乘,再求所有积的和)

5、生成下三角mask让模型 不能看到未来的 token

复制代码
mask = torch.tril(torch.ones(time,time,dtype=bool))
score = score.masked_fill(mask==0, float("-inf"))
  • float("-inf"):表示负无穷。

6. softmax 得到注意力权重,并乘上 v

每个 head 对所有 token 的信息做加权平均

7. 把多头结果拼回去

------小狗照亮每一天

20251201

相关推荐
AI视觉网奇1 小时前
数字人 语音驱动
人工智能·python
宁大小白1 小时前
pythonstudy Day24
人工智能·机器学习
胡乱儿起个名1 小时前
Embedding查表操作
python·机器学习·embedding
伯远医学1 小时前
CUT&RUN
java·服务器·网络·人工智能·python·算法·eclipse
攻城狮7号1 小时前
微软开源的Fara-7B 如何让你的电脑长出“双手”
人工智能·fara-7b·微软开源小型语言模型·端侧ai·ai控制电脑
艾莉丝努力练剑1 小时前
【Python基础:语法第二课】Python 流程控制详解:条件语句 + 循环语句 + 人生重开模拟器实战
人工智能·爬虫·python·pycharm
Amctwd1 小时前
【数据挖掘】用户行为分析中的应用与模型构建
人工智能·数据挖掘
智链RFID1 小时前
信创RFID:涉密数据共享的“安全密钥”
网络·人工智能·安全