ARTICLE DETAIL

资讯详情

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

机器学习笔记整理:原理与Python代码迭代实践

机器学习笔记整理:原理与Python代码迭代实践 朋友如果你正打算入坑机器学习或者已经在坑里挣扎那这个标题直接说到了点子上机器学习——笔记整理原理、python代码——反反复复出精活这句话不是我随便起的题目而是我这一年多来折腾机器学习最深的体会。所谓“反反复复”不是简单的重复抄写而是一种迭代式的学习节奏今天看懂一个概念写一段垃圾代码明天回头发现理解错了推翻重来后天再看又能和另一个知识点串起来。这个过程极其狼狈但每次翻新笔记、重构代码你对模型的理解就会深一层这就是“出精活”的唯一路径。这篇博文我打算分享自己是如何整理机器学习笔记的里面包含了原理层面的梳理思路和对应的Python代码实现还有我踩过的一堆坑。如果你正在学李宏毅或吴恩达的课程或者在准备机器学习期末复习又或者想从零搭建一个机器学习Python环境但被各种报错折磨得想砸电脑这篇文章应该能帮你省下不少时间。先说好我不是什么理论大牛数学推导也经常翻车但正因如此我的整理方式可能更贴近普通人的认知习惯。我用的工具很朴素Markdown记笔记Jupyter Notebook跑实验配合VSCode写正式的Python脚本。整套流程完全开源免费你照着做就行。1. 内容整体设计与思路拆解笔记不是一次写完的1.1 为什么“反反复复”才是本质很多新手学机器学习有一个误区觉得买一本《西瓜书》或者刷完吴恩达的网课笔记抄得工工整整就算学会了。结果一到自己写代码连fit和predict都分不清更别提调参了。原因很简单机器学习是一个理解→实现→反馈→再理解的循环不是线性的知识堆积。我自己的切身体会是第一遍看梯度下降觉得不就是沿导数方向走几步嘛简单。第二遍自己用Python实现才发现学习率选大了loss直接爆炸选小了又龟速收敛。第三遍再回头看书上的动量更新、Adam优化器才真正明白它们解决的是什么问题。每一次回炉笔记都要大改一次而这个改动过程就是真正内化知识的过程。所以我的笔记系统从来不追求“一次成型”而是刻意留白、反复修订。每个主题的笔记都有一个“迭代日志”区域记录我第几次理解了什么、之前哪里理解错了。这个习惯让我在机器学习期末复习的时候获益匪浅别人翻厚厚的笔记本找不到重点我直接看每个知识点的迭代历史就知道哪些地方最容易出错。1.2 三层笔记体系概念层、推导层、代码层我的机器学习笔记分三层概念层、推导层、代码层。概念层对应“这是什么、能干什么”推导层对应“为什么有效、数学依据是什么”代码层对应“怎么用Python实现、参数怎么调、效果如何验证”。为什么要分三层因为这三层对大脑的刺激方式完全不同。概念层靠类比和直觉推导层靠逻辑和数学代码层靠动手和调试。混在一起记大脑会偷懒只停留在最舒服的“概念层”以为自己懂了其实根本不会用。分三层之后每层对笔记的要求也不一样概念层用大白话加生活类比推导层必须手推关键公式代码层必须有可运行的代码片段和运行结果截图。这个体系和李宏毅、吴恩达的课程结构也很贴合。他们的课程本来就是先讲直觉概念再上数学推导最后布置作业让你写代码。我做的就是把这三段式固化成笔记模板每次学完一个主题三部分都填满缺一个都不算完成。1.3 迭代的具体节奏三轮打磨法具体怎么迭代我总结了三轮打磨法。第一轮是“速记”跟着课程或教程走用最粗糙的方式把概念、公式、代码草稿塞进笔记不追求排版不追求完整只追求当时能跟上节奏。这一轮的笔记通常烂得没法看。第二轮是“复现”隔一两天后关掉课程视频打开笔记尝试根据第一轮记下的框架自己推导公式、重写代码。卡住的地方就是理解薄弱的地方回去翻视频或者查资料然后补进笔记。这一轮是内容增厚的主要阶段。第三轮是“重构”等整个课程学完或者一个大的主题结束后重新审视笔记结构把零散的知识点通过自己的理解串成体系精简冗余补充新的关联。这一轮是“出精活”的关键笔记的逻辑会变得很清晰而不是课程讲义的文字转录。三轮下来一个主题的笔记才算真正完成。这个过程听着很累但每一轮的时间其实是递减的而且效果极其扎实。我这些代码里很多“看起来写得不错”的部分都是第三轮重构时才真正想清楚的。2. 原理部分怎么记从公式到直觉2.1 梯度下降一定要动手推一遍梯度下降是机器学习里最核心的优化方法没有之一。笔记里不能只写“沿负梯度方向更新参数”这句话那样等于没记。我的笔记模板里有三块必填内容损失函数的完整形式、参数更新公式、以及一个具体例子的手推过程。以最简单的线性回归为例损失函数是均方误差这里就是求偏导的过程。我之前以为自己懂了直到自己动手对每个参数求偏导才意识到链式法则在复合函数嵌套时的威力。笔记里我一般会写一个具体数值例子假设有两个样本预测函数是y wx b初始权重为0学习率为0.01手动算两步梯度下降看看损失值怎么变化。这个过程虽然麻烦但对理解“梯度告诉你要往哪个方向走学习率告诉你走多远”这句话有奇效。还有一个很重要的点是区分损失函数、目标函数和梯度的关系。很多初学者搞混我笔记里用了一个类比你要下山目标是山谷的最低点损失函数就是你的海拔高度梯度就是脚下最陡的方向学习率就是你的步长。是不是瞬间清晰了2.2 泛化误差界给模型能力一个理论标尺你在看更进阶的资料时会碰到“泛化误差”这个概念。为啥模型在训练集上表现很好一到测试集就拉胯通俗地说就是因为模型记住了训练集上特有的噪声而没有学到普遍规律。我的笔记把泛化误差拆成三部分偏差、方差、噪声。偏差高说明模型太简单欠拟合方差高说明模型太复杂过拟合噪声是数据本身无法消除的部分。泛化误差界则是从理论上分析模型泛化能力的工具。简单说它给了我们一个可以期待的误差上限这个上限通常由训练样本数、假设空间的复杂度和置信度共同决定。我最初看泛化误差界的时候差点被数学符号劝退。后来我换了个方式理解你可以把模型想象成一个学生训练样本就是做过的习题泛化误差界就是在考场上遇到没见过的题时成绩能差到什么程度。如果这个学生把习题答案死记硬背下来过拟合换一套题就翻车如果只做了三道题就去考试数据量不足成绩波动也很大。所以机器学习里常说的“用更多数据”“让模型更简单”“正则化”本质上都是在压住这个上界。这个类比帮助我在笔记里把“为什么数据量很重要”和“为什么正则化有效”串成了一条线。2.3 损失函数与评估指标笔记里的“翻译对照表”学机器学习你会发现一个特别折磨人的现象同一个意思论文里一个叫法课程里一个叫法源代码里又是一种叫法。比如“损失函数”英文叫loss function“代价函数”叫cost functionSklearn文档里叫人提到“评分函数”Scikit-learn的score方法默认返回准确率但常用的损失函数是交叉熵或均方误差这两压根不是一个意思。我在笔记里专门维护了一个“翻译对照表”把同一个概念的多种叫法和适用场景列出来学新知识时先对齐术语能少很多不必要的困惑。这里列出我在笔记中整理的对照表概念别名别名/相关词场景损失函数代价函数、误差函数、目标函数部分情况单个样本的预测错误程度L2正则化岭回归、权重衰减、weight decay防止参数过大导致的过拟合epoch迭代轮数、全数据遍历次数训练时对完整数据集的扫描次数学习率步长、lr控制参数更新的幅度这些对照关系同样融入到你的代码注释里。我写代码时注释里会特意标清楚这个变量对应公式里的哪个符号下次回来看代码根本不用重新翻理论。比如实现梯度下降时我会在代码里写# 损失函数 L(w, b) (1/2m) * sum((y - (w*x b))^2) # 对 w 的偏导: (1/m) * sum((y - (w*x b)) * (-x))这样理论推导和代码实现直接“面对面”复习时效率极高。如果你也正在做机器学习期末复习这个习惯绝对能帮你省下大量翻书时间。3. Python代码部分怎么整理环境、模板与完整案例3.1 踩坑指南从零搭建机器学习Python环境讲代码之前必须先解决“代码在哪里跑”的问题。我见过太多新手倒在了环境配置这一步装了Python又装Anaconda结果两个版本冲突或者pip装包装到一半报错信息完全看不懂。所以你花半小时把环境搞定是很值得的。我自己目前最稳定的组合是这样的WSL Ubuntu作为Linux子系统配最新版Python外加VSCode作为编辑器。WSL的好处是既能用Linux的命令行生态又能直接用Windows的文件系统后期跑一些需要gcc、ffmpeg之类的依赖也不会太折腾。安装命令极其简单在Windows终端执行wsl --install -d Ubuntu装完后在WSL里更新软件源然后安装Python的开发环境。这里注意一定不要直接在系统级Python里乱装包要用虚拟环境。我一般每个项目建一个.venv隔离不同项目的依赖以后不会再出现某个包版本把另一个项目的环境搞坏的情况。具体命令sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-venv mkdir ~/my_ml_project cd ~/my_ml_project python3 -m venv .venv source .venv/bin/activate pip install numpy pandas matplotlib scikit-learn jupyter这里再推荐几个对新手极其友好的配置选项。第一VSCode里安装Python扩展和Jupyter扩展可以直接打开.ipynb文件跑代码还能逐格调试第二把WSL Ubuntu的终端字体和编辑器字体设置成和macOS接近的等宽字体比如Cascadia Code或者更纱黑体写代码和看公式的舒适度会提升一个档次。我指的是体验上的舒适度和系统本身没有关系纯粹是个人审美偏好。第三pip如果下载速度慢可以临时指定国内源比如清华源、豆瓣源。这一条很多教程都提过我就不重复命令了你自己搜一下“pip 清华源”就能找到。3.2 代码模板从玩具到实验的脚手架环境搞定之后建议统一用一种固定的代码组织方式不要每次都从头往上堆代码。我自己的机器学习项目目录长这样my_ml_project/ ├── data/ # 存放数据集raw和processed分开 ├── notebooks/ # Jupyter Notebook做探索性分析 ├── src/ # 正式的Python代码模型定义、训练、评估 ├── results/ # 模型输出、图表、指标结果 └── README.md # 项目的说明文档相当于这个项目的总笔记这个结构的好处是数据和代码分离实验结果可复现。每次跑实验代码、数据、结果三个部分各司其职不会像一锅粥一样堆在一起。我见过太多人在一个Notebook里又加载数据又定义模型又画图跑完一遍之后第二天根本不知道哪些代码是干嘛的别提复现了。README.md其实就是这个项目的“精活笔记”我会记录这个项目想解决的问题、核心思路、实验结论和遇到的坑。很多时候一个项目做完README.md的价值比代码本身还大因为它记录了决策过程。3.3 完整案例一KNN两行代码识别手写数字为了让你直观感受整套笔记代码的玩法我用一个最简单的算法给你演示。假设要做一个“认识猫”的分类器输入一张图片模型判断是猫还是不是猫。当然识别猫需要大量图片数据和神经网络这里我用一个更经典的数据集来演示——手写数字识别。from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score # 加载 sklearn 自带的手写数字数据集 digits datasets.load_digits() # 划分训练集和测试集60%训练40%测试随机种子固定保证可复现 X_train, X_test, y_train, y_test train_test_split( digits.data, digits.target, test_size0.4, random_state42 ) # 创建 KNN 分类器k3用欧氏距离衡量样本相似度 knn KNeighborsClassifier(n_neighbors3) knn.fit(X_train, y_train) # 预测并计算准确率 y_pred knn.predict(X_test) print(f准确率: {accuracy_score(y_test, y_pred):.4f})这段代码跑完准确率在98%以上。别看它短里面已经把机器学习里的核心概念全部包含进来了数据集划分、模型初始化、训练、预测、评估。笔记里我会重点写清楚为什么划分训练集和测试集为什么不能用测试集调参什么是随机种子这些问题的回答就是前面原理部分“泛化误差”的代码体现。你还会注意到我用了random_state42这是为了保证结果可复现。不然每次跑同一个模型因为数据划分不同准确率会波动你就很难判断改动模型带来的效果提升是真的还是随机波动。这一点我下节还会详细说。3.4 完整案例二线性回归与梯度下降的Python实现用Sklearn的KNN有一个小缺点推理过程overly封装好了初学者根本看不到模型内部是怎么根据数据更新参数的。为了串起前面的原理笔记还是要自己手写一个梯度下降的线性回归。代码我一般会在Notebook里写方便边写边看效果import numpy as np import matplotlib.pyplot as plt # 生成模拟数据: y 3x 5 噪声 rng np.random.default_rng(42) X rng.uniform(0, 10, 100) true_w, true_b 3.0, 5.0 y true_w * X true_b rng.normal(0, 2, sizeX.shape) # 初始化参数 w, b 0.0, 0.0 learning_rate 0.01 epochs 500 m len(X) # 记录每轮的损失方便后面画图 loss_history [] for epoch in range(epochs): y_pred w * X b loss np.mean((y_pred - y) ** 2) loss_history.append(loss) # 梯度计算 dw (2 / m) * np.sum((y_pred - y) * X) db (2 / m) * np.sum(y_pred - y) # 参数更新 w - learning_rate * dw b - learning_rate * db if (epoch 1) % 100 0: print(fEpoch {epoch 1}, Loss: {loss:.4f}, w: {w:.4f}, b: {b:.4f}) print(f最终: w {w:.4f}, b {b:.4f} (真实值 w{true_w}, b{true_b}))跑完这个代码你就能亲眼看到那个“反反复复”的过程每一轮迭代w和b在梯度作用下一点一点挪动loss慢慢降下来。画个损失曲线你会更直观plt.plot(loss_history) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(梯度下降的损失曲线) plt.show()一般在笔记里我会把这张损失曲线图贴上去然后在旁边标注几个关键观察点前100轮loss下降最快后面开始平坦如果学习率调大到0.1loss曲线会直接飞掉变成nan如果学习率调成0.0001500轮根本不够看loss还在高位。这些观察和结论是光看理论视频永远得不到的。3.5 数据可视化与结果记录代码层笔记还有一个非常重要的组成部分——可视化记录。机器学习实验不能只看最终的数字指标要养成把过程画出来的习惯。损失曲线、预测值和真实值的对比散点图、分类边界图、混淆矩阵这些都是你调试模型的“眼睛”。我在做“认识猫”这样的图像分类项目时会把模型的预测结果按“正确”和“错误”分组可视化错的图里再细分“把猫错认成什么”和“把什么错认成猫”通过可视化往往能发现数据或模型更深层的问题。我的笔记模板里每个实验板块都有固定的“结果区”一张过程图、一张结果表、一段结论描述。结论描述必须用完整的句子写出“因为什么所以结果怎样下一步想怎么改”。不要只放图不写字否则过一个月回来你自己都看不懂为什么当时要画这张图。图片用Markdown的![描述](图片路径)语法嵌入笔记就把分析和结果完好地保存下来了。4. 常见问题与排查技巧实录4.1 环境问题速查表机器学习里百分之八十的报错都是环境问题不是算法问题。我整理了两年来最常遇到的环境问题直接做成速查表问题常见原因解决方向pip安装包很慢或者超时网络访问境外PyPI源慢换国内镜像源一行代码解决Python环境混乱import报错找不到模块系统Python和虚拟环境混用每次进入项目目录先激活.venv检查which pythonmatplotlib画图中文显示成方块系统缺少中文字体或未设置字体设置plt.rcParams[font.sans-serif]指定中文字体再设axes.unicode_minusFalseJupyter Notebook内核连不上内核与当前虚拟环境不匹配在虚拟环境中重新执行python -m ipykernel install --userWSL里跑GUI画图弹不出来WSL缺图形显示组件或配置改用Agg后端保存图片到文件或安装WSLg组件其中“中文显示方块”是很多人最头疼的事我笔记里贴了一个固定的配置片段每次新建Notebook先运行一遍之后就再也不用折腾。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 下用黑体 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题如果你的中文环境是Linux系统换成[Noto Sans CJK SC]或[WenQuanYi Zen Hei]这类系统中文字体即可。字体名可以通过fc-list :langzh命令查出来。4.2 原理理解常见卡点我来列举几个初学者最容易在原理理解上卡住的地方。第一个卡点是“为什么梯度方向是上升方向参数更新却要减去梯度”。这个问题的本质是导数的方向。假设损失函数像一座山你想往山下走而偏导告诉你的是山在该点最陡上升的方向所以参数更新要用负号。当初我就是在这个问题上绕了很久最后用上面那个手推的例子豁然开朗的。第二个卡点是“为什么训练集上的loss已经很低了测试集上还是不准”。这就是泛化问题和过拟合问题。解决思路一般是正则化、增加训练数据、降低模型复杂度、交叉验证。笔记中一定要记录你自己跑出来的那个“训练集完美但测试集拉胯”的实际例子这比书上十句定义更有说服力。第三个卡点是“为什么不同的随机种子结果差这么多”。机器学习里很多操作都涉及随机性数据划分、参数初始化、Dropout、数据增强。随机种子相当于一个固定的抽签编号固定下来之后实验才可复现。如果别人问你结果是多少你要回答时一定要带一句“随机种子设的是多少”否则这个结果没有参考意义。4.3 机器学习代码跑不通的经典坑最后集中分享几个我见过最多、也最让人吐血的代码级坑。坑一数据没有标准化直接丢进模型。对KNN、SVM、梯度下降这类对尺度敏感的算法特征之间尺度差距过大比如一个特征范围是0到1另一个是0到100000距离计算会被大尺度特征主导模型基本等于白练。解决方法是做标准化Sklearn里的StandardScaler一行搞定但要注意先用训练集的数据fit然后对训练集和测试集都用同一个scaler去transform不要用测试集单独fit否则会数据泄露指标虚高。坑二训练集和测试集没有分开或数据泄露。这个坑隐蔽性极强。比如你提前用全量数据做了标准化再划分训练测试那测试集的一部分统计信息均值、方差就已经“泄露”给了训练过程模型在测试集上的表现被高估了。正确的顺序是先划分再标准化。任何“看过”测试集信息的操作都算数据泄露这个习惯一定要在写第一段代码时就养成。坑三把predict和predict_proba搞混。分类模型里predict返回的是预测的类别标签predict_proba返回的是属于各个类别的概率。如果你想在业务中把低置信度的预测单独处理需要概率值而不是类别。一个好朋友当初用predict调了一个线上决策系统结果因为得分全是0和1根本没法排序排查了一个下午才发现是API用错了。坑四在训练前没有固定随机种子。这个坑和上面原理部分的坑三是一体两面。代码层面最常见的表现是你跑了两遍完全相同的代码结果却不同。以为是模型写错了其实只是因为模型初始化是随机的数据划分每次也不一样。解决办法是统一设置随机种子import numpy as np import random np.random.seed(42) random.seed(42)PyTorch用户还需要多设置一个import torch torch.manual_seed(42)这一套组合拳打下来你的实验才谈得上可复现后续每次改动才有可比性。当然你要做超参数搜索或者大规模不确定性评估时会故意关掉这部分但那是后话。坑五忽视Jupyter Notebook的隐藏状态。Notebook的“反反复复”调试是很方便的但也埋了雷如果你在某个格子定义了X_train_shuffled后来删掉了那个格子但没重新运行后面的格子后面代码依然能访问到那个变量因为它在内存里还活着。所以我写代码有个习惯修改前面格子里的变量定义后必须从那个格子开始“Run All Below”不能直接跳着跑否则你会发现“这代码我明明删了怎么还能用”然后在期末复习时被自己坑得怀疑人生。最后的几句实在话写到这儿我真心觉得机器学习学习过程中“记笔记”本身就是一门技术活甚至比学算法本身更能锻炼人的工程能力。按照“反反复复出精活”的思路我现在的每一篇笔记都分概念层、推导层和代码层三层写每个实验都配有可复现的数据集划分、固定随机种子、独立训练测试流程和可视化结论。这让我从“看完课就忘”的状态里彻底走了出来。如果你也是一边学原理一边撸Python代码的人我个人体会是从今天开始就给自己立一个小规矩学完任何一个小知识点立刻写一段能跑通的Python代码哪怕就是拿内置数据集跑一个最简单的模型也比干看书强十倍。反过来代码跑通后回头把对应的数学公式在纸上推一遍把“原来代码里的那个参数就是公式里的那个符号”对应上这个过程会让你觉得自己像个真正的工程师而不再是被动接收知识的学生。最后再分享一个小技巧给你的笔记和代码文件都养成写日期的习惯比如20240605_knn_baseline.ipynb这样当你有天翻到三个月前的代码不会被自己当时怎么想的绕晕。机器学习是一条很长的路笔记和代码就是你的地图和行囊它们会跟着你一起成长。希望我的这套折腾方法能给你一点启发。
返回列表