2、TensorFlow基础概念:张量、计算图、会话与Eager Execution
好,咱们正式开始啃 TensorFlow 的硬骨头了。
这一章,我带你捋清楚四个最核心的概念:张量、计算图、会话,还有后来居上的 Eager Execution。说白了,这四个东西就是 TensorFlow 的骨架。你把这四个搞明白了,后面写模型就跟搭积木一样顺手。
2.1 张量(Tensor)—— 数据的基本单位
先说说张量。你把它理解成「带维度的数组」就行。
标量是 0 阶张量,向量是 1 阶,矩阵是 2 阶,再往上就是高阶张量。我刚开始学的时候,总觉得「张量」这词儿唬人,其实它就是数据的容器。
核心要点:TensorFlow 里所有数据都以张量形式流动。图像是 4 阶张量(批次、高、宽、通道),文本是 2 阶张量(批次、序列长度)。
看个例子你就明白了:
import tensorflow as tf
# 0阶张量(标量)
scalar = tf.constant(3.14)
# 1阶张量(向量)
vector = tf.constant([1.0, 2.0, 3.0])
# 2阶张量(矩阵)
matrix = tf.constant([[1, 2], [3, 4]])
# 3阶张量
tensor_3d = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(scalar.shape) # 输出: ()
print(vector.shape) # 输出: (3,)
print(matrix.shape) # 输出: (2, 2)
print(tensor_3d.shape) # 输出: (2, 2, 2)
嗯,这里要注意:tf.constant 创建的是不可变的张量。如果你想改数据,用 tf.Variable。我在项目中经常用 Variable 来存模型参数,因为权重需要不断更新。
我的习惯:调试时先用 .numpy() 把张量转成 NumPy 数组打印出来看看,确认数据形状对不对。这一步能省掉你后面很多排查时间。
2.2 计算图(Graph)—— 运算的蓝图
接下来是计算图。这个概念有点抽象,我换个说法:计算图就是运算流程的「设计图纸」。
在 TensorFlow 1.x 时代,你得先搭图,再跑图。就像盖房子,先画好图纸,再施工。你想想看,这样做的好处是什么?
- 并行计算:图里没有依赖关系的节点可以同时跑
- 分布式部署:图可以拆开放到不同设备上执行
- 自动求导:框架根据图的结构自动算梯度
举个例子,计算 z = (a + b) * c:
import tensorflow as tf
# 定义占位符(老版本写法)
a = tf.placeholder(tf.float32, name='a')
b = tf.placeholder(tf.float32, name='b')
c = tf.placeholder(tf.float32, name='c')
# 构建计算图
add_op = tf.add(a, b, name='add')
mul_op = tf.multiply(add_op, c, name='multiply')
# 此时图已经搭好了,但还没执行
print(mul_op) # Tensor("multiply:0", dtype=float32)
看到没?mul_op 只是一个 Tensor 对象,里面没有具体数值。这就是「静态图」的特点——先定义,后执行。
我曾经踩过的坑:在循环里反复创建计算图,导致内存暴涨。后来我习惯把图定义放在函数外面,或者用 tf.Graph() 显式管理图对象。记住:图只用建一次,别在循环里重复建。
2.3 会话(Session)—— 让图跑起来
图搭好了,谁来执行?会话(Session)就是那个「包工头」。
Session 负责分配资源、执行运算、返回结果。没有 Session,你的图就是一张废纸。
# 创建会话并执行
with tf.Session() as sess:
result = sess.run(mul_op, feed_dict={a: 2.0, b: 3.0, c: 4.0})
print(result) # 输出: 20.0
这里 feed_dict 就是给占位符喂数据。你想想看,如果图是模具,Session 就是压铸机,feed_dict 就是原材料。
我个人习惯用 with 语句管理 Session,这样能自动释放资源。以前有人忘了关 Session,导致 GPU 显存一直被占用,排查半天才发现。
| 组件 | 作用 | 类比 |
|---|---|---|
| 张量 | 数据容器 | 砖块、水泥 |
| 计算图 | 运算流程定义 | 建筑设计图 |
| 会话 | 执行运算 | 施工队 |
| 占位符 | 数据入口 | 材料进场口 |
2.4 Eager Execution 模式—— 动态图的革命
好了,前面讲的都是 TensorFlow 1.x 的老路子。到了 2.x,情况变了。
Eager Execution 模式,说白了就是「立即执行,不用等 Session」。你写一行代码,结果马上就出来。这跟 Python 的正常写法一模一样。
import tensorflow as tf
# 默认就是 Eager 模式(TF 2.x)
a = tf.constant(2.0)
b = tf.constant(3.0)
c = a + b
print(c.numpy()) # 直接输出: 5.0
看到了吗?没有 Session,没有 run(),没有 feed_dict。代码写起来就跟 NumPy 一样自然。
为什么会这样?因为 Google 自己也意识到,静态图虽然性能好,但调试起来太痛苦了。我记得以前调试一个模型,想打印中间结果,得专门写 tf.Print 节点,然后重新跑图……那体验,谁用谁知道。
Eager Execution 的优势:
- 调试方便:可以用 Python 的 print、pdb 直接调试
- 代码直观:写起来像 NumPy,学习成本低
- 动态控制流:if、while 可以直接用 Python 原生的
当然,Eager 模式也有缺点——性能不如静态图。不过 TensorFlow 2.x 提供了 @tf.function 装饰器,可以把一段 Python 函数自动编译成静态图,兼顾了易用性和性能。
@tf.function
def compute(x, y):
return tf.matmul(x, y) + 1.0
# 第一次调用会编译,后面直接跑编译后的图
result = compute(tf.constant([[1.0, 2.0]]), tf.constant([[3.0], [4.0]]))
print(result)
嗯,这里要注意:@tf.function 不是万能的。如果你的函数里有大量 Python 原生操作(比如列表推导、字典操作),编译效果会打折扣。我建议把核心的矩阵运算包进去,数据预处理部分留在外面。
我的建议:新手直接用 Eager 模式写代码,跑通了再用 @tf.function 优化热点部分。别一开始就想着静态图,容易把自己绕晕。
2.5 总结一下
这一章的内容,说白了就是三件事:
- 张量是数据,记住形状和数据类型
- 计算图 + 会话是老版本的核心,理解思想就行
- Eager Execution 是现在的主流,写起来爽,调试也方便
我个人觉得,理解计算图的思想比记住 API 更重要。因为后面学分布式、模型部署、自定义算子,都离不开图的概念。Eager 模式只是让开发更舒服,但底层跑的依然是图。
下一章,咱们开始动手写第一个完整的模型。到时候你会发现,有了这些基础概念打底,写代码就跟喝水一样自然。