LLM大模型基础-自注意力机制:QKV和工作原理、多头自注意力机制、位置关联、层归一化
文章目录
一、自注意力机制
自注意力机制(Self-Attention)是现代深度学习模型,尤其是在自然语言处理(NLP)和计算机视觉(CV)领域中广泛应用的一种技术。最早在 Transformer 模型中提出,并成为许多先进模型(例如 BERT、GPT)的核心组件。
1.什么是自注意力机制?
自注意力机制的核心思想是:在处理某个输入数据时,模型会根据该输入中的不同部分之间的关系来“关注”不同的位置,并加权每个位置的重要性。这意味着,每个输入元素(例如一个词或一个像素)都可以根据其他元素来动态调整自己的表示。
2.为什么需要自注意力机制?
传统的神经网络(如RNN、LSTM)在处理序列数据时,通常是逐步处理输入序列的。然而,这些网络在捕捉长距离依赖(即输入序列中远距离元素之间的关系)时会遇到困难。RNN、LSTM 通过逐步计算“记忆”来处理序列,但它们仍然难以高效地建模全局信息,尤其是长序列中的关系。
用CNN的思想来解决并行化问题,虽然解决了捕捉长时序问题,但堆叠的层数过多,因此提出自注意力机制。
CNN通过层的堆叠,提高感受野,使得上层输出可以捕获长时序关系
自注意力机制通过直接计算序列中任意两个元素之间的关系,能够有效地捕捉长距离依赖关系,从而避免了传统方法的限制。
b 1 , b 2 , b 3 , b 4 b^1,b^2,b^3,b^4 b1,b2,b3,b4 的计算是基于整个输入的,且并行计算,无需像CNN那样堆叠层
2.Q K V
为了提升表达能力和灵活度,我们不能直接使用一个词嵌入向量。
自注意力机制通过引入查询向量(Query)、键向量(Key)、值向量(Value) 概念来实现序列中各元素之间的信息交互和依赖建模。
2.1 Query(查询)
作用:Query 是用于“查询”其他元素的信息。每个输入元素都有一个对应的 Query,它决定了当前元素在与其他元素交互时所关注的内容。
- 直观理解:你可以把 Query 看作是你在当前位置上发出的一个“问题”。你想知道其他位置的哪些信息对你来说是重要的,Query 就是这个“问题”。
- 计算过程:Query 与 Key 的相似度决定了它对每个 Value 的关注度。因此,Query 的作用是引导模型决定哪些 Key(也就是其他元素的信息)对当前元素最为相关。
2.2 Key(键)
作用:Key 是与其他元素进行“匹配”的信息。每个输入元素也有一个对应的 Key,它携带了元素的特征信息,用来与 Query 进行比较。
- 直观理解:Key 就像是数据库中的“键”,它决定了查询(Query)是否能匹配到该位置的内容。每个输入元素都有一个 Key,它是其他位置的“标签”,用来和 Query 进行匹配。
- 计算过程:Query 和 Key 之间的相似度决定了该 Query 应该关注这个位置的 Value 向量多少。通常,这个相似度通过点积(即内积)来计算。相似度越高,意味着当前的 Query 更加“关注”该位置的信息。
2.3 Value(值)
作用:Value 是最终用于更新当前元素表示的内容。在自注意力机制中,Value 是每个元素的特征信息,它会根据 Query 与 Key 之间的关系来决定是否被采纳和如何采纳。
- 直观理解:Value 就是我们最终想要用来更新当前元素表示的信息。它是输入数据的实际内容,通过 Query 和 Key 计算出的权重来加权整合。
- 计算过程:Value 是通过加权求和的方式影响当前元素的输出。每个 Query 都会根据它与其他 Key 的相似度来决定每个 Value 对它的贡献度。因此,Value 是最终影响当前元素新表示的元素。
关系
- Query 和 Key 计算相似度:通过计算 Query 和 Key 之间的相似度(通常是通过点积操作),我们能得到每个元素对其他元素的“关注度”。这个过程决定了每个元素的 Query 会与哪些元素的 Key 有较强的相关性。
- 权重化 Value:得到相似度后,我们对每个 Key 对应的 Value 进行加权,权重由 Query 与 Key 的相似度决定。加权求和后的结果即为当前元素的新表示,它融合了与其相关的其他元素的信息。
3.自注意力机制的工作原理
简单来说,自注意力机制就是找到当前词和所在句子的所有此之间的关联关系。目标是让每个位置的表示能够根据整个序列中其他位置的信息进行加权融合,从而捕获实体之间的相互关系。
假设我们有一个长度为 n n n 的输入序列,每个元素是一个向量表示。自注意力机制通过计算每个元素与其他元素的关系来生成新的表示。这个过程通常包括以下步骤:
1.输入表示:
假设我们有一个输入序列 X = [ x 1 , x 2 , … , x n ] X = [x_1, x_2, \dots, x_n] X=[x1,x2,…,xn],每个 x i x_i xi 是一个向量,表示输入序列中的一个元素。
x i x_i xi向量通过词嵌入获取
def test():
embedding=nn.Embedding(10,512)#比如语料有10个单词,转换后每个单词的维度是512
input=torch.randint(0,10,(512,),dtype=torch.int64)#随便给一个输入
embedding_out=embedding(input)#每个数字被映射为5个数字的向量
print(embedding_out,embedding_out.shape)
2.计算 Query、Key 和 Value:
对于每个输入元素 x i x_i xi,我们通过三个不同的权重矩阵分别将它映射为 Query(查询向量)、Key(键向量)和 Value(值向量)。
这可以通过矩阵乘法实现: Q = X W Q Q = X W_Q Q=XWQ, K = X W K , V = X W V \quad K = X W_K, \quad V = X W_V K=XWK,V=XWV
其中, W Q W_Q WQ、 W K W_K WK、 W V W_V WV是学习的权重矩阵,Q、K 和 V 是通过这些权重计算得到的向量。
通过线性变换得到三个向量的变化如下图所示:
3.计算注意力权重:
注意力的计算过程是计算 Query 和 Key 之间的相关性/相似度,通常使用点积来衡量它们的相似度: Attention ( Q i , K j ) = Q i T K j \text{Attention}(Q_i, K_j) = Q_i^T K_j Attention(Qi,Kj)=QiTKj
除以缩放因子 d k \sqrt{d_k} dk 来避免数值过大,使得梯度稳定更新。得到注意力得分矩阵:
Attention ( Q i , K j ) = Q i K j T d k \text{Attention}(Q_i, K_j) = \frac{Q_iK_j^T}{\sqrt{d_k}} Attention(Qi,Kj)=dkQiKjT
这个分数表示了元素 x i x_i xi 和 x j x_j xj之间的关系或相似度。
4.应用 Softmax 计算权重:
为了确保注意力权重在 0 到 1 之间,并且所有的权重和为 1,我们对每个查询 Q i Q_i Qi 和所有键 K j K_j Kj 的得分应用 Softmax 函数: α i j = Softmax ( Q i K j T d k ) \alpha_{ij} = \text{Softmax} \left( \frac{Q_i K_j^T}{\sqrt{d_k}} \right) αij=Softmax(dkQiKjT)
这会给出一个表示每个位置对于当前位置的重要性的权重。
5.加权求和:
对于每个输入元素 x i x_i xi,我们使用这些权重对 Value 向量进行加权求和,得到每个元素的新表示:
Output = Attention Weight × V = softmax ( Q K T d k ) × V \text{Output} =\text{Attention Weight} \times V = \text{softmax} \left( \frac{QK^T}{\sqrt{d_k}} \right) \times V Output=Attention Weight×V=softmax(dkQKT)×V
加权求和是乘对应位置的权重,并将这些加权后的 Value 向量进行求和。每个词的表示都会根据它与其他词的关系(通过注意力权重)来更新,这样能够有效地捕捉上下文信息。
Q和K计算相似度后,经 s o f t m a x softmax softmax 得到注意力,再乘V,最后相加得到包含注意力的输出
6.输出:
最终得到的输出是一个维度为 n × d v n \times d_v n×dv 的新矩阵,其中每个元素的表示都被加权了。
本质:将Query和Key分别计算相似性,然后经过softmax得到相似性概率权重即注意力,再乘以Value,最后相加即可得到包含注意力的输出
【示例】假如有个简单的例子:“我 喜欢 编程”
步骤1:输入表示
我们先将每个单词转化为向量表示(假设它们已经通过词向量嵌入)。比如:
- “我” → x1
- “喜欢” → x2
- “编程” → x3
这些向量 x1,x2,x3组成输入序列 X=[x1,x2,x3]
步骤2:计算 Query、Key 和 Value
然后我们为每个词生成 Query、Key 和 Value,通过对输入序列进行矩阵乘法来得到这些向量:
- 对于“我”来说,生成 Q1, K1 V1
- 对于“喜欢”来说,生成 Q2, K2, V2
- 对于“编程”来说,生成 Q3, K3, V3
步骤3:计算注意力权重
接下来,我们计算每个 Query 和其他单词的 Key 之间的相似度(通过点积),来衡量它们的相关性。比如:
- 对于单词“我”的 Query(Q1),我们计算它与所有其他单词 Key 的相似度:
- Attention(Q1,K1)
- Attention(Q1,K2)
- Attention(Q1,K3)
这个得分表示了“我”与其他单词(“喜欢”和“编程”)的关系。
步骤4:应用 Softmax 计算权重
然后,我们对每个 Query 和所有 Key 计算出的得分应用 Softmax 函数,得到每个位置的注意力权重(在 0 到 1 之间)。这些权重决定了每个单词对当前单词的影响程度。例如,对于“我”来说,可能:
- α11=0.6(自己“我”的关注度)
- α12=0.3(“喜欢”的关注度)
- α13=0.1(“编程”的关注度)
步骤5:加权求和
接下来,我们使用这些注意力权重对每个单词的 Value 向量进行加权求和,得到每个单词的新表示。例如:
- 对于“我”来说,它的新表示是:
z1=0.6V1+0.3V2+0.1V3
通过这种加权求和,单词“我”的新表示包含了它与“喜欢”和“编程”之间的关系。
步骤6:输出
最后,所有的加权求和结果 z1,z2,z3 就是自注意力机制处理后的新表示。这些新的表示融合了每个单词与其他单词之间的上下文信息。
4.代码实现
用代码实现一个 自注意力机制 的简单示例
import torch
import torch.nn as nn
vocab = {
"我": 0,
"是": 1,
"人工智能": 2,
"的": 3,
"一名": 4,
"学生": 5
}
# 词数
num_words = len(vocab)
# 词嵌入
embedding = nn.Embedding(num_words, 512)
vocab_id = [vocab[word] for word in vocab.keys()]
# print(vocab_id)
# 得到词嵌入向量
vocab_vec = embedding(torch.tensor(vocab_id))
# print(vocab_vec.shape)
# 计算QKV
# qkv权重
q_weight = nn.Linear(512, 512)
k_weight = nn.Linear(512, 512)
v_weight = nn.Linear(512, 512)
# qkv向量
q = q_weight(vocab_vec)
k = k_weight(vocab_vec)
v = v_weight(vocab_vec)
# print(q.shape, k.shape, v.shape)
# 计算每一个词的注意力
attention = torch.matmul(q,k.T)/torch.sqrt(torch.tensor(512))
# print(attention.shape) #torch.Size([6, 6])
# softmax:矩阵每一行归一化
attention = torch.softmax(attention, dim=-1)
# 乘以V: 得到每一个词的表示
output = torch.matmul(attention, v)
print(output.shape)
5.自注意力机制的优点
- 捕捉长距离依赖:自注意力机制允许模型在计算每个元素时考虑序列中的所有其他元素,因此能够捕捉长距离依赖。
- 并行计算:与 RNN 和 LSTM 不同,RNN 和 LSTM 在计算时依赖于前一个时刻的状态,因此不能并行计算。而自注意力机制可以在每个时间步骤并行计算所有元素,提高了训练效率。
- 灵活性:自注意力机制不仅限于序列数据,也可以应用于其他类型的数据(如图像),并且可以扩展到多头注意力机制(Multi-Head Attention)等更复杂的形式。
二、多头注意力机制(Multi-Head Attention)
在Transformer模型中,多头注意力机制是核心组件之一。它通过并行计算多个注意力头来捕捉输入序列中不同位置之间的依赖关系和特征,从而增强模型的表示能力。相比单头注意力,多头注意力能够关注输入序列中不同的语义信息,提升模型的学习能力。
1.多头注意力机制的核心思想:
多头注意力的关键在于将自注意力计算分成多个头,每个头对输入数据进行不同的关注,最终将所有头的输出结果结合起来,最后通过一个线性变换得到最终的输出**(分组的思想)**。
比如一个词有512维 那么可以把它分为n个组,然后每个组分别有三个w向量映射为qkv,那么qkv的结果就会得到有n组
下面以2个头为例:
q i = W q a i q i , 1 = W q , 1 q i q i , 2 = W q , 2 q i q^{i}=W^{q}a^{i} \quad \quad q^{i,1}=W^{q,1}q^{i}\quad\quad q^{i,2}=W^{q,2}q^{i} qi=Wqaiqi,1=Wq,1qiqi,2=Wq,2qi
输入向量通过不同的权重矩阵生成多个查询(Q)、键(K)和值(V)。每个头在不同的子空间中进行自注意力计算,即通过计算查询与键的相似度来确定每个值的重要性。多个头并行处理输入数据,每个头关注输入的不同部分,从而学习到不同的特征表示。最后,所有头的输出会被拼接在一起,并通过一个线性变换得到最终的输出。
1. 多头注意力的工作流程:
-
输入嵌入:首先,输入序列通过嵌入层被转化为向量,这些向量与位置编码相加,得到每个词的表示。
-
分头操作:对于每个注意力头,我们会将输入的Query (Q)、Key (K)、**Value (V)**向量与每个头的不同权重矩阵进行线性变换。假设有H个头,我们会得到H组独立的Q、K、V。
- 在多头注意力机制中,不是一开始就将维度划分,而是通过线性变换生成 Q、K、V 后再分头,主要是为了确保每个注意力头都能独立地从输入中学习不同的信息。
-
每个头的自注意力计算:每个注意力头都会独立地进行自注意力计算:
- 计算Q和K的相似度,得到注意力得分。
- 对得分应用Softmax,得到注意力权重。
- 使用注意力权重对V进行加权求和,得到该头的输出。
o u t p u t i = A t t e n t i o n ( Q i , K i , V i ) outputi=Attention(Qi,Ki,Vi) outputi=Attention(Qi,Ki,Vi)
-
拼接和线性变换:所有H个头的输出会被拼接起来(通过将各头的输出按列拼接),得到一个较大的向量。然后,这个拼接后的向量通过一个线性变换(通常是一个全连接层)来整合各头的信息,得到最终的多头注意力输出。
2. 多头机制实现
在多头注意力机制中,不是一开始就将维度划分,而是通过线性变换生成 Q、K、V 后再分头,主要是为了确保每个注意力头都能独立地从输入中学习不同的信息。
import torch
import torch.nn as nn
# ======================
# 1. 词嵌入层 (Embedding)
# ======================
# 创建词嵌入层:假设10个单词,每个单词映射为512维向量
vocab_size = 10 # 词汇表大小
embed_dim = 512 # 嵌入维度
embedding = nn.Embedding(vocab_size, embed_dim)
# 生成随机输入:序列长度为512的token索引
input_seq = torch.randint(0, vocab_size, (512,), dtype=torch.long)
# 通过嵌入层:将索引转换为向量
# 输出形状: (序列长度, 嵌入维度) = (512, 512)
embedding_out = embedding(input_seq)
print("嵌入层输出形状:", embedding_out.shape) # torch.Size([512, 512]) 有512个单词,每个单词映射为512维向量
# ==============================
# 2. 多头注意力权重矩阵初始化
# ==============================
num_heads = 8 # 注意力头数量
head_dim = embed_dim // num_heads # 每个头的维度 512/8=64
# 初始化Q、K、V的投影矩阵 (标准Transformer实现方式)
# 注意:使用单个大矩阵而不是多个小矩阵
W_Q = torch.randn(embed_dim, embed_dim) # (512, 512)
W_K = torch.randn(embed_dim, embed_dim) # (512, 512)
W_V = torch.randn(embed_dim, embed_dim) # (512, 512)
全部映射
# =====================================
# 3. 计算Query, Key, Value (正确实现)
# =====================================
# 一次性计算所有头的投影
all_queries = torch.matmul(embedding_out, W_Q) # (512,512) × (512,512) = (512,512)
all_keys = torch.matmul(embedding_out, W_K) # (512,512)
all_values = torch.matmul(embedding_out, W_V) # (512,512)
分割多头
# 将投影结果分割成多个头
# 步骤:
# 1. 重塑形状: (seq_len, num_heads, head_dim)
# 2. 调整维度顺序: (num_heads, seq_len, head_dim)
queries = all_queries.view(512, num_heads, head_dim).permute(1, 0, 2)
keys = all_keys.view(512, num_heads, head_dim).permute(1, 0, 2)
values = all_values.view(512, num_heads, head_dim).permute(1, 0, 2)
print("Queries形状:", queries.shape) # torch.Size([8, 512, 64])
print("Keys形状:", keys.shape) # torch.Size([8, 512, 64])
print("Values形状:", values.shape) # torch.Size([8, 512, 64])
分别计算每一个头的注意力分数
# ==============================
# 4. 计算注意力分数 (Scaled Dot-Product)
# ==============================
# 计算Q和K的点积 (每个头独立计算)
# 矩阵乘法: (8,512,64) × (8,64,512) = (8,512,512)
attention_scores = torch.matmul(queries, keys.permute(0, 2, 1))
# 缩放因子 (sqrt(d_k))
scale_factor = torch.sqrt(torch.tensor(head_dim, dtype=torch.float))
attention_scores = attention_scores / scale_factor
# 应用softmax得到注意力权重
attention_weights = torch.softmax(attention_scores, dim=-1)
print("注意力权重形状:", attention_weights.shape) # torch.Size([8, 512, 512])
v的每一个权重进行加权乘v
# ==============================
# 5. 计算注意力输出
# ==============================
# 注意力权重与Value相乘
# (8,512,512) × (8,512,64) = (8,512,64)
attention_output = torch.matmul(attention_weights, values)
合并多头
# ==============================
# 6. 合并多头输出
# ==============================
# 步骤:
# 1. 调整维度顺序: (num_heads, seq_len, head_dim) -> (seq_len, num_heads, head_dim)
# 2. 重塑形状: (seq_len, embed_dim)
attention_output = attention_output.permute(1, 0, 2).contiguous() # (512, 8, 64)
combined_output = attention_output.view(512, embed_dim) # (512, 512)
print("合并后的注意力输出形状:", combined_output.shape) # torch.Size([512, 512])
3. 官方用法MultiheadAttention
实际开发中, 官方已经帮我们实现好了就一行代码
# ===========================================
# 7. 使用PyTorch内置多头注意力层 (推荐方式)
# ===========================================
# 实际开发中推荐使用内置实现
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
# 传入qkv
attn_output, attn_weights = multihead_attn(
embedding_out, # (512,1,512)
embedding_out, # (512,1,512)
embedding_out # (512,1,512)
)
print("内置多头注意力输出形状:", attn_output.shape) # torch.Size([512,512])
print("内置多头注意力权重形状:", attn_weights.shape) # torch.Size([512,512])
4. 多头注意力的优势
- 捕捉不同的语义信息:每个注意力头可以聚焦于不同的输入特征或不同位置之间的关系。例如,在自然语言处理中,一个头可能关注句子中的主语和动词关系,而另一个头可能关注名词和形容词之间的关系。
- 并行计算:通过多个头并行计算注意力,模型能够高效地从不同的角度学习数据的表示。这种并行性有助于加速训练,并增强模型的表达能力。
- 灵活的依赖建模:由于每个头独立计算注意力,它能够为每个头分配不同的注意力权重,使得模型能够灵活地捕捉输入序列中的长程依赖和局部特征。
5. 多头注意力在Transformer中的应用
在Transformer中,编码器和解码器都使用了多头自注意力机制:
- 编码器中的多头注意力:通过并行计算多个注意力头,编码器能够有效地建模输入序列的依赖关系。
- 解码器中的多头注意力:解码器中的多头注意力机制与编码器类似,但解码器中的注意力机制有一个额外的掩蔽(masking)步骤,以确保解码过程是自回归的(即只能使用已生成的部分输出进行下一步预测)。
三、位置关联
在自注意力机制中,位置关联指的是每个位置(词或token)是如何与其他位置的表示交互的。自注意力机制通过计算输入序列中每个位置之间的相似度来捕捉这些关联信息。这种机制使得模型能够动态地关注输入序列的不同部分,根据上下文来调整每个位置的表示。
位置关联的计算基于查询向量和键向量之间的相似度,具体如下:
-
查询与键的点积:每个查询向量与所有键向量进行点积,得到该查询与每个键之间的相似度。查询和键之间的相似度表示了它们之间的关联程度。
score ( Q , K ) = Q ⋅ K T d k \text{score}(Q, K) = \frac{Q \cdot K^T}{\sqrt{d_k}} score(Q,K)=dkQ⋅KT
其中, d k d_k dk 是键向量的维度, d k \sqrt{d_k} dk 是为了避免点积值过大而进行的缩放。 -
归一化(Softmax):然后对每个查询与键的点积结果应用 Softmax 函数,将其转换为概率分布,确保所有的关联度总和为 1:
attention weights = Softmax ( score ( Q , K ) ) \text{attention weights} = \text{Softmax}(\text{score}(Q, K)) attention weights=Softmax(score(Q,K)) -
加权求和:接下来,用得到的注意力权重加权求和值向量(Value)。每个位置的表示不仅仅是其自身信息,而是加权了其他所有位置的信息,这样模型就能够捕捉到位置之间的关联性。
output = attention weights × V \text{output} = \text{attention weights} \times V output=attention weights×V
这意味着,每个输出位置的表示是通过与其他位置的值向量加权求和得到的,权重由查询与其他位置的键之间的相似度决定。
由于自注意力机制本身不考虑序列中位置的顺序,Transformer 网络通过引入**位置编码(Positional Encoding)**来为每个词加入位置信息。位置编码是为了让模型能够识别序列中词的相对位置,从而捕捉到顺序关系。
- 位置编码:通常使用正弦和余弦函数生成不同频率的位置信息,并加到输入嵌入向量中。这种方法使得模型能够理解词在序列中的顺序和相对位置。
位置编码将会在transformer网络中详细解释
四、层归一化
层归一化(Layer Normalization, 简称 LN)是深度学习中常用的一种归一化技术,尤其在处理递归神经网络(RNN)、变换器(Transformer)等模型时十分重要。它的主要作用是对每一层的输入进行归一化,改善训练的稳定性,并加速训练过程。
与批量归一化(Batch Normalization)不同,层归一化是对每个样本内的特征进行归一化,而不是对一个批次的样本进行归一化。也就是说,层归一化是按样本维度进行归一化的,而批量归一化是按批次维度进行归一化的。
计算过程:
假设输入张量是一个矩阵,其中每一行代表一个样本(batch_size),每一列代表一个特征维度(embedding_dim)。对某一层的输入 x ∈ R d x \in \mathbb{R}^{d} x∈Rd(其中 d d d 是特征维度),层归一化的计算可以按照以下步骤进行:
- 计算均值和方差:
- 对当前样本的所有 d 个特征求平均,消除整体偏移: μ = 1 d ∑ i = 1 d x i \mu = \frac{1}{d} \sum_{i=1}^d x_i μ=d1∑i=1dxi
- 衡量特征值的离散程度,后续用于消除尺度差异: σ 2 = 1 d ∑ i = 1 d ( x i − μ ) 2 \sigma^2 = \frac{1}{d} \sum_{i=1}^d (x_i - \mu)^2 σ2=d1∑i=1d(xi−μ)2
- 加入微小值 ϵ \epsilon ϵ(通常取 (10^{-5}))是为了避免分母为 0,保证数值稳定性: σ 2 = 1 d ∑ i = 1 d ( x i − μ ) 2 + ϵ \sigma^2 = \frac{1}{d} \sum_{i=1}^d (x_i - \mu)^2 + \epsilon σ2=d1∑i=1d(xi−μ)2+ϵ
- 归一化:
- 将每个特征值减去均值、除以标准差,得到 “均值 0、方差 1” 的标准化结果: x ^ i = x i − μ σ 2 \hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2}} x^i=σ2xi−μ
- 可学习的缩放和偏移:
- 标准化可能会破坏特征原有的表达能力(如使激活函数输出过于集中),因此引入可学习参数 γ \gamma γ(缩放因子)和 β \beta β(偏移因子),恢复特征的灵活性: y i = γ ⋅ x ^ i + β y_i = \gamma \cdot \hat{x}_i + \beta yi=γ⋅x^i+β
- γ \gamma γ 和 β \beta β 与输入特征维度相同(均为 d 维),会随网络训练一起更新。
- 标准化可能会破坏特征原有的表达能力(如使激活函数输出过于集中),因此引入可学习参数 γ \gamma γ(缩放因子)和 β \beta β(偏移因子),恢复特征的灵活性: y i = γ ⋅ x ^ i + β y_i = \gamma \cdot \hat{x}_i + \beta yi=γ⋅x^i+β
- 输出:
- 最终的输出是:
y = γ ⋅ x − μ σ 2 + ϵ + β y = γ ⋅ x − μ σ 2 + ϵ + β y=γ⋅x−μσ2+ϵ+βy = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta y=γ⋅x−μσ2+ϵ+βy=γ⋅σ2+ϵx−μ+β
- 最终的输出是:
层归一化(Layer Normalization)和批量归一化(Batch Normalization)都用于解决神经网络训练中的梯度消失和梯度爆炸问题,但它们的作用方式不同。
批量归一化和层归一化的不同
1.批量归一化(Batch Normalization)
- 按批次维度归一化:BN会计算整个批次数据(即一组样本)的均值和方差,基于这个均值和方差对每个特征进行归一化。
- 这样做的好处是,在训练过程中批次内的数据共同参与归一化,这有助于减少不同样本之间的偏差,提高训练的稳定性。
以 2 D 2D 2D 特征为例:
μ c = 1 N ⋅ H ⋅ W ∑ n , h , w x n c h w , σ c 2 = 1 N ⋅ H ⋅ W ∑ n , h , w ( x n c h w − μ c ) 2 \mu_c = \frac{1}{N \cdot H \cdot W} \sum_{n,h,w} x_{nchw}, \quad \sigma_c^2 = \frac{1}{N \cdot H \cdot W} \sum_{n,h,w} (x_{nchw} - \mu_c)^2 μc=N⋅H⋅W1n,h,w∑xnchw,σc2=N⋅H⋅W1n,h,w∑(xnchw−μc)2
归一化每个通道维度:
x ^ n c h w = x n c h w − μ c σ c 2 + ϵ \hat{x}_{nchw} = \frac{x_{nchw} - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}} x^nchw=σc2+ϵxnchw−μc
2.层归一化(Layer Normalization)
- 按样本维度归一化:LN则对每个样本的每个特征进行独立的归一化。换句话说,它对每个样本单独计算该样本的均值和方差。
- 因此,LN不依赖于批量大小,它在处理每个样本时都是独立的。这使得层归一化在小批量或单样本情况下表现得更好,尤其是在循环神经网络(RNN)等场景中。
以每个样本为单位,对其所有特征维度归一化:
μ = 1 H ∑ i = 1 H x i , σ 2 = 1 H ∑ i = 1 H ( x i − μ ) 2 \mu = \frac{1}{H} \sum_{i=1}^{H} x_i, \quad \sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2 μ=H1i=1∑Hxi,σ2=H1i=1∑H(xi−μ)2
归一化:
x ^ i = x i − μ σ 2 + ϵ \hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} x^i=σ2+ϵxi−μ
更形象的对比:
假设一个 batch 的数据是 (3, 4):
- 3 表示有 3 个样本
- 每个样本有 4 个特征
-
批量归一化 (BN):竖着看
-
BN是针对每一列(特征维度)来做均值和方差的。
-
比如第1列 x 11 , x 21 , x 31 {x_{11}, x_{21}, x_{31}} x11,x21,x31,它是“所有样本在特征1上的值”,BN会算这 3 个数的均值和方差,然后归一化。
-
所以 BN 竖着看每一列:
- 特征维度一致,样本之间比较。
-
-
层归一化 (LN):横着看
- LN是针对每一行(样本内部的所有特征)来做均值和方差的。
- 比如第一行 x 11 , x 12 , x 13 , x 14 {x_{11}, x_{12}, x_{13}, x_{14}} x11,x12,x13,x14,LN会算这 4 个数的均值和方差,然后对它们归一化。
- 所以 LN 横着看每一行:
- 样本独立,每个样本自己做归一化。
更多推荐
所有评论(0)