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 总结一下

这一章的内容,说白了就是三件事:

  1. 张量是数据,记住形状和数据类型
  2. 计算图 + 会话是老版本的核心,理解思想就行
  3. Eager Execution 是现在的主流,写起来爽,调试也方便

我个人觉得,理解计算图的思想比记住 API 更重要。因为后面学分布式、模型部署、自定义算子,都离不开图的概念。Eager 模式只是让开发更舒服,但底层跑的依然是图。

下一章,咱们开始动手写第一个完整的模型。到时候你会发现,有了这些基础概念打底,写代码就跟喝水一样自然。