1.1 TensorFlow 是什么:一张会求导的计算图


文档摘要

1.1 TensorFlow 是什么:一张会求导的计算图 本节摘要:TensorFlow 是一个端到端的深度学习框架,核心能力是把你写的 Python 计算编译成一张数据流图,在 CPU、GPU、TPU 上自动调度执行,并沿图自动求导。本节回答一个比"怎么装"更根本的问题:框架到底替你干了哪些手动写 NumPy 干不了的事,以及为什么"计算图"这个视角能统一解释全框架的行为。全书的排程室视角就从这里立起来。 学习目标 阅读完本节,你应当能够: 说出深度学习框架相对手写 NumPy 的三个核心增量; 用"节点是运算、边是张量"的语言描述一段简单代码对应的计算图; 解释"前向三线循环"——前向、反向、更新——在训练中的分工;

1.1 TensorFlow 是什么:一张会求导的计算图

本节摘要:TensorFlow 是一个端到端的深度学习框架,核心能力是把你写的 Python 计算编译成一张数据流图,在 CPU、GPU、TPU 上自动调度执行,并沿图自动求导。本节回答一个比"怎么装"更根本的问题:框架到底替你干了哪些手动写 NumPy 干不了的事,以及为什么"计算图"这个视角能统一解释全框架的行为。全书的排程室视角就从这里立起来。

学习目标

阅读完本节,你应当能够:

  1. 说出深度学习框架相对手写 NumPy 的三个核心增量;
  2. 用"节点是运算、边是张量"的语言描述一段简单代码对应的计算图;
  3. 解释"前向三线循环"——前向、反向、更新——在训练中的分工;
  4. 说明 TensorFlow 与 NumPy 的关键差异在执行层面体现在哪里。

先看现场:手写反向传播有多疼

假设不用任何框架,用 NumPy 训练一个两层网络。前向传播不难,几十行矩阵乘法而已。疼的是反向传播:你得为每一个运算手推梯度公式,链式法则一层层展开,矩阵维度稍一变化推导就得重来;再疼的是设备调度:矩阵乘到一千维以上 CPU 跑不动了,你得自己调 BLAS 的 GPU 版本、自己管显存分配、自己决定哪些数据什么时候拷过去。第三疼是算子效率:softmax 交叉熵这种复合运算,朴素实现的数值稳定性与性能都不堪用。

TensorFlow 的解法可以浓缩成一句话:你只描述计算,框架负责调度执行并沿图求导。 你写的每一行张量代码,都会被登记成图上的节点;框架拿到整张图后,可以整体优化(算子融合、常量折叠、自动放置设备),可以沿图反向遍历自动算出每个参数的梯度,可以把同一张图反复分发给 GPU 的成百上千个核心。这三个增量,分别对应排程室里的三件事:登记、优化调度、按图执行

图 3 同一段计算的两种身份:NumPy 手写与图上登记

图 3 同一段计算的两种身份:NumPy 手写与图上登记

核心原理:把一段代码翻译成一张图

排程室的第一条规矩:任何计算都先登记成图,再谈执行。 看一小段代码:

import tensorflow as tf # 两个输入张量:批大小 2,特征数 3 x = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) # 一个权重张量:3 个特征映射到 2 个输出 w = tf.constant([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) # 矩阵乘法:登记一个 MatMul 工单 y = tf.matmul(x, w) # 逐元素加偏置:登记一个 Add 工单 b = tf.constant([0.5, 0.5]) z = tf.add(y, b) print(z) # 输出: # tf.Tensor( # [[1.9 2.5] # [4.6 5.5]], shape=(2, 2), dtype=float32)

在排程室视角下,这五行代码做的事是:登记一个 MatMul 节点,登记一个 Add 节点,两个节点之间由张量 y 连接。xwb 是常量源节点,z 是汇节点。数据沿边流动:3 维特征被 MatMul 映射成 2 维,再加偏置得到最终输出。训练时的循环也一样是图上的调度:

# 训练的三条调度线,先建立直观,细节在 1.5 与第 5 章展开 y_true = tf.constant([[0.0, 1.0], [1.0, 0.0]]) # 监督信号 w_var = tf.Variable(tf.zeros([3, 2])) # 变量:图上唯一可写状态 opt = tf.optimizers.SGD(learning_rate=0.1) with tf.GradientTape() as tape: # 线一:前向,同时被录制 y_pred = tf.matmul(x, w_var) loss = tf.reduce_mean(tf.square(y_true - y_pred)) # 误差信号 grads = tape.gradient(loss, [w_var]) # 线二:反向,沿图遍历求梯度 opt.apply_gradients(zip(grads, [w_var])) # 线三:更新,写入变量

三行核心调用对应三条调度线:前向计算损失(顺流而下)、反向求梯度(溯流而上)、优化器写参数(落笔到变量)。全册所有内容——数据管道、Keras、分布式、部署——都是围绕这三条线做文章:要么让第一条线吃得更饱(数据管道),要么把三条线安排到更多设备上(分布式),要么把训练好的图搬去别处执行(部署)。

与 NumPy 的边界:像的部分与不像的部分

TensorFlow 张量操作的手感刻意做得像 NumPy:切片、广播、 reshape 几乎一一对应。但执行层面有三处关键差异,越早知道越少踩坑:

维度 NumPy TensorFlow
求导 无,需手推 沿图自动微分
执行时机 立即算出结果 Eager 下立即,图模式下延后
设备 单机 CPU 为主 自动跨 CPU、GPU、TPU 调度
异步执行 GPU 端运算可异步排队
# 差异一的具体体现:异步执行下,错误可能延迟出现 import tensorflow as tf a = tf.constant([1.0, 2.0]) b = tf.constant([3.0]) try: c = a + b # 形状不兼容,但报错可能不是在这一行抛出 except Exception as e: print(type(e).__name__, str(e)[:60]) # Eager 模式下通常此处即抛 InvalidArgumentError; # 图模式(tf.function 内)则可能推迟到下一次具体执行时才报, # 因为图只是被登记了,还没真正运行。
# 差异二的具体体现:打印张量经常"没有数值" v = tf.Variable(3.0) r = v * 2 print(r) # tf.Tensor(6.0, shape=(), dtype=float32) —— Eager 下有值 g = tf.function(lambda t: t * 2) # 登记成图函数 concrete = g.get_concrete_function(tf.TensorSpec(shape=(), dtype=tf.float32)) print(concrete) # ConcreteFunction —— 只有图结构,谈不上"值"

第二段代码里那个 ConcreteFunction 就是图模式的产物:函数被"跟踪"成图后,它的身份是一段待执行的调度计划,而不是某个具体结果。1.5 节会把这个机制讲透。

工程视角:什么时候该选 TensorFlow

框架选型没有绝对答案,但有工程取舍。TensorFlow 的优势集中在:部署链路完整(服务器 Serving、移动端 Lite、浏览器 tfjs 三线齐备,见第 5 章)、分布式与 TPU 支持成熟、tf.data 管道在大规模数据吞吐上表现稳定、SavedModel 格式对生产环境友好。Keras 作为官方高级 API 让入门曲线足够平缓。而它的图模式虽有性能收益,调试时确实比即时执行多一层心智负担——这恰恰是本书坚持排程室视角的原因:把图看懂,这层负担就不存在了。

⚠️ 常见坑:网上大量旧教程还是 1.x 写法(tf.Sessionplaceholder)。TensorFlow 2.x 已默认 Eager 执行,Session 被移出主 API。看到 feed_dict 字样的代码先确认版本,否则照抄必报错。

💡 关键直觉:遇到任何没见过的 TensorFlow API,先问三个问题——它往图里加了什么节点?它何时真正执行?它改变哪条调度线的时序?三问之下,绝大多数 API 的行为可以预判。

本节要点回顾

  • 框架三增量:自动求导、设备调度、算子复用,分别对应图的登记、优化、执行。
  • 计算图:节点是运算,边是流动的张量,训练是前向、反向、更新三条线的循环。
  • 与 NumPy 的差异:求导能力、执行时机、异步与设备调度,是行为差异的根源。
  • 图模式产物:被跟踪的函数是"调度计划",没有即时数值,错误可能延迟出现。
  • 选型取舍:部署链路与大规模吞吐是 TensorFlow 的长板,调试心智负担靠看懂图化解。

下一节把环境搭起来,跑通你登记的第一张图。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U