Pytorch 官方文档教程整理 (二)

对应官方的 Instuction to Pytorch 后半部分

运行的Python版本:3.9.12

所使用的库:


numpy 1.23.0 pandas 1.4.3 pip 21.2.4 tensorboard 2.9.1 torch 1.12.0+cu116 torchaudio 0.12.0+cu116 torchvision 0.13.0+cu116


注:后面涉及的链接均不再附上 都可以在官方文档找到

Chapter 4: BUILD THE NEURAL NETWORK

通常,神经网络是由层和块构成的(layers\modules)
torch.nn提供了你构建网络所需要的块 Pytorch中的所有快都是nn.Module的子类

一个神经网络本身是一个模块 同时他又由其他模块组成

这种嵌套的结构可以让我们轻松便捷的搭建和管理复杂的网络结构
下面的内容将通过搭建一个分类FashionMNIST的网络来进行学习

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

Get Device for Training

检查cuda是否可用
如果可用在GPU上训练 如果不可用在CPU上训练

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
Using cuda device

Define the Class

通过继承nn.Module来定义网络结构类,并且在__init__中初始化网络
每一个nn.Module的子类都应该实现forward方法 此方法用于前向传播,根据构建的网络对输入数据进行顺序处理

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )
        
    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

实例化一个NeuralNetwork的对象,并且将它转移到device中

model = NeuralNetwork().to(device)
print(model)
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
  )
)

为了使用这个模型我们将数据传入给对象,这将执行forward方法
千万不要直接调用model.forward()

模型的返回值是一个1x10的张量代表着是个种类的预测概率,然后将这个张量传给nn.Softmax()层来得到标准的概率

X = torch.rand(1, 28, 28, device=device)  # 模拟一张图片
logits = model(X)  # 前向传播
pred_probab = nn.Softmax(dim=1)(logits)  # 通过softmax层
y_pred = pred_probab.argmax(1)  # 找到概率最大的索引
print(f"Predicted class: {y_pred}")
Predicted class: tensor([9], device='cuda:0')

Model Layers

下面逐层来分析模型
以3个28x28的minibatch来看看模型是怎么处理样本的

# 模拟数据
input_image = torch.rand(3,28,28)
print(input_image.size())
torch.Size([3, 28, 28])

nn.Flatten

将输入的每个图片从28x28转化成784
同时保持代表minibatch那一维(即第零维)的维数不变

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
torch.Size([3, 784])

nn.Linear

线形层通过权重(weight)和偏置(bias)对输入数据进行线性变化

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
torch.Size([3, 20])

nn.ReLU

非线性激活层在输入域输出层之间建立复杂的映射关系
一般放在线性层之后用来引入非线性性质 帮助神经网络学习到更多的东西

除了ReLU还有很多非线性激活函数,具体见官方文档

print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
Before ReLU: tensor([[ 0.3587,  0.2102, -0.1715, -0.3917, -0.2206,  0.3599,  0.0967, -0.0566,
         -0.8888, -0.0997,  0.0402, -0.1858,  0.0453,  0.5766, -0.5349, -0.0141,
          0.3302, -0.3855,  0.6606,  0.1536],
        [ 0.0278,  0.2044,  0.0194, -0.3012,  0.1331,  0.1855,  0.2654, -0.0500,
         -1.0276, -0.0428,  0.1695, -0.6108,  0.0639,  0.3781, -0.3800,  0.0016,
          0.1006, -0.2737,  0.0049, -0.0561],
        [-0.0031,  0.4846,  0.0255, -0.2441,  0.2868,  0.2964,  0.1932, -0.0841,
         -1.0678,  0.2935,  0.0235, -0.2438,  0.3161,  0.6100, -0.5155,  0.0664,
          0.2547, -0.7639,  0.1661,  0.0084]], grad_fn=<AddmmBackward0>)


After ReLU: tensor([[0.3587, 0.2102, 0.0000, 0.0000, 0.0000, 0.3599, 0.0967, 0.0000, 0.0000,
         0.0000, 0.0402, 0.0000, 0.0453, 0.5766, 0.0000, 0.0000, 0.3302, 0.0000,
         0.6606, 0.1536],
        [0.0278, 0.2044, 0.0194, 0.0000, 0.1331, 0.1855, 0.2654, 0.0000, 0.0000,
         0.0000, 0.1695, 0.0000, 0.0639, 0.3781, 0.0000, 0.0016, 0.1006, 0.0000,
         0.0049, 0.0000],
        [0.0000, 0.4846, 0.0255, 0.0000, 0.2868, 0.2964, 0.1932, 0.0000, 0.0000,
         0.2935, 0.0235, 0.0000, 0.3161, 0.6100, 0.0000, 0.0664, 0.2547, 0.0000,
         0.1661, 0.0084]], grad_fn=<ReluBackward0>)

nn.Sequential

nn.Sequential是一个有序容器,顺序就是定义模块的顺序
可以快速生成一个需要的模型

seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)

nn.Softmax

将线性层的结果归一化(对于预测没有,但是对于训练的反向传播是必须的)
dim参数指定了总和必须为1那一维

softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
pred_probab
tensor([[0.1006, 0.0905, 0.1013, 0.0872, 0.0908, 0.0826, 0.1178, 0.0947, 0.1103,
         0.1242],
        [0.1014, 0.0926, 0.1075, 0.0888, 0.0929, 0.0814, 0.1133, 0.0975, 0.0976,
         0.1270],
        [0.0956, 0.0889, 0.1124, 0.0959, 0.0908, 0.0919, 0.1060, 0.0927, 0.0972,
         0.1286]], grad_fn=<SoftmaxBackward0>)

Model Parameters

神经网络的许多层都是参数化的
比如线性层的权重和偏置 在训练过程中不断更新
使用parameters()named_parameters()方法可以查看模型参数

print(f"Model structure: {model}\n\n")  # 打印模型结构

for name, param in model.named_parameters():
    # 参数太多只切片出前两行
    print(f"Layer: {name} | Size: {param.size()} | Values : \n{param[:2]} \n")
Model structure: NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
  )
)


Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : 
tensor([[ 0.0179, -0.0317,  0.0030,  ..., -0.0030, -0.0020,  0.0053],
        [ 0.0156, -0.0281, -0.0177,  ...,  0.0180,  0.0110, -0.0190]],
       device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : 
tensor([ 0.0222, -0.0159], device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : 
tensor([[ 0.0048, -0.0003, -0.0424,  ..., -0.0026, -0.0006, -0.0176],
        [ 0.0085, -0.0041, -0.0046,  ..., -0.0101,  0.0131, -0.0031]],
       device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : 
tensor([-0.0206,  0.0308], device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : 
tensor([[-0.0323, -0.0378, -0.0346,  ...,  0.0121, -0.0439,  0.0028],
        [ 0.0294, -0.0189, -0.0059,  ...,  0.0434,  0.0384, -0.0268]],
       device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : 
tensor([ 0.0313, -0.0208], device='cuda:0', grad_fn=<SliceBackward0>)

Chapter 5: AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD

注:关于反向传播和自动微分的内容这里只是简略的介绍 加单了解便可以自主搭建模型
若要详细学习请参考其他内容

  • 训练模型最常见的算法就是梯度下降法
    在这种算法中根据损失函数给定的各个参数的梯度来对参数进行更新
  • 为了计算梯度 Pytorch提供了内置的微分计算torch.autograd,它能计算任何计算图中的梯度

下面以一层网络为例

import torch

x = torch.ones(5)  # 模拟输入
y = torch.zeros(3)  # 模拟期望输出
w = torch.randn(5, 3, requires_grad=True)  # 模拟权重
b = torch.randn(3, requires_grad=True)  # 模拟偏置
z = torch.matmul(x, w)+b  # 前向传播

loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)  # 交叉熵损失

Tensors, Functions and Computational graph

上述代码定义了这样一个计算图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MhyLPXB-1658937531515)(attachment:5f153475-a194-4aca-ab5c-b5efcc2b48a2.png)]

在上面的网络中,wb是需要更新优化的参数
为了能够根据损失函数给定的梯度进行更新,我们需要将前向传播过程中的张量的requires_grad设置为True

  • 注意:我们可以在创建张量的时候设置requires_grad 也可以在之后用x.requires_grad_(True)来设置

我们构造计算图时,对张量进行计算操作所使用的函数实际上是Function的对象
这个对象可以进行前向传播,同时也能保存好反向传播所需要的梯度,其反向传播所需要的梯度的reference存储在张量的grad_fn中。

更多能容请见官方文档

print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")
Gradient function for z = <AddBackward0 object at 0x7f5fc60b79d0>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f5fc60b7e80>

Computing Gradients

为了进行参数优化,我们需要计算相关参数对于损失函数的导数(derivatives)
也就是说(namely) 我们需要 pytorch官网进不去 pytorch 官方文档_深度学习在所有变量取固定值条件下的取值(即当前时刻的偏导数)

为了计算导数,我们可以使用loss.backward(),然后通过w.gradb.grad查看计算结果

loss.backward()

print(w.grad)
print(b.grad)
tensor([[0.1592, 0.1275, 0.3297],
        [0.1592, 0.1275, 0.3297],
        [0.1592, 0.1275, 0.3297],
        [0.1592, 0.1275, 0.3297],
        [0.1592, 0.1275, 0.3297]])
tensor([0.1592, 0.1275, 0.3297])
  • 注意:
  1. 我们只能获得计算图中requires_grad置为true的叶节点的梯度,其他节点均无法获得
  2. 我们只能在同一个计算图上使用一次反向传播来进行计算,如果需要重复计算则需要将retain_grad=True传递给backward来进行调用

Disabling Gradient Tracking

在我们进行预测、验证、测试等操作时是用不到方向传播和梯度这时候需要将requires_grad=True关闭
通过torch.no_grad()方法达到目的

z = torch.matmul(x, w)+b
print(z.requires_grad)  # 存在梯度计算

# 方法一
with torch.no_grad():
    # 不存在梯度计算
    z = torch.matmul(x, w)+b
print(z.requires_grad)

# 方法二
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
True
False
False

一些无需梯度的情况:

  1. 将网络中的一些参数视为frozen parameters,这在微调预训练网络中十分常见
  2. 为了加速,当你只做前向传播时可以关闭跟踪

More on Computational Graphs

从概念上说,autograd在由Function对象组成的DAG(有向无环图)中保存了计算过程的数据和操作。
在这个DAG中,叶子是输入,根是输出,从根追踪到叶子,采用链式法则进行自动计算梯度(具体如何计算可以参考鱼书的教程)

前向传播:

  • 计算给定的操作,得到结果张量
  • 在DAG中保存操作的梯度函数

反向传播(在.backward时调用):

  • 从每一个grad_fn中计算梯度 (见上文
  • 在各自张量的.grad属性中累加他们
  • 使用连式法则,反向传播到叶子张量

注意:
每次.backward()后计算图总是重新构建,这使得你在训练或者其他过程中可以任意修改所需要进行反向传播的结构

Optional Reading: Tensor Gradients and Jacobian Products

选读:略

Chapter 6: OPTIMIZING MODEL PARAMETERS

我们已经有了数据和模型,可以通过优化数据来训练、测试和验证模型。
每将全部数据迭代一次称为一个epoch,过程中模型对输出进行预测、计算预测的误差和对应参数的导数,并使用梯度下降优化参数。

Prerequisite Code

我们使用之前chapter2和chapter4中的代码

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda

training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

Hyperparameters

超参数用于控制模型优化的过程。
不同的超参数会影响模型训练和收敛速度。

  • Number of Epochs 迭代全部数据的次数
  • Batch Size 每次反向传播更新参数传入的样本个数
  • Learning Rate 更新的快慢(不长)
learning_rate = 1e-3
batch_size = 64
epochs = 5

Optimization Loop

设置好超参数之后,我们可以使用优化循环来训练和优化模型
每循环一次称为一个epoch

每个epoch包含包含两个主要的部分:

  • The Train Loop 迭代训练数据尝试找到最优参数
  • The Validation/Test Loop 迭代测试集检查模型的效果

下面让我们简单熟悉一下训练循环中的一些感念

Loss Function

损失函数度量获得的结果与目标值的不同程度,并且它是我们在训练期间想要最小化的损失函数
为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。

常见的损失函数包括用于回归任务的nn.MSELoss(均方差)、用于分类任务的nn.NLLLossnn.CrossEntropyLoss结合了nn.LogSotfmaxnn.NLLLoss

我们将模型的输出logits传递给交叉熵损失函数,他会将输出标准化同时计算预测误差

# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()

Optimizer

优化器定义了在每一步训练过程中调整模型参数减少误差的方式。
所有优化逻辑都封装在了优化器对象当中。

这里我们使用的是SGD(随机梯度下降法)优化器
此外,PyTorch中还有许多不同的优化器,比如ADAM和RMSProp,它们可以更好地处理不同类型的模型和数据。

我们通过填入需要训练的模型参数和学习率来初始化优化器

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练循环中,通过下面三步进行优化:

  • 调用optimizer.zero_grad()将梯度重置,避免重复累加、计算
  • 将预测的损失反向传播loss.backward() pytorch存储每个参数的损失梯度
  • 计算完梯度后,调用optimizer.step()进行参数优化

Full Implementation

定义train_loop来优化参数
定义test_loop来分析模型在测试集上的表现

def train_loop(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        # Compute prediction and loss
        pred = model(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, y in dataloader:
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

然后初始化损失函数和优化器,传给train_loop和test_loop
随意增加训练的批次观察模型表现

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train_loop(train_dataloader, model, loss_fn, optimizer)
    test_loop(test_dataloader, model, loss_fn)
print("Done!")
Epoch 1
-------------------------------
loss: 2.300190  [    0/60000]
loss: 2.287218  [ 6400/60000]
loss: 2.266051  [12800/60000]
loss: 2.265971  [19200/60000]
loss: 2.242163  [25600/60000]
loss: 2.203471  [32000/60000]
loss: 2.223280  [38400/60000]
loss: 2.174584  [44800/60000]
loss: 2.178796  [51200/60000]
loss: 2.151764  [57600/60000]
Test Error: 
 Accuracy: 37.9%, Avg loss: 2.141109 

Epoch 2
-------------------------------
loss: 2.155069  [    0/60000]
loss: 2.146334  [ 6400/60000]
loss: 2.079271  [12800/60000]
loss: 2.106361  [19200/60000]
loss: 2.047215  [25600/60000]
loss: 1.979362  [32000/60000]
loss: 2.017431  [38400/60000]
loss: 1.924942  [44800/60000]
loss: 1.937264  [51200/60000]
loss: 1.875216  [57600/60000]
Test Error: 
 Accuracy: 57.1%, Avg loss: 1.864516 

Epoch 3
-------------------------------
loss: 1.900329  [    0/60000]
loss: 1.870771  [ 6400/60000]
loss: 1.742582  [12800/60000]
loss: 1.800071  [19200/60000]
loss: 1.677390  [25600/60000]
loss: 1.635337  [32000/60000]
loss: 1.659964  [38400/60000]
loss: 1.552326  [44800/60000]
loss: 1.582451  [51200/60000]
loss: 1.492734  [57600/60000]
Test Error: 
 Accuracy: 60.6%, Avg loss: 1.501106 

Epoch 4
-------------------------------
loss: 1.569164  [    0/60000]
loss: 1.537819  [ 6400/60000]
loss: 1.377583  [12800/60000]
loss: 1.463795  [19200/60000]
loss: 1.335719  [25600/60000]
loss: 1.342435  [32000/60000]
loss: 1.349736  [38400/60000]
loss: 1.271155  [44800/60000]
loss: 1.310501  [51200/60000]
loss: 1.225152  [57600/60000]
Test Error: 
 Accuracy: 63.1%, Avg loss: 1.244920 

Epoch 5
-------------------------------
loss: 1.323912  [    0/60000]
loss: 1.308639  [ 6400/60000]
loss: 1.133736  [12800/60000]
loss: 1.248159  [19200/60000]
loss: 1.121069  [25600/60000]
loss: 1.154779  [32000/60000]
loss: 1.163743  [38400/60000]
loss: 1.100531  [44800/60000]
loss: 1.144539  [51200/60000]
loss: 1.072873  [57600/60000]
Test Error: 
 Accuracy: 64.7%, Avg loss: 1.088131 

Done!

Chapter 7: SAVE AND LOAD THE MODEL

下面我们进行模型的保存和加载

import torch
import torchvision.models as models

Saving and Loading Model Weights

pytorch将模型学习到的参数存储在一个内部的字典里(state_dict)
可以通过torch.save方法保存

model = models.vgg16(pretrained=True)
torch.save(model.state_dict(), 'model_weights.pth')
/home/dwei/.conda/envs/mytorch/lib/python3.9/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and will be removed in 0.15, please use 'weights' instead.
  warnings.warn(
/home/dwei/.conda/envs/mytorch/lib/python3.9/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and will be removed in 0.15. The current behavior is equivalent to passing `weights=VGG16_Weights.IMAGENET1K_V1`. You can also use `weights=VGG16_Weights.DEFAULT` to get the most up-to-date weights.
  warnings.warn(msg)

要加载以上述方式保存的模型需要实现实例化一个同样的模型
然后使用load_state_dict()方法加载保存好的参数

model = models.vgg16() # 初始化一个模型
model.load_state_dict(torch.load('model_weights.pth'))
'''
    确保在推理之前进入model.eval()模式,将dropout层和batch normalization层设为评估模式
    如果不设置将出现不一致的情况
'''
model.eval()  # 进入验证模式
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)
  )
)

Saving and Loading Models with Shapes

前面的方法不能将模型的结构一起保存
如果我们需要保存模型的结构 就需要用下面的方式

torch.save(model, 'model.pth')

加载模型:
(同样需要事先定义好模型的类才能加载)
This approach uses Python pickle module when serializing the model, thus it relies on the actual class definition to be available when loading the model.

model = torch.load('model.pth')

未完待续