1. 从两个框架的“性格差异”说起
如果你在2016年前后入行做深度学习,大概率是从TensorFlow 1.x的tf.Session()和静态图开始被折磨的。那时候写一个简单的卷积网络,得先定义计算图、再开Session、再run,调试全靠tf.Print,报错信息长得像天书。而到了2024年,你去问一个刚入门的学生用什么框架,十有八九回答PyTorch,而且大概率是在Anaconda里conda install pytorch一把梭,然后在PyCharm里直接print(tensor)看结果。
这个转变不是偶然的。TensorFlow和PyTorch的竞争,本质上是两种设计哲学的竞争:一个是“先定义后执行”的工业级静态图思路,一个是“边写边跑”的科研级动态图思路。我这些年两个框架都深度用过,从TensorFlow 1.x的tf.contrib时代一路跟到TF 2.x的Keras集成,也从PyTorch 0.3的Variable时代跟到现在的2.x编译模式。这篇文章不打算给你一个“谁更好”的简单结论,而是想从实际使用者的角度,把两个框架的发展速度、生态变化、安装配置、实战体验这几个维度拆开聊透,让你自己判断在什么场景下该选谁。
先说一个我自己的观察:框架的“发展速度”不能只看版本号更新频率,而要看它解决实际问题的效率提升有多快。TensorFlow在2.x之后把Keras抬为核心API,本质上是在向PyTorch的易用性靠拢;而PyTorch在1.0之后引入TorchScript和2.0的torch.compile,则是在向TensorFlow的部署能力靠拢。两个框架在互相“抄作业”,但抄的方向和节奏不一样,这就导致了它们在2024年的流行趋势出现了明显的分化。
2. 安装配置的第一道门槛:谁更让人省心
2.1 TensorFlow安装的“历史包袱”
TensorFlow的安装,在1.x时代是出了名的麻烦。CUDA版本、cuDNN版本、Python版本三者必须严格对应,差一个小版本就给你报ImportError: libcudart.so.9.0: cannot open shared object file。我印象最深的是2018年在一台Ubuntu 16.04的机器上装TF 1.8,光是CUDA 9.0和cuDNN 7.1的匹配就折腾了一下午,最后发现是系统里预装的NVIDIA驱动版本太新,和CUDA 9.0不兼容,又得降驱动。
到了TF 2.x,官方推出了tensorflow这个“一站式”包,安装体验好了不少。但问题依然存在:如果你用pip install tensorflow,默认装的是CPU版本;要装GPU版本得用pip install tensorflow[and-cuda],而且从TF 2.11开始,Windows上的GPU支持被砍掉了,官方只推荐用WSL2。这个决策对Windows用户来说是个不小的打击,毕竟很多人就是想在Win10上用Anaconda+PyCharm搞深度学习。
提示:如果你在Windows上坚持要用TensorFlow GPU版本,要么降级到TF 2.10,要么老老实实装WSL2。我试过在Win10上直接装TF 2.15的GPU版,
tf.config.list_physical_devices('GPU')返回空列表,折腾驱动也没用,最后换了WSL2才跑通。
2.2 PyTorch安装的“后来居上”
PyTorch的安装体验,从0.4版本之后就开始明显优于TensorFlow。官网的安装命令生成器做得非常直观,你选好系统、包管理器、Python版本、CUDA版本,它直接给你一行命令。比如在Ubuntu 22.04上装CUDA 12.1版本的PyTorch,就是:
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia这行命令的好处是,conda会自动帮你解决CUDA运行时库的依赖,不需要你手动装CUDA Toolkit。我实测下来,在一台干净的Ubuntu 22.04机器上,从零开始装完PyTorch GPU版本并跑通torch.cuda.is_available(),大概只需要10分钟。而同样条件下装TensorFlow GPU版本,如果遇到驱动问题,可能得花一两个小时。
不过PyTorch也不是没有坑。如果你用pip而不是conda,在某些Linux发行版上会遇到libgomp冲突;如果你在Windows上用conda装PyTorch,有时候conda的求解器会卡在“Solving environment”半天不动。我的经验是,Windows用户优先用pip装PyTorch,Linux用户优先用conda,这样踩坑概率最低。
2.3 环境搭建的“版本锁定”策略
不管你选哪个框架,我都强烈建议用conda创建一个独立环境,而不是在base环境里直接装。原因很简单:深度学习框架对numpy、protobuf、h5py这些依赖的版本要求很敏感,你在base环境里装了一堆包,很容易出现版本冲突。
conda create -n dl_env python=3.10 conda activate dl_env # 然后在这个环境里装PyTorch或TensorFlow我自己的习惯是,每个项目一个环境,环境名带上框架和版本号,比如pt2.1_cu121、tf2.15_cpu。这样半年后回来跑旧代码,直接conda activate pt2.1_cu121就能复现,不会因为框架升级导致API不兼容。
3. 动态图与静态图:两种编程范式的实战体验
3.1 PyTorch的“所见即所得”为什么更受欢迎
PyTorch最大的卖点就是动态图。你写一个前向传播,就是普通的Python代码,for循环、if判断、print调试,全都跟写普通Python一样。这对科研人员来说太重要了——你在设计一个新模型结构时,经常需要根据中间结果动态调整计算流程,动态图让你可以像调试普通程序一样调试模型。
举个例子,我在做seq2seq任务时,decoder的attention模块需要根据encoder的输出长度动态计算注意力权重。用PyTorch写,就是:
class Attention(nn.Module): def forward(self, decoder_hidden, encoder_outputs): # encoder_outputs: (batch, seq_len, hidden) attn_scores = torch.bmm(decoder_hidden.unsqueeze(1), encoder_outputs.transpose(1, 2)) attn_weights = F.softmax(attn_scores, dim=-1) context = torch.bmm(attn_weights, encoder_outputs) return context, attn_weights这段代码里,seq_len是动态的,每次前向传播都可以不一样。你可以在forward里直接print(attn_weights.shape)看维度对不对,不需要开Session,不需要tf.Print。
3.2 TensorFlow 2.x的“折中方案”到底折了什么
TensorFlow 2.x默认开启Eager Execution,也就是动态图模式,写起来跟PyTorch很像。但它的底层还是保留着图模式的能力,你可以用@tf.function装饰器把Python函数编译成静态图,提升执行效率。这个设计理论上很美好:开发时用Eager模式调试,部署时用tf.function加速。
但实际用起来,@tf.function的坑不少。最典型的是Python副作用:你在被装饰的函数里print,只在第一次trace的时候输出,后面就不输出了;你在函数里改一个Python列表,图模式下这个修改可能不会按你预期的方式生效。我踩过的一个坑是,在@tf.function里用tf.print而不是Python的print,结果发现tf.print的输出顺序和Eager模式下完全不一样,调试起来很费劲。
另一个问题是,tf.function的自动图优化有时候会“过度优化”。比如你在函数里写了一个if判断,如果条件依赖的是Python变量而不是Tensor,图模式下这个分支会被固定下来,导致模型行为跟Eager模式不一致。这种问题在调试时很难发现,因为Eager模式下跑得好好的,一上tf.function就出错。
3.3 从“写模型”到“调模型”的效率对比
抛开调试体验,单看写模型的代码量,PyTorch和TensorFlow 2.x其实差不多。定义一个简单的全连接网络,PyTorch大概10行,TensorFlow用Keras也是10行左右。但到了自定义层、自定义损失函数、自定义训练循环这些场景,PyTorch的灵活性优势就体现出来了。
比如你要实现一个带梯度惩罚的WGAN-GP,PyTorch里可以用torch.autograd.grad直接算梯度:
def gradient_penalty(critic, real, fake, device): alpha = torch.rand(real.size(0), 1, 1, 1).to(device) interpolated = alpha * real + (1 - alpha) * fake interpolated.requires_grad_(True) critic_interpolated = critic(interpolated) gradients = torch.autograd.grad( outputs=critic_interpolated, inputs=interpolated, grad_outputs=torch.ones_like(critic_interpolated), create_graph=True, retain_graph=True )[0] gp = ((gradients.norm(2, dim=1) - 1) ** 2).mean() return gp这段代码在TensorFlow里也能实现,但需要用tf.GradientTape的嵌套,写起来更绕。我个人的感受是,PyTorch在“非标准”模型结构上的开发效率明显更高,这也是为什么学术界的新论文几乎清一色用PyTorch实现。
4. 生态与部署:TensorFlow的“护城河”还在吗
4.1 生产部署:TensorFlow Serving vs TorchServe
TensorFlow最早打出“从研究到生产”的口号,TensorFlow Serving确实是工业界部署模型的一个成熟方案。它支持模型版本管理、A/B测试、gRPC和REST接口,而且跟Kubernetes集成得很好。如果你在一个大公司做推荐系统或广告系统,TensorFlow Serving的稳定性和性能是经过验证的。
PyTorch这边,TorchServe是后来才推出的,功能上跟TensorFlow Serving对标,但成熟度稍逊一筹。不过PyTorch有个杀手锏:TorchScript。你可以用torch.jit.trace或torch.jit.script把模型转成TorchScript格式,然后直接在C++里加载,不需要Python运行时。这对嵌入式部署或高性能推理场景很有吸引力。
我去年做一个移动端图像分类的项目,用PyTorch训练完模型后,用torch.jit.trace导出TorchScript,再通过PyTorch Mobile部署到Android上,整个流程很顺畅。TensorFlow那边对应的方案是TensorFlow Lite,也很成熟,但转换过程中遇到的算子不支持问题比TorchScript多一些。
4.2 可视化工具:TensorBoard的“通用化”
TensorBoard原本是TensorFlow的配套工具,但现在PyTorch也能用。你只需要装tensorboard包,然后在PyTorch训练循环里写:
from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter('runs/experiment_1') writer.add_scalar('Loss/train', loss, epoch) writer.add_graph(model, input_tensor) writer.close()然后tensorboard --logdir=runs就能看曲线了。这个变化很有意思:TensorBoard从一个框架专属工具变成了一个跨框架的可视化标准。我现在的习惯是,不管用PyTorch还是TensorFlow,都统一用TensorBoard看训练曲线,省得在两个工具之间切换。
4.3 预训练模型库:HuggingFace的“去框架化”
HuggingFace Transformers库的崛起,某种程度上削弱了框架之间的差异。你可以在PyTorch和TensorFlow之间自由切换模型,只需要改一个参数:
from transformers import AutoModel model = AutoModel.from_pretrained("bert-base-uncased", framework="pt") # 或 "tf"这意味着,对于NLP任务,你选PyTorch还是TensorFlow,很大程度上取决于你更喜欢哪个框架的训练循环写法,而不是模型本身能不能用。我身边很多做NLP的同事,训练用PyTorch,部署时转成ONNX,然后用在任何支持ONNX的推理引擎上,框架的绑定越来越弱了。
5. 2024年的流行趋势:数据背后的真实变化
5.1 论文实现的语言分布
如果你去arXiv上看2023-2024年的深度学习论文,附带的代码实现里,PyTorch的占比超过80%。NeurIPS、ICML、ICLR这些顶会的论文,官方实现几乎全是PyTorch。这个趋势从2019年开始就很明显了,到2024年基本定型。我审稿的时候,如果看到论文只提供TensorFlow实现,第一反应是“这个作者可能不是做这个方向的主流圈子”。
5.2 工业界的“双轨制”
但工业界的情况不一样。大厂里TensorFlow的存量代码非常多,尤其是推荐系统、广告系统这些2018年之前就开始做的业务。我认识的一些在电商公司做推荐的工程师,他们的线上模型还是TensorFlow 1.x的静态图,因为迁移成本太高,而且线上服务对延迟和吞吐的要求,静态图确实有优势。
不过新项目上,PyTorch的比例在快速上升。我了解到的几个2023年新成立的AI团队,清一色用PyTorch。原因很简单:招人容易,新人上手快,社区活跃,遇到问题搜到的答案多。
5.3 框架融合的“中间地带”
还有一个值得注意的趋势是ONNX的普及。ONNX作为一个开放的模型交换格式,让PyTorch训练的模型可以轻松转到TensorFlow、TensorRT、OpenVINO等推理引擎上。我现在的标准流程是:PyTorch训练 -> 导出ONNX -> 用ONNX Runtime或TensorRT部署。这样既享受了PyTorch的开发效率,又利用了TensorFlow生态的部署工具。
# PyTorch导出ONNX dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}})这段代码导出的ONNX模型,可以在C++、Python、Java等多种语言里加载,框架的边界越来越模糊了。
6. 给不同阶段开发者的选型建议
6.1 如果你是刚入门的学生或转行者
直接学PyTorch,不要犹豫。PyTorch的官方教程(pytorch.org/tutorials)写得非常清楚,从torch.Tensor到nn.Module再到完整的训练循环,循序渐进。而且PyTorch的报错信息比TensorFlow友好得多,你写错一个维度,它会直接告诉你Expected input batch_size (32) to match target batch_size (16),而TensorFlow 1.x的报错经常是一堆C++堆栈,看得人一头雾水。
安装方面,如果你在Windows上,用Anaconda创建一个Python 3.10的环境,然后去PyTorch官网复制conda安装命令,基本不会出问题。如果你在Ubuntu上,同样用conda,注意CUDA版本要跟你的显卡驱动匹配。nvidia-smi看驱动支持的CUDA版本,然后选对应的PyTorch安装命令。
6.2 如果你在公司做新项目选型
先看团队的技术栈。如果团队里大部分人熟悉TensorFlow,而且项目需要跟现有的TensorFlow Serving基础设施集成,那继续用TensorFlow 2.x是合理的。但如果是一个全新的项目,团队没有历史包袱,我建议用PyTorch,因为招人更容易,社区支持更好,而且部署方案(TorchServe、ONNX Runtime、TensorRT)已经足够成熟。
6.3 如果你要维护老代码
TensorFlow 1.x的代码,如果还能跑,就不要轻易迁移到2.x。我试过用tf_upgrade_v2脚本迁移一个中等规模的项目,自动转换后大概有30%的代码需要手动修,主要是tf.contrib的模块在2.x里被移除了,得找替代方案。如果老代码稳定运行,迁移的收益可能抵不上成本。
7. 几个我踩过的具体坑和绕行方案
7.1 PyTorch的num_workers在Windows上的坑
在Windows上用PyTorch的DataLoader,如果num_workers大于0,经常会遇到BrokenPipeError或者卡死。这是因为Windows的进程启动方式和Linux不一样,PyTorch的多进程DataLoader在Windows上需要把主训练代码放在if __name__ == '__main__':里面。我的建议是,Windows上调试时把num_workers设为0,等代码跑通了再考虑要不要开多进程。如果一定要开,确保你的训练脚本有if __name__ == '__main__':保护。
7.2 TensorFlow的tf.data性能调优
TensorFlow的tf.data.Dataset管道,如果不做prefetch和cache,GPU利用率会很低。我见过一个典型的错误写法:
dataset = tf.data.Dataset.from_tensor_slices((x, y)) dataset = dataset.batch(32) # 直接训练,没有prefetch这样每个batch都要等数据加载,GPU经常空转。正确的做法是:
dataset = tf.data.Dataset.from_tensor_slices((x, y)) dataset = dataset.shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)prefetch(tf.data.AUTOTUNE)让数据加载和GPU计算重叠,实测能把训练速度提升30%以上。
7.3 混合精度训练的框架差异
PyTorch的混合精度训练用torch.cuda.amp,TensorFlow用tf.keras.mixed_precision。两者都能显著减少显存占用、提升训练速度,但PyTorch的autocast上下文管理器用起来更灵活。我在PyTorch里通常这样写:
scaler = torch.cuda.amp.GradScaler() for x, y in dataloader: with torch.cuda.amp.autocast(): output = model(x) loss = criterion(output, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad()TensorFlow那边需要在模型编译时设置policy,然后fit的时候自动处理,代码更简洁但可控性稍弱。
8. 我个人的最终选择和工作流
说了这么多,我自己现在的主力工作流是:PyTorch训练 + TensorBoard监控 + ONNX导出 + ONNX Runtime/TensorRT部署。这套流程在2024年已经非常成熟,社区支持也好。TensorFlow我还在用,但主要是维护老项目和读一些工业界的开源实现。
如果你问我“哪个框架发展最快”,我的答案是:PyTorch在科研和新人入门领域的发展速度更快,TensorFlow在工业部署和存量系统维护上依然有不可替代的位置。但更准确的说法是,两个框架都在向对方学习,边界越来越模糊,最终受益的是我们这些使用者——你可以在PyTorch里用torch.compile享受图优化的性能,也可以在TensorFlow里用Eager模式享受动态图的便利。
最后分享一个我最近发现的实用技巧:如果你在Ubuntu 24.04上装PyTorch,用pip而不是conda,因为conda的默认channel里PyTorch版本更新比pip慢一截。pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121这行命令,能让你第一时间用上最新稳定版。装完之后用python -c "import torch; print(torch.__version__, torch.cuda.is_available())"验证一下,输出类似2.3.1 True就说明GPU环境没问题了。