news 2026/10/31 15:18:43

NumPy手推反向传播:从矩阵求导到梯度形状的深度理解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NumPy手推反向传播:从矩阵求导到梯度形状的深度理解

简介:这是一本面向深度学习实践者与自学工程师的数学基础指南,专为弥补神经网络理论理解中的数学短板而设计。全书系统覆盖线性代数、概率统计、微积分及优化算法四大核心模块,并紧密关联CNN、RNN、LSTM等主流模型的数学原理,辅以Python代码示例(依托NumPy、TensorFlow等库),实现从公式推导到可运行实现的贯通。资源为单文件PDF格式,共1个7.71MB高清电子书,排版规范、章节清晰,含作者亲撰前言、完整参考文献与技术审校信息,便于逐章精读与公式速查。目前已有84人下载学习,适合数学基础薄弱但希望扎实掌握深度学习底层逻辑的入门者,以及需快速回顾关键数学工具的算法工程师。

1. 深度学习不是“调参玄学”:这本书把反向传播的梯度怎么算、为什么必须是矩阵求导、为什么ReLU的导数在0处定义为0——全拆进NumPy可复现的代码里

你有没有过这种时刻:调完一个模型,loss曲线平滑下降,准确率稳步上升,但当别人问“这个梯度到底是怎么从输出层一层层传回第一层权重的”,你张嘴想说链式法则,却卡在“那∂L/∂W₁到底长什么样?它是个向量?矩阵?还是张量?形状怎么对得上?”——不是不会写model.train(),而是连autograd在背后替你算的那坨东西,都讲不清维度来源和数学结构。这不是能力问题,是知识断层:我们学了微积分,但没练过标量对矩阵求导;学了线性代数,但没亲手用np.einsum推过卷积核更新的梯度形状;知道交叉熵,却没验证过它的梯度为什么恰好是y_pred - y_true。《Math for Deep Learning》不是另一本“数学复习大纲”,它是一份可执行的数学逆向工程手册:每章结尾的Python示例(全部基于NumPy,零框架依赖)强制你把抽象符号落地成.shape可查、.dtype可验、.allclose()可断言的数组。它解决的不是“要不要学数学”,而是“学了之后,怎么让数学真正长进你的调试直觉里”。当你能徒手写出np.dot(dL_dy, W2.T) * (z1 > 0)并说出每个括号的数学含义时,你就不再需要靠“多加一层试试”来碰运气了。


2. 线性代数:不是背公式,而是理解“数据流”如何被张量运算编码

深度学习中的“数据流”,本质是张量在计算图中按特定规则变形、组合、压缩的过程。而线性代数,就是描述这种变形的语言。Kneusel没有从行列式或特征值开始炫技,而是直击神经网络最原始的单元:全连接层的前向传播y = Wx + b。这行代码背后,藏着三个必须厘清的底层事实:W的形状为何是(output_dim, input_dim)而非反过来?x必须是列向量吗?b的广播机制如何与梯度反传耦合?本书用 NumPy 的@运算符和reshape操作,把教科书里的“矩阵乘法”还原成内存中真实的字节搬运。

2.1 全连接层的前向:@运算符背后的内存对齐逻辑

在 NumPy 中,W @ x要求W.shape[1] == x.shape[0]。这并非语法限制,而是线性映射的数学必然:W的每一行代表输出空间的一个基向量在输入空间的坐标,x是输入向量在标准基下的坐标,W @ x的结果是该向量在输出空间新基下的坐标。Kneusel 在第5章给出的示例代码,刻意使用np.random.randn(128, 784)作为W(对应 MNIST 的 128 个隐藏单元,784 维输入),x则是(784,)的一维数组。关键在于他紧接着做x.reshape(-1, 1)将其转为(784, 1)的列向量,再执行W @ x。为什么?因为@运算符要求右操作数是二维的(即使只有一列),这是 NumPy 对“矩阵乘法”的严格定义——它强制你面对一个事实:神经网络的输入,在数学上永远是一个列向量,而非行向量。若你错误地用x.reshape(1, -1),W @ x会直接报错ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0。这个报错不是 bug,是数学在提醒你:W的列数必须匹配x的行数,这是线性变换定义域与值域的刚性约束。

import numpy as np # 模拟MNIST输入:784维像素 x_flat = np.random.randn(784) # shape: (784,) W = np.random.randn(128, 784) # shape: (128, 784) # ✅ 正确:将x视为列向量 (784, 1) x_col = x_flat.reshape(-1, 1) # shape: (784, 1) y = W @ x_col # shape: (128, 1) —— 输出是128维列向量 # ❌ 错误:x被视为行向量 (1, 784),W @ x_col 会失败 # x_row = x_flat.reshape(1, -1) # shape: (1, 784) # y_wrong = W @ x_row # ValueError!

这段代码后,Kneusel 立即指出:y.shape是(128, 1),这正是下一层W2的输入维度。形状的传递,就是数据流的拓扑结构。如果你跳过reshape,用np.dot(W, x_flat),虽然也能得到(128,)的结果,但np.dot对一维数组有特殊规则(内积),它掩盖了x作为列向量的本质,导致你在推导反向传播时极易混淆维度。@运算符的“不妥协”,恰恰是本书训练你数学直觉的第一课:用最严格的工具,逼你写出最精确的数学表达。

2.2 梯度反传:dL/dW的形状为何是(128, 784)?——从链式法则到 einsum 的显式展开

前向传播的y = W @ x只是开始。真正的挑战在反向:损失L对权重W的梯度dL/dW。根据链式法则,dL/dW = (dL/dy) @ (dy/dW)。dL/dy是(128, 1)的列向量(假设L是标量),dy/dW是什么?它是一个四维张量:对W[i, j]求导,结果是x[j](当i固定时),所以dy/dW的形状是(128, 784, 128, 784)——这显然不可行。Kneusel 在第8章(Matrix Calculus)中给出的解法,是绕过张量,直接利用“标量对矩阵求导”的标准恒等式:若y = W @ x,则∂L/∂W = (∂L/∂y) @ x.T。这个公式为何成立?书中用einsum做了彻底展开:

# 假设 dL_dy 是 (128, 1) 的列向量 dL_dy = np.random.randn(128, 1) # 手动计算 dL_dW: 对每个 W[i, j], dL_dW[i, j] = sum_k (dL_dy[k] * dy[k]/dW[i, j]) # 而 dy[k]/dW[i, j] = x[j] if k==i else 0, 所以 dL_dW[i, j] = dL_dy[i] * x[j] # 这正是外积: dL_dy (128,1) 和 x.T (1,784) 的 outer product dL_dW_manual = np.einsum('ik,jl->ijkl', dL_dy, np.ones((1, 1))) # 复杂化演示 # 更简洁:dL_dW = dL_dy @ x.T # shape: (128, 1) @ (1, 784) = (128, 784) dL_dW = dL_dy @ x_col.T # ✅ 形状完美匹配 W assert dL_dW.shape == W.shape # (128, 784) == (128, 784)

dL_dy @ x_col.T这一行,是本书最硬核的“顿悟点”。它告诉你:dL/dW的形状不是凭空约定的,而是由dL/dy和x的形状通过矩阵乘法规则唯一确定的。x_col.T是(1, 784),dL_dy是(128, 1),它们的乘积自然落在(128, 784)这个“权重空间”里。这个推导过程,比任何“梯度下降更新公式”都更能让你理解:为什么优化器的step()方法,最终修改的是W -= lr * dL_dW,而不是其他任何形状的数组。它把“更新权重”这个动作,牢牢锚定在了线性代数的坐标系里。

2.3 卷积层的张量视角:conv2d不是黑匣子,是im2col+gemm的组合

进入第9章“Data Flow in Neural Networks”,Kneusel 将线性代数的威力推向高潮:解释卷积。他没有直接祭出torch.nn.Conv2d,而是先展示一个3x3卷积核在4x4输入上的手工计算,然后引出im2col技术——将局部感受野“拉直”成列向量。这才是理解 CNN 数据流的关键:卷积运算,本质上是将输入图像的空间结构,通过im2col映射到一个巨大的矩阵,再用一次W @ X_col完成所有位置的加权求和。书中给出的 NumPy 实现,清晰展示了这一映射:

def im2col(x, kernel_size, stride=1): """x: (C, H, W), return: (C*kH*kW, out_H*out_W)""" C, H, W = x.shape kH, kW = kernel_size out_H = (H - kH) // stride + 1 out_W = (W - kW) // stride + 1 # 预分配输出矩阵 cols = np.zeros((C * kH * kW, out_H * out_W)) # 遍历每个输出位置 for i in range(out_H): for j in range(out_W): # 提取感受野 patch = x[:, i*stride:i*stride+kH, j*stride:j*stride+kW] # (C, kH, kW) # 拉直成列向量 cols[:, i * out_W + j] = patch.reshape(-1) # (C*kH*kW,) return cols # 示例:输入 (1, 4, 4), 卷积核 (1, 3, 3) x = np.random.randn(1, 4, 4) W_conv = np.random.randn(1, 1, 3, 3) # (out_C, in_C, kH, kW) W_flat = W_conv.reshape(1, -1) # (1, 9) # im2col 后,X_col.shape = (9, 4) [因为 out_H=out_W=2] X_col = im2col(x, (3, 3)) # (9, 4) # 卷积结果 = W_flat @ X_col -> (1, 4), 再 reshape 为 (1, 2, 2) out_flat = W_flat @ X_col # (1, 4) out = out_flat.reshape(1, 2, 2) # (out_C, out_H, out_W)

这段代码揭示了 CNN 的“灵魂”:它把空间局部性(卷积)和全局线性组合(矩阵乘)这两个看似矛盾的操作,通过im2col这个桥梁,统一到了线性代数的框架下。W_flat @ X_col这一操作,与前面全连接层的W @ x完全同构。唯一的区别是X_col的构造方式。这意味着,你之前推导的dL/dW_flat = dL/dout_flat @ X_col.T,可以原封不动地用于卷积层的梯度计算。im2col不是技巧,而是将高维张量运算降维到经典线性代数问题的数学接口。当你看懂这一点,nn.Conv2d就不再是魔法,而是一个精心设计的im2col+matmul封装。

2.4 避坑:线性代数实战中,那些让模型静默崩溃的维度陷阱

提示:以下踩坑均来自真实项目,且在 Kneusel 的代码示例中均有对应修复方案。

  • 现象:模型训练时 loss 不下降,梯度norm接近零,但W的grad却显示为None。
    原因:在自定义层中,错误地使用了np.array创建权重,而非np.random.randn初始化,并且未设置requires_grad=True(在纯 NumPy 中,这表现为W是普通 ndarray,其.grad属性根本不存在)。更隐蔽的是,x输入时用了x = x.astype(np.float64),而W是float32,NumPy 的混合精度运算会静默降级,导致梯度计算失效。
    解决:统一使用np.float32;权重初始化后,手动模拟grad属性(如W.grad = np.zeros_like(W));在forward函数开头添加assert x.dtype == np.float32 and W.dtype == np.float32。

  • 现象:dL/dW计算出来后,W -= lr * dL_dW更新后,W的std迅速变为nan。
    原因:dL_dy的形状是(batch_size, out_dim),而x的形状是(batch_size, in_dim),直接dL_dy @ x.T会得到(batch_size, batch_size)的错误形状。正确做法是:对 batch 内每个样本单独计算dL_dW_i = dL_dy_i.reshape(-1, 1) @ x_i.reshape(1, -1),再求平均。Kneusel 在第10章明确指出,批量梯度是单样本梯度的均值,不是矩阵乘积的均值。
    解决:dL_dW = np.mean([np.outer(dL_dy_i, x_i) for dL_dy_i, x_i in zip(dL_dy, x)], axis=0),或更高效地用np.einsum('bi,bj->ij', dL_dy, x) / batch_size。

  • 现象:im2col后的X_col形状与W_flat不匹配,@运算报错ValueError: shapes (9,4) and (1,9) not aligned。
    原因:W_flat被reshape(1, -1)成(1, 9),而X_col是(9, 4),W_flat @ X_col要求W_flat.shape[1] == X_col.shape[0],即9 == 9,是对的。但若你误写成X_col @ W_flat.T,就会因X_col.shape[1] (4) != W_flat.T.shape[0] (1)而失败。
    解决:永远遵循W @ X的顺序,其中W是参数,X是输入;在im2col后,打印X_col.shape和W_flat.shape,并用assert W_flat.shape[1] == X_col.shape[0]强制校验。

  • 现象:使用np.dot替代@进行矩阵乘,dL_dW计算结果与 PyTorch 的autograd结果allclose失败,误差在1e-3量级。
    原因:np.dot对高维数组的行为与@不同。np.dot(a, b)当a和b都是二维时等价于@,但当a是(N,),b是(N, M)时,np.dot执行的是向量-矩阵乘(结果(M,)),而@会报错。这种隐式转换在梯度计算中会引入难以察觉的形状错误。
    解决:无条件使用@运算符;在所有涉及矩阵乘的函数中,添加assert a.ndim == 2 and b.ndim == 2断言。


3. 概率与统计:不是算 P(A|B),而是理解“模型预测”为何天然带不确定性

深度学习模型的输出,无论是分类的概率分布,还是回归的预测值,其背后都站着概率论。但 Kneusel 并未陷入贝叶斯公式的推导泥潭,而是聚焦一个尖锐问题:为什么 softmax 的输出可以被解释为“概率”,而 sigmoid 的输出在二分类中又可以?它们的数学基础是否一致?本书的答案是:一致性源于最大似然估计(MLE)。当你选择交叉熵作为损失函数时,你实际上是在假设:模型的输出y_pred是真实类别分布p(y|x)的参数化估计,而最小化交叉熵,等价于最大化观测数据的似然。这个视角,将“调参”升维为“参数估计”,让每一个loss.backward()都有了统计学意义。

3.1 交叉熵:从信息论到 MLE 的完整闭环

第3章“More Probability”中,Kneusel 用整整一节(3.3)拆解交叉熵。他首先从香农信息量出发:一个事件y发生的信息量是-log p(y)。如果模型预测p_model(y|x),而真实标签是y_true,那么模型为此事件付出的“信息代价”就是-log p_model(y_true|x)。这就是单样本的交叉熵。接着,他转向 MLE:假设数据集{(x_i, y_i)}独立同分布(i.i.d.),则联合似然是∏_i p_model(y_i|x_i)。取对数,得到对数似然∑_i log p_model(y_i|x_i)。最大化对数似然,等价于最小化-∑_i log p_model(y_i|x_i),即平均交叉熵。Kneusel 的 Python 示例,用 NumPy 手动实现了这个闭环:

def categorical_cross_entropy(y_true, y_pred): """y_true: (N, C) one-hot, y_pred: (N, C) softmax output""" # 防止 log(0) y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15) return -np.sum(y_true * np.log(y_pred)) / y_true.shape[0] def softmax(x): exp_x = np.exp(x - np.max(x, axis=1, keepdims=True)) # 数值稳定 return exp_x / np.sum(exp_x, axis=1, keepdims=True) # 模拟一批数据 logits = np.random.randn(32, 10) # (batch, classes) y_pred = softmax(logits) # (32, 10) y_true = np.eye(10)[np.random.choice(10, 32)] # (32, 10) one-hot loss = categorical_cross_entropy(y_true, y_pred) print(f"Cross-entropy loss: {loss:.4f}") # 验证:loss 是否等于 -mean(log(p_true_class)) p_true_class = np.sum(y_true * y_pred, axis=1) # (32,) log_likelihood = np.mean(np.log(p_true_class)) print(f"Log-likelihood: {log_likelihood:.4f}") print(f"-Log-likelihood: {-log_likelihood:.4f}") # 应与 loss 非常接近

这段代码的精妙之处,在于p_true_class = np.sum(y_true * y_pred, axis=1)。y_true是 one-hot,y_pred是 softmax 输出,它们的逐元素乘积y_true * y_pred只保留了真实类别的预测概率,其余为 0;再沿类别轴求和,就得到了每个样本的p_model(y_true|x_i)。np.mean(np.log(p_true_class))就是平均对数似然。运行它,你会发现-log_likelihood与loss的差值在1e-12量级。这不是巧合,而是数学的必然。Kneusel 用这个例子告诉你:你每天写的nn.CrossEntropyLoss(),其背后是坚实的统计学基石——你不是在最小化一个任意的“距离”,而是在寻找最可能生成你所见数据的模型参数。

3.2 贝叶斯视角:为什么 dropout 是一种隐式的模型平均?

第3章末尾,Kneusel 引入贝叶斯定理,但目的不是让你去实现 MCMC 采样,而是解释一个常见现象:为什么 dropout 在训练时随机置零神经元,而在测试时要将输出乘以保留概率(如 0.5)?他将其解释为“模型集成(Ensemble)”的廉价近似。在贝叶斯框架下,我们不想要单一的最佳参数θ*,而是想要后验分布p(θ|D)。p(θ|D) ∝ p(D|θ) p(θ)。p(D|θ)是似然,p(θ)是先验。Dropout 的随机性,可以被看作是在对θ施加一个特殊的先验:p(θ)鼓励参数稀疏,即很多θ_i为零。每次 dropout,相当于从这个先验中采样一个子网络θ_s。训练过程,就是在学习一个能生成大量不同θ_s的分布。测试时,我们无法遍历所有2^N个子网络,于是用期望E_{θ_s}[f(x; θ_s)]近似。Kneusel 指出,对于线性层y = Wx + b,若W的每个元素以概率p保留,则E[W] = p * W_full,因此测试时需用W_full * p来保持期望一致。他的 NumPy 示例,直观展示了这一补偿:

# 模拟一个线性层权重 W_full = np.random.randn(10, 5) # (10, 5) p = 0.5 # 训练时:dropout mask = (np.random.rand(*W_full.shape) < p).astype(float) # (10, 5) W_dropout = W_full * mask # (10, 5) # 测试时:补偿 W_test = W_full * p # (10, 5) # 验证:E[W_dropout] == W_test print(f"Mean of W_dropout:\n{W_dropout.mean(axis=0)}") print(f"W_test (p * W_full):\n{W_test[0]}") # 取第一行比较 # 你会发现,W_dropout 的均值(多次运行取平均)趋近于 W_test

这个例子将 dropout 从一个“防止过拟合的技巧”,还原为一个有明确概率解释的、对模型不确定性进行建模的策略。它让你明白:model.eval()时的scale操作,不是魔法,而是为了保证E[f(x)]的无偏性。当你理解了这一点,你就能举一反三:为什么 BatchNorm 在eval模式下要用 running_mean/runing_var,而不是 batch 统计?答案同样是:为了保证E[f(x)]的一致性。

3.3 统计检验:为什么你的 A/B 测试结果可能只是噪声?

第4章“Statistics”中,Kneusel 用一整节(4.6)讨论假设检验,但目标非常务实:如何判断你新提出的模型架构,真的比 baseline 好,还是只是随机波动?他没有堆砌 t 检验的公式,而是用一个具体场景切入:你跑了 5 次实验,baseline 的准确率是[85.2, 84.9, 85.1, 85.0, 85.3],新模型是[86.1, 85.8, 86.0, 85.9, 86.2]。平均提升0.9%,看起来很美。但 Kneusel 会问:这个差异的标准误(Standard Error)是多少?t统计量是多少?p值是否小于0.05?他的 NumPy 实现,用scipy.stats.ttest_rel(配对 t 检验)给出了答案:

from scipy import stats baseline_acc = np.array([85.2, 84.9, 85.1, 85.0, 85.3]) new_acc = np.array([86.1, 85.8, 86.0, 85.9, 86.2]) # 配对 t 检验:检验差值的均值是否显著不为 0 t_stat, p_value = stats.ttest_rel(new_acc, baseline_acc) print(f"t-statistic: {t_stat:.4f}") print(f"p-value: {p_value:.4f}") print(f"Significant at alpha=0.05? {p_value < 0.05}") # 计算效应量(Cohen's d) diff = new_acc - baseline_acc cohens_d = diff.mean() / diff.std(ddof=1) print(f"Cohen's d (effect size): {cohens_d:.4f}")

运行结果可能显示p-value = 0.002,显著;但也可能p-value = 0.12,不显著。Kneusel 强调:p值不是“新模型有多好”的度量,而是“观察到的差异由随机性导致的概率”。一个p=0.002的结果,意味着如果新模型和 baseline 真的没有区别,你重复实验 1000 次,只有 2 次会看到这么大的差异。这比单纯看0.9%的提升,更能说服审稿人或产品经理。更重要的是,他引入了Cohen's d,一个无量纲的效应量指标。d=0.2是小效应,d=0.5是中等,d=0.8是大效应。0.9%的绝对提升,若d=0.1,说明它在统计上虽显著,但实际价值有限。这个视角,帮你跳出“数字崇拜”,建立对结果稳健性的判断力。

3.4 避坑:概率与统计中,那些让结论完全翻车的常见误用

注意:这些错误在顶会论文的附录和工业界模型报告中高频出现。

  • 现象:报告模型在测试集上的准确率是92.5%,并声称“显著优于 baseline 的91.8%”。
    原因:未进行统计检验,仅比较了两个点估计。92.5%和91.8%的差异,可能完全在各自的置信区间内。例如,baseline 的95% CI是[91.5%, 92.1%],new model 的是[92.2%, 92.8%],二者重叠,差异不显著。
    解决:对每个模型,用 bootstrap 或scipy.stats.bootstrap计算95% CI;对差异,用配对 t 检验或 Wilcoxon 符号秩检验(当数据不满足正态性时)。

  • 现象:使用sklearn.metrics.classification_report得到 precision/recall/f1,发现某类 recall 极低(0.1),于是断言“模型对该类完全失效”。
    原因:忽略了该类的样本量。如果该类只有 10 个样本,而模型只预测对了 1 个,recall=0.1是合理的,但不能据此否定整个模型。应结合support(样本数)看。Kneusel 在 4.1 节强调:任何统计量,脱离其方差或置信区间,都是无意义的。
    解决:在classification_report中,务必查看support列;对低支持度的类,用scikit-learn的precision_recall_fscore_support获取average=None的详细结果,并计算其95% CI。

  • 现象:在数据预处理中,对整个数据集(包括 train/val/test)一起计算mean/std,然后归一化。
    原因:这造成了数据泄露(Data Leakage)。测试集的统计信息被用来处理训练集,导致模型在测试时获得了“未来信息”,accuracy虚高。Kneusel 在 4.4 节“Missing Data”中,将此列为最高危错误。
    解决:永远只用训练集的mean/std来归一化所有集合。代码中必须是:

    train_mean = train_data.mean(axis=0) train_std = train_data.std(axis=0, ddof=1) train_norm = (train_data - train_mean) / train_std val_norm = (val_data - train_mean) / train_std # 用 train 的统计量! test_norm = (test_data - train_mean) / train_std
  • 现象:用Pearson相关系数分析两个特征x和y的关系,得到r=0.85,于是认为x是y的强预测因子。
    原因:Pearson 相关只衡量线性相关性。如果y = x^2,x在[-1,1]上均匀分布,则r接近0,但x和y显然强相关。Kneusel 在 4.5 节对比了 Pearson 和 Spearman,指出后者基于秩次,能捕捉单调关系。
    解决:先画散点图;若关系疑似非线性,用scipy.stats.spearmanr;若怀疑复杂关系,用互信息(Mutual Information)或距离相关(Distance Correlation)。


4. 微积分与矩阵微积分:不是求导公式表,而是构建“可微分编程”的直觉

深度学习的引擎是梯度下降,而梯度下降的燃料是导数。Kneusel 没有把微积分写成一本极限练习册,而是把它塑造成一门可微分编程(Differentiable Programming)的实践指南。他反复强调一个核心思想:在深度学习中,我们几乎从不手动求解解析导数,而是构建一个由基本可微操作(add, mul, sin, exp...)组成的计算图,然后用链式法则自动组合它们的局部导数。本书的第7、8章,就是教你如何亲手“组装”这个计算图,并验证autograd的每一步。

4.1 标量函数的梯度:从f(x)=x²到f(W)=||W||_F²

第7章“Differential Calculus”从最简单的f(x) = x²开始,但这不是为了复习,而是为了建立梯度的几何直觉:df/dx = 2x,在x=3处,梯度是6,意味着x增加一个微小量ε,f会增加约6ε。Kneusel 立即把这个直觉推广到矩阵:f(W) = ||W||_F² = sum_{i,j} W_{ij}²。这是一个标量函数,其对矩阵W的梯度∂f/∂W是一个与W同形的矩阵,且∂f/∂W_{ij} = 2W_{ij}。所以∂f/∂W = 2W。这个结论,是 L2 正则化λ||W||_F²的梯度2λW的来源。他的 NumPy 示例,用数值微分(Numerical Differentiation)验证了它:

def frobenius_norm_sq(W): return np.sum(W ** 2) # 数值微分:在 W[i,j] 处加一个小扰动 h,看 f 的变化 W = np.random.randn(3, 4) h = 1e-5 i, j = 1, 2 # 解析梯度 grad_analytic = 2 * W # 数值梯度 W_plus = W.copy() W_plus[i, j] += h f_plus = frobenius_norm_sq(W_plus) f_center = frobenius_norm_sq(W) grad_numeric = (f_plus - f_center) / h print(f"Analytic grad[{i},{j}]: {grad_analytic[i, j]:.6f}") print(f"Numeric grad[{i},{j}]: {grad_numeric:.6f}") print(f"Error: {abs(grad_analytic[i, j] - grad_numeric):.2e}") # Error 应该在 1e-10 量级

这个验证过程,是本书的标志性方法:**用

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 15:16:55

电脑病毒与木马清除防范:排查、重装与备份

电脑病毒和木马的清除与防范&#xff0c;说到底是两件事&#xff1a;一件是“中招之后怎么把损失摁住、把机器救回来”&#xff0c;另一件是“怎么让自己压根不进入那个救火流程”。我做过不少次上门救机的活&#xff0c;也见过太多人一发现电脑不对劲&#xff0c;第一反应就是…

作者头像 李华
网站建设 2026/10/31 15:14:57

LeMiCa:视频生成缓存优化算法解析与实战

1. 项目背景与核心价值 视频生成技术正经历从传统GAN到扩散模型的范式转移。当前主流方案如Stable Diffusion、Open-Sora等虽然能生成高质量视频&#xff0c;但面临两个关键瓶颈&#xff1a;一是逐帧生成的计算开销呈线性增长&#xff0c;二是全局内容一致性难以保持。LeMiCa通…

作者头像 李华
网站建设 2026/10/31 15:14:46

MATLAB kmeans聚类:从一行调用到可解释实现

简介&#xff1a;面向Matlab初学者与数据分析入门者的K-means聚类算法学习资料&#xff0c;以一份docx文档形式系统讲解无监督聚类的基本原理与代码实现&#xff0c;帮助读者理解如何用Matlab内置kmeans函数完成数据分簇与结果可视化。压缩包内共1个docx文件&#xff0c;约15KB…

作者头像 李华
网站建设 2026/10/31 15:14:24

CPU升级兼容性三重验证:物理、电气、固件全解析

1. 为什么换CPU比换显卡难十倍&#xff1a;从“插上去就能用”到“查三天主板手册”的真实落差很多人第一次想升级电脑&#xff0c;直觉就是“换个更好的CPU”&#xff0c;毕竟CPU是大脑&#xff0c;性能提升最直接。结果拆开机箱&#xff0c;把新买的CPU往插槽里一放——咔哒一…

作者头像 李华
网站建设 2026/10/31 15:14:24

高德地图API实现高效批量地理编码技术方案

1. 项目背景与核心价值地理编码技术在现代信息化应用中扮演着越来越重要的角色。我最近接手了一个物流调度系统的改造项目&#xff0c;需要将客户提供的2000多条文本地址批量转换为经纬度坐标。这个看似简单的需求&#xff0c;在实际操作中却遇到了地址格式不规范、API调用限制…

作者头像 李华
网站建设 2026/10/31 15:14:24

编译原理试卷五关:正则DFA、逆波兰、LL(1)与四元式解析

简介&#xff1a;常州工学院编译原理试卷A面向计算机专业本科生及考研复习者&#xff0c;用于检验词法分析、语法分析与中间代码生成等核心章节的掌握程度。压缩包内仅含1个doc文件&#xff0c;大小约55KB&#xff0c;虽体量精简&#xff0c;但题目覆盖正规表达式与最简DFA构造…

作者头像 李华