
简介吴恩达机器学习课程的作业实现包同时提供Python自写版和Matlab原版覆盖线性回归、逻辑回归、神经网络、支持向量机、K均值、PCA等经典算法练习。包内共323个文件以169个m文件承载Matlab原版实现另配21个ipynb的Python笔记、57个txt说明、57个mat数据文件及16个pdf扩展资料整体约71.86MB章节编号清晰可逐次对照预习题、代码与结果。已有2837人学习适合希望动手复现课程实验、深入理解算法原理或在两个语言生态中比较实现方式的初学者和进阶者。通过逐行调试ipynb与m代码可完整走通数据预处理、特征工程、模型训练、参数调优与可视化评估流程同时借助Matlab原版体会课程原有的教学设计并对比Python生态的灵活工程化优势为后续独立完成机器学习任务积累扎实的代码基础与排错经验。 很多人在Coursera上刷吴恩达的机器学习课程时都会卡在同一个地方课程作业用的是Matlab/Octave而自己平时用的是Python。一边是熟悉的NumPy、Matplotlib另一边是早就忘光的Matlab语法。这节课的作业到底要不要用Python重写一遍重写过程中会遇到哪些问题这里我用自己实际跑通全部作业的经历把从Matlab原版到Python版本迁移的核心思路、关键差异和踩过的坑一次性说清楚。我最早也是跟着课程原版用Matlab做作业做到ex3神经网络那一节时向量化的矩阵运算在Matlab里其实写起来很顺手但转到Python后最直观的感受是NumPy的索引规则、广播机制、以及scipy.optimize.minimize的接口参数跟Matlab的fminunc完全是两套逻辑。很多同学在论坛里问为什么我的cost function算出来是NaN或者为什么优化器迭代了几百次还不收敛其实根源大多不在算法本身而在Python重写时对原版代码做了逐行直译——用Matlab的思路去写NumPy自然处处碰壁。这篇内容适合三类人一是正在跟课、想把作业换成Python环境完成的同学二是想借这套经典作业深入理解梯度下降、神经网络反向传播等基础算法的学习者三是已经刷完课想重新用Python实现一遍加深印象的进阶玩家。如果你是零基础还没装好Python环境建议先配好Anaconda再回来看。1. 为什么值得把Matlab原版作业重写成Python这门课的作业价值不在代码本身而在用最小代价实现完整算法链路的训练量。吴恩达在视频里把公式推导讲得很细但真正动手写代码时你会发现自己对θ更新时到底是逐元素乘还是矩阵乘偏置项要不要加在X矩阵里这些问题其实是一知半解的。Matlab原版作业的好处是矩阵运算语法简洁坏处是你如果只用Matlab出了课程就很难有继续拓展的生态。Python版作业在这门课社区里已经有非常成熟的方案但市面上大部分仓库是直接给出答案很少有人讲清楚每一步迁移背后的原因。我自己重写时立了两个标准第一不直接抄别人写好的Python作业必须自己从Matlab原版思路推导一遍第二尽量保持与课程原版的算法结构一致但用Pythonic的方式重写——用numpy替代矩阵运算用scipy.optimize替代fminunc用matplotlib替代plot。这样既能对照原版验证正确性又能真正理解两种语言在数值计算上的设计差异。重写的收益在后期会越来越明显。课程后半段的ex7K-means和PCA、ex8异常检测和协同过滤涉及大量数据可视化操作Python生态里的matplotlib、seaborn、pandas能让你更直观地观察算法行为。而Matlab原版的绘图虽然也很强大但脱离课程环境之后你很难把那些脚本直接复用到真实项目中。Python版作业写完后你可以直接把数据加载、特征归一化、模型训练这些代码迁移到Kaggle竞赛或者工作里的数据集上这正是这门课作业最大的隐藏价值。2. 从.m到.py的映射核心差异与关键函数对照2.1 文件结构与数据格式的前置处理Matlab原版作业通常会给ex1.m、ex1_multi.m这类主脚本外加若干costFunction.m、gradientDescent.m函数文件数据则是ex1data1.txt这种纯文本格式。Python重写的第一步是拆解主脚本的执行顺序把加载数据-初始化参数-调用优化器-可视化结果这几个步骤拆成独立的函数模块。我的做法是每个作业建一个Python包类似这样ex1/ ├── main.py # 主流程线性回归单变量多变量 ├── utils.py # 数据加载、特征归一化、绘图辅助 ├── cost_function.py # 代价函数与梯度 └── gradient_descent.py # 梯度下降实现数据加载上Matlab用load(ex1data1.txt)Python用numpy.loadtxt或pandas.read_csv这一步比较简单。但要注意原版作业里有些数据是.mat格式比如ex3的手写数字、ex8的movie ratings这时候Python需要用scipy.io.loadmat来读读出来是一个字典结构里面每个键对应一个Matlab变量。很多人在这一步就卡住了总以为loadmat读出来直接就是数组实际上必须通过data[X]这种键名方式取数据。2.2 矩阵运算的思维切换从下标到广播Matlab和NumPy在矩阵运算上最大的差异是索引规则和广播机制。Matlab下标从1开始NumPy从0开始更重要的是Matlab默认把二维数组当作矩阵A * B是矩阵乘法而NumPy里*是逐元素相乘矩阵乘法必须用或np.dot。这个差异在写梯度下降时尤其容易出问题。以ex1的线性回归为例更新公式是θ : θ - (α/m) * X^T * (X*θ - y)。在Matlab里直接写theta theta - (alpha/m) * (X * (X * theta - y))。但在Python里如果你想着直译成theta theta - (alpha/m) * (X.T (X theta - y))在只有单一特征时可能正确但一旦扩展到多特征X矩阵维度变了如果X是(m, n1)而不是(m, n)你的theta维度就对不上广播机制会悄悄报错或者算出一个奇怪的形状。所以重写时我给自己定了一条规则每一步运算都检查结果形状是不是(m, 1)或者(n, 1)确保没有因广播导致的隐性错误。2.3 优化器替代方案fminunc的Python味选择课程原版用了大量fminunc来做无约束优化从ex2的逻辑回归到ex4的神经网络几乎每道题都在用。Python里最对等的替代是scipy.optimize.minimize。两者最关键的参数映射是MatlabfminuncPythonscipy.optimize.minimize说明costFunctionfuncost_function代价函数签名必须为fun(theta, X, y)grad在costFunction里返回jacTrue需要在代价函数里同时返回cost和gradientoptions.MaxIteroptions{maxiter: 50}最大迭代次数options.GradObjjac是否使用梯度theta0x0初始参数向量这里有个特别容易踩的坑scipy.optimize.minimize默认调用的是拟牛顿法BFGS它会自动用有限差分估计梯度如果你在代价函数里同时返回了梯度和代价一定要把jac设为True否则优化器会忽略你手动计算的梯度用数值微分重新算一遍梯度不仅慢还容易出现精度问题。我在重写ex2时第一次没设jacTrue结果迭代了300多次代价仍在缓慢下降设了之后几十次就收敛了。3. 逐题拆解从ex1到ex8的Python重写重点与难点3.1 ex1线性回归与ex2逻辑回归扎实基础ex1太简单大部分人在半小时内就能搞定。但有两个细节值得留意。一是特征归一化原版作业专门写了featureNormalize.mPython里直接用mean和std计算但要注意std在NumPy里默认是有偏估计除以nMatlab的std默认是无偏估计除以n-1。这个差异会让归一化后的特征值略有不同但线性回归的最终θ结果差异很小不过如果你后面用正规方程求解就会发现在某些数据上数值差异会被放大所以建议统一用np.std(X, axis0, ddof1)去模拟Matlab的std行为。ex2逻辑回归开始引入代价函数和梯度的成对返回模式。这里有一个细节scipy.optimize.minimize要求代价函数返回的是标量代价和梯度向量但很多人在实现时会把gradient写成对每个参数分别求解的循环这在特征不多时没问题一旦特征多了性能会很差。正确做法是用矩阵运算一次性算出整个梯度向量代码核心就是h sigmoid(X theta) grad (1/m) * (X.T (h - y))我见过不少人因为把sigmoid函数里的指数运算写成了np.exp(-X theta)导致溢出最后cost直接变NaN。一个稳妥的做法是对np.exp的输入做一个裁剪或者使用scipy.special.expit这个数值稳定的sigmoid实现就完全不会出现溢出问题。3.2 ex3多类分类与ex4神经网络向量化是硬仗ex3开始上强度了。用一对多one-vs-all做手写数字识别需要在训练集上循环10次训练分类器每一步都是矩阵运算。这里Python重写最容易出问题的是scipy.optimize.minimize的x0参数因为你训练10个分类器每个分类器有一个初始θ向量如果直接用同一个theta0数组去循环minimize内部可能会修改x0实际上不会但如果你把theta0当作可变对象传入其他函数可能会被意外覆盖。稳妥做法是在循环里用theta0.copy()生成新的初始参数。ex4的反向传播是整门课最难的作业没有之一。Matlab原版里神经网络的参数以向量形式传入nnCostFunction需要先用reshape还原成各层的权重矩阵算完梯度后再reshape回向量。Python重写时这一步的reshape操作最容易出bug。因为numpy的数组默认按行优先存储而Matlab按列优先存储。如果你直接照搬Matlab代码里的reshape顺序参数的排列方式会和Matlab不同但只要你保证展开和还原是互逆操作结果其实不受影响。警惕的是如果某处不小心用FFortran顺序展开另一处用CC顺序还原那参数就全乱了训练出来的模型准确率会极其诡异。这个坑我调了一整个下午才发现。反向传播里梯度检查gradient checking在Python里实现反而比Matlab更顺手因为scipy.optimize.check_grad可以直接用。不过要注意梯度检查时一定要把epsilon设成1e-4左右太大梯度近似误差大太小数值计算会有舍入误差而且数值梯度计算时要用中心差分而不是单边差分。3.3 ex5偏差方差与ex6 SVM训练曲线绘制与核函数ex5的核心是绘制学习曲线learning curves用来诊断高偏差还是高方差。这个作业的本质是训练多个模型每次用训练集的不同子集然后在训练集和验证集上分别计算代价。Python里这一步比较直观但有个隐藏坑原版作业里训练模型时用的是fmincg共轭梯度法而Python端如果继续用scipy.optimize.minimize的BFGS在小样本集上训练时间会明显增长。我实测下来在lambda0的情况下BFGS确实比共轭梯度慢一些但在数据量小的场景下差别不大只是迭代次数多一些。如果你觉得速度太慢可以试试methodCG配置。ex6 SVM那节原版作业其实调用了Matlab的svmTrain工具包并不是你自己手写SVM。Python重写这边比较主流的方式是直接调scikit-learn的svm.SVC但为了保持自己动手实现的体验很多人在调参后用matplotlib绘制高斯核的决策边界。这部分作业在Python生态里非常舒服因为sklearn.svm.SVC的接口比Matlab的svmTrain更简洁而且可视化工具链更成熟。这里我给一个建议不要在这节花太多时间手写SVM优化器重点放到高斯核的sigma参数对决策边界形状的影响上。3.4 ex7 K-means与PCA、ex8异常检测与协同过滤数据降维实战ex7的K-means作业包含两个部分像素压缩和PCA人脸降维。像素压缩这节关键是实现findClosestCentroids和computeCentroids两个函数。Python里用scipy.spatial.distance.cdist可以快速计算每个样本到所有聚类中心的距离比纯循环快得多这是我在重写时最喜欢的一个优化点。PCA部分的难点在于理解np.linalg.svd的返回值和Matlab的svd完全一致都是U、S、V三者。但如果你习惯用np.linalg.eig算特征值分解那么要注意特征向量是按列排的跟Matlab的eig结果顺序可能不同需要做符号对齐处理即如果特征向量的某个元素符号相反整个向量的方向就会翻转但降维后重建的图像视觉上一致。ex8的异常检测作业里多元高斯分布的参数估计非常简单但Python重写时有个细节协方差矩阵的半正定性。如果某个特征是常数协方差矩阵会奇异np.linalg.det会算出0概率密度函数直接变无穷或NaN。所以在估计多元高斯时先对数据做一次标准化或者给协方差矩阵加一个小的对角扰动比如 1e-6 * np.eye(n)是很必要的。协同过滤部分同样地正则化是在代价函数里加一项(lambda/2) * (sum(X**2) sum(Theta**2))这部分如果理解了矩阵形状基本一次过。4. 重写过程中最常见的Bug与排查路径4.1 从NaN到正确排查维度不匹配的三板斧我在重写过程中遇到过很多次cost突然变成NaN的情况最典型的原因是用了np.log(0)或者sigmoid溢出。np.log(0)会直接得到负无穷后面的np.multiply或者np.sum就会产生NaN。排查思路是先在sigmoid函数里加一个下限裁剪np.clip或者改用scipy.special.expit然后检查X theta的结果范围如果数值超过几十大概率是特征没有归一化或者初始参数设得太大。第二个高频坑是维度不匹配。numpy的运算在维度不匹配时会直接抛ValueError这个还算好定位。最麻烦的是广播机制偷偷改变了矩阵形状。比如你有theta形状是(3,)X形状是(47, 3)X theta结果是(47,)再减去y(47, 1)广播就会变成(47, 47)的矩阵——完全跑偏。为了避免这种问题我在所有涉及theta的地方都会先theta theta.reshape(-1, 1)强制列向量形状或者用theta.reshape(n, )拉平成一维数组。另外写完后用np.shape检查每个中间变量的维度是排查这类问题最有效的手段。4.2 优化器不收敛不是算法问题是接口姿势问题很多同学把代价函数和梯度写好后用minimize优化发现迭代到几十次后fun还在下降但下降速度极慢最后也没达到课程要求的精度。这未必是算法写错了很可能是minimize的tol和options设置不对。scipy.optimize.minimize默认的tol是1e-8但maxiter默认非常小对BFGS是1e4。如果你的代价函数初始值在1000级别tol1e-8会导致优化器认为还没收敛一直迭代到maxiter耗尽。一个合适做法是设置options{maxiter: 400}同时把tol调到1e-6这样既能观察到代价的稳步下降又不会卡死在太严苛的收敛条件上。另外有个特别容易被忽略的点minimize的x0必须是一维数组。这在神经网络作业里尤其致命——如果你把权重矩阵直接以二维形式传入minimize会直接报错或者破坏权重矩阵的结构。所以ex4里一定要先把每一层的权重矩阵reshape成一维向量拼成一个大的参数向量代价函数内部再按分割点reshape回各层矩阵。4.3 可视化结果不对先检查数据加载和预处理有一类问题跟算法无关纯粹是数据加载或预处理阶段埋下了雷。典型例子是用loadmat读.mat文件时数据是(m, n)还是(n, m)很容易搞混转置一下结果画面完全不同。我的排查经验是先把X.shape、y.shape、X.min()、X.max()这些基本信息打印出来和原版Matlab工作区里的变量核对一遍。如果归一化后出现全零列多半是原数据里有常数特征或者归一化的axis参数写错了。这些坑都不是算法问题但如果你没有一套系统的排查思路很容易耗掉大量时间。5. 关于自己写一遍这件事的最终建议如果你是打算直接搜一个Python版作业仓库来跑通课程那确实省时省力但收获会大打折扣。我自己的体会是真正动手从Matlab原版思路迁移到Python期间遇到的每一个维度报错、每一次NaN问题、每一回优化器不收敛都是在逼着你把矩阵运算的形状梯度计算的链式法则正则化如何影响代价函数这些概念从看过变成用过。这门课最大的价值从来不是那几张证书而是你在调试过程中建立的直觉——看到代价下降变慢就大概知道是学习率太大还是特征尺度不同看到梯度爆炸就自然想到要检查中间层的激活值范围这种判断力只能靠手写代码喂出来。如果你决定重写我建议按这个节奏来先挑ex2和ex3练手等熟悉了scipy.optimize.minimize和NumPy的矩阵运算节奏再去啃ex4的反向传播。ex4值得多花时间因为它是整门课算法深度的天花板从神经网络前向传播到反向传播、从梯度检查到参数展开这一套流程走通之后后续的课程包括深度学习专项里类似的代码模式你会一眼就认出来。最后再分享一个小技巧每道作业完成后把你的Python输出和Matlab原版对应变量比如theta、cost、accuracy打印到同一个表里对比一遍误差在1e-4级别就说明迁移正确。这一步相当于给你的重写过程上了一个自动判题器能省下后期大量调试时间。这门课虽然老但它的作业设计到今天依然是最好的机器学习动手训练材料之一用Python把它完整啃下来你的收获绝对物超所值。本文还有配套的精品资源点击获取