代码炼器
你以为写 for 循环就够了——逐个元素算,多直觉。可循环之魔正盘踞在你的直觉傲慢里:它不住在 for 循环里,住在你"以为 for 循环就够了"的地方。每一笔天真的代价,它都给你记着账。
炼器殿的门
殿门没有锁。推开时你闻到一股烧焦的铜味——像有什么东西在这里算了一整夜,烧穿了自己。正中悬着一块发光的石板,底座刻着一行小字:"此为炼器之台。代码即法器,算力即灵力。"贝叶斯先师递来一块玉简——上面刻着四个字:Jupyter Notebook。
「接住。在 cell 中输入 %lsmagic——这是法器的铭文。它认了主,才肯为你算。」
「for 循环……谁都写得出 for 循环。打赌吧——你押哪段用 for 写和 NumPy 差不多?押错一笔,我记一笔。」
速度之赌挑战器 · Jupyter 认主 + 反直觉下注 + 性能差距图谱
认主 %lsmagic → 三段代码剪影押注 → 计时器弹出真容 → 性能差距图谱拼合
性能差距图谱
详解 · K02-12 Jupyter Notebook与实验管理
炼器之台的原型就是 Jupyter:以 cell 为执行单元,Shift+Enter 逐块运行,代码、输出、笔记同页共存——「认主」的 %lsmagic 列出全部魔法命令,正是 S0 台上那块铭文。三个工程要点:①内核状态陷阱——cell 可以乱序执行,变量残留自上次运行,实验复现不了时先 Restart & Run All;②计时要用 %timeit(自动多次运行取最优,本章 S0 三段计时数据即出自它),单次 %%time 会被系统抖动污染;③实验管理三件套——参数写进 cell 顶部、随机种子固定(np.random.seed(42)、torch.manual_seed(42))、产出用 nbconvert --to html 归档。修炼者最常见的翻车:改了参数忘了跑全本、拿旧图讲新结论——法器认了主,账还是要你自己记。
语法试炼台 · Python 的底子
试炼台是一面巨大的石壁,上面空着无数凹槽,等着被代码填满。贝叶斯先师递来一枚刻刀:"刻对了,凹槽亮起来;刻错了,石壁会吃掉你的字。"循环之魔盘踞在石壁顶端:"从最简单的开始——求 1 到 100 万的和。你先写个 for 循环,多顺手啊。"
「对是对了。但你有没有觉得……慢了点?石碑角落会弹出计时——别怪我,怪你的写法。」
语法试炼台 · for 累加 vs 内置 sum + 5 张语法卡片
刻 for 循环求和 → 计时浮现 → 换 sum() 重写 → 点亮 5 张语法卡片
详解 · K02-01 Python基础语法:数据类型/控制流/函数/类
石壁上五张卡片的底细:①变量是名字标签不是盒子——动态类型,类型跟着对象走,运行时才绑定(d2l Ch1 的起点);②列表是可变引用——b = a 之后 b.append(x) 会改到 a,浅拷贝 .copy() 只复制第一层,嵌套结构要 copy.deepcopy;③字典平均 O(1) 靠哈希,最坏 O(n) 在键碰撞时出现;④函数是一等公民——可作参数传递,map/回调的根基;⑤推导式 [x*x for x in range(10)] 走 CPython 专用字节码,比等价 for+append 快得多。S1 台上 85ms vs 12ms 的账:内置 sum() 的循环发生在 C 层,Python 字节码逐条解释的开销被整段跳过。另记一笔:Python 3 的 int 是任意精度,不受 32/64 位限制——这是它灵活的来源,也是它慢的来源。
法器觉醒 · ndarray 的真名
那块刻着 import numpy as np 的石板悬浮在半空,表面流动着蓝色的光纹。你伸手触碰——光纹涌入指尖,掌心浮现一个发光的括号 []。循环之魔在暗处冷笑:"一个数组而已。你以为换个名字就能跑得更快?"
「ndarray 不是更快的列表。它是一块连续内存上的同质数据。点验广播三规则;走通三种索引光路——它才肯醒。」
法器觉醒 · 连续内存网格 + 广播维度标尺 + 索引迷宫
对比 list 与 ndarray 内存布局 → 点验广播三规则 → 点亮布尔/花式/切片三种索引
ndarray 连续内存网格 · 整批运算可视化
广播维度标尺 · 从右向左比较
索引迷宫 · 三种取法光路
详解 · K02-02 NumPy核心:ndarray/广播机制/索引切片
ndarray 三要素:dtype(同质类型)、shape、strides(每维步长)——一块连续内存加这三张说明书,就是法器的全部本体(d2l Ch2)。对比 S2 台上那条「指针分散」的 list:每个元素都是独立 PyObject,访问要多跳一次指针、多算一次引用计数。广播三规则的机器口径:从右向左逐维比较,缺失补 1,为 1 则拉伸复制——(3,4)×(4,) 右对齐后 (4 vs 4) 相容,(1,4) 拉伸成 (3,4);反例 (3,4)×(3,) 直接报 shapes not aligned。索引光路里藏着一个工程暗雷:切片 a[1:4] 返回视图——改视图就是改原数组;布尔索引 a[a>5] 和花式索引 a[[0,2,4]] 返回副本。np.shares_memory(a, b) 一查便知。多少「数据莫名被改」的 bug,源头都是把切片当副本用了。
循环之刑 · 三重 for 矩阵乘
试炼台尽头是一面巨幅石壁,上面浮着两个 1000×1000 的矩阵——密密麻麻的数字,像两片数字的汪洋。循环之魔从暗影里完全显形:一个由无数 for 关键字缠绕而成的巨人,每一个 for 都是一根肋骨。"来吧,用你最拿手的三重 for 循环,把这两个矩阵乘出来。"
「一个一个算,多直观。你感觉到了吗?每一个 for 都在替你数数——10 亿次迭代,我替你数。」
「写。然后看时间。再用法器——np.dot,一行。」
循环之刑 · 三重 for 进度爬行 + 向量化光束 + 15000x 差距柱状图
执行三重 for 看龟速爬行 → 换 np.dot 光束贯穿 → 差距具现柱状图
三重 for 循环矩阵乘 · 1000×1000
详解 · K02-04 向量化编程思维:避免循环/批量运算/内存效率
亲手验算这笔 15000 倍的账:1000×1000 矩阵乘 = 10⁹ 次乘加;纯 Python 每次迭代背约 123ns 的解释器开销(字节码分发、类型检查、引用计数)→ 123 秒;np.dot 交给 BLAS——SIMD 指令一次算 4~8 个 double、分块计算让缓存命中拉满 → 8ms。两层差距相乘,就是悬崖。向量化三原则(d2l Ch2):①整批运算——把「对每个元素做什么」翻译成「对整块数组做什么」;②避免中间副本——a += b 原位更新,c = a + b 另开一块内存,长管道里临时对象会吃掉性能;③选对 API——np.sum 与 Python sum 是两个世界(S0 已验:差 100 倍)。内存效率的真相:向量化省的是解释器对象开销,不必然省内存。它的边界也记下:数据量极小(数百以内)或逻辑含跨步依赖(a[i] 依赖 a[i−1] 的递推)时,for 反而是对的选择——S10 Boss 会回来收这笔账。
线性代数殿 · 矩阵的心脏
穹顶大厅里悬浮着无数矩阵——有的方正端庄,有的边缘在颤抖。贝叶斯先师指着石壁上的方程组 Ax=b:"三百年前人类用手算这个。现在你用一行代码。但一行写错了,比手算还糟。"循环之魔从穹顶传来:"先求逆再乘——多直觉啊,教科书第一页就教了。"
「拖动条件数滑块,亲手看 inv 在病态矩阵上的误差比 solve 大多少。inv 把条件数平方放大——solve 用 LU 分解从不显式构造 A⁻¹。」
线性代数殿 · inv/solve/lstsq/SVD + 条件数滑块 + 矩阵形态
四种解法切换 → 拖动条件数看误差 → 点亮 4 种矩阵形态
详解 · K02-03 NumPy线性代数操作:矩阵运算/特征值/SVD
inv 与 solve 的误差账(d2l Ch2 数值细节):x = A⁻¹b 这条路,先算 A⁻¹(自身误差已 ~cond(A)·ε),再乘 b 时误差再乘一次 cond(A)——累计可达 cond(A)²·ε;np.linalg.solve 走 LU 分解 + 前代回代,误差 ~cond(A)·ε,且从不显式构造逆。条件数滑块推到 10¹⁰ 以上时 inv 曲线飞出画布,就是这个平方项。经典病态样本:Hilbert 矩阵 12 阶条件数已达 10¹⁶ 量级,双精度 ε≈2.2×10⁻¹⁶,inv 路线的答案几乎每一位都是噪声。按形态选刀:方阵良态 → solve;超定(行>列)→ lstsq(底层 SVD 求最小二乘);秩亏 → np.linalg.svd——A = UΣVᵀ,秩 = 非零奇异值个数,伪逆 pinv = VΣ⁺Uᵀ。特征值:对称矩阵用 eigh(快且数值稳),一般矩阵才用 eig——矩阵的心脏,跳的也是数值稳定的节拍。
数据炼洗池 · DataFrame
穹顶落下的卷轴在你面前展开——有的缺了半行,有的涂改得面目全非,有的字迹根本不属于这张纸。贝叶斯先师递来第二把法器:一枚刻着 import pandas as pd 的玉牌。"这叫 DataFrame,能把混乱的数据变成秩序。但前提是——你用对了方法。"循环之魔从池边探出头:"一行一行洗嘛,用 apply 逐行处理,多清楚。"
「一行一行来,多稳。功能正确就行——慢点怕什么?账,我继续记。」
数据炼洗池 · 脏数据可视化 + apply vs 向量化计时
看缺失/重复/类型错/异常 → 逐行 apply 120ms → 向量化 2ms → Pandas 觉醒
详解 · K02-05 Pandas核心:DataFrame/Series/数据读写
第二把法器的构造:Series 是带索引的一维数组,DataFrame 是共享同一索引的列集合(d2l Ch3)——读写用 read_csv / to_parquet(列式存储,重读比 CSV 快数倍)。S5 台上 120ms vs 2ms 的机理:df.apply(clean_row, axis=1) 逐行回调 Python 函数,每行跨一次 Python-C 边界;向量化写法 df['age'].fillna(0).astype(int) 在 C 层整列处理。能用整列运算、np.where、.str 访问器替代的,就别用 apply——这是 Pandas 的第一军规。两大向量化重器:groupby 走 split-apply-combine 三段式(df.groupby('dept')['income'].mean()),merge 做类 SQL 连接(how='inner'/'left')。取数分两把钥匙:.loc 按标签、.iloc 按位置——混用是新手事故高发区。Pandas 的一切便捷,都建立在「整列一次流过」这条铁律上。
详解 · K02-06 数据清洗:缺失值/异常值/数据类型转换
炼洗池四种脏数据的对症药方:①缺失——dropna 整行删(缺得少时)、fillna(中位数) 补数值列、时序数据用 ffill 前向填充;②重复——drop_duplicates(subset=['id']) 按 key 去重;③类型错——pd.to_numeric(col, errors='coerce') 把 'abc' 这类脏值统一转成 NaN 再处理,比逐个 try/except 干净得多;④异常值——IQR 法(低于 Q1−1.5·IQR 或高于 Q3+1.5·IQR 判异常)或 3σ 法(需正态假设)。S5 台上那张表是完整教材:age=200 是离群点、income='abc' 是类型错、income=−999 是哨兵值陷阱——这类「看起来是数」的占位符必须先 replace 成 NaN 再统计,否则均值全错。清洗顺序有讲究:先去重、再转类型、最后处理缺失与异常——顺序错一步,后面的统计量就在脏数据上白算一遍。
精度深渊 · 0.1+0.2≠0.3
池水退去,地面裂开一道缝。你往下看——没有底。缝隙里飘出低沉的嗡鸣,像无数个数字在同时报错。贝叶斯先师蹲下身,在裂缝边缘写下:0.1 + 0.2。"你猜,等于几?"循环之魔冷笑:"0.3 呗,幼儿园就会了。"缝隙深处的嗡鸣忽然清晰了一瞬——那不是数字,是一个声音:"64 位装不下无穷。"
「写 softmax 不减最大值会溢出成 nan。减去最大值——这是数值稳定的救赎。逐一验证五种陷阱。」
精度深渊 · 52 位尾数可视化 + softmax 稳定化 + 陷阱清单
验证 0.1+0.2 → 看 52 位尾数截断 → softmax 溢出成 nan → 减最大值稳定
0.1 + 0.2 = ? · IEEE 754 double
数值稳定性陷阱清单 · 五种
详解 · K02-08 数值计算陷阱:浮点精度/溢出/数值稳定性
深渊的结构(花书 Ch3):IEEE 754 double 共 64 位——1 符号 + 11 指数 + 52 尾数,机器精度 ε = 2⁻⁵² ≈ 2.22×10⁻¹⁶。0.1 的二进制是 0.000110011… 无限循环,52 位尾数处截断——所以 0.1+0.2 = 0.30000000000000004,这不是 bug,是有限位装不下无限循环的物理限制。溢出的边界:float64 上限约 1.8×10³⁰⁸,e⁷¹⁰ 附近就到顶——裸 softmax 喂 1000 直接 inf,再相除变 nan;减去最大值后最大项变 e⁰=1,分母永远 ≥1,这就是「稳定 softmax」的全部机关(log-sum-exp 技巧同源)。灾难性抵消:1e16+1−1e16 = 0——因为 2⁵³≈9.0×10¹⁵ 以上浮点间距已 ≥2,那个 1 根本没被存下。工程守则三条:判等用 np.allclose(默认 rtol=1e-5);精确累加用 math.fsum 或 Kahan 补偿求和;计算顺序先减大量、再算小量差。
可视化之镜 · 照见真容
深渊合拢处升起一面巨大的铜镜,镜面暗淡无光。贝叶斯先师递来一支光笔:"把你的数据画上去。数字会骗你,图形不会。"循环之魔缩在角落,第一次沉默了——它怕镜子。你在镜面上画下第一笔——镜面亮了,数字变成了形状。
「拖动数据量滑块,看 for 曲线和向量化曲线在 10⁴ 处分叉、10⁶ 处差距拉到 100 倍。图形是数值计算的诊断器。」
可视化之镜 · 性能曲线 + 误差箱线图 + 缺失值热力图
画 for vs 向量化曲线 → 误差箱线图 → 缺失值热力图
for vs 向量化 · 耗时曲线(拖动数据量)
inv vs solve · 相对误差箱线图
缺失值热力图 · 清洗前后
详解 · K02-07 数据可视化:Matplotlib/Seaborn基础
铜镜的铸造工艺(d2l Ch3):Matplotlib 的心智模型是 figure(画布)→ axes(坐标系)→ plot/scatter/hist 逐层落笔;Seaborn 在其上封装统计图——heatmap 看缺失模式、boxplot 看分布、pairplot 看特征两两关系。镜中三幅图各是一门诊断术:①性能曲线用 log 轴——指数级差异在普通轴上是一条贴地直线,log 轴上才显出 S7 台上 10⁴ 处的分叉;②箱线图读五数——中位线、箱体(Q1~Q3 即 IQR)、须(1.5·IQR 内最远点)、须外点即异常值——比均值±标准差稳健得多,均值会被一个 200 拉飞而中位数纹丝不动(这正是 S8 RobustScaler 的伏笔);③缺失值热力图判断缺失机制——随机缺失(MCAR)还是与某列相关(MAR),决定了 fillna 策略敢不敢用。「数字会骗你,图形不会」的准确含义:单点数值看不见分布形状,而形状里藏着系统性偏差。
预处理工坊 · 起跑线
铜镜之后是一间锻造工坊,墙上挂着各种量具——有的刻度均匀,有的只有 0 和 1。贝叶斯先师拿起一把量具:"你的数据,年龄从 0 到 100,收入从 0 到 100 万。把它们丢进同一个模型——收入会碾碎年龄。"循环之魔靠在门框上:"那就一个一个标准化嘛,逐列算。"先师没理它,只看你:"用向量化。但注意顺序——先划分,还是先标准化?"
「先标准化再划分,多省事——一次 fit 全部数据。验证集不也在里面嘛,有什么关系?」
预处理工坊 · Scaler/编码 + 数据泄露可视化 + K-fold
切 StandardScaler/MinMaxScaler/OneHot → 切换划分顺序看泄露 → K-fold 5 折
数据泄露陷阱 · 划分顺序决定一切
K-fold 交叉验证 · 5 折轮流验证
详解 · K02-10 数据预处理:标准化/归一化/编码/特征缩放
工坊里四把量具的精确刻度(d2l Ch3;ESL Ch3),用台上数据 [25, 30, 35, 200, 28] 亲手验算:StandardScaler 做 (x−μ)/σ,μ=63.6、σ≈68.3——一个异常值 200 把 σ 从约 5 拉到 68,200 自己的 z 分数只有 2.0,正常点全被压扁在 ±0.6 内;MinMaxScaler 压到 [0,1],但 max=200 意味着其余值全挤进 0~0.06;RobustScaler 用中位数 30 和 IQR=7——抗污染,异常值该多大就多大(200 → 24.3),这是 S5 箱线图智慧的直接复用。OneHotEncoder:类别 → 每类一列 0/1;无序类别若直接编码 0/1/2,模型会读出「CS < EE < ME」的大小关系。缩放为什么必须:梯度下降的等高线被大量纲特征拉成狭长椭圆、步长难选;KNN、PCA 这类靠距离吃饭的方法,不缩放就等于让收入特征独裁。
详解 · K02-11 数据集划分:训练集/验证集/测试集/K-fold
三集分工(ESL Ch7):训练集学参数、验证集调超参数、测试集只在最终评估碰一次——常用 60/20/20,大数据集可到 98/1/1。S8 台上「泄露推演」的机理:先对全量数据 fit scaler 再划分,验证集的 μ/σ 已经渗进了训练流程——你在评估一个偷看过考卷的模型,验证分数 95% 的幻觉,真实泛化只有 78%。正确顺序:train_test_split 划分 → 用训练集 fit → 验证集只 transform。K-fold 交叉验证:5 折轮流做验证集(每折 80% 训练 / 20% 验证),报均值±标准差——小数据集上单次划分的运气成分被平均掉。终极防线是 sklearn Pipeline:把 scaler 和模型串成一条管道,交叉验证时 fit 自动只发生在训练折上——用机制防手滑,而不是靠人记纪律。
梯度之门 · autograd 与工具链
金光中浮现第三把法器——一枚刻着 import torch 的玉牌,表面流动着前向和反向的光流。贝叶斯先师在空中画了一个函数:y = (x²).sum()。"你手算 dy/dx 要多久?让法器自己算。"循环之魔嗤笑:"求导嘛,一个一个来——"先师没等它说完,把玉牌递给你。
「前向建图,反向求导。点前向看蓝色光流,点反向看金色光流沿链式法则回溯。再把张量移到 GPU——成百上千条光线同时走。」
梯度之门 · 计算图 + 三大 ML 库 + GPU 并行
构建计算图 → 前向/反向光流 → 点亮三大 ML 库 → GPU 并行对比
计算图 · y=(x²).sum() · 前向蓝 / 反向金
CPU 串行 vs GPU 并行 · 大矩阵乘
详解 · K02-09 自动微分初步:计算图与梯度计算
三种求导路线的分野(d2l Ch13):数值微分 (f(x+ε)−f(x−ε))/2ε 简单但有截断误差,且每个参数要独立算两次前向;符号微分会表达式膨胀,式子越推越长;自动微分(AD)把导数信息编码进计算图——精确、高效、可程序化。台上这条图的完整账目:前向 x=3 → x²=9 → sum()=9,每步运算被记录成图节点;反向从 dy/dy=1 出发沿链式法则回溯,经过 sum(梯度 1)到 x²(局部导数 2x=6),得 dy/dx = 6.0——正是金色光流里那串 ∇ 值。反向模式的威力:一次回溯同时得到全部参数的梯度——损失函数一个输出、百万个参数,也只要一趟;代价是必须保存中间值(这就是训练吃显存的主要来源)。PyTorch 三行咒语:x.requires_grad_(True); y = (x**2).sum(); y.backward(),随后 x.grad 即 6.0。
详解 · K02-13 常用ML库概览:scikit-learn/XGBoost/LightGBM
三面镜子的来历:scikit-learn 用统一的 fit/predict/transform API 收编了传统机器学习(线性模型、树、聚类、预处理)——换模型只改一行,Pipeline 防泄露的机制也在这里(S8 的终极防线);XGBoost(Chen 2016)把梯度提升树做到工业强度——目标函数带二阶泰勒展开与正则项(叶权 L2 + 树复杂度惩罚),结构化/表格数据上长期是竞赛与业务的实质标准;LightGBM(Ke 2017)两项加速——leaf-wise 生长(每次分裂增益最大的叶子,而非整层推进)+ 特征直方图分桶,同精度下比 XGBoost 快约 3-5 倍,台上镜面那个倍数即由此来。选型直觉:表格数据先试树系,图像/序列/表征学习才轮到深度学习——库不是越新越好,是越对症越好。
详解 · K02-14 GPU计算基础:CUDA/PyTorch GPU编程入门
并行之地的地形(PyTorch 文档;d2l Ch13):CPU 少量强核(大缓存、高主频、串行逻辑强)vs GPU 数千弱核(SIMT——单指令多线程,一条指令驱使成千上万个线程同时走)。矩阵乘 10⁹ 次乘加彼此独立、天然可并行——S9 台上大矩阵乘 CPU 约 2.3s、GPU 约 12ms,约 190 倍的差距就来自这个结构差异。CUDA 的线程层级:grid → block → thread,同 block 线程可经共享显存同步协作。PyTorch 入门只需两条:x = x.to('cuda')、model.to('cuda')——数据与模型必须在同一设备,否则报 device mismatch。两个新手陷阱:①主机内存 ↔ 显存走 PCIe,小任务传输开销吃掉全部收益——batch 要够大才喂得饱 GPU;②显存是独立容量(8~24GB 级),泄漏无处 swap,nvidia-smi 是你的探照灯。
Boss 战 · 循环之魔三问
殿门尽头是一片虚空。循环之魔盘踞在虚空中,身形比之前庞大十倍——无数 for 关键字编织成它的鳞片,每一片都在缓慢转动,发出齿轮咬合的噪音。它身后是一面巨大的石碑,上面空空如也。"你以为学会了向量化就能赢我?回答三个问题。答对一个,我退一步;答错一个——你的法器碎一把。"石碑上开始浮现字迹——你这一路刻下的每一道排除刻痕,正在一一亮起。
「三问。第一问翻循环账,第二问追本质,第三问——用你刻下的七道刻痕收网。你以为 for 循环是错的?最后我问你:什么时候 for 反而比向量化好?」
循环债累计 0 笔 · 三问全对后清零
复盘 · 代码炼器主线
本章从 Jupyter 认主开始,历经语法试炼台(S1 动态类型与一等函数)、法器觉醒(S2 广播三规则与三索引)、循环之刑(S3 性能账)、线性代数殿(S4 inv vs solve)、数据炼洗池(S5 脏数据)、精度深渊(S6 IEEE 754)、可视化之镜(S7 图形诊断)、预处理工坊(S8 泄露与划分)、梯度之门(S9 自动微分),最终在循环之魔三问(S10)中完成境界突破。核心脉络是从「逐个元素计算」到「向量化整批处理」的思维转变——d2l Ch2 反复敲打的底层逻辑:把「对每个元素做什么」翻译成「对整块数组做什么」。七道排除刻痕逐条记账:手写 for 累加(85ms,内置 sum 12ms、np.sum 0.8ms)、ndarray 误解(它不是更快的列表,而是连续内存上的同质数据块)、三重 for 矩阵乘(123 秒 vs np.dot 8 毫秒,15000 倍)、先求逆再乘(inv 病态误差被条件数平方放大,solve 走 LU 从不构造 A⁻¹)、apply 逐行处理(Pandas 里披着 API 外衣的 for)、浮点精度误解(softmax 溢出、大数相减吃掉有效位)、数据泄露(先标准化后划分,验证集分数虚高)。三把法器各司其职:NumPy 管数值向量化(ndarray + 广播 + BLAS)、Pandas 管数据向量化(DataFrame + 向量化算子)、PyTorch 管梯度向量化(计算图 + 自动微分 + GPU 并行)——三者合起来正是机器学习工具链的完整骨架。台上四件组件可反复回炼:S0 速度之赌预测挑战器、S6 精度深渊三旋钮实验台、S8 数据泄露四选一推演器、S10 循环之魔排除法收网器。最终领悟与 S10 石碑同文:向量化是工具而非信仰——数据量极小或逻辑含跨步依赖时,for 循环仍是正确选择。