【CNN】搭建AlexNet网络——并处理自定义的数据集(猫狗分类)_alexnet训练自己的数据-程序员宅基地

技术标签: cnn  网络  深度学习  分类  

前言

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!")

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

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!')

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}"')

三,总结

  • 在每个卷机后面添加了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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41860497/article/details/128068569

智能推荐

Python pandas练习Retuns50stocks股票,纯英文ipynb作业20题,100%正确答案_create a function that takes a positive integer an-程序员宅基地

文章浏览阅读848次,点赞2次,收藏3次。#%% mdAssigment 4Instructions: This problem set should be done individuallyAnswer each question in the designated space belowAfter you are done. save and upload in blackboard.Please check that you are submitting the correct file. One way to avoid mist_create a function that takes a positive integer and returns the next bigger

FDR校正的程序实现及严格程度对比_fdr校正后全没有意义-程序员宅基地

文章浏览阅读2.4w次,点赞9次,收藏44次。FDR校正的程序实现及严格程度对比前言 做统计分析就离不开P value<0.05,而写过科研文章的人也都知道没有经过FDR校正的P值就像一盘散沙,不用风吹,走两步自个儿就散了。那么FDR校正这个让人又爱又恨的东西是什么呢?又是如何实现呢? 原理是这样:设总共有m个候选基因,每个基因对应的p值从小到大排列分别是 p(1),p(2),..._fdr校正后全没有意义

淦VINS-MONO源码 01--IMU的误差模型和标定-程序员宅基地

文章浏览阅读921次。IMU的误差模型和标定工作原理加速度计工作原理陀螺仪测量原理误差来源IMU噪声模型biasscale轴偏差IMU校准确定性误差六面法标定bias和scale factor温度相关的参数标定随机误差高斯白噪声Bias 随机游走艾伦方差标定数学模型加速度计数学模型陀螺仪数学模型在看完orb-slam后,也投了一些实习,发现纯视觉的方案在很多方面都没得到采用,特别是自动驾驶中定位感知方面,基本上是雷达+IMU+相机+GPS等等的多传感器融合,所以接下来的计划会看看vins-mono和loam的源代码,一个是IM

Dotween常用方法详解-程序员宅基地

文章浏览阅读10w+次,点赞213次,收藏880次。Dotween是unity里非常常用的插件,但是很多人应该没有仔细的阅读过它的api,它实际有很多好用的方法,下面我就针对一些相对常用的方法,做下总结:一、Unity常用组件拓展方法(1) Transform拓展方法1)Position 改变世界坐标 移动方法,第一个参数是要移动到的目标点,不是移动这个向量的距离 transform.DOMov..._dotween

matlab图片纹理特征直方图,常用的图像特征有颜色特征、纹理特征、形状特征、空间关系特征。...-程序员宅基地

文章浏览阅读1k次。常用的图像特征有颜色特征、纹理特征、形状特征、空间关系特征。一 颜色特征(一)特点:颜色特征是一种全局特征,描述了图像或图像区域所对应的景物的表面性质。一般颜色特征是基于像素点的特征,此时所有属于图像或图像区域的像素都有各自的贡献。由于颜色对图像或图像区域的方向、大小等变化不敏感,所以颜色特征不能很好地捕捉图像中对象的局部特征。另外,仅使用颜色特征查询时,如果数据库很大,常会将许多不需要的图像也检...

OutSystems中,利用分组数据来计算值_outsystems tan関数-程序员宅基地

文章浏览阅读418次。将数据放入组中以计算聚合值,从而提取更多信息。在 OutSystems 中,您可以使用聚合函数对相同数据组计算值。利用聚合方式获取数据和执行以下操作:在具有相同数据的属性中,菜单中选择 Group by &amp;lt;attribute&amp;gt;;在要计算的属性中,使用菜单并选择聚合函数,例如 Count;在属性上对聚合函数进行分组或使用后,这些属性将成为聚合的唯一输出。示例在 GoOutW..._outsystems tan関数

随便推点

4.链表组件(LeetCode 817)_给定链表头结点 head,该链表上的每个结点都有一个唯一的整型值 。 同时给定列表 g-程序员宅基地

文章浏览阅读254次。问题描述 :给定链表头结点 head,该链表上的每个结点都有一个唯一的整型值 。同时给定列表 G,该列表是上述链表中整型值的一个子集。返回列表 G 中组件的个数,这里对组件的定义为:链表中一段极长连续结点的值(该值必须在列表 G 中)构成的集合。极长的含义是:这段连续结点的前面或后面结点不属于G。示例 1:输入:head: 0->1->2->3G = [0, 1, 3]输出: 2解释:链表中,0 和 1 是相连接的,且 G 中不包含 2,所以 [0, 1] 是 G 的一_给定链表头结点 head,该链表上的每个结点都有一个唯一的整型值 。 同时给定列表 g

使用神器MobaXterm连接远程mysql和redis-程序员宅基地

文章浏览阅读3.4k次。https://mobaxterm.mobatek.net/download-home-edition.htmlmysqlredis连接测试mysql127.0.0.13307密码使用线上密码redis127.0.0.16380密码使用线上密码转载于:https://www.cnblogs.com/cxscode/...

Shell判断字符串是否为空-程序员宅基地

文章浏览阅读1k次。主要有以下几种方法:echo “$str”|awk '{print length($0)}'expr length “$str”echo “$str”|wc -c但是第三种得出的值会多1,可能是把结束符也计算在内了判断字符串为空的方法有三种:if [ "$str" ="" ] if [ x"$str" = x ]if [ -z "$str" ] (-n ...

ingress-nginx-限速_ingress nginx速率限制-程序员宅基地

文章浏览阅读2.8k次。限速这些注释定义了可由单个客户端IP地址打开的连接的限制。这可用于缓解DDoS攻击。nginx.ingress.kubernetes.io/limit-connections:来自单个IP地址的并发连接数。 nginx.ingress.kubernetes.io/limit-rps:每秒可从给定IP接受的连接数。 nginx.ingress.kubernetes.io/limit-rpm..._ingress nginx速率限制

Jenkins 报错ssh executable not found. The git plugin only supports official git_java.lang.runtimeexception: ssh executable not fou-程序员宅基地

文章浏览阅读575次。今天用jenkins 构建job一直报错:分析:ssh executable not found. The git plugin only supports official git client http://git-scm.com/download/win的报错,可能与git --version一直打印不出来版本有关。检查Jenkins中git路径看起来没什么问题,D:\work\git_latest\Git\git-bash.exe直接复制粘贴到文件路径也能打开git。然而,实际上这个路径配_java.lang.runtimeexception: ssh executable not found. the git plugin only su

Android CE DE加密小结_android de ce-程序员宅基地

文章浏览阅读4.4k次,点赞3次,收藏16次。1.全盘加密 数据在空闲的时候进行加密 每个设备只有一个用户可以被全盘加密进行保护 那个用户在做任何工作前必须登录 2.文件级别加密 使用ext4文件系统 使用AES-256方式对文件内容进行加密 文件名也需要进行加密 文件级别加密的性能要稍微好于全盘加密f2fs文件系统用于支持nand flashDE(device en..._android de ce