0
点赞
收藏
分享

微信扫一扫

【深度学习02】PyTorch之LeNet

LeNet

PyTorch tensor的通道排序:[batch_size, channel, height, width]

官方Demo:Training a Classifier — PyTorch Tutorials 1.11.0+cu102 documentation

3.1 网络结构

image-20220505094448741

说明:

卷积运算后的特征图输出尺寸计算公式:

image-20220505164745419

其中, W W W为输入图像的尺寸(W), F F F为过滤器的尺寸(kernel_size), P P P为填充的幅度(padding), S S S为步长(stride)

  1. 第一层:卷积层(C1)
    • 这一层的输入就是原始的图像像素:若图像是灰度图(单通道),LeNet接受的输入层大小为1x32x32,若图像是彩色RGB图(三通道),LeNet接受的输入层大小为3x32x32
    • 本层过滤器(卷积核)的尺寸为5x5,深度(个数)为6,不使用全0填充,步长为1
    • 因为没有使用全0填充,所以这一层的特征图输出尺寸为32-5+1=28,深度(张数)为6,即6x28x28
    • 按照标准的卷积层 ,本层总共有5x5x1x6+6=156个参数,其中6个为偏置项参数
  2. 第二层:池化层(S2)
    • 这一层的输入为第一层的输出,是一个6x28x28的节点矩阵
    • 本层采用的过滤器(池化窗口)大小为2x2,步长为2,所以本层的输出矩阵大小为6x14x14
  3. 第三层:卷积层(C3)
    • 这一层的输入为第二层的输出,输入矩阵大小为6x14x14
    • 本层过滤器(卷积核)的尺寸为5x5,深度(个数)为16
    • 本层不使用全0填充,步长为1,所以本层的输出矩阵大小为 16x10x10
    • 按照标准的卷积层 ,本层应该有5x5x6x16+16=2416个参数,其中16个为偏置项参数
  4. 第四层:池化层(S4)
    • 这一层的输入为第三层的输出,是一个16x10x10的节点矩阵
    • 本层采用的过滤器(池化窗口)大小为2×2,步长为2,所以本层的输出矩阵大小为16x5x5
  5. 第五层:全连接的卷积层(C5)
    • 这一层的输入为第二层的输出,输入矩阵大小为16x5x5
    • 本层过滤器(卷积核)的尺寸为5×5,深度(个数)为120
    • 本层不使用全0填充,步长为1,所以本层的输出矩阵大小为120x1x1
    • 按照标准的卷积层 ,本层应该有5x5x16x120+120=48120个参数,其中120个为偏置项参数
    • 本层也可以如下理解:
      • 本层的输入矩阵大小为16x5x5,因为过滤器(卷积核)的尺寸就是5×5,所以和全连接层没有区别
      • 16x5x5矩阵中的节点拉成一个向量/一维张量,那么这一层和全连接层输入就一样了
  6. 第六层:全连接层(F6)
    • 本层的输入节点个数为120个,输出节点个数为84个,总共参数为120x84+84=10164个,其中84个为偏置项参数
  7. 第七层:输出层(Output)
    • 本层的输入节点个数为84个,输出节点个数为10个,分别代表数字0到9,总共参数为84x10+10=850个,其中10个为偏置项参数

3.2 PyTorch之LeNet实现CIFAR10分类

image-20220505164555257

Pytorch官方Demo(LeNet)

3.2.1 加载和规范化

# PyTorch框架
import torch
# torchvision:计算机视觉工具包
import torchvision
import torchvision.transforms as transforms

# 数据预处理
# torchvision加载数据集的输出是范围[0,1]的PILImage图像,我们需将它们转换为归一化范围[-1,1]的张量
transform = transforms.Compose(
    [# 随机旋转图片
     # transforms.RandomHorizontalFlip(),
     # ToTensor()能够把灰度范围从0-255变换到0-1之间,将shape为(H, W, C)的PIL.Image或者numpy.array数据类型转为shape为(C, H, W)的torch.FloatTensor类型
     # 例如:test_data_x = test_data.data.type(torch.FloatTensor)/255.0 
     # 尺寸为Channel*Height*Width,数值归一化范围缩小为[0.0, 1.0](归一化方法:直接除以255即可)
     transforms.ToTensor(),
     # Normalize()数据标准化处理(均值为0,方差为1):对数据按通道进行标准化,即减去均值,再除以标准差
     # 使用公式"(x-mean)/std",将0-1变换到(-1,1)
     # CIFAR10数据值都是彩色RGB图,所以图像的通道数有3个,因此均值和标准差各有3个
     # Normalize的两个参数是两个不变的list,即是tuple
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 训练集(50000张图片)
# CIFAR10数据集的下载:下载位置当前目录的data文件下
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)

# 实际过程需要分批次(batch)训练,打乱训练集,每一批随机拿出50张图片进行训练
trainloader = torch.utils.data.DataLoader(trainset, batch_size=50,
                                          shuffle=True, num_workers=0)

# 测试集(10000张图片)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=False, transform=transform)

# batch_size=10000:一次性全部拿出来,来计算测试集的准确率
testloader = torch.utils.data.DataLoader(testset, batch_size=10000,
                                         shuffle=False, num_workers=0)

# 将testloader转换成可迭代的迭代器,它的next方法获取测试集单个batch中的所有样本图像和标签,用于accuracy计算
test_data_iter = iter(testloader)
test_image,test_label = test_data_iter.next()

# 标签
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 可视化部分训练图像
import matplotlib.pyplot as plt
import numpy as np

def imshow(img):
    img = img / 2 + 0.5     # 反标准化
    npimg = img.numpy()     # 将图片转化为原来的numpy格式
    plt.imshow(np.transpose(npimg, (1, 2, 0))) # 转换成原始shape格式
    plt.show()
    
# 获取训练集单个batch中的所有样本图像和标签
train_data_iter = iter(trainloader)
train_images, train_labels = train_data_iter.next()

# 输出标签
print(' '.join(f'{classes[train_labels[j]]:5s}' for j in range(50)))
# 显示图像
imshow(torchvision.utils.make_grid(train_images))

3.2.2 定义卷积神经网络(LeNet)

  1. 卷积(Conv2d)在PyTorch中对应的函数是:
    • torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’)
  2. 激活函数(ReLU)在PyTorch中对应的函数是:
    • ReLU()
  3. 平均池化(AvgPool2d)在PyTorch中对应的函数是:
    • AvgPool2d(kernel_size, stride)
  4. tensor的展平(对tensor进行reshape)在PyTorch中重构张量的维度作为全连接层的输入对应的函数是:
    • x = x.view(x.shape[0], -1) —> x.shape[0]=batch_size行 channel*height*width列【一行对应一张图片】
  5. 全连接( Linear)在PyTorch中对应的函数是:
    • Linear(in_features, out_features, bias=True)
import torch.nn as nn
import torch.nn.functional as F

# 定义网络
class LeNet(nn.Module):                    # 继承父类nn.Module
    # 初始化网络
    def __init__(self):                    
        super(LeNet, self).__init__()      # super()继承父类的构造函数,多继承需用到super函数
        self.conv1 = nn.Conv2d(3, 16, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
     
    # 正向传播                        # 图像尺寸参数变化
    def forward(self, x):            # input(3, 32, 32)        
        x = F.relu(self.conv1(x))    # output(16, 28, 28)
        x = self.pool1(x)            # output(16, 14, 14)
        x = F.relu(self.conv2(x))    # output(32, 10, 10)
        x = self.pool2(x)            # output(32, 5, 5)
        # x.view():对tensor进行reshape,即重构张量的维度
        # x = x.view(x.shape[0], -1) 展平为[1个批量大小的行,32*5*5=800列]的矩阵,矩阵的每一行就是这个批量中每张图片的各个参数(即32*5*5),即矩阵中一行对应一张图片
        x = x.view(-1, 32*5*5)       # output(32*5*5) 1个批量大小的行 32*5*5=800列
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        
        return x
    
# 测试验证【可选】  
# import torch
# input = torch.rand([50,3,32,32])
# model = LeNet()
# print(model)
# output = model(input)

3.2.3 定义损失函数和优化器

import torch.optim as optim
from model import LeNet
import torch.nn as nn

# 实例化网络模型
net = LeNet()                                     
# 定义损失函数为分类交叉熵损失函数 
loss_function = nn.CrossEntropyLoss()
# 定义带有动量的SGD优化器(训练参数,学习率,动量参数)
# optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# 定义Adam优化器(训练参数,学习率)
optimizer = optim.Adam(net.parameters(), lr=0.001)

3.2.4 训练网络

名称说明:

  • epoch:对训练集的全部数据进行一次完整的训练,称为一次epoch
  • batch:由于硬件算力有限,实际训练时将训练集分成多个批次训练,每批数据尺寸(大小)为batch_size
  • iteration或step:对一个batch的数据训练的过程称为一个iteration或step

以本训练为例,训练集一共有50000个样本,batch_size=50,那么完整训练一次样本:iteration或step=1000

# 训练过程
for epoch in range(5):  # 一个epoch即对整个训练集进行一次训练,循环几次就是几轮
    
    running_loss = 0.0
    # enumerate用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和索引,一般用在for循环当中
    # 通过这个循环来遍历训练集的样本,不仅能返回每一批的训练数据data,还会返回每一批data对应的步数
    for step, data in enumerate(trainloader, start=0):
       # get the inputs: data is a list of [tarin_inputs, tarin_labels]
        tarin_inputs, tarin_labels = data
        # 历史损失梯度清零,每次计算一个batch都要调用一次
        # 如果不清除历史梯度,就会对计算的历史梯度进行累加(通过这个特性可以变相的实现一个很大的batch数值训练)
        optimizer.zero_grad()                      
        outputs = net(tarin_inputs)                # 正向传播             
        loss = loss_function(outputs, labels)      # 计算损失
        loss.backward()                            # 反向传播
        optimizer.step()                           # 优化器更新参数

       # 打印统计信息
        running_loss += loss.item()        # tensor中取数值的方法:.item() 
        if step % 500 == 499:              # 每500步打印一次
           # with是一个上下文管理器,在以下步骤中(验证过程中)不用计算每个节点的损失梯度,防止内存占用
            with torch.no_grad():   
                # 测试集传入网络(test_batch_size=10000),output维度为[10000,10]
                outputs = net(test_image)  
                
                # 查找output每一行中最大值对应的索引(标签)作为预测输出
                # 参数dim=1表示取tensor的第2维度,[1]表示只需知道index值是多少,无需取value
                # predict_y = torch.max(outputs, dim=1)[1]
                
                # 查找output每一行中最大值对应的索引(标签)作为预测输出
                # argmax函数:torch.argmax(input, dim=None, keepdim=False)
                #       返回指定维度最大值的序号,就是dim这个维度的最大值的index
                # 参数dim的不同值表示不同维度:特别的在dim=0表示二维中的列,dim=1在二维矩阵中表示行
                predict_y  = torch.argmax(outputs, dim=1)
                
                accuracy = (predict_y == test_label).sum().item() / test_label.size(0)
                # 训练迭代到第几轮,某一轮的第几步,500步的平均误差,测试样本的准确率
                print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                      (epoch + 1, step + 1, running_loss / 500, accuracy))
                running_loss = 0.0
print('Finished Training')

# 保存训练完成的网络模型参数(权重)
save_path = './Lenet.pth'     # 保存路径
torch.save(net.state_dict(), save_path)

3.2.5 网络预测分类

import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
 
transform = transforms.Compose(
    [transforms.Resize((32, 32)),   # 图片尺寸应保持与训练集一样,所以需要将图片尺寸resize到32x32
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
 
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
 
# 实例化网络模型
net = LeNet()
# 加载训练完成的网络模型参数(权重)
net.load_state_dict(torch.load('Lenet.pth'))
 
# 通过from PIL import Image此模块来载入图像
img = Image.open('1.jpg')   # [H,W,C] ---> 需要转换成tensor
img = transform(img)        # [C,H,W]
 
# 对数据加一个新的维度,dim=0在最前面加,因为tensor的参数是[batch_size, channel, height, width] 
img = torch.unsqueeze(img, dim=0)  # [N, C, H, W]

 # 在以下步骤中(预测过程中)不用计算每个节点的损失梯度,防止内存占用
with torch.no_grad():
    outputs = net(img)
    # 以output中值最大位置对应的索引(标签)作为预测输出
    # predict_index  = torch.argmax(outputs, 1).data.numpy()
    predict_index = torch.max(outputs, dim=1)[1].data.numpy()
    # 得到一个预测各类别的概率分布
    predict = torch.softmax(outputs,dim=1)
    
# 输出概率
print(predict)
# 输出类别
print(classes[int(predict_index)])
举报

相关推荐

0 条评论