FA:Formulas and Algorithm,CNN:Convolutional Neural Network
1. 引言
在深度学习项目中,训练好的模型需要保存到磁盘,以便后续部署或继续训练。PyTorch 提供了两种主流的模型保存方式,各有优劣。本文将结合 VGG16 实例,详细介绍这两种方式的保存与加载方法,并剖析一个常见的「网络陷阱」——为什么有时加载模型会失败。
2. 准备工作
在开始之前,请确保已安装 PyTorch 和 torchvision:
pipinstalltorch torchvision本文所有代码均基于 PyTorch 框架,使用 VGG16 作为示例网络。
3. 网络模型保存(方式一):模型结构 + 模型参数
方式一通过torch.save(model, path)将整个模型对象(包括网络结构和参数)序列化保存到磁盘。
importtorchvisionimporttorch vgg16=torchvision.models.vgg16(pretrained=False)# 保存方式一:模型结构 + 模型参数torch.save(vgg16,"./model/vgg16_method1.pth")print(vgg16)运行后,控制台会打印出 VGG16 的完整网络结构,包括特征提取层(features)、池化层(avgpool)和分类器(classifier):
VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (18): ReLU(inplace=True) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (25): ReLU(inplace=True) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (27): ReLU(inplace=True) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) ) )4. 网络模型导入(方式一)
方式一保存的模型,加载时同样使用torch.load(path),直接返回完整的模型对象:
importtorch# 保存方式一对应的加载模型model=torch.load("./model/vgg16_method1.pth")print(model)加载成功后,model即为完整的 VGG16 网络,可直接用于推理或继续训练。
5. 网络模型保存(方式二):仅保存模型参数(官方推荐)
方式二只保存模型的状态字典(state_dict),即网络各层的权重和偏置参数,不保存网络结构。这也是 PyTorch 官方推荐的做法。
importtorchvisionimporttorch vgg16=torchvision.models.vgg16(pretrained=False)# 保存方式二:模型参数(官方推荐),不再保存网络模型结构torch.save(vgg16.state_dict(),"./model/vgg16_method2.pth")print(vgg16)打印的仍是 VGG16 的网络结构,但保存到磁盘的.pth文件只包含参数字典。
6. 网络模型导入(方式二)
方式二保存的模型,加载时不能直接得到完整的网络对象,需要分两步:
6.1 直接加载参数(仅得到参数字典)
importtorchimporttorchvision# 导入模型参数model=torch.load("./model/vgg16_method2.pth")print(model)此时model是一个OrderedDict,包含各层的权重和偏置,例如:
OrderedDict([('features.0.weight', tensor([...])), ('features.0.bias', tensor([...])), ...])6.2 将参数导入模型结构
要得到完整的网络,需要先创建网络结构,再通过load_state_dict将参数导入:
importtorchimporttorchvision vgg16=torchvision.models.vgg16(pretrained=False)print(vgg16)# 将模型参数导入到模型结构中vgg16.load_state_dict(torch.load("./model/vgg16_method2.pth"))print(vgg16)加载成功后,vgg16即为带有训练参数的完整网络。
7. 网络陷阱:为什么有时加载模型会失败?
下面通过一个自定义网络Tudui来演示常见的加载陷阱。
7.1 创建自定义模型并保存
importtorchfromtorchimportnnclassTudui(nn.Module):def__init__(self):super(Tudui,self).__init__()self.conv1=nn.Conv2d(3,64,kernel_size=3)defforward(self,x):x=self.conv1(x)returnx tudui=Tudui()torch.save(tudui,"./model/tudui_method1.pth")7.2 失败场景:直接加载方式一保存的模型
如果在新的、未定义Tudui类的环境中直接加载方式一保存的模型,会报错:
importtorch# 无法直接加载方式一保存的网络结构model=torch.load("./model/tudui_method1.pth")print(model)原因分析:方式一保存的是整个模型对象,其中包含网络结构定义。加载时,PyTorch 需要反序列化这个类,但当前环境中没有Tudui类的定义,因此无法还原网络结构。
提示:在 Jupyter Notebook 中,如果先运行了定义
Tudui的代码块,再运行加载代码,可能不会报错。但重启 Kernel 后(点击 Kernel → Restart),再单独运行加载代码,就会失败。
7.3 成功场景:加载前先定义网络结构
要成功加载方式一保存的模型,必须在加载前重新定义相同的网络结构:
importtorchfromtorchimportnn# 确保网络模型是我们想要的网络模型,要在加载前写明网络模型classTudui(nn.Module):def__init__(self):super(Tudui,self).__init__()self.conv1=nn.Conv2d(3,64,kernel_size=3)defforward(self,x):x=self.conv1(x)returnx# 不需要创建网络模型实例,直接加载model=torch.load("./model/tudui_method1.pth")print(model)加载成功后,输出:
Tudui( (conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)) )7.4 成功场景:通过 import 引入网络定义
更规范的做法是,将网络定义放在独立的.py文件中,通过import引入:
importtorchfrommodel_saveimport*# 相当于把 model_save.py 里的网络模型定义写到这里# 不需要创建网络模型实例,直接加载model=torch.load("tudui_method1.pth")print(model)8. 两种方式对比总结
| 对比项 | 方式一:保存整个模型 | 方式二:仅保存参数(官方推荐) |
|---|---|---|
| 保存内容 | 网络结构 + 参数 | 仅参数(state_dict) |
| 保存代码 | torch.save(model, path) | torch.save(model.state_dict(), path) |
| 加载代码 | torch.load(path) | 先建网络,再load_state_dict(torch.load(path)) |
| 加载依赖 | 需要网络类定义 | 需要网络结构定义 |
| 文件体积 | 较大 | 较小 |
| 可移植性 | 较差(依赖类定义) | 较好 |
| 适用场景 | 快速实验、临时保存 | 正式部署、模型发布 |
9. 总结
- 方式一(保存整个模型)使用简单,但加载时依赖网络类的定义,容易踩坑。
- 方式二(仅保存参数)是官方推荐做法,文件更小、更灵活,加载时只需先构建网络结构再导入参数。
- 无论哪种方式,加载模型前都要确保网络结构定义可用,这是避免「网络陷阱」的关键。
希望本文能帮助你正确掌握 PyTorch 模型的保存与加载,避开常见的坑。