人工智能数学基础习题答案与源码:公式到NumPy数值验证
简介《人工智能数学基础资源习题答案源代码》面向准备系统补齐AI数学基础的学生与从业者围绕唐宇迪相关课程内容覆盖线性代数、概率论与统计、微积分、最优化、图论与组合优化、离散数学及动态规划等知识点并配套习题答案与源代码帮助读者把矩阵运算、概率建模、梯度下降、图算法和序列优化等概念落到实际计算与编程练习中。压缩包约6.41MB上游未提供有效文件总数与类型明细具体文件构成以实际下载为准。已有1930人学习说明该资料在AI入门与进阶复习场景中具有一定参考价值。读者可结合习题核对推导过程再对照代码理解公式如何转化为实现从向量矩阵、贝叶斯定理到梯度下降、Viterbi算法等形成从理论到实践的闭环适合课程复习、面试准备和算法入门时查漏补缺。1. 从 loss 不降说起这份人工智能数学基础资源补的是哪块短板调模型时最怕一种情况网络结构看着没问题数据也清洗过loss 就是不降换个初始化又偶尔能跑通像玄学。回头翻人工智能数学基础的教材公式都认识但一到代码里就不知道对应哪一行。这份资源把习题答案和配套源代码放在一起覆盖线性代数、微积分、概率统计和信息论几条主线每题先给推导再给可运行的数值验证。适合已经能跑通训练脚本、但遇到维度对齐、梯度校验、概率计算出 nan 时说不清原因的人也适合刚学完数学课、想把纸面公式落到 NumPy 里的新手。它不是框架教程价值在于把“公式—代码—数值结果”三者对上。2. 拆开资源包习题答案与源码目录怎么对齐四条数学主线资源到手后最容易犯的错是从第一页答案开始逐行抄。习题答案给的是推导链源码给的是数值出口两边编号和变量名往往不一致。先花半小时做一张对应表后面遇到脚本跑不通能立刻判断是公式理解偏了还是代码写错了。这一章先把资源结构、运行环境和第一个可复现样例走通后面再拆具体算法。2.1 目录映射先建立“题号—公式—脚本”三列表人工智能数学基础的习题通常按“定义—性质—计算—证明”四类出题。答案文档里计算题会给中间步骤证明题会给关键不等式源码目录里脚本一般按主题命名比如线代分解、梯度校验、概率采样。两边不是一一对应有的习题没有代码有的脚本覆盖好几道题。我一般先拉一张三列表把题号、核心公式、验证脚本填进去填不上的先空着跑通一个补一个。资源位置知识点源码验证方式预期输出线代习题 03正交矩阵与内积随机正交阵乘向量比较范数相对误差小于 1e-12微积分习题 07复合函数求导中心差分对比解析梯度相对误差小于 1e-6概率习题 05伯努利极大似然采样后估计 p增大 n 看收敛估计值靠近真值信息论习题 02交叉熵logits 直算与朴素实现对比差值在 1e-12 内这张表不用填得很漂亮关键是逼自己把“答案里的符号”和“代码里的变量”对上。答案里写 (x_i)代码里可能是x[i-1]答案里写行向量代码里可能是列向量。先把这个差异记下来比后面调试半天强。表格里“预期输出”一列很重要它相当于给每个脚本设了一个通过标准跑完对不上就知道有问题。读答案也有顺序。计算题先遮住答案自己做做完只看关键步骤不逐行抄证明题不要求背但要能说出每一步用了什么性质比如“这里用了正交矩阵保内积”“这里用了 Jensen 不等式”。源码先跑通再看实现跑之前猜一下输出大概是什么量级跑完对不上说明直觉有问题回头补那一块。2.2 环境与依赖四个库跑通全部样例这套资源的代码以数值验证为主不需要深度学习框架。我一般不在系统 Python 里直接装建一个虚拟环境把依赖收在一个文件里。下面命令里的包名按用途选版本不写死跑之前用pip freeze记一份当前环境后面换机器好对照。python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install numpy scipy matplotlib sympy jupyter pip freeze requirements-lock.txtnumpy是主力矩阵运算、随机数、分解都在这里scipy用来做分布函数和优化求解的对照matplotlib画收敛曲线和分布图方便肉眼判断sympy做符号求导验证手推结果。Jupyter 不是必须但习题答案里公式多边写推导边跑数值比来回切文件舒服。装完先做一次自检确认随机数新接口可用、浮点默认精度是 64 位。很多“梯度校验全红”的翻车根子在于数组被建成了float32差分步长一小就全是舍入误差。自检代码不用长三行就够import numpy as np rng np.random.default_rng(42) x rng.normal(size3) print(x.dtype, x.shape) assert x.dtype np.float64这里用default_rng而不是全局np.random.seed原因是新接口把随机状态封装在生成器对象里不会和别的脚本互相干扰。seed42只是习惯换成别的也行关键是同一个实验里固定住。打印dtype是为了确认后面做差分时不会掉到半精度。2.3 第一个可复现样例矩阵乘法、广播与形状断言先拿矩阵乘法开刀因为它能同时练三件事按定义写循环、用形状断言卡住输入、用allclose做数值对比。答案里的矩阵乘法往往写成求和符号代码里要落成下标两边最容易在“行乘列”还是“列乘行”上错位。下面这个手写版本故意用三重循环慢但和定义一一对应适合第一次对照。import numpy as np def matmul_manual(A, B): # 只接受二维矩阵按定义做三重循环用来对照 np.matmul assert A.ndim 2 and B.ndim 2, 输入必须是二维矩阵 assert A.shape[1] B.shape[0], f形状不匹配: {A.shape} x {B.shape} m, k A.shape k2, n B.shape C np.zeros((m, n), dtypenp.float64) for i in range(m): for j in range(n): s 0.0 for t in range(k): s A[i, t] * B[t, j] C[i, j] s return C rng np.random.default_rng(42) A rng.normal(size(3, 4)) B rng.normal(size(4, 2)) C1 matmul_manual(A, B) C2 A B print(最大绝对误差:, np.max(np.abs(C1 - C2))) assert np.allclose(C1, C2, rtol1e-12, atol1e-12)函数开头两个assert是血泪经验形状不匹配的报错如果留到循环里会变成IndexError看不出是哪一步错了。rtol是相对容差atol是绝对容差两个都给小值是为了在结果接近零时也不放过误差。dtypenp.float64显式指定避免整数输入把结果截断。接着看广播。答案里常写“偏置逐行相加”代码里就是(5, 3)加(3,)NumPy 会把后者沿第 0 轴复制。这个规则不复杂但维度一多就容易想当然。x rng.normal(size(5, 3)) b rng.normal(size(3,)) y x b # b 沿第 0 轴广播等价于每行加同一个偏置 print(y.shape) assert y.shape (5, 3)广播的对齐规则是从尾部开始比维度相等或其中一个为 1 才能 broadcast。写代码时我习惯在加法前打印两个操作数的shape跑通一次再删掉。这个习惯看着笨但能省掉大量“结果形状不对后面全错”的排查时间。3. 线性代数与微积分源码从 SVD 分解到梯度校验的落地写法线代和微积分是人工智能数学基础里最容易脱节的两块纸面上会算特征值代码里不知道怎么中心化纸面上会链式法则代码里一写反向传播就维度对不上。这一章挑三个最常被习题覆盖、又最容易在工程里翻车的点PCA 的两条实现路径、梯度校验的步长选择、链式法则的手写拆解。3.1 PCA 的两种实现路径协方差特征分解与 SVD习题答案里 PCA 一般先讲协方差矩阵再讲特征分解工程代码里更常见的是直接对中心化后的数据做 SVD。两条路径数学上等价但数值行为和输出符号不一样。下面这个实现把两条都跑一遍方便对照答案里的推导。import numpy as np def pca_fit(X, n_components): # X: (n_samples, n_features)按列中心化 X np.asarray(X, dtypenp.float64) mean X.mean(axis0, keepdimsTrue) Xc X - mean # 路径一协方差矩阵特征分解 cov (Xc.T Xc) / (Xc.shape[0] - 1) eigvals, eigvecs np.linalg.eigh(cov) idx np.argsort(eigvals)[::-1] eigvals eigvals[idx] eigvecs eigvecs[:, idx] components eigvecs[:, :n_components].T # 路径二对中心化数据做 SVD U, S, Vt np.linalg.svd(Xc, full_matricesFalse) explained_ratio_svd (S ** 2) / np.sum(S ** 2) Z Xc components.T return Z, components, eigvals, explained_ratio_svd rng np.random.default_rng(7) X rng.normal(size(200, 6)) rng.normal(size(6, 6)) Z, comp, vals, ratio pca_fit(X, 3) print(Z.shape, comp.shape, vals[:3], ratio[:3])mean(axis0, keepdimsTrue)保留二维形状后面减的时候不会因为广播把样本轴也带上。协方差除以n-1是样本协方差的无偏写法答案里如果写 (1/n)那是总体写法数值会差一点不影响主成分方向。np.linalg.eigh专门用于对称矩阵返回的特征值默认升序所以要argsort后反转。components取前n_components行转置后形状是(k, d)投影时用Xc components.T。SVD 路径里S是奇异值平方后正比于特征值explained_ratio_svd就是常说的解释方差比。两条路径的components可能差一个负号这是特征向量方向不唯一造成的不是代码错。比较的时候不要直接allclose两个components先比投影后的重构误差或者把每个主成分绝对值最大的分量统一成正号再比。参数上n_components不要超过min(n_samples, n_features)样本数远大于特征数时协方差路径更直观特征维度很高时SVD 路径更稳因为不用显式构造d x d的协方差矩阵。习题里常问“PCA 前要不要标准化”答案一般说看量纲。代码里如果各列量纲差得多先做(X - mean) / std再做中心化否则方差大的列会主导主成分。3.2 梯度校验脚本中心差分、相对误差与 eps 选择梯度校验是判断反向传播写没写对的后悔药。原理不复杂解析梯度算一个数值梯度用差分算一个两者相对误差足够小就认为通过。翻车点主要在步长和精度。下面这个脚本拿一个标量函数做例子结构可以直接套到神经网络层上。import numpy as np def f(x): return np.sum(np.sin(x) * x**2) def grad_analytic(x): return 2 * x * np.sin(x) x**2 * np.cos(x) def grad_numeric(x, eps1e-6): g np.zeros_like(x, dtypenp.float64) for i in range(x.size): xp x.copy() xm x.copy() xp[i] eps xm[i] - eps g[i] (f(xp) - f(xm)) / (2 * eps) return g rng np.random.default_rng(0) x rng.normal(size5) ga grad_analytic(x) gn grad_numeric(x, eps1e-6) rel np.linalg.norm(ga - gn) / (np.linalg.norm(ga) np.linalg.norm(gn) 1e-12) print(相对误差:, rel) assert rel 1e-6中心差分的截断误差是 (O(\epsilon^2))比前向差分高一阶代价是每个分量多算一次函数值。eps不能太大太大截断误差上来也不能太小浮点数相减会吃舍入误差。float64下我一般从1e-5试到1e-7看相对误差是否稳定在1e-7到1e-9量级。分母里加1e-12是防止解析梯度全零时除零。这个校验器本身也要先自检。拿一个二次函数 (f(x)x^T A x) 跑一遍如果二次函数都过不了说明校验器写错了别急着怀疑反向传播。另外ReLU 在零点不可导校验时要么避开零点要么把该点排除。批量大小和特征维度不影响校验逻辑但数组一大多跑几秒调试阶段用小批量就够。3.3 链式法则的手写拆解一个复合函数的反向传播习题答案里的链式法则常写成连乘代码里要落成缓存中间变量。下面用一个两层线性加 ReLU 的小例子把前向缓存和反向拆开维度都标在注释里。这个结构看懂之后换成卷积或注意力思路是一样的。import numpy as np def forward(x, W1, b1, W2, b2): # x: (N, D), W1: (D, H), b1: (H,), W2: (H, C), b2: (C,) z1 x W1 b1 a1 np.maximum(z1, 0) # ReLU z2 a1 W2 b2 cache (x, W1, b1, z1, a1, W2, b2) return z2, cache def backward(dz2, cache): x, W1, b1, z1, a1, W2, b2 cache dW2 a1.T dz2 # (H, N) (N, C) - (H, C) db2 dz2.sum(axis0) # (C,) da1 dz2 W2.T # (N, C) (C, H) - (N, H) dz1 da1 * (z1 0) # ReLU 导数 dW1 x.T dz1 # (D, N) (N, H) - (D, H) db1 dz1.sum(axis0) # (H,) return dW1, db1, dW2, db2 rng np.random.default_rng(3) x rng.normal(size(8, 4)) W1 rng.normal(size(4, 5)) b1 np.zeros(5) W2 rng.normal(size(5, 3)) b2 np.zeros(3) dz2 rng.normal(size(8, 3)) _, cache forward(x, W1, b1, W2, b2) grads backward(dz2, cache) print([g.shape for g in grads])cache里存z1是为了算 ReLU 导数存a1是为了算dW2存x是为了算dW1。反向的顺序必须和前向相反先dW2、db2再da1再dz1最后dW1、db1。dz1 da1 * (z1 0)这一行是链式法则里“乘上局部导数”的落点z1 0返回布尔数组乘的时候自动转成 0/1。写完不要只看形状拿数值梯度抽几个分量对一下。把W1的某个元素扰动一下前向算两次 loss差分结果和dW1对应位置比相对误差小于1e-6才算过。这个习惯能抓住“公式抄对但下标转置错”的问题比盯着代码看有效。4. 概率统计与信息论习题采样、极大似然与交叉熵的代码验证概率统计的习题容易给人“会推公式就行”的错觉一到代码里采样用错生成器、方差除错自由度、交叉熵先 softmax 再 log都会让结果偏掉或者直接出 nan。这一章按“采样—估计—信息量”的顺序把习题里最常见的三类计算落到可运行的代码上。4.1 分布采样与参数估计随机数生成器的正确用法答案里写“从某分布采样 n 个样本”代码里第一反应是np.random.normal。全局接口不是不能用但多个脚本一起跑时种子会互相污染。新接口把状态收进Generator对象下面这段同时采均匀、正态、伯努利再反算均值和标准差。import numpy as np rng np.random.default_rng(2024) n 10000 u rng.uniform(0, 1, sizen) normal_samples rng.normal(loc2.0, scale3.0, sizen) bernoulli rng.binomial(1, 0.3, sizen) print(均匀均值:, u.mean()) print(正态均值/标准差:, normal_samples.mean(), normal_samples.std(ddof0)) print(伯努利均值:, bernoulli.mean())rng.uniform(0, 1, sizen)的区间是左闭右开均值和方差的理论值分别是 0.5 和 1/12样本量一万时波动已经不大。rng.normal(loc, scale)的scale是标准差不是方差答案里如果给方差记得开根号。rng.binomial(1, p)等价于伯努利采样bernoulli.mean()就是频率估计。std(ddof0)是总体标准差除以 nddof1是样本标准差除以 n-1。极大似然估计里正态方差的 MLE 是除以 n很多习题答案会专门考这个区别。代码里用哪个取决于你在做“已知分布参数的真值验证”还是“样本无偏估计”别混着用。采样代码还要看一个东西样本量增大时估计值是否收敛。把 n 从 100 改成 10000 再改成 100000均值应该逐步靠近真值。如果反而越跑越偏先检查是不是每次循环都重新设了种子导致样本重复。4.2 极大似然估计从习题推导到数值求解伯努利和正态的 MLE 有闭式解习题里推一遍代码里算一遍正好对照。下面两个函数分别返回闭式解正态部分故意同时给 MLE 方差和无偏方差方便看差别。import numpy as np def bernoulli_mle(samples): samples np.asarray(samples, dtypenp.float64) assert np.all((samples 0) | (samples 1)), 伯努利样本只能是 0/1 return samples.mean() def normal_mle(samples): samples np.asarray(samples, dtypenp.float64) mu samples.mean() sigma2_mle np.mean((samples - mu) ** 2) # 除以 n sigma2_unbiased np.var(samples, ddof1) # 除以 n-1 return mu, sigma2_mle, sigma2_unbiased rng np.random.default_rng(11) b rng.binomial(1, 0.35, size5000) print(伯努利 p 估计:, bernoulli_mle(b)) x rng.normal(loc1.5, scale2.0, size5000) print(正态 mu/sigma2_mle/sigma2_unbiased:, normal_mle(x))bernoulli_mle里的断言不是多余习题数据里偶尔混进 -1 或 2均值算出来也能看但已经不是伯努利似然了。正态 MLE 的均值就是样本均值方差是样本二阶中心矩除以 n无偏版本除以 n-1样本量小时差别明显样本量大时差别可以忽略但考试和面试会问。如果习题要求用数值优化求 MLE比如带约束的指数族可以用scipy.optimize.minimize最小化负对数似然。写法上先把似然写成求和再取负传methodL-BFGS-B给一个初始点。数值解和闭式解对不上时先看负号有没有漏、边界有没有越界再看优化器有没有收敛。4.3 交叉熵与 KL 散度logits 直算与数值稳定信息论习题里交叉熵的定义是 (-\sum p \log q)代码里如果先做 softmax 再取 loglogits 一大就溢出。正确做法是直接在 logits 上算 log-softmax先减最大值再取 exp。下面这段把稳定版和 KL 散度放在一起。import numpy as np def log_softmax(logits): logits np.asarray(logits, dtypenp.float64) m np.max(logits, axis-1, keepdimsTrue) shifted logits - m return shifted - np.log(np.sum(np.exp(shifted), axis-1, keepdimsTrue)) def cross_entropy(logits, labels): # labels: 整数类别下标形状 (N,) logp log_softmax(logits) n logits.shape[0] return -np.mean(logp[np.arange(n), labels]) def kl_divergence(p_logits, q_logits): logp log_softmax(p_logits) logq log_softmax(q_logits) p np.exp(logp) return np.sum(p * (logp - logq), axis-1)m np.max(..., keepdimsTrue)减最大值不改变 softmax 结果但把指数控制在 0 附近避免exp(1000)溢出。log_softmax返回的是对数概率交叉熵里直接取负平均不用再log一次。labels用整数下标索引np.arange(n)配labels取出每个样本对应类别的对数概率。KL 散度是非负的且不对称(D_{KL}(p|q)) 和 (D_{KL}(q|p)) 不一样。代码里先算logp和logq再用p * (logp - logq)求和比先算概率再取 log 稳。习题里如果给的是概率而不是 logits先把概率转成 logits 再走同一套函数避免两套代码路径。5. 避坑与排查答案对不上、源码跑不通的五种典型情况这套资源最常见的投诉不是“不会做”而是“答案看懂了代码跑出来不对”。下面五条是我自己踩过、也帮别人排查过的典型情况每条按现象、原因、解决写。遇到问题先对号入座比从头读代码快。5.1 符号与下标错位维度表先于公式现象答案里公式推得通代码跑出来形状不对或者转置之后能跑但数值差很远。原因数学下标习惯从 1 开始代码从 0 开始答案里行向量、列向量写法不统一代码里X可能是(n_samples, n_features)也可能是转置过的设计矩阵。两边没对齐乘法顺序就错。解决动笔前先写维度表把每个变量的 shape 标出来比如X: (N, D)、W: (D, H)、b: (H,)。代码里每个函数入口加assert卡形状转置前问一句“这是数据矩阵还是设计矩阵”。答案里的 (x_i) 对应代码里的x[i-1]这个偏移在写循环时最容易漏。5.2 梯度校验全红精度、步长与随机种子现象解析梯度和数值梯度的相对误差在 0.1 以上甚至有的分量符号相反。原因数组被建成了float32差分步长取到1e-8相减全是舍入误差或者函数里有 ReLU、绝对值这类不可导点正好踩在零点上也有可能是每次调用函数时随机种子变了两次前向输入不一致。解决统一float64eps从1e-5试到1e-7用中心差分。校验前先把随机输入固定住函数内部不要偷偷采样。拿一个二次函数先校验校验器本身二次函数过了再测目标函数。ReLU 网络校验时避开零点或者把该点从比较里排除。5.3 分解结果符号相反特征向量的不唯一性现象PCA 的components和答案逐元素比有的行差一个负号但投影后的重构误差很小。原因特征向量方向不唯一(v) 和 (-v) 都是单位特征向量。SVD 的符号也受实现影响不同库、不同数据顺序可能给出相反方向。解决不要直接逐元素比components先比投影矩阵 (VV^T)或者比重构误差。需要固定符号时让每个主成分绝对值最大的分量为正再比较。答案里如果只给方向不给符号说明符号本身不重要别在这上面耗。5.4 概率计算出 nan先 softmax 再 log 的代价现象交叉熵损失跑着跑着变成nan或者 KL 散度出现inf。原因先对 logits 做 softmax再取 loglogits 较大时exp溢出softmax 出现 0log(0)就是-inf标签越界时索引到不存在的类别也会出问题。解决直接在 logits 上算log_softmax先减最大值再取 exp。检查标签范围是否在[0, num_classes-1]。概率转 logits 时加一个极小值防止log(0)但更好的做法是全程保留 logits不在概率空间来回转。5.5 环境与版本漂移依赖锁定与复现记录现象同一份源码在另一台机器上报AttributeError说某个随机数接口不存在或者matmul结果精度不一样。原因新旧随机数 API 混用np.random.seed和default_rng各跑各的依赖没锁换机器装到了不同大版本Notebook 单元格执行顺序乱了变量是上一次残留的。解决统一用default_rng脚本开头建一个生成器往下传。装完环境立刻pip freeze存一份锁定文件记录 Python 版本。Notebook 调试完从头Restart Run All一遍确认没有隐藏的单元顺序依赖。换机器先跑自检脚本再跑业务代码。6. 进阶用法把答案变成自测题库再加一道数值稳定性检查答案看第二遍容易产生“我会了”的错觉。更稳的用法是把习题编号、知识点、对应脚本抽成一个题库遮住答案随机抽题做完再跑脚本对数值。下面这个抽题脚本不依赖第三方库改QUESTION_BANK就能用seed固定后每次抽同一组方便对比进度。import random QUESTION_BANK [ {id: LA-01, topic: 线代, stem: 验证正交矩阵保内积, script: la_orthogonal.py}, {id: CAL-03, topic: 微积分, stem: 中心差分校验复合函数梯度, script: grad_check.py}, {id: PROB-02, topic: 概率, stem: 推导伯努利 MLE 并采样验证, script: mle_bernoulli.py}, {id: INFO-01, topic: 信息论, stem: 手写 log_softmax 计算交叉熵, script: ce_stable.py}, ] def draw_questions(bank, k3, seed7): rng random.Random(seed) # 独立随机状态不污染全局 assert 0 k len(bank), 抽题数不能超过题库长度 return rng.sample(bank, k) # 不放回抽样 for q in draw_questions(QUESTION_BANK): print(f[{q[id]}] {q[topic]} | {q[stem]} - 跑 {q[script]})random.Random(seed)把随机状态收在对象里和 NumPy 的生成器互不干扰。sample是不放回抽样抽过的题不会重复想每天换一组就改seed想追踪同一组就固定。k默认 3按可用时间调通勤路上抽一道证明题晚上抽一道计算题加脚本验证比一次刷十道记得牢。抽完题不要只写答案跑一遍对应脚本再做一次数值稳定性检查。下面这张表是我提交前必过的五项每项都有明确的失败信号不靠感觉。检查项通过标准失败先看什么形状断言每个函数入口的 shape 都符合维度表转置、batch 轴、广播方向梯度校验相对误差小于 1e-6dtype、eps、不可导点概率输出无 nan/inf概率和为 1是否先 softmax 再 log、标签越界随机种子重跑两次结果一致是否混用全局随机和 Generator依赖记录换环境能按锁定文件装回是否漏记 Python 版本这张表里最容易被跳过的是“随机种子”和“依赖记录”因为它们不影响推导正确性但会让复现变成黑匣子。我吃过一次亏本地跑通的结果和另一台机器差在小数点后第四位查了半天是两边 NumPy 默认精度和随机数接口不一样最后靠锁定文件才对齐。从那以后我每次跑这份资源里的脚本都先做一遍形状断言和梯度校验再去看答案答案对不上时先怀疑自己的维度表和数值设置不急着怀疑公式。希望帮到你。本文还有配套的精品资源点击获取