ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器学习入门知识骨架与复习清单:从监督学习到神经网络

机器学习入门知识骨架与复习清单:从监督学习到神经网络

简介:围绕斯坦福大学吴恩达机器学习课程整理的学习包,面向希望系统入门并夯实机器学习基础的高校学生、数据科学从业者与自学者。内容覆盖监督与无监督学习、线性回归、逻辑回归、神经网络与反向传播、支持向量机、决策树与随机森林、聚类、降维,以及梯度下降、正则化、交叉验证和模型评估等核心主题,可用于课程复盘、算法理解与实践准备。压缩包共130个文件,约17.21MB,以PDF讲义和阅读材料为主,辅以JPG/GIF图示、TXT说明、ZIP子包及HTML/CSS页面文件,便于按知识点查阅与离线学习。目前已有400人学习下载,适合作为课程配套资料,帮助读者梳理算法脉络、理解关键概念并积累实践参考。

1. 一张图看懂机器学习这门课的知识密度

机器学习入门课的第一印象往往是“算法很多、公式很密”,真正学完一遍再回头看,骨架其实很清楚:一条主线、两套评估、三类场景。主线是监督学习从线性回归一路推到逻辑回归和神经网络;两套评估是训练误差与泛化误差的区分,以及由此长出来的偏差与方差诊断;三类场景按数据形态分,有标签、无标签、序列决策。几乎所有公式都挂在这根骨架上,知道自己在哪个位置,推导就不会迷路。适合已经听过一遍课、手里有笔记却串不起来的人,也适合正准备复习、想把散点连成面的人。

2. 监督学习主线:从线性回归到逻辑回归的推导要点

监督学习是整个课程的重量所在,从最简单的单变量线性回归开始,一路推到多分类。这条线上的每一步推导都不是孤立的技术点,而是前一步暴露问题后的补丁。复习时如果只背公式,很容易出现“每个公式都认识、连起来不知道在干嘛”的状态;把每一步的动机记住,公式反而不用死记。

2.1 假设函数、代价函数与梯度下降三件套

任何监督学习模型都可以拆成三件套:假设函数描述模型长什么样,代价函数描述“错得有多离谱”,优化算法描述怎么把错降到最低。线性回归的假设函数是特征的线性组合,代价函数是所有样本预测误差的平方和再取平均,优化方法是梯度下降。这三件套换个模型就换一套皮,但骨架不变,后面逻辑回归、神经网络都是在这个框架里换假设函数和代价函数。

线性回归的代价函数写成平方误差的均值,前面乘 1/2 是为了求导时把平方的 2 消掉,让梯度表达式干净。这个 1/2 不影响最优解的位置,只影响代价函数的具体数值,所以不要拿 J 的绝对大小去比较两个不同定义的模型。

import numpy as np def compute_cost(X, y, theta): # X: (m, n+1),第一列全是 1,对应偏置项 theta[0] # y: (m,),theta: (n+1,) m = len(y) residual = X.dot(theta) - y # 预测值减真实值 J = (1.0 / (2 * m)) * residual.dot(residual) return J def gradient_descent(X, y, theta, alpha, iterations): m = len(y) J_history = [] for _ in range(iterations): residual = X.dot(theta) - y # 对 theta_j 求偏导,统一写成矩阵形式 gradient = (1.0 / m) * X.T.dot(residual) theta = theta - alpha * gradient J_history.append(compute_cost(X, y, theta)) return theta, J_history

上面两段代码对应课程里最基础的两步:算代价、走一步梯度。参数说明上,alpha是学习率,决定每步迈多大;iterations是迭代轮数,不是收敛判据,真正判断收敛要看J_history是否还在下降。矩阵写法里X.T.dot(residual)一次算出所有参数的偏导,比逐参数循环更贴近实际实现,也能避免维度写错。

梯度下降的更新是同步更新:所有参数用同一轮的旧值算完梯度后再一起改,不能改完一个立刻用新值算下一个。这个细节在单变量时看不出问题,变量一多就会导致结果对不上,属于典型的手写实现踩坑点。

正规方程是另一条路,直接解出解析解,不需要选学习率、不需要迭代。代价是要算矩阵逆,特征数量大时计算量按三次方增长;特征存在线性相关时矩阵不可逆,这时要么删冗余特征,要么用正则化版本。课程里给的判断是特征数在一万以内可以考虑正规方程,再多就老实上梯度下降。

2.2 特征缩放与学习率:收敛的玄学部分

特征尺度差异大是梯度下降收敛慢最常见的原因。一个特征取值在 0 到 1 之间,另一个在 0 到 100000 之间,代价函数的等高线会被拉成极扁的椭圆,梯度下降会在狭长方向来回震荡,看起来像不收敛。特征缩放把每个特征拉到大致相同的量级,等高线变圆,下降路径短很多。

常用的做法是均值归一化,把特征减均值再除以标准差或极差。目标不是严格落在 0 到 1,而是大致落在负一到一附近,量级差别不大就行。复习时容易忽略的一点是:缩放参数必须只用训练集算出来,然后原样应用到验证集和测试集,否则会引入数据泄漏,验证结果会虚高。

学习率的调试有个朴素方法:固定迭代次数,画代价函数随迭代次数的曲线。曲线持续下降但很缓,说明学习率偏小;曲线上下震荡甚至上升,说明学习率偏大,需要往下调。常见的尝试顺序是按三倍或十倍量级跳,比如 0.001、0.003、0.01、0.03、0.1,找到能稳定下降的最大值附近。

代价函数曲线在若干次迭代后变平,不等于模型已经训好,只说明在当前学习率下参数更新幅度变小了。想确认是否真的收敛,要看梯度范数是否接近零,而不是只看 J 的绝对值小不小。这个区别在后面判断欠拟合和过拟合时还会用到。

2.3 逻辑回归:从决策边界到代价函数为什么不能平方

逻辑回归虽然名字里带回归,做的是分类。假设函数在线性组合外面套一个 sigmoid 函数,把输出压到 0 到 1 之间,解释成“属于正类的概率”。阈值 0.5 只是默认值,不是模型的一部分,调整阈值改变的是精确率和召回率的平衡,不是模型本身。

决策边界是假设函数等于 0.5 时对应的那條线,由参数决定,和训练数据的分布没有直接关系。数据集里两类点分得开不开,决定的是能不能找到好的参数;边界画在哪里,是参数说了算。复习时把这两件事混在一起,就会误以为数据线性不可分时逻辑回归一定失效。

逻辑回归的代价函数用对数损失,而不是平方损失。原因不是平方损失不能算,而是平方损失套上 sigmoid 之后关于参数是非凸的,梯度下降容易停在局部极小。对数损失保证了代价函数是凸的,梯度下降能走到全局最优。这个动机比公式本身更值得记。

def sigmoid(z): # 防止 exp 溢出,对极端值做截断是工程上的常规做法 return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500))) def logistic_cost(X, y, theta): m = len(y) h = sigmoid(X.dot(theta)) # 加极小值避免 log(0) eps = 1e-12 J = -(1.0 / m) * (y.dot(np.log(h + eps)) + (1 - y).dot(np.log(1 - h + eps))) return J

对数损失的梯度形式和线性回归长得几乎一样,都是“预测减真实,再乘特征”,区别只在预测值经过了 sigmoid。这个巧合让很多人误以为两者可以互换,实际上代价函数不同,最优解的含义也不同。逻辑回归输出的是概率,线性回归输出的是连续值,混用会让评估指标失去意义。

多分类用一对多策略:为每个类别训练一个二分类器,预测时取输出概率最大的那个类别。这个方法简单、可并行,类别数不多时够用。类别之间互斥且数量大时,softmax 更合适,但它已经不是基础课程的主线内容,复习时知道有这个替代方案即可。

3. 模型评估与正则化:偏差方差、学习曲线怎么读

模型训出来能拟合训练集,不代表能用。评估这一块解决的就是“怎么知道模型在新数据上还行”,以及“效果不好时该往哪个方向调”。这部分内容在课程里篇幅不算最大,但对实际项目的影响最大,复习时值得单独拎出来过一遍。

3.1 训练集/验证集/测试集与诊断流程

数据划分的常见比例是 60/20/20,数据量大时验证集和测试集的比例可以更小。验证集用来选超参数、选模型结构、选正则化强度;测试集只在最后用一次,用来估计泛化误差。用测试集反复调参,等于把测试集变成了验证集,报出来的数字会偏乐观,这是最典型的评估污染。

诊断流程有个固定顺序:先实现一个能跑通的简单版本,哪怕效果一般;再画学习曲线判断是偏差问题还是方差问题;最后按判断结果决定加特征、加数据还是调正则化。跳过第一步直接上复杂模型,往往连问题出在哪都说不清。

误差分析是另一个容易被跳过的步骤。人工看一遍被分错的样本,统计错误类型分布,往往能发现某几类样本占了错误的大头,针对性补数据比盲目调模型有效得多。课程里给的建议是先做快速实现、再做误差分析、最后才优化,顺序反了会浪费大量时间。

3.2 偏差与方差:学习曲线告诉你的四件事

高偏差对应欠拟合,训练误差和验证误差都高,且两者很接近;高方差对应过拟合,训练误差低、验证误差明显高,两者之间有明显间隙。这两个判断不看绝对数值,看的是两条误差曲线的相对位置和随样本量变化的趋势。

学习曲线横轴是训练样本数,纵轴是误差。高偏差时,增加训练样本对验证误差的改善很有限,曲线很快变平;高方差时,增加训练样本能让验证误差持续下降,两条曲线逐渐靠近。这个区别直接决定了对策:高偏差加样本没用,得加特征或换更复杂的模型;高方差加样本有用,或者减特征、加正则化。

症状训练误差验证误差两者差距优先对策
高偏差高高小加特征、加多项式项、减小正则化
高方差低高大加样本、减特征、增大正则化
刚好低低小保持,关注数据质量

这张表的用法不是对号入座就完事,而是先定位症状再选对策。现实里更常见的是两者兼有:验证误差高,同时训练误差也没低到哪去,这时要先降偏差,把模型能力提上来,再处理方差。顺序搞反,会在一个本身欠拟合的模型上拼命加正则化,越调越差。

提示:判断偏差方差之前,先确认训练误差的计算方式和验证误差一致,比如是否都包含正则化项、是否都做了同样的预处理。口径不一致时,两条曲线没有可比性。

3.3 正则化与正则化参数的选择

正则化的作用是把参数压小,降低模型对个别特征的敏感度,从而缓解过拟合。线性回归里加参数平方和,逻辑回归里同样加,神经网络里每一层的权重矩阵都加。正则化项不含偏置项,因为偏置只控制整体平移,不影响模型的复杂度。

正则化强度由参数控制,太大时所有参数被压到接近零,模型退化成常数预测,训练误差和验证误差都高,表现成欠拟合;太小时起不到约束作用,过拟合照旧。选择的方法是在验证集上扫一组候选值,取验证误差最低的那个,不要用测试集选。

def regularized_cost(X, y, theta, lam): m = len(y) # theta[0] 是偏置项,不参与正则化 reg = (lam / (2.0 * m)) * theta[1:].dot(theta[1:]) return compute_cost(X, y, theta) + reg def regularized_gradient(X, y, theta, lam): m = len(y) residual = X.dot(theta) - y grad = (1.0 / m) * X.T.dot(residual) # 只对非偏置项加惩罚,偏置项梯度保持原样 grad[1:] += (lam / m) * theta[1:] return grad

代码里两个细节值得注意:正则化项除以样本数 m,是为了让惩罚强度不随样本量剧烈变化;偏置项单独排除,是因为把它也压小只会让模型整体偏移,不解决过拟合。这两点在手写实现时最容易被漏掉,漏了之后表现是“正则化加了但没效果”或者“模型怎么调都欠拟合”。

正则化和特征缩放是绑定的。特征量级差异大时,同样的正则化强度对不同特征的实际惩罚不一样,大尺度特征的参数被压得相对更狠,等于给特征加了隐式权重。所以做正则化之前先把特征缩放到同一量级,两者要一起做。

4. 神经网络与无监督学习:从反向传播到 K-means

监督学习的主线推到神经网络,本质上还是三件套的延伸,只是假设函数变成了多层复合,代价函数依然是对数损失,优化依然靠梯度下降。无监督学习换了问题设定:没有标签,目标变成找结构。这两块内容在课程里位置靠后,但复习时不能只记结论,尤其是反向传播的维度关系和 K-means 的初始化,出错概率很高。

4.1 神经网络结构、前向传播与反向传播的维度对齐

神经网络的结构由层数和每层单元数决定。输入层单元数等于特征维度,输出层单元数等于类别数,隐藏层数量和每层单元数是超参数。前向传播就是逐层做线性组合加激活函数,把上一层的输出当作下一层的输入,直到输出层给出预测。

def forward(X, Thetas): # Thetas: 每层的权重矩阵列表,形状 (out_dim, in_dim+1) activations = [X] a = X for i, theta in enumerate(Thetas): a = np.hstack([np.ones((a.shape[0], 1)), a]) # 补一列偏置 z = a.dot(theta.T) a = sigmoid(z) if i < len(Thetas) - 1 else sigmoid(z) activations.append(a) return activations

前向传播写出来不长,但维度是最容易翻车的地方。权重矩阵的形状是“输出单元数 × 输入单元数加一”,转置之后才能和补了偏置的激活值相乘。把形状记成“行是输出、列是输入”,比记公式更不容易错。

反向传播的核心是误差项的递推:输出层的误差是预测减真实,隐藏层的误差由后一层的误差乘权重再乘激活函数的导数得到。每一层的误差项维度必须和该层激活值维度一致,这个一致性检查能在写代码前就抓出大部分维度错误。

注意:反向传播的公式推导可以慢慢补,但维度对齐必须先对。维度不对时,很多时候不会报错,只会算出看似合理但完全错误的结果,属于最难排查的一类问题。

4.2 训练神经网络的工程细节:初始化、梯度检验、随机失活

参数初始化不能全设成零。全零时同一层的所有单元在前向传播中输出相同,反向传播中梯度也相同,训练多少轮都保持对称,等于这一层只起了一个单元的作用。正确做法是随机初始化,把权重取成接近零的小随机数,打破对称性。

梯度检验用来验证反向传播实现是否正确。做法是用数值方法近似计算梯度,和解析梯度比较相对误差。相对误差在十的负七次方量级以下说明实现基本正确,负五次方以上就要怀疑有 bug。关键是梯度检验只在调试时开,正式训练必须关掉,否则每一轮都要多算两倍的前向传播,慢得没法用。

def numerical_gradient(cost_fn, params, eps=1e-4): # 只用于调试,正式训练关闭 grad = np.zeros_like(params) for i in range(len(params)): plus = params.copy(); plus[i] += eps minus = params.copy(); minus[i] -= eps grad[i] = (cost_fn(plus) - cost_fn(minus)) / (2 * eps) return grad def relative_error(g1, g2): return np.linalg.norm(g1 - g2) / (np.linalg.norm(g1) + np.linalg.norm(g2) + 1e-12)

这段代码的意思是逐参数扰动,用中心差分近似偏导。参数多时很慢,所以实际只在小型网络上验证,或者只抽一部分参数检查。检验之前记得把正则化项也算进代价函数,否则数值梯度和解析梯度的口径不一致,会误判成实现有错。

随机失活在训练时按概率随机把一部分隐藏单元的输出置零,相当于每次迭代训练一个不同的子网络,推理时用完整网络并做相应缩放。它的效果类似集成学习,能缓解过拟合。训练和推理的行为不一致是它的特点,不是 bug;忘记在推理时关掉失活才是 bug。

训练集要打乱。按类别顺序喂数据会让梯度方向在批次之间剧烈摆动,收敛变慢甚至震荡。打乱之后每个批次的分布更接近整体分布,梯度估计更稳。这个操作成本很低,但漏掉的人不少。

4.3 无监督学习:K-means、PCA 与异常检测的边界

K-means 是迭代两步:先把每个样本分给最近的簇中心,再用每簇样本的均值更新中心。两步交替直到分配不再变化。它对初始化敏感,随机选点可能收敛到局部最优,常规做法是多次随机初始化,取代价函数最小的那次结果。

簇数 K 的选择没有标准答案。肘部法看代价函数随 K 变化的拐点,拐点不明显时这个方法就失效。更实际的做法是结合业务含义定 K,或者用轮廓系数之类的指标辅助。复习时不要把肘部法当成通用解法,它只是一个启发式手段。

PCA 做的是降维,先对数据做均值归一化,再求协方差矩阵的特征向量,取前若干个主成分做投影。主成分数量可以按保留方差的比例来定,常见目标是保留 99% 的方差。PCA 的前提是数据已经缩放,否则量级大的特征会主导主成分方向,降维结果没有意义。

异常检测和高斯分布绑定:对每个特征估计均值和方差,算样本的概率密度,低于阈值判为异常。阈值用验证集上的 F1 分数来选,不要拍脑袋定。数据严重偏斜时,准确率完全不能反映效果,必须看精确率和召回率。

4.4 推荐系统与大规模数据的一点常识

推荐系统用协同过滤:从用户对物品的评分矩阵出发,学习用户偏好向量和物品特征向量,用两者内积预测缺失评分。冷启动用户没有任何评分时,用均值归一化把该用户的预测拉回全局均值附近,避免预测出无意义的零分。

大规模数据的训练策略是换优化方法。批量梯度下降每轮要扫全部样本,数据量大时一轮都跑不完;随机梯度下降每次只用一个样本,速度快但梯度噪声大;小批量梯度下降折中,是实际最常用的做法。在线学习进一步把模型做成持续更新,适合数据流式到达的场景。

提示:随机梯度下降的学习率通常要随迭代衰减,否则后期会在最优点附近持续震荡。衰减策略没有统一最优解,常见做法是按轮数线性或指数衰减。

5. 易错点清单:五个最容易翻车的地方

这一章单独拎出来,是因为下面这几条不是知识点不会,而是会了也照样错。它们共同的特征是“看起来对、跑起来也不报错”,只能靠流程约束避免。

5.1 把代价函数收敛当成模型变好

易漏点:看到代价函数曲线变平就认为训练完成、模型可用。

为什么漏:代价函数只反映训练集上的拟合程度,它在下降说明优化在推进,不说明泛化能力在提升。过拟合时训练代价可以降得很低,验证误差却在上升。

怎么补:每次看训练曲线,同时看验证误差曲线,两条一起判断。训练代价下降而验证误差抬头,就是过拟合的信号,该停下来调正则化或减特征了。

5.2 用测试集调超参数

易漏点:反复在测试集上试不同配置,取测试分数最高的那个。

为什么漏:这样操作时,测试集事实上承担了验证集的角色,报出来的分数是对“测试集加调参过程”的乐观估计,换一批数据就掉下来。

怎么补:把数据切成三份,调参只看验证集,测试集只在最终确定方案后跑一次。如果数据量实在小,用交叉验证在训练集内部做选择,测试集依然只碰一次。

5.3 正则化漏掉偏置项或漏做特征缩放

易漏点:正则化项把偏置项也算进去,或者特征没缩放就直接加正则化。

为什么漏:代码里直接对整个参数向量求平方和,写起来最省事;特征缩放和正则化在课程里是两节,复习时容易当成两个独立知识点。

怎么补:正则化只作用于非偏置参数,实现时显式排除下标为零的项;做正则化之前先完成特征缩放,两者绑定执行,形成固定顺序。

5.4 逻辑回归的阈值和类别不平衡混为一谈

易漏点:类别比例悬殊时,仍然用准确率和 0.5 阈值评估逻辑回归。

为什么漏:准确率直观、好算,默认阈值 0.5 也是课程里最先出现的设定,容易形成惯性。

怎么补:先看类别分布,明显不平衡时改用精确率、召回率和 F1;阈值根据业务对两类错误的不同容忍度来定,用验证集扫一遍候选阈值,而不是固定 0.5。

5.5 梯度检验只做一次或忘记关闭

易漏点:训练前跑一次梯度检验通过,之后改了网络结构不再检验;或者检验开着忘了关。

为什么漏:检验通过给人“已经验证过”的安全感,但结构、激活函数、正则化项任何一处改动都会让之前的结论失效。

怎么补:每次改动网络结构或代价函数后重新做一次梯度检验;把检验写成一个默认关闭的开关,训练入口显式关掉,避免带着它跑完整训练。

6. 复习清单:把公式变成可执行的检查表

复习到这个阶段,公式推导应该已经过了一遍,剩下的问题是“上了考场或者上了项目,怎么保证不漏”。我的做法是把整门课压缩成一张可勾选的检查表,按流程顺序排,而不是按章节顺序排。按章节排的清单只能帮你回忆内容,按流程排的清单能帮你在动手时定位。

阶段检查项判断标准
数据准备特征是否缩放各特征量级接近,缩放参数只来自训练集
数据准备数据划分是否独立验证集调参,测试集只评估一次
模型建立先跑通简单版本有基线结果,能算训练误差和验证误差
诊断偏差方差定位两条误差曲线的相对位置和间隙大小
调优正则化强度在验证集上扫候选值,偏置项不参与
调优学习率代价曲线稳定下降,不震荡不爬升
神经网络初始化随机小值,不全零
神经网络维度对齐每层误差项维度等于该层激活维度
神经网络梯度检验相对误差在负七次方量级,训练时关闭
评估指标选择类别不平衡时用精确率/召回率/F1

这张表里最容易被跳过的是“先跑通简单版本”这一行。很多人一上来就搭复杂模型,跑不通时无法判断是数据问题、实现问题还是模型能力问题,只能挨个试,时间全耗在排查上。先有一个能算误差的基线,后面每一步改动都有参照,问题定位会快很多。

另一个值得单独练的动作是手写一遍梯度下降和反向传播的维度检查。不需要写完整实现,只写形状推导,拿纸笔把每层的输入维度、输出维度、权重形状、误差项形状列出来,对照代码检查。这个动作十分钟能做完,但能挡掉大部分运行时才暴露的错误。

# 维度自检:把每层形状打印出来,和手推结果对照 def check_shapes(layer_sizes): # layer_sizes 形如 [输入维度, 隐藏层1, 隐藏层2, 输出维度] for i in range(len(layer_sizes) - 1): in_dim, out_dim = layer_sizes[i], layer_sizes[i + 1] print(f"层 {i}: 权重形状 ({out_dim}, {in_dim + 1}), " f"激活形状 (m, {in_dim}), 误差形状 (m, {out_dim})")

这段代码不参与训练,只是把维度关系打印出来对照。参数说明上,layer_sizes的第一项是特征数,最后一项是类别数,中间是隐藏层单元数。把这个函数放在网络定义之后、训练之前调用一次,维度错误基本当场暴露,不用等到反向传播报错或者算出离谱结果才发现。

最后说一个我自己的习惯。每次复习完一个模块,我会合上笔记,在白纸上默写这个模块的“输入、输出、代价函数、更新规则、常见失败模式”五项,写不出来的那一项就是没真掌握的地方,回去只看那一节。这个方法比从头再读一遍快得多,也更能暴露“看着眼熟其实不会”的状态。从那以后我每次准备复习,都强制先默写再翻书,顺序反了就会产生已经学会的错觉。

希望帮到你。这份笔记的完整版整理成了可打印的文档,按章节分块、带检查表和代码片段,需要的话可以拿去对着自己的进度逐项过一遍。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进