【菜狗学深度学习】注意力机制手撕——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几秒前
SNN脉冲神经网络:全连接、卷积与循环SNN
人工智能·深度学习·神经网络·机器学习·自然语言处理
程序猿编码5 分钟前
纯 C++ 手写 LLM 推理引擎:分页 KV 缓存、连续批、多卡张量并行全部内置
c++·深度学习·缓存·大模型推理·自动微分·模型推理
code2cat6 分钟前
【随笔】RAG检索缓存:复用证据之前,先把权限和版本放进缓存键
java·人工智能·缓存·知识库·rag
武天旭8 分钟前
AI安全--AI大模型的数据投毒攻击污染
人工智能·安全
2301_7671139811 分钟前
Codex、Claude Code 怎么系统学?四门官方课程与学习顺序
人工智能·学习方法·codex·1024程序员节·claude code
雨中飘荡的记忆11 分钟前
10 个值得关注的 GitHub Agent Skills:让 AI 编程从“能写”走向“会做”
人工智能
GlobalInfo16 分钟前
全球及中国机器人现场部署服务行业研究报告(2026版):市场现状、行业前景与占有率分析
大数据·运维·人工智能
汤姆宝库21 分钟前
检索增强深拆:嵌入、索引、重排与知识仲裁
人工智能
jimmyleeee26 分钟前
Agent安全之二:Tool Misuse & Exploitation(ASI02)----当合法工具被用来做错事
人工智能·安全
Jsundoku29 分钟前
第八章 机器学习 -- 集成学习
人工智能·机器学习·集成学习