ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

手写机器学习算法:吴恩达课程核心知识点全解析

手写机器学习算法:吴恩达课程核心知识点全解析 简介斯坦福大学吴恩达机器学习课程资源包是机器学习入门与进阶的经典配套资料面向希望系统掌握监督学习、无监督学习及神经网络的学习者。资源梳理了线性回归、逻辑回归、支持向量机、决策树、聚类等核心算法并融汇梯度下降、正则化、交叉验证等关键技巧帮助读者搭建算法知识体系。压缩包内含一百三十个文件大小约十七点二一兆字节以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 用 HomebrewWindows 去官网装安装包即可。我这里给一个 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) 是整个批量梯度下降的核心它一次性把所有样本的误差向量和特征矩阵做了乘法等价于把所有样本的梯度累加。再看参数alpha0.01 是学习率课程给了一个保守值保证收敛。如果你改大一点比如 0.5这个数据集依然可能收敛但换到别的数据集就会爆掉。iterations1500 是迭代次数对 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/(1exp(-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×ntheta 是 n×1X*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) 下溢为 0sigmoid 被压成正好 0 或 1。这不是代码算错而是数值稳定性问题。解决先检查 sigmoid 实现确认是1 ./ (1 exp(-z))而不是1/(1exp(-z))。再考虑对 log 参数加一个极小量比如log(h 1e-10)这就是机器学习里最常见的 epsilon 修正。课程代码里没加这个修正但原版 Octave 环境一般也不会触发极端值而如果你把输入特征换成了量级很大的数据就会踩这个坑。5.3 训练误差很低但验证集效果差现象ex2 附加题用多项式特征做逻辑回归训练集准确率接近 100%但看不见的测试集或验证集上预测错得离谱。原因多加了几十上百个多项式特征以后模型完全记住了训练样本的细节把噪声当成规律抓住了。这就是典型的过拟合、高方差问题。不是模型不对而是模型容量远大于样本量。解决先尝试加正则化把 lambda 从 0 开始逐步增大观察验证集准确率开始稳定提升的区间。选最低验证集误差对应的 lambda 值即可。如果加 lambda 后提升不明显再考虑减少多项式次数比如从 degree8 降到 degree4。课程作业想让你动的是 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 的.TOctave 的X * X对应X.T XOctave 的./对应 NumPy 的/Octave 的sum(...)对应 NumPy 的np.sum(..., axis...)。有了这张表课程里全部作业的迁移量其实很小黄金规则是先保留 Octave 版结果作为 gold standard再逐段对照 NumPy 输出。说一个我自己的教训我刚学完这门课就用 Python 重写 ex4 神经网络反向传播结果训练误差一直收敛不到和 Octave 一样的水平。查了整整一个下午最后发现是把 NumPy 里的*当成了矩阵乘法而实际上在 Octave 里*是矩阵乘法NumPy 里才是。眼前一黑之后我把所有作业都用这张对照表重新过了一遍再没犯过同类错误。希望对你有帮助。本文还有配套的精品资源点击获取
返回列表