深度学习模型基础回顾:PyTorch/TensorFlow模型结构、前向推理、计算图与张量

各位同学,欢迎来到模型优化的第一站。在动手做量化、剪枝、蒸馏之前,咱们得先把地基打牢。说白了,你连模型长什么样、数据怎么在模型里流动都不清楚,后面那些优化技巧就是空中楼阁。

我刚开始带团队做模型部署时,有个同事上来就对一个大模型做剪枝,结果剪完精度直接崩了。后来一查,他连模型里哪些层是冗余的都没搞清楚。嗯,这就是基础不牢的代价。今天这一讲,我们就从最核心的几个概念入手,把PyTorch和TensorFlow的模型结构、前向推理、计算图和张量彻底捋一遍。

1. 张量:深度学习的基本数据单元

张量,说白了就是多维数组。标量是0维张量,向量是1维,矩阵是2维,再往上就是高维张量。在深度学习里,图像、文本、音频最终都会被表示成张量。

举个例子,一张RGB彩色图片,形状通常是 (3, 224, 224),表示3个通道,每个通道是224x224的像素矩阵。一个批次32张图片,形状就是 (32, 3, 224, 224)

核心要点:张量的三个关键属性——形状(shape)、数据类型(dtype)、设备(device)。我见过太多新手因为数据类型不匹配导致推理报错,或者张量在CPU上而模型在GPU上,结果白白浪费时间调试。

在PyTorch中,张量是 torch.Tensor;在TensorFlow中,是 tf.Tensor。两者用法大同小异,但内存布局和自动求导机制略有不同。我个人习惯用PyTorch做研究,用TensorFlow做生产部署,但这不是绝对的。

# PyTorch 张量示例
import torch
x = torch.randn(32, 3, 224, 224)  # 随机张量
print(x.shape)  # torch.Size([32, 3, 224, 224])
print(x.dtype)  # torch.float32

# TensorFlow 张量示例
import tensorflow as tf
y = tf.random.normal([32, 224, 224, 3])  # 注意:TF是NHWC格式
print(y.shape)  # (32, 224, 224, 3)
print(y.dtype)  # <dtype: 'float32'>

避坑指南:PyTorch默认是NCHW格式(通道在前),TensorFlow默认是NHWC格式(通道在后)。我在做模型转换时,经常因为这个问题导致推理结果不对。建议你在写代码时,始终明确指定数据格式,不要依赖默认值。

2. 模型结构:从层到网络

一个深度学习模型,本质上就是一堆层(Layer)堆叠起来的计算图。每一层接收输入张量,经过某种变换,输出新的张量。

常见的层包括:

  • 全连接层(Linear/Dense):做线性变换 + 激活函数
  • 卷积层(Conv2D):提取空间特征
  • 池化层(Pooling):降采样,减少参数量
  • 归一化层(BatchNorm/LayerNorm):稳定训练过程
  • 激活层(ReLU/Sigmoid):引入非线性

在PyTorch中,模型通常继承 nn.Module;在TensorFlow中,模型继承 tf.keras.Model。两者的设计哲学很像,都是把层当作积木,搭出整个网络。

# PyTorch 模型定义
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3)
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2)
        self.fc = nn.Linear(16 * 111 * 111, 10)  # 假设输入224x224

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu(x)
        x = self.pool(x)
        x = x.view(x.size(0), -1)  # 展平
        x = self.fc(x)
        return x

# TensorFlow 模型定义
import tensorflow as tf

class SimpleCNN(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.conv1 = tf.keras.layers.Conv2D(16, 3, activation='relu')
        self.pool = tf.keras.layers.MaxPooling2D(2)
        self.flatten = tf.keras.layers.Flatten()
        self.fc = tf.keras.layers.Dense(10)

    def call(self, x):
        x = self.conv1(x)
        x = self.pool(x)
        x = self.flatten(x)
        x = self.fc(x)
        return x

注意:PyTorch的 forward 和TensorFlow的 call 都是定义前向传播逻辑的地方。但TF的 call 在第一次调用时会构建计算图,而PyTorch是动态图,每次调用都重新构建。这个差异会影响后续的模型优化策略。

3. 前向推理:数据在模型中的流动

前向推理,就是输入数据经过模型各层,最终输出结果的过程。你想想看,一张图片从输入到输出,经历了多少次矩阵乘法、卷积运算、激活函数?

我曾在一次项目里,需要把模型部署到嵌入式设备上。当时我仔细分析了每一层的前向推理时间,发现有个卷积层占了总时间的40%。后来通过量化,把这一层从FP32降到INT8,推理速度直接翻倍。这就是理解前向推理的价值所在。

前向推理的步骤很简单:

  1. 输入张量进入第一层
  2. 经过层内的计算(如卷积、激活)
  3. 输出张量作为下一层的输入
  4. 重复直到最后一层
  5. 输出最终结果(如分类概率)
# PyTorch 前向推理
model = SimpleCNN()
input_tensor = torch.randn(1, 3, 224, 224)
output = model(input_tensor)  # 自动调用 forward
print(output.shape)  # torch.Size([1, 10])

# TensorFlow 前向推理
model = SimpleCNN()
input_tensor = tf.random.normal([1, 224, 224, 3])
output = model(input_tensor)  # 自动调用 call
print(output.shape)  # (1, 10)

关键洞察:前向推理的耗时,主要取决于两个因素——计算量(FLOPs)和内存带宽。量化主要降低内存带宽需求,剪枝主要减少计算量,蒸馏则是改变模型结构本身。理解了这一点,你就能针对性地选择优化方法。

4. 计算图:模型的骨架

计算图,是深度学习框架用来表示模型计算过程的抽象结构。它由节点(操作)和边(张量)组成,描述了数据如何流动、如何变换。

PyTorch用的是动态计算图(Define-by-Run),每次前向传播都会动态构建图。TensorFlow 2.x默认也是动态图(Eager Execution),但底层仍然保留静态图的能力(通过 @tf.functionGraph 模式)。

为什么计算图对模型优化很重要?

  • 量化:需要遍历计算图,找到所有可量化的操作(如卷积、全连接)
  • 剪枝:需要分析计算图,识别冗余连接或层
  • 蒸馏:需要同时维护教师和学生两个计算图
  • 部署:需要将计算图导出为ONNX、TensorRT等格式

我曾经在做一个模型压缩项目时,需要把PyTorch模型转成ONNX。结果发现有个自定义操作在ONNX里不支持,折腾了两天才找到替代方案。嗯,这就是对计算图理解不够深入的结果。

# PyTorch 查看计算图(通过 torchviz)
# 需要安装 torchviz: pip install torchviz
from torchviz import make_dot
x = torch.randn(1, 3, 224, 224)
y = model(x)
make_dot(y, params=dict(model.named_parameters())).render("model_graph", format="png")

# TensorFlow 查看计算图
# 使用 tf.summary.trace 或 TensorBoard
tf.summary.trace_on(graph=True, profiler=True)
y = model(input_tensor)
with tf.summary.create_file_writer('logs').as_default():
    tf.summary.trace_export(name="model_trace", step=0, profiler_outdir='logs')

个人经验:在做模型优化前,我建议你先用工具把计算图可视化出来。看一眼图,你就能直观地看到哪些层是瓶颈、哪些层可以合并、哪些层是冗余的。这比盯着代码看效率高得多。

5. 总结与展望

好了,这一讲的内容就到这里。我们回顾了张量、模型结构、前向推理和计算图这四个核心概念。它们就像深度学习模型的四根支柱,缺一不可。

下一讲,我们会正式进入模型量化的世界。到时候你会发现,今天讲的这些基础概念,每一个都会派上用场。比如,量化需要知道张量的数值范围,需要修改计算图中的操作,需要重新跑前向推理来校准。

记住一句话:基础不牢,地动山摇。把今天的内容消化透,后面的优化之路会顺畅很多。

课后思考:如果你现在手头有一个训练好的模型,你能说出它的计算图里有哪些类型的操作吗?哪些操作可能是量化的瓶颈?试着用可视化工具看一下,你会发现新大陆。