正在准备面试人工智能、深度学习相关岗位,或者刚开始接触深度学习的朋友,几乎都逃不过一个问题:PyTorch 和 TensorFlow,到底应该学哪个?这个问题表面上是“框架选型”,实际上对方想从你的回答里判断三件事:你有没有真正动手做过项目,你是否理解深度学习的基本流程,以及你在技术选型时能不能讲出支撑逻辑。很多同学在简历里同时写“熟悉 PyTorch”和“熟悉 TensorFlow”,但一问到两者的底层区别就卡壳了。
更常见的是另一种情况:学校课程和开源项目用的是 PyTorch,实习公司内部代码库用的是 TensorFlow,项目需求一变,只能去网上找片段拼凑,代码能跑,但不知道为什么这样写,出了问题也不知道去哪里排查。
这篇文章要解决的就是这个问题。我会把 PyTorch 和 TensorFlow 放在同一条主线上讲:先讲清楚它们最核心的架构差异——动态图和静态图,再看它们处理同一套任务时在 API 设计和编程范式上的区别,然后用一个经典的图像分类项目分别给出两份完整可运行的代码,最后补充环境配置、常见踩坑和选型建议。读完这篇文章,你不需要“精通两个框架”,但至少能做到:理解两者本质,能在实际项目里独立选型,能给面试官讲清楚“为什么你的项目用这个框架而不是那个”。
1. 为什么还要在 2024 年讨论 PyTorch 和 TensorFlow
先给一个明确判断:PyTorch 和 TensorFlow 至今仍然是人工智能和深度学习领域使用范围最广、招聘需求里出现频率最高的两个框架,没有之一。市面上很多新框架,比如 JAX、MindSpore、PaddlePaddle,各有场景,但绝大多数论文开源代码、GitHub 热门项目、企业生产环境,还是集中在这两个框架上。
从趋势来看,PyTorch 在学术研究和开源社区里的占比持续上升。主要原因不是 TensorFlow 变弱了,而是 PyTorch 的动态图机制让研究者写代码的思维方式更接近“写普通 Python 程序”,调试时可以直接 print 中间结果,不需要构建完整计算图再执行。研究场景强调的是快速迭代、改模型结构、验证想法,PyTorch 天然匹配这种节奏。很多顶级会议的论文代码都默认给 PyTorch 版本,这在 CV、NLP 领域特别明显。
TensorFlow 则更多出现在工业界和已有系统中。它的 Keras 高层 API 对新手很友好,几行代码就能搭出一个模型;SavedModel 格式对部署非常友好,配合 TensorFlow Serving、移动端、嵌入式设备有比较成熟的链路。如果在公司里负责把一个训练好的模型上线为在线服务,TensorFlow 的工程化工具链确实更完整。
换句话说,这不是“谁淘汰谁”的问题,而是“不同阶段、不同目标下怎么选”的问题。2024 年以后的人才市场也越来越倾向于要求候选人“熟练使用至少一个框架,同时理解另一个”,而不是“只会调包”。
2. 基础概念与核心原理:动态图、静态图与自动求导
2.1 张量:两个框架共同的语言
无论 PyTorch 还是 TensorFlow,最基本的操作对象都是张量(Tensor)。可以把它理解为“带形状的多维数组”:0 维是标量,1 维是向量,2 维是矩阵,3 维以上统称张量。一张 28×28 的灰度图片可以表示为形状为(1, 28, 28)的张量,一批 64 张图片就是(64, 1, 28, 28)。在深度学习里,几乎所有计算都是在张量之间进行的,包括矩阵乘法、卷积、激活函数、损失计算等。
PyTorch 里的张量类型是torch.Tensor,TensorFlow 里的张量类型是tf.Tensor。两者都支持在 CPU 和 GPU 上计算,也支持自动求导。但在“如何构建和运行计算图”这个核心设计上,两者走了完全不同的路线。
2.2 动态图与静态图:最核心的架构差异
TensorFlow 早期版本采用静态图机制。用户先定义好一张计算图,再在会话(Session)里执行。图是“先搭后跑”,一旦定义完成,结构基本固定。这种设计的优点是:图是静态的,系统可以对整张图做优化,部署时可以把图和参数一起导出,执行效率高;缺点是调试困难,想打印中间结果必须在图里插入打印节点,非常反直觉。
PyTorch 采用动态图机制,也就是“边定义边执行”。每一行张量操作在执行的同时就被记录进自动求导图,网络结构可以在运行时动态修改,控制流直接用 Python 的if、for就可以写。因为图和执行是同步的,调试方式和普通 Python 程序一样,遇到问题可以随时 print,或者用断点调试。这种设计让研究阶段的开发效率大大提高。
TensorFlow 后来也意识到了动态图的重要性,推出了 Eager Execution 模式,并在 TensorFlow 2.x 中默认启用。从 2.x 开始,TensorFlow 的日常写法其实和 PyTorch 越来越像,都是“定义模型→前向计算→计算损失→反向传播→更新参数”的流程。但是底层设计仍然保留了两套模型:一套面向研究的动态执行,一套面向生产的静态图优化与部署。
2.3 自动求导:反向传播不再需要手推
传统机器学习里,更新参数需要手动计算梯度,或者依赖数值微分近似。深度学习框架的核心能力之一就是自动求导:你只需要定义前向传播过程,框架会在反向传播时自动计算每个参数相对于损失的梯度。
PyTorch 里,只要张量设置了requires_grad=True,前向计算过程中框架就会记录所有操作,调用loss.backward()后,每个参数的梯度会保存在.grad属性中。
TensorFlow 则通过GradientTape上下文管理器来记录前向计算过程中的操作,在上下文内部执行的操作会被自动记录,退出上下文后调用tape.gradient(loss, model.trainable_variables)拿到梯度。
从写法上看,PyTorch 的自动求导“隐藏”在张量内部,TensorFlow 则把“记录”这个行为显式暴露出来。理解这一区别,你再看任何一段训练代码,就会发现底层逻辑完全一致,只是 API 名称和组织方式不同。
3. 环境准备与安装:Python、CUDA、GPU 版本匹配详解
环境配置是新手放弃深度学习的第一道坎。两个框架的安装思路是相同的:先准备 Python 虚拟环境,再安装 CUDA 相关依赖,最后安装框架本体。下面给出可复制的完整流程。
3.1 创建独立的 Python 虚拟环境
强烈建议不要直接在系统 Python 里安装。深度学习项目依赖众多,版本冲突会让排查成本成倍增加。推荐使用 Anaconda 或者 Miniconda 管理环境。
# 安装好 Anaconda/Miniconda 后,创建 Python 3.11 环境 conda create -n torch_env python=3.11 -y conda activate torch_env conda create -n tf_env python=3.11 -y conda activate tf_envPytorch 和 TensorFlow 可以分别建两个环境,互不干扰。这是最省心的做法。
3.2 安装 PyTorch(含 GPU 版本)
PyTorch 官网会根据你的操作系统、包管理工具、CUDA 版本动态生成安装命令。以常见的 Linux + pip + CUDA 12.1 为例,安装命令大致如下:
# 激活环境后执行 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121如果不需要 GPU,直接执行:
pip install torch torchvision torchaudio需要特别提醒:PyTorch 和 CUDA 的版本是配套的。安装前先用nvidia-smi查看驱动支持的 CUDA 版本,驱动版本可以等于或高于 PyTorch 编译所需的 CUDA 版本,但并不要求完全一致。PyTorch 内部的 CUDA 运行时会随 PyTorch 包一起安装,不需要额外安装完整版 CUDA Toolkit。
验证安装是否成功:
import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU only")如果torch.cuda.is_available()返回True,说明 GPU 版本安装成功。
3.3 安装 TensorFlow
TensorFlow 的安装相对简单,2.x 版本默认包含 GPU 支持。按官方推荐使用 pip 安装:
pip install tensorflow如果你需要安装特定版本,比如 2.18 系列版本,可以指定版本号:
pip install tensorflow==2.18.*验证安装:
import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU'))如果输出的物理设备列表里包含 GPU 信息,说明 TensorFlow 可以使用 GPU。
3.4 关于 CUDA 和 NVIDIA 驱动的常见误区
很多初学者以为需要手动安装“完整的 CUDA”才能用深度学习框架,这是一个误区。框架通过 pip 安装时,通常已经捆绑了它编译时对应的 CUDA 运行库。你真正需要关注的是显卡驱动版本是否足够新,因为驱动是操作系统和 CUDA 运行库之间的桥梁。
在 Jetson、树莓派等嵌入式设备上安装时,情况又会不同,需要选择对应 JetPack 版本预编译好的 PyTorch 包,安装前必须确认 PyTorch 版本与 JetPack、CUDA、Python 版本的匹配关系。这一点在嵌入式 AI 开发场景中特别重要,建议以官方发布页的兼容性说明为准。
4. 两大框架核心 API 对比与心智模型
4.1 模型定义的差异
PyTorch 中,模型继承自nn.Module,你需要显式实现__init__方法和forward方法:
import torch.nn as nn class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(28 * 28, 256) self.fc2 = nn.Linear(256, 128) self.fc3 = nn.Linear(128, 10) self.relu = nn.ReLU() def forward(self, x): x = x.view(x.size(0), -1) x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.fc3(x) return x model = MLP()TensorFlow 中,使用 Keras 高层 API 时可以通过Sequential快速搭建:
from tensorflow.keras import models, layers model = models.Sequential([ layers.Flatten(input_shape=(28, 28)), layers.Dense(256, activation='relu'), layers.Dense(128, activation='relu'), layers.Dense(10, activation='softmax') ])从写法上可以看到:PyTorch 更偏“程序化”,网络结构就是 Python 类的定义;TensorFlow 的 Keras 则偏向“声明式”,把层按顺序堆叠。如果使用 TensorFlow 的Subclassing API,也可以写出类似 PyTorch 的类定义方式,这也说明两者在 API 设计上正在互相靠近。
4.2 训练流程的差异
PyTorch 的训练循环是显式的,你亲手控制每个步骤:
criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) output = model(images) loss = criterion(output, labels) optimizer.zero_grad() loss.backward() optimizer.step()TensorFlow 有两种常见训练方式。最简洁的方式是使用model.fit():
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, epochs=5, batch_size=64)如果追求更细粒度的控制,TensorFlow 也可以写成手动训练循环,配合GradientTape:
with tf.GradientTape() as tape: logits = model(x_batch, training=True) loss = loss_fn(y_batch, logits) grads = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables))两种框架的底层逻辑是同一个:前向计算、计算损失、反向传播、更新参数。区别在于 PyTorch 把所有操作都放在表面上,TensorFlow 则提供了不同层级的封装。理解这一点,学第二个框架时不会觉得陌生。
5. 项目实战:MNIST 手写数字识别完整实现
下面用一份非常经典的数据集 MNIST 来跑通两个框架的完整流程。MNIST 是 28×28 的灰度手写数字图片,共 10 个类别。这个例子不算复杂,但足够把“数据加载、模型定义、训练、评估”这条主线走完。建议先单独跑 PyTorch 版本,再单独跑 TensorFlow 版本,对比两者的写法和输出差异。
5.1 PyTorch 版本完整代码
# 文件路径:pytorch_mnist.py import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms # 数据预处理:转为张量,并按均值 0.1307、标准差 0.3081 归一化 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 加载 MNIST 训练集和测试集 train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform) test_dataset = datasets.MNIST('./data', train=False, download=True, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False) # 定义三层全连接网络 class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(28 * 28, 256) self.fc2 = nn.Linear(256, 128) self.fc3 = nn.Linear(128, 10) self.relu = nn.ReLU() def forward(self, x): x = x.view(x.size(0), -1) x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.fc3(x) return x model = MLP() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) def train(epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() output = model(data) loss = criterion(output, target) loss.backward() optimizer.step() if batch_idx % 200 == 0: print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}') def test(): model.eval() correct = 0 total = 0 with torch.no_grad(): for data, target in test_loader: output = model(data) pred = output.argmax(dim=1) correct += (pred == target).sum().item() total += target.size(0) print(f'Test Accuracy: {correct / total:.4f}') if __name__ == '__main__': for epoch in range(1, 6): train(epoch) test()关键逻辑说明:
transforms.Normalize((0.1307,), (0.3081,))是 MNIST 数据集的全局均值和标准差,用于归一化,可以加速收敛。model.train()和model.eval()切换训练和评估模式。这个例子没有 Dropout 和 BatchNorm,但养成分模式习惯非常重要。loss.backward()计算梯度,optimizer.step()更新参数,optimizer.zero_grad()清空上一步的梯度,这三步顺序不能乱。- 在测试阶段使用
torch.no_grad(),避免记录计算图、节省显存和内存。
运行方式:
python pytorch_mnist.py5.2 TensorFlow 版本完整代码
# 文件路径:tf_mnist.py import tensorflow as tf from tensorflow.keras import layers, models # 加载 MNIST 数据集 (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() # 归一化到 [0, 1] x_train = x_train.astype('float32') / 255.0 x_test = x_test.astype('float32') / 255.0 # 定义模型 model = models.Sequential([ layers.Flatten(input_shape=(28, 28)), layers.Dense(256, activation='relu'), layers.Dense(128, activation='relu'), layers.Dense(10, activation='softmax') ]) # 编译模型:配置优化器、损失函数和评估指标 model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 训练模型 history = model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.1) # 评估模型 test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2) print(f'Test accuracy: {test_acc:.4f}')关键逻辑说明:
keras.datasets.mnist.load_data()返回的是 NumPy 数组,不需要再额外下载二进制数据到本地目录。sparse_categorical_crossentropy适合整数标签(0~9),如果标签是 one-hot 编码,则使用categorical_crossentropy。model.fit内部会自动完成反向传播和参数更新,validation_split=0.1表示从训练集中分出 10% 作为验证集。- 归一化这里直接除以 255.0,和 PyTorch 版本使用了不同的归一化方式,两者都能正常工作,但要注意 PyTorch 版本用了均值和标准差归一化,这不会影响最终比较模型效果,只需要理解不同预处理方式即可。
运行方式:
python tf_mnist.py5.3 两个版本的差异小结
| 环节 | PyTorch | TensorFlow |
|---|---|---|
| 数据加载 | DataLoader + Dataset | tf.data / keras.datasets |
| 模型定义 | 继承 nn.Module,实现 forward | Sequential 或 Subclassing |
| 损失函数 | 单独定义,如 nn.CrossEntropyLoss | compile 里配置 loss |
| 训练循环 | 手动写 for 循环 | model.fit 一行完成 |
| 梯度计算 | loss.backward() | GradientTape 或内部处理 |
| 评估方式 | 手动遍历测试集 | model.evaluate |
如果你先学其中一个,再看另一个,会发现核心概念都能对应上。真正需要花时间的是写法和 API 位置,而不是深度学习原理。
6. 运行结果验证与调试方法
6.1 PyTorch 预期输出
训练 5 个 epoch 后,终端输出类似:
Train Epoch: 1 [12800/60000] Loss: 0.313421 Train Epoch: 1 [25600/60000] Loss: 0.188352 ... Test Accuracy: 0.9578随着 epoch 增加,Loss 应该整体下降,Test Accuracy 应该保持在 0.95 以上。如果 loss 变成nan,优先检查数据预处理是否有除零问题、学习率是否过大。
6.2 TensorFlow 预期输出
model.fit会输出每个 epoch 的训练进度条和验证结果:
Epoch 1/5 844/844 [==============================] - 2s 2ms/step - loss: 0.2531 - accuracy: 0.9261 - val_loss: 0.1382 - val_accuracy: 0.9602 ... Test accuracy: 0.9736如果看到loss和val_loss都在下降,说明训练正常。如果出现Epoch 1: val_loss did not improve from inf,先看数据是否归一化,再看标签是否连续编码,最后看网络输出层是否使用 softmax。
6.3 判断成功的标准
两个版本跑完后,测试集准确率都应该在 95% 以上。MNIST 是非常简单的数据集,达不到这个数字往往意味着代码里存在 bug,而不是“模型设计不够好”。建议把跑通这两个版本当成一个基准测试,后续可以尝试在这些代码上改成卷积神经网络(CNN),看看准确率能否提升到 99% 左右,这一步对理解深度学习比单纯看教程有效得多。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| pip 安装框架时速度极慢或超时 | 网络波动,默认源较慢 | 查看 pip 日志 | 使用国内镜像源:pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple,但注意 PyTorch 官方 GPU 版本可能需要使用官方 index-url |
torch.cuda.is_available()返回 False | 安装的 PyTorch 是 CPU 版本;驱动版本过旧;CUDA 不匹配 | 检查torch.__version__,运行nvidia-smi | 重新安装对应 CUDA 版本的 PyTorch,升级显卡驱动 |
| TensorFlow 找不到 GPU | 安装的是 CPU 版本;缺少显卡驱动 | 运行tf.config.list_physical_devices('GPU'),运行nvidia-smi | 安装完整 TensorFlow 版本,升级驱动 |
torch.load加载模型时报错,提示weights_only相关 | PyTorch 2.6 开始,torch.load的weights_only默认值发生变化 | 查看报错信息,确认 PyTorch 版本 | 加载可信的完整模型对象时设置参数,或改用state_dict方式保存和加载模型 |
| 训练时显存不足 OOM | batch_size 过大,模型过大,显存被其他进程占用 | 查看 GPU 显存占用:nvidia-smi | 减小 batch_size,使用梯度累积,清理GPU进程 |
| Loss 不下降或变成 nan | 学习率过大,数据未归一化,标签错误 | 打印 loss 曲线,检查数据分布 | 降低学习率,检查数据预处理,使用标签平滑 |
| conda 创建环境非常慢 | conda 默认源慢 | 查看 conda 配置 | 配置 conda 国内镜像源 |
使用model.fit时提示 shape 不匹配 | 输入数据形状和模型输入层不一致 | 打印x_train.shape和y_train.shape | 检查 flatten 输入维度或 reshape 数据 |
这里重点解释一下weights_only的问题。从 PyTorch 2.6 开始,torch.load的weights_only参数默认值发生了变化,这是为了减小加载 pickle 文件带来的安全风险。从网络上下载的模型文件可能是恶意构造的,直接用torch.load加载有执行任意代码的风险。更稳妥的做法是只保存模型的state_dict(参数权重)并在加载时重建模型结构,或者严格确认模型文件来源可信。这一点在安全要求较高的生产环境中尤其重要。
8. 最佳实践与工程建议
8.1 环境管理:把环境文件固定下来
无论使用哪个框架,都应该把依赖环境保存成文件提交到代码仓库。Conda 环境可以导出:
conda env export > environment.ymlpip 环境可以导出:
pip freeze > requirements.txt这样团队成员可以快速复现同一个环境,避免“在我电脑上能跑”的尴尬。
8.2 模型保存与加载:不建议只保存一个完整对象
PyTorch 推荐保存state_dict:
# 保存 torch.save(model.state_dict(), 'model_weights.pth') # 加载 model = MLP() model.load_state_dict(torch.load('model_weights.pth', weights_only=True)) model.eval()TensorFlow 推荐使用 SavedModel 格式:
model.save('saved_model/my_model')SavedModel 是 TensorFlow 部署链路的基础格式,可以配合 TensorFlow Serving 做线上推理。如果你在项目里使用 TensorFlow,建议学习从训练到导出的完整流程。
8.3 训练流程规范化
- 固定随机种子:PyTorch 用
torch.manual_seed和torch.cuda.manual_seed_all,TensorFlow 用tf.random.set_seed,保证实验可复现。 - 使用日志和可视化:TensorBoard 是通用工具,两个框架都能接入。记录 loss、准确率、学习率,不要只靠 print。
- 先跑小规模实验:任何新模型先用少量数据跑通,确认代码逻辑正确再加全量训练,节省时间也方便调试。
- 不要在测试集上调参:验证集和测试集必须分离,选择模型时用验证集,最终评估用测试集。
8.4 安全与权限提醒
训练脚本如果运行在服务器或生产环境中,要注意以下几点:
- 最小权限原则:训练、推理脚本只使用需要的文件系统权限,不要用管理员账号运行。
- 不要加载不明来源模型:加载预训练模型前确认来源渠道,警惕通过 pickle 序列化传播的恶意代码。
- 数据脱敏:使用真实业务数据训练时,注意个人信息和敏感数据合规。
- 变更前备份:修改已有训练流程或覆盖模型文件前,先备份旧版本,方便回滚。
9. 学习路线与选型建议
9.1 如果你是初学者
我的建议是先从 PyTorch 入手。原因是 PyTorch 的动态图机制更贴近普通 Python 编程直觉,非常适合边写边调试;同时 PyTorch 的学习资料、开源项目、论文代码最多,遇到问题很容易找到解决方案。先把本文学会的 MNIST 例子跑通,然后换一个更有挑战性的数据集,比如 CIFAR-10,并尝试把全连接网络改成卷积网络nn.Conv2d。这一步做扎实,你对“深度学习流程”的理解会超过很多只刷教程的人。
学完 PyTorch 之后,再接触 TensorFlow,重点学 Keras 高层 API 和《模型训练、导出、部署》这条链路。不要试图同时深挖两个框架,人的精力有限,先精通一个,再学会用另一个。
9.2 如果你在准备面试或做项目选型
面试时被问到“PyTorch 还是 TensorFlow”,不要只回答“PyTorch 好用”或“TensorFlow 工业界用得多”。更好的回答思路是分场景:
- 研究、论文复现、快速迭代原型:选择 PyTorch,因为动态图调试方便,开源生态丰富。
- 生产系统、在线推理、移动端、嵌入式设备:TensorFlow 的 SavedModel、TensorFlow Serving 等部署工具链更成熟。
- 团队已有技术栈:尊重团队现状,而不是重新引入一个框架增加维护成本。
9.3 简历里可以怎么写
如果简历里写了“熟悉人工智能/深度学习”,建议用能体现实际能力的方式描述,而不是只写框架名字。比如:
- 使用 PyTorch 实现并训练了一个图像分类模型,数据预处理、训练、评估流程完整,测试准确率达到 97% 以上。
- 使用 TensorFlow Keras 完成模型训练、超参数调优和 SavedModel 导出,并将模型接入线上服务。
- 掌握 CUDA 和 GPU 环境配置,能独立解决框架安装、版本匹配、显存优化等问题。
这样的描述比“熟练使用 PyTorch 和 TensorFlow”可信得多,面试官能从中看出你走完了完整项目流程。
9.4 回到本文开头的问题
学框架不是为了“站队”,而是为了理解深度学习工程化的通用逻辑。PyTorch 和 TensorFlow 的底层思想一致,差异主要体现在“动态图/静态图”的设计理念和 API 封装层级上。把本文学到的两套 MNIST 代码跑通后,建议你做两个进一步的小项目:一个把模型换成 CNN,比较全连接网络和卷积网络的性能差异;另一个尝试把训练好的模型导出,用 TensorFlow Serving 或 PyTorch 的 TorchScript 部署为在线推理服务。这两个小项目做完,你的深度学习框架知识就不再是零散函数拼凑,而是一张完整的知识网络。
如果你正在准备人工智能大作业或者面试项目,可以直接把这两套代码作为起点,换数据、换模型、加可视化、加实验对比,每一步都会成为简历里的真实亮点。建议收藏备用,遇到环境问题可以回到第 7 章的排查表直接定位,省下大量搜索时间。