矩阵分解(SVD)
对矩阵进行SVD分解,通式如下:
orthogonal:正交矩阵
dagonal:对角阵
,该矩阵每一个列向量都是{AA^T}的特征向量
, 将或者的特征值开根号,得到的就是该矩阵主对角线上的元素,也就是{A}的奇异值
,该矩阵的每一个列向量都是的特征向量
-
因为没学过研究生的矩阵课程,所以不太懂这个
-
但是看来也很好理解。
-
就是对一个 的矩阵进行分解。
-
1.先通过 变成一个的方阵,然后算出来对应的个特征向量
- m个特征向量组成左边的矩阵U
-
2.然后通过变成一个的方阵,然后算出来对应的个特征向量
- n个特征向量组成右边的的矩阵V
-
3.算A的奇异值只需要把 AA^T的特征值开根号即可
- 奇异值总是非负的(要取绝对值再开根号)
- 奇异值通常按从大到小排序
张量运算(einsum)
Einstein Summation:变量下标重复出现时,即可省略繁琐的求和符号。
由于上面公式中a的下标和b的下标是重复出现的,所以可以记为:
eg1:向量点乘
C = np.einsum('i, i->', a, b)
"i,i->" 表示 a[i] * b[i],然后对 i 进行求和,最终返回一个标量。
eg2:向量外积
C = np.einsum("i,j->ij", a, b)
# 等价于numpy的outer函数
numpy_outer = np.outer(a, b)
"i,j->ij":表示 a[i] * b[j],不对 i, j 求和,返回一个 ixj 矩阵。
eg3:矩阵转置
B = np.einsum("ij->ji", A)
# 等价于 B = A.T
eg4:矩阵乘法
C = np.einsum("ij,jk->ik", A, B) # 矩阵乘法
#等价于
C = np.matmul(A, B) # 或 A @ B
上述公式对应的计算公式为C_{ik} = \sum_jA_{ij}B_{jk},矩阵A与B相乘。
eg5:批量矩阵乘法
A = np.random.rand(32, 3, 4) # batch_size=32, shape (32, 3, 4)
B = np.random.rand(32, 4, 5) # shape (32, 4, 5)
C = np.einsum("bij,bjk->bik", A, B) # 批量矩阵乘法
# 等价于
C = np.matmul(A, B) # 适用于批量计算
- 批量处理数据 1.一次处理多个样本(batch_size=32) 2.提高计算效率 3.充分利用GPU并行计算能力
einsum 在 Transformer 自注意力机制中的应用
在Transformer 的 自注意力(Self-Attention) 计算中,我们常需要执行如下计算:
用 einsum 表示:
import torch
import torch.nn.functional as F
def dot_product_attention(q, K, V):
"""计算点积注意力"""
# 计算 QK^T
logits = torch.einsum("k,mk->m", q, K)
# 归一化
weights = F.softmax(logits, dim=0)
# 计算 softmax(QK^T) * V
return torch.einsum("m,mv->v", weights, V)
# 创建输入张量
q = torch.tensor([1.0, 2.0]) # shape (2,)
K = torch.tensor([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) # shape (3, 2)
V = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) # shape (3, 2)
# 计算注意力输出
output = dot_product_attention(q, K, V)
print(output.numpy()) # [4.0, 5.0]
感觉上述代码略微会误导人,K是 (3,2) 的形式,K^T就应该是 (2,3) ,所以应该是
K_T = torch.einsum("mk->km",K)
logits = torch.einsum("k,km->m", q, K_T)
现在的代码就表示这
不过现在看来是我的思路出问题了,einsum会自动处理....
einsum的自动处理机制:
在 k,mk->m 这个表达式中:
k表示第一个输入q的维度mk表示第二个输入K的维度->m表示输出的维度 当einsum看到这个表达式时,它会:- 发现需要把
mk中的k和第一个输入的k对齐 - 自动把K中的维度重新排列,相当于完成了转置操作
- 然后执行乘法运算
以后写代码,读代码,只需要关注对应的维度即可。不需要想着转置啥的能不能相乘啥的。直接写即可。
这就是einsum的强大之处:它让我们可以专注于维度的对应关系,而不用关心具体的实现细节。这样代码更简洁,也更容易理解。
归一化
- Min-Max归一化:
- 把数据缩放到[0,1]区间
- 保持原始数据的分布形状
- Z-Score标准化:
- 转换后数据均值为0,标准差为1
- 适用于数据大致呈正态分布的情况
- L2归一化:
- 向量的长度变为1
- 保持向量方向不变
- Softmax:(
tf.nn.softmax(logits))
- 转换为概率分布(和为1)
- 强调相对大小差异
- 常用于注意力机制和分类问题
梯度计算(链式法则)
eg1: 举个栗子
import torch
# 创建张量,并设置 requires_grad=True 以追踪其计算历史
x = torch.tensor(2.0, requires_grad=True) # 标记需要计算梯度
y = torch.sin(x) # 前向传播第一步
z = y ** 2 # 前向传播第二步
# 计算图中 y 的梯度
z.backward() # # 反向传播,计算 z 对 x 的梯度
print(x.grad) # 输出 x 的梯度
前向传播:
- 数据从输入层流向输出层
- 就像示例中的
x → sin(x) → sin²(x)(x→ y → z)
反向传播:
- 梯度从输出层反向流向输入层
- 使用链式法则计算每一层的梯度
- 通过 backward() 自动完成这个过程
梯度的作用:
- 指示参数需要调整的方向和大小
- 帮助模型学习和优化
- 最终目的是减小损失函数的值
torch.Tensor:
- Tensor 是 PyTorch 中存储数据和定义计算图的基础数据结构。默认情况下,所有的张量(Tensor)都不会自动追踪计算的历史。
- 如果要使张量参与计算图并能够进行自动求导,需要在创建张量时设置 requires_grad=True。
backward():
- 调用张量的 backward() 方法,PyTorch 会自动计算该张量的所有依赖张量的梯度,并存储在各自的 .grad 属性中。
- backward() 只接受标量张量(一个数值),如果不是标量张量,通常会传递一个与张量形状匹配的梯度参数。
torch.no_grad():
- 在评估模型或推理时,我们不需要计算梯度,可以使用 torch.no_grad() 以节省内存和计算资源。
更多细节[来源blog.csdn.net/wahahaha116…]
- 梯度累积与清零:每次调用 backward(),梯度会累积(即,累加到 .grad 属性中),因此在每次新的梯度计算之前通常需要清零现有的梯度,例如通过 optimizer.zero_grad()。
- 多次反向传播:如果在同一个计算图上进行多次反向传播(例如在 RNN 中),需要设置 retain_graph=True,以防止计算图被释放。
eg2: 详细介绍
梯度求导的基本步骤
- 定义计算图:
每当对 torch.Tensor 进行操作时,PyTorch 会动态地创建一个计算图来记录操作。
如果 Tensor 的 requires_grad 属性设置为 True,那么该张量会开始追踪其上的所有操作,这样就可以调用 backward() 来自动计算其梯度。
- 前向传播(Forward Pass):
计算图的构建是在前向传播过程中完成的。
在前向传播过程中,输入数据通过神经网络的各层进行计算,最终生成输出。
- 计算损失(Loss Calculation):
通常情况下,在前向传播结束后会计算损失函数(Loss),这是一个标量值,用于评估模型的输出与目标之间的差距。
- 反向传播(Backward Pass):
调用损失张量的 backward() 方法。反向传播通过链式法则计算损失函数相对于每个叶子节点(即,所有具有 requires_grad=True 的张量)的梯度。
- 更新参数(Parameter Update):
使用优化器(如 SGD、Adam 等)通过梯度下降或其他优化算法更新模型的参数。