简介:斯坦福大学吴恩达机器学习课程资源包,是机器学习入门与进阶的经典配套资料,面向希望系统掌握监督学习、无监督学习及神经网络的学习者。资源梳理了线性回归、逻辑回归、支持向量机、决策树、聚类等核心算法,并融汇梯度下降、正则化、交叉验证等关键技巧,帮助读者搭建算法知识体系。压缩包内含一百三十个文件,大小约十七点二一兆字节,以PDF讲义为主体,辅以JPG图片、GIF动画、TXT笔记、HTML页面及ZIP分组包,类型覆盖讲义、示意图、文本资料与代码参考,结构清晰便于检索。已有397人学习下载。内容包括课程讲义、编程作业素材、算法示意图及阅读文本,可作为课程复习指南、算法速查手册或项目实战参考,尤其适合边看视频边对照学习,加深对模型原理和调优方法的理解。
1. 为什么斯坦福吴恩达这门课值得你花60个小时手写算法
不少同学学机器学习入门,上来就调sklearn、pytorch,跑通几个demo就觉得自己会了。真去面试或者独自接项目,被问到“为什么选这个模型”“代价函数怎么来的”就露馅。吴恩达在斯坦福主讲的机器学习课程,思路正好相反:它不让你调包,而是用Octave/Matlab把线性回归、逻辑回归、神经网络、SVM的公式一行行写出来,先把“算法到底在算什么”这件事钉进脑子里。这门课适合两类人:零基础打算系统入门的,以及已经在用库但想补底层原理的从业者。它解决的问题不是“跑通一个模型”,而是“手里只有一份数据集时,知道该选哪个算法、调哪个参数、效果不好往哪个方向排查”。这是把机器学习当工程,而不是当魔法看的第一课。
2. 先建一张知识地图:这门课的算法主线和选型逻辑
2.1 监督学习主线:线性回归、逻辑回归、神经网络的递进关系
吴恩达课程的前半段全部压在监督学习上,顺序是线性回归、逻辑回归、神经网络,再往后是SVM。这不是随意排列,而是一条完整的递进链。
线性回归解决连续值预测,比如房价、气温。它的核心是一个线性假设函数 h(x) = Xθ,配上最小二乘代价函数。实现它的关键动作是梯度下降:沿着代价函数下降最快的方向迭代 θ。课程作业 ex1 让你做的就是这件事,全程不调用任何现成的回归库。逻辑回归解决二分类问题,比如邮件是否垃圾、肿瘤是否恶性。它的做法不是把回归结果硬切成 0/1,而是在线性函数外套一层 sigmoid,把输出压到 (0,1) 区间,解释成概率。这一步如果没理解透,后面看交叉熵损失会一直觉得像黑匣子。
神经网络则是逻辑回归的堆叠版本——多个 logistic unit 组成隐藏层,每层输出继续作为下一层输入。吴恩达在课程里用了大量篇幅讲反向传播,本质上就是链式法则在你的代码里显式展开。很多调包选手怕反向传播,因为框架已经把它封装好了;但作业 ex4 逼你手写一遍,写完以后再看框架文档,你会感觉它不再是一个需要“背API”的东西。SVM 在这门课里给了一个更几何的解释:它追求最大化决策边界到样本的 margin,并且引入了核函数。课程只用了不到两周时间讲它,因为实际作业里你只需要调 C 和 γ,真正的推导留在 CS229 原版讲义里。
2.2 无监督学习与评估方法:偏差方差是比算法更实用的工具
课程后半段的看点不是算法数量,而是它把“怎么判断一个模型好不好”这件事真正讲明白了。K-means 和 PCA 各花了一周,异常检测花了一周,这些内容单拎出来都不难,难的是理解它们放在什么场景。课程里给的决策建议很朴素:没有标签时先用 K-means 看数据分群,特征维度太高先用 PCA 压到可视化范围,样本类别极不平衡时考虑异常检测。这套思路比直接上一堆深度模型更接近传统工业项目的常态。
更值得反复看的是偏差方差那一章。它告诉你训练集误差低、验证集误差高叫过拟合,解决办法是加正则化或增数据;训练集验证集误差都高叫高偏差,解决办法是加特征或换更强的模型。这些判断准则后来成为我做项目时先画学习曲线的习惯。吴恩达在课上反复说“机器学习的大部分时间不是在调算法,而是在做诊断”,我觉得这是整门课最值钱的一句。
2.3 为什么用 Octave/MATLAB 而不是一开始就上 Python
很多新手会对课程使用 Octave 感到困惑:明明 Python 才是主流,为什么还教一个工程上用得少的语言?吴恩达的解释很直接:Octave 的语法和数学公式几乎一一对应,能让初学者把注意力放在算法本身,而不是语言锅。比如 X' * X 在 Octave 里就是矩阵转置乘,和教材公式一模一样;在 Python 里你得写 np.dot(X.T, X) 或 X.T @ X,虽然也不难,但对刚接触矩阵的人多了一层代号转换。
从我自己的经验看,Octave 在课程前五周的体验确实比 Python 顺畅。你不需要处理 NumPy 的 broadcast 规则,不需要记 shape 对齐的隐式行为,写错了维数它会直接报错而不是悄悄给你算一个奇怪结果。对零基础者这是保护,对熟手这是约束。课程的后半段你已经理解算法以后,转 Python 只需要花一周左右适应 API,概念完全通用。所以不必担心学了 Octave 是浪费,它只是教学上更短的路径。
3. 从搭环境到跑通作业:ex1 和 ex2 的完整复现路径
3.1 搭一个最小可运行的 Octave 环境:安装、数据与目录约定
课程作业在 Octave 上跑最省事。Ubuntu 或 Debian 系系统直接安装,macOS 用 Homebrew,Windows 去官网装安装包即可。我这里给一个 Ubuntu 上的最小流程:
sudo apt update sudo apt install octave octave-optim mkdir -p ~/ml-course/ex1 ~/ml-course/ex2 cd ~/ml-course/ex1安装后先确认 Octave 能起来,然后下载课程指定的 ex1 数据文件 ex1data1.txt 放进 ex1 目录。注意 Octave 里和 MATLAB 不同,部分函数依赖额外的 package,比如后面 ex2 里要用 fminunc,就必须先安装并加载 optim 包。很多教程只写个 “pkg load optim” 就完事,忘了前面一步 “pkg install -forge optim”,导致新手在这里卡很久。我第一次装的时候也翻车了,后来养成的习惯是:遇到 undefined function 先敲pkg list看看有没有装,再敲pkg load去加载,不要直接怀疑代码。
目录结构建议按作业编号分文件夹,不要全部堆在一起。数据文件、主脚本、函数文件分类放,这样后面做 ex2 到 ex8 的时候,不用到处找文件。课程作业的初始代码是给你一个残缺的脚本,要求你把 costFunction 和 gradient 补全,不是让你从零写整个工程,所以目录很简单,但分类的好习惯值得从一开始养成。
3.2 ex1 线性回归:读数据、写代价函数、跑梯度下降
ex1 的完整目标是对人口和利润数据做一元线性回归。我先说代码,再说每段在干什么。
% load data data = load('ex1data1.txt'); X = data(:, 1); y = data(:, 2); % add intercept term m = length(y); X = [ones(m, 1), X]; theta = zeros(2, 1); % cost function function J = computeCost(X, y, theta) m = length(y); h = X * theta; J = 1/(2*m) * sum((h - y) .^ 2); end % gradient descent alpha = 0.01; iterations = 1500; for iter = 1:iterations theta = theta - (alpha/m) * X' * (X*theta - y); end解释一下 X = [ones(m,1), X] 这一行。它给特征矩阵加了一列全 1,对应 θ₀ 这个偏置项。如果不加,你的模型会被强制穿过原点,对大多数数据都是错的。代价函数 computeCost 把假设值和真实值的差平方后取平均,再除以 2,这个 2 是为了让求导后的系数干净一点,纯粹数学上的方便。梯度下降的更新式里 X' * (X*theta - y) 是整个批量梯度下降的核心,它一次性把所有样本的误差向量和特征矩阵做了乘法,等价于把所有样本的梯度累加。
再看参数:alpha=0.01 是学习率,课程给了一个保守值,保证收敛。如果你改大一点,比如 0.5,这个数据集依然可能收敛,但换到别的数据集就会爆掉。iterations=1500 是迭代次数,对 ex1 这个小数据集,1500 次足够收敛到和解析解一致。判断收敛的方法是画出代价函数随迭代次数的曲线,看到它单调下降并趋于水平,就说明收敛了。不画曲线的习惯很差,因为只看 theta 的输出无法判断你到底是收敛还是在震荡。我一般会顺手存一个 J_history 数组,每轮迭代末尾记录 J,最后画出来。
3.3 ex2 逻辑回归:sigmoid、代价函数与 fminunc 替换手动迭代
ex2 换成二分类问题:根据两门考试的成绩判断学生是否被录取。它不再要求你手写梯度下降循环,而是让你实现 costFunction,然后用 fminunc 去优化。这一步是帮你从“理解迭代”过渡到“理解优化器接口”。
% sigmoid function function g = sigmoid(z) g = 1 ./ (1 + exp(-z)); end % cost function with gradient for fminunc function [J, grad] = costFunction(theta, X, y) m = length(y); h = sigmoid(X * theta); J = -(1/m) * sum(y .* log(h) + (1-y) .* log(1-h)); grad = (1/m) * X' * (h - y); end % main script data = load('ex2data1.txt'); X = data(:, [1, 2]); y = data(:, 3); X = [ones(size(X, 1), 1), X]; initial_theta = zeros(size(X, 2), 1); % run optimizer options = optimset('GradObj', 'on', 'MaxIter', 400); [theta, cost] = fminunc(@(t) costFunction(t, X, y), initial_theta, options);这段代码里 sigmoid(z) 用了1 ./ (1 + exp(-z)),而不是1/(1+exp(-z))。点除表示逐元素除法,因为 z 是向量,用不带点的除会直接报维度错误。这是 Octave 和 MATLAB 最容易犯的错之一。代价函数用交叉熵而不是均方误差,原因是均方误差配上 sigmoid 会让代价函数呈现凹凸不平的形状,梯度下降容易走弯路;交叉熵能让优化目标变成凸函数,fminunc 能稳定找到全局最优。grad 的公式和线性回归长得一样,但 h 已经是 sigmoid 的结果,含义完全不同。
fminunc 是 MATLAB 自带的优化器,Octave 里需要 pkg load optim。options 里'GradObj', 'on'告诉优化器你提供了梯度,它就不用数值微分而是用你给的解析梯度,收敛更快更准。MaxIter 400 是最大迭代次数,一般足够的。你可能会好奇为什么不像 ex1 那样手写迭代,答案很朴素:真实项目里没人手写梯度下降,都用优化器,但你必须能看懂优化器的输入输出。
4. 把四个参数和边界摸透:alpha、lambda、特征缩放与向量化
4.1 学习率 alpha:怎么判断收敛、调大调小的规律
这门课里 alpha 是最先接触也最先出问题的参数。判断它是否合理,标准动作是画代价曲线。收敛正常的曲线是单调下降后趋于一条水平线。如果代价曲线来回震荡甚至一路上扬,多半是 alpha 太大,步子跨过头了。如果曲线下降得极慢,2000 次迭代还在缓慢爬坡,那 alpha 可能太小,需要放大或增加迭代次数。
课程给了一个实用技巧:取 0.001、0.003、0.01、0.03、0.1、0.3 这样按三倍递增的方式试,看哪一档能让代价曲线在差不多迭代次数内降到最低。这种方法比凭感觉设一个数靠谱得多,因为同样一个 alpha 对量纲不同的数据集表现完全不同。如果某数据集特征量级差异很大,比如一列是 0~1,另一列是 1000~9999,此时即使 alpha 很小也可能不收敛。这时候不要急着调 alpha,先做特征缩放,见 4.3。
4.2 正则化参数 lambda:偏差和方差之间的旋钮
lambda 的作用是给参数 θ 加惩罚,防止模型过分拟合训练集。逻辑回归作业 ex2 的第三部分会让你的模型在特征只有两列时先过拟合,然后要求你加 lambda 恢复泛化能力,这个过程会非常直观地展示什么是“高方差”。
lambda 设太大的后果是模型把所有 θ 都压到接近 0,最后变成一条几乎水平的线,训练集误差很高——这就是高偏差。lambda 设太小时惩罚不够,决策边界为了迎合每个训练点扭成一团。吴恩达的调试建议是:把 lambda 从 0, 0.01, 0.1, 1, 10, 100 这样按量级递增去扫,观察验证集误差的变化,挑验证集误差最低的那个值。这种调试方式在今天的 sklearn 里也有对应物,就是 GridSearchCV,但如果你不理解 lambda 的语义,网格搜索就只是在盲试。
4.3 特征缩放:为什么梯度下降对量纲敏感
课程第三章就讲了均值归一化,但在 ex1 的数据上其实可有可无,因为两个特征的人口和利润量纲还算接近。到 ex2 或 ex4(神经网络)时,量纲问题会被忽视并直接导致不收敛。特征缩放的标准公式是 x_norm = (x - μ) / (σ),μ 是均值,σ 是标准差。注意除以的是标准差而不是取值范围,虽然课程里也演示了除以 max-min 的版本,但用标准差在 Octave 里更简单:
% feature normalization function [X_norm, mu, sigma] = featureNormalize(X) mu = mean(X); sigma = std(X); X_norm = (X - mu) ./ sigma; end缩放必须在划分数据集之后做,只对训练集计算 μ 和 σ,再把验证集和测试集用同一组 μ 和 σ 变换。这是个容易踩坑的细节——很多新手先缩放全部数据再划分,结果就是把测试集信息泄露到了训练里,评估结果偏高。另一个容易被忽略的点是:完成预测后要想把结果解释回原始量纲,需要记录放缩前的参数边界,但不要试图把 θ 反变换,而是把新样本按同一 μ、σ 缩放后再预测。
4.4 向量化:从 for 循环到矩阵乘法的收益
课程里花了完整的一节讲为什么矩阵运算比 for 循环快几个量级。Octave 里写 X' * (X*theta - y) 一行,等价的 for 循环要几行,而且慢得多。更重要的是,向量化写法和数学推导几乎一一对应,不容易在循环里搞乱下标。
向量化的关键是矩阵维度的对齐。X 是 m×n,theta 是 n×1,X*theta 是 m×1 的预测值,减去 y 再左乘 X',结果正好是 n×1 的梯度。写代码前先在草稿纸上把每个矩阵的 shape 标出来,能避免 90% 的维度报错。这也是为什么我建议新手哪怕在 Octave 里也别用语法糖跳过矩阵概念——后面迁移到 Python 的 NumPy 时,同样的 shape 问题会以更隐蔽的方式出现,比如广播规则会“帮”你把错误矩阵算出一个不报错的结果,那种错误更难查。
5. 跑通这门课的避坑清单:现象、原因、解法
5.1 报错 operator *: nonconformant arguments
现象:点运行后 Octave 直接报维度不匹配,常见于 ex2 或 ex3。
原因:矩阵乘法在没有对齐的维度上被触发。最常见的是把 X 和 theta 的顺序调换,比如写了 theta * X;或者忘了给 X 加那一列全 1 的偏置项,导致 X 的列数比 theta 行数少 1。另一个高频场景是使用'把向量转置时,Octave 的'是共轭转置,对实数没影响,但如果数据里有复数就直接出问题。
解决:写代码之前先打印变量尺寸,用disp(size(X))、disp(size(theta))逐个核对。把每一处的矩阵维度写在注释里,例如% X: 118x2, theta: 2x1, y: 118x1。等熟练以后,养成所有新写矩阵运算前先过一遍维度的习惯,能节约大量调试时间。
5.2 fminunc 报错 “Function value at initial point is Inf”
现象:调用 fminunc 时提示初始点的函数值是无穷大,优化直接放弃。
原因:逻辑回归代价函数里log(h)和log(1-h)在 h 等于 0 或 1 时变成 log(0),结果是 -Inf。这种极端值通常在 sigmoid 输入为非常大的正数或负数时出现,比如线性项 X*theta 算出 ±1000,那么 exp(-z) 下溢为 0,sigmoid 被压成正好 0 或 1。这不是代码算错,而是数值稳定性问题。
解决:先检查 sigmoid 实现,确认是1 ./ (1 + exp(-z))而不是1/(1+exp(-z))。再考虑对 log 参数加一个极小量,比如log(h + 1e-10),这就是机器学习里最常见的 epsilon 修正。课程代码里没加这个修正,但原版 Octave 环境一般也不会触发极端值,而如果你把输入特征换成了量级很大的数据,就会踩这个坑。
5.3 训练误差很低,但验证集效果差
现象:ex2 附加题用多项式特征做逻辑回归,训练集准确率接近 100%,但看不见的测试集或验证集上预测错得离谱。
原因:多加了几十上百个多项式特征以后,模型完全记住了训练样本的细节,把噪声当成规律抓住了。这就是典型的过拟合、高方差问题。不是模型不对,而是模型容量远大于样本量。
解决:先尝试加正则化,把 lambda 从 0 开始逐步增大,观察验证集准确率开始稳定提升的区间。选最低验证集误差对应的 lambda 值即可。如果加 lambda 后提升不明显,再考虑减少多项式次数,比如从 degree=8 降到 degree=4。课程作业想让你动的是 lambda,不是帮你砍特征,所以优先调 lambda。
5.4 代价随迭代次数升高,甚至变成 NaN
现象:自己写梯度下降的 ex1 或 ex3(多分类)过程中,打印代价函数每轮的结果,发现数字越变越大,后来变成 NaN。
原因:几乎总是学习率 alpha 过大。当 theta 更新幅度太大,每一步跨过了代价函数的谷底,跳到对面更陡的坡,下一步又弹回来,形成发散震荡,最终数值溢出变成 NaN。少部分情况是特征没有归一化,梯度路径又长又陡,放大了 alpha 过大带来的问题。
解决:把 alpha 改小一个数量级,例如从 0.01 变成 0.001,重跑一遍。日志里确认代价曲线单调下降后,再试着逐级增大。不要一边调 alpha 一边还盯着参数 theta 的数值,代价曲线是最直观的诊断信号。
5.5 作业里用endfor在 MATLAB 里跑不了
现象:同学之间互相对答案时,有人用 Octave 写的endfor、endif,拿到 MATLAB 里运行直接语法错误。
原因:Octave 为了兼容旧脚本允许写成endfor,但 MATLAB 只认end。
解决:统一按 MATLAB 风格写endfor,在 Octave 里同样能运行,这是两套环境兼容性最好的写法。另外,函数的endfunction也是同理,写end最保险。这些事情在作业提交时不会扣分,但等你想换到公司的 MATLAB 许可证时就是实打实的血泪经验。
6. 学完之后用“双轨法”验证自己,并把 Octave 代码迁到 Python
学完不代表会做项目。我常用的验证方法是双轨法:拿同一份数据集,一边用手写的梯度下降或逻辑回归,一边用 scikit-learn 的现成实现,对比两边输出的 theta 和最终准确率。
import numpy as np from sklearn.linear_model import LinearRegression # 手写结果 theta 来自 Octave theta_manual = np.array([-3.895, 1.193]) X = np.loadtxt('ex1data1.txt', delimiter=',')[:, 0] y = np.loadtxt('ex1data1.txt', delimiter=',')[:, 1] X_b = np.c_[np.ones(len(X)), X] # 用正规方程复算 theta_np = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y model = LinearRegression() model.fit(X.reshape(-1, 1), y) print(theta_manual, theta_np, model.coef_, model.intercept_)你应该看到三组数字在小数点后几位内一致。如果偏差超过 0.01,多半是某一步预处理或者代价函数写错了。这个验证过程能过滤掉大量“以为自己会了”的情况。
迁移的捷径是画一张对照表:Octave 的'对应 NumPy 的.T,Octave 的X' * X对应X.T @ X,Octave 的./对应 NumPy 的/,Octave 的sum(...)对应 NumPy 的np.sum(..., axis=...)。有了这张表,课程里全部作业的迁移量其实很小,黄金规则是先保留 Octave 版结果作为 gold standard,再逐段对照 NumPy 输出。
说一个我自己的教训:我刚学完这门课就用 Python 重写 ex4 神经网络反向传播,结果训练误差一直收敛不到和 Octave 一样的水平。查了整整一个下午,最后发现是把 NumPy 里的*当成了矩阵乘法,而实际上在 Octave 里*是矩阵乘法,NumPy 里@才是。眼前一黑之后,我把所有作业都用这张对照表重新过了一遍,再没犯过同类错误。希望对你有帮助。
本文还有配套的精品资源,点击获取