CNN经典网络(2)---AlexNet(模型搭建+训练+预测+可视化)

写在前面:根据b站博主霹雳吧啦Wz 学习CNN,作为个人的学习记录。

目录

论文:

模型架构:

模型参数:

模型搭建:

应用:

数据集:

1、公开数据集

2、自己的数据集

训练:

1、寻找可使用的GPU

2、图像预处理

3、找到训练集 

4、将类索引写入json文件

5、加载数据集

 6、定义网络、训练设备、损失函数、优化器

7、训练

训练结果:

预测: 

​编辑

预测结果:

数据可视化:


论文:

2012年才出现的AlexNet,在ImageNet LSVRC-2010比赛中实现了前1和前5的错误率分别为37.5%和17.0%,在ILSVRC-2012比赛中获得了15.3%的前5的获胜测试错误率。

论文:《ImageNet Classification with Deep Convolutional Neural Networks》

论文贡献:

  • 在ILSVRC-2010和ILSVRC-2012比赛中使用的ImageNet子集上训练了迄今为止最大的卷积神经网络之一,并在这些数据集上获得了迄今为止报道过的最好的结果
  • 为了防止过拟合,首次使用ReLUs作为激活函数,并指出快速学习对在大数据集上训练的大模型的性能有很大的影响。
  • 首次使用两个GPU进行训练,这将前1和前5的错误率分别降低了1.7%和1.2%,双gpu网络的训练时间比单gpu网络略短。
  • 使用局部响应归一化(LRN),有助于快速收敛,增强了模型的泛化能力。(一般是在激活、池化后进行的一种处理方法)。
  • 使用重叠池化层,降低了0.4%和0.3%的错误率。避免过拟合。
  • 使用数据增强方式:平移和翻转;PCA进行降维。避免过拟合。
  • 首次应用DropOut机制,大约使收敛所需的迭代次数翻了一倍。避免过拟合。

模型架构:

翻译:网络架构明确地显示了两个gpu之间的职责描述。一个GPU运行图形顶部的层部件,另一个运行图形底部的层部件。gpu只在特定的层进行通信。网络的输入是150,528维,网络剩余层中的神经元数量由253,440-186,624-64,896-64,896-43,264 - 4096-4096-1000给出。 

模型参数:

模型搭建:

由于这个模型太大,在这里,所有的卷积核个数和全连接层节点数都减半。

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.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),               # 随机失活一半的神经元
            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),     # 这里的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

    def _initialize_weights(self):              # 初始化权重
        for m in self.modules():
            if isinstance(m, nn.Conv2d):      # 如果在卷积层,那就使用kaiming_normal_方法初始化
                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)

应用:

数据集:

1、公开数据集

使用的是花分类数据集,分别有雏菊、蒲公英、玫瑰、向日葵、郁金香五种类别。首先新建一个名为"flower_data"的新文件夹,然后在浏览器下载该数据集,解压进"flower_data",使用split_data.py程序对数据集进行划分,变成训练集和验证集。

这是所有图片下的文件,一种类别的图片是一个文件夹,文件夹名称为类别名称: 

 

2、自己的数据集

只需要将自己的数据集按类别分开(例如daisy、dandelion、roses、sunflowers、tulips),放在一个文件夹内(例如flower_data)。然后使用split_data.py程序划分训练集和验证集。最后在训练程序和测试程序中找到变量num_classes的值,修改成自己数据集的种类个数。即可

训练程序里: 

预测程序里:

 

 split_data.py:

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

训练:

1、寻找可使用的GPU

如果有,默认使用第一块GPU;如果没有,那就用CPU。

    ### 1. 电脑如果有GPU的话,就会选择使用GPU,否则就用CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

2、图像预处理

用transforms方式进行预处理,用compose将各种方法合在一起。

### 2. 图像预处理
data_transform = {
    # 训练集图片预处理方式
    "train": transforms.Compose([transforms.RandomResizedCrop(224),   # 将图像随机裁剪成224*224
                                 transforms.RandomHorizontalFlip(),   # 水平翻转
                                 transforms.ToTensor(),               # 转换成(C*H*W),并将像素值大小归一化[0,1]
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),  # 将三个通道的像素值标准化[-1,1]
    # 验证集图片预处理方式
    "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))])}

3、找到训练集 

### 3. 找到训练集
# 返回上一级目录
data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path
# 在上一级目录下寻找data_set文件夹,然后进入其下一级目录flower_data
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)
# image_path是指向flower_data文件夹,接着将这个文件夹下的train定义成训练集,然后对训练集的图像根据前面写的data_transform预处理方法处理图像,最后放进train_dataset中。
train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                     transform=data_transform["train"])
train_num = len(train_dataset)   # 获取训练集数量

4、将类索引写入json文件

### 4. 将类索引写入json文件
# {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
flower_list = train_dataset.class_to_idx   # 这里返回一个字典,key是文件夹名,value是顺序值。如上一行所示
cla_dict = dict((val, key) for key, val in flower_list.items())   # 交换键值,主要因为后面的预测值是数字,想输出的时候是花的名称
# write dict into json file   将类索引的字典写入json文件
json_str = json.dumps(cla_dict, indent=4)
with open('class_indices.json', 'w') as json_file:
    json_file.write(json_str)

生成的json文件如下图所示: 

5、加载数据集

加载训练集

根据找训练集的方式去找到验证集,而且加载进来。

### 5. 加载进训练集和验证集
batch_size = 32   # 设置批次大小
nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # 设置加载数据时的进程数
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))

 6、定义网络、训练设备、损失函数、优化器

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)   # 定义优化器

7、训练

### 7. 训练
epochs = 10  # 设置训练轮数
save_path = './AlexNet.pth'   # 模型权重保存路径
best_acc = 0.0   # 用于记录最高的准确率
train_steps = len(train_loader)    # 训练步数
for epoch in range(epochs):
    # train
    net.train()
    running_loss = 0.0   # 计算平均损失
    train_bar = tqdm(train_loader, file=sys.stdout)    # 设置训练的进度条
    for step, data in enumerate(train_bar):
        images, labels = data    # 将data分成图像和标签
        optimizer.zero_grad()    # 梯度清零
        outputs = net(images.to(device))     # 正向传播获得输出
        loss = loss_function(outputs, labels.to(device))    # 计算损失值
        loss.backward()   # 损失进行反向传播
        optimizer.step()   # 更新权重w

        # 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   # 将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()   # 将预测值与真实值进行对比,如果相等,eq(predict_y, val_label)返回为1,否则为0;然后用sum求和求出预测对了多少

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

训练结果:

预测: 

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():
    ### 1、寻找可使用的GPU,如果没有就用cpu
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    ### 2、 定义图像预处理方式
    data_transform = transforms.Compose(
        [transforms.Resize((224, 224)),    # 将图像resize成224*224的
         transforms.ToTensor(),    # 变成C*H*W格式,并且归一化
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])   # 标准化

    ### 3、加载进需要的预测的图像
    img_path = "rose.jpeg"
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    ### 4、图像预处理
    plt.imshow(img)  # 显示图像
    # [N, C, H, W]
    img = data_transform(img)   # 预处理
    # expand batch dimension
    img = torch.unsqueeze(img, dim=0)     # 拓展成四维,增加一个批次纬度

    ### 5、获取类别
    # 加载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)

    ### 6、定义模型,加载权重,然后预测
    # 定义模型
    model = AlexNet(num_classes=5).to(device)

    # 加载模型权重
    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())    # 在json文件里找到索引对应的种类名称打印出来,而且加上概率值
    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()

预测图片:

预测结果:

数据可视化:

准确率和轮数的关系:

损失值和轮数的关系:

 

网络架构:

 

猜你喜欢

转载自blog.csdn.net/m0_57224196/article/details/128440592
今日推荐