AlexNet网络结构的实现和详解

AlexNet网络结构的实现和详解_第1张图片

AlexNet网络结构特点:

1.首次利用GPU进行网络加速训练 (GPU处理速度是CPU的20-50倍)

2.使用了ReLU激活函数,而不是传统的Sigmoid激活函数(缺点:求导麻烦、容易梯度消失)以及tanh激活函数

AlexNet网络结构的实现和详解_第2张图片

 3.使用了LRN(Local Response Normalization)局部相应归一化

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

解释一下什么叫过拟合

拟合的函数完美预测训练集,但对新数据的测试集预测结果差。

过度的拟合了训练数据,而没有考虑到泛化能力

根本原因

特征维度过多,模型假设过于复杂,参数过多,训练数据过少,噪声过多

解决方法

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

AlexNet网络结构的实现和详解_第3张图片

 Dropout会在每一层中随机失活一部分神经元,可以理解为变相的减少了网络训练的参数,从而达到减少过拟合的作用

在将AlexNet网络结构之前。首先回顾一下卷积之后的矩阵尺寸大小计算公式

N = (W - F + 2P) / S + 1 

N:卷积之后的矩阵尺寸大小

W:输入图片的大小W * W

F:卷积核的大小F * F

P:padding(补0)的像素p

S:滑动的步长

第一个卷积层

AlexNet网络结构的实现和详解_第4张图片

 之所以有上下两部分,是因为有两块GPU进行并行运算

先看一下Conv1 就是框选出来的这一部分

输入图片是一个高为224 宽为224 深度为3的彩色图像

卷积核大小是11*11

所采用的步距是4

padding:[1,2]  在特征矩阵的左边加上1列0 左边加上2列0 上面加上一列0 下面加上2列0

采用卷积核的个数是2*48=96  一层有两个,一共48层

(224-11+2+1)/4 +1 =55

那么第一个卷积层输入:224*224*3

第一个卷积层输出:55*55*96(输出的深度是卷积核的个数)

第一个池化层

AlexNet网络结构的实现和详解_第5张图片

第一个池化层的输入:55*55*96

池化层池化核的尺寸:3

padding:0

步长:2

(55-3+2*0)/2+1=27

第一个池化层的输出:27*27*96   (池化操作只会改变特征矩阵的高度和宽度,不会改变深度

第二个卷积层:

AlexNet网络结构的实现和详解_第6张图片

 卷积核的个数:2*128=256(也是输出矩阵的深度)

卷积核的尺寸:5*5

padding:[2,2]

步长:1

第二个卷积层输出矩阵的尺寸:

(27-5+2+2)/1+1= 27

第二个卷积层的输入:[27,27,96]

第二个卷积层的输出:[27,27,256]

第二个池化层

AlexNet网络结构的实现和详解_第7张图片

 卷积核的尺寸:3

padding:0

stride:2

池化后的矩阵尺寸:

(27-3+2*0)/2+1=13

第二个池化层输入:[27,27,256]

第二个池化层输出:[13,13,256]

第三个卷积层:

AlexNet网络结构的实现和详解_第8张图片

 卷积核的个数:2*192=384

卷积核的尺寸:3

padding:[1,1]

stride:1

(13-3+1+1)/1+1=13

第三个卷积层输入:[13,13,256]

第三个卷积层输出:[13,13,384]

第四个卷积层

AlexNet网络结构的实现和详解_第9张图片

 卷积核的个数:2*192=384

卷积核的大小:3

padding:[1,1]

步长:1

(13-3+1+1)/1+1=13

第四个卷积层的输入:[13,13,384]

第四个卷积层的输出:[13,13,384]

第五个卷积层

AlexNet网络结构的实现和详解_第10张图片

 卷积核的个数:2*128=256

卷积核的大小:3

padding:[1,1]

步长:1

(13-3+1+1)/1+1=13

第五个卷积层的输入:[13,13,384]

第五个卷积层的输出:[13,13,256]

第三个池化层

AlexNet网络结构的实现和详解_第11张图片

 池化核的大小:3

padding:0

stride:2

(13-3+2*0)/2+1=6

第三个池化层的输入:[13,13,256]

第三个池化层的输出:[6,6,256]

最后面是三个全连接层,将Maxpool输出的进行展平,然后和三个层进行连接

总结一下这个AlexNet网络结构:
一共是五个卷积层三个全连接层

分别是:

Conv1,Maxpool1,Conv2,Maxpool2,Conv3,Conv4,Conv5,Maxpool3,Full connection1,Full connection2,Full connection3

接下来是本次训练用到的数据集,是一个花分类的数据集

AlexNet网络结构的实现和详解_第12张图片

数据集下载地址:

https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz

将下载的数据集放在这个文件夹下面,并将压缩包解压

AlexNet网络结构的实现和详解_第13张图片

 提前写好一个将数据集分为训练集和测试集的脚本,比例是9:1

下面是脚本的代码

import os
from shutil import copy, rmtree
import random


def mk_file(file_path: str):
    if os.path.exists(file_path):
        # 如果文件夹存在,则先删除原文件夹在重新创建
        rmtree(file_path)
    os.makedirs(file_path)


def main():
    # 保证随机可复现
    random.seed(0)

    # 将数据集中10%的数据划分到验证集中
    split_rate = 0.1

    # 指向你解压后的flower_photos文件夹
    cwd = os.getcwd()
    data_root = os.path.join(cwd, "flower_data")
    origin_flower_path = os.path.join(data_root, "flower_photos")
    assert os.path.exists(origin_flower_path), "path '{}' does not exist.".format(origin_flower_path)

    flower_class = [cla for cla in os.listdir(origin_flower_path)
                    if os.path.isdir(os.path.join(origin_flower_path, cla))]

    # 建立保存训练集的文件夹
    train_root = os.path.join(data_root, "train")
    mk_file(train_root)
    for cla in flower_class:
        # 建立每个类别对应的文件夹
        mk_file(os.path.join(train_root, cla))

    # 建立保存验证集的文件夹
    val_root = os.path.join(data_root, "val")
    mk_file(val_root)
    for cla in flower_class:
        # 建立每个类别对应的文件夹
        mk_file(os.path.join(val_root, cla))

    for cla in flower_class:
        cla_path = os.path.join(origin_flower_path, cla)
        images = os.listdir(cla_path)
        num = len(images)
        # 随机采样验证集的索引
        eval_index = random.sample(images, k=int(num*split_rate))
        for index, image in enumerate(images):
            if image in eval_index:
                # 将分配至验证集中的文件复制到相应目录
                image_path = os.path.join(cla_path, image)
                new_path = os.path.join(val_root, cla)
                copy(image_path, new_path)
            else:
                # 将分配至训练集中的文件复制到相应目录
                image_path = os.path.join(cla_path, image)
                new_path = os.path.join(train_root, cla)
                copy(image_path, new_path)
            print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="")  # processing bar
        print()

    print("processing done!")


if __name__ == '__main__':
    main()

把脚本文件放在这个文件夹里面

AlexNet网络结构的实现和详解_第14张图片

 就在这个页面,按住shit+鼠标右键 ,在终端中打开

 AlexNet网络结构的实现和详解_第15张图片

 输入这行代码,然后敲回车

AlexNet网络结构的实现和详解_第16张图片

完成

AlexNet网络结构的实现和详解_第17张图片 生成了训练集文件夹和测试集文件夹

AlexNet网络结构的实现和详解_第18张图片

接下来是代码详解部分

AlexNet网络结构的搭建代码详解

import torch.nn as nn
import torch


class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(#nn.Sequential能够将一系列层结构组合成一个新的结构   features用于提取图像特征的结构
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55] 48是卷积核个数 batch channel high weight
                                                                    #padding参数解释:如果是int型,比如说1 就是上下左右都补1列0  如果传入一个tuple(元组)的话 比如传入(1,2),1代表上下方各补一行0,2代表左右两侧各补两列0
            nn.ReLU(inplace=True),#inplace这个参数可以理解为pytorch通过一种方法增加计算量,来降低内存使用容量的一种方法,可以通过这种方法在内存中载入更大的模型
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27] 没有设置stride是因为这个卷积层的步长是1,而默认的步长就是1
            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),#p是每一层随机失活的比例  默认是0.5
            nn.Linear(128 * 6 * 6, 2048),#第一个全连接层,全连接层的节点个数是2048个
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),#第二个全连接层
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),#第三个全连接层 输入的是数据集 类别的个数,默认是1000
        )
        if init_weights:#初始化权重
            self._initialize_weights()

    def forward(self, x):#正向传播过程
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)#进行一个展平处理  将传进来的变量x进行展平,从index=1 这个维度开始 也就是channel
        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')#kaiming_normal初始化权重方法
                if m.bias is not None:#如果偏置不为空的话,那么就用0去初始化
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):#如果这个实例是全连接层
                nn.init.normal_(m.weight, 0, 0.01)#采用normal,也就是正态分布来给我们的权重进行赋值,正态分布的均值等于0,方差是0.01
                nn.init.constant_(m.bias, 0)#将偏置初始化为0

训练模块的代码详解

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():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#指定训练过程中所指定的设备 如果有GPU设备就使用第一块GPU设备,如果没有的话,就使用CPU设备
    print("using {} device.".format(device))

    data_transform = {#数据预处理函数
        "train": transforms.Compose([transforms.RandomResizedCrop(224),#随机裁剪成224*224像素大小
                                     transforms.RandomHorizontalFlip(),#在水平方向随机反转
                                     transforms.ToTensor(),#转换成一个张量
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),#进行一个标准化处理 output=(input-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.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path getcwd获取当前文件所在的目录  ..是返回上级目录 ../..是返回上上级目录 path.join是将两个目录连接在一起
    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 = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                         transform=data_transform["train"])
    train_num = len(train_dataset)#记录训练集有多少张图片

    # {'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())#遍历分类索引的字典,然后将key value反过来 变成 0 daisy
    # write dict into json file
    json_str = json.dumps(cla_dict, indent=4) #通过json.dumps将cla这个字典转化为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,
                                               num_workers=0)

    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=batch_size, shuffle=False,
                                                  num_workers=0)

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

    net = AlexNet(num_classes=5, init_weights=True)

    net.to(device)
    loss_function = nn.CrossEntropyLoss()
    # pata = list(net.parameters())
    optimizer = optim.Adam(net.parameters(), lr=0.0002)

    epochs = 10
    save_path = './AlexNet.pth'#保存权重的路径
    best_acc = 0.0
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # train
        net.train()#dropout只在训练集中使用 而不在验证集中使用
        running_loss = 0.0
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data
            optimizer.zero_grad()
            outputs = net(images.to(device))
            loss = loss_function(outputs, labels.to(device))
            loss.backward()#将损失反向传播
            optimizer.step()#更新节点参数

            # print statistics
            running_loss += loss.item()

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

        # validate
        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
                outputs = net(val_images.to(device))
                predict_y = torch.max(outputs, dim=1)[1]
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()

        val_accurate = 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:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)

    print('Finished Training')


if __name__ == '__main__':
    main()

训练结果

AlexNet网络结构的实现和详解_第19张图片

预测模块的代码详解

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 = 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_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)
    # expand batch dimension
    img = torch.unsqueeze(img, dim=0)

    # read class_indict
    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()
    with torch.no_grad():
        # predict class
        output = torch.squeeze(model(img.to(device))).cpu()
        predict = torch.softmax(output, dim=0)
        predict_cla = torch.argmax(predict).numpy()

    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()

 预测结果

AlexNet网络结构的实现和详解_第20张图片

你可能感兴趣的:(网络,深度学习,人工智能)