ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

吴恩达机器学习ex1作业全解析:线性回归与梯度下降实战指南

吴恩达机器学习ex1作业全解析:线性回归与梯度下降实战指南 如果你正打算刷吴恩达老师的机器学习课程那你大概率会卡在第一次编程作业上。作为全球最经典的机器学习入门课Coursera 版《Machine Learning》的第一份编程作业ex1名义上只要求你实现一个线性回归但真正动手时你会发现自己面临的不只是把公式写成代码还有环境配置、数据维度、调试思路、提交机制等一堆课程视频里没细讲的东西。这份作业我在刷课期间完整写过两遍——先 Octave 后 Python期间踩过不少坑。这篇文章会把完成 ex1 的完整思路、核心原理、代码实现和常见报错整理出来给刚入门、正准备做这道经典作业的你当一份实战地图。内容不碰任何捷径和代做只讲为什么这么做和怎么做才靠谱。1. 第一次作业到底在做什么题目设定与数据解剖1.1 官方作业要求的一句话概括吴恩达这门课是典型的边学边做设计第一周讲线性回归和梯度下降第二周第一次编程作业 ex1 就来了。官网下载的作业包里通常有ex1.pdf、ex1data1.txt、ex1data2.txt以及一套用于远程判分的提交脚本。作业要求拆开来看核心就四件事写一个warmUpExercise返回 5×5 的单位矩阵实现单变量线性回归的代价函数computeCost和梯度下降gradientDescent在多变量数据上完成特征归一化再用梯度下降/正规方程预测房价把关键数字可视化并提交到判分系统。第一次做的人往往低估第四步。前面几个函数写对了还不算完还要能通过 submit 的自动判分这要求函数签名、文件名甚至变量名都和模板完全一致。这一点我会在后面的常见问题部分专门展开。1.2 两份数据集背后的行业场景ex1data1.txt是经典的城市人口-餐车利润数据。为什么选这个场景因为它是商业决策里最直观的一类问题已知过去 97 个城市的人口规模和对应利润让你预测一个新开城市能赚多少钱。注意这里面的完整流程是读数据 → 定义模型 → 迭代优化 → 做预测这和真实工业界的机器学习应用流程完全一致并没有因为这是一份作业就简化掉某个环节。数据集里的趋势几乎是线性增长的从散点图上看就是一条斜线所以单变量线性回归足够用。ex1data2.txt则是房价预测的经典数据每一行是房屋面积平方英尺、卧室数量和房价。新手最容易忽略的是特征尺度问题面积动辄上千卧室数量只有个位数两个特征数值范围差了几个数量级。如果不做归一化梯度下降在等高线图上会长时间沿着狭长山谷来回震荡收敛速度慢得让人怀疑人生。这里就自然引出了特征缩放的意义。1.3 为什么偏偏选线性回归当第一道题吴恩达把线性回归放在第一周背后是有深意的。一方面线性回归的假设函数 hθ(x)θ^T x是后续逻辑回归、神经网络、支持向量机等模型的最小公分母。你会发现后面很多公式都长得很像它只是换了激活函数或损失函数。另一方面线性回归具备完整的几何直觉。它可以在二维平面画出来用普通坐标系就能解释清楚。很多刚接触机器学习的人会问怎么判断一个模型学得好不好线性回归的代价函数给出了一个非常自然的度量预测值和真实值之间的均方误差。这个误差最小化的思想贯穿整个课程所以作业第一个要写的函数是computeCost而不是直接上梯度下降。先把怎么度量误差想明白再去做参数优化整个流程的逻辑就顺了。2. 从公式到代码核心算法的原理底牌2.1 代价函数0.5 到底哪来的代价函数公式长这样J(θ) (1 / (2m)) * Σ (hθ(x(i)) - y(i))²很多新手会问为什么要多此一举除以 2答案藏在求导里。如果代价函数不除以 2平方项对 θj 求偏导时会带出一个系数 2梯度表达式里就会残留这个因子。除以 2 之后2 被消掉梯度变成干净的形式∂J/∂θj (1/m) * Σ (hθ(x(i)) - y(i)) * xj(i)这个常数 2 并不影响最优点位置但会让梯度迭代的数值尺度更舒服也更容易推公式、写代码。你会在很多开源代码里看到1/(2m)的写法它不是数学巧合而是为了让梯度公式更整洁。同样值得理解的是为什么用均方误差而不是绝对误差因为平方函数处处可导梯度计算方便而且对大偏差更敏感。代价是它对离群点也更敏感这一点到后面学正则化、鲁棒损失时会再提到但作为第一课均方误差是最稳的选择。向量化写法可以写成J sum((X * theta - y) .^ 2) / (2 * m);这和公式完全等价但比 for 循环快得多也少了很多出错机会。2.2 梯度下降迭代同步更新与收敛条件梯度下降的迭代公式θj : θj - α * (1/m) * Σ (hθ(x(i)) - y(i)) * xj(i)注意一个关键点同步更新simultaneous update。也就是说等式右边用到的 θ 都是更新前的旧值不能在算完 θ0 之后立刻用新 θ0 去算 θ1。在向量化实现里这通常不是问题因为所有 θ 的更新在同一个矩阵表达式里一次完成但如果你手写 for 循环就特别容易写成边算边覆盖的错误版本结果完全不对。收敛条件怎么判断最朴素的桌面级做法是画 J_history 曲线每次迭代后把代价函数值存下来如果曲线在下降且最终趋于水平说明模型在学习如果曲线上升或震荡大概率是 α 太大。吴恩达在课程里给过一个经验值区间0.001、0.003、0.01、0.03、0.1、0.3……按 3 倍递增去试而不是随便拍一个。我在 ex1 上试过 α0.01 和 α0.3前者要跑几百次才明显收敛后者十几次就逼近最优值但 α1 以上时 loss 直接变成 NaN这就是步子迈太大了。调试梯度下降时把J_history打出来看比盯着 θ 瞎猜有效得多。2.3 特征归一化让椭圆变成圆多变量线性回归的第二个大坑是特征尺度。ex1data2里面积均值约两千卧室数均值只有三左右数值范围差几百倍。如果不处理代价函数的等高线会是一组非常扁的椭圆梯度下降会沿着长轴方向来回震荡收敛极慢。做均值归一化后x_norm (x - μ) / σ两个特征都被压到接近 [-1, 1] 区间等高线趋近圆梯度方向几乎直指最低点。这里有一个作业里容易翻车的细节归一化时用的 μ 和 σ 是从训练集上算出来的之后预测新样本时新样本也要用同一组 μ 和 σ 来变换。换句话说μ 和 σ 是模型的一部分千万别在测试时拿测试集自己的均值和方差去归一化否则特征分布就不一致了。这个训练/测试一致预处理的原则在后面的课程和真实项目里会一直遇到。2.4 正规方程一条公式直达终点除了梯度下降作业还要求实现正规方程θ (X^T X)^(-1) X^T y它不需要迭代也不需要选学习率一条矩阵运算直接给出解析解。听起来很省事但有两个限制X^T X 可能不可逆。常见原因是特征冗余或者样本数小于特征数求逆的计算量随特征数增长很猛。当特征数上万时矩阵求逆的 O(n³) 开销会超过梯度下降的迭代开销。吴恩达给的经验分界线大约是 n10000。放在期末题和面试题里这就是高频考点给出两种方法让考生比较适用场景。ex1 的考察意图很明确——你要会用梯度下降也要会用正规方程并且能解释为什么在多变量部分要用特征归一化加速梯度下降而正规方程不需要归一化。因为正规方程是一次性矩阵运算特征尺度不影响最终解只是数值稳定性可能会受影响。对比维度梯度下降正规方程需要选择学习率需要不需要需要迭代需要不需要特征数量大较友好求逆开销高需要特征归一化强烈建议通常不需要实现难度稍高较低3. 完整实操从加载数据到提交通过3.1 环境准备与提交机制先提环境。课程官方推荐 GNU Octave原因有两个一是免费二是与 MATLAB 语法几乎完全兼容。已经会用 MATLAB 的可以直接用 MATLAB但学生最常见的选择还是 Octave。装好后打开 ex1 文件夹注意路径里不要有中文和空格先跑一遍ex1.m如果看到图弹出来说明路径和数据加载正常。提交通过submit()完成账号就是 Coursera 的账号密码。这里要提醒一句提交失败大多是网络连接问题和代码本身关系不大换个网络环境再试往往会通。作业包里的目录结构一般是ex1/ ├── ex1.m ├── ex1data1.txt ├── ex1data2.txt ├── computeCost.m ├── gradientDescent.m ├── featureNormalize.m ├── normalEqn.m └── submit.m你的任务是往各自的.m文件里填实现不要改文件名。3.2 单变量线性回归的完整实现在写computeCost和gradientDescent之前先看数据处理。作业模板已经帮你把 X 和 y 从数据文件里读出来了X 里默认包含一列 1对应截距项 θ0。所以computeCost的实现其实只有三行function J computeCost(X, y, theta) m length(y); J sum((X * theta - y) .^ 2) / (2 * m); endgradientDescent的实现function [theta, J_history] gradientDescent(X, y, theta, alpha, num_iters) m length(y); J_history zeros(num_iters, 1); for iter 1:num_iters theta theta - (alpha / m) * (X * (X * theta - y)); J_history(iter) computeCost(X, y, theta); end end这段代码里最关键的向量化梯度更新是X * (X * theta - y)。X 是 m×(n1) 的矩阵theta 是 (n1)×1 的列向量X * theta - y是 m×1 的残差向量再左乘 X等效于对每个特征 j 求 Σ(h-y)xj最后除以 m正好和梯度公式一一对应。如果你习惯用 for 循环逐个更新 theta(j)也不是不行但向量化版本更简洁、更快也更不容易犯同步更新错误。跑完梯度下降后把 theta 和 J_history 分别画出来。你会看到一张散点图加一条穿过数据云的回归线以及一条随迭代次数下降的代价函数曲线。作业里给的标准自查结果是θ [0, 0] 时代价约 32.07θ [-1, 2] 时代价约 54.24梯度下降完成后 θ 大约 [-3.63, 1.17]用 3.5 万人口预测利润结果大约在 4519 美元左右。如果你的结果跟这个量级差很远说明前面有地方写错了。3.3 多变量线性回归与学习率调参ex1_multi 部分分两小问先用梯度下降做一遍再用正规方程做一遍。梯度下降版本的第一步是特征归一化function [X_norm, mu, sigma] featureNormalize(X) mu mean(X); sigma std(X); X_norm (X - mu) ./ sigma; end注意保存好 mu 和 sigma后面预测时要用。第二步是用与单变量相同的computeCost和gradientDescent跑选一个合适的 α 和迭代次数最后得到一组 θ。在梯度下降版本里预测 1650 平方英尺、3 个卧室的房价时需要先把 [1650, 3] 用同一个 mu 和 sigma 归一化再和截距项 1 拼成特征向量然后算 θ^T xprice [1, (1650 - mu(1)) / sigma(1), (3 - mu(2)) / sigma(2)] * theta;正规方程版本则完全不需要归一化直接用原始 X 和 y 算function [theta] normalEqn(X, y) theta pinv(X * X) * X * y; end这里用pinv而不是inv是为了处理 X^T X 可能奇异的情况。预测时直接用原始特征值 [1, 1650, 3] 和 theta 做乘法。我自己跑出来的经验是α0.01 时大约 400 次迭代后代价函数降到稳定值α0.3 时50 次就差不多了α1 以上会出现代价函数在某次迭代后变成 NaN。这是典型的发散信号这时候不要盲目加迭代次数要先减小 α。不同学习率下的表现大致如下学习率 α50 次迭代后的行为结论0.001代价函数缓慢下降收敛太慢调大0.01稳定下降400 次左右趋于平台可用0.3快速下降约 50 次接近最优推荐1.0 及以上loss 出现 NaN发散调小作业给出的1650 平方英尺住宅价格预测答案课程社区里常见结果在 29 万到 30 万美元之间。正规方程版本约 293081梯度下降版本因为学习率不同可能略有差异但数量级应该在同一范围。3.4 用 Python 完整复现一遍如果你更习惯 Python可以用 numpy 一行行对照实现。读数据、加截距列、算 cost、跑梯度下降、画图只要公式一致结果应该和 Octave 版本完全一致。核心代码import numpy as np import pandas as pd import matplotlib.pyplot as plt data pd.read_csv(ex1data1.txt, headerNone, names[Population, Profit]) X data.iloc[:, 0].values.reshape(-1, 1) y data.iloc[:, 1].values.reshape(-1, 1) m len(y) X np.hstack([np.ones((m, 1)), X]) theta np.zeros((2, 1)) def compute_cost(X, y, theta): return float((1 / (2 * m)) * np.sum(np.square(X theta - y))) def gradient_descent(X, y, theta, alpha, iterations): costs [] for _ in range(iterations): theta theta - (alpha / m) * (X.T (X theta - y)) costs.append(compute_cost(X, y, theta)) return theta, costs theta, costs gradient_descent(X, y, theta, alpha0.01, iterations1500) print(theta)Python 版对新手更友好因为它能在 Jupyter Notebook 里直接看到每一步的中间变量。但注意课程作业的判分系统认的是 Octave所以提交时还是要用 Octave 版本Python 版更适合自己加深理解。3.5 提交前的完整检查单我把踩过坑之后总结的检查单列在这里提交前过一遍文件名是不是computeCost.m、gradientDescent.m、featureNormalize.m、normalEqn.m不能多字母少字母函数名和文件名是否完全一致Octave 对这一点很敏感目录路径没有中文和空格warmUpExercise返回值是 5×5 矩阵不是向量ex1.m能不能从头跑到尾不报错如果改了代码保存后再 submit别让 Octave 还在跑旧版本。4. 常见问题排查我从失败到通过踩过的坑4.1 代价函数输出不对问题出在哪作业给的两个自查值是硬指标θ[0,0] 时代价约 32.07θ[-1,2] 时代价约 54.24。如果你的结果不是这两个数优先检查是否有括号错误J sum((X * theta - y) .^ 2) / (2 * m)如果把平方写在外面就完全错了数据维度X 必须是 m×2y 是 m×1theta 是 2×1可以用size(X)打印确认公式里的 2m在 Octave 里1/(2m)是非法语法必须写1/(2*m)这是一个很隐蔽的小坑。4.2 梯度下降发散自查清单梯度下降调试不需要看太多理论直接看现象代价函数在迭代过程中上升学习率过大代价函数变成 NaN/Inf学习率过大或者数据里有 NaN迭代几十次后 J_history 还在明显下降学习率过小或迭代次数不足得到的回归线明显偏离数据分布检查 theta 的更新是否做了同步更新或者初始 theta 设错了。调试技巧很简单把每轮J_history打出来或者画出来比自己盯着 theta 猜要高效得多。4.3 提交总是报错的处理思路常见报错和原因整理成一张速查表报错信息可能原因解决办法Function computeCost undefined函数名和文件名不一致改成完全一致operator *: nonconformant arguments矩阵维度对不上用size()检查 X、y、thetasubmitWithConfiguration连接失败网络或登录问题检查账号密码换网络环境中文路径下的文件读取失败Octave 对中文路径支持不好把目录挪到纯英文路径提交后显示 0 分函数签名与模板不一致重新比对作业 PDF 的函数定义还有一个隐蔽问题在 Octave 里写了中文注释但文件编码不对也会导致解析错误。保险起见代码注释统一用英文。4.4 期末考点衔接这份作业隐藏的考试题做 ex1 的同学大概率也是准备期末考试的人。这份作业的内容和期末考点高度重合几个从 ex1 直接衍生的高频题写出线性回归代价函数并用梯度下降推导参数更新公式为什么特征归一化能加速梯度下降梯度下降和正规方程各自的适用场景是什么为什么线性回归可以用解析解而后面学的逻辑回归、神经网络一般不用如何判断梯度下降是否收敛学习率过大会出现什么现象这五道题如果能在做完 ex1 后不看笔记自己答出来说明这一周的知识是真吃透了而不是只会调用函数。考试前把 ex1 的代码重新默写一遍往往比单纯背概念更有效。对我来说做完 ex1 最大的收获不是学会两个函数而是第一次完整走通从数据到模型再到预测的闭环。很多人会急着跳到后面的神经网络和深度学习但我建议你把这一步的基础打扎实。线性回归虽然简单却是理解所有复杂模型的起点这份作业里的每一个细节都值得你慢慢吃透。
返回列表