深度学习10——pytorch与数学前置
1. 张量形状与广播机制张量与形状张量可以理解为多维数组atorch.tensor([1,2,3])# shape: [3]btorch.tensor([[1,2,3],[4,5,6]])# shape: [2, 3]常见形状[3] 一维张量包含 3 个数字 [2, 3] 二维张量2 行 3 列 [B, S, D] 三维张量常见于 Transformer其中B batch_size批次中的样本数量 S seq_len每个样本的 token 数量 D d_model每个 token 的向量维度广播机制广播机制用于检查两个不同形状的张量能不能像形状相同一样进行逐元素运算。逐元素运算包括ab a-b a*b a/b广播从最后一维开始比较。每一维满足以下任意条件即可两个维度相同。其中一个维度是1可以扩展。其中一个张量不存在该维度可以看作大小为1。否则不能广播。例如atorch.tensor([[1,2],[3,4]])# shape: [2, 2]btorch.tensor([1,2])# shape: [2]从右往左对齐a: [2, 2] b: [ 2]b缺少第一维可以逻辑上扩展为[ [1, 2], [1, 2] ]所以ab结果为[ [2, 4], [4, 6] ]结果形状仍然是[2, 2]广播通常只是逻辑上的扩展不一定真的复制数据。另一个例子[2, 3, 4] [1, 1, 4]可以广播为[2, 3, 4]但下面不能广播[2, 3] [2, 4]因为最后一维的3和4不相同也没有一个是1。2.nn.Linear与nn.Embeddingnn.Linearnn.Linear建立一个线性层把一个向量转换成另一个向量。layernn.Linear(3,2)表示输入3 个特征 输出2 个特征它包含两个可学习参数weight.shape [2, 3] bias.shape [2]计算公式y x weight.T bias其中 矩阵乘法 weight.T 权重矩阵的转置例如xtorch.randn(4,3)layernn.Linear(3,2)ylayer(x)形状变化x: [4, 3] y: [4, 2]表示一次处理 4 个样本每个样本从 3 维向量变成 2 维向量。Linear 只改变最后一维xtorch.randn(2,5,4)layernn.Linear(4,6)ylayer(x)形状变化[2, 5, 4] - [2, 5, 6]可以理解为一共有 2 × 5 个向量 每个向量从 4 维变成 6 维前面的维度不变只有最后一维被线性层处理。nn.Embeddingnn.Embedding是一张可训练的向量表输入整数编号输出该编号对应的向量。例如embeddingnn.Embedding(num_embeddings10000,embedding_dim768)含义num_embeddings 10000 可以查询 10000 个编号 embedding_dim 768 每个编号对应一个 768 维向量Embedding 内部的参数矩阵形状是[10000, 768]合法编号范围是0 到 9999假设 Tokenizer 将“我喜欢猫”转换为我 - 15 喜欢 - 203 猫 - 78代码input_idstorch.tensor([15,203,78])# shape: [3]xembedding(input_ids)# shape: [3, 768]nn.Embedding相当于执行xembedding.weight[input_ids]也就是取出参数矩阵的第 15、203、78 行。需要注意Token ID 只是查表编号没有大小和距离含义。例如“猫”的 ID 是 78“狗”的 ID 是 79不代表“狗比猫大”也不代表它们天然相似。Batch 输入实际训练时通常一次输入多个序列input_idstorch.tensor([[15,203,78,0],[26,203,91,4]])# shape: [2, 4]经过 Embeddingxembedding(input_ids)形状变化[B, S] - [B, S, D] [2, 4] - [2, 4, 768]含义是2 个序列 每个序列有 4 个 token 每个 token 被转换成 768 维向量Embedding 向量如何得到刚创建 Embedding 时每个 token 的向量通常是随机初始化的。训练时前向传播 - 计算 Loss - 反向传播 - 更新 Embedding 参数如果某些 token 经常出现在相似上下文中它们对 Loss 产生的影响可能相似参数更新方向也可能相似因此向量可能逐渐接近。3. 矩阵乘法与转置矩阵乘法矩阵乘法使用A B或者torch.matmul(A,B)形状规则[m, n] [n, k] - [m, k]左边矩阵的列数必须等于右边矩阵的行数。例如A.shape [2, 3] B.shape [3, 4] A B - [2, 4]矩阵乘法中的每个结果是左边的一行和右边的一列对应相乘后求和result[i, j] A 第 i 行与 B 第 j 列对应相乘后求和逐元素乘法逐元素乘法使用*atorch.tensor([1,2,3])btorch.tensor([10,20,30])a*b# [10, 40, 90]两个张量需要形状相同或者能够广播。区别* 对应位置相乘 矩阵乘法行与列相乘后求和转置转置就是交换矩阵的行和列。A [ [1, 2, 3], [4, 5, 6] ]形状[2, 3]转置后A.T [ [1, 4], [2, 5], [3, 6] ]形状变成[3, 2]二维矩阵可以写A.T对于高维张量通常明确指定要交换的两个维度x.transpose(-2,-1)它表示只交换最后两个维度。例如[B, H, S, d] - transpose(-2, -1) [B, H, d, S]高维张量做矩阵乘法时最后两个维度是矩阵维度 前面的维度是批次维度通用形状规则[..., m, n] [..., n, k] - [..., m, k]4. Transformer 中的常见形状与多头拆分常用符号B batch_size 一个批次中的样本数量 S seq_len 每个序列的 token 数量 D d_model 每个 token 的总向量维度 H num_heads 注意力头的数量 d head_dim 每个注意力头的向量维度需要满足D H * d例如d_model 768 num_heads 12 head_dim 64 768 12 * 64从 Token ID 到 Q、K、V输入 Token IDinput_ids: [B, S]经过 Embedding[B, S] - Embedding [B, S, D]然后使用三个不同的线性层生成 Q、K、VQq_proj(x)Kk_proj(x)Vv_proj(x)其中q_projnn.Linear(D,D)k_projnn.Linear(D,D)v_projnn.Linear(D,D)形状不变Q: [B, S, D] K: [B, S, D] V: [B, S, D]虽然形状相同但三个 Linear 使用不同参数所以得到的内容不同Q当前 token 想寻找什么 K当前 token 可以用什么特征被匹配 V当前 token 实际要传递的信息多头拆分多头拆分是把每个 token 的一个大向量拆成多个小向量每个注意力头处理一个小向量。假设B 2 S 5 D 8 H 2 d 4因为8 2 * 4首先使用reshape拆开最后一维QQ.reshape(B,S,H,d)形状变化[B, S, D] - [B, S, H, d] [2, 5, 8] - [2, 5, 2, 4]然后调整维度顺序QQ.permute(0,2,1,3)形状变化[B, S, H, d] - [B, H, S, d] [2, 5, 2, 4] - [2, 2, 5, 4]K 和 V 进行相同处理Q、K、V: [B, H, S, d]多头拆分不是增加数据量而是把D重新看作H * d例如8 个特征 - 2 个 head - 每个 head 处理 4 个特征实际是在 Q、K、V 经过可学习的线性投影后沿最后一个特征维度拆分。不同 head 使用不同的参数和特征子空间因此可能学习到不同的注意力模式。注意力分数的形状拆分后Q: [B, H, S, d] K: [B, H, S, d]先转置 K 的最后两个维度K_TK.transpose(-2,-1)得到K_T: [B, H, d, S]然后scoresQ K_T只看最后两个维度[S, d] [d, S] - [S, S]所以整体形状是[B, H, S, d] [B, H, d, S] - [B, H, S, S]最后两个S表示每个查询 token 对每个被关注 token 的分数每个 head 都会得到一张独立的[S, S]注意力分数矩阵。5. Padding Mask 与 Causal MaskMask 用于告诉注意力机制哪些位置不能被关注通常在 Softmax 之前使用scoresscores.masked_fill(mask,float(-inf))attention_weightstorch.softmax(scores,dim-1)因为softmax(-inf) 约等于 0所以被屏蔽位置的注意力权重会变成 0。Mask要解决的问题屏蔽内容Padding Mask忽略补齐位置PADtokenCausal Mask防止看到未来信息当前 token 后面的 tokenPadding Mask一个 Batch 中的序列长度可能不同句子 A我 喜欢 猫 PAD 句子 B你 喜欢 吃 苹果PAD只是为了让序列长度相同不是真实内容。Padding Mask 告诉模型注意力计算时不要关注 PAD。假设False 可以关注 True 需要屏蔽Padding Mask 可以表示为[ [False, False, False, True ], [False, False, False, False] ]原始形状[B, S]为了应用到注意力分数scores.shape [B, H, S, S]通常将其扩展为padding_maskpadding_mask[:,None,None,:]形状[B, S] - [B, 1, 1, S]然后通过广播应用到所有 head 和所有查询 token。计算 Loss 时也要忽略 Padding 位置loss_fnnn.CrossEntropyLoss(ignore_indexpad_token_id)需要区分Padding Mask 注意力计算时不要关注 PAD。 ignore_index 计算 Loss 时不要计算 PAD 位置的损失。Causal MaskCausal Mask 主要用于 GPT 等自回归语言模型当前 token 不能看到它后面的未来 token。假设序列为我 喜欢 吃 苹果允许的注意力关系是我 - 我 喜欢 - 我、喜欢 吃 - 我、喜欢、吃 苹果 - 我、喜欢、吃、苹果允许查看的位置可以表示为1 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1如果使用True表示需要屏蔽可以生成上三角 Maskcausal_masktorch.triu(torch.ones(S,S,dtypetorch.bool),diagonal1)结果False True True True False False True True False False False True False False False False这样模型训练时就不能通过未来 token 提前看到答案。Padding Mask 和 Causal Mask 可以同时使用combined_maskpadding_mask|causal_mask可以简单记为Padding Mask这个位置是补出来的不是真实内容不要看。 Causal Mask这个位置属于未来现在不能看。