资讯详情 机器学习中的乘积地图:从内积到Kronecker乘积实战指南
📅 2026/10/2 11:31:48
机器学习里有一件特别容易被忽略的事乘积。从最入门的线性回归到最前沿的大模型底层几乎全是乘法在支撑。很多人学矩阵、学张量的时候总觉得“不就是点乘、矩阵乘法吗”但真正落到模型实现、调试梯度、设计网络结构时经常搞混内积、Hadamard乘积、外积、Kronecker乘积该用在哪儿。更现实的是面试时被问到“Attention为什么除以根号d_k”如果你只答“防止数值过大”基本暴露出没往深处想过乘积的数学性质。这篇我围绕机器学习中最常见的几种乘积做一个系统梳理核心是我在实际写模型、调参、复现论文过程中反复用到、也反复踩坑的东西。不适合理科小白从头看线性代数适合已经会一些基本概念、但想把这些乘法彻底串起来的人。我会讲清楚每个乘积的定义、几何直觉、在模型里的典型位置以及它们为什么只能用在那个位置、换一种行不行。1. 为什么机器学习中“乘积”无处不在从高维空间的一张地图说起要理解机器学习的乘积得先跳出“数组相乘”的机械操作回到一个更根本的问题模型本质在学什么一个训练好的模型内部实际上存储了一张从输入空间到特征空间、再到输出空间的高维映射。图像分类里“猫”对应的不是一个具体的像素矩阵而是一个高维特征区域。模型要做的事情是把输入图像经过层层变换投影到某个特征空间中让“猫”的分布集中在一处“狗”的分布在另一处。这种投影操作数学上就是线性变换而线性变换的实现载体就是矩阵乘法。可以说机器学习模型再复杂核心骨架也就是一堆矩阵乘法中间夹着非线性激活函数。乘积承担的角色远比我们想象的丰富内积点积度量方向的一致性用在相似度、注意力机制、线性层中矩阵乘法批量执行线性变换是全连接层、卷积层、Transformer的基础运算Hadamard乘积逐元素乘做掩码、门控、非线性缩放在LSTM、LayerNorm、Dropout里反复出现外积和Kronecker乘积构造结构化的权重矩阵、特征交互在推荐系统、双线性池化、张量分解里很常用。这些乘积并非彼此孤立。我在跑模型时最大的体会是选哪一种乘法本质上是在选择“如何定义特征之间的交互关系”。点积是“方向相似度”逐元素乘是“同位置的信息筛选”外积是“构造组合特征矩阵”。背后的空间含义完全不同。这篇文章的结构就是围绕这张“乘积地图”展开。每讲一种乘积我会给出定义、计算方式、模型中的实例以及选择它的理由和常见坑。既然标题是“常见的乘积”我会尽可能覆盖完整让你读完以后能对模型里的每个乘法运算都有清晰的“归属感”。2. 内积与矩阵乘法神经网络的脊柱也是初学最容易混淆的起点2.1 内积的本质方向上的“共振”先看内积。给定两个同维向量a [a1, a2, ..., an]和b [b1, b2, ..., bn]内积定义为a · b Σ ai * bi这个人人都知道但很少有人从几何意义上真正“感觉”到它。内积还有一个等价形式a · b |a| |b| cos(θ)当两个向量同方向时cos(0)1内积最大当两个向量垂直时cos(90°)0内积为零反方向时内积为负。所以内积天然是一个相似度度量前提是你先把向量归一化到单位长度。我在面试候选人的时候特别喜欢问一个问题Word2Vec里计算词向量相似度为什么用余弦相似度而不是直接算欧氏距离答案就在于余弦相似度本质上是归一化后的内积它丢掉了向量长度信息只保留方向信息而词向量的模长往往与词频相关方向才真正编码语义。这个细节直接体现了对“内积”的理解深度。2.2 从单一内积到全连接层全连接层的数学形式是y Wx b其中W是m × n矩阵x是n维向量输出y是m维向量。拆开看y的第i个分量就是W的第i行向量与x做内积再加上偏置。所以一个全连接层本质上就是让输入向量和权重矩阵的每一行都做一次内积。以手写数字识别为例假设输入是 784 维的像素向量中间层是 128 个神经元那权重矩阵 W 的尺寸就是128 × 784。W的第i行可以理解为“第i个特征检测器”它对输入进行加权求和得到该特征在输入中出现的强度。m × n矩阵乘以n维向量计算量是m*n次乘加复杂度 O(mn)。这个复杂度在模型规模变大后是灾难性的所以工程上会利用 GPU 做并行化也就是批处理把多个输入向量堆叠成B × n的矩阵一次性完成B个样本的线性变换这就是批量矩阵乘法batch matmul。2.3 矩阵乘法的维度之眼矩阵乘法C A B要求 A 的最后一个维度等于 B 的倒数第二个维度。在我实际调试模型的过程中维度不匹配是出现频率最高的报错来源比任何数学错误都常见。总结一个自查清单A 的形状是(..., M, K)B 的形状是(..., K, N)结果的形状是(..., M, N)广播规则在...部分生效但两个矩阵的K必须严格相等别把A B和B A搞混它们的结果几乎总是不一样甚至可能根本无法相乘在 PyTorch 中torch.matmul支持高维批量乘torch.mm只支持二维矩阵乘二者用法别混。2.4 计算图中的矩阵乘法梯度反向传播中矩阵乘法层的前向是C A B如果损失对C的梯度是dC那么dA dC B.T dB A.T dC注意这个转置方向的对称性特别容易记错。我当时学反向传播时自己推导过一遍远比自己死记结论管用。建议你也动笔推一次就三行公式推完以后再也不会忘。3. Hadamard乘积逐元素乘法的“门控”与“掩码”江湖3.1 定义与直觉Hadamard乘积也叫逐元素乘积、Schur乘积符号是⊙定义是C[i][j] A[i][j] * B[i][j]要求 A、B 形状完全相同。它不改变矩阵结构只对每一位元素进行独立缩放。矩阵乘是“全局混合”Hadamard乘积是“局部调制”。两者对比矩阵乘好比把食材搅拌成一道菜逐元素乘像是往每道菜上撒不同的调料。正是这种“按位缩放”的性质让 Hadamard 乘积在神经网络中成了实现门控机制和掩码机制的首选。3.2 门控LSTM 和 GRU 的隐式主角LSTM 的遗忘门、输入门、输出门公式核心就是f_t σ(W_f · [h_{t-1}, x_t] b_f) c_t f_t ⊙ c_{t-1} i_t ⊙ c_hat_t这里的⊙就是 Hadamard 乘积。遗忘门 f_t 是一个取值在 0 到 1 之间的向量它逐元素地决定“保留多少旧记忆、遗忘多少旧记忆”。如果换成矩阵乘法就会产生不同位置之间的交叉干扰——弄丢“保留与遗忘”这个语义本身。我早期用 LSTM 做时序预测时曾试过把逐元素乘改成矩阵乘序列建模能力不升反降还引入了一堆额外参数。因为 LSTM 的 C 状态携带了长期记忆门控需要精确地对每个维度进行独立放行跨维度的混合反而会毁掉记忆通道的纯净性。这验证了一个原则在序列建模中维度间信息交换由权重矩阵负责门控只做逐维度的自适应缩放两者各司其职。3.3 掩码Transformer 里的 padding maskTransformer 的注意力计算中padding mask 的操作本质上就是 Hadamard 乘积或等价的加法掩码# 把 padding 位置对应的注意力分数设为 -inf scores scores.masked_fill(mask 0, -1e9)这个masked_fill操作改的是待 softmax 的分数但很多实现里瓶颈在于 mask 也常作用于特征矩阵本身。BERT 预训练中[MASK]token 的隐藏状态会乘以一个掩码这也是逐元素乘。Dropout 就更直接了训练时把特征向量逐元素地乘以 0/1 mask本质也是 Hadamard 乘积h_dropped h ⊙ mask所以你在看 LSTM、Transformer、Dropout 的源码时只要见到形状相同的两个张量逐位相乘基本就是 Hadamard 乘积在发挥作用。识别它是读懂模型代码的快捷方式。3.4 反向传播中 Hadamard 乘积的“暗线”在反向传播里Hadamard 乘积其实是一条贯穿始终的暗线。假设前向传播是y Wx b经过激活函数z σ(y)损失对z的梯度是dz。那么dy dz ⊙ σ(y)这里的⊙ σ(y)就是逐元素乘。Sigmoid、Tanh 这类激活函数的导数矩阵是对角矩阵矩阵形式下梯度传播是dz diag(σ(y))但因为对角矩阵的特殊结构实际实现里直接用逐元素乘替代。这个细节解释了为什么 ReLU 在反向传播中那么高效——它的导数是 0 或 1逐元素乘后非零元素原样通过。同时这也是梯度消失的根源之一多层 Sigmoid 反向传播时每一层都要乘一个σ(y)而σ(y)的最大值只有 0.25连乘几次梯度就指数级缩小。你把0.25^5算一下只剩约 0.001十层以上基本看到的就是一片梯度沙漠。3.5 与矩阵乘的关键区分运算Shape 要求语义典型场景矩阵乘法A 的最后一维等于 B 的倒数第二维维度间信息混合全连接、Attention、卷积Hadamard 乘积两者 shape 完全一致逐维度独立缩放门控、掩码、激活导数初学的时候把这两种乘法的语义混淆是很多 bug 的根源。比如在自定义模型中想对特征做“通道级缩放”结果用了操作导致输出长度都不对还排查了半天。4. 外积与Kronecker乘积构造结构化权重的高级工具4.1 外积从两个向量构造一个交互矩阵外积的定义很简洁向量u ∈ R^m和v ∈ R^n的外积得到一个m × n矩阵u ⊗ v u * v^T其中(u ⊗ v)[i][j] u[i] * v[j]。内积是两个向量“比较方向”求的是一个标量衡量相似度外积则是把两个向量“编织”成矩阵矩阵的每个位置代表两个分量之间的交互强度。如果说内积是问“你俩像不像”外积就是在问“你俩搭配能产生什么”。外积在推荐系统里非常常见。用户 embeddingu和物品 embeddingv的外积构成交互矩阵M[i][j] u[i] * v[j]表示用户第 i 个偏好维度与物品第 j 个属性维度的匹配度。这比直接做内积得出一个标量分数保留了更多的交互信息。Neural Collaborative Filtering 就是在外积、内积、拼接等方法之间做对比结论是不同特征交互结构确实影响效果上限。4.2 外积在位置编码里的妙用Transformer 的正弦位置编码公式PE(pos, 2i) sin(pos / 10000^(2i/d_model)) PE(pos, 2i1) cos(pos / 10000^(2i/d_model))如果同时处理batch和seq_len你会看到一个形状为(batch, seq_len, d_model)的三维张量它事实上就是位置索引向量与维度索引向量的某种外积组合拓展。我对这个公式印象深刻是因为它用不同频率的三角函数给每个位置生成了一串“指纹”。换一个角度理解位置向量由不同频率的正弦波叠加而成高频正弦波在相邻位置差异明显能区分近邻低频正弦波在远处才产生明显变化能区分长距离。内积位置编码的“外积构造”让 Transformer 能同时感知绝对位置和相对位置关系。4.3 Kronecker乘积矩阵中的“套娃”Kronecker乘积的定义是矩阵A ∈ R^{m×n}和B ∈ R^{p×q}的 Kronecker 乘积得到一个mp × nq的分块矩阵A ⊗ B [[a11*B, a12*B, ..., a1n*B], [a21*B, a22*B, ..., a2n*B], ... [am1*B, am2*B, ..., amn*B]]本质就是把 A 的每个元素替换成“该元素整体乘以 B”的小矩阵块。它在机器学习中的应用通常和张量分解、低秩近似有关。KRON 分解的思路是大规模权重矩阵 W 可以表示成若干小矩阵的 Kronecker 乘积之和。这样做的好处是参数量大幅下降。一个1024 × 1024的权重矩阵有约 100 万个参数如果表示成四个32 × 32的 Kronecker 因子乘积参数总量只有4 × 32 × 32 4096——参数减少了 250 倍。这在边缘部署、模型压缩场景里意义重大。4.4 Kronecker乘积的经典恒等式Kronecker乘积最经典的恒等式是向量化恒等式vec(AXB) (B^T ⊗ A) vec(X)其中vec是把矩阵按列拉直成向量。这个恒等式在矩阵方程求解如 Lyapunov 方程、Sylvester 方程、高斯过程、稀疏线性代数中非常有用。我在实现高斯过程回归时遇到过一次性能瓶颈需要求解涉及多个矩阵的线性系统直接展开计算不仅内存爆炸数值稳定性也差。后来利用 Kronecker 结构加速核矩阵求逆把原来的O(n^3)降到O(n^{1.5})级别。这是乘积类型选对了性能差一个数量级。4.5 低秩分解乘积思维的现实延伸提到 Kronecker、外积就不得不顺带聊低秩分解。低秩逼近的核心思想是一个高维矩阵可以用两个低维矩阵的乘积近似。W ≈ U * V^T其中U ∈ R^{m×r}V ∈ R^{n×r}r min(m, n)。LoRALow-Rank Adaptation就是这样它把大模型的权重更新量限制在一个低秩子空间内W W_0 ΔW W_0 B * A这个结构直接决定了大模型微调的资源需求。把“更新量”限制在低秩空间本质上是先验假设大模型微调时权重变化主要发生在少数几个重要方向上。我在实际微调 7B 模型时设置r8就已经能逼近全参微调的效果rank 提升到 16 带来的收益相当有限但显存和磁盘开销明显增加。这就是典型的“用矩阵乘积结构换效率”的工程实践。5. 缩放点积、Attention与数值稳定性乘积在深度模型中的实战5.1 为什么是缩放点积从方差推导说起Transformer 中的 Attention 核心公式Attention(Q, K, V) softmax(Q K^T / sqrt(d_k)) V问题来了为什么要除以sqrt(d_k)直觉回答“防止数值过大”只是表象。严谨推导是这样的假设q和k是均值为 0、方差为 1 的独立随机变量。点积的均值是 0方差是Var(q·k) d_k * Var(q) * Var(k) d_k如果不做缩放那么点积的方差是d_k。当d_k很大比如 512、1024时点积的分布存在长长的尾部。一些分数会特别大另一些特别小。经过 softmax 后大的值对应概率接近 1小的值概率趋近 0梯度在饱和区几乎为零模型怎么训都动不了。除以sqrt(d_k)后方差被拉回 1Var(q·k / sqrt(d_k)) d_k / d_k 1softmax 的输入分布保持相对温和梯度能正常流动。我之前写过一个快速验证实验当d_k1时softmax 输出近似均匀分布当d_k100但不缩放时输出几乎变成 0/1 分布除以sqrt(100)之后恢复到相对可学习的分布状态。5.2 点积注意力实现中的两个魔鬼细节实现 QK^T 时Q的形状是(batch, num_heads, seq_len, d_k)K的形状是(batch, num_heads, seq_len, d_k)。第一步需要转置KK.transpose(-2, -1)得到(batch, num_heads, d_k, seq_len)。Q K^T得到注意力分数矩阵(batch, num_heads, seq_len, seq_len)。第二个容易踩的坑是 mask 的加法。padding mask 在 softmax 之前做把 padding 位置对应的分数设为-inf或一个绝对值很大的负数。这里有个细节——如果全用-inf数值稳定性更好但某些框架/算子对-inf的 softmax 处理方式不同可能导致 NaN更稳妥的做法是加一个绝对值足够大的负数如-1e9既能达到遮断效果又避免 NaN。5.3 混合精度与乘积的浮点陷阱训练大模型时混合精度已经是标配。FP16 能省一半显存但代价是溢出风险。FP16 的最大有限表示约 65504。如果你的注意力分数QK^T稍微大一点除以sqrt(d_k)之前就已经溢出为 inf那 softmax 输出就是 NaN。我在用 FP16 做训练时遇到过一次崩溃最后的定位结果就是注意力得分过大溢出。Pytorch 的 AMPAutomatic Mixed Precision通过一个 loss scaling 技术前向计算前放大 loss反向传播算完后缩小梯度保证中间梯度不进小数下溢区间。但如果你自己在自定义 CUDA 算子或手写反向传播没做 loss scaling问题就会冒出来。这里列一个浮点精度的自查表精度最大有限值最小正规格化数典型问题FP32~3.4e38~1.2e-38大多数场景下足够FP1665504~6.1e-5中间结果容易溢出/下溢BF16~3.4e38~1.2e-38范围接近 FP32但精度低BF16 目前是深度学习训练的宠儿因为它先把 FP16 的溢出问题解决了一半——范围比精度更重要因为在大多数张量运算中范围超限导致的 NaN 会让整个训练崩溃而精度损失只影响训练质量的一小部分。5.4 softmax 的 log-sum-exp 技巧也是乘积的守卫softmax 的常见实现softmax(x)_i exp(x_i) / Σ_j exp(x_j)如果x_i都很大比如都是 500exp(500)在 FP32 里直接溢出为 inf结果变成 NaN。工程上的做法是softmax(x)_i exp(x_i - max(x)) / Σ_j exp(x_j - max(x))减去最大值后再算 exp所有指数都落在(-inf, 0]区间不会溢出而且 softmax 的结果在数学上完全等价。这就是 log-sum-exp 技巧。在手写 attention 时我见过不少初学者在 softmax 这一步栽跟头分数没做最大减法d_k 稍大一点再加上 FP16 精度训练第一个 step 就 NaN。这个细节比损失函数的公式更容易忽略却比损失函数更致命。5.5 实际调参中遇到的乘积相关坑最后分享一个我实际踩过、排查了很久的坑。场景是训练一个 Transformer 做序列推荐。模型前几轮 loss 正常下降到第 5 轮左右突然变成 NaN。一开始怀疑学习率太大降了以后还是 NaN。后来怀疑是数据里有脏数据清洗之后依然复现。最后排查到根因某一层的 hidden state 增长过快导致残差连接中的加和溢出。而增长过快的原因是 LayerNorm 之前没有做 scale 约束矩阵乘法出来的值域在训练过程中逐渐变大最终超过 FP16 可表示范围一旦到 65504 就翻车。解决方式是调整初始化把 attention 输出层的参数初始化标准差调小同时给残差分支加一个 learnable scale。这件事给了我一条宝贵的经验在混合精度训练中别把所有问题都归因于优化器或数据很多 NaN 的根源都在某种乘积/加和操作的溢出上。排查思路是定位到第一次出现 inf/NaN 的张量然后往前倒推是哪个运算把它撑爆了。常规的数值稳定性检查清单里“最大值在哪一步产生”“那一步是否有平方/求和”是必须回答的两个问题。6. 从乘积视角重新审视深度学习的线性变换本质绕了一大圈回到一个最基础的认知深度学习模型的表达能力来自线性变换和非线性激活的交替堆叠。而线性变换的载体就是各种乘积。矩阵乘法负责跨维度信息混合Hadamard乘积负责逐维度门控缩放外积负责构造特征交互Kronecker乘积负责结构化低秩表达。你把模型里的每种乘法都看清了模型对你来说就基本透明了。以 CNN 为例卷积核滑动计算的本质就是局部区域与卷积核做内积多个卷积核就是多个内积模板池化是局部窗口内的“点积变体”average 是权重全 1 的内积全连接层是全局矩阵乘法BatchNorm 是逐通道缩放Hadamard乘积。深度模型里没有一个角落能脱离乘积而存在。我当年入门时最受益的一个习惯是读模型源码时看到一个运算先在旁边标注它属于哪一类乘积、它的输出 shape 是多少。一段时间以后不同类型的乘积在我脑子里就自动有了各自的“声音”。你如果也打算深入机器学习方向强烈建议从这个习惯开始。按照我自己的经验与其花大量时间背各种网络架构不如先把线性代数里的各种乘法玩熟。你可能会发现很多看起来花哨的模型结构本质不过是几种乘积的组合变形并没有想象中那么神秘。