开源AI入门课程拆解:环境、TensorFlow版本与代码链路
简介:这是一套面向零基础学习者与高校教学场景的微软人工智能课程中文版资料,按十二周、二十四课的学习路径组织内容,覆盖符号人工智能、知识表示与推理、神经网络与深度学习、计算机视觉、自然语言处理,以及遗传算法和多智能体系统等主题。包内共三百七十五个文件,以八十个 Markdown 课程讲义、五十三个 Notebook 练习代码、一百零一个示意图和三十多个配置文件为主,另有少量 Python 脚本、网页文件、视频与容器配置,压缩包约五十六点七兆。已有约一百四十人学习,适合按大纲自学、课堂演示或实验复现。资料结合 TensorFlow 与 PyTorch 代码讲解关键概念,配有测验、实验室活动和伦理人工智能原则,便于读者从概念理解走向动手实践,并借助 Notebook 逐步复现图像与文本处理模型,形成可回查、可运行的课程学习档案。
1. 从一堆跑不起来的 notebook 说起:这套 AI 入门课程到底值不值得拆
上个月帮一个做后端的 A 同学看环境,他硬盘里存了七八套“人工智能学习路线”,其中有一套是某头部软件厂商开源、已经被社区翻成中文的入门课程,讲义、练习代码、课后小测都齐。结果他卡在第三个 notebook,报错ModuleNotFoundError: No module named 'tensorflow',折腾两天就放弃了。这个反直觉的点值得先说:劝退新人的从来不是反向传播的数学,而是内核选错、版本对不上、数据路径找不到这些工程琐事。
这份 AI 入门课程资源的价值也在这里——它把“人工智能”拆成了二十多个能在一台普通笔记本上跑完的课时,从 AI 历史、神经网络基础,一路铺到计算机视觉、自然语言处理、强化学习和遗传算法,每一课配一份讲义、一份可执行的 Notebook 练习代码、一次课前小测和一次课后小测。它适合两类人:想系统过一遍 AI 基础但不想一上来啃论文的工程开发者,以及需要现成教学素材的讲师。下面按我自己拆的过程讲:结构怎么排、环境怎么钉、核心代码长什么样、哪里最容易翻车、最后怎么改成自己的东西。
2. 课程结构拆解:二十多个课时是怎么从感知机排到强化学习的
一套课程能不能自学,看目录就知道。这套东西的组织方式很“工程”:按周次切片,每周一个主题域,主题域下面再按课时编号展开。中文版在原始结构上做了一层翻译,讲义的 Markdown 单元基本全中文,代码单元里的变量名和函数名保留英文——这个取舍是对的,翻译变量名会让后面查 API 文档的人彻底断线。
2.1 仓库目录:按「周次 + 主题」两级切片的排布
我把仓库拉到本地后重命名成了ai-for-starters-zh,目录大致是这个形状:
ai-for-starters-zh/ ├── lessons/ │ ├── 1-Introduction/ │ │ ├── README.md # 本课讲义,中文 │ │ ├── notebook.ipynb # 练习代码,可直接跑 │ │ └── assignment.ipynb # 课后作业,带 TODO 空位 │ ├── 2-Symbolic/ │ ├── 3-NeuralNetworks/ │ ├── 4-ComputerVision/ │ ├── 5-NLP/ │ ├── 6-Other/ │ └── 7-Ethics/ ├── translations/ # 各语言版本的对照表与翻译进度 ├── etc/ # 图片、示意图、数据集说明 └── README.md # 总目录 + 学习路径建议这种切法的好处是每一课都自成闭环:打开lessons/4-ComputerVision/就能从讲义读到代码,不需要在仓库里来回跳。坏处也明显——课时之间的依赖是隐式的,比如第 3 周的神经网络课默认你已经理解了第 2 周讲的感知机和梯度,讲义里不会反复提醒。我一般会先把README.md的周次表抄成一张自己的进度表,标上“已跑通 / 只读未跑 / 跳过”,不然很容易在第 5 周回头补第 3 周的代码。
translations/这个目录容易被忽略。它是给多语言版本做对照用的,里面有术语表和翻译进度标记。中文版里术语不统一的情况确实存在,比如 “regression” 有的地方译“回归”,有的地方直接留英文;“embedding” 一半译“嵌入”一半写“词向量”。做二次开发或者给团队做内训时,建议自己维护一份术语对照,别指望仓库里那份是全的。
2.2 每一课的四件套:讲义、练习、小测、作业
同一个课时目录里通常有四类文件,阅读顺序错了效率会差很多。我踩过一次:先打开assignment.ipynb,结果里面全是# TODO和空的assert,完全不知道要填什么,浪费了半小时。
| 文件 | 作用 | 建议打开顺序 | 备注 |
|---|---|---|---|
README.md | 讲义正文,含概念、图示、外部链接 | 1 | 中文版主体,先通读一遍不要求全懂 |
notebook.ipynb | 可执行练习代码,边讲边跑 | 2 | 逐 cell 执行,别点“全部运行” |
| 课前/课后小测 | 选择题,检验概念 | 3 | 有的版本放在讲义内部或独立链接 |
assignment.ipynb | 课后作业,留空让你补 | 4 | 带测试 cell,跑通才算过 |
讲义和 Notebook 的分工很清晰:讲义负责讲“为什么”,Notebook 负责演示“怎么做”。以神经网络那一课为例,讲义里画了神经元、激活函数、损失函数的示意图,Notebook 里则是用几十行代码把同样的东西实现一遍。两边对照着看,比单独看任何一边都有效。
提示:Notebook 里有些 cell 会训练模型,跑一次几分钟。别用“全部运行”,按 cell 逐个跑,中间出错能立刻定位到是哪一步的数据形状不对。
2.3 依赖地图:哪些库在哪一类课里才出现
这套课程的依赖是分阶段的,不是一上来全装。搞清楚这张地图,能省掉一大半“装了用不上”的麻烦。
| 阶段 | 主要依赖 | 用在哪类课 | 是否必需 |
|---|---|---|---|
| 基础/符号 AI | numpy、pandas、matplotlib | 前几周的导论、搜索算法、知识表示 | 必需 |
| 神经网络 | tensorflow/keras、scikit-learn | 感知机、全连接网络、训练技巧 | 必需 |
| 计算机视觉 | opencv-python、Pillow、tensorflow | 卷积网络、图像分类、目标检测入门 | 必需 |
| 自然语言处理 | nltk、scikit-learn、jieba | 文本预处理、词袋/TF-IDF、情感分类 | 必需 |
| 其他 AI | scikit-learn、numpy | 遗传算法、强化学习的最小实现 | 按需 |
tensorflow是唯一一个需要重点盯版本的依赖,后面第 5 章会专门讲它和 Keras 的版本纠缠。jieba是中文版额外需要的——原始课程的文本课基于英文语料,中文版补充了中文分词示例,不加这个库那几个 cell 直接报错。opencv-python只在视觉课里用,如果你只想跑神经网络那几课,可以先不装,能省两百多兆。
3. 环境搭建:把 Jupyter 内核、依赖版本和数据路径一次钉死
环境这一步是新手最容易翻车的地方,也是最不值得反复花时间的地方。我的做法是一遍做对、把配置写成文件,以后换机器直接照着跑。
3.1 建一个干净的虚拟环境,别用系统 Python
系统 Python 是所有玄学问题的源头。我见过太多“明明装了却说找不到”的案例,本质是 pip 装到了 A 解释器,Jupyter 用的是 B 解释器。
# 1. 在仓库根目录建虚拟环境(3.10 或 3.11 都行,别用太新的) python -m venv .venv # 2. 激活。Windows 用 .venv\Scripts\activate source .venv/bin/activate # 3. 升级 pip,老版本 pip 解析依赖会失败 python -m pip install --upgrade pip # 4. 分两步装:先装基础,再装深度学习,出错好定位 pip install jupyterlab numpy pandas matplotlib seaborn scikit-learn pip install "tensorflow==2.15.1" opencv-python nltk jieba这里刻意把tensorflow钉在2.15.1。原因不在课程本身,而在 2.16 之后 Keras 被拆成了独立包,keras.layers的导入路径和旧写法对不上,课程里大量from tensorflow.keras import ...的老代码会直接崩。钉版本是成本最低的止损。
装完立刻验证一次,别等到打开 Notebook 才发现问题:
python -c "import tensorflow as tf, numpy, sklearn, cv2; print(tf.__version__, tf.config.list_physical_devices('GPU'))"这行代码做两件事:确认四个核心库都能导入,以及打印 TensorFlow 版本和可见的 GPU 列表。返回[]说没检测到 GPU,用 CPU 跑这套课程的练习完全够,手写数字那个数据集在 CPU 上训练一轮也就十几秒。
依赖装完之后把锁文件固化下来:
pip freeze > requirements.txt以后同事克隆你的改造版仓库,一句pip install -r requirements.txt就能复现你的环境。这一步看着多余,等你在第二台机器上被版本差异坑过一次,就会习惯性加上了。
3.2 注册内核 + 批量自检 notebook 能不能跑
虚拟环境建好了,Jupyter 默认还是不会用它。必须显式注册一个内核:
python -m ipykernel install --user --name ai-starters --display-name "Python (AI Starters)"--name是内核的内部标识,起个短的不带空格的;--display-name是 Notebook 界面里下拉框看到的文字,可以写人话。注册完打开 Notebook,在 Kernel 菜单里切到Python (AI Starters),右上角显示的内核名要和它一致。
仓库里几十个 notebook,手动一个个开太慢。写个脚本扫一遍,把内核不对的、没有代码单元的挑出来:
import json import pathlib bad = [] for p in pathlib.Path("lessons").rglob("*.ipynb"): nb = json.loads(p.read_text(encoding="utf-8")) # 内核名写在 metadata.kernelspec.name 里,缺省说明没设过 kernel = nb.get("metadata", {}).get("kernelspec", {}).get("name", "") n_code = sum(1 for c in nb["cells"] if c["cell_type"] == "code") if kernel != "python3" or n_code == 0: bad.append((str(p), kernel, n_code)) print(f"待修正 {len(bad)} 个") for row in bad[:10]: print(row)脚本的逻辑很短:遍历lessons下所有.ipynb,读 JSON,从metadata.kernelspec.name取内核名,统计代码单元数量。判定条件是“内核不是python3”或者“一个代码单元都没有”。前者说明这个 notebook 绑了别的内核,后者多半是纯讲义或者翻译时把代码单元弄丢了。输出待修正 0 个就说明整套练习代码可以直接跑。
注意:
metadata.kernelspec.name是python3不代表一定用你的虚拟环境,它只是默认名。真正判断标准是打开 notebook 后跑一句import sys; print(sys.executable),路径里带.venv才算对。
3.3 数据、模型、图片的路径约定
路径问题是第三大坑。Notebook 的工作目录取决于你从哪里启动 Jupyter——在仓库根目录启动和在lessons/4-ComputerVision/里启动,Path.cwd()返回的完全不一样,pd.read_csv("data/xxx.csv")这种相对路径就时灵时不灵。
我的习惯是在每个 notebook 的第一个 cell 里固定一个根目录常量:
from pathlib import Path # 从当前工作目录向上找,直到看见 lessons 目录,认定这是仓库根 ROOT = Path.cwd() while not (ROOT / "lessons").exists() and ROOT != ROOT.parent: ROOT = ROOT.parent DATA = ROOT / "etc" / "data" print("仓库根:", ROOT, "| 存在:", DATA.exists())向上回溯找lessons目录的做法,比硬编码绝对路径可移植,比os.chdir安全。后面所有读写都基于ROOT拼,notebook 从哪个目录启动都不影响。
数据集本身分两类:一类是课程自带的小样本,放在etc/下面跟着仓库走;另一类是框架自动下载的,比如keras.datasets.mnist.load_data()会拉到~/.keras/datasets/。第一类要求你把仓库完整克隆下来,别只复制lessons目录;第二类第一次运行需要能下载,下载过一次之后就缓存在本地,后面离线也能跑。
4. 核心代码拆解:从手写感知机到 Keras 卷积网络的完整链路
课程最值得逐行读的就是代码部分。它有个很好的设计:同一个概念先用最原始的 NumPy 手写一遍,再换成框架的几行 API,让你知道框架替你做了什么。
4.1 先手写一个感知机,把「权重更新」这件事看清楚
神经网络那几课的开头不是直接上 Keras,而是让你用二十行 NumPy 实现一个感知机。这段代码短,但把训练的本质讲透了。
import numpy as np class Perceptron: def __init__(self, n_features, lr=0.1, epochs=20): self.w = np.zeros(n_features) # 权重向量,长度等于特征数 self.b = 0.0 # 偏置,标量 self.lr = lr # 学习率,决定每次修正的步子多大 self.epochs = epochs # 遍历训练集的轮数 def predict(self, X): # 加权和 >= 0 判为正类 1,否则 0;阶跃函数不可导,所以只做分类不做概率 return (X @ self.w + self.b >= 0).astype(int) def fit(self, X, y): for _ in range(self.epochs): for xi, yi in zip(X, y): y_hat = int(xi @ self.w + self.b >= 0) err = yi - y_hat # 分对了 err=0,分错了 err=±1 self.w += self.lr * err * xi # 只有 err 非 0 时才真的更新 self.b += self.lr * err return selffit里那个err是整个算法的核心。分类正确时err = 0,权重一点都不动;分类错误时err是+1或-1,权重沿着输入向量的方向推一把。这就是“感知机学习规则”,也是后面梯度下降的雏形——区别只在于这里用阶跃函数直接给对错,而神经网络用损失函数的梯度给出方向和幅度。
lr的取值直接影响收敛。太大,权重会来回震荡甚至发散;太小,二十轮跑完还没走到边界。课程里给的默认值偏保守,实际换数据集时要自己调。epochs建议一开始设小一点(比如 5),看损失曲线有没有下降趋势,再往上加。
配套的小练习通常是把数据换成线性不可分的异或问题,然后让你观察准确率卡在 50% 上不去。这个设计很到位:它逼你承认单层感知机的天花板,为后面“加隐藏层”和“换激活函数”铺好了动机。
4.2 换成 Keras:全连接网络做手写数字分类
讲完手写版,神经网络课的第二段用 Keras 重做同一件事。代码短了十倍,但每一行都有讲究。
import tensorflow as tf from tensorflow import keras # 1. 数据:60000 张 28x28 的灰度图,标签是 0-9 (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() # 2. 预处理:展平成 784 维向量并归一化到 [0,1] x_train = x_train.reshape(-1, 784).astype("float32") / 255.0 x_test = x_test.reshape(-1, 784).astype("float32") / 255.0 # 3. 模型:一层隐藏层 + Dropout + 输出层 model = keras.Sequential([ keras.layers.Dense(128, activation="relu", input_shape=(784,)), keras.layers.Dropout(0.2), keras.layers.Dense(10, activation="softmax"), ]) # 4. 编译:优化器、损失函数、监控指标 model.compile( optimizer=keras.optimizers.Adam(learning_rate=1e-3), loss="sparse_categorical_crossentropy", metrics=["accuracy"], ) # 5. 训练:每批 64 张,抽 10% 做验证 history = model.fit( x_train, y_train, epochs=5, batch_size=64, validation_split=0.1, )几个参数值得单独说。reshape(-1, 784)里的-1让 NumPy 自己算样本数,784 = 28 × 28,因为Dense层只吃一维输入。除以255.0是归一化,不除的话像素值在 0-255 之间,梯度尺度太大,学习率得调到很小才能稳住。
损失函数选sparse_categorical_crossentropy而不是categorical_crossentropy,区别只在标签格式:前者吃整数标签(3),后者吃 one-hot([0,0,0,1,0,...])。MNIST 的标签本来就是整数,用 sparse 版本省一次转换,也省内存。
Dropout(0.2)放在隐藏层之后,训练时随机丢掉 20% 的神经元,是抑制过拟合最便宜的手段。验证集准确率和训练集差得远的时候,把这个值往上调;两者都很低,说明是欠拟合,该加层或者加训练轮数。
validation_split=0.1从训练集里切 5400 张出来做验证,history里能拿到每轮的val_loss和val_accuracy。跑五轮,验证准确率一般能到 97% 上下——这就是“框架替你做了梯度计算、参数更新、批次调度”之后的效果,对比 4.1 那二十行,能直观感受到抽象层的价值。
4.3 卷积网络:图像分类那一课的骨架长什么样
全连接网络把图像展平,丢掉了空间结构。计算机视觉那几课的重点就是把这个结构捡回来。
# MNIST 图像是单通道,卷积层需要 (28, 28, 1) 的形状 x_train_cnn = x_train.reshape(-1, 28, 28, 1) x_test_cnn = x_test.reshape(-1, 28, 28, 1) model = keras.Sequential([ # 第一组卷积:32 个 3x3 卷积核,提取边缘、角点这类低级特征 keras.layers.Conv2D(32, (3, 3), activation="relu", input_shape=(28, 28, 1)), keras.layers.MaxPooling2D((2, 2)), # 2x2 池化,长宽各减半 # 第二组卷积:64 个卷积核,在低级特征上组合出更复杂的图案 keras.layers.Conv2D(64, (3, 3), activation="relu"), keras.layers.MaxPooling2D((2, 2)), keras.layers.Flatten(), # 展平后接分类头 keras.layers.Dense(64, activation="relu"), keras.layers.Dense(10, activation="softmax"), ]) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.summary() # 打印每层输出形状和参数量,调结构前先看这个Conv2D的第一个参数是卷积核数量,第二个是核尺寸。数字越大提取的特征越丰富,但参数量和显存占用同步上涨。MaxPooling2D((2, 2))在保留显著特征的前提下把特征图缩小一半,既能降计算量,也能扩大后续卷积核的感受野。
model.summary()是调试时最该养成的习惯。它会打印每一层的输出形状和参数量,形状对不上的问题一眼就能看出来,比等到fit报错再往回翻快得多。这套结构在 MNIST 上跑五轮能到 99% 左右,比全连接版本高一个多点,代价是每次训练慢几倍——这就是用算力换精度最朴素的例子。
4.4 文本课的一条链路:分词、向量化、分类
自然语言处理那几课的流程和图像不一样,中间多了“文本转数字”这一环。中文版在这里做了本地化,把原本基于英文的分词换成了中文分词。
import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # 1. 分词:jieba 按词切,比按字切保留更多语义 def cut(text): return " ".join(jieba.cut(text)) texts = [cut(t) for t in raw_texts] # raw_texts 是原始中文语料 # 2. 向量化:TF-IDF,词频高但到处都有的词权重会被压低 vec = TfidfVectorizer(max_features=5000, ngram_range=(1, 2)) X = vec.fit_transform(texts) # 稀疏矩阵,形状 (样本数, 特征数) y = labels # 0/1 情感标签 # 3. 切分 + 训练 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y) clf = LogisticRegression(max_iter=1000) clf.fit(X_train, y_train) print("准确率:", clf.score(X_test, y_test))jieba.cut返回的是生成器,用" ".join()拼成空格分隔的字符串,因为TfidfVectorizer默认按空格切词。max_features=5000只保留权重最高的 5000 个词,控制特征维度;ngram_range=(1, 2)同时统计单词和相邻词组,“不 好看”这种否定结构才不会被拆散。
stratify=y保证训练集和测试集的类别比例一致,样本不均衡时这一项很关键。LogisticRegression在这里只是当基线用,跑通之后课程会引导你换成神经网络分类头,对比两者的差距——这也是整套课的一贯节奏:先给你一个能跑通的朴素方案,再让你补上更复杂的版本。
5. 常见问题排查:内核、版本、路径这三类坑占了九成
仓库里的报错看着千奇百怪,按我的经验九成能归到内核、版本、路径三类。下面五条都是我自己或身边人真实遇到的,每条按“现象—原因—解决”写。
5.1 明明装了 TensorFlow,Notebook 里还是 ModuleNotFoundError
现象:命令行pip list能看到tensorflow 2.15.1,Notebook 里import tensorflow依然报ModuleNotFoundError。
原因:Jupyter 内核指向的是系统解释器,你的pip install装进了虚拟环境,两个 Python 各活各的。pip list走的是激活后的虚拟环境,Notebook 用的是另一个。
解决:跑 3.2 那行ipykernel install注册内核,然后在 Notebook 里执行import sys; print(sys.executable),输出路径必须包含.venv。如果显示的是/usr/bin/python之类,说明内核还是没切过来,重启 JupyterLab 再选一次。
5.2 升级到新版 TensorFlow 后,所有from tensorflow.keras全部报错
现象:AttributeError: module 'keras.api._v2.keras' has no attribute ...,或者ImportError: cannot import name 'layers' from 'keras'。
原因:新版本把 Keras 3 拆成了独立包,导入路径和 API 都有变动,课程里的老代码是照着 Keras 2 写的。这不是代码写错了,是环境跑在了不匹配的版本上。
解决:退回去。pip install "tensorflow==2.15.1",这个版本内部还是绑的 Keras 2,兼容性最好。实在要用新版,就装tf-keras兼容包,并在每个 notebook 顶部把导入改成import tf_keras as keras。我一般选前者,课程练习不值得为新 API 花时间。
5.3 数据集下载超时,load_data()卡住不动
现象:跑到keras.datasets.mnist.load_data()就没反应了,等几分钟后抛TimeoutError或URLError。
原因:这个函数第一次调用会去下载mnist.npz,网络不通或者下载被限速就卡在这。
解决:手动下载mnist.npz,放到~/.keras/datasets/目录下,文件名保持原样。下次load_data()检测到本地有缓存就不走网络了。同理,视觉课里其他框架自带的数据集也是这个路径规则。放之前确认一下文件名带不带版本后缀,放错了函数依然会重新下载。
5.4 相对路径时灵时不灵,换个目录启动就 FileNotFoundError
现象:昨天还能跑,今天从另一个目录启动 Jupyter,pd.read_csv("etc/data/xxx.csv")直接报找不到文件。
原因:Notebook 的工作目录等于启动 Jupyter 时所在的目录,不是 notebook 文件所在的目录。启动位置变了,所有相对路径的基准都变了。
解决:用 3.3 里的ROOT向上回溯方案,所有路径基于ROOT拼接,别写裸的相对路径。临时排查可以在第一个 cell 加print(Path.cwd()),先看清楚当前在哪。
5.5 中文图表全是方块,matplotlib 显示不出汉字
现象:画出来的图标题、坐标轴标签里的中文全是小方块,报missing from font(s)。
原因:matplotlib 的默认字体是英文字体,没有中文字形。
解决:在绘图前显式指定一个系统里装了的字体。Windows 上常用SimHei,macOS 上常用Arial Unicode MS,Linux 上装Noto Sans CJK之后用同名。再加两行修负号显示:
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei", "Arial Unicode MS", "Noto Sans CJK SC"] plt.rcParams["axes.unicode_minus"] = False # 否则负号也会变成方块字体列表按顺序回退,写多个能在不同机器上都兼容。这个配置只影响当前内核,重启后要重新跑,所以建议放进每个绘图 notebook 的初始化 cell。
6. 二次开发:把课程 notebook 抽成能复用的训练脚本
课程 notebook 适合学习,不适合复用。想拿它改造成自己的实验模板,第一步是把训练逻辑从 cell 里抽出来,变成带参数的函数和脚本。
我一般按这张表对照着改:
| 改造点 | 课程里的写法 | 我改成什么 | 为什么 |
|---|---|---|---|
| 数据加载 | 直接调load_data() | 抽成load_dataset(name, root) | 换成自己的数据时只改一处 |
| 模型结构 | 在 cell 里定义Sequential | build_model(dropout=0.2)返回模型 | 方便做超参对比 |
| 训练参数 | 硬编码epochs=5 | argparse命令行传入 | 跑多组实验不用改代码 |
| 随机性 | 不设种子 | 固定seed并写进日志 | 结果可复现,不然调参全靠玄学 |
| 输出 | 只 print 准确率 | 存 metrics.json + 模型权重 | 事后能对比、能回溯 |
落地成一个脚本大概是这样:
# train_mnist.py —— 从 notebook 抽出来的可复现训练脚本 import argparse, json, random import numpy as np import tensorflow as tf from tensorflow import keras def set_seed(seed: int) -> None: # 三处随机源都要固定,少一个都复现不了 random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed) def build_model(dropout: float = 0.2) -> keras.Model: return keras.Sequential([ keras.layers.Dense(128, activation="relu", input_shape=(784,)), keras.layers.Dropout(dropout), keras.layers.Dense(10, activation="softmax"), ]) def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--epochs", type=int, default=5) ap.add_argument("--batch-size", type=int, default=64) ap.add_argument("--lr", type=float, default=1e-3) ap.add_argument("--seed", type=int, default=42) args = ap.parse_args() set_seed(args.seed) (x_tr, y_tr), (x_te, y_te) = keras.datasets.mnist.load_data() x_tr = x_tr.reshape(-1, 784).astype("float32") / 255.0 x_te = x_te.reshape(-1, 784).astype("float32") / 255.0 model = build_model() model.compile(optimizer=keras.optimizers.Adam(args.lr), loss="sparse_categorical_crossentropy", metrics=["accuracy"]) hist = model.fit(x_tr, y_tr, epochs=args.epochs, batch_size=args.batch_size, validation_split=0.1, verbose=2) loss, acc = model.evaluate(x_te, y_te, verbose=0) json.dump({"test_loss": loss, "test_acc": acc, "args": vars(args)}, open(f"run_seed{args.seed}.json", "w"), ensure_ascii=False, indent=2) if __name__ == "__main__": main()改完之后验证方式也变了:不再靠眼睛看 notebook 输出,而是跑两次同 seed 的训练,比对run_seed42.json里的test_acc是否完全一致。一致说明随机源固定住了,实验才具备可比性。再补一个快速冒烟测试——把--epochs设成 1、样本量砍到 1000,确认整条链路不报错再放全量跑。
从那以后我每次改造课程代码,都强制先走一遍“抽脚本、固定种子、存指标”这三步再动手调模型。不然改了七八版超参,回头想复现最好那一次,连当时用的 dropout 是多少都记不清,那种感觉比调不出精度还难受。希望帮到你。
本文还有配套的精品资源,点击获取