跳到主要内容

TensorFlow 三件套:张量、自动微分、神经层

这一章讲三件事: 深度学习框架到底替你干了什么;反向传导的「求梯度」怎么变成三行代码; 以及一个只用两个参数的神经网络如何自己找出华氏摄氏换算公式(本章主走查)。 读完这一章,后面所有章节的程序看起来都是同一个故事的变奏。

1. 框架为什么存在:三件套

第 02 章末尾把训练总结成「正向算损失、反向求梯度、按梯度更新」。手工做一遍就知道哪里疼:求梯度要手推链式法则,矩阵运算要手写且慢,几十层网络要手搭。深度学习框架的存在理由就是包掉这三件疼事,书里把 TensorFlow 的核心功能列为三条1:

  1. 张量运算——各种向量、矩阵运算(第 01 章的工具,这里变成基础设施);
  2. 自动微分——反向传导的梯度自动算(第 02 章的链式法则,这里变成一行代码);
  3. 神经层——现成的网络构件(第 02 章的「多条回归线」,这里变成积木)。

先记住这个「三件套」,后面所有的框架、所有的模型,都是这三件事的不同排列组合。

背景交代:TensorFlow 是 Google Brain 于 2015 年发布的框架,占有率与 PyTorch 分居前两名2;本书全部案例用它。另一条要交代的现实:TensorFlow 2.x 起默认 Eager Execution(立即执行)模式,废弃了 1.x 的 Session 语法——网上教程新旧杂陈,1.x 的代码在 2.x 里直接报错,书里借电视剧对白评价:「已经回不去了」3

还有一个容易踩的坑:Keras 起初是独立框架,后来被 TensorFlow 收编重写,Keras 之父 François Chollet 已宣告独立版不再升级——要写 Keras,就写在 TensorFlow 里4

2. 张量运算:算得快,而且自动上 GPU

TensorFlow 的张量运算「遵循 NumPy 的设计理念与语法」,一个重要机制是传播(Broadcasting):形状不同的张量做运算时,小的一边自动复制补齐。显示数值要转成 NumPy 数组;reduce_sum 沿特定轴加总,比如 [1, 2, 3] 加总后等于 6,输出比输入少一维5

GPU 那部分,书里做了一组反直觉的实测:

执行时机CPUGPU
第一次(冷启动)64.00ms311.49ms(GPU 反而慢)
多次之后58.00ms1.00ms

第一行打破了「GPU 一定快」的想当然——GPU 有初始化开销,只在重复运算上赚回来6

两个框架的行为差异值得记:TensorFlow 自动决定数据放 CPU 还是 GPU(常数默认放 CPU,变量放 GPU,运算时自动搬运);PyTorch 必须手动搬,且不允许一个变量在 CPU、另一个在 GPU7。这个差异决定了两家入门体验的很大一部分。另有一个工程坑:TensorFlow 的 GPU 显存(显卡自带的内存)回收不完美,长跑任务常报内存不足,书里的处方是重启 Kernel 或限制显存配额8

3. 自动微分:GradientTape,反向传导的正式形态

第 02 章说「框架提供自动微分,不用手推」,现在兑现。工具叫 tf.GradientTape(梯度录音带):在它的上下文里做的每一步运算都被「录」下来,之后随时可以回放求导。

书里的三组实测,把第 02 章的数学逐条对上:

  • f(x)=x²:求 x=3 处的导数,得 f′(3)=6(手算 2×3=6,分毫不差)9;
  • 二阶导数:把求导过程再套一层,得一阶导 6.0、二阶导 2.0——x² 的二阶导确实是常数 2,第 01 章「二阶导定凹凸」的说法有了代码版10;
  • 多变量:z=x⁴ 在 x=3 处 dz/dx=108(4×3³=108)11

一个使用细节:变量(tf.Variable)自动参与微分,常数要手动 g.watch() 才行12。对照另一家:PyTorch 的三步是 requires_grad=True(声明参与)、y.backward()(反向求导)、x.grad(取梯度)——书里顺手放了这个对照,换框架时不至于两眼一抹黑。

然后把第 02 章的「简单线性回归」原样重跑:定义损失 MSE,用 GradientTape 求梯度,按「w −= 学习率×梯度」更新,训练后得到 w=0.9464、b=0.0326,损失逐轮下降,回归线穿过样本点中间13第 01 章「把未知数打包成矩阵一次求解」的模式,在这里兑现成「全部权重一次更新」——第 02 章纸面上的循环,到这里变成了十几行代码。

4. 神经层:Dense 就是 y=wx+b

每次都手写 GradientTape 循环仍然太累。框架的最上层是神经层——现成的网络积木。书里先给结构命名:输入层、隐藏层(夹在输入与输出之间的层)、输出层;隐藏层 ≥2 层,就称为深度学习(这是书里的口径)14

最基础的积木叫 Dense(完全连接(上一层每个神经元都连到下一层每个)层):上一层的每个神经元连到下一层的每个神经元。关键的一句是书里自己点破的:

一个只含 Dense 的模型,「其实就等于 y=wx+b」——Dense 的 use_bias 参数就是那条偏差项 b15

也就是说,Dense 层=第 01 章的直线方程。 神经网络的神秘感可以就此放下:它的原子构件就是一条直线,「深度」来自把几百条直线摞起来、中间夹非线性。

主走查:让神经网络自己发现华氏摄氏公式

书里设计了一个漂亮的实验,是本章的主走查:

背景:华氏与摄氏的换算是物理书上的公式——F = C × 9/5 + 32。这次不告诉模型公式。

第一步,造数据:按这个公式生成 151 组(摄氏,华氏)配对,喂给模型。

第二步,搭模型:只有一个 Dense 层,输入一个神经元(摄氏),输出一个神经元(华氏)。按上面的等式,这个模型就是把 y=wx+b 里的 w、b 各对应一个待定参数

第三步,训练:普通的 fit,损失逐轮下降。

第四步,验收:拿两个没参与训练的整数去测——

输入摄氏 100 度,模型输出华氏 212.00;输入摄氏 0 度,输出 32.00。两个答案全对16

第五步,拆开看模型学到了什么:读出参数——w=1.8000,b=31.9999。对照公式 F=C×(9/5)+32:9/5=1.8,截距 32。书里的评语是:学到的参数「近似于华氏与摄氏」的换算公式17

这件事为什么值得停一下: 没有人告诉模型「乘 1.8、加 32」;它只看了 151 对数字和一条朴素的反馈(猜错了就调参数),就把公式从数据里抠了出来——w 精确到 1.8000,b 只差约万分之一。全书后面每一章的「训练」,无论模型多大,底层的动作都和这个实验一模一样:给数据、定损失、按梯度调参数。差别只在:辨识手写数字时,「公式」变成几百万个参数共同表达的一套规则,没人能把它写成一行。

判断(我们的,不是书里的): 这个实验也是理解「模型容量」的最好教具——只有两个参数的模型只能学出直线,所以它恰好能学会一个一次公式;给同样数据一个带弯的模型,它反而可能学出过拟合的怪曲线。模型容量要和问题的真实复杂度(参数多少、结构多繁)匹配。 如果错,会错在: 如果任务本身不是线性关系(比如后面章节的图像识别),单层 Dense 就学不动,会误以为「神经网络不过如此」;反过来,把简单任务交给超大模型,也解释不了它为什么「学不出花来」。

5. 可带走的

  1. 框架三件套:张量运算、自动微分、神经层——看懂这三件,任何深度学习框架的文档都不再陌生;
  2. 写 Keras 要写在 TensorFlow 里,独立版 Keras 已停止升级;TensorFlow 2.x 默认 Eager Execution,网上 1.x 代码会报错;
  3. GPU 首次执行反而慢(311ms vs 64ms),赢了在重复运算(1ms vs 58ms);TF 自动搬数据,PyTorch 手动搬;
  4. GradientTape 把第 02 章的链式法则变成三行代码:f′(3)=6、二阶导 2.0、x⁴ 在 3 处 108,全部与手算一致;变量自动参与微分,常数要 watch;
  5. Dense 层=y=wx+b,use_bias 就是那条偏差项;隐藏层 ≥2 层即深度学习(书中口径);
  6. 主走查:151 组数据训练出的单层模型,w=1.8000、b=31.9999,输入摄氏 100 度输出 212.00 华氏度——神经网络把教科书公式从数据里学了回来;
  7. 模型容量要匹配问题复杂度:两个参数恰好装下一次公式;任务更复杂时,同样的方法学出的就是没人写得出的「分布式公式」。

6. 原文地图

主题原书章原文位置
TensorFlow 2015 发布、框架格局第3章 TensorFlow架构与主要功能text/18-ch02-tensorflow.txt:7(搜「Google Brain于2015年」) · text/19-ch03-3-tensorflow.txt:15(搜「占有率的前两名」)
框架三大功能3-2 TensorFlow架构text/20-ch03-3-2-tensorflow.txt:7(搜「张量运算:包括」)
Eager Execution、Keras 停止升级3-2 TensorFlow架构text/20-ch03-3-2-tensorflow.txt:59(搜「Eager Execution Mode」) · text/20-ch03-3-2-tensorflow.txt:31(搜「不再升级」)
reduce_sum、GPU 自动调度3-3 张量运算text/21-ch03-3-3.txt:43(搜「reduce_sum」) · text/21-ch03-3-3.txt:45(搜「PyTorch框架稍显麻烦」)
GPU 冷启动实测3-3 张量运算text/21-ch03-3-3.txt:59(搜「64.00ms」) · text/21-ch03-3-3.txt:61(搜「311.49」)
GradientTape 三组实测3-4 自动微分text/22-ch03-3-4.txt:11(搜「GradientTape()函数可自动微分」) · text/22-ch03-3-4.txt:29(搜「二阶导数=2.0」) · text/22-ch03-3-4.txt:43(搜「dz/dx=108」)
watch() 与线性回归 w=0.94643-4 自动微分text/22-ch03-3-4.txt:23(搜「watch()」) · text/22-ch03-3-4.txt:85(搜「w=0.9464」)
隐藏层 ≥2 层即深度学习3-5 神经网络层text/23-ch03-3-5.txt:7(搜「隐藏层大于或等于两层」)
Dense=y=wx+b3-5 神经网络层text/23-ch03-3-5.txt:31(搜「use_bias」)
华氏摄氏实验(主走查)3-5 神经网络层text/23-ch03-3-5.txt:59(搜「华氏(F)=摄氏(C)」) · text/23-ch03-3-5.txt:77(搜「华氏(F):212.00」) · text/23-ch03-3-5.txt:83(搜「w:1.8000」)

Footnotes

  1. 出处:「3-2 TensorFlow架构」第 7 段(text/20-ch03-3-2-tensorflow.txt:7,搜「张量运算:包括」)。三大功能中的后两项(自动微分、神经层)在同一段的 (2)(3) 条。

  2. 出处:「第二篇 TensorFlow基础篇」第 7 段(text/18-ch02-tensorflow.txt:7,搜「Google Brain于2015年」)与「第3章 TensorFlow架构与主要功能」第 15 段(text/19-ch03-3-tensorflow.txt:15,搜「占有率的前两名」)。

  3. 出处:「3-2 TensorFlow架构」第 59 段(text/20-ch03-3-2-tensorflow.txt:59,搜「Eager Execution Mode」)与「3-3 张量运算」第 85 段(text/21-ch03-3-3.txt:85,搜「已经回不去了」)。

  4. 出处:「3-2 TensorFlow架构」第 31 段(text/20-ch03-3-2-tensorflow.txt:31,搜「不再升级」)。

  5. 出处:「3-3 张量运算」第 43 段(text/21-ch03-3-3.txt:43,搜「reduce_sum」)。

  6. 出处:「3-3 张量运算」第 59 段(text/21-ch03-3-3.txt:59,搜「64.00ms」)与第 61 段(text/21-ch03-3-3.txt:61,搜「311.49」)。两次实测的对照是书中原始数字。

  7. 出处:「3-3 张量运算」第 45 段(text/21-ch03-3-3.txt:45,搜「PyTorch框架稍显麻烦」)。

  8. 出处:「3-3 张量运算」第 101 段(text/21-ch03-3-3.txt:101,搜「GEMM」)。

  9. 出处:「3-4 自动微分」第 11 段(text/22-ch03-3-4.txt:11,搜「GradientTape()函数可自动微分」)与第 19 段(text/22-ch03-3-4.txt:19,搜「f′(x)= 2x」)。

  10. 出处:「3-4 自动微分」第 29 段(text/22-ch03-3-4.txt:29,搜「二阶导数=2.0」)。

  11. 出处:「3-4 自动微分」第 43 段(text/22-ch03-3-4.txt:43,搜「dz/dx=108」)。

  12. 出处:「3-4 自动微分」第 23 段(text/22-ch03-3-4.txt:23,搜「watch()」)。

  13. 出处:「3-4 自动微分」第 85 段(text/22-ch03-3-4.txt:85,搜「w=0.9464」)。

  14. 出处:「3-5 神经网络层」第 7 段(text/23-ch03-3-5.txt:7,搜「隐藏层大于或等于两层」)。

  15. 出处:「3-5 神经网络层」第 31 段(text/23-ch03-3-5.txt:31,搜「use_bias」)。

  16. 出处:「3-5 神经网络层」第 77 段(text/23-ch03-3-5.txt:77,搜「华氏(F):212.00」)。摄氏 0 度对应华氏 32.00 度在同段。

  17. 出处:「3-5 神经网络层」第 59 段(text/23-ch03-3-5.txt:59,搜「华氏(F)=摄氏(C)」)与第 83 段(text/23-ch03-3-5.txt:83,搜「w:1.8000」)。