LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)

Lenet-5

LeNet-5的结构如下图所示,网络一共有7层(不包含输入层):

图中的Convolutions代表卷积、Subsampling代表池化、Full connection代表全连接层。

LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)_第1张图片

1、卷积层C1

卷积层的作用为提取图像特征,输入为1×32×32的原始图像,卷积核大小为5×5,得到的feature map大小为28×28。卷积核个数为6,卷积得到feature map的通道也为6,所以得到的feature map为6×28×28。

2、池化层S2

池化层用于压缩数据和参数的量。输入为C1的输出6×28×28的feature map,按步幅2进行2 × 2的Max池化,输出的feature map大小是14×14。而池化不影响通道数,所以得到的feature map为6×14×14。

3、卷积层C3

输入为S2的输出6×14×14的feature map,卷积核大小为5×5,卷积得到的输出feature map大小为10。卷积核个数为16,卷积得到feature map的通道也为16,所以得到的feature map为16×10×10。

4、池化层S4

输入为C1的输出16×10×10的feature map,按步幅2进行2 × 2的Max池化,输出的feature map大小是5×5。而池化不影响通道数,所以得到的feature map为16×5×5。

5、卷积层C5

该层的输入为S4层输出的16×5×5大小的feature map,卷积核大小为5×5,卷积核个数为120,卷积得到feature map的通道也为16,所以得到的feature map为120×1×1。其实也可以理解成全连接层,因为这里5×5的输入,用5×5的卷积核做卷积,最后输出1×1

6、全连接层F6层

该层的输入为C5的120维向量计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数输出。

7、全连接层Output

Output层也是全连接层,共有10个节点,分别代表0~9。

小结

LeNet-5是一种用于手写体字符识别的非常高效的卷积神经网络,它被美国银行用于手写数字识别。卷积神经网络能够很好的利用图像的结构信息。卷积层的参数较少,这也是由卷积层的主要特性即局部连接和共享权重所决定。下图是数组3的识别过程。

LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)_第2张图片

学习CIFAR-10数据集识别物品类别

1.建立模型

模型的建立稍与LeNet-5有所不同。我们在卷积层C1使用16个卷积核,卷积层C3使用32个卷积核。而C5我们不选择用120个卷积核,而选择展开32×5×5用全连接层得到120×1×1。

def forward(self, x):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) 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 = x.view(-1, 32*5*5)       # output(32*5*5)
        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.nn as nn
import torch.nn.functional as F


class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        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):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) 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 = x.view(-1, 32*5*5)       # output(32*5*5)
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        return x
2.训练模型

下载CIFAR-10数据集。第一次使用时要将download设置为True会自动去下载数据集,下载完成后将True改成False。root指的是下载路径。train为True会导入训练集图片。transforms表示对图像进行预处理。

transforms.ToTensor():
Converts a PIL Image or numpy.ndarray (H x W x C) in the range [0, 255] 
to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0]
简单来说就是将其他图片格式转化成torch.FloatTensor格式,闭区间0~1范围内C x H x W

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)):
Given mean: ``(mean[1],...,mean[n])`` and std: ``(std[1],..,std[n])`` for ``n``
    channels, this transform will normalize each channel of the input
简单来说就是根据给定的均值和标准差来标准化
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 50000张训练图片
# 第一次使用时要将download设置为True才会自动去下载数据集
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                         download=True, transform=transform)

下载完成数据集后,导入数据集:

# 50000张训练图片
# 第一次使用时要将download设置为True才会自动去下载数据集
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                         download=False, transform=transform)

# batch_size为每批随机拿出36张图片训练  shuffle=True是否打乱  num 线程数 windows=0 linux自己定义
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                           shuffle=True, num_workers=0)

# 10000张验证图片
# 第一次使用时要将download设置为True才会自动去下载数据集
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                         shuffle=False, num_workers=0)
val_data_iter = iter(val_loader)  # 将val_loader转化成迭代器
val_image, val_label = val_data_iter.__next__()  # 通过next获取数据

导入之后我们可以打印几张测试集图片看一下,注意batch_size设置小一点,32*32的像素有点小糊:

LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)_第3张图片

在这里插入图片描述

def imshow(img):
    # unnormalize
    # 标准化是output=(input-0.5)/0.5
    # 反标准化是input=output*0.5+0.5
    img = img / 2 + 0.5
    # 转成np
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# 10000张验证图片
# 第一次使用时要将download设置为True才会自动去下载数据集
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=4,
                                         shuffle=False, num_workers=0)
val_data_iter = iter(val_loader)  # 将val_loader转化成迭代器
val_image, val_label = val_data_iter.__next__()  # 通过next获取数据
classes = ('plane', 'car', 'bird', 'cat',
            'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# print labels
print(' '.join(f'{classes[val_label[j]]:5s}' for j in range(4)))
# print image
imshow(torchvision.utils.make_grid(val_image))

接下来建立model:

net = LeNet()  # 模型
loss_function = nn.CrossEntropyLoss()  # 损失函数 包含softmax
optimizer = optim.Adam(net.parameters(), lr=0.001)  # 优化器adam  训练参数  学习率

开始训练,每500步输出一下准确率,实际上,每个epoch有1389步,50000/36:

for epoch in range(5):  # 迭代5次训练集

      running_loss = 0.0
      for step, data in enumerate(train_loader, start=0):
          # get the inputs; data is a list of [inputs, labels]
          inputs, labels = data

          # zero the parameter gradients
          # 梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。
          optimizer.zero_grad()

          # forward + backward + optimize
          outputs = net(inputs)
          loss = loss_function(outputs, labels)
          loss.backward()
          optimizer.step()
          print(step)
          # print statistics
          running_loss += loss.item()
          if step % 500 == 499:    # print every 500 mini-batches
              with torch.no_grad():  # with是一个上下文管理器 不进行误差损失梯度
                  outputs = net(val_image)  # [batch, 10]
                  predict_y = torch.max(outputs, dim=1)[1]
                  accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)

                  print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                        (epoch + 1, step + 1, running_loss / 500, accuracy))
                  running_loss = 0.0

print('Finished Training')

LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)_第4张图片

最后的识别率训练集可以达到83%,测试集达到68%,已经是一个很不错的结果,毕竟这个模型是1998年提出的,最后保存:

save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)

完整实现代码:

import numpy as np
import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt


def imshow(img):
    # unnormalize
    # 标准化是output=(input-0.5)/0.5
    # 反标准化是input=output*0.5+0.5
    img = img / 2 + 0.5
    # 转成np
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


def main():
    transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

    # 50000张训练图片
    # 第一次使用时要将download设置为True才会自动去下载数据集
    train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                             download=False, transform=transform)

    # batch_size为每批随机拿出36张图片训练  shuffle=True是否打乱  num 线程数 windows=0 linux自己定义
    train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                               shuffle=True, num_workers=0)

    # 10000张验证图片
    # 第一次使用时要将download设置为True才会自动去下载数据集
    val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
                                           download=True, transform=transform)
    val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                             shuffle=False, num_workers=0)
    val_data_iter = iter(val_loader)  # 将val_loader转化成迭代器
    val_image, val_label = val_data_iter.__next__()  # 通过next获取数据

    # classes = ('plane', 'car', 'bird', 'cat',
    #            'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
    # print labels
    #print(' '.join(f'{classes[val_label[j]]:5s}' for j in range(4)))
    # print image
    #imshow(torchvision.utils.make_grid(val_image))

    net = LeNet()  # 模型
    loss_function = nn.CrossEntropyLoss()  # 损失函数 包含softmax
    optimizer = optim.Adam(net.parameters(), lr=0.001)  # 优化器adam  训练参数  学习率

    for epoch in range(5):  # 迭代5次训练集

        running_loss = 0.0
        for step, data in enumerate(train_loader, start=0):
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = data

            # zero the parameter gradients
            # 梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。
            optimizer.zero_grad()

            # forward + backward + optimize
            outputs = net(inputs)
            loss = loss_function(outputs, labels)
            loss.backward()
            optimizer.step()
            print(step)
            # print statistics
            running_loss += loss.item()
            if step % 500 == 499:    # print every 500 mini-batches
                with torch.no_grad():  # with是一个上下文管理器 不进行误差损失梯度
                    outputs = net(val_image)  # [batch, 10]
                    predict_y = torch.max(outputs, dim=1)[1]
                    accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)

                    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)


if __name__ == '__main__':
    main()


测试泛化能力

从网上找两张图片进行测试:
LeNet网络详解(学习CIFAR-10数据集实现识别图片类别)_第5张图片
图像处理这里多了一步resize,因为训练集图片大小都是32x32。导入训练好的模型进行识别。

import torch
import torchvision.transforms as transforms
from PIL import Image

from model import LeNet


def main():
    transform = transforms.Compose(
        [transforms.Resize((32, 32)),
         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'))

    im = Image.open('1.jpg')
    im = transform(im)  # [C, H, W]
    im = torch.unsqueeze(im, dim=0)  # [N, C, H, W]

    with torch.no_grad():
        outputs = net(im)
        predict = torch.max(outputs, dim=1)[1].numpy()
    print(classes[int(predict)])


if __name__ == '__main__':
    main()

成功识别出图片的类别:

在这里插入图片描述在这里插入图片描述

你可能感兴趣的:(深度学习,学习,深度学习,cnn)