(十一) 深度学习笔记 | 使用pytorch搭建AlexNet并训练花分类数据集

一、AlexNet前言

  1. 此前有写过关于AlexNet网络结构详解,有需要可以自行查看https://blog.csdn.net/weixin_45579930/article/details/112525010
  2. 这是关于AlexNet论文:AlexNet原文

二、数据集的下载与划分

2.1数据集下载

首先,在本次搭建过程中。我们先下载需要使用到的数据集。下载完成之后可以发现,里面包含5种类型的花,每种类型有600-900张不等
http://download.tensorflow.org/example_images/flower_photos.tgz

在这里插入图片描述

在这里插入图片描述

2.2数据集与测试集划分

  • 由于此数据集不像 CIFAR10 那样下载时就划分好了训练集和测试集,因此需要自己划分。
  • 执行下方代码,即可自动将数据集划分成训练集与测试集
    在这里插入图片描述
    完成之后,会对应生成训练集train 和 验证集val
    在这里插入图片描述

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

四、model.py

import torch.nn as nn
import torch


# 创建一个AlexNet类,继承与nn.module父类
class AlexNet(nn.Module):
    # 定义初始化函数,来定义网络在正向传播中所需要使用到的一些层结构
    def __init__(self, num_classes=1000, init_weights=False):

        super(AlexNet, self).__init__()
        # 与demo不一样的是 nn.Sequential模块,可以将一系列的层结构进行打包,组合成新的结构
        # features专门用于提取图像特征的结构
        # nn.Sequential模块,可以对比上一个demo,self.模块名称,如果每个模块都需要这么定义的话。对于一个网络层次比较多的网络话
        # 会比较麻烦,所以可以通过nn.Sequential函数,可以精简代码
        self.features = nn.Sequential(
            # 第一层 卷积核大小是11 所以kernel_size=11
            # 使用了96个卷积核 由于我们的卷积核比较小,所以我们在这里只取它的一半,所达到的准确率也是基本一样的
            # 第一层是rgb的彩色图像所以是3

            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55]
            # 经过nn.ReLU激活函数,这里的inplace可以理解为pytorch增加计算量。通过这个方法可以在内存中载入更大的内存
            nn.ReLU(inplace=True),
            # 池化层是没有池化核个数这个参数的
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            # 卷积默认的就是1 如果是1,就不需要写了
            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]
            # 第三个卷积层 上一层的输出时128,所以这边是128
            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),
            # 卷积层5
            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(
            # 可以在展平
            # p代表随机失活的比例,默认等于0.5
            nn.Dropout(p=0.5),
            # 第一层全连接等于上一层的输出 上一层的输出是6*6*256,由于我们使用的只有一半,所以就是6*6*128.2048个节点个数
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            # 以百分之50的概率 百分之50的比例失活神经元
            nn.Dropout(p=0.5),
            # 输入就等于上一层的输出
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            # num_classes输出就是我们数据集的类别的个数,默认等于1000。
            nn.Linear(2048, num_classes),
        )
        # 初始化权重的权限,如果在搭建网络过程中等于true,就会进入初始化权重的函数
        if init_weights:
            self._initialize_weights()

    # 定义正向传播过程,x就是输入进来的变量
    def forward(self, x):
        # 首先我们将输入进来的训练样本,输入到我们的features部件当中
        x = self.features(x)
        # flatten - 我们再将它进行展平处理,纬度是从index=1开始,1是channel开始展平为一纬向量
        x = torch.flatten(x, start_dim=1)
        # 展平之后,我们输入到我们的分类结构当中。就是我们后面定义的全连接层
        x = self.classifier(x)
        return x
    # 初始化权重函数
    def _initialize_weights(self):
        # 遍历self.modules,继承nn.moudel这个父类
        # 定义中说会返回一个迭代器,遍历每一层结构,
        for m in self.modules():
            # 判断是属于哪个类别,isinstance判断得到的是否是我们得到的所给定的类型
            if isinstance(m, nn.Conv2d):
                # 当我们这层是卷积层的话Conv2d,我们用kaiming_normal_初始化变量方法来定义我们卷积权重来进行初始化。
                # 如果偏置不为空,我们用0来初始化
                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):
                # 如果传进来是全连接成,那我们用normal_,通过正态分布给我们进行赋值。
                # 正态分布的均值是0,方差是0.01,偏置fire是0
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


五、train.py

5.1导入所需要使用的包

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 pytorch_classification.Test2_alexnet.model import AlexNet
import os
import json
import time


def main():
	# 使用GPU训练
	# 使用torch.device函数,来指定我们训练过程中的设备。
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

5.2数据预处理

需要注意的是,对训练集的预处理,多了随机裁剪和水平翻转这两个步骤。可以起到扩充数据集的作用,增强模型泛化能力。

    data_transform = {
        # 当我们的key为训练集的时候,我们就返回训练集所需要使用的一系列预处理方法 
        # RandomResizedCrop随机裁剪
        # RandomHorizontalFlip随机反转
        # 转化为ToTenso r在进行一个标准化处理Normalize
        "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                     transforms.RandomHorizontalFlip(),
                                     transforms.ToTensor(),
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
        # 对于验证集,我们只是Resize到我们的224*224,接着转化为一个ToTensor。同样进行标准化处理
        "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))])}

5.3导入训练集

对于之前的LeNet网络搭建中是使用的torchvision.datasets.CIFAR10和torch.utils.data.DataLoader()来导入和加载数据集。

# 导入训练集
train_set = torchvision.datasets.CIFAR10(root='./data', 	 # 数据集存放目录
										 train=True,		 # 表示是数据集中的训练集
                                        download=True,  	 # 第一次运行时为True,下载数据集,下载完成后改为False
                                        transform=transform) # 预处理过程
# 加载训练集                              
train_loader = torch.utils.data.DataLoader(train_set, 	  # 导入的训练集
										   batch_size=50, # 每批训练的样本数
                                          shuffle=False,  # 是否打乱训练集
                                          num_workers=0)  # num_workers在windows下设置为0

  • 但是这次的 花分类数据集 并不在 pytorch 的 torchvision.datasets. 中,因此需要用到datasets.ImageFolder()来导入。
  • ImageFolder()返回的对象是一个包含数据集所有图像及对应标签构成的二维元组容器,支持索引和迭代,可作为torch.utils.data.DataLoader的输入。
    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)
    # 通过ImageFolder去加载我们的数据集
    # transform数据预处理,传入train这个key,会传入训练集
    train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                         transform=data_transform["train"])
    # 打印训练集有多少张图片
    train_num = len(train_dataset)

5.4加载验证集

# 导入验证集并进行预处理
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)

5.5存储 索引:标签 的字典

为了方便在 predict 时读取信息,将 索引:标签 存入到一个 json 文件中

# 字典,类别:索引 {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
flower_list = train_dataset.class_to_idx
# 将 flower_list 中的 key 和 val 调换位置
cla_dict = dict((val, key) for key, val in flower_list.items())

# 将 cla_dict 写入 json 文件中
json_str = json.dumps(cla_dict, indent=4)
with open('class_indices.json', 'w') as json_file:
    json_file.write(json_str)

class_indices.json 文件内容如下:

{
    "0": "daisy",
    "1": "dandelion",
    "2": "roses",
    "3": "sunflowers",
    "4": "tulips"
}

六、predict.py

import torch
from pytorch_classification.Test2_alexnet.model import AlexNet
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json

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 = Image.open("../tulip.jpg")
plt.imshow(img)
# [N, C, H, W]
img = data_transform(img)
# expand batch dimension
img = torch.unsqueeze(img, dim=0)

# read class_indict
try:
    json_file = open('./class_indices.json', 'r')
    class_indict = json.load(json_file)
except Exception as e:
    print(e)
    exit(-1)

# create model
model = AlexNet(num_classes=5)
# load model weights
model_weight_path = "./AlexNet.pth"
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
    # predict class
    output = torch.squeeze(model(img))
    predict = torch.softmax(output, dim=0)
    predict_cla = torch.argmax(predict).numpy()
print(class_indict[str(predict_cla)], predict[predict_cla].item())
plt.show()

参考:
https://blog.csdn.net/m0_37867091/article/details/107150142

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值