【菜狗学深度学习】注意力机制手撕——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

相关推荐
波动几何9 分钟前
OpenClaw 构建指南:打造智能多工具编排运行时框架
人工智能
程序猿追10 分钟前
深度解码AI之魂:CANN Compiler 核心架构与技术演进
人工智能·架构
新缸中之脑12 分钟前
Figma Make 提示工程
人工智能·figma
赫尔·普莱蒂科萨·帕塔13 分钟前
智能体工程
人工智能·机器人·软件工程·agi
觉醒大王15 分钟前
AI写的青基中了
人工智能·笔记·深度学习·学习·职场和发展·学习方法
深鱼~16 分钟前
深度剖析ops-transformer:LayerNorm与GEMM的融合优化
人工智能·深度学习·transformer
哈__19 分钟前
CANN图优化技术:深度学习模型的编译器魔法
人工智能·深度学习
灰灰勇闯IT21 分钟前
神经网络的基石——深度解析 CANN ops-nn 算子库如何赋能昇腾 AI
人工智能·深度学习·神经网络
秋邱22 分钟前
深度解析CANN与AIGC的核心联系:算力底座赋能生成式AI规模化落地
人工智能·aigc
一枕眠秋雨>o<23 分钟前
数学的底座:ops-math如何为AI计算注入确定性
人工智能