2012AlexNet详解

AlexNet网络结构

一:网络结构详解

ISVRC:一个用于图像分类的数据集,属于ImageNet的一个子集

网络结构

在这里插入图片描述

网络的亮点:
(1)首次利用GPU进行网络加速训练。

(2)使用了ReLU激活函数,而不是传统的Sigmoid激活函数以及Tanh激活函数。

(3)使用了LRN局部响应归一化。

(4)在全连接层的前两层中使用了Dropout随机失活神经元操作,以减少过拟合。

  • 高端GPU的提速比可以达到CPU的20-50倍的速度差距

  • sigmoid激活函数的两个缺点:1、求导的过程比较麻烦;2、当网络比较深的时候会出现梯度消失的现象。Relu能够解决以上问题

  • dropout操作可以减少过拟合现象

    什么是过拟合?

    过拟合:根本原因是特征维度过多,模型假设过于复杂,参数过多,训练数据过少,噪声过多,导致拟合的函数完美的预测训练集,但对新数据的测试集预测结果差。过度的拟合了训练数据,而没有考虑到泛化能力。

在这里插入图片描述

第一幅图是网络的一个初始状态,随机地划分了一条边界对于样本进行分类通过不断的训练过程中,网络会慢慢学习出一条分类的边界如第二幅图所示,得到了一个比较好的分类的结果

第三幅图虽然能够将训练样本进行完全正确的分类,但是图中出现了过拟合现象

过拟合的函数能够完美地预测训练集,但是对新数据的测试机预测效果较差,过度的拟合了训练数据,而没有考虑到泛化能力

出现过拟合现象的原因有很多:
  • 网络中参数过多

  • 模型过于复杂

  • 训练数据过少等

    使用dropout减少过拟合现象

在这里插入图片描述

使用Dropout的方式在网络正向传播过程中随机失活一部分神经元

  • 左图是一个正常的全连接的正向传播过程,每一个节点都与下层的节点进行全连接

  • 使用了dropout之后,会在每一层随机地失活一部分神经元,变相地减少了网络中训练的参数,从而达到了减少过拟合现象的作用

    知识回顾:卷积或者池化之后高度和宽度的计算公式:

    网络结构图:

在这里插入图片描述

这个图可以看成上下两部分:作者使用了两块GPU进行了并行运算,上下两部分都是一样的,只用看其中一部分即可

#### Conv1:

原始图像是一个224*224的channel为3的彩色图像

卷积核大小是11*11

步长为4

一共有48*2=96个卷积核

可以推理出padding的大小是1和2:表示在特征矩阵的左边加上一列0,右边加上两列0,上面加上一列0,下面加上两列0(代表padding的2p值的是两边padding的像素之和,并不一定要求两边像素一定要一样)

根据公式可以算出卷积后的矩阵尺寸为N=55

#### Maxpooling1:最大池化下采样操作

在这里插入图片描述

- 这一层的输入是第一层卷积层的输出
  • 池化操作只会改变输出矩阵的高度和宽度,不会改变特征矩阵的深度
Conv2:

在这里插入图片描述

由图可以看出卷积核个数是128*2=256,其他参数通过查阅资料得到

根据公式可以算出卷积后的矩阵尺寸为N=27

Maxpooling2:

在这里插入图片描述

Conv3:

在这里插入图片描述

由图可知卷积核的个数为192*2=384

根据公式可以算出卷积后的矩阵尺寸为N=13

Conv4:

在这里插入图片描述

同样根据公式可以算出卷积后的矩阵尺寸为N=13

Conv5:

在这里插入图片描述

同样根据公式可以算出卷积后的矩阵尺寸为N=13

Maxpooling3:

图中依旧没给出过多的信息,通过查阅资料和源码可得:池化核的大小为3,padding等于0,步长为2

三个全连接层:

输出的特征矩阵展平之后和三个全连接层进行连接(注意最后一个全连接层只有1000个节点,对应数据集的1000个类别,如果要将这个网络应用到自己的数据集的话,只需要将最后一层全连接层的节点个数改成和自己数据集的类别数一致即可)

二、项目预备–下载花朵分类的数据集

1、在该链接上的(2)下载花朵数据集压缩包,并放在data_set文件夹下新建的flower_data文件夹下。

在这里插入图片描述

解压这个压缩包后,现在文件位置是这样:

2、在上面那个链接中把split_data.py文件拷贝下来,放到data_set文件夹下。这个脚本会自动按照9:1将数据集分为测试集和验证集。
鼠标放在“data_set”文件夹下的某个位置,按住shift,同时鼠标点击右键,选择“在此处打开PowerShell窗口”:
输入:python .\split.py 开始执行文件代码。
处理成功!

3.使用pytorch进行搭建

各层参数总结

img

https://blog.csdn.net/qq_44629163/article/details/123923467?spm=1001.2014.3001.5502

1.构建模型model.py
import torch.nn as nn
import torch

#定义一个类AlexNet继承了nn.Module的父类,
# 然后在其中定义一个初始化函数init(),来定义网络正向传播中需要的一些层结构。
class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        #这里我们使用Sequential()模块,能将一系列的层结构打包,组合成一个新的结构,我们将之命名为features,用于提取图像特征。
        self.features = nn.Sequential(
            #对应图表中的Conv1,为了训练方便,我们只训练一半,将卷积核个数减半了=48,卷积核大小仍=11,步距stride=4,因为是RGB图像所以channel=3。
            #padding=i(整数)代表在上下左右分别补 i 行 0。如果使用tuple:(1,2),其中1代表上下方各补一行零,2代表左右两侧各补两列零;
            # 如果想用更精细化的nn.ZeroPad2d((1,2,1,2)),代表左侧补1列,右侧补2列,上方补1行,下方补2行。因为这里计算N的时候有小数会自动舍弃,所以我们用padding=2的结果跟上面精细化处理的效果一致。
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224] [224-11+(1+2)]/4+1=55   output[48, 55, 55]
            nn.ReLU(inplace=True),
            #上一层卷积层的卷积核个数直接=这一层卷积层的channel。
            nn.MaxPool2d(kernel_size=3, stride=2),                  # [55-3]/2+1=27   output[48, 27, 27]
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # [27-5+4]/1+1=27 output[128, 27, 27]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # [27-3]/2+1=13   output[128, 13, 13]
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          # [13-3+2]/1+1=13 output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # [13-3+2]/1+1=13 output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # [13-3+2]/1+1=13 output[128, 13, 13]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # [13-3]/2+1=6    output[128, 6, 6]
        )
        #使用Sequential()模块将后面的三个全连接层打包。
        self.classifier = nn.Sequential(
            #在展平操作之前使用dropout函数按某个比例将一部分神经元失活。
            nn.Dropout(p=0.5),
            #因为下采样层后的特征层是【6,6,256】,因为这里我们只采用了一半,所以传入128X6X6,而由参数表可知FC1的卷积核大小=2048,所以第一个展平函数Linear(128X6X6,2048)。
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            #上一个Linear()函数第二维的2048作为第二个展平函数的输入,而FC2=2048,所以第二个展平函数Linear(2048,2048)。
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            #同理,第三个展平函数的第一个值就是2048,第二个值就是种类数num_classes。
            nn.Linear(2048, num_classes),
        )
        #如果在搭建网络过程中传入了初始化权重,就是init_weights为true了,就会执行初始化权重方法_initialize_weights()。
        if init_weights:
            self._initialize_weights()
 #定义正向传播过程forward()
    def forward(self, x):
        #首先将训练样本x输入features中,也就是init()函数中定义的打包结构。
        x = self.features(x)
        #然后将其进行展平处理flatten(),是使用第1维channel的(因为【batch,channel,width,height】,所以channel是第1维)。
        x = torch.flatten(x, start_dim=1)
        #展平之后我们将其丢进分类器classifier(),也就是3层全连接层中,输出的x就是最终的预测标签。
        x = self.classifier(x)
        return x
 #定义初始化权重方法initialize_weights()
    def _initialize_weights(self):
        #遍历每个层结构m是属于哪个类别的module
        for m in self.modules():
            #用isinstance()函数判断m层结构是否属于卷积层nn.Conv2d。如果是的话,就用kaiming_normal()变量初始化方法对权重w进行初始化
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                #如果偏置bias不为空的话,就用0对其进行初始化。
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            #如果m是属于全连接层nn.Linear,那么就使用normal()方法正态分布给权值赋值
            elif isinstance(m, nn.Linear):
                #,这里指定正态分布的均值=0,方差=0.01
                nn.init.normal_(m.weight, 0, 0.01)
                #同样,将偏置bias初始化为0。
                nn.init.constant_(m.bias, 0)
2.训练模型train.py
import os
import sys
import json

import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
from tqdm import tqdm

from model import AlexNet


def main():
    #如果有GPU设备的话就选择第一块GPU,如果没有的话就用CPU。
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    #数据预处理函数data_transform
    data_transform = {
        "train": transforms.Compose([transforms.RandomResizedCrop(224),#RandomResizedCrop()函数为随机裁剪函数,将其裁剪到224 X 224的像素大小;
                                     transforms.RandomHorizontalFlip(),#RandomHorizontalFlip()函数表示在水平方向随机翻转;
                                     #然后将其转换成Tensor张量;
                                     transforms.ToTensor(),
                                     #再进行Normalize()随机标准化处理。
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
        "val": 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))])}
    #data_root用来获取数据集的根目录,其中os.getcwd()表示当前目录,join拼接字符串,"…“表示返回上一层目录,”…/…"表示返回上上层目录。
    data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path
    image_path = os.path.join(data_root, "data_set", "flower_data")  # flower data set path
    assert os.path.exists(image_path), "{} path does not exist.".format(image_path)
    #然后train_dataset的目录位置就是image_path中的train文件夹,并按照这个标签使用transform()函数进行预处理。
    train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                         transform=data_transform["train"])
    train_num = len(train_dataset)

    # 据train_dataset.class_to_idx可以根据分类的名字获得索引,即{'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
    flower_list = train_dataset.class_to_idx
    cla_dict = dict((val, key) for key, val in flower_list.items())
    # write dict into json file
    #使用json.dumps()方法将cla_dict字典进行编码,编码成json格式
    json_str = json.dumps(cla_dict, indent=4)
    #;接着打开class_indices.json文件,将上述类别及对应的index以json格式写入
    with open('class_indices.json', 'w') as json_file:
        json_file.write(json_str)

    batch_size = 32
    nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workers
    print('Using {} dataloader workers every process'.format(nw))

    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size, shuffle=True,
                                               num_workers=nw)

    validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
                                            transform=data_transform["val"])
    val_num = len(validate_dataset)
    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=4, shuffle=False,
                                                  num_workers=nw)

    print("using {} images for training, {} images for validation.".format(train_num,
                                                                           val_num))
    # test_data_iter = iter(validate_loader)
    # test_image, test_label = test_data_iter.next()
    #
    # def imshow(img):
    #     img = img / 2 + 0.5  # unnormalize
    #     npimg = img.numpy()
    #     plt.imshow(np.transpose(npimg, (1, 2, 0)))
    #     plt.show()
    #
    # print(' '.join('%5s' % cla_dict[test_label[j].item()] for j in range(4)))
    # imshow(utils.make_grid(test_image))

    #接下来使用Model.py中的AlexNet定义网络net,分类5种,初始化权重为True。
    net = AlexNet(num_classes=5, init_weights=True)
    #使用net.to()方法加载GPU或CPU设备
    net.to(device)
    #然后定义损失函数及优化器
    loss_function = nn.CrossEntropyLoss()
    # pata = list(net.parameters())
    #优化网络中所有参数,学习率lr=0.0002。
    optimizer = optim.Adam(net.parameters(), lr=0.0002)

#开始训练
    #将训练好的路径保存到’./AlexNet.pth’
    epochs = 10
    save_path = './AlexNet.pth'
    #将准确率保存在全局变量best_acc中。
    best_acc = 0.0
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # train
        #在train训练过程中我们需要使用Dropout来随机失活一部分神经元,所以调用的是net.triain()方法
        net.train()
        #running_loss用来统计训练过程的平均损失
        running_loss = 0.0
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data   #将data中的图像和label分别取出来
            optimizer.zero_grad()   #将历史梯度清零
            outputs = net(images.to(device))#将图像(可以是用GPU处理后的)传入网络后得到的预测标签放到outputs中
            loss = loss_function(outputs, labels.to(device))#将实际标签与预测标签作为参数传入,统计损失
            loss.backward() #将loss反向传播
            optimizer.step() #更新每个节点的参数


            # print statistics
            running_loss += loss.item()

            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
                                                                     epochs,
                                                                     loss)

        # validate验证
        #在验证过程中我们不需要Dropout(),就用net.eval()关闭Dropout()方法
        net.eval()
        acc = 0.0  # accumulate accurate number / epoch
        with torch.no_grad():
            val_bar = tqdm(validate_loader, file=sys.stdout)
            for val_data in val_bar:
                val_images, val_labels = val_data        #将数据分别按照图像、label取出
                outputs = net(val_images.to(device))     #将图像放到net中去,然后获得预测标签
                predict_y = torch.max(outputs, dim=1)[1] #取出预测标签中最大的那个标签
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()#验证正确的样本个数

        val_accurate = acc / val_num #acc是预测正确的样本个数,val_num表示样本总数
        print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
              (epoch + 1, running_loss / train_steps, val_accurate))

        if val_accurate > best_acc:#更新准确率并保存该模型参数到save_path路径
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)

    print('Finished Training')


if __name__ == '__main__':
    main()

训练完成的结果:
在这里插入图片描述

3.预测模型predit.py
import os
import json

import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt

from model import AlexNet


def main():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    #预处理函数data_transform
    data_transform = transforms.Compose(
        #改变图片大小为224*224
        [transforms.Resize((224, 224)),
         #然后转为张量Tensor
         transforms.ToTensor(),
         #使用Normalize正则化
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

    # load image
    #用PIL库载入这张要预测的图片,然后扩展batch这个维度;读取json文件。
    img_path = "../tulip.jpg"
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    # 展示一下这张图片
    plt.imshow(img)
    # [N, C, H, W]
    img = data_transform(img)#将图片传入预处理函数转为Tensor [N, C, H, W]
    # expand batch dimension
    img = torch.unsqueeze(img, dim=0)#扩充batch这个维度 放到第0维

    # read class_indict
    # 读取json文件,也就是索引及类别对应的名称
    json_path = './class_indices.json'
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)

    with open(json_path, "r") as f:
        class_indict = json.load(f)

    # create model创建模型
    model = AlexNet(num_classes=5).to(device)

    # load model weights下载模型权重
    weights_path = "./AlexNet.pth"
    assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path)
    model.load_state_dict(torch.load(weights_path))

    model.eval() #关闭Dropout
    with torch.no_grad():
        # predict class
        output = torch.squeeze(model(img.to(device))).cpu()#将已传入图像的模型压缩,将batch压缩掉
        predict = torch.softmax(output, dim=0)#用softmax处理后变成一个概率分布
        predict_cla = torch.argmax(predict).numpy()#argmax()获取概率最大处对应的索引值
    # 打印类别名称及预测概率
    print_res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)],
                                                 predict[predict_cla].numpy())
    plt.title(print_res)
    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                                  predict[i].numpy()))
    plt.show()


if __name__ == '__main__':
    main()

训练完成的结果:

下载好一张郁金香的图片,命名为tulip.jpg后存在Test02的平行目录下。右键运行predict.py文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P21jarFB-1675932210113)(C:\Users\GuoShuXian\AppData\Roaming\Typora\typora-user-images\image-20221224182617222.png)]

                                  predict[predict_cla].numpy())
plt.title(print_res)
for i in range(len(predict)):
    print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                              predict[i].numpy()))
plt.show()

if name == ‘main’:
main()


训练完成的结果:

下载好一张郁金香的图片,命名为tulip.jpg后存在Test02的平行目录下。右键运行predict.py文件:
![在这里插入图片描述](https://img-blog.csdnimg.cn/f10441673d104975aabfdad00b50b82b.png)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值