简介:《机器学习期末复习题及答案》面向高校机器学习课程备考学生与自学者,围绕期末考点整理成一份可直接刷题的复习文档。内容涵盖单项选择题、多项选择题、名词解释、简答题与编程题等题型,涉及数据集划分、欠拟合与过拟合、K近邻、朴素贝叶斯、决策树、支持向量机核函数、聚类算法、线性回归与均方误差等核心概念,并附答案解析,便于对照知识点回顾原理与判别依据。压缩包内共1个docx文档,约20KB,体量轻便,适合打印或在平板上随时翻阅。目前已有1764人学习下载。读者可借此梳理课程知识框架,通过题目与解析定位薄弱环节;编程题部分给出线性回归建模步骤与K近邻分类的实现思路,能帮助理解从导入库、建模、拟合到预测评估的完整流程,适合考前集中突破与查漏补缺。
1. 期末复习别从第一页翻起,先按题型把考点切开
机器学习期末卷面基本是三类题的组合:概念辨析与简答、公式推导与手算迭代、代码或伪代码填空。按教材目录从头翻,容易在定义堆里耗掉一半复习时间,推导和手算还是空的。更划算的做法是先找近三年的卷子,把每道题对应的知识点列成一张表,再倒着回去补。这张表里,线性回归、逻辑回归、SVM 稳定出推导题,朴素贝叶斯和 K-means 出概率与迭代题,混淆矩阵、P/R/F1、ROC-AUC 出计算题,偏差方差与正则化几乎只出简答。后续几章按这个权重展开,每段公式都配可运行的验证代码,考场上手算的中间结果能直接和代码输出对答案,错在哪一步一眼看得出来。
2. 判别式模型复习:线性回归、逻辑回归与 SVM 的手推与代码对照
2.1 线性回归:闭式解和梯度下降两条路都得会写
卷面最常见的问法有两种:给定样本求最优参数 w,或者写出梯度下降的更新式并迭代两步。最小二乘目标写成 J(w) = ½‖Xw − y‖²,对 w 求偏导置零得到 Xᵀ(Xw − y) = 0,整理出正规方程 w = (XᵀX)⁻¹Xᵀy。这个式子要背,更要会判断 XᵀX 是否可逆:特征之间存在强共线,或者样本数少于特征数时矩阵奇异,这时标准答案是加 L2 正则,变成 w = (XᵀX + λI)⁻¹Xᵀy,也就是岭回归的闭式解。
梯度下降版本的更新式是 w ← w − η·Xᵀ(Xw − y)/m,m 为样本数。考试一般给定初始 w、学习率和两三个样本,让你写两轮迭代结果,逐项代入比背公式更稳。
import numpy as np # 设计矩阵:第 0 列固定为 1,用来吸收截距项 X = np.array([[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]]) y = np.array([2.0, 3.1, 4.2]) # 路线一:正规方程闭式解 w_closed = np.linalg.inv(X.T @ X) @ X.T @ y # 路线二:批量梯度下降 w_gd = np.zeros(2) eta = 0.05 # 学习率,过大直接发散 for _ in range(500): grad = X.T @ (X @ w_gd - y) / len(y) w_gd = w_gd - eta * grad print("闭式解:", w_closed) # 约 [0.9, 1.1] print("梯度下降:", w_gd) # 500 步后贴近闭式解逻辑说明:X.T @ X是 2×2 的格拉姆矩阵,求逆在特征维度很高时开销大,工程上常用np.linalg.lstsq代替。学习率 0.05 是针对这组数据试出来的,换成 1.0 会震荡发散。考试手算时通常只要求两步,按 w ← w − ηXᵀ(Xw − y)/m 逐项代进去,保留两位小数就够了。
另外要区分"损失函数"和"评估指标":训练用的是均方误差,卷面问"模型好坏"时答的是 RMSE 或 R²,两者不能混着写。
2.2 逻辑回归:从对数似然推到梯度就是 (h − y)x
逻辑回归的假设是 h_w(x) = σ(wᵀx),其中 σ(z) = 1/(1 + e^{−z})。对数似然写成 L(w) = Σ[yᵢ log hᵢ + (1 − yᵢ) log(1 − hᵢ)],对 w_j 求导,中间用到 σ′(z) = σ(z)(1 − σ(z)),最后化简成 ∂L/∂w_j = Σ(yᵢ − hᵢ)xᵢⱼ。"梯度等于误差乘特征"这个结论是高频考点,建议自己推一遍再背,推导过程本身就是一道大题。
import numpy as np def sigmoid(z): return 1.0 / (1.0 + np.exp(-z)) # 4 个样本,2 个特征,标签 0/1 X = np.array([[1.0, -1.0], [1.0, 0.5], [1.0, 1.0], [1.0, 2.0]]) y = np.array([0.0, 0.0, 1.0, 1.0]) w = np.zeros(2) lr = 0.5 for step in range(200): h = sigmoid(X @ w) # 前向:线性输出压到 (0,1) grad = X.T @ (h - y) / len(y) # 梯度方向 w -= lr * grad # 沿负梯度走 print("权重:", w) print("预测概率:", sigmoid(X @ w))参数说明:学习率 0.5 比线性回归能放得更大,因为 sigmoid 导数最大只有 0.25,梯度天然被压缩过。判分点常常卡在梯度写成 (y − h):那是梯度上升的形式,必须配 w += lr·grad 才对,符号和更新方向混用是扣分重灾区。np.exp(-z)在 z 绝对值很大时会溢出,工程写法要按符号分支处理,卷面写公式不受影响,但代码题里出现这种写法要能反应过来。
2.3 SVM:间隔、对偶与 KKT 条件在卷面上怎么答完整
SVM 的复习主线是三层结构:原始问题、对偶问题、KKT 条件。原始问题是 min ½‖w‖²,约束为 yᵢ(wᵀxᵢ + b) ≥ 1。构造拉格朗日函数,对 w 和 b 求偏导置零,代回消去 w、b,得到对偶问题 max Σαᵢ − ½ΣΣαᵢαⱼyᵢyⱼxᵢᵀxⱼ,约束是 Σαᵢyᵢ = 0、αᵢ ≥ 0。这一步是从原始问题推到对偶的完整链条,考试常给一半让你补另一半。
KKT 条件里最常被问的是互补松弛 αᵢ(yᵢ(wᵀxᵢ + b) − 1) = 0。它说明只有落在间隔边界上的样本 αᵢ 才非零,其余样本对模型没有贡献,这正是"模型只依赖少数支持向量"的来源。软间隔把约束放宽成 yᵢ(wᵀxᵢ + b) ≥ 1 − ξᵢ,对偶里 αᵢ 的上界变成 C;核技巧就是把内积 xᵢᵀxⱼ 换成 K(xᵢ, xⱼ),RBF 核的 K(x, x′) = exp(−γ‖x − x′‖²),γ 越大决策边界越贴合训练点。
| 维度 | 线性回归 | 逻辑回归 | SVM |
|---|---|---|---|
| 输出 | 连续实数 | (0,1) 概率 | 决策函数符号 |
| 损失 | 均方误差 | 对数似然 | Hinge 损失 + L2 |
| 闭式解 | 有 | 无 | 无 |
| 关键超参 | 正则系数 λ | 学习率、正则系数 | C、核参数 γ |
| 卷面高频问法 | 正规方程、梯度式 | 梯度推导、概率解释 | 对偶、KKT、核技巧 |
from sklearn.linear_model import LinearRegression, LogisticRegression from sklearn.svm import SVC import numpy as np rng = np.random.default_rng(0) X = rng.normal(size=(60, 2)) y = (X[:, 0] + X[:, 1] > 0).astype(int) # 线性可分的二分类 lin = LinearRegression().fit(X, y) # 回归硬套分类,看分界方向 log = LogisticRegression(C=1.0).fit(X, y) svm = SVC(kernel="linear", C=1.0).fit(X, y) print("线性回归系数:", lin.coef_) print("逻辑回归系数:", log.coef_) print("SVM 系数:", svm.coef_, "支持向量数:", len(svm.support_))逻辑说明:LinearRegression对 0/1 标签做回归虽然给出一个分界方向,但会被远离边界的样本拉动,系数和 SVM 差得明显;svm.support_的长度就是支持向量个数,考试问"哪些是支持向量"可以用它核对答案。参数 C 控制误分类惩罚,C 变大间隔变窄、容易过拟合,在对偶里表现为 αᵢ 的上界抬升。
3. 生成式与无监督:朴素贝叶斯、K-means、EM 的推导套路与手算
3.1 朴素贝叶斯:拉普拉斯平滑的手算一步都不能省
朴素贝叶斯的核心假设是特征条件独立,决策规则为 argmax_c P(c)·ΠP(xⱼ | c)。卷面几乎必出一道表格题:给一小份训练集,算某个测试样本的后验概率。真正容易丢分的是零概率问题——某个特征取值在某个类别下从未出现,连乘直接归零。拉普拉斯平滑写成 P(xⱼ = a | c) = (N_jc + 1) / (N_c + S_j),其中 N_jc 是该取值在该类别下的计数,N_c 是该类的样本总数,S_j 是第 j 个特征可能的取值个数。
import numpy as np # 特征矩阵:0/1 两个取值;最后一列是类别 data = np.array([[1, 0, 0], [1, 1, 0], [0, 1, 1], [0, 0, 1]]) X, y = data[:, :-1], data[:, -1] n_feat, n_val = X.shape[1], 2 def nb_predict(x, alpha=1.0): scores = {} for c in np.unique(y): mask = (y == c) prior = (mask.sum() + alpha) / (len(y) + alpha * len(np.unique(y))) logp = np.log(prior) for j in range(n_feat): cnt = (X[mask, j] == x[j]).sum() + alpha denom = mask.sum() + alpha * n_val logp += np.log(cnt / denom) # 取对数避免连乘下溢 scores[c] = logp return max(scores, key=scores.get), scores print(nb_predict(np.array([1, 1])))参数说明:alpha=1.0就是标准拉普拉斯平滑。考试若追问 alpha 取 0 会怎样,答案是训练集中未出现的特征取值概率为 0,整个后验被一票否决。用对数相加代替概率连乘是工程写法,卷面上写乘积形式即可,但要补一句"实际计算取对数防止下溢"。
3.2 K-means:目标函数、迭代步骤与收敛性判断
K-means 的目标是最小化簇内平方和 J = Σ_k Σ_{x∈C_k} ‖x − μ_k‖²。迭代只有两步:把每个样本分配到最近的质心;把质心更新为所属样本的均值。考试常让手算两轮并比较 J 值变化,或者问"算法为什么一定收敛"。标准答案是两步都在单调不增 J,而样本的划分方案数量有限,因此有限步内必然停止——但它收敛到的是局部最优,不保证全局最优。
import numpy as np X = np.array([[1.0, 1.0], [1.5, 2.0], [3.0, 4.0], [5.0, 7.0], [3.5, 5.0], [4.5, 5.0], [3.5, 4.5]]) def kmeans(X, k=2, iters=10, seed=0): rng = np.random.default_rng(seed) centers = X[rng.choice(len(X), k, replace=False)].copy() for _ in range(iters): d = ((X[:, None, :] - centers[None, :, :]) ** 2).sum(-1) labels = d.argmin(1) # 分配步 new_centers = np.array([X[labels == i].mean(0) if (labels == i).any() else centers[i] for i in range(k)]) if np.allclose(new_centers, centers): # 质心不动即收敛 break centers = new_centers return centers, labels centers, labels = kmeans(X) print("质心:\n", centers) print("簇内平方和:", sum(((X[labels == i] - centers[i]) ** 2).sum() for i in range(2)))参数说明:随机初始化的种子影响最终结果。考试若问"两次运行结果不同怎么解释",答案就是目标函数非凸、初始点不同会落进不同的局部极小。工程上常用 k-means++ 初始化缓解,sklearn.cluster.KMeans的init="k-means++"是默认值。k 的选取常与肘部法一起考,指标是不同 k 下的簇内平方和曲线拐点。
3.3 EM 算法:E 步与 M 步在 GMM 里各做什么
EM 解决的是含隐变量的最大似然估计。E 步用当前参数算隐变量的后验 γ_ik = π_k·N(xᵢ | μ_k, Σ_k) / Σ_j π_j·N(xᵢ | μ_j, Σ_j);M 步用 γ 做加权更新 μ_k = Σᵢγ_ik·xᵢ / Σᵢγ_ik,π_k = Σᵢγ_ik / N,协方差同样按加权形式算。还需要能解释"为什么 EM 每轮似然不降"——它优化的是对数似然的下界(ELBO),每轮抬高下界,真实似然随之单调不减。
| 维度 | K-means | GMM |
|---|---|---|
| 隐变量 | 硬分配 0/1 | 软分配后验概率 |
| 簇形状 | 球形、等方差 | 任意椭球 |
| 优化方法 | 交替最小化 | EM |
| 输出 | 簇标签 | 均值、协方差、混合系数 |
| 初始敏感性 | 高 | 高,可多次重启 |
| 卷面考点 | 收敛性证明、手算迭代 | E/M 步公式、ELBO |
from sklearn.mixture import GaussianMixture import numpy as np X = np.array([[1.0, 1.0], [1.5, 2.0], [3.0, 4.0], [5.0, 7.0], [3.5, 5.0], [4.5, 5.0], [3.5, 4.5]]) gmm = GaussianMixture(n_components=2, covariance_type="full", random_state=0).fit(X) print("均值:\n", gmm.means_) print("混合系数:", gmm.weights_) print("软分配前两行:\n", gmm.predict_proba(X)[:2])参数说明:covariance_type决定协方差结构,full让每个簇有独立的完整协方差,diag只学对角。考试若问"GMM 和 K-means 的关系",答案是当各簇协方差趋于零且相等时,GMM 的软分配退化成硬分配的 K-means。predict_proba给出的正是 E 步的 γ,可以拿来和手算结果逐项对照。
4. 模型评估与调参:混淆矩阵、ROC-AUC、交叉验证的算式拆解
4.1 混淆矩阵到 Precision/Recall/F1,每一步都要写清分母
评估题的失分大多不在概念,而在分母写错。给定 TP、FP、FN、TN:精确率 P = TP/(TP + FP),召回率 R = TP/(TP + FN),F1 = 2PR/(P + R)。要记牢精确率的分母是"预测为正",召回率的分母是"真实为正",这两个方向弄反,后面几问连锁全错。
| 预测正 | 预测负 | 合计 | |
|---|---|---|---|
| 真实正 | TP = 40 | FN = 10 | 50 |
| 真实负 | FP = 20 | TN = 130 | 150 |
按上表算:P = 40/60 ≈ 0.667,R = 40/50 = 0.8,F1 = 2 × 0.667 × 0.8 / (0.667 + 0.8) ≈ 0.727,准确率 = 170/200 = 0.85。注意准确率高不代表模型可用,正负样本 1:3 时全部预测为负也能拿到 0.75 的准确率,所以卷面上经常要求同时给出 P、R、F1。
from sklearn.metrics import precision_recall_fscore_support, confusion_matrix import numpy as np y_true = np.array([1]*40 + [1]*10 + [0]*20 + [0]*130) # 1 为正类 y_pred = np.array([1]*40 + [0]*10 + [1]*20 + [0]*130) cm = confusion_matrix(y_true, y_pred) p, r, f1, _ = precision_recall_fscore_support(y_true, y_pred, average="binary") print(cm) print(f"P={p:.3f} R={r:.3f} F1={f1:.3f}")逻辑说明:confusion_matrix默认把较大的标签当作正类,标签是 0/1 时正类为 1,输出矩阵按 [[TN, FP], [FN, TP]] 排列,手算时务必先确认排列顺序,顺序写错等于整题作废。average="binary"表示只统计正类指标,多分类题要改成macro或weighted,并在解答里说明选了哪个以及为什么。
4.2 ROC 与 AUC:手算排序法和它的概率解释
ROC 曲线以 FPR = FP/(FP + TN) 为横轴、TPR = TP/(TP + FN) 为纵轴,遍历所有阈值画出。AUC 有两条理解路径:一是曲线下的面积,二是随机抽一个正样本和一个负样本、正样本得分更高的概率。手算时排序法更快——把样本按预测分数降序排列,AUC = 正样本排在负样本前面的对数 / (P × N),遇到同分按 0.5 计入。
from sklearn.metrics import roc_auc_score import numpy as np # 预测分数(不是标签),分数越高越倾向正类 scores = np.array([0.9, 0.8, 0.7, 0.4, 0.35, 0.3]) y = np.array([1, 1, 0, 1, 0, 0]) auc = roc_auc_score(y, scores) print(f"AUC={auc:.3f}") # 手算校验:逐对比较正负样本 pos, neg = scores[y == 1], scores[y == 0] wins = sum((p > n) + 0.5 * (p == n) for p in pos for n in neg) print("对数法:", wins / (len(pos) * len(neg)))参数说明:roc_auc_score的第二个参数必须是连续分数或概率,直接传 0/1 标签会退化成准确率的变体,AUC 就失去意义。手算部分用(p > n) + 0.5 * (p == n)处理同分,只有和库函数结果一致,才能说明排序法真学会了。当类别极不平衡时,卷面常要求比较 PR 曲线与 ROC,结论是 PR 曲线对少数类更敏感。
4.3 偏差方差、交叉验证与正则化:简答题的答题骨架
偏差-方差分解写成 E[(y − f̂)²] = 偏差² + 方差 + 噪声。作答时按三段推进:偏差描述模型假设与真实函数的偏离程度,方差描述模型对训练集扰动的敏感度;欠拟合对应高偏差,过拟合对应高方差;正则化通过限制参数范数降低方差、略微抬高偏差,从而压低总误差。岭回归惩罚 λ‖w‖²,Lasso 惩罚 λ‖w‖₁,后者能产生稀疏解,这一点常被追问。
交叉验证的作用是让评估不依赖单次划分,k 折把数据分成 k 份轮流做验证集再取平均。k 越大评估越稳但计算越贵,工程常用 5 或 10。卷面会问"留一法(k = N)的偏差和方差",标准答案是偏差小、方差大且计算代价最高。
from sklearn.linear_model import Ridge, Lasso from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.datasets import make_regression import numpy as np X, y = make_regression(n_samples=120, n_features=20, noise=8.0, random_state=0) for alpha in [0.01, 0.1, 1.0, 10.0]: # 正则化前必须标准化,否则惩罚强度被特征量纲带偏 pipe = make_pipeline(StandardScaler(), Ridge(alpha=alpha)) scores = cross_val_score(pipe, X, y, cv=5, scoring="neg_mean_squared_error") print(f"alpha={alpha:<5} CV MSE={-scores.mean():.2f}") lasso = make_pipeline(StandardScaler(), Lasso(alpha=0.1)).fit(X, y) print("Lasso 非零系数个数:", np.sum(lasso[-1].coef_ != 0))参数说明:alpha就是公式里的 λ,越大正则越强、参数越被压向零。把StandardScaler放进管道,是为了保证每一折内部只用训练部分算均值和方差,避免数据泄漏,手写交叉验证最容易在这里出错。neg_mean_squared_error取负号是因为 sklearn 统一按"越大越好"计分。看 Lasso 的非零系数个数是验证稀疏性的直接办法,20 个特征下如果全部非零,说明 alpha 取得太小。
5. 用 sklearn 把答案跑成自检脚本:错题复现与参数扰动
复习阶段性价比最高的动作,是把复习题里的数字塞进脚本跑一遍。手算和代码不一致的地方,八成是公式记错或符号方向反了,比反复看书有效得多。做法是每类题写一个函数,输入题目给的数字、输出答案,再和手算对照。
from sklearn.metrics import accuracy_score, f1_score from sklearn.linear_model import LogisticRegression from sklearn.model_selection import StratifiedKFold, cross_val_score import numpy as np def check_metric(y_true, y_pred): """把手算的指标和库函数对齐""" return {"acc": accuracy_score(y_true, y_pred), "f1": f1_score(y_true, y_pred)} # 同一份数据跑 10 次分层交叉验证,看指标波动 X = np.random.default_rng(1).normal(size=(200, 5)) y = (X[:, 0] + X[:, 1] > 0).astype(int) cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=0) scores = cross_val_score(LogisticRegression(max_iter=1000), X, y, cv=cv, scoring="f1") print(f"F1 均值 {scores.mean():.3f},标准差 {scores.std():.3f}") print("逐折:", np.round(scores, 3))StratifiedKFold保证每折的类别比例和整体一致,类别不平衡时比普通 KFold 稳;shuffle=True配合random_state让结果可复现。逐折分数散得开,说明模型对划分敏感,这时候单看某一次划分的分数没有意义,这个观察可以直接当作"为什么需要交叉验证"的论据。
参数扰动是另一条自检路线:把 C 从 0.01 扫到 100,记录训练集和验证集分数,训练分数高、验证分数低且差距大的区间就是过拟合区。判断标准很直接——验证分数随 C 增大先升后降,峰值处就是这道题要的答案。
| 自检对象 | 扰动参数 | 观察指标 | 对应卷面结论 |
|---|---|---|---|
| 逻辑回归正则 | C(0.01→100) | 训练/验证 F1 | 过拟合与欠拟合的分界 |
| 岭回归 | alpha(0.01→10) | 交叉验证 MSE | 偏差方差权衡 |
| GMM | n_components(2→5) | 对数似然、BIC | 模型选择 |
| K-means | k(2→8) | 簇内平方和 | 肘部法选 k |
扫参数时固定随机种子,否则同一组参数两次跑出不同分数,自检就失去参照。如果时间只够做一件事,优先把混淆矩阵和 AUC 这两类计算题写成脚本——它们手算时最容易因分母写反而整题作废,代码能在几秒内给出对照。把逐折分数和扰动曲线抄到复习笔记的边上,比只记一个平均值更能解释清楚模型稳不稳。
本文还有配套的精品资源,点击获取