
简介围绕李宏毅机器学习课程整理的全套学习资料面向希望系统入门并进阶机器学习的学习者覆盖监督学习、无监督学习、强化学习等核心主题兼顾理论推导与代码实践适合课前预习、课后复盘与作业练习场景。包内共234个文件压缩包约70.21MB教学PPT与PDF讲稿承载核心概念与公式讲解49个Python脚本与3个ipynb笔记本对应课程实验与模型训练25个CSV数据集可用于动手复现流程另有Markdown笔记与示意图辅助理解作业解答部分便于对照检验。目前已有2497人学习下载。目录按章节组织可循着课程进度逐层推进从数据预处理、特征选择到模型训练与误差分析读者能获得从听懂到跑通完整链路的参考材料为后续人工智能方向深入打基础。1. 一套中文机器学习课程资料价值在讲义与作业而不在视频时长硬盘里放着几十个 G 的课程录像播放进度条却永远停在第三讲这是很多人下载公开课资料后的真实状态。这套中文机器学习全套资料的结构不一样课堂讲义 PDF、手写推导板书、课后作业说明与示例代码骨架覆盖从回归、分类、CNN、RNN 到注意力与生成模型的主线。它要解决的具体问题是中文语境下把公式推导和可运行代码对应起来而不只是听人讲得通俗。适合两类人有 Python 基础但数学忘得差不多的转岗工程师以及刷过英文课程却卡在推导细节的同学。下面按资料怎么拆、代码怎么跑、坑在哪推进。2. 拆开三层结构讲义、手写推导、作业骨架的对应关系这套资料最容易出现的误用方式是把它当成一套视频课来收藏从头到尾线性播放。实际使用时应先弄清四类文件各自负责什么再按模型复杂度排一条自己的主线最后把环境一次性配好。三层结构搭配好了后面每一章的学习都有明确的“验收动作”而不是看完就忘。2.1 四类文件各自解决什么问题刚解压完目录时容易懵一堆 PDF、一堆 notebook、还有几个压缩包。我一般先按“密度”给它们分档密度高的当索引密度低的当练习场。下面这张表是我自己拆包后的归类方式不同年份的资料文件名会变但结构基本一致文件类型信息密度什么时候看常见误用课堂讲义 PDF高一页可能压着一整个模型的公式上新课前扫一遍先知道这节要推什么当成教材逐字读卡在符号定义上出不来手写推导板书中补齐讲义省略的中间步骤讲义某一步跳过去了立刻对照看只看不写看完仍然推不出来作业说明与评分口径低但定义了评价指标动手写代码前先读完跳过说明直接跑 sample code示例代码骨架低通常只有一个勉强跑通的 baseline第一天就把它跑通跑不通先修环境直接在骨架上改模型结构讲义 PDF 的特点是“结论密度极高”。一页里可能同时出现损失函数、梯度表达式和一个假设条件中间三行代数变换被省掉了这正是板书存在的理由它不负责排版美观只负责把跳过的步骤补回来。作业说明则常被忽略但它决定了你最后提交的是什么指标如果评分看的是 macro-F1 而你一直在盯 accuracy后面所有调参都会指错方向。示例代码骨架的定位要再强调一次它是环境自检工具本身没有性能。我见过不少人一上来就往骨架里塞 Transformer结果训练三轮不收敛回头才发现数据加载的 label 映射是反的。先让骨架原封不动跑出它文档里写的那个数字再谈改。2.2 按模型复杂度排六周主线而不是按视频顺序课程视频的顺序是按学期排的跟着走会被学期节奏拖住。比较实用的做法是按“模型复杂度”重排每一周都配一个可以当场验收的动作验收不过就不往下走。资料里通常包含作业一到作业若干正好可以充当检查点周次主题主要看哪份资料验收标准做不到就别往下走第 1 周回归与梯度下降线性回归讲义 对应板书能手推损失对参数的偏导并写出 30 行 numpy 版本第 2 周分类与正则化logistic / softmax 讲义、偏差方差那节能解释验证集上 loss 与 acc 曲线为什么背离第 3 周训练技巧反向传播、初始化、BN、dropout能说清 BN 在 train 与 eval 两个模式下的差别第 4 周卷积网络卷积、池化、感受野能手算任意一层卷积的输出尺寸与参数量第 5 周序列与注意力RNN、LSTM、注意力机制能手写缩放点积注意力并核对 shape第 6 周生成模型VAE、GAN、扩散的概念页能逐项说出损失函数里每一项在约束什么这张表的用法是每周只挑一份讲义精读其余当参考。第 1 周如果连“为什么学习率大会震荡”都讲不清第 3 周的优化器对比就纯属背结论。反过来如果第 4 周手算卷积尺寸已经很熟可以直接跳到第 5 周把时间留给注意力那几页推导——那几页是全套资料里信息密度最高的部分之一。2.3 环境一个能跑完全部 demo 的最小依赖资料里的 notebook 大多写于不同年份直接pip install最新版大概率会撞上 API 变更。我的习惯是建一个独立环境把版本钉死先保证“能跑”再谈“跑得好”。CPU 版本足够应付所有教学 demo不必一上来就折腾显卡驱动# 建一个只给这套资料用的环境避免污染主环境 conda create -n ml-course python3.10 -y conda activate ml-course # 教学 demo 的主力依赖版本按资料里 notebook 的写法挑 pip install numpy1.26.4 pandas2.2.2 matplotlib3.8.4 pip install scikit-learn1.4.2 jupyterlab4.1.5 pip install torch2.2.2 torchvision0.17.2 # 装 CPU 版即可 # 把环境注册成 jupyter kernel避免 notebook 用错解释器 python -m ipykernel install --user --name ml-course --display-name ml-course这段命令的逻辑是“先隔离、再钉版本、最后绑定内核”。conda create单独建环境是为了防止后面某个包升级把主环境里的项目搞崩numpy和pandas钉在中版本而不是最新版因为资料里 notebook 用的还是np.float_这类旧写法新版本已经移除。torch装 CPU 版就够教学 demo 的模型规模撑死几万参数用显卡属于浪费。最后一步ipykernel install经常被漏掉漏掉的典型症状是命令行里import torch正常notebook 里却报ModuleNotFoundError——因为 Jupyter 用的是另一个解释器。提示如果 notebook 打开后 kernel 列表里没有ml-course先执行jupyter kernelspec list看注册是否成功再重启 JupyterLab 进程。环境跑通之后建议立刻做一次“冒烟测试”随便打开一个示例 notebook从第一格执行到最后一格中途不许跳过。这一步能提前暴露 80% 的环境问题比学到第三周再发现依赖缺失划算得多。3. 把讲义公式落成代码三个最小可跑片段打通推导与实现讲义上的公式看懂和写出来之间隔着一层东西这层东西只能靠手敲代码来补。下面三个片段对应资料主线里最关键的三个节点梯度下降、softmax 分类、注意力。每个片段都不长但都刻意保留了“容易写错的那一行”跑完之后再回去看讲义会有明显的对照感。3.1 线性回归闭式解和小批量梯度下降差在哪线性回归是整套资料的第一页也是最容易被跳过的部分。很多人直接调sklearn.linear_model.LinearRegression出个结果就往下走结果学到后面反向传播时完全没有“梯度”的实感。建议先手写一遍小批量版本把梯度表达式和自己推的对一遍import numpy as np rng np.random.default_rng(42) X rng.normal(size(512, 8)) # 512 个样本8 个特征 true_w rng.normal(size8) y X true_w 0.1 * rng.normal(size512) # 故意加噪声别让 loss 掉到 0 造成“完美收敛”的错觉 w np.zeros(8) b 0.0 lr, bs, epochs 0.05, 64, 200 for ep in range(epochs): idx rng.permutation(len(X)) # 每个 epoch 重新打乱避免固定顺序带来周期性震荡 for i in range(0, len(X), bs): batch idx[i:i bs] xb, yb X[batch], y[batch] err xb w b - yb grad_w 2 * xb.T err / len(batch) # 均方误差对 w 的梯度2 来自平方项求导 grad_b 2 * err.mean() w - lr * grad_w b - lr * grad_b print(参数误差:, np.linalg.norm(w - true_w), 截距:, round(b, 4))这段代码的逻辑是标准的“前向算误差、反向算梯度、原地更新”。关键在grad_w那一行xb.T err把所有样本的“特征 × 残差”累加成一个 8 维向量再除以批大小取均值得到的正是均方误差对w的梯度。很多人第一次写会漏掉除以len(batch)结果学习率必须调到极小才不炸然后误以为“梯度下降很难调”。参数上有三个旋钮值得手动改一遍看现象lr从 0.05 调到 0.5loss 曲线会从平滑下降变成来回横跳bs从 64 调到 512梯度噪声变小、收敛更稳但每轮更慢epochs加到 2000 而 loss 不再下降说明模型容量到此为止再训也没用。这三个现象在讲义里都是文字描述自己调一遍才知道“震荡”长什么样。跑完之后再和闭式解np.linalg.lstsq(X, y, rcondNone)[0]对比一下。两者结果应该非常接近差距只来自噪声和迭代轮数。这个对照很有价值它告诉你梯度下降不是“近似版闭式解”而是在数据量大到无法求逆时的替代路径。3.2 softmax 回归为什么不建议自己写 softmax 再取 log分类那几页讲义会给出交叉熵的公式形式上很干净。但一落到代码最常见的翻车写法是先softmax再log然后在概率接近 0 时得到-inf。正确做法是让框架在 logits 上直接算交叉熵import torch import torch.nn.functional as F torch.manual_seed(0) N, D, C 2000, 20, 5 X torch.randn(N, D) W_true torch.randn(D, C) y (X W_true).argmax(dim1) # 用 argmax 造标签保证数据近似线性可分 model torch.nn.Linear(D, C) # 只留一层线性专注验证损失函数本身 opt torch.optim.SGD(model.parameters(), lr0.1) for step in range(500): logits model(X) # 注意这里输出的是 logits不是概率 loss F.cross_entropy(logits, y) # 内部走 log_softmax NLL数值上更稳 opt.zero_grad() loss.backward() opt.step() acc (model(X).argmax(dim1) y).float().mean() print(loss:, round(loss.item(), 4), acc:, round(acc.item(), 4))逻辑说明F.cross_entropy接收的是未归一化的 logits内部用 log-sum-exp 技巧计算等价于先做 softmax 再取负对数但避免了指数溢出。这是讲义里一句话带过、实际写代码却天天遇到的细节。参数上lr0.1对纯线性模型是合适的换成一个三层 MLP 就得降到 1e-3 量级500步是个保守值看 loss 曲线趋平就可以停。想验证数值稳定性可以手动把某个 logit 加到 100 再跑一次用cross_entropy不会出问题自己写torch.log(torch.softmax(...))就会拿到-inf或者 nan。这个对比实验建议真做一遍比看十遍公式管用。另外提醒一句造标签时用argmax而不是随机生成是为了让准确率能爬到接近 1。如果标签是随机的模型最多学到 20%你会误以为代码写错了其实是数据本身没信号。3.3 缩放点积注意力十行代码验证那个 √d_k注意力那几页是整套资料里推导最密的部分尤其是缩放因子为什么是√d_k。光看公式容易觉得“乘个常数而已”写一遍再改一改就明白了import math import torch def scaled_dot_product_attention(Q, K, V, maskNone): d_k Q.size(-1) scores Q K.transpose(-2, -1) / math.sqrt(d_k) # 缩放项不是装饰d_k 大时点积方差会爆 if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) # 用 -inf 而非 0softmax 后正好是零权重 attn torch.softmax(scores, dim-1) return attn V, attn torch.manual_seed(1) Q torch.randn(2, 4, 8) # batch2, 序列长度4, d_k8 K torch.randn(2, 4, 8) V torch.randn(2, 4, 8) out, attn scaled_dot_product_attention(Q, K, V) print(输出 shape:, out.shape) # 期望 (2, 4, 8) print(权重行和:, attn.sum(-1)) # 每一行应接近 1逻辑说明Q K.transpose(-2, -1)得到的是(batch, seq_q, seq_k)的相似度矩阵transpose里的两个负索引是为了兼容任意前导维度避免写死 batch 维。除以sqrt(d_k)之后过 softmax最后与V相乘完成加权求和。参数验证建议做两组。第一组把d_k从 8 改到 512注释掉缩放那一行观察attn的分布它会退化成接近 one-hot 的尖锐分布梯度几乎消失这就是“不缩放会训不动”的具体表现。第二组加上 mask把mask设成上三角为 0 的下三角矩阵检查attn的上三角是否恰好为 0——如果写成masked_fill(mask 0, 0)softmax 会把 0 当成一个合法得分上三角就会漏权重这是实现 causal mask 时最常见的 bug。三组代码跑完讲义的三个核心节点就从“看懂”变成了“写得出”。此时再回看板书里的推导会发现那些当时觉得多余的中间步骤恰好对应代码里最容易漏的那几行。4. 作业本的打开方式从 baseline 到调参的固定顺序资料里的作业通常带评分口径和一份很朴素的 baseline很多人上来就改模型结构改完发现分数还不如 baseline。问题不在模型在顺序。下面这套顺序是我踩过几次坑之后固定下来的先出分、再切数据、最后才调参。每一步都有对应的检查动作跳步的代价会在后面成倍放大。4.1 第一天只做一件事让 baseline 原封不动出分打开作业目录先别改任何一行代码。按说明里的命令跑一遍确认输出格式、文件路径、评价指标都对得上。这一步的验收标准很明确你能拿到一个和说明里写着的分数处于同一量级的数字。跑 baseline 时最容易忽略的是“提交格式”。作业往往要求输出特定格式的文件比如每行一个预测标签或者指定编码的 csv。模型分数再高格式不对就是零分。我一般会先写一个最笨的校验脚本把输出读回来检查行数、标签取值范围、是否有多余表头import pandas as pd sub pd.read_csv(output/pred.csv) assert len(sub) 10000, f行数不对: {len(sub)} # 和测试集规模对齐 assert set(sub[label].unique()) set(range(10)), 标签越界 assert sub[label].notna().all(), 存在空值 print(格式检查通过标签分布:, sub[label].value_counts().to_dict())这段逻辑很短但把三类事故挡在门外行数少了几百行通常是 dropna 惹的祸、标签超出类别范围类别映射表写错、存在空值模型对某些样本输出 nan。花五分钟写它能省掉后面反复上传、反复被判格式错误的来回。拿到 baseline 分数之后把它记在一个固定的表格里作为后续所有实验的参照线。没有参照线的调参就是碰运气。4.2 切分数据的顺序决定后面所有结论可不可信第二件事是切数据而不是调模型。很多作业只给了训练集和测试集验证集要自己切。这里的顺序很重要先切分、再做任何全局统计均值方差归一化、词表构建、类别权重计算否则验证集的信息会通过统计量渗进训练过程分数虚高线上崩盘。from sklearn.model_selection import train_test_split # 先分层切出验证集random_state 固定保证每次实验可比 X_tr, X_val, y_tr, y_val train_test_split( X, y, test_size0.15, stratifyy, random_state42 ) # 归一化统计量只能来自训练集 mu, sigma X_tr.mean(axis0), X_tr.std(axis0) 1e-8 X_tr (X_tr - mu) / sigma X_val (X_val - mu) / sigma # 验证集用训练集的统计量不能自己重算逻辑说明stratifyy保证类别比例一致避免小类别在验证集里只剩几个样本导致指标抖动random_state固定是为了让“改动前后”的对比有意义——如果每次切分都变你永远不知道涨点是来自模型还是来自数据。归一化的两行是重点mu和sigma只从X_tr算验证集直接套用这是一条必须守住的纪律。检查动作也很具体切完之后打印两个集合的标签分布差异超过几个百分点就说明切分有问题再打印归一化后的均值方差训练集应该接近 0 和 1验证集允许有小偏差但不该差太远。4.3 调参顺序表先学习率、再正则、最后才动结构到了真正调参的环节顺序错了会浪费大量时间。我的一般做法是固定模型结构先把学习率扫到一个能稳定收敛的值再处理过拟合最后才考虑加容量。理由很直白——学习率不对的时候任何正则化手段的效果都无法判断。顺序调什么观察什么判断标准常见错误第 1 步学习率前 20 步的 loss单调下降且不爆直接上 1e-2 训 Transformer第 2 步批大小loss 曲线的抖动幅度抖动可接受、显存够用批太小却怪模型不收敛第 3 步正则化强度训练集与验证集的 gapgap 收窄且验证集不降一上来就把 dropout 拉到 0.5第 4 步模型容量训练 loss 的下限训练 loss 明显下降训练 loss 都降不下去还加正则第 5 步集成与后处理验证集最终指标稳定复现用测试集反复挑模型这张表的核心是“别跳步”。第 4 步尤其容易被误解如果训练 loss 都降不到一个较低水平说明模型根本没拟合上此时加 dropout、加权重衰减只会让情况更糟。判断“欠拟合还是过拟合”只需要看两条曲线的 gap这个动作在两分钟之内就能完成但它决定了后面几小时往哪个方向走。作业里的 leaderboard 分数可以看但别拿它当调参信号。用测试集反复挑模型本质上是在测试集上过拟合这个习惯一旦养成后面做真实项目时会非常危险。验证集上稳定复现的提升才算提升。5. 避坑与排查跟这套资料学习最容易翻车的五个地方这一节记录的是我自己和身边人真实踩过的坑现象都很有迷惑性原因往往和直觉相反。按“现象 → 原因 → 解决”整理遇到问题时可以直接对照。现象一notebook 里import torch报找不到模块但终端里明明能导入。原因是 Jupyter 内核和你激活的 conda 环境不是同一个。解法是先python -m ipykernel install --user --name ml-course注册内核再在 notebook 右上角手动切换到ml-course切完仍然不行就执行import sys; print(sys.executable)看实际路径。这个问题在同时装了系统 Python 和 conda 的机器上几乎必现。现象二讲义上的公式自己能推但一写代码 shape 就对不上。原因通常是混淆了“样本维”和“特征维”的位置。数学写法里矩阵乘法常常省略维度顺序代码里却必须明确。解法是养成习惯每个张量创建后立刻打印 shape并在旁边写一行注释说明每一维代表什么。注意力那部分尤其明显(batch, seq, d_k)和(seq, batch, d_k)只差一个transpose但报错信息完全不指向那里。现象三模型训练 loss 一路下降验证集准确率却在第 3 轮之后开始掉。这是典型的过拟合但很多人第一反应是“模型不够大”。真实原因往往是训练集太小或者正则太弱。解法按顺序试先看训练集规模是否支撑当前参数量再加权重衰减或 dropout最后才考虑数据增强。反过来做先加容量会让 gap 更大。现象四按照板书改了损失函数loss 变成 nan。原因多半是除零或者对零取对数。交叉熵里如果某一类概率恰好是 0log(0)直接炸。解法是不要在概率上取对数把 logits 喂给框架的cross_entropy或者log_softmax自己实现时在 log 里加一个1e-8同时检查分母有没有可能为 0。这个坑在实现 Focal Loss、KL 散度时同样会碰到。现象五作业提交上去分数极低但本地验证集指标很好。原因一般有两个类别映射顺序和官方不一致或者用了全局统计量做归一化导致信息泄漏。解法是回到 4.1 节那个校验脚本逐项核对行数、标签范围、空值再检查归一化统计量是不是只用训练集算的。这类问题不看代码只看分数是查不出来的必须把输出读回来验。6. 进阶用法把讲义当自测题库手推一遍再代码验一遍学到后面会发现一个问题讲义看第二遍时觉得“都懂”合上又讲不出来。这是输入型学习的通病。我的解法是把讲义当成题库用每页提炼一个可验证的问题先手推再用十行代码验证。下面这张表是我自己整理的自测清单覆盖资料里最关键的几页讲义主题自测问题代码验证方式梯度下降学习率翻倍后loss 曲线为什么可能震荡改lr跑三组画出 loss 曲线对比偏差方差训练误差低、验证误差高属于哪一边固定数据只改模型复杂度画两条误差曲线批归一化训练和推理时 BN 的行为差在哪手动切model.eval()前后对比同一批输入的输出卷积尺寸输入 32×32、卷积核 3×3、步长 2、padding 1输出多大用nn.Conv2d跑一遍和手算结果对照注意力为什么除以√d_k而不是d_k去掉缩放项观察 softmax 输出的熵这张表的用法是“先遮住答案”。手推的时候允许卡住卡住之后再翻板书记忆强度完全不一样。代码验证环节不需要写得多漂亮十几行、跑通、打印出关键中间量就够。我习惯把每次自测的结论写在一页纸上隔一周再拿出来重推一遍能推出来的才算真的会了。再往上一个层次可以试着把讲义里的模型做“减法复现”只保留核心的那一项损失、去掉所有正则和技巧看它在一个极小数据集上能不能收敛。比如把注意力简化成单头、把序列长度压到 4、把d_k压到 8跑通之后再逐步加回多头、mask、位置编码。每加一项都能清楚知道它解决了什么问题——这比一次性抄一个完整实现有价值得多。血泪经验是抄来的代码能跑但你不知道哪一行在起作用出问题就只能瞎改。最后一个技巧关于复习节奏。这套资料的讲义页数不少通读一遍成本很高比较划算的做法是只反复看两类页面一类是带完整推导的通常是每个模型的第一次出现一类是带对比表格的比如几种优化器的差异。前者训练推导能力后者在面试和方案选型时直接用得上。中间那些纯结论页扫一眼留个印象就行。从那以后我每次拿到一套新课程资料都会先挑三页手推、再用三十行代码验一遍验不过就不往下看。慢是慢了点但省掉了“看完一遍什么都没留下”的后悔药。希望帮到你。本文还有配套的精品资源点击获取