TensorFlow 1.x 和 2.x 有什么区别?迁移指南和核心变化详解
TF 1.x 到 2.x 最核心的变化就一句话:默认执行模式从"先建图再跑"变成了"写一行算一行"。1.x 必须先定义计算图再通过 Session.run() 执行,2.x 默认 Eager 模式,代码写完直接出结果——和 NumPy、PyTorch 一样自然。
| 特性 | TF 1.x | TF 2.x |
|---|---|---|
| 执行模式 | 静态图(先定义后执行) | Eager(即时执行) |
| 求梯度 | optimizer.minimize() | tf.GradientTape |
| 控制流 | tf.cond / tf.while_loop | Python if / for |
| 变量 | 手动初始化 + variable_scope | 自动初始化 + Python 对象 |
| 高级 API | tf.layers / tf.contrib | tf.keras 深度集成 |
| Session | 必须用 | 不需要 |
Eager 模式:最大变化
1.x 里 a = tf.constant(5) 只是往图里加了个节点,不运行就没有值。2.x 里同样一行代码 a 直接就是 5.0,能 print、能调试、能 if 判断。调试体验天差地别——1.x 报错只说"某个图节点有问题",2.x 直接定位到 Python 代码行。
代价是 Eager 模式每次操作都有 Python 开销,比编译后的静态图慢。解决方案是 @tf.function——加一个装饰器就把 Python 函数编译成图,开发时用 Eager 调试,部署时用 @tf.function 加速。
梯度计算:GradientTape 取代 optimizer.minimize
1.x 的 optimizer.minimize(loss) 把梯度计算和参数更新绑在一起,不灵活。2.x 用 tf.GradientTape 显式求梯度,你可以自由地在更新前做裁剪、加正则、修改梯度——自定义训练逻辑的空间大得多。
python# TF 2.x 标准训练步骤 with tf.GradientTape() as tape: loss = loss_fn(model(x), y) grads = tape.gradient(loss, model.trainable_variables) # 这里可以做梯度裁剪、梯度累积等自定义操作 optimizer.apply_gradients(zip(grads, model.trainable_variables))
API 清理:删掉了什么
tf.contrib 整个删了(太杂太乱),tf.Session、tf.placeholder 不再推荐,tf.app/tf.flags/tf.logging 被移除(用标准 Python 库替代)。Keras 升级为官方高级 API(tf.keras),模型构建用 Sequential 或 Functional API,不再需要手写 low-level 的 layers。
迁移老代码用 tf.compat.v1 模块可以跑大部分 1.x 代码,但建议直接重写——Eager 代码通常比对应的静态图代码短 50%。
追问
现在新项目应该用 TF 还是 PyTorch?
2024 年以后新项目多数选 PyTorch——学术圈和开源社区生态更大。但 TF 仍有优势场景:TPU 训练(TF 原生支持最好)、生产部署(TF Serving / TF Lite / TF.js 工具链成熟)、大型企业已有 TF 基础设施。如果你没有明确的部署需求,PyTorch 上手更快。
tf.compat.v1 能不能一直用?
能跑但不推荐。compat 模块不会获得新优化,Eager 模式下跑 compat 代码性能反而可能不如原生 1.x。而且新硬件(如 TPU v5)和新特性(如 JAX 兼容)只支持 2.x 原生 API。迁移成本大约是每万行代码 1-2 天。
@tf.function 和 TF 1.x 的静态图一样吗?
不一样。1.x 的图是全局的——所有变量和操作挂在同一个默认图上。@tf.function 的图是函数级的——每次装饰器创建一个独立的小图,函数之间通过参数传值。这避免了 1.x 里变量名冲突和图污染的问题,也更符合 Python 的模块化思维。