• 【CNN】搭建AlexNet网络——并处理自定义的数据集(猫狗分类)


    前言

    2012年,AlexNet横空出世。它首次证明了学习到的特征可以超越手工设计的特征。它一举打破了计算机视觉研究的现状。 AlexNet使用了8卷积神经网络,并以很大的优势赢得了2012ImageNet图像识别挑战赛。

    论文地址:http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf

    这里我用的是猫狗分类的数据集,如下图所示:
    在这里插入图片描述
    本博文完整数据集:链接:https://pan.baidu.com/s/1ySqPErgpnUdk_mqrQU-GTg?pwd=6666

    一,介绍

    AlexNet和LeNet的架构非常相似,
    在这里插入图片描述
    在这里插入图片描述
    AlexNetLeNet的设计理念非常相似,但也存在显著差异。 首先,AlexNet比相对较小的LeNet5要深得多。 AlexNet由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。
    其次,AlexNet使用ReLU而不是sigmoid作为其激活函数。
    AlexNet的第一层,卷积窗口的形状是 11 x 11。 由于ImageNet中大多数图像的宽和高比MNIST图像的多10倍以上,因此,需要一个更大的卷积窗口来捕获目标。 第二层中的卷积窗口形状被缩减为 5 x 5,然后是3 x 3。 此外,在第一层、第二层和第五层卷积层之后,加入窗口形状为、步幅为2的最大汇聚层。 而且,AlexNet的卷积通道数目是LeNet10倍。

    在最后一个卷积层后有两个全连接层,分别有4096个输出。
    但是,我们这里只有两类需要输出,所以,这里最后把全两层拉成2个输出。

    二,代码实现

    按照卷积的计算公式和上面的超参数,通过卷积的输出计算公式搭建网络:
    在这里插入图片描述
    项目中的目录结构:
    在这里插入图片描述

    2.1 数据处理

    对网络中的数据进行处理,由于我们已经得到了猫狗数据,开始对模型中的数据进行2:8开,

    • 训练集:8;
    • 验证集:2;
    import os
    from shutil import copy
    import random
    
    def mkfile(file):
        if not os.path.exists(file):
            os.makedirs(file)
    # 获取所有要分类的文件夹
    file_path = "./raw_data/"
    train_path = 'data/train/'
    validate_path = 'data/validate/'
    # 列出所有花的种类
    flow_cases  = [clazz for clazz in os.listdir(file_path)]
    # 创建出验证集和训练集文件夹,并由类名在其目录下创建五个子目录
    mkfile(train_path)
    mkfile(validate_path)
    for clazz in flow_cases:
        mkfile(train_path + clazz)
        mkfile(validate_path + clazz)
    # 按照比例来进行划分, 训练集和测试集 = 9 : 1
    split_rate = 0.1
    # 遍历所有类别的全部图像,并按照比例分成训练集合验证集
    for clazz in flow_cases:
        clazz_path = file_path + '/' + clazz + '/' # 某一个类别的文件夹
        images = os.listdir(clazz_path) # images 列表存储来目录下的所有图片的名称
        num = len(images)
        sample_images = random.sample(images, k=int(num * split_rate)) # 从images列表随机sample出k个样本
        for index, image in enumerate(images):
            # sample_images保存的是所有取出来的图片
            if image in sample_images:
                image_path = clazz_path + image
                new_path = validate_path + clazz
                copy(image_path, new_path)
            # 其他的所有图片都保留在训练集中
            else:
                image_path = clazz_path + image
                new_path = train_path + clazz
                copy(image_path, new_path)
            print(f'\r[{clazz}] processing [{index + 1} / {num}]', end="") # process bar
        print()
    
    print("processing done!")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    2.2 模型的搭建

    按照文章中的内容,对模型进行搭建

    import torch.nn as nn
    import torch
    
    
    class AlexNet(nn.Module):
        def __init__(self, num_classes=1000, init_weights=False):
            super(AlexNet, self).__init__()
            # 用nn.Sequential()将网络打包成一个模块,精简代码
            self.features = nn.Sequential(  # 卷积层提取图像特征
                nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55]
                nn.ReLU(inplace=True),  # 直接修改覆盖原值,节省运算内存
                nn.MaxPool2d(kernel_size=3, stride=2),  # output[48, 27, 27]
                nn.Conv2d(48, 128, kernel_size=5, padding=2),  # output[128, 27, 27]
                nn.ReLU(inplace=True),
                nn.MaxPool2d(kernel_size=3, stride=2),  # output[128, 13, 13]
                nn.Conv2d(128, 192, kernel_size=3, padding=1),  # output[192, 13, 13]
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 192, kernel_size=3, padding=1),  # output[192, 13, 13]
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 128, kernel_size=3, padding=1),  # output[128, 13, 13]
                nn.ReLU(inplace=True),
                nn.MaxPool2d(kernel_size=3, stride=2),  # output[128, 6, 6]
            )
            self.classifier = nn.Sequential(  # 全连接层对图像分类
                nn.Dropout(p=0.5),  # Dropout 随机失活神经元,默认比例为0.5
                nn.Linear(128 * 6 * 6, 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:
                self._initialize_weights()
    
        # 前向传播过程
        def forward(self, x):
            x = self.features(x)
            x = torch.flatten(x, start_dim=1)  # 展平后再传入全连接层
            x = self.classifier(x)
            return x
    
        # 网络权重初始化,实际上 pytorch 在构建网络时会自动初始化权重
        def _initialize_weights(self):
            for m in self.modules():
                if isinstance(m, nn.Conv2d):  # 若是卷积层
                    nn.init.kaiming_normal_(m.weight, mode='fan_out',  # 用(何)kaiming_normal_法初始化权重
                                            nonlinearity='relu')
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)  # 初始化偏重为0
                elif isinstance(m, nn.Linear):  # 若是全连接层
                    nn.init.normal_(m.weight, 0, 0.01)  # 正态分布初始化
                    nn.init.constant_(m.bias, 0)  # 初始化偏重为0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    2.3 开始训练

    自定义数据的训练逻辑

    TRAIN_ROOT = r'data/train'
    VALIDATE_ROOT = 'data/validate'
    # 进行数据的处理,定义数据转换
    data_transform = {
        "train": transforms.Compose([transforms.RandomResizedCrop(224),       # 随机裁剪,再缩放成 224×224
                                     transforms.RandomHorizontalFlip(p=0.5),  # 水平方向随机翻转,概率为 0.5, 即一半的概率翻转, 一半的概率不翻转
                                     transforms.ToTensor(),
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
    
        "validate": transforms.Compose([transforms.Resize((224, 224)),  # cannot 224, must (224, 224)
                                   transforms.ToTensor(),
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
    # 加载数据集
    train_dataset = ImageFolder(TRAIN_ROOT, transform=data_transform['train'])
    validate_dataset = ImageFolder(VALIDATE_ROOT, transform=data_transform['validate'])
    # 讲数据进行小批量处理
    train_dataloader = DataLoader(train_dataset,
                                  batch_size=32,
                                  shuffle=True,
                                  num_workers=0)
    val_dataloader = DataLoader(validate_dataset,
                                batch_size=32,
                                shuffle=True,
                                num_workers=0)
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    
    model = AlexNet(num_classes=2).to(device)
    # 定义一个损失函数
    loss_fn = nn.CrossEntropyLoss()
    # 定义一个优化器
    optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
    # 学习率每隔10轮变为原来的0.5
    lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
    # 定义训练函数
    def train(dataloader, model, loss_fn, optimizer):
        model.train()
        time_start = time.perf_counter()  # 对训练一个 epoch 计时
        loss, current, n = 0.0, 0.0, 0
        for batch, (x, y) in enumerate(dataloader):
            image, y = x.to(device), y.to(device)
            output = model(image)
            cur_loss = loss_fn(output, y)
            # 取最大的哪个坐标
            _, pred = torch.max(output, axis=1)
            cur_acc = torch.sum(y==pred) / output.shape[0]
    
            # 反向传播
            optimizer.zero_grad()
            cur_loss.backward()
            optimizer.step()
            loss += cur_loss.item()
            current += cur_acc.item()
            n = n+1
            # 打印训练进度(使训练过程可视化)
            rate = (batch + 1) / len(dataloader)  # 当前进度 = 当前step / 训练一轮epoch所需总step
            a = "*" * int(rate * 50)
            b = "." * int((1 - rate) * 50)
            print("\r{:^3.0f}%[{}->{}]".format(int(rate * 100), a, b), end="")
        print('%f s' % (time.perf_counter() - time_start))
        # 返回平均的loss
        train_loss = loss / n
        train_acc = current / n
        print('train_loss' + str(train_loss))
        print('train_acc' + str(train_acc))
        return train_loss, train_acc
    
    # 定义一个验证函数
    def val(dataloader, model, loss_fn):
        # 将模型转化为验证模型
        model.eval()
        loss, current, n = 0.0, 0.0, 0
        with torch.no_grad():
            for batch, (x, y) in enumerate(dataloader):
                image, y = x.to(device), y.to(device)
                output = model(image)
                cur_loss = loss_fn(output, y)
                _, pred = torch.max(output, axis=1)
                cur_acc = torch.sum(y == pred) / output.shape[0]
                loss += cur_loss.item()
                current += cur_acc.item()
                n = n + 1
    
        val_loss = loss / n
        val_acc = current / n
        print('val_loss' + str(val_loss))
        print('val_acc' + str(val_acc))
        return val_loss, val_acc
    
    # 解决中文显示问题
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    # 定义画图函数
    def matplot_loss(train_loss, val_loss):
        plt.plot(train_loss, label='train_loss')
        plt.plot(val_loss, label='val_loss')
        plt.legend(loc='best')
        plt.ylabel('loss')
        plt.xlabel('epoch')
        plt.title("训练集和验证集loss值对比图")
        plt.show()
    
    def matplot_acc(train_acc, val_acc):
        plt.plot(train_acc, label='train_acc')
        plt.plot(val_acc, label='val_acc')
        plt.legend(loc='best')
        plt.ylabel('acc')
        plt.xlabel('epoch')
        plt.title("训练集和验证集acc值对比图")
        plt.show()
    
    # 开始训练
    loss_train = []
    acc_train = []
    loss_val = []
    acc_val = []
    
    
    epoch = 20
    min_acc = 0
    for t in range(epoch):
        lr_scheduler.step()
        print(f"epoch{t+1}\n-----------")
        train_loss, train_acc = train(train_dataloader, model, loss_fn, optimizer)
        val_loss, val_acc = val(val_dataloader, model, loss_fn)
    
        loss_train.append(train_loss)
        acc_train.append(train_acc)
        loss_val.append(val_loss)
        acc_val.append(val_acc)
    
        # 保存最好的模型权重
        if val_acc > min_acc:
            folder = 'save_model'
            if not os.path.exists(folder):
                os.mkdir('save_model')
            min_acc = val_acc
            print(f"save best model, 第{t+1}轮")
            torch.save(model.state_dict(), 'save_model/best_model.pth')
        # 保存最后一轮的权重文件
        if t == epoch-1:
            torch.save(model.state_dict(), 'save_model/last_model.pth')
    
    matplot_loss(loss_train, loss_val)
    matplot_acc(acc_train, acc_val)
    print('Done!')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    2.4 最后对模型进行验证

    TRAIN_ROOT = 'data/train'
    VALIDATE_ROOT = 'data/validate'
    # 进行数据的处理,定义数据转换
    data_transform = {
        "train": transforms.Compose([transforms.RandomResizedCrop(224),       # 随机裁剪,再缩放成 224×224
                                     transforms.RandomHorizontalFlip(p=0.5),  # 水平方向随机翻转,概率为 0.5, 即一半的概率翻转, 一半的概率不翻转
                                     transforms.ToTensor(),
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
    
        "validate": transforms.Compose([transforms.Resize((224, 224)),  # cannot 224, must (224, 224)
                                   transforms.ToTensor(),
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
    # 加载数据集
    train_dataset = ImageFolder(TRAIN_ROOT, transform=data_transform['train'])
    validate_dataset = ImageFolder(VALIDATE_ROOT, transform=data_transform['validate'])
    # 讲数据进行小批量处理
    train_dataloader = DataLoader(train_dataset,
                                  batch_size=32,
                                  shuffle=True,
                                  num_workers=0)
    val_dataloader = DataLoader(validate_dataset,
                                batch_size=32,
                                shuffle=True,
                                num_workers=0)
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    
    model = AlexNet(num_classes=2).to(device)
    
    # 加载模型
    model.load_state_dict(torch.load("save_model/best_model.pth", map_location='cpu'))
    # 获取预测结果
    classes = [
        "cat",
        "dog",
    ]
    
    # 把张量转化为照片格式
    show = ToPILImage()
    
    # 进入到验证阶段
    model.eval()
    for i in range(10):
        x, y = validate_dataset[i][0], validate_dataset[i][1]
        show(x).show()
        x = Variable(torch.unsqueeze(x, dim=0).float(), requires_grad=True).to(device)
        x = torch.tensor(x).to(device)
        with torch.no_grad():
            pred = model(x)
            # 用argmax获取概率最大的一个物体
            predicted, actual = classes[torch.argmax(pred[0])], classes[y]
            print(f'predicted:"{predicted}", Actual:"{actual}"')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    三,总结

    • 在每个卷机后面添加了Relu激活函数,解决了Sigmoid的梯度消失问题,使收敛更快。
    • 使用随机丢弃技术(dropout)选择性地忽略训练中的单个神经元,避免模型的过拟合(也使用数据增强防止过拟合)
    • 添加了归一化LRNLocal Response Normalization,局部响应归一化)层,使准确率更高。
    • 重叠最大池化(overlapping max pooling),即池化范围z 与步长 s 存在关系 z>s 避免平均池化(average pooling)的平均效应

    完整代码: https://github.com/fckey/DeepLearning_cases/tree/master/AlexNet

    四,参考


    https://zhuanlan.zhihu.com/p/116197079
    https://blog.csdn.net/frighting_ing/article/details/120774252

  • 相关阅读:
    基于Spring Boot+MyBatis+MySQL的高校试卷管理系统
    spring注解开发
    13.keepalived实现高可用
    网络协议:应用层
    Python学习记录(4)元组:戴了紧箍咒的列表
    数据库设计与数据库范式
    独家 | 2022 年十项突破性技术
    新版校园跑腿外卖独立版+APP+小程序前端外卖配送平台源码(含搭建教程)
    剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
    【Linux】—— 在Linux上进行读写文件操作
  • 原文地址:https://blog.csdn.net/qq_41860497/article/details/128068569