本文档详细说明 OriginDL 框架中各个算子的前向传播与反向传播数学原理,并通过具体例子展示计算过程,帮助快速理解算子实现。
加法算子的前向传播执行元素级加法运算:y = x0 + x1
数学描述:
- 对于相同位置的元素,输出等于两个输入对应元素的和:
y[i] = x0[i] + x1[i] - 当两个输入张量的形状不同时,支持广播机制:较小的张量会在缺失维度上扩展,然后进行元素级加法
- 广播规则:从最右边的维度开始,如果维度大小相同或其中一个为 1,则可以广播
对于加法运算 y = x0 + x1,根据链式法则和偏导数:
∂y/∂x0 = 1,∂y/∂x1 = 1
因此,如果输出梯度为 gy,则:
gx0 = gy × ∂y/∂x0 = gy × 1 = gygx1 = gy × ∂y/∂x1 = gy × 1 = gy
数学原理:
- 加法对每个输入的偏导数都是 1,因此梯度直接传递
- 当发生广播时,被广播的输入在多个位置被使用,其梯度需要累加所有使用位置的梯度
- 具体地,如果
x1被广播到形状S,而原始形状是S0,则gx1需要在广播维度上求和:gx1 = sum(gy, dims=广播维度)
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2) - 输入:
x1 = [[5, 6], [7, 8]],形状(2, 2) - 输出:
y = x0 + x1 = [[6, 8], [10, 12]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx0 = gy = [[0.1, 0.2], [0.3, 0.4]]gx1 = gy = [[0.1, 0.2], [0.3, 0.4]]
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2) - 输入:
x1 = [10, 20],形状(2,) - 广播后:
x1广播为[[10, 20], [10, 20]],形状(2, 2) - 输出:
y = [[11, 22], [13, 24]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 初始梯度:
gx0 = gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)(匹配原始形状)gx1 = gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)(不匹配原始形状(2,))
- 形状恢复:
gx0形状已匹配,无需调整gx1需要压缩:gx1 = sum_to([[0.1, 0.2], [0.3, 0.4]], (2,)) = [0.1+0.3, 0.2+0.4] = [0.4, 0.6]
- 最终梯度:
gx0 = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)gx1 = [0.4, 0.6],形状(2,)
数学解释:
在广播加法中,x1 的每个元素被加到 x0 的对应行上。反向传播时,x1 的梯度需要累加所有使用该元素的位置的梯度:
x1[0] = 10被用于y[0,0]和y[1,0],所以gx1[0] = gy[0,0] + gy[1,0] = 0.1 + 0.3 = 0.4x1[1] = 20被用于y[0,1]和y[1,1],所以gx1[1] = gy[0,1] + gy[1,1] = 0.2 + 0.4 = 0.6
减法算子的前向传播执行元素级减法运算:y = x0 - x1
数学描述:
- 对于相同位置的元素,输出等于两个输入对应元素的差:
y[i] = x0[i] - x1[i] - 当两个输入张量的形状不同时,支持广播机制:较小的张量会在缺失维度上扩展,然后进行元素级减法
- 广播规则:从最右边的维度开始,如果维度大小相同或其中一个为 1,则可以广播
对于减法运算 y = x0 - x1,根据链式法则和偏导数:
∂y/∂x0 = 1,∂y/∂x1 = -1
因此,如果输出梯度为 gy,则:
gx0 = gy × ∂y/∂x0 = gy × 1 = gygx1 = gy × ∂y/∂x1 = gy × (-1) = -gy
数学原理:
- 减法对第一个输入的偏导数是 1,对第二个输入的偏导数是 -1
- 当发生广播时,被广播的输入在多个位置被使用,其梯度需要累加所有使用位置的梯度(第二个输入需要取负号后累加)
前向传播:
- 输入:
x0 = [[5, 6], [7, 8]],形状(2, 2) - 输入:
x1 = [[1, 2], [3, 4]],形状(2, 2) - 输出:
y = x0 - x1 = [[4, 4], [4, 4]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx0 = gy = [[0.1, 0.2], [0.3, 0.4]]gx1 = -gy = [[-0.1, -0.2], [-0.3, -0.4]]
前向传播:
- 输入:
x0 = [[5, 6], [7, 8]],形状(2, 2) - 输入:
x1 = [1, 2],形状(2,) - 广播后:
x1广播为[[1, 2], [1, 2]],形状(2, 2) - 输出:
y = [[4, 4], [6, 6]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 初始梯度:
gx0 = gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)(匹配原始形状)gx1 = -gy = [[-0.1, -0.2], [-0.3, -0.4]],形状(2, 2)(不匹配原始形状(2,))
- 形状恢复:
gx0形状已匹配,无需调整gx1需要压缩:gx1 = sum_to([[-0.1, -0.2], [-0.3, -0.4]], (2,)) = [-0.1+(-0.3), -0.2+(-0.4)] = [-0.4, -0.6]
- 最终梯度:
gx0 = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)gx1 = [-0.4, -0.6],形状(2,)
数学解释:
在广播减法中,x1 的每个元素被从 x0 的对应行中减去。反向传播时,x1 的梯度需要累加所有使用该元素的位置的梯度(注意是负号):
x1[0] = 1被用于y[0,0]和y[1,0],所以gx1[0] = -gy[0,0] + (-gy[1,0]) = -0.1 + (-0.3) = -0.4x1[1] = 2被用于y[0,1]和y[1,1],所以gx1[1] = -gy[0,1] + (-gy[1,1]) = -0.2 + (-0.4) = -0.6
乘法算子的前向传播执行元素级乘法运算:y = x0 × x1
数学描述:
- 对于相同位置的元素,输出等于两个输入对应元素的乘积:
y[i] = x0[i] × x1[i] - 当两个输入张量的形状不同时,支持广播机制:较小的张量会在缺失维度上扩展,然后进行元素级乘法
- 广播规则:从最右边的维度开始,如果维度大小相同或其中一个为 1,则可以广播
对于乘法运算 y = x0 × x1,根据链式法则和偏导数:
∂y/∂x0 = x1,∂y/∂x1 = x0
因此,如果输出梯度为 gy,则:
gx0 = gy × ∂y/∂x0 = gy × x1gx1 = gy × ∂y/∂x1 = gy × x0
数学原理:
- 乘法对第一个输入的偏导数是第二个输入的值,对第二个输入的偏导数是第一个输入的值
- 当发生广播时,被广播的输入在多个位置被使用,其梯度需要累加所有使用位置的梯度
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2) - 输入:
x1 = [[5, 6], [7, 8]],形状(2, 2) - 输出:
y = x0 × x1 = [[5, 12], [21, 32]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx0 = gy × x1 = [[0.1, 0.2], [0.3, 0.4]] × [[5, 6], [7, 8]] = [[0.5, 1.2], [2.1, 3.2]]gx1 = gy × x0 = [[0.1, 0.2], [0.3, 0.4]] × [[1, 2], [3, 4]] = [[0.1, 0.4], [0.9, 1.6]]
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2) - 输入:
x1 = [5, 6],形状(2,) - 广播后:
x1广播为[[5, 6], [5, 6]],形状(2, 2) - 输出:
y = [[1×5, 2×6], [3×5, 4×6]] = [[5, 12], [15, 24]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 初始梯度:
gx0 = gy × x1_broadcast = [[0.1, 0.2], [0.3, 0.4]] × [[5, 6], [5, 6]] = [[0.5, 1.2], [1.5, 2.4]],形状(2, 2)(匹配x0原始形状)gx1 = gy × x0 = [[0.1, 0.2], [0.3, 0.4]] × [[1, 2], [3, 4]] = [[0.1, 0.4], [0.9, 1.6]],形状(2, 2)(不匹配x1原始形状(2,))
- 形状恢复(对
gx1执行sum_to):gx1 = sum_to([[0.1, 0.4], [0.9, 1.6]], (2,)) = [0.1+0.9, 0.4+1.6] = [1.0, 2.0],形状(2,)
数学解释:
x1[0] = 5被用于y[0,0] = 1×5和y[1,0] = 3×5,所以gx1[0] = gy[0,0]×1 + gy[1,0]×3 = 0.1×1 + 0.3×3 = 1.0x1[1] = 6被用于y[0,1] = 2×6和y[1,1] = 4×6,所以gx1[1] = gy[0,1]×2 + gy[1,1]×4 = 0.2×2 + 0.4×4 = 2.0
除法算子的前向传播执行元素级除法运算:y = x0 / x1
数学描述:
- 对于相同位置的元素,输出等于两个输入对应元素的商:
y[i] = x0[i] / x1[i] - 当两个输入张量的形状不同时,支持广播机制:较小的张量会在缺失维度上扩展,然后进行元素级除法
- 广播规则:从最右边的维度开始,如果维度大小相同或其中一个为 1,则可以广播
- 注意:当
x1[i] = 0时,除法未定义,通常会产生错误或特殊值(如 inf)
对于除法运算 y = x0 / x1,根据链式法则和偏导数:
∂y/∂x0 = 1 / x1∂y/∂x1 = -x0 / x1²
因此,如果输出梯度为 gy,则:
gx0 = gy × ∂y/∂x0 = gy / x1gx1 = gy × ∂y/∂x1 = -gy × x0 / x1²
数学原理:
- 除法对第一个输入的偏导数是
1/x1,对第二个输入的偏导数是-x0/x1² - 当发生广播时,被广播的输入在多个位置被使用,其梯度需要累加所有使用位置的梯度
前向传播:
- 输入:
x0 = [[6, 8], [10, 12]],形状(2, 2) - 输入:
x1 = [[2, 2], [2, 3]],形状(2, 2) - 输出:
y = x0 / x1 = [[3, 4], [5, 4]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx0 = gy / x1 = [[0.1, 0.2], [0.3, 0.4]] / [[2, 2], [2, 3]] = [[0.05, 0.1], [0.15, 0.133...]]gx1 = -gy × x0 / x1² = -[[0.1, 0.2], [0.3, 0.4]] × [[6, 8], [10, 12]] / [[4, 4], [4, 9]] = [[-0.15, -0.4], [-0.75, -0.533...]]
前向传播:
- 输入:
x0 = [[6, 8], [10, 12]],形状(2, 2) - 输入:
x1 = [2, 4],形状(2,) - 广播后:
x1广播为[[2, 4], [2, 4]],形状(2, 2) - 输出:
y = x0 / x1_broadcast = [[6/2, 8/4], [10/2, 12/4]] = [[3, 2], [5, 3]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 初始梯度(使用广播后的
x1_broadcast):gx0 = gy / x1_broadcast = [[0.1, 0.2], [0.3, 0.4]] / [[2, 4], [2, 4]] = [[0.05, 0.05], [0.15, 0.1]],形状(2, 2)(匹配x0原始形状)gx1 = -gy × x0 / x1_broadcast²x1_broadcast² = [[4, 16], [4, 16]]-gy × x0 / x1_broadcast² = -[[0.1×6/4, 0.2×8/16], [0.3×10/4, 0.4×12/16]] = [[-0.15, -0.1], [-0.75, -0.3]],形状(2, 2)(不匹配x1原始形状(2,))
- 形状恢复(对
gx1执行sum_to):gx1 = sum_to([[-0.15, -0.1], [-0.75, -0.3]], (2,)) = [-0.15+(-0.75), -0.1+(-0.3)] = [-0.9, -0.4],形状(2,)
数学解释:
x1[0] = 2被用于y[0,0] = 6/2和y[1,0] = 10/2,所以gx1[0] = -gy[0,0]×6/2² + -gy[1,0]×10/2² = -0.15 + -0.75 = -0.9x1[1] = 4被用于y[0,1] = 8/4和y[1,1] = 12/4,所以gx1[1] = -gy[0,1]×8/4² + -gy[1,1]×12/4² = -0.1 + -0.3 = -0.4
矩阵乘法算子的前向传播执行矩阵乘法运算:y = x0 @ x1
数学描述:
- 输入:两个矩阵
x0形状(m, k)和x1形状(k, n) - 输出:矩阵
y形状(m, n),其中y[i, j] = sum(x0[i, :] × x1[:, j]) - 维度要求:
x0的列数必须等于x1的行数 - 支持批量矩阵乘法:
x0形状(batch, m, k)和x1形状(k, n),输出形状(batch, m, n)
数学表示:
对于矩阵乘法 y = x0 @ x1,根据链式法则和偏导数:
∂y/∂x0 = gy @ transpose(x1)∂y/∂x1 = transpose(x0) @ gy
因此,如果输出梯度为 gy,则:
gx0 = gy @ transpose(x1)gx1 = transpose(x0) @ gy
数学原理:
- 矩阵乘法对第一个输入的梯度是输出梯度与第二个输入转置的矩阵乘法
- 矩阵乘法对第二个输入的梯度是第一个输入转置与输出梯度的矩阵乘法
- 转置操作确保了维度匹配
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2) - 输入:
x1 = [[5, 6], [7, 8]],形状(2, 2) - 输出:
y = x0 @ x1 = [[1×5+2×7, 1×6+2×8], [3×5+4×7, 3×6+4×8]] = [[19, 22], [43, 50]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx0 = gy @ transpose(x1) = [[0.1, 0.2], [0.3, 0.4]] @ [[5, 7], [6, 8]] = [[1.7, 2.3], [3.9, 5.3]]gx1 = transpose(x0) @ gy = [[1, 3], [2, 4]] @ [[0.1, 0.2], [0.3, 0.4]] = [[1.0, 1.4], [1.4, 2.0]]
幂运算算子的前向传播执行:$y = x^{\text{exponent}}$
数学描述:
-
输入:张量
$x$ 和标量指数$\text{exponent}$ -
输出:$y_i = x_i^{\text{exponent}}$
-
数学表达式:
$$ y = \operatorname{pow}(x, \text{exponent}) = x^{\text{exponent}} $$
-
支持任意实数指数,包括分数指数(开方)
对于幂运算
因此,如果输出梯度为
数学原理:
- 幂函数的导数是
$\text{exponent} \cdot x^{\text{exponent}-1}$ - 当
$\text{exponent} = 0$ 时,$y = 1$,梯度为 0 - 当
$\text{exponent} = 1$ 时,$y = x$,梯度为$g_y$ - 当
$\text{exponent} = 2$ 时,$y = x^2$,梯度为$g_y \times 2x$
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 指数:
exponent = 2 - 输出:
y = x² = [[1, 4], [9, 16]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = gy × 2 × x = [[0.1, 0.2], [0.3, 0.4]] × 2 × [[1, 2], [3, 4]] = [[0.2, 0.8], [1.8, 3.2]]
在 OriginDL 中,指数函数特指以自然常数 e 为底的指数函数,即 exp(x) = e^x,而不是一般形式的 a^x(其中 a 为任意底数)。
指数算子的前向传播执行:$y = \exp(x)$
数学描述:
-
输入:张量
$x$ (可以是任意实数) -
输出:$y_i = \exp(x_i) = e^{x_i}$
-
数学表达式:
$$ y = \exp(x) $$
-
指数函数的输出范围是
$(0, +\infty)$ ,无论输入是正数、负数还是零,输出总是大于 0
对于指数运算
因此,如果输出梯度为
数学原理:
- 指数函数的导数等于其自身:$\frac{\mathrm{d}}{\mathrm{d}x}\exp(x) = \exp(x)$
- 反向传播时,梯度等于输出梯度乘以输出值本身
- 这使得指数函数的梯度计算非常高效
前向传播:
- 输入:
x = [[0, 1], [2, -1]],形状(2, 2) - 输出:
y = exp(x) = [[1.0, 2.718...], [7.389..., 0.368...]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = gy × y = [[0.1, 0.2], [0.3, 0.4]] × [[1.0, 2.718...], [7.389..., 0.368...]] = [[0.1, 0.544...], [2.217..., 0.147...]]
在 OriginDL 中,对数函数特指以自然常数 e 为底的对数函数,即 log(x) = ln(x),等于数学上的自然对数。
对数算子的前向传播执行:$y = \log(x)$
数学描述:
-
输入:张量
$x$ (要求$x > 0$ ) -
输出:$y_i = \log(x_i) = \ln(x_i)$
-
数学表达式(自然对数,以
$e$ 为底):$$ y = \log(x) $$
-
对数函数将正数映射到实数范围
对于对数运算
因此,如果输出梯度为
数学原理:
- 对数函数的导数是
$1/x$ :$\frac{\mathrm{d}}{\mathrm{d}x}\log(x) = 1/x$ - 反向传播时,梯度等于输出梯度除以输入值
- 注意:当
$x \le 0$ 时,对数未定义,通常会产生错误或特殊值
前向传播:
- 输入:
x = [[1, 2.718...], [7.389..., 0.5]],形状(2, 2) - 输出:
y = log(x) = [[0, 1], [2, -0.693...]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = gy / x = [[0.1, 0.2], [0.3, 0.4]] / [[1, 2.718...], [7.389..., 0.5]] = [[0.1, 0.074...], [0.041..., 0.8]]
取负算子的前向传播执行:$y = -x$
数学描述:
-
输入:张量
$x$ -
输出:$y_i = -x_i$
-
数学表达式:
$$ y = \operatorname{neg}(x) = -x $$
-
取负操作将每个元素的值取相反数
对于取负运算
因此,如果输出梯度为
数学原理:
- 取负函数的导数是
$-1$ - 反向传播时,梯度等于输出梯度的相反数
- 这是最简单的线性变换之一
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 输出:
y = -x = [[-1, -2], [-3, -4]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = -gy = [[-0.1, -0.2], [-0.3, -0.4]],形状(2, 2)
平方算子的前向传播执行:$y = x^2$
数学描述:
-
输入:张量
$x$ -
输出:$y_i = x_i^2 = x_i \cdot x_i$
-
数学表达式:
$$ y = \operatorname{square}(x) = x^2 $$
-
平方操作是幂运算的特例($\text{exponent} = 2$)
对于平方运算
因此,如果输出梯度为
数学原理:
- 平方函数的导数是
$2x$ - 反向传播时,梯度等于输出梯度乘以
$2x$ - 这是幂运算在
$\text{exponent} = 2$ 时的特例
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 输出:
y = x² = [[1, 4], [9, 16]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = gy × 2 × x = [[0.1, 0.2], [0.3, 0.4]] × 2 × [[1, 2], [3, 4]] = [[0.2, 0.8], [1.8, 3.2]]
求和算子的前向传播执行:y = sum(x, axis=axis, keepdim=keepdim)
数学描述:
- 输入:张量
x、指定的轴axis和keepdim参数 - 输出:沿指定轴求和后的张量
- 如果
axis = -1或未指定,则对所有元素求和 - 如果指定了
axis,则沿该轴求和 keepdim参数控制是否保持维度:keepdim=false(默认):输出维度减少 1(移除求和轴)keepdim=true:输出维度保持不变,但求和轴的大小变为 1
数学表示:
-
沿第
$k$ 维求和,移除该维度($\text{keepdim} = \mathrm{false}$):$$ y = \operatorname{sum}(x, \mathrm{axis}=k, \mathrm{keepdim}=\mathrm{false}) $$
$$ y_{i_0, \dots, i_{k-1}, i_{k+1}, \dots, i_n} = \sum_{i_k} x_{i_0, \dots, i_k, \dots, i_n} $$
-
沿第
$k$ 维求和,保持维度($\text{keepdim} = \mathrm{true}$):$$ y = \operatorname{sum}(x, \mathrm{axis}=k, \mathrm{keepdim}=\mathrm{true}) $$
$$ y_{i_0, \dots, i_{k-1}, 1, i_{k+1}, \dots, i_n} = \sum_{i_k} x_{i_0, \dots, i_k, \dots, i_n} $$
对于求和运算 y = sum(x, axis=k, keepdim=keepdim),根据链式法则和偏导数:
∂y/∂x = 1(在求和维度上)
因此,如果输出梯度为 gy,则:
gx = broadcast(gy, x.shape)
数学原理:
- 求和的反向传播是广播:输出梯度被广播回原始形状
- 因为每个输入元素对输出的贡献是独立的,所以梯度直接传递
- 广播操作确保梯度形状与输入形状匹配
keepdim参数不影响反向传播逻辑,因为broadcast_to可以自动处理包含维度 1 的梯度形状
前向传播:
- 输入:
x = [[1, 2, 3], [4, 5, 6]],形状(2, 3) - 沿轴 0 求和:
y = sum(x, axis=0, keepdim=false) = [1+4, 2+5, 3+6] = [5, 7, 9],形状(3,)- 沿第 0 维(行)压缩,保留第 1 维(列)
反向传播:
- 输出梯度:
gy = [0.1, 0.2, 0.3],形状(3,) - 输入梯度:
gx = broadcast(gy, (2, 3)) = [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]],形状(2, 3)- 梯度广播回原始形状,每行的梯度相同
前向传播:
- 输入:
x = [[1, 2, 3], [4, 5, 6]],形状(2, 3) - 沿轴 0 求和,keepdim=true:
y = sum(x, axis=0, keepdim=true) = [[5, 7, 9]],形状(1, 3)- 沿第 0 维(行)压缩,但保持维度,第 0 维变为 1
反向传播:
- 输出梯度:
gy = [[0.1, 0.2, 0.3]],形状(1, 3) - 输入梯度:
gx = broadcast(gy, (2, 3)) = [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]],形状(2, 3)- 梯度从
(1, 3)广播回(2, 3),每行的梯度相同
- 梯度从
前向传播:
- 输入:
x = [[1, 2, 3], [4, 5, 6]],形状(2, 3) - 全局求和:
y = sum(x, axis=-1, keepdim=false) = 21,形状(1,)- 所有维度都被压缩,输出标量(形状为
(1,))
- 所有维度都被压缩,输出标量(形状为
反向传播:
- 输出梯度:
gy = 0.5(标量,形状(1,)) - 输入梯度:
gx = broadcast(gy, (2, 3)) = [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]],形状(2, 3)- 标量梯度广播到所有位置
前向传播:
- 输入:
x = [[1, 2, 3], [4, 5, 6]],形状(2, 3) - 全局求和,keepdim=true:
y = sum(x, axis=-1, keepdim=true) = 21,形状(1, 1)- 所有维度都被压缩为 1,但保持维度数
反向传播:
- 输出梯度:
gy = 0.5(形状(1, 1)) - 输入梯度:
gx = broadcast(gy, (2, 3)) = [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]],形状(2, 3)- 梯度从
(1, 1)广播回(2, 3)
- 梯度从
广播算子的前向传播执行:y = broadcast_to(x, shape)
数学描述:
- 输入:张量
$x$ 和目标形状$\mathrm{shape}$ - 输出:将
$x$ 广播到目标形状$\mathrm{shape}$ - 广播规则:从最右边的维度开始,如果维度大小相同或其中一个为 1,则可以广播
- 广播时,大小为 1 的维度会被复制到目标大小
数学表示:
输出
对于广播运算 y = broadcast_to(x, shape),根据链式法则:
- 反向传播是压缩:
gx = sum_to(gy, x.shape)
数学原理:
- 广播的反向传播是压缩:输出梯度需要在广播维度上求和
- 因为前向传播时,一个输入元素被复制到多个输出位置
- 反向传播时,这些位置的梯度需要累加回对应的输入位置
前向传播:
- 输入:
x = [1, 2],形状(2,) - 目标形状:
(2, 2) - 输出:
y = broadcast_to(x, (2, 2)) = [[1, 2], [1, 2]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = sum_to(gy, (2,)) = [0.1+0.3, 0.2+0.4] = [0.4, 0.6],形状(2,)
数学解释:
x[0] = 1被广播到y[0,0]和y[1,0],所以gx[0] = gy[0,0] + gy[1,0] = 0.1 + 0.3 = 0.4x[1] = 2被广播到y[0,1]和y[1,1],所以gx[1] = gy[0,1] + gy[1,1] = 0.2 + 0.4 = 0.6
压缩到指定形状算子的前向传播将输入张量压缩到目标形状:y = sum_to(x, target_shape)
数学描述:
- 输入:张量
x,形状为(d0, d1, ..., dm) - 目标形状:
target_shape = (t0, t1, ..., tk),其中k ≤ m - 输出:
y,形状为target_shape - 压缩规则:在需要压缩的维度上对元素求和,将维度大小从
di压缩到ti
压缩过程:
- 如果
di > ti:在第i维上对元素求和,将维度大小从di压缩到ti - 如果
di == ti:保持该维度不变 - 如果
di < ti:这种情况不应该发生,会抛出错误
对于压缩运算 y = sum_to(x, target_shape),根据链式法则和偏导数:
∂y/∂x在压缩维度上的偏导数为 1(因为求和操作对每个元素的偏导数为 1)
因此,如果输出梯度为 gy,则:
gx = broadcast_to(gy, x.shape)
数学原理:
- 压缩操作的反向传播是广播操作
- 输出梯度
gy的形状是target_shape,需要广播回输入形状x.shape - 在压缩的维度上,梯度会被广播到所有原始元素位置
前向传播:
- 输入:
x = [[1, 2, 3], [4, 5, 6]],形状(2, 3) - 目标形状:
target_shape = (2,) - 压缩:在第 1 维上求和,
y = sum_to(x, (2,)) = [1+2+3, 4+5+6] = [6, 15],形状(2,)
反向传播:
- 输出梯度:
gy = [0.1, 0.2],形状(2,) - 广播:
gx = broadcast_to(gy, (2, 3)) = [[0.1, 0.1, 0.1], [0.2, 0.2, 0.2]],形状(2, 3)
比较算子包括:>(Greater)、>=(GreaterEqual)、<(Less)、<=(LessEqual)、==(Equal)、!=(NotEqual)。
本节以**大于算子(Greater)**为例详细介绍比较算子的原理,其他比较运算符的实现方式与大于算子类似,只是比较条件不同。
大于算子的前向传播执行元素级比较运算:y = x > threshold
数学描述:
- 输入:张量
x,形状为(d0, d1, ..., dm) - 阈值:
threshold,可以是标量(元素数量为 1)或与x相同形状的张量 - 输出:
y[i] = 1如果x[i] > threshold[i],否则y[i] = 0 - 输出形状:与输入
x相同 - 输出数据类型:与输入
x相同,用0或1表示布尔值)
支持的模式:
- 相同形状比较:当
threshold与x形状相同时,执行逐元素比较 - 标量广播比较:当
threshold是标量(元素数量为 1)时,将标量广播到x的形状,然后执行逐元素比较
数学表达式:
当
比较运算符是不可微的离散操作,在反向传播中对输入的梯度为零。
数学原理:
- 比较操作
y = (x > threshold)在几乎所有点处的偏导数∂y/∂x = 0 - 这是因为比较操作只在
x = threshold处发生跳变,而这是一个测度为零的集合 - 参考 PyTorch 的实现,比较运算符的反向传播返回全零梯度
因此,如果输出梯度为 gy,则:
gx = zeros_like(x)(全零梯度,形状与x相同)gthreshold = zeros_like(threshold)(全零梯度,形状与threshold相同)
数学原理:
- 比较运算符本质上是不可微的,因为它们是离散的跳跃操作
- 在深度学习中,比较运算符通常用于生成掩码(mask),而不是直接参与梯度计算
- 如果需要梯度信息,通常会将比较结果转换为浮点数后再参与后续计算
前向传播:
- 输入:
x = [[1.0, 2.0], [3.0, 4.0]],形状(2, 2) - 阈值:
threshold = 2.0(标量) - 输出:
y = [[0.0, 0.0], [1.0, 1.0]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = [[0.0, 0.0], [0.0, 0.0]],形状(2, 2)(全零)gthreshold = 0.0,形状()(全零)
前向传播:
- 输入:
x = [[1.0, 2.0], [3.0, 4.0]],形状(2, 2) - 阈值:
threshold = [[2.0, 1.0], [3.0, 5.0]],形状(2, 2) - 输出:
y = [[0.0, 1.0], [0.0, 0.0]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = [[0.0, 0.0], [0.0, 0.0]],形状(2, 2)(全零)gthreshold = [[0.0, 0.0], [0.0, 0.0]],形状(2, 2)(全零)
其他比较运算符(>=、<、<=、==、!=)的实现方式与大于算子类似:
- 大于等于(GreaterEqual):
y = (x >= threshold) ? 1 : 0 - 小于(Less):
y = (x < threshold) ? 1 : 0 - 小于等于(LessEqual):
y = (x <= threshold) ? 1 : 0 - 等于(Equal):
y = (x == threshold) ? 1 : 0 - 不等于(NotEqual):
y = (x != threshold) ? 1 : 0
所有比较运算符的反向传播都返回全零梯度,因为它们都是不可微的离散操作。
ReLU(Rectified Linear Unit)算子的前向传播执行:$y = \max(0, x)$
数学描述:
-
对于输入
$x$ 的每个元素,如果$x_i > 0$ ,则$y_i = x_i$ ;否则$y_i = 0$ -
数学表达式:
$$ y = \operatorname{ReLU}(x) = \max(0, x) $$
-
ReLU 是一个非线性激活函数,用于引入非线性特性
对于 ReLU 运算
\begin{cases} 1, & x > 0 \ 0, & x \le 0 \end{cases} $$
因此,如果输出梯度为
或者写成“掩码”形式:$g_x = g_y \times \mathbf{1}_{x > 0}$
数学原理:
- ReLU 的梯度是分段函数:正数位置梯度为 1,非正数位置梯度为 0
- 反向传播时,需要根据前向传播时的输入值
x来判断哪些位置的梯度需要传递 - 具体地,
gx[i] = gy[i]如果x[i] > 0,否则gx[i] = 0
前向传播:
- 输入:
x = [[-1, 2], [-3, 4]],形状(2, 2) - 输出:
y = relu(x) = [[0, 2], [0, 4]],形状(2, 2)x[0,0] = -1 < 0,所以y[0,0] = 0x[0,1] = 2 > 0,所以y[0,1] = 2x[1,0] = -3 < 0,所以y[1,0] = 0x[1,1] = 4 > 0,所以y[1,1] = 4
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 计算 mask(基于前向输入
x):mask = [[0, 1], [0, 1]](x > 0的位置为 1,否则为 0)
- 输入梯度:
gx = gy × maskgx = [[0.1, 0.2], × [[0, 1], = [[0, 0.2], [0.3, 0.4]] [0, 1]] [0, 0.4]] - 最终梯度:
gx = [[0, 0.2], [0, 0.4]],形状(2, 2)
数学解释:
x[0,0] = -1和x[1,0] = -3在前向传播时输出为 0,因此这些位置的梯度不传播(梯度为 0)x[0,1] = 2和x[1,1] = 4在前向传播时输出等于输入,因此梯度直接传递
Sigmoid 算子的前向传播执行:$y = \sigma(x) = \frac{1}{1 + e^{-x}}$
数学描述:
-
输入:张量
$x$ -
对于每个元素,有
$y_i = \sigma(x_i) = \frac{1}{1 + e^{-x_i}}$ -
数学表达式:
$$ y = \sigma(x) = \frac{1}{1 + e^{-x}} $$
-
Sigmoid 函数将输入映射到
$(0, 1)$ 区间,常用于二分类问题的输出层
对于 Sigmoid 运算
因此,如果输出梯度为
数学原理:
- Sigmoid 函数的导数可以用其自身表示:$\sigma'(x) = \sigma(x)\bigl(1 - \sigma(x)\bigr)$
- 反向传播时,梯度等于输出梯度乘以
$y \times (1 - y)$ - 当
$y$ 接近 0 或 1 时,梯度接近 0,可能导致梯度消失问题
前向传播:
- 输入:
x = [[0, 1], [-1, 2]],形状(2, 2) - 输出:
y = sigmoid(x) = [[0.5, 0.731...], [0.269..., 0.881...]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 计算
y × (1 - y):y × (1 - y) = [[0.5×0.5, 0.731×0.269...], [0.269×0.731..., 0.881×0.119...]] = [[0.25, 0.197...], [0.197..., 0.105...]]
- 输入梯度:
gx = gy × y × (1 - y) = [[0.1, 0.2], [0.3, 0.4]] × [[0.25, 0.197...], [0.197..., 0.105...]] = [[0.025, 0.039...], [0.059..., 0.042...]]
Softmax 算子的前向传播执行:$y = \operatorname{softmax}(x, \mathrm{axis})$
数学定义(向量形式):
- 对任意实数向量
$z = (z_1, z_2, \dots, z_n)$ ,第$i$ 个输出定义为:
- 对于向量中的每个元素
$z_i$ ,有 $\operatorname{softmax}(z)i \in (0, 1)$,且 $\sum{i=1}^n \operatorname{softmax}(z)_i = 1$
张量 / axis 形式描述:
- 输入:张量
$x$ 和指定的轴$\mathrm{axis}$ ,在该轴上的每个切片视作一条向量$\mathbf{z}$ - 输出:$y = \operatorname{softmax}(x, \mathrm{axis})$,即对每条向量
$\mathbf{z}$ 按上述定义做 Softmax,使得:- 在
$\mathrm{axis}$ 轴上有:$0 < y[i] < 1$ - 在
$\mathrm{axis}$ 轴上有:$\operatorname{sum}(y, \mathrm{axis}=\mathrm{axis}) = 1$
- 在
数学特性:
- 归一化:输出在指定轴上的和为 1,适合作为概率分布
- 非线性:通过指数函数引入非线性变换
- 单调性:在同一向量内部保持输入元素的相对大小关系
- 平滑性:函数处处可导,提供平滑梯度,有利于优化
计算稳定性(数值稳定 Softmax):
- 直接按
$e^{z_i}$ 计算时,当$z_i$ 绝对值较大时容易出现数值溢出 - 常用的数值稳定技巧是减去一个常数
$C$ (通常取该向量的最大值),不改变结果:
对于 Softmax 运算
因此,如果输出梯度为
数学原理:
- Softmax 的梯度计算需要考虑所有输出之间的相关性
- 梯度等于输出值乘以(输出梯度减去加权平均)
- 这确保了梯度在概率分布上的正确传播
前向传播:
- 输入:
x = [[1, 2], [3, 1]],形状(2, 2),axis = 1 - 计算:
max(x, axis=1) = [2, 3],exp(x - max) = [[exp(-1), 1], [1, exp(-2)]] - 输出:
y = softmax(x, axis=1) = [[0.269..., 0.731...], [0.881..., 0.119...]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 计算
sum(gy × y, axis=1) = [0.1×0.269+0.2×0.731, 0.3×0.881+0.4×0.119] = [0.173..., 0.300...] - 输入梯度:
gx = y × (gy - sum(gy × y)) = [[0.269..., 0.731...], [0.881..., 0.119...]] × [[0.1-0.173..., 0.2-0.173...], [0.3-0.300..., 0.4-0.300...]]
SiLU(Sigmoid Linear Unit)算子的前向传播执行:$y = \operatorname{SiLU}(x) = x \cdot \sigma(x)$
数学描述:
-
输入:张量
$x$ -
输出:$y_i = x_i \cdot \sigma(x_i)$
-
数学表达式:
$$ y = \operatorname{SiLU}(x) = x \cdot \sigma(x) $$
-
SiLU 是 Swish 激活函数的变体,结合了线性和非线性特性
对于 SiLU 运算
因此,如果输出梯度为
数学原理:
- SiLU 的导数可以用 sigmoid 函数表示
- 反向传播时,需要计算
$\sigma(x)$ 及其导数相关项
前向传播:
- 输入:
x = [[0, 1], [-1, 2]],形状(2, 2) - 计算:
sigmoid(x) = [[0.5, 0.731...], [0.269..., 0.881...]] - 输出:
y = x × sigmoid(x) = [[0, 0.731...], [-0.269..., 1.762...]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 计算导数项:
sigmoid(x) × (1 + x × (1 - sigmoid(x))) - 输入梯度:
gx = gy × sigmoid(x) × (1 + x × (1 - sigmoid(x)))
二维卷积算子的前向传播执行:y = conv2d(x, W, b, stride, pad)
数学描述:
- 输入:
x形状(N, C, H, W)(N 个样本,C 个通道,高度 H,宽度 W) - 卷积核:
W形状(OC, C, KH, KW)(OC 个输出通道,C 个输入通道,卷积核高度 KH,宽度 KW) - 偏置:
b形状(OC,)(可选) - 参数:步长
stride、填充pad - 输出:
y形状(N, OC, H_out, W_out),其中:H_out = (H + 2×pad_h - KH) / stride_h + 1W_out = (W + 2×pad_w - KW) / stride_w + 1
数学表示:
卷积操作通过滑动窗口在输入特征图上提取特征
对于二维卷积 y = conv2d(x, W, b, stride, pad),其梯度为:
gx = conv2d_transpose(gy, W, stride, pad):输入梯度通过转置卷积计算gW = conv2d(x, gy, stride, pad):权重梯度通过卷积计算gb = sum(gy, axis=(0,2,3)):偏置梯度是输出梯度在空间维度上的求和
数学原理:
- 卷积的反向传播涉及转置卷积(输入梯度)和卷积(权重梯度)
- 转置卷积可以理解为卷积的逆操作
- 偏置的梯度是所有输出位置的梯度之和
前向传播:
- 输入:
x,形状(1, 1, 4, 4)(1 个样本,1 个通道,4×4 特征图) - 卷积核:
W,形状(1, 1, 3, 3)(1 个输出通道,1 个输入通道,3×3 卷积核) - 参数:
stride = (1, 1),pad = (0, 0) - 输出:
y,形状(1, 1, 2, 2)(输出特征图大小:(4-3+1) × (4-3+1) = 2×2)
反向传播:
- 输出梯度:
gy,形状(1, 1, 2, 2) - 输入梯度:
gx,形状(1, 1, 4, 4)(通过转置卷积计算) - 权重梯度:
gW,形状(1, 1, 3, 3)(通过卷积计算) - 偏置梯度:
gb,形状(1,)(输出梯度在空间维度上的求和)
最大池化算子的前向传播执行:y = max_pool2d(x, kernel_size, stride, pad)
数学描述:
- 输入:4D 张量
x,形状为(N, C, H, W) - 参数:卷积核大小
kernel_size、步长stride、填充pad - 输出:
y,形状为(N, C, H_out, W_out),其中:H_out = (H + 2×pad_h - kernel_h) / stride_h + 1W_out = (W + 2×pad_w - kernel_w) / stride_w + 1
- 操作:在每个
kernel_size × kernel_size的窗口内取最大值 - 保存索引:前向传播时保存最大值的位置索引,用于反向传播
数学表示:
对于最大池化运算 y = max_pool2d(x, kernel_size, stride, pad),其梯度为:
- 只有最大值位置传递梯度:
gx[argmax位置] = gy,其他位置梯度为 0
数学原理:
- 前向传播中,输出是输入窗口内所有元素的最大值:
out = max(x₁, x₂, ..., xₙ)
- 对于窗口内的每个元素 xᵢ,其对输出的偏导数是:
∂out/∂xᵢ = 1(如果 xᵢ 是最大值)∂out/∂xᵢ = 0(如果 xᵢ 不是最大值)
- 根据链式法则,输入梯度为:
dx = dout * (1 或 0)
- 因此,上游梯度 dout 只传递给最大值位置,其他位置的梯度为 0
- 使用前向传播时保存的索引来确定梯度传递位置
前向传播:
- 输入:
x,形状(1, 1, 4, 4)x = [[[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]]] - 参数:
kernel_size = (2, 2),stride = (2, 2),pad = (0, 0) - 输出:
y,形状(1, 1, 2, 2)y = [[[[6, 8], [14, 16]]]]y[0,0] = max(x[0:2, 0:2]) = max(1,2,5,6) = 6y[0,1] = max(x[0:2, 2:4]) = max(3,4,7,8) = 8y[1,0] = max(x[2:4, 0:2]) = max(9,10,13,14) = 14y[1,1] = max(x[2:4, 2:4]) = max(11,12,15,16) = 16
反向传播:
- 输出梯度:
gy = [[[[0.1, 0.2], [0.3, 0.4]]]],形状(1, 1, 2, 2) - 输入梯度:
gx,形状(1, 1, 4, 4)gx = [[[[0, 0, 0, 0], [0, 0.1, 0, 0.2], [0, 0, 0, 0], [0, 0.3, 0, 0.4]]]]- 只有最大值位置(6, 8, 14, 16)传递梯度
平均池化算子的前向传播执行:y = avg_pool2d(x, kernel_size, stride, pad)
数学描述:
- 输入:4D 张量
x,形状为(N, C, H, W) - 参数:卷积核大小
kernel_size、步长stride、填充pad - 输出:
y,形状为(N, C, H_out, W_out) - 操作:在每个
kernel_size × kernel_size的窗口内计算平均值
数学表示:
对于平均池化运算 y = avg_pool2d(x, kernel_size, stride, pad),其梯度为:
- 梯度平均分配:
gx[窗口内所有位置] = gy / (kernel_h × kernel_w)
数学原理:
- 前向传播中,输出是输入窗口内所有元素的平均值:
out = (x₁ + x₂ + ... + xₙ) / n- 其中
n = kernel_h × kernel_w是窗口大小
- 对于窗口内的每个元素 xᵢ,其对输出的偏导数是:
∂out/∂xᵢ = 1/n
- 根据链式法则,输入梯度为:
dx = dout * (1/n)
- 因此,上游梯度 dout 被均匀地分配到输入窗口的每个位置,每个位置获得
dout/n的梯度贡献 - 这确保了梯度在窗口内的均匀分布
前向传播:
- 输入:
x,形状(1, 1, 4, 4)x = [[[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]]] - 参数:
kernel_size = (2, 2),stride = (2, 2),pad = (0, 0) - 输出:
y,形状(1, 1, 2, 2)y = [[[[3.5, 5.5], [11.5, 13.5]]]]y[0,0] = mean(x[0:2, 0:2]) = (1+2+5+6)/4 = 3.5
反向传播:
- 输出梯度:
gy = [[[[0.1, 0.2], [0.3, 0.4]]]],形状(1, 1, 2, 2) - 输入梯度:
gx,形状(1, 1, 4, 4)gx = [[[[0.025, 0.025, 0.05, 0.05], [0.025, 0.025, 0.05, 0.05], [0.075, 0.075, 0.1, 0.1], [0.075, 0.075, 0.1, 0.1]]]]- 每个窗口内的梯度平均分配:
gy[0,0] = 0.1分配到 4 个位置,每个位置0.1/4 = 0.025
- 每个窗口内的梯度平均分配:
自适应平均池化算子的前向传播执行:y = adaptive_avg_pool2d(x, output_size)
数学描述:
- 输入:4D 张量
x,形状为(N, C, H, W) - 参数:目标输出大小
output_size = (H_out, W_out) - 输出:
y,形状为(N, C, H_out, W_out) - 特点:无论输入大小如何,输出总是固定大小
- 计算:自适应地计算每个输出位置对应的输入窗口大小,然后进行平均池化
数学表示:
-
窗口大小:
$$ \mathrm{kernel}h = \left\lceil \frac{H}{H{\text{out}}} \right\rceil, \quad \mathrm{kernel}w = \left\lceil \frac{W}{W{\text{out}}} \right\rceil $$
-
对每个输出位置进行平均池化:
$$ y_{i, c, h, w} = \operatorname{mean}\bigl( x_{i, c,, h \cdot \mathrm{kernel}_h : (h+1) \cdot \mathrm{kernel}_h,, w \cdot \mathrm{kernel}_w : (w+1) \cdot \mathrm{kernel}_w} \bigr) $$
对于自适应平均池化 y = adaptive_avg_pool2d(x, output_size),其梯度为:
- 梯度平均分配:
gx[窗口内所有位置] = gy / (kernel_h × kernel_w)
数学原理:
- 自适应平均池化的反向传播原理与普通平均池化相同,区别在于窗口大小是自适应的
- 前向传播中,输出是输入窗口内所有元素的平均值:
out = (x₁ + x₂ + ... + xₙ) / n- 其中窗口大小
n = kernel_h × kernel_w根据输入和输出大小自适应计算
- 对于窗口内的每个元素 xᵢ,其对输出的偏导数是:
∂out/∂xᵢ = 1/n
- 根据链式法则,输入梯度为:
dx = dout * (1/n)
- 因此,上游梯度 dout 被均匀地分配到输入窗口的每个位置,每个位置获得
dout/n的梯度贡献
前向传播:
- 输入:
x,形状(1, 1, 5, 5)(高度 5,宽度 5) - 输出大小:
output_size = (2, 2) - 窗口大小:
kernel_h = ceil(5/2) = 3,kernel_w = ceil(5/2) = 3 - 输出:
y,形状(1, 1, 2, 2)y[0,0] = mean(x[0:3, 0:3])y[0,1] = mean(x[0:3, 2:5])y[1,0] = mean(x[2:5, 0:3])y[1,1] = mean(x[2:5, 2:5])
反向传播:
- 输出梯度:
gy,形状(1, 1, 2, 2) - 输入梯度:
gx,形状(1, 1, 5, 5)- 每个窗口内的梯度平均分配:
gy[0,0]分配到x[0:3, 0:3]的 9 个位置,每个位置gy[0,0] / 9
- 每个窗口内的梯度平均分配:
拼接算子将多个张量沿着指定维度拼接在一起。
数学描述:
- 输入:
n个张量[x0, x1, ..., xn],每个张量的形状为(d0, d1, ..., dk, ..., dm) - 拼接维度:
dim = k,指定在第k维上进行拼接 - 形状要求:除了第
k维外,所有输入张量的其他维度必须相同 - 输出形状:
(d0, d1, ..., sum(dk_i), ..., dm),其中sum(dk_i)是所有输入在第k维大小的总和
数学表示:
输出
拼接的反向传播是分割:将输出梯度按照原始输入的形状分割回各个输入。
数学原理:
- 前向操作:
y = cat([x0, x1, ..., xn], dim=k) - 反向操作:
[gx0, gx1, ..., gxn] = split(gy, [shape0, shape1, ..., shapen], dim=k) - 核心思想:输出梯度
gy在第k维上被分割成n段,每段对应一个输入的梯度 - 分割位置:第
i个输入的梯度gxi对应gy在第k维上的区间[sum(dk_0...dk_{i-1}), sum(dk_0...dk_i))
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2)x1 = [[5, 6], [7, 8]],形状(2, 2)x2 = [[9, 10], [11, 12]],形状(2, 2)
- 拼接维度:
dim = 0 - 输出:
y = cat([x0, x1, x2], dim=0)形状y = [[1, 2 ], [3, 4 ], [5, 6 ], [7, 8 ], [9, 10], [11, 12]](6, 2),其中第 0 维6 = 2 + 2 + 2
反向传播:
- 输出梯度:
gy,形状(6, 2) - 分割形状:
[(2, 2), (2, 2), (2, 2)] - 输入梯度:
gx0 = gy[0:2, :],形状(2, 2)gx1 = gy[2:4, :],形状(2, 2)gx2 = gy[4:6, :],形状(2, 2)
前向传播:
- 输入:
x0 = [[1, 2], [3, 4]],形状(2, 2)x1 = [[5, 6], [7, 8]],形状(2, 2)
- 拼接维度:
dim = 1 - 输出:
y = cat([x0, x1], dim=1)形状y = [[1, 2, 5, 6], [3, 4, 7, 8]](2, 4),其中第 1 维4 = 2 + 2
反向传播:
- 输出梯度:
gy = [[0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8]],形状(2, 4) - 分割形状:
[(2, 2), (2, 2)] - 输入梯度:
gx0 = gy[:, 0:2] = [[0.1, 0.2], [0.5, 0.6]],形状(2, 2)gx1 = gy[:, 2:4] = [[0.3, 0.4], [0.7, 0.8]],形状(2, 2)
分割算子将一个张量沿着指定维度分割成多个张量。
数学描述:
- 输入:单个张量
x,形状为(d0, d1, ..., dk, ..., dm) - 分割维度:
dim = k,指定在第k维上进行分割 - 分割方式:
- 固定大小分割:按固定大小
s分割,第k维被分成⌈dk/s⌉段,最后一段可能小于s - 大小列表分割:按指定大小列表
[s0, s1, ..., sn]分割,要求sum(si) = dk
- 固定大小分割:按固定大小
- 输出:
n个张量[y0, y1, ..., yn],每个yi的形状为(d0, d1, ..., si, ..., dm)
数学表示:
每个输出
分割的反向传播是拼接:将所有分割后的梯度拼接回原始形状。
数学原理:
- 前向操作:
[y0, y1, ..., yn] = split(x, sizes, dim=k) - 反向操作:
gx = cat([gy0, gy1, ..., gyn], dim=k) - 核心思想:分割是拼接的逆操作,因此反向传播就是将分割后的梯度重新拼接
- 梯度连续性:由于前向传播时数据在分割维度上是连续切分的,反向传播时梯度也在同一维度上连续拼接
前向传播:
- 输入:
x,形状(10, 3) - 分割参数:
split_size = 3,dim = 0 - 计算:
dim_size = 10num_splits = (10 + 3 - 1) / 3 = 4actual_split_sizes = [3, 3, 3, 1]
- 输出:
y0 = x[0:3, :],形状(3, 3)y1 = x[3:6, :],形状(3, 3)y2 = x[6:9, :],形状(3, 3)y3 = x[9:10, :],形状(1, 3)
反向传播:
- 输出梯度:
gy0,形状(3, 3)gy1,形状(3, 3)gy2,形状(3, 3)gy3,形状(1, 3)
- 输入梯度:
gx = cat([gy0, gy1, gy2, gy3], dim=0),形状(10, 3)
前向传播:
- 输入:
x = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]],形状(2, 5) - 分割参数:
split_sizes = [2, 3],dim = 1 - 输出:
y0 = x[:, 0:2] = [[1, 2], [6, 7]],形状(2, 2)y1 = x[:, 2:5] = [[3, 4, 5], [8, 9, 10]],形状(2, 3)
反向传播:
- 输出梯度:
gy0 = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)gy1 = [[0.5, 0.6, 0.7], [0.8, 0.9, 1.0]],形状(2, 3)
- 输入梯度:
gx = cat([gy0, gy1], dim=1)形状gx = [[0.1, 0.2, 0.5, 0.6, 0.7], [0.3, 0.4, 0.8, 0.9, 1.0]](2, 5)
重塑算子的前向传播执行:$y = \operatorname{reshape}(x, \mathrm{shape})$
数学描述:
- 输入:张量
$x$ 和目标形状$\mathrm{shape}$ - 输出:将
$x$ 重塑为目标形状$\mathrm{shape}$ - 要求:
x的元素总数必须等于shape的元素总数 - 重塑操作不改变数据,只改变张量的维度排列
数学表示:
输出
对于重塑运算 y = reshape(x, shape),根据链式法则:
- 反向传播也是重塑:
gx = reshape(gy, x.shape)
数学原理:
- 重塑是形状变换,不涉及数值计算
- 反向传播时,梯度被重塑回原始形状
- 这是恒等变换在形状层面的体现
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 目标形状:
(4,) - 输出:
y = reshape(x, (4,)) = [1, 2, 3, 4],形状(4,)
反向传播:
- 输出梯度:
gy = [0.1, 0.2, 0.3, 0.4],形状(4,) - 输入梯度:
gx = reshape(gy, (2, 2)) = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)
转置算子的前向传播执行:$y = \operatorname{transpose}(x)$
数学描述:
- 输入:张量
$x$ - 输出:将
$x$ 转置,交换最后两个维度 - 对于 2D 矩阵:$y_{i j} = x_{j i}$
- 对于更高维张量:只交换最后两个维度
数学表示:
输出
对于转置运算 y = transpose(x),根据链式法则:
- 反向传播也是转置:
gx = transpose(gy)
数学原理:
- 转置是形状变换,不涉及数值计算
- 反向传播时,梯度也被转置
- 转置的转置等于原矩阵
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 输出:
y = transpose(x) = [[1, 3], [2, 4]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = transpose(gy) = [[0.1, 0.3], [0.2, 0.4]],形状(2, 2)
展平算子的前向传播执行:$y = \operatorname{flatten}(x, \mathrm{start_dim}, \mathrm{end_dim})$
数学描述:
- 输入:张量
$x$ 和维度范围$[\mathrm{start_dim}, \mathrm{end_dim}]$ - 输出:将
x在指定维度范围内展平 - 展平操作将多个连续维度合并为一个维度
- 例如:
(2, 3, 4)在[1, 2]展平后变为(2, 12)
数学表示:
输出
对于展平运算 y = flatten(x, start_dim, end_dim),根据链式法则:
- 反向传播是重塑:
gx = reshape(gy, x.shape)
数学原理:
- 展平是重塑的特例,将多个维度合并为一个
- 反向传播时,梯度被重塑回原始形状
- 这确保了梯度形状与输入形状匹配
前向传播:
- 输入:
x,形状(2, 3, 4) - 展平范围:
start_dim = 1,end_dim = 2 - 输出:
y = flatten(x, 1, 2),形状(2, 12)
反向传播:
- 输出梯度:
gy,形状(2, 12) - 输入梯度:
gx = reshape(gy, (2, 3, 4)),形状(2, 3, 4)
Dropout 算子的前向传播执行:y = dropout(x, p, training)
数学描述:
- 输入:张量
x、丢弃概率p和训练模式标志training - 训练模式:以概率
p将元素置为 0,其余元素除以(1 - p)进行缩放 - 推理模式:直接返回输入
x,不做任何变换 - 目的:防止过拟合,通过随机丢弃部分神经元来增强模型泛化能力
数学表示:
-
训练模式:
$$ y_i = \begin{cases} \dfrac{x_i}{1 - p}, & \text{如果 } \mathrm{mask}_i = 1 \ 0, & \text{如果 } \mathrm{mask}_i = 0 \end{cases} $$
其中
$\mathrm{mask}_i$ 是随机生成的掩码。 -
推理模式:
$$ y = x $$
对于 Dropout 运算 y = dropout(x, p, training),其梯度为:
- 训练模式:
gx = dropout_backward(gy, mask),根据前向传播时的掩码传递梯度 - 推理模式:
gx = gy
数学原理:
- 训练模式下,只有未被丢弃的位置才传递梯度
- 梯度需要与前向传播时的缩放因子保持一致
- 推理模式下,梯度直接传递
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 丢弃概率:
p = 0.5 - 缩放因子:
scale = 1/(1-p) = 1/(1-0.5) = 2 - 假设掩码:
mask = [[2, 0], [2, 2]](随机生成,保存缩放因子scale或0)- 位置
(0,0)和(1,0)、(1,1)未被丢弃,mask保存scale = 2 - 位置
(0,1)被丢弃,mask保存0
- 位置
- 输出:
y[i] = x[i] * mask[i]y = [[1*2, 2*0], [3*2, 4*2]] = [[2, 0], [6, 8]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx[i] = gy[i] * mask[i]gx = [[0.1*2, 0.2*0], [0.3*2, 0.4*2]] = [[0.2, 0], [0.6, 0.8]],形状(2, 2)
关键说明:
mask既用于决定哪些位置传递梯度(mask = 0的位置梯度为 0),也用于保持缩放因子(mask = scale的位置梯度乘以scale)- 前向传播时:
y[i] = x[i] * mask[i],其中mask[i] = scale(未被丢弃)或mask[i] = 0(被丢弃) - 反向传播时:
gx[i] = gy[i] * mask[i],梯度传递与缩放因子保持一致
当前实现约束: 仅支持 4D 输入,形状为 (N, C, H, W);不支持 3D/5D。mode 接口取值 "nearest" 或 "bilinear",但当前实现未使用,实际均为最近邻插值。
上采样算子将输入张量的空间维度(高度和宽度)进行放大。
数学描述:
- 输入:4D 张量
x,形状为(N, C, H, W),其中N是批次大小,C是通道数,H是高度,W是宽度 - 上采样方式:可以通过指定目标大小
(H_out, W_out)或缩放因子(scale_h, scale_w)来放大 - 输出形状:
(N, C, H_out, W_out),其中H_out = H × scale_h,W_out = W × scale_w - 插值方法:通常使用最近邻插值或双线性插值
数学表示:
或
输出
上采样的反向传播是下采样:将输出梯度按照缩放因子压缩回原始形状。
数学原理:
- 前向操作:
y = upsample(x, scale_factor=(scale_h, scale_w)) - 反向操作:
gx = downsample(gy, scale_factor=(scale_h, scale_w)) - 核心思想:上采样时,一个输入像素对应多个输出像素;反向传播时,需要将多个输出梯度累加回对应的输入位置
- 梯度累加:对于输入位置
(i, j),其梯度等于所有对应输出位置的梯度之和
数学表示:
-
如果
$y[h_{\text{out}}, w_{\text{out}}]$ 对应$x[h, w]$ ,其中 $$ h = \frac{h_{\text{out}}}{\mathrm{scale}h}, \quad w = \frac{w{\text{out}}}{\mathrm{scale}_w}, $$ 则$$ g_x[h, w] = \sum_{\substack{h_{\text{out}}, w_{\text{out}} \ h_{\text{out}} / \mathrm{scale}h = h \ w{\text{out}} / \mathrm{scale}w = w}} g_y[h{\text{out}}, w_{\text{out}}] $$
前向传播:
- 输入:
x,形状(1, 1, 2, 2)(批次大小 1,通道数 1,高度 2,宽度 2)x = [[[[1, 2], [3, 4]]]] - 缩放因子:
scale_h = 2,scale_w = 2 - 输出:
y,形状(1, 1, 4, 4)(使用最近邻插值,每个输入像素复制到对应的 2×2 区域)y = [[[[1, 1, 2, 2], [1, 1, 2, 2], [3, 3, 4, 4], [3, 3, 4, 4]]]]
反向传播:
- 输出梯度:
gy,形状(1, 1, 4, 4)gy = [[[[0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8], [0.9, 1.0, 1.1, 1.2], [1.3, 1.4, 1.5, 1.6]]]] - 输入梯度:
gx,形状(1, 1, 2, 2)gx[0, 0]对应gy[0:2, 0:2]的所有元素,所以gx[0, 0] = 0.1 + 0.2 + 0.5 + 0.6 = 1.4gx[0, 1]对应gy[0:2, 2:4]的所有元素,所以gx[0, 1] = 0.3 + 0.4 + 0.7 + 0.8 = 2.2gx[1, 0]对应gy[2:4, 0:2]的所有元素,所以gx[1, 0] = 0.9 + 1.0 + 1.3 + 1.4 = 4.6gx[1, 1]对应gy[2:4, 2:4]的所有元素,所以gx[1, 1] = 1.1 + 1.2 + 1.5 + 1.6 = 5.4
- 最终梯度:
gx = [[[[1.4, 2.2], [4.6, 5.4]]]]
数学解释:
- 上采样时,
x[0, 0] = 1被复制到y[0:2, 0:2]的 4 个位置 - 反向传播时,这 4 个位置的梯度需要累加回
gx[0, 0] - 类似地,其他输入位置的梯度也是对应输出区域梯度的累加
恒等算子的前向传播执行:$y = \operatorname{identity}(x) = x$
数学描述:
-
输入:张量
$x$ -
输出:直接返回输入
$x$ ,不做任何变换 -
数学表达式:
$$ y = \operatorname{identity}(x) = x $$
-
恒等算子主要用于控制计算图结构,不改变数据
对于恒等运算
因此,如果输出梯度为
数学原理:
- 恒等函数的导数是
$1$ - 反向传播时,梯度直接传递,不做任何变换
- 这是最简单的算子之一
前向传播:
- 输入:
x = [[1, 2], [3, 4]],形状(2, 2) - 输出:
y = identity(x) = [[1, 2], [3, 4]],形状(2, 2)
反向传播:
- 输出梯度:
gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2) - 输入梯度:
gx = gy = [[0.1, 0.2], [0.3, 0.4]],形状(2, 2)
接口与 PyTorch 对齐说明:
- Functional 接口:OriginDL 使用
batch_norm(...),与 PyTorch 的torch.nn.functional.batch_norm命名一致;通过参数区分输入维度(见下)。 - NN 层接口:与 PyTorch 一致,提供
BatchNorm1d、BatchNorm2d、BatchNorm3d三种层;当前实现仅支持 BatchNorm1d 和 BatchNorm2d,BatchNorm3d 尚未实现(见「当前支持范围」)。
当前支持范围:
- BatchNorm1d:已实现。输入形状
(N, C),对应 functional 的num_dims=2。 - BatchNorm2d:已实现。输入形状
(N, C, H, W),对应 functional 的num_dims=4。 - BatchNorm3d:未实现。输入形状应为
(N, C, D, H, W),对应 functional 的num_dims=5;当前 CPU/CUDA 后端仅处理num_dims=2与num_dims=4,传入num_dims=5会报错。
批归一化算子的前向传播执行:y = batch_norm(x, gamma, beta, running_mean, running_var, training)
数学描述:
- 输入:
x形状(N, C, H, W)或(N, C)(N 个样本,C 个通道);当前实现仅支持上述两种形状(即 BatchNorm2d 与 BatchNorm1d) - 参数:缩放参数
gamma、平移参数beta、运行均值running_mean、运行方差running_var - 训练模式:
- 计算当前 batch 的均值和方差:
mean = mean(x, axis=0),var = var(x, axis=0) - 归一化:
x_norm = (x - mean) / sqrt(var + ε)。其中 ε 为小正数(如 1e-5),加在方差上可避免分母为零或过小,保证数值稳定。 - 缩放和平移:
y = gamma × x_norm + beta - 更新运行统计:
running_mean = momentum × running_mean + (1-momentum) × mean
- 计算当前 batch 的均值和方差:
- 推理模式:使用
running_mean和running_var进行归一化
数学表示:
-
训练模式:
$$ y = \gamma \cdot \frac{x - \mathrm{mean}}{\sqrt{\mathrm{var} + \varepsilon}} + \beta $$
-
推理模式:
$$ y = \gamma \cdot \frac{x - \mathrm{running_mean}}{\sqrt{\mathrm{running_var} + \varepsilon}} + \beta $$
对于批归一化 y = batch_norm(x, gamma, beta, running_mean, running_var, training),其梯度为:
gx:输入梯度,通过归一化操作的链式法则计算dgamma = sum(gy × x_norm, axis=0):缩放参数梯度dbeta = sum(gy, axis=0):平移参数梯度
数学原理:
- 批归一化的梯度计算需要考虑归一化操作对均值和方差的依赖
- 输入梯度需要考虑 batch 内所有样本的统计信息
- 缩放和平移参数的梯度直接计算
前向传播:
- 输入:
x,形状(2, 3)(2 个样本,3 个特征)x = [[1, 2, 3], [4, 5, 6]] - 计算均值:
mean = [2.5, 3.5, 4.5](沿第 0 维求平均) - 计算方差:
var = [2.25, 2.25, 2.25] - 归一化:
x_norm = (x - mean) / sqrt(var + ε) - 缩放和平移:
y = gamma × x_norm + beta
反向传播:
- 输出梯度:
gy,形状(2, 3) - 输入梯度:
gx,形状(2, 3)(考虑归一化操作的梯度) - 缩放参数梯度:
dgamma = sum(gy × x_norm, axis=0),形状(3,) - 平移参数梯度:
dbeta = sum(gy, axis=0),形状(3,)
Softmax 交叉熵损失算子的前向传播执行:loss = softmax_cross_entropy(x, target)
直观理解(为什么需要这个损失):
- 场景:分类问题。例如 N 张图片,每张图属于 C 个类别之一(如猫/狗/鸟)。网络对每张图输出 C 个“得分”
x(logits),我们想知道:预测与真实标签差多少。 - 两步合一:先把得分变成“概率”
p(softmax),再用量化“预测概率与真实标签的差距”的交叉熵作为损失。概率越集中在正确类别上,损失越小;越分散或给错误类别高概率,损失越大。
数学描述:
-
输入:
x形状(N, C)(N 个样本,C 个类别),target形状(N,)(每个样本的真实类别编号,取值 0 到 C-1) -
计算步骤:
- 计算 softmax:
p = softmax(x, axis=-1),形状(N, C) - 提取目标类别的概率:
p_selected = p[i, target[i]],形状(N,) - 计算交叉熵:
loss = -mean(log(p_selected + ε))
- 计算 softmax:
-
数学表达式:
$$ \mathrm{loss} = -\frac{1}{N} \sum_{i=1}^N \log\bigl(p_{i,,\mathrm{target}_i} + \varepsilon\bigr) $$
各步含义(便于普通人理解):
-
Softmax:从“得分”到“概率”
- 输入
x[i, c]是第 i 个样本在第 c 类上的得分(logit),可正可负、可大于 1。 - Softmax 公式:
p[i, c] = exp(x[i, c]) / Σ(k) exp(x[i, k])。对每个样本 i,把 C 个得分用指数放大后归一化,得到 C 个非负且和为 1 的数,即“该类别的概率”。 - 直观:得分越高,对应概率越大;得分相对低时,概率被压小。
- 输入
-
提取目标概率
p_selected- 对样本 i,真实类别是
target[i],我们只关心“模型给正确类别多少概率”,即p[i, target[i]]。 p_selected[i] = p[i, target[i]],形状(N,),表示每个样本上正确类别的预测概率。
- 对样本 i,真实类别是
-
交叉熵:用
-log(概率)衡量“错多少”- 单样本:若正确类别概率为
p_selected[i],定义该样本的损失为-log(p_selected[i] + ε)。 - 直观:概率越接近 1,
log(p)越接近 0,-log(p)越小(预测越对);概率越小,-log(p)越大(预测越错)。ε 是为了数值稳定,避免 log(0)。 - 多样本:对所有样本的
-log(p_selected + ε)取平均,得到loss。
- 单样本:若正确类别概率为
数学表示(小结):
将 logits 转换为概率分布(每行和为 1)。
交叉熵损失衡量预测分布与“真实类别”的一致性,越小表示预测越准。
对于 Softmax 交叉熵损失 loss = softmax_cross_entropy(x, target),其梯度为:
gx = (softmax(x) - one_hot(target)) / Ngtarget = 0(标签不需要梯度)
数学原理:
- Softmax 交叉熵的梯度等于预测概率减去真实标签的 one-hot 编码
- 梯度已经包含了 softmax 的梯度,因此计算非常高效
- 这是 softmax 和交叉熵组合的数学性质
前向传播:
- 输入:
x = [[1, 2, 3], [2, 1, 3]],形状(2, 3)(2 个样本,3 个类别) - 标签:
target = [2, 0],形状(2,)(第一个样本类别 2,第二个样本类别 0) - 计算 softmax:
p = softmax(x) = [[0.090..., 0.245..., 0.665...], [0.245..., 0.090..., 0.665...]]
- 提取目标概率:
p_selected = [0.665..., 0.245...]
- 计算损失:
loss = -mean(log(p_selected)) = -mean([-0.407..., -1.407...]) = 0.907...
反向传播:
- 输出梯度:
gy = 1.0(标量) - 计算 one-hot 编码:
one_hot(target) = [[0, 0, 1], [1, 0, 0]]
- 输入梯度:
gx = (p - one_hot(target)) / N = ([[0.090..., 0.245..., 0.665...], [0.245..., 0.090..., 0.665...]] - [[0, 0, 1], [1, 0, 0]]) / 2gx = [[0.045..., 0.123..., -0.167...], [-0.377..., 0.045..., 0.333...]]
| 分类 | 算子 | 特点 |
|---|---|---|
| 数学运算 | Add, Sub, Mul, Div, MatMul, Pow, Exp, Log, Neg, Square, Sum, BroadcastTo, SumTo, Compare | 基础数学运算,支持广播 |
| 激活函数 | ReLU, Sigmoid, Softmax, SiLU | 非线性变换,引入非线性特性 |
| 卷积运算 | Conv2d | 特征提取,参数共享 |
| 池化运算 | MaxPool2d, AvgPool2d, AdaptiveAvgPool2d | 降维,减少计算量 |
| 形状变换 | Cat, Split, Reshape, Transpose, Flatten | 改变张量形状和维度 |
| 神经网络层 | Dropout, Upsample, Identity | 网络层操作 |
| 归一化 | BatchNorm(BatchNorm1d/2d 已实现,BatchNorm3d 未实现) | 加速训练,稳定梯度 |
| 损失函数 | SoftmaxCrossEntropy | 计算损失值 |
前面的每个算子小节已经分别给出了详细的数学公式和梯度推导,这里不再重复展开,只做整体性的归纳:
- 前向:所有算子都可以看成是对张量元素或张量块应用某种标量运算、线性变换、卷积/池化、归一化或形状重排,并严格遵守形状和广播规则。
- 反向:统一遵循链式法则,梯度始终可以写成“输出梯度
gy× 局部导数”的形式;局部导数的具体表达式已经在对应算子的数学原理小节中给出。
| 算子对 | 前向操作 | 反向操作 | 关系 |
|---|---|---|---|
| Cat / Split | y = cat([x0, x1, ...], dim) |
[gx0, gx1, ...] = split(gy, shapes, dim) |
互为反向操作 |
| Split / Cat | [y0, y1, ...] = split(x, sizes, dim) |
gx = cat([gy0, gy1, ...], dim) |
互为反向操作 |
| Upsample / Downsample | y = upsample(x, scale) |
gx = downsample(gy, scale) |
互为反向操作 |
| BroadcastTo / SumTo | y = broadcast_to(x, shape) |
gx = sum_to(gy, x.shape) |
互为反向操作 |
| SumTo / BroadcastTo | y = sum_to(x, shape) |
gx = broadcast_to(gy, x.shape) |
互为反向操作 |
对偶性说明:
- Cat 和 Split 互为反向操作,体现了算子的对称性
- 这种对偶性使得反向传播的实现更加直观和高效
- 理解对偶关系有助于快速掌握算子的前向和反向传播原理
src/operators/math/:数学运算算子实现(Add、Sub、Mul、Div、MatMul、Pow、Exp、Log、Neg、Square、Sum、BroadcastTo、SumTo、Compare 等)src/operators/activation/:激活函数算子实现(ReLU、Sigmoid、Softmax、SiLU 等)src/operators/conv/:卷积算子实现(Conv2d 等)src/operators/pooling/:池化算子实现(MaxPool2d、AvgPool2d、AdaptiveAvgPool2d 等)src/operators/shape/:形状变换算子实现(Cat、Split、Reshape、Transpose、Flatten 等)src/operators/nn/:神经网络层相关算子实现(Dropout、Upsample、Identity 等)src/operators/normalization/:归一化算子实现(BatchNorm 等)src/operators/loss/:损失函数算子实现(SoftmaxCrossEntropy 等)