【学习笔记】AlexNet 网络结构

跟着大佬学图像分类系列,→ 传送门 ←

本博客图像分类系列文章传送门:

AlexNet(当前)
VGG
GoogleNet
ResNet

前言

图像分类是学习目标检测的“量变”内容,那么,废话不多说,开搞!


一、AlexNet 是什么?

        AlexNet 是2012年 ISLVRC 2012 竞赛的冠军网络,将分类准确率由传统的 70%+ 提升到了 80%+。是由 Hinton 和他的学生 Alex Krizhevsky 设计完成的网络。

(ISLVRC 是用于图像分类的数据集,属于 ImageNet 的子集,其包含 1,281,167 张训练集;50,000 张验证集合 100,000 张测试集)

二、网络结构

1.网络特点

  • 首次利用了 Gpu 进行网络加速训练
  • 使用了ReLU激活函数,而不是传统的 Sigmoid 激活函数以及 Tanh 函数
    (Sigmoid 这类的激活函数存在求导复杂以及当网络较深时,可能导致梯度消失等问题)
  • 使用了 LRN 局部响应归一化
  • 在全连接层的前两层中使用了 Dropout 随机失活神经元操作,以减少过拟合
    【学习笔记】AlexNet 网络结构_第1张图片
    (使用 Dropout,随机失活部分神经元)

2.结构

【学习笔记】AlexNet 网络结构_第2张图片

AlexNet 网络的结构示意图
(图中可以看出结构分为上下两层,这是由于使用 GPU 进行并行运算,所以在分析时只看一层即可)
number Input_size output_size kernels kernels_size padding stride
Conv1 [224, 224, 3] [55, 55, 96] 48 * 2 11 [1, 2] 4
MaxPooling1 [55, 55, 96] [27, 27, 96] \ 3 \ 2
Conv2 [27, 27, 96] [27, 27, 256] 128 * 2 5 [2, 2] 1
MaxPooling2 [27, 27, 256] [13, 13, 256] \ 3 \ 2
Conv3 [13, 13, 256] [13, 13, 384] 192 * 2 3 [1, 1] 1
Conv4 [13, 13, 384] [13, 13, 384] 192 * 2 3 [1, 1] 1
Conv5 [13, 13, 384] [13, 13, 256] 128 * 2 3 [1, 1] 1
MaxPooling3 [13, 13, 256] [6, 6, 256] \ 3 \ 2
FC1 6*6*256 (展平) \ \ 2048 \ \
FC2 2048 \ \ 2048 \ \
FC3 2048 \ \ 1000 \ \
  • Conv1(第一层卷积层)+ MaxPooling1(第一层池化层)

input:一张大小为 224x224 的RGB图片
卷积核大小为 11x11
卷积核的个数为 96 个(一层48,有两层)
步长为4
padding 为 [1, 2]
output:根据公式,输出为 [55, 55, 96] 的特征矩阵,96为这一层卷积核的个数,如果没有用GPU并行跑两层,这里就应该是48(建议去看一下卷积与池化的原理)
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)

  • Conv2(第二层卷积层)+ MaxPooling2(第二层池化层)

input:一张大小为 27x27 的RGB图片
卷积核大小为 5x5
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [2, 2]
output:根据公式,输出为 [27, 27, 256] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)

  • Conv3(第三层卷积层)

input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)

  • Conv4(第四层卷积层)

input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)

  • Conv5(第五层卷积层)+ MaxPooling3(第三层池化层)

input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 256 个(一层128,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)

  • FC1(第一层全连接层)+ FC2(第二层全连接层)+ FC3(第三层全连接层)

input:最后一层卷积层展平后的结果,即 66256
output:2048(全连接层的神经元个数)


三、使用 Pytorch 搭建 AlexNet 网络

本代码使用的数据集来自 “花分类” 数据集,→ 传送门 ←(具体内容看 data_set文件夹下的 README.md)


  • model.py ( 搭建AlexNet网络模型 )
import torch.nn as nn
import torch


class AlexNet(nn.Module):
    # num_classes表示模型可以分类的数量,如花数据集中,1000表示可以训练1000种花来识别(可通过传值来改变num_classes)
    # init_weights为初始化权重
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        '''
         nn.Sequential 可以将模型的各个层打包成一个新的结构,能让模型看起来更清晰
            如果不用 nn.Sequential,要表示各个层就需要 
                self.conv1 = nn.Conv2d( )
                self.pool1 = nn.MaxPool2d( )
                 ......
                self.fc = nn.Linear( )
            看起来会比较累赘
        '''
        # features :5层卷积 + 3层池化
        self.features = nn.Sequential(
            # 3=通道数(深度),48=kernel数量
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=nn.ZeroPad2d(1,2,1,2)),
            # inplace 降低内存使用
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(48, 128, kernel_size=5, stride=1, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(128, 192, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        # classifier :3层全连接层
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),   # 随机失活
            nn.Linear(6 * 6 * 128, 2048),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),
        )
        if init_weights:
            # 如果用户需要初始化权重,调用_initialize_weights()方法
            self._initialize_weights()

    # 前馈
    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)   # 展平成一维向量
        x = self.classifier(x)
        return x

    # 初始化权重
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

  • train.py ( 训练网络 )
import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils

import torch.optim as optim
from model import AlexNet

import os
import json
import time


# 根据电脑配置,判断是否使用 GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

# 数据预处理
data_transforms = {
    "train":transforms.Compose([
        transforms.RandomResizedCrop(224),      # 随机裁剪为 224 * 224 大小
        transforms.RandomHorizontalFlip(),      # 随机翻转
        transforms.ToTensor(),                  # 转换为 tensor 类型
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 标准化处理
    ]),
    "val":transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
}

image_path = "./data/data_set/flower_data/"          # 获取图像路径
# 训练集路径
train_dataset = datasets.ImageFolder(root=image_path + "/train", transform=data_transforms["train"])
train_num = len(train_dataset)

# 数据集各类别的索引     '花名':索引号
flower_list = train_dataset.class_to_idx
cla_dict = dict((val, key) for val, key in flower_list.items())     # 将类别放入字典
json_str = json.dumps(cla_dict, indent=4)       # 将字典转为 json 格式
# 保存 json 文件
with open('class_indices.json', 'w') as json_file:
    json_file.write(json_str)

batch_size = 32
# 加载训练集
train_loader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size=batch_size,
    shuffle=True,   # shuffle=True 表示随机去 batch
    num_workers=0   # num_worker设为0,意味着每一轮迭代时,dataLoader 不再自主加载数据到RAM(windows都是设为0)
)

# 验证集(测试)
validate_dataset = datasets.ImageFolder(root = image_path + '/val', transform=data_transforms["val"])
validate_num = len(validate_dataset)
# 加载验证集
validate_loader = torch.utils.data.DataLoader(
    validate_dataset,
    batch_size=batch_size,
    shuffle=False,
    num_workers=0
)

''' 训练模型 '''
net = AlexNet(num_classes=5, init_weights=True)
net.to(device)          # 使用 GPU 或 CPU
loss_funcation = nn.CrossEntropyLoss()  # 损失函数类型
# param = list(net.parameters())         # 查看模型的参数
optimizer = optim.Adam(net.parameters(), lr=0.0002)     # 优化器类型

save_path = './AlexNet.pth'     # 训练好后的模型存放路径
best_acc = 0.0                  # 记录最好的结果

for epoch in range(10):
    '''train'''
    net.train()         # train() 方法会执行 Dropout
    running_loss = 0.0  # 统计训练过程中的平均损失
    start_time = time.perf_counter()    # 记录训练一次 epoch 所需的时间
    for step, data in enumerate(train_loader, start=0):
        images, labels = data   # 得到图像及对应的分类
        optimizer.zero_grad()   # 清空梯度信息
        outputs = net(images.to(device))    # 放入网络正向传播,得到训练结果
        loss = loss_funcation(outputs, labels.to(device))   # 计算损失
        loss.backward()     # 反向传播
        optimizer.step()    # 更新模型参数

        running_loss += loss.item()
        # 打印训练进度
        rate = (step + 1) / len(train_loader)
        a = "*" * int(rate * 50)
        b = "." * int((1 - rate) * 50)
        print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="")
        print()
    print(time.perf_counter() - start_time)

    '''val'''
    net.eval()  # rain() 方法不会执行 Dropout
    acc = 0.0
    with torch.no_grad():
        for data_test in validate_loader:
            test_images, test_labels = data_test
            outputs = net(test_images.to(device))
            predict_y = torch.max(outputs, dim=1)[1]    # 这种分类其实输出的是一个概率,要将概率转换为最贴近的类别序号
            acc += (predict_y == test_labels.to(device)).sum().item()   # 预测值与实际值相等,acc+1
        accurate_test = acc / validate_num  # 计算正确率
        # 保存最好结果
        if accurate_test > best_acc:
            best_acc = accurate_test
            torch.save(net.state_dict(), save_path)
        print('[epoch %d] train_loss: %.3f test_accuracy: %.3f' % (epoch + 1, running_loss / step, acc / validate_num))

print("Finished !")
  • predict.py ( 使用训练好的模型网络对图像分类 )
import torch
from model import AlexNet
import numpy
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json

data_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# load image
img = Image.open("./tulip.jpg")
plt.imshow(img)     # show img
img = data_transform(img)
img = torch.unsqueeze(img, dim=0)

try:
    json_file = open('/class_indicts.json', 'r')    # 打开之前保存的“花分类”文件
    class_indicts = json.load(json_file)
except Exception as e:
    print(e)
    exit(-1)

# 初始化网络
model = AlexNet(num_classes=5)
# 加载在 model.py 中训练好的模型
model_weight_path = "./AlexNet.pth"
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
    # 获取 img 的预测结果
    output = torch.squeeze(model(img))
    predict = torch.softmax(output, dim=0)
    predict_class = torch.argmax(predict).numpy()
print(class_indicts[str(predict_class)], predict[predict_class].item())
plt.show()

代码连接 https://github.com/WZMIAOMIAO/deep-learning-for-image-processing

你可能感兴趣的:(图像分类,机器学习,图像识别,pytorch)