“机器学习3-4章”,这个标题看着像是课程笔记的随手命名,但恰恰是不少同学在期末复习阶段最容易翻车的地方。如果你用的是周志华《机器学习》或李航《统计学习方法》这类常见教材,第3章基本就是线性模型那一整套东西,第4章则是决策树。这两章承上启下——前面承接第1、2章的模型评估与选择,后面又为集成学习、聚类等章节埋了伏笔。更直白点说,期末卷面上至少有20到30分直接来自这里,面试问基础模型时也绕不开。这篇我会把两章的关键推导、容易混淆的概念、刷题和写代码时常见的坑一次性捋清楚,适合正在期末复习、准备复试或者刚开始入手机器学习实战的读者。
1. 第3章核心:线性模型为什么能通吃回归和分类
很多人学线性模型时会觉得内容太简单,无非就是 y = wx + b。但事实上第3章是把“线性”这个最朴素假设玩出花的一章:线性回归解决连续值预测,对数几率回归(逻辑回归)解决二分类,线性判别分析(LDA)从另一个角度做分类,再加一个多分类学习。理解这一章的关键不在于背公式,而在于搞清楚每一类方法到底在优化什么目标。
1.1 线性回归的两种视角:最小二乘与极大似然
一元线性回归 f(x) = wx + b,目标是让预测值尽量接近真实值。教材上用的是均方误差作为性能度量,也就是:
E(w,b) = Σ(yi - wxi - b)²
为什么用均方误差?因为它在几何上对应欧氏距离,而且误差函数是凸函数,有闭式解。把 E 对 w 和 b 分别求偏导并令其等于零,就可以直接求出参数。这是最小二乘的视角,同时也是“几何视角”。
另一个容易被忽略的视角是“概率视角”:假设噪声服从均值为0的高斯分布,那么极大似然估计推导出来的结果和最小二乘完全一致。这个等价关系非常重要,因为后续很多模型(比如逻辑回归)都是走极大似然这条路,你能理解线性回归等价于高斯噪声下的极大似然估计,后面学广义线性模型就顺了。
多元线性回归的形式是 f(x) = wᵀx + b,把 b 吸收进 w 后变成 f(x) = ŵᵀx̂。闭式解 ŵ* = (XᵀX)⁻¹Xᵀy。这里有个高频考点:如果 XᵀX 不可逆怎么办?常见处理办法是引入正则化项,比如岭回归里加 λI,这就自然衔接到后面第6章的正则化话题。
code复制import numpy as np
# 多元线性回归闭式解示例
def linear_regression_closed_form(X, y):
# 加一列1,把偏置b吸收进w
X_b = np.c_[np.ones((X.shape[0], 1)), X]
# 闭式解:w = (X^T X)^(-1) X^T y
w = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
return w
实际写代码时我不会直接用 np.linalg.inv,而是用 np.linalg.pinv(伪逆),或者直接用 np.linalg.lstsq,因为当数据存在共线性或矩阵接近奇异时,直接求逆会得到很不稳定的数值结果。这也是“看起来很简单,一跑就出问题”的典型场景。
1.2 逻辑回归不是回归,是分类
对数几率回归的命名坑了无数初学者。它虽然叫回归,但干的是分类的事。核心思路是用线性模型的输出去逼近真实标记的对数几率:
ln(y/(1-y)) = wᵀx + b
解出来就是 y = 1 / (1 + e^(-(wᵀx+b))),也就是把线性输出经过 Sigmoid 函数映射到(0,1)区间,当作正例概率。
这里必须理解一个关键选择:为什么不直接用线性回归做分类?因为线性回归的输出范围没有限制,对于离群点极其敏感。比如一组样本的正例集中在某个区间,来了一个特别大的负例,线性回归的拟合直线会被拉偏,分类边界就废了。Sigmoid 把输出压缩到0到1之间,天然适合表示概率。
还有一个高频面试点:逻辑回归的损失函数为什么用交叉熵(对数似然)而不是均方误差?
如果逻辑回归用均方误差,损失函数关于参数 w 是非凸的,用梯度下降很容易陷入局部最优。而用交叉熵损失后,损失函数是凸函数,梯度下降能收敛到全局最优。再从概率角度看,逻辑回归本身就是对伯努利分布建模,交叉熵就是负对数似然,这是最自然的损失设计。
code复制def sigmoid(z):
return 1 / (1 + np.exp(-z))
def log_loss(y_true, y_pred):
# 加极小值防止log(0)
eps = 1e-15
y_pred = np.clip(y_pred, eps, 1 - eps)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
梯度推导是很多学校期末计算的标答区间。交叉熵损失对 w 求导后得到一个极其优雅的形式:
∂L/∂w = Xᵀ(sigmoid(Xw) - y) / m
这个式子的含义是:预测值与真实值的残差乘以特征值,再取平均。它和线性回归的梯度形式非常像,唯一区别在于预测值经过了 Sigmoid 映射。所以逻辑回归的代码实现并不复杂,手动写梯度下降时只要注意这个残差项就够了。
1.3 线性判别分析LDA与多分类扩展
LDA(Linear Discriminant Analysis)的思想也很直观:给定训练样本,设法将样例投影到一条直线上,使得同类样例的投影点尽可能接近、异类样例的投影点尽可能远离。对新样本分类时,看它投影到直线上的位置来判断类别。
LDA 的优化目标是最大化广义瑞利商:
J = wᵀSbw / wᵀSww
其中 Sb 是类间散度矩阵,Sw 是类内散度矩阵。解出来的 w = Sw⁻¹(μ0 - μ1)。这一步推导过程很多同学容易卡住,其实抓住“对 w 求导并令导数为零”这个核心步骤就行。
LDA 在期末题里常以计算题形式出现,给你两个类别的样本,要求计算类内散度、类间散度并求出最优投影方向。这种题本质就是矩阵运算,把 Sw、Sb 算清楚基本能拿满分。
多分类学习这边,教材讲了拆分策略:一对一(OvO)、一对其余(OvR)、多对多(MvM)。OvO 训练 N(N-1)/2 个分类器,OvR 训练 N 个分类器。考试常问“哪个更好”,其实没有绝对答案,OvO 每个分类器只用两类数据,训练开销小,但分类器数量多;OvR 分类器数量少,但每个分类器要面对不平衡问题。实际应用中还要考虑存储和预测时间,不能只看准确率。
这一节还有一个容易忽略的知识点:类别不平衡问题。比如正负例比例是 1:1000,直接训练出来的分类器会偏向多数类。常见处理手段是“再缩放”:预测值大于阈值时判为正例,阈值可以调整。过采样(如SMOTE)和欠采样也是工程上的常用做法。考试可能只考概念,但在真实数据集上跑实验时,不平衡问题不处理的话,准确率再高也可能是假的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第4章核心:决策树的生长与剪枝全流程
决策树是机器学习里最接近人类决策过程的模型。它天生具备可解释性,这也是它至今仍在金融风控、医疗诊断等领域被广泛使用的原因。学第4章时要有两条主线:一是树的生长逻辑,也就是从根节点开始怎么选特征、怎么划分数据;二是怎么防止过拟合,也就是剪枝。两条线交错在一起,才是完整的决策树。
2.1 三种划分准则:信息增益、增益率、基尼指数
决策树划分的核心是“让分支节点所包含的样本尽可能属于同一类别”,也就是纯度越来越高。教材给出了三种度量纯度的指标。
信息熵是基础概念,样本集合 D 的信息熵定义为:
Ent(D) = -Σ pk * log2(pk)
Ent(D) 越小,纯度越高。信息增益就是划分前后熵的减少量:
Gain(D,a) = Ent(D) - Σ |Dv|/|D| * Ent(Dv)
ID3 决策树就是选择信息增益最大的属性来划分。但信息增益有一个明显问题:它偏好取值数目多的属性。比如“编号”这个属性,每个样本一个值,划分后每个子节点纯度都是100%,信息增益最大,但这显然是过拟合的。这也是为什么 C4.5 用增益率来替代:
Gain_ratio(D,a) = Gain(D,a) / IV(a)
IV(a) = -Σ |Dv|/|D| * log2(|Dv|/|D|)
IV(a) 是属性 a 的固有值,取值数目越多,IV 越大。但增益率又有一个反向问题:它偏好取值数目少的属性。C4.5 不是直接选增益率最大的属性,而是用了一个启发式:先从候选属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。这个细节要是不注意,期末选择题很容易被绕进去。
CART 决策树用的是基尼指数:
Gini(D) = 1 - Σ pk²
基尼指数表示从数据集 D 中随机抽取两个样本,其类别标记不一致的概率。基尼指数越小,数据纯度越高。CART 在分类任务中选择基尼指数最小的属性作为划分属性。
三者的使用场景做个表对比:
| 决策树算法 | 划分准则 | 偏好倾向 | 备注 |
|---|---|---|---|
| ID3 | 信息增益 | 偏好取值多的属性 | 不能处理连续特征 |
| C4.5 | 增益率 | 偏好取值少的属性 | 需先选信息增益高于平均的属性 |
| CART | 基尼指数 | 偏好取值多的属性(类似) | 分类树用基尼指数,回归树用均方误差 |
注意CART的基尼指数是从信息熵的泰勒展开近似推出来的,计算时不需要对数运算,所以速度更快。sklearn 里的 DecisionTreeClassifier 默认就是 CART,用的 criterion='gini',你也可以改成 'entropy'。
2.2 剪枝处理:预剪枝vs后剪枝
决策树过拟合几乎是必然的,如果不限制生长,它能把训练集每个样本都分对。剪枝是决策树对抗过拟合的主要手段,也是期末考试特别喜欢出大题的环节。
预剪枝在决策树生成过程中,对每个节点在划分前先进行估计,若当前节点的划分不能带来泛化性能提升(比如在验证集上准确率没有提高),就停止划分并将当前节点标记为叶节点。优点是训练时间短,但本质是“贪心”,有可能因为当前划分收益不大而停止,导致后续本来可以带来更大收益的划分也无法进行,造成欠拟合。
后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上对非叶节点进行考察,若将该节点对应的子树替换为叶节点能提升泛化性能,就替换。后剪枝的欠拟合风险小,泛化性能往往优于预剪枝,但训练时间开销大得多。
考试计算题常给你一棵生成好的树和验证集,让你判断每个内部节点是否应该被剪掉。这种题只要严格遵循“替换后验证集准确率是否提高”来判断,就不会错。这里还要注意一个细节:预剪枝的“验证集准确率”是基于当前节点划分后所有叶节点的预测结果计算出来的,不是只看该节点本身。
我在实际项目里通常用后剪枝或者限制树的生长参数(max_depth、min_samples_split),很少单纯依赖预剪枝。原因是预剪枝的阈值不好调,调严了欠拟合,调松了没效果,而后剪枝虽然慢一点,但结果更可靠。sklearn 中虽然没有直接暴露后剪枝的接口,但可以通过 cost_complexity_pruning 实现代价复杂度剪枝,这个其实就是 CART 里的 CCP 方法。
2.3 连续值与缺失值的处理逻辑
现实数据基本都有连续特征,但决策树的划分准则针对的是离散值。C4.5 的解决方案是二分法:将连续特征 a 的取值排序,然后取相邻值的中点作为候选划分点,对每个候选点计算增益(或增益率),选最优的那个点作为划分阈值。
这里需要注意:连续特征在当前节点参与划分后,后续子节点还能继续使用这个特征进行划分。比如对“年龄”选了一个阈值 30,左子节点还可以继续用“年龄”按另一个阈值划分。这与离散特征的划分方式不同,离散特征一旦在祖先节点被用作划分属性,子孙节点通常不再重复使用该属性(除非是多变量决策树)。
考试和作业里,计算连续属性的划分点时,要先把所有取值排序,计算相邻中点,对每个中点计算信息增益,取增益最大的中点和它对应的增益值。这个计算量虽然不大,但很容易因为排序或中点算错导致全盘皆输,建议每一步都做表格记录。
缺失值处理则涉及两个方面:一是属性缺失时如何进行划分选择,二是选定划分属性后,样本在该属性上缺失时该被分到哪个子节点。
C4.5 的做法是:仅通过无缺失值的样本子集来计算信息增益,然后乘以一个“无缺失值样本占比”的权重。对后一个问题,做法是把缺失样本按权重分配到所有子节点中,权重由各子节点的样本占比决定。这个机制对初学者来说比较难理解,可以类比成“这个样本虽然不知道属性值,但可以根据其他样本在子节点中的分布概率,同时参与多个分支的计算”。
sklearn 的 DecisionTreeClassifier 支持处理缺失值吗?严格来说,sklearn 的实现不允许特征矩阵里有 NaN,需要提前做填充或者使用专门支持缺失值处理的库(比如 HistGradientBoostingClassifier)。这一点和课本上的 C4.5 处理机制并不完全一致,写作业、跑比赛时要特别注意。
3. 把第3、4章串成一条期末复习主线
不少同学学完这两章会觉得内容是散的。线性模型是一套思路,决策树又是另一套思路,怎么复习才能不混乱?我的经验是抓两条轴。
3.1 模型维度上的核心考点盘点
从模型维度来看,线性模型的核心知识点是“损失函数 + 优化方式 + 输出映射”。线性回归的损失是均方误差、输出是实值;逻辑回归的损失是交叉熵、输出经过 Sigmoid 变成概率;LDA 则不直接建模条件概率,而是找投影方向。决策树的核心知识点则是“划分准则 + 剪枝策略 + 缺失值处理”。
期末复习时,可以按以下清单自查:
- 会不会推导线性回归的闭式解,能不能讲清楚 XᵀX 不可逆的应对方案
- 知不知道逻辑回归的损失函数为什么不能换回均方误差,能不能写出梯度表达式
- 能不能手算信息增益、增益率、基尼指数
- 能不能说清 ID3、C4.5、CART 三者的区别和适用场景
- 会不会用验证集判断预剪枝和后剪枝的节点是否应该保留
- 知不知道连续特征在决策树中为什么要排序后取相邻中点,缺失样本如何分配
这个清单如果有一半以上答不上来,说明还有盲区,赶紧回去翻书对应章节的重推导。
3.2 代码复现与头歌实训对照
复习期间,动手写代码比只看书有用得多。国内很多高校的机器学习课会配套“头歌”实训平台,里面线性回归、逻辑回归、决策树相关实训题目和期末题型高度接近。我的建议是不要对着答案填空,而是把核心代码模块自己重写一遍,然后和平台答案做对比。
逻辑回归的核心只有四步:初始化参数、计算预测概率、计算梯度、更新参数。决策树的核心难点则在递归建树时的停止条件和特征选择函数。比如我自己写决策树代码时,最常出错的地方是递归出口:
- 当前节点的样本都属于同一类别,直接返回叶节点
- 属性集为空或者所有样本在所有属性上取值相同,返回样本数最多的类别
- 某个子节点样本为空,把父节点的样本最多的类别赋给该叶节点
这三个条件缺一个,树的生长就会出问题。
code复制class DecisionNode:
def __init__(self, feature=None, threshold=None, left=None, right=None, value=None):
self.feature = feature # 划分特征索引
self.threshold = threshold # 划分阈值(连续特征)
self.left = left
self.right = right
self.value = value # 叶节点的类别
def build_tree(X, y, features, depth=0, max_depth=5):
# 条件1:样本全属于同一类
if len(set(y)) == 1:
return DecisionNode(value=y[0])
# 条件2:特征用尽或达到最大深度
if not features or depth >= max_depth:
return DecisionNode(value=most_common(y))
# 选择最优划分特征(这里省略CART准则的计算)
best_feature, best_threshold = choose_best_split(X, y, features)
# 条件3:无法继续划分
if best_feature is None:
return DecisionNode(value=most_common(y))
# 按阈值切分数据,递归建树
left_idx = X[:, best_feature] <= best_threshold
right_idx = X[:, best_feature] > best_threshold
if not any(left_idx) or not any(right_idx):
return DecisionNode(value=most_common(y))
left = build_tree(X[left_idx], y[left_idx], features, depth+1, max_depth)
right = build_tree(X[right_idx], y[right_idx], features, depth+1, max_depth)
return DecisionNode(best_feature, best_threshold, left, right)
这段代码只是展示了树的递归骨架,实际使用时你还要额外实现 choose_best_split,计算每个特征每个阈值下的基尼指数并找到最小划分点。你在头歌平台做决策树实训时如果卡住了,多半是递归出口的顺序写得不对,而不是划分准则本身算错了。
3.3 从期末题到真实项目的迁移
期末考试和真实项目之间的差距在哪里?考试题通常给的是干净的小数据集,手算能完成;真实项目里数据又脏又大,算法性能受特征工程和数据质量的影响比受模型选择的影响更大。
比如掌握线性模型后,你可以用逻辑回归做风控评分卡,每个特征的系数 w 直接可以换算成评分;掌握决策树后,你可以用树模型输出特征重要性,做特征筛选。很多 Kaggle 比赛的 baseline 就是逻辑回归和决策树/随机森林,不是因为它效果最好,而是因为它稳定、解释性强,能提供一个可靠的参照系。
从项目角度看,第3、4章教你的是“如何根据任务类型选择第一个模型”:回归任务先试线性回归,分二分类任务先试逻辑回归,数据有大量离散特征且对可解释性有要求时先试决策树。之后再根据验证集结果逐步尝试更复杂的模型。
4. 常见卡壳点与避坑经验
这部分是实操中的“干货区”。我梳理了学生提问最多、作业错误率最高、期末最容易失分的几个点,一个个说清楚。
4.1 关于逻辑回归,最常见的理解误区
第一个误区是认为逻辑回归的输出就是概率。严格来说,Sigmoid 的输出可以被解释为概率,但这需要满足一定的条件假设(比如模型校准良好)。实际中,预测结果的概率值不能直接当作真实概率使用,尤其在样本不平衡的场景下,输出概率会偏向多数类。
第二个误区是初始化参数全为0会导致无法训练。逻辑回归不一样,参数全为0依然可以正常迭代,因为它的梯度不会因为对称性而消失。这个问题在神经网络里才会出现,很多同学把不同模型的结论混在一起记忆,考试时容易被选择题带偏。
第三个误区是拿准确率衡量不平衡数据下的逻辑回归模型。一个99%都是负例的数据集,模型全预测为负就能有99%的准确率,但这显然没有用处。正确做法是看 AUC、F1、召回率等指标。我记得一次练习赛里,用逻辑回归跑不平衡数据,准确率一度达到98%,但召回率只有12%,这就是典型的失效模型。
4.2 决策树实现与调参的实操心得
用 sklearn 做决策树时,criterion、max_depth、min_samples_split、min_samples_leaf 这几个参数要优先调。按我的习惯,先限制 max_depth 在3到7之间,再设 min_samples_leaf 至少5,避免叶节点样本太少带来过拟合。
剪枝参数的调整逻辑和课本上的“预剪枝/后剪枝”可以对照理解:max_depth、min_samples_split 这些前置限制就类似预剪枝;树生长完之后,再通过 cost_complexity_pruning 做后剪枝,sklearn 里的实现需要先拿 tree_.ccp_alpha 获取候选值,然后用网格搜索选最优 alpha。
顺手给一个 sklearn 代价复杂度剪枝的参考代码:
python复制from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
# 假设 X, y 已经准备好
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
clf = DecisionTreeClassifier(random_state=42)
path = clf.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas, impurities = path.ccp_alphas, path.impurities
# 去掉最后一个无效alpha
clfs = []
for ccp_alpha in ccp_alphas[:-1]:
clf = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp_alpha)
clf.fit(X_train, y_train)
clfs.append(clf)
# 然后根据验证集效果选最优alpha
这里要注意:ccp_alphas 是从大到小排列的,alpha 越大剪枝越狠,树越简单。选择最优 alpha 时不要用训练集评估,要用验证集,否则选出来的树基本不剪枝,过拟合依旧。我见过很多新手在这里直接把 clf.score(X_train, y_train) 当作选择依据,结果剪了个寂寞。
4.3 手算题的失分点总结
期末手算题里,最容易失分的不是公式记不住,而是以下这些细节:
- 算信息熵时,pk 用的频率到底是“该类别样本数除以总样本数”,有人会忘记取对数时底数是2
- 划分后子节点的熵要按“样本占比加权求和”,不是直接求平均
- 计算信息增益时,父节点的熵必须用划分前的整个数据集计算,不能用某个子集代替
- 增益率公式中 IV(a) 的分母是属性取值的熵,这个值和分类纯度无关,只和属性的取值分布有关,所以会偏好取值少的属性
- 连续属性的信息增益计算完之后,还要注意同一个特征在后续子节点仍可参与划分,这和离散特征不同
还有一点,CART 的基尼指数在选择属性时,是选择“划分后基尼指数最小”的属性,不是选择“基尼指数减少量最大”的属性,虽然从效果看两者很接近,但严格按教材定义,CART 是直接最小化划分后的加权基尼指数。做题时如果题目特意区分这两个表述,请以教材为准。
4.4 考试与实战中的时间管理建议
课程期末复习阶段,不建议一上来就抱着整本书从头看到尾。第3、4章内容多且计算密集,比较合理的节奏是:
- 第一天:重读教材第3章,重点做线性回归、逻辑回归的公式推导,再刷头歌线性回归和逻辑回归两个实训单元
- 第二天:重读教材第4章,手算至少一棵树的信息增益和剪枝判断,然后在 sklearn 上跑一遍决策树的可视化
- 第三天:把两章公式抄成一张A4纸,不看笔记重做一遍错题,重点检查容易混淆的概念
- 第四天:找历年真题或配套习题集做限时训练,检验复习效果
我见过不少同学在逻辑回归的梯度推导上耽误太久,其实期末计算题很少让你推完整梯度,更重要的是“写得出损失函数”和“判断用哪个损失函数”。真正需要死磕公式的,是把目标函数列出来求导并令其为零的闭式解推导,以及决策树的熵和增益计算。分清楚“需要精算”和“只需要理解”的内容,复习效率会高很多。
5. 这两章在机器学习知识体系中的位置
学完第3、4章之后,可以站在更高维度看一眼它们和后续章节的关系。这样做的好处是,期末考的综合题往往不会只考单章内容,而是把模型评估、线性模型、决策树串在一起出题。
第3章学到的“线性假设 + 概率解释”是第6章支持向量机、第7章贝叶斯分类器的思想源头之一。SVM 可以看作在特征空间找一个最大间隔超平面,逻辑回归则是在找决策边界的同时输出概率;第4章决策树的划分准则和剪枝思想,直接对应第8章集成学习里的随机森林和梯度提升树。随机森林本质上是多棵决策树的集成,而 XGBoost、LightGBM 这些工业界常用模型,也大量借用了 CART 树的结构和分裂增益计算公式。
所以,第3、4章没学透,后面集成学习基本寸步难行。不少同学到了第8章问“为什么 XGBoost 用二阶导、怎么处理缺失值”,答案根源都在第4章的决策树机制里。
交叉学科视角也值得一提。机器学习不是纯数学,也不是纯编程,而是统计学、数据库、算法三者的交叉。线性回归、逻辑回归本身是统计学里的经典方法,决策树的划分准则和概率估计也离不开统计量。而到了实际落地阶段,数据从数据库里取出来怎么清洗、怎么变换、怎么存储,又和数据库技术强相关。学第3、4章时,如果只盯着公式不放,忽略它们和统计推断、数据工程的联系,做真实项目时会感觉处处是瓶颈。
第3章和第4章在我的经验里是最值得花时间“手推+手写”的两章。线性模型的闭式解和梯度下降帮我建立了对损失函数的基本直觉,决策树的信息增益和剪枝让我第一次理解什么叫过拟合、什么叫泛化。期末复习时把这两章的每个公式都亲手推导一遍,比任何“速成笔记”都更能巩固长期记忆。后面你无论是去做深度学习还是转行搞数据挖掘,这些基础都会反复回来找你的。
最后分享一个小技巧:每次学完一章,试着不看教材,用大白话把这一章的核心技术讲给朋友听。比如“逻辑回归就是先用线性公式算一个分数,再通过Sigmoid把它压成0到1之间的概率,用交叉熵来衡量预测和真实之间的差距,然后让这个差距越小越好”——如果能讲通顺,说明你是真懂了。讲不出来或者讲着讲着自己开始怀疑的地方,就是需要回炉重看的位置。这个方法我用了很多年,帮自己查漏补缺,带人复习时也屡试不爽。
