网络层

网络框架

  1. __init__ 中,初始化父类并创建「网络模块」
  2. 重写 forward ,组织「网络模块」
PYTHON
import torch

class Model(torch.nn.Module):
    def __init__(self):
        """ 初始化网络层 """
        # 初始化父类
        super(Model, self).__init__()
        self.fcLyaer1 = torch.nn.Linear(32,16)
        self.fcLyaer2 = torch.nn.Linear(16,8)

    def forward(self,input):
        """ 搭建网络 """
        a1 = self.fcLyaer1(input)
        a2 = self.fcLyaer2(a1)
        return a2

# 创建模型
model = Model()


# 模型前向传播
x = torch.ones((3,32))

# 模型输出
out = model(x)
  • model.train():启动模型训练模式,在该模式下,例如 drop out 这些特殊层,才会被执行
  • model.eval():启动模型预测模式,该模式会关闭辅助训练特殊模块,例如 drop out

网络模块

全连接层

  • 输入: (N, in_features),N 为输入样本数
  • 输出: (N, out_features)
PYTHON
# in_features:输入的维度
# out_featuers:输出的维度
# bias:是否启用 b
torch.nn.Linear(in_features : int, out_features: int, bias: bool=True)

# 根据输入自动判断 in_features
torch.nn.LazyLinear( out_features: int, bias: bool=True)

二维卷积

  • 输入: $(N,C_{in},H,W)$

  • 输出: $(N,C_{out},H_{out},W_{out})$

    $$ \begin{aligned} H_{\rm {out }} &=\left\lfloor\frac{H_{i n}+2 \times \rm { padding }[0]-\rm { dilation }[0] \times(\rm { kernel_size }[0]-1)-1}{\operatorname{stride}[0]}+1\right\rfloor\ W_{\rm {out }} &=\left \lfloor \frac{W_{\rm {in }}+2 \times \rm { padding }[1]-\operatorname{dilation}[1] \times(\rm { kernel_size }[1]-1)-1}{\rm { stride }[1]}+1 \right \rfloor \end{aligned} $$

  • dilation: 卷积核膨胀

  • 每个参数的意义

PYTHON
# in_channels:输入图片的通道
# out_channels:输出图片的通道
# kernel_size:卷积核的尺寸
# stride:卷积移动步长
# dilation:卷积核膨胀倍数
# groups:卷积核的分配,1 就是常规意义的卷积
torch.nn.Conv2d(in_channels: int, out_channels: int, 
                kernel_size, stride=1, 
                padding=0, , padding_mode: str='zeros'
                dilation=1, groups: int=1, bias: bool=True)

三维卷积

  • 输入: $(N,C_{in},D,H,W)$,在二维图片的基础上多了一个维度,用来表示三维的数据。
  • 输出: $(N,C_{out},D_{out},H_{out},W_{out})$,经过卷积后的结果是三维的
  • 卷积核: 卷积核是三维的小立方体,在三维数据的三个方向上滑动。
PYTHON
torch.nn.conv3d(in_channels: int, out_channels: int, 
                kernel_size: _size_2_t, stride: _size_2_t=1, 
                padding=0, , padding_mode: str='zeros'
                dilation=1, groups: int=1, bias: bool=True)

池化层

  • ceil_mode:当池化层的卷积核出界时,结果是否保留
PYTHON
# 最大池化
torch.nn.MaxPool2d(kernel_size, stride, padding, dilation=1, 
                    ceil_mode=False)

# 平均池化层
torch.nn.AvgPool2d(kernel_size, stride, padding, dilation=1, 
                    ceil_mode=False)

激活层

PYTHON
torch.nn.ReLU(inplace=False)

其他层

sequential

  • 作用:将多个神经网络层组合为一个
PYTHON
class Model(torch.nn.Module):
    def __init__(self):
        """ 初始化网络层 """
        # 初始化父类
        super(Model, self).__init__()

        # 将多模块整合
        self.sequentialNet = torch.nn.Sequential(
            torch.nn.Conv2d(3,4,(3,3)),
            torch.nn.MaxPool2d((2,2)),
            torch.nn.ReLU(True),
            torch.nn.Flatten(),
            torch.nn.Linear(3844,16),
            torch.nn.ReLU(True),
            torch.nn.Linear(16,8)
        )

    def forward(self,input):
        """ 搭建网络 """
        out = self.sequentialNet(input)
        return out

损失函数

  • 平均绝对误差 MAE(mean absolute error)

    PYTHON
    torch.nn.L1Loss(size_average:bool, reduce:bool, reduction='mean')
  • 均方差 MSE

    PYTHON
    torch.nn.MSELoss(size_average:bool, reduce:bool, reduction='mean')
  • 交叉熵 :会根据标签是「顺序编码」还是「独热编码」来选择对应的交叉熵损失函数

    PYTHON
    orch.nn.CrossEntropyLoss(weight=None, size_average:bool, ignore_index=- 100, reduce:bool, reduction='mean', label_smoothing=0.0)

经典网络模型

PYTHON
# vgg16 模型
# pretrained:模型的系数是否重新训练
torchvision.models.vgg16(pretrained: bool = False, progress: bool = True, **kwargs: Any)

优化器

PYTHON
# adam 算法
# params:神经网络的相关系数,例如 w,b,卷积核等
optimizer = torch.optim.Adam(params=net.parameters(), lr=0.001, 
        betas=(0.9, 0.999), eps=1e-08, weight_decay=0, 
        amsgrad=False, *, maximize=False)

# 相关系数梯度重置
optimizer.zero_grad()

# 反向传播,根据损失函数,计算系数梯度
loss.backward()

# 相关系数在反向传播后进行一次更新
optimizer.step()

模型保存与读取

  • 保存模型结构信息+系数
    PYTHON
    # 保存模型,文件后缀为 .pth 
    torch.save(net,path)
    
    # 加载模型
    torch.load(path)
  • 保存模型系数
    PYTHON
    # 保存系数,文件后缀为 .pth
    torch.save(net.state_dict(),path)
    
    # 读取
    net.load_state_dict(torch.load(path)) 

[!note|style:flat] 方案一保存模型,仅仅只是保存了模型信息,模型的 class 并没有保存,因此,导入模型时,还是需要给出模型的具体 class

GPU 调用

GPU 训练

  • 思路: 网络模型、数据(样本与标签)、损失函数全部添加到GPU中

  • 实现:

    • to()

      PYTHON
      device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
      
      # 模型
      model = Model()
      model = model.to(device)
      
      # 损失函数
      lossFcn = torch.nn.CrossEntropyLoss()
      lossFcn = lossFcn.to(device)
      
      # 数据
      datas = datas.to(device)
    • cuda()

      PYTHON
      # 模型
      model = Model()
      model = model.cuda()
      
      # 损失函数
      lossFcn = torch.nn.CrossEntropyLoss()
      lossFcn = lossFcn.cuda()
      
      # 数据
      datas = datas.cuda()

加载模型

对「GPU」训练的模型,想要在「CPU」上进行测试时,需要在加载时进行转换

PYTHON
model = torch.load('../model/cifar_19.pth',map_location=torch.device('cpu'))

附录:cifa10 案例

PYTHON
import torch
import torchvision
from  torch.utils.tensorboard import SummaryWriter
from PIL import Image
# %% 模型
class Model(torch.nn.Module):
    """ 分类模型 """
    def __init__(self):
        super(Model, self).__init__()
        self.cifar = torch.nn.Sequential(
            torch.nn.Conv2d(3,6,(5,5)),
            torch.nn.MaxPool2d(2),
            torch.nn.ReLU(True),
            torch.nn.Conv2d(6,16,(5,5)),
            torch.nn.MaxPool2d(2),
            torch.nn.ReLU(True),
            torch.nn.Flatten(),
            torch.nn.Linear(400,120),
            torch.nn.ReLU(True),
            torch.nn.Linear(120,84),
            torch.nn.ReLU(True),
            torch.nn.Linear(84,10)
        )

    def forward(self,input):
        out = self.cifar(input)
        return out

# %% 训练

# 设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 日志
logWriter = SummaryWriter('../logs')

# 转换图片为 tensor
toTensor = torchvision.transforms.ToTensor()

# 数据集
trainImages = torchvision.datasets.CIFAR10('../asset/cifa10',train=True,
                                            transform=toTensor,download=True)
testImages = torchvision.datasets.CIFAR10('../asset/cifa10',train=False,
                                            transform=toTensor,download=True)
# loader
trainLoader = torch.utils.data.DataLoader(trainImages,64)
testLoader = torch.utils.data.DataLoader(testImages,128)

# 参数
epochs = 20
learnRate = 0.001

# 模型
cifarNet = Model()
cifarNet = cifarNet.to(device)


# 损失函数
lossFcn = torch.nn.CrossEntropyLoss()
lossFcn = lossFcn.to(device)

# 优化器
optimizer = torch.optim.Adam(cifarNet.parameters(),lr=learnRate)

for epoch in range(epochs):
    print("--------------epoch {} ----------------".format(epoch))
    batchCount = 0
    lossSum = 0.0

    # 训练
    for batch in trainLoader:
        # 获取训练数据与标签
        datas,targets = batch
        datas = datas.to(device)
        targets = targets.to(device)
        
        # 前向传播
        cifarNet.train()
        out = cifarNet(datas)


        # 计算损失
        loss = lossFcn(out,targets) 
        lossSum = lossSum + loss.item()
        
        # 重置梯度
        optimizer.zero_grad()

        # 反向传播
        loss.backward()

        # 更新梯度
        optimizer.step()

        # 打印损失
        batchCount = batchCount + 1
        if batchCount % 100 == 0:
            print("\tbatch: {},loss: {}".format(batchCount,loss.item()))

    print("epoch: {},loss: {}".format(epoch,lossSum / len(trainLoader)))
    # 记录损失
    logWriter.add_scalar('train loss', lossSum / len(trainLoader),global_step=epoch)

    # 测试
    rightCount = 0
    for batch in testLoader:
        datas,targets = batch
        datas = datas.to(device)
        targets = targets.to(device)

        # 关闭梯度计算
        with torch.no_grad():
            # 预测
            cifarNet.eval()
            out = cifarNet(datas)
    
            # 得到分类
            predict = torch.argmax(out,dim=1)
            
            # 记录分类正确的
            rightCount = rightCount + (predict==targets).sum()

    print("epoch: {},accuracy: {}".format(epoch,float(rightCount) / len(testImages)))
    # 记录损失
    logWriter.add_scalar('test accuracy', float(rightCount) / len(testImages),global_step=epoch)

    # 保存模型
    torch.save(cifarNet,'../model/cifar_{}.pth'.format(epoch))

logWriter.close()

模型测试

PYTHON
resize = torchvision.transforms.Resize((32,32))
# 预测图片
img = Image.open('../asset/cat.jpeg')
img = resize(img)
imgTensor = toTensor(img).reshape(1,3,32,32)

# 加载模型
cifarNet = torch.load('../model/cifar_19.pth',map_location=torch.device('cpu'))

# 关闭梯度计算
with torch.no_grad():
    # 预测
    cifarNet.eval()
    out = cifarNet(imgTensor)

    # 得到分类
    predict = torch.argmax(out,dim=1)
    print(testImages.classes[predict])

License

Author: 海拉鲁的三角

Link: http://localhost:1313/artificial_intelligence/posts/pytorch/model/

License: MIT

只要学不死,就往死里学