使用pytorch搭建AlexNet网络模型

AlexNet详解

AlexNet是2012年ISLVRC 2012(ImageNet Large Scale Visual Recognition Challenge)竞赛的冠军网络,分类准确率由传统的 70%+提升到 80%+。 它是由Hinton和他的学生Alex Krizhevsky设计的。也是在那年之后,深度学习开始迅速发展。

ISLVRC 2012

训练集:1,281,167张已标注图片

验证集:50,000张已标注图片

测试集:100,000张未标注图片

 

模型结构

PyTorch 模型 iOS使用_PyTorch 模型 iOS使用

该网络的亮点在于: (1)首次利用 GPU 进行网络加速训练。 (2)使用了 ReLU 激活函数,而不是传统的 Sigmoid 激活函数以及 Tanh 激活函数。 (3)使用了 LRN 局部响应归一化。 (4)在全连接层的前两层中使用了 Dropout 随机失活神经元操作,以减少过拟合

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

PyTorch 模型 iOS使用_pytorch_02

 

经卷积后的矩阵尺寸大小计算公式为: N =(W-F+2P)/S+1

① 输入图片大小 W×W

② Filter大小 F×F

③ 步长 S

④ padding的像素数 P

PyTorch 模型 iOS使用_深度学习_03

 

PyTorch 模型 iOS使用_神经网络_04

 

 此模型所采用的的数据集是花分类数据集

PyTorch 模型 iOS使用_python_05

 

 

 1、构建网络模型:

 

model

import torch.nn as nn
import torch


class AlexNet(nn.Module):       #AlexNet类继承来自于nn.Module
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        #nn.Sequential将一系列的层结构打包,组合成一个新的结构features,专门用来提取图像的特征的结构
        #对于网络层结构比较多的网络,使用nn.Sequential来精简代码

        #inplace为True,将计算得到的值直接覆盖之前的值,产生的计算结果不会有影响。
        # 利用in-place计算可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。但是会对原变量覆盖,只要不带来错误就用。
        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],卷积默认的stride为 1
            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]
        )
        #nn.Sequential模块将全连接层打包成一个新的模块
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),                                        #nn.Dropout会以概率p随机的丢弃一些神经元
            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:                       #初始化权重的方法,其实在这里并不需要对权重初始化,因为pytorch自动使用凯明的初始化权重的方法
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)      #将输入x输入到构建features当中
        x = torch.flatten(x, start_dim=1)      #将x展平处理,索引从1开始(batch,channel,height,width),从后三个开始展平成一维的向量
        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)

2、写入训练脚本:

train

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
import time


device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")     #指定所使用的设备,有gpu的话就用设备的第一块GPU,没有的话默认使用cpu
print(device)


#随机裁剪,随机翻转,转化为tensor,然后进行标准化处理
data_transform = {
    "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                 transforms.RandomHorizontalFlip(),
                                 transforms.ToTensor(),
                                 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))])}

#os.getcwd()获取当前文件所在的目录,join将os.getcwd(), "../.."将这两个路径连在一起。也就是data_root的路径在此项目的上上层路径中
data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path,"../..返回上上层目录

# flower data set path    image_path的路径在data_root/data_set/flower_data,与flower_data同等路径
image_path = os.path.join(data_root, "data_set", "flower_data")
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"])  #在image_path同等路径下加入train,数据预处理传入train
train_num = len(train_dataset)     #用len()函数打印训练集有多少图片

# {'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反过来
# write dict into json file
json_str = json.dumps(cla_dict, indent=4)      #通过json包将获得的字典编码成json的格式
with open('class_indices.json', 'w') as json_file:       #将class_indices保存到json文件中,并把字典写入到json文件中
    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))


#DataLoader将训练的数据集加载进来
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size, shuffle=True,
                                           num_workers=0)    #所使用的线程个数

#ImageFolder载入测试集
validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
                                        transform=data_transform["val"])
val_num = len(validate_dataset)    #计算测试集图片个数,DataLoader载入测试集
validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                              batch_size=4, shuffle=True,
                                              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)     #实例化模型,花分类的类别数为5,初始化权重

net.to(device)      #判断GPU还是CPU
loss_function = nn.CrossEntropyLoss()        #定义多类别的损失交叉熵损失
# pata = list(net.parameters())      #查看模型的参数
optimizer = optim.Adam(net.parameters(), lr=0.0002)     #定义Adam优化器

epochs = 10
save_path = './AlexNet.pth'      #保存路径
best_acc = 0.0          #定义最好的准确率
train_steps = len(train_loader)
for epoch in range(epochs):
    # train
    #在训练和测试的时候,nn.Dropout的表现是不同的,在训练时nn.Dropout会以概率p随机的丢弃一些神经元,但是在测试时,所有神经元都不会被丢弃,
    # 所以用net.train()和net.eval()来管理这种规则方法。net.train()启用dropout方法,net.eval()关闭dropout方法
    net.train()
    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))      #判断使用GPU还是CPU
        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():      #禁止pytorch对参数的跟踪,验证过程中不会计算损失梯度
        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')

输入测试的代码段,进行测试:

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

测试结果如下:

PyTorch 模型 iOS使用_深度学习_06

PyTorch 模型 iOS使用_PyTorch 模型 iOS使用_07

整个训练代码的运行结果如下:

PyTorch 模型 iOS使用_神经网络_08

得到测试的准确率最高为72% 

 3、写入预测脚本:

predict

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 = "../daisy.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)   #在最左边加入一个维度batch

    # 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:    #读取我们载入的json文件
        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():     #禁止pytorch跟踪参数的损失梯度
        # predict class
        output = torch.squeeze(model(img.to(device))).cpu()    #将输出进行压缩,压缩掉batch维度
        predict = torch.softmax(output, dim=0)     #softmax获取概率分布
        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()

运行,预测结果如下:

PyTorch 模型 iOS使用_深度学习_09

PyTorch 模型 iOS使用_pytorch_10

预测的准确率还是不错的。