Pytorch之AlexNet花朵分类

news/2024/9/23 10:20:10/
  • 💂 个人主页:风间琉璃
  • 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)订阅专栏

目录

一、AlexNet

1.卷积模块

2.全连接模块

3.AlexNet创新点

1.更深的神经网络结构

2.ReLU激活函数的使用

3.局部响应归一化(LRN)的使用

4.数据增强和Dropout

5.大规模分布式训练

二、AlexNet实现

1.定义AlexNet网络模型

2.加载数据集

3.训练模型

4.测试模型

三、实现图像分类


一、AlexNet

AlexNet是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton在2012年ImageNet图像分类竞赛中提出的一种经典的卷积神经网络。当时,AlexNet在 ImageNet 大规模视觉识别竞赛中取得了优异的成绩,把深度学习模型在比赛中的正确率提升到一个前所未有的高度。因此,它的出现对深度学习发展具有里程碑式的意义。

AlexNet输入为RGB三通道的3 x 224 × 224大小的色彩图像。AlexNet 共包含5 个卷积层(包含3个池化)和 3 个全连接层。其中,每个卷积层都包含卷积核、偏置项、ReLU激活函数和局部响应归一化(LRN)模块。第1、2、5个卷积层后面都跟着一个最大池化层,后三个层为全连接层。最终输出层为softmax,将网络输出转化为概率值,得到样本属于 1000 个类别的概率分布,用于预测图像的类别。

为了能够在当时的显卡设备NVIDIA GTX 580(3GB显存)上训练模型, Alex Krizhevsky 将卷积层、前 2 个全连接层等拆开在两块显卡上面分别训练,最后一层合 并到一张显卡上面,进行反向传播更新。AlexNet 在 ImageNet 取得了 15.3%的Top-5 错误 率,比第二名在错误率上降低了 10.9%。

1.卷积模块

AlexNet共有五个卷积层,每个卷积层都包含卷积核、偏置项、ReLU激活函数和局部响应归一化(LRN,Local Response Normalization)模块。

卷积层C1:

输入数据:3x224x224

使用96个核(2个部分之和,每个部分为48,因为输出特征层的通道数等于卷积核个数)对3 x 224 × 224的输入图像进行特征提取,卷积核大小为3 x 11 × 11(输入特征层的通道数等于卷积核的通道数),步长为4。将一对48x55×55的特征图分别放入ReLU激活函数。激活后的图像进行最大池化,size为3×3,stride为2,池化后的特征图size为48x27×27(一部分),总共就是96x27x27。池化后进行LRN处理。

LRN(Local Response Normalization) 局部响应归一化,LRN模拟神经生物学上一个叫做侧抑制(lateral inhibitio)的功能,侧抑制指的是被激活的神经元会抑制相邻的神经元。

LRN局部响应归一化借鉴侧抑制的思想实现局部抑制,使得响应比较大的值相对更大,提高了模型的泛化能力。LRN只对数据相邻区域做归一化处理,不改变数据的大小和维度。LRN概念是在AlexNet模型中首次提出,在GoogLenet中也有应用,但是LRN的实际作用存在争议,如在2015年Very Deep Convolutional Networks for Large-Scale Image Recognition 论文中指出LRN基本没什么用。

卷积层C2:

输入数据:96x27x27

使用卷积层C1的输出作为输入,并使用256个卷积核进行滤波,核大小为48x5 × 5。卷积后数据为256x27x27,做了padding,使得卷积后图像大小不变。其次进行relu,然后进行最大池化,最大池化Maxpool的核大小为3x3,步长为2,池化后的数据为256x13x13([27-3]/2 + 1),下部分:128x13x13。

卷积层C3:

输入数据:256x13x13

使用384个卷积核,核大小为256 x 3 × 3 ,与卷积层C2的输出相连。卷积后的数据:384x13x13,卷积前后图像的大小不变,然后经过relu层,输出数据384x13x13, C3层没有Maxpool层和norm层

卷积层C4:

输入数据:384x13x13

使用384个卷积核,核大小为384 x 3 × 3。卷积核数据384 x 13 x13,做了padding填充,使卷积后图像大小不变,relu后数据384 x 13 x 13。同样没有池化层和norm层

卷积层C5:

输入数据:384 x 13 x13

使用256个卷积核,核大小为384 x 3 × 3。经过relu和池化后256 x 6 x 6(9216),池化核size同样为3×3,stride为2。

其中,卷积层C3、C4、C5互相连接,中间没有接入池化层或归一化层

2.全连接模块

全连接层F6:

输入数据:256x6x6

全连接层输出:因为是全连接层,卷积核size为6×6×256,4096个卷积核生成4096个特征图,尺寸为1×1。然后放入ReLU函数、Dropout处理。值得注意的是AlexNet使用了Dropout层,以减少过拟合现象的发生。

Dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率(一般是50%,这种情况下随机生成的网络结构最多)将其暂时从网络中丢弃(保留其权值),不再对前向和反向传输的数据响应。注意是暂时,对于随机梯度下降来说,由于是随机丢弃,故而相当于每一个mini-batch都在训练不同的网络,drop out可以有效防止模型过拟合,让网络泛化能力更强,同时由于减少了网络复杂度,加快了运算速度。还有一种观点认为drop out有效的原因是对样本增加来噪声,变相增加了训练样本。

全连接层F7:与F6层相同。

全连接层F8:最后一层全连接层的输出是1000维softmax的输入,softmax会产生1000个类别预测的值

各网络层参数:

较为详细的网络结构:(227x227是经过填充后的图像),这里将分开训练的两部分合在一起啦。

 注意:C3和C4层无池化层哦

3.AlexNet创新点

1.更深的神经网络结构

AlexNet 是首个真正意义上的深度卷积神经网络,它的深度达到了当时先前神经网络的数倍。通过增加网络深度,AlexNet 能够更好地学习数据集的特征,从而提高了图像分类的精度。

2.ReLU激活函数的使用

AlexNet 首次使用了修正线性单元(ReLU)这一非线性激活函数。相比于传统的 sigmoid 和 tanh 函数,ReLU 能够在保持计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效

3.局部响应归一化(LRN)的使用

LRN是在卷积层和池化层之间添加的一种归一化操作。在卷积层中,每个卷积核都对应一个特征图(feature map),LRN就是对这些特征图进行归一化。具体来说,对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和

LRN本质是抑制邻近神经元的响应,从而增强了神经元的较大响应。这种技术在一定程度上能够避免过拟合,并提高网络的泛化能力。

4.数据增强和Dropout

为了防止过拟合,AlexNet 引入了数据增强和 Dropout 技术。

数据增强可以通过对图像进行旋转、翻转、裁剪等变换,增加训练数据的多样性,提高模型的泛化能力

Dropout 则是在训练过程中随机删除一定比例的神经元,强制网络学习多个互不相同的子网络,从而提高网络的泛化能力。Dropout简单来说就是在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。

 5.大规模分布式训练

AlexNet在使用GPU进行训练时,可将卷积层和全连接层分别放到不同的GPU上进行并行计算,从而大大加快了训练速度。像这种大规模 GPU 集群进行分布式训练的方法在后来的深度学习中也得到了广泛的应用。

二、AlexNet实现

1.定义AlexNet网络模型

AlexNet网络模型的构建完全按照前面的各层的参数设置的,包括5层卷积层和3层全连接层。

class AlexNet(nn.Module):def __init__(self, num_classes=1000, init_weights=False):super(AlexNet, self).__init__()self.features = nn.Sequential(# input [3, 224, 224]    output [96, 55 ,55]nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=2),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2),  # output[96, 27, 27]nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, padding=2),  # output[256, 27, 27]nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2),  # output[256, 13, 13]nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, padding=1),  # output[384, 13, 13]nn.ReLU(inplace=True),nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1),  # output[384, 13, 13]nn.ReLU(inplace=True),nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1),  # output[256, 13, 13]nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2)  # output[256, 6, 6])self.classifier = nn.Sequential(nn.Dropout(p=0.5),nn.Linear(256 * 6 * 6, 4096),nn.ReLU(inplace=True),nn.Dropout(p=0.5),nn.Linear(4096, 4096),nn.ReLU(inplace=True),nn.Linear(4096, num_classes))# 初始化参数if init_weights:self._initialize_weights()def forward(self, x):x = self.features(x)x = torch.flatten(x, start_dim=1)# x = x.view(-1, 6*6*256)x = self.classifier(x)return xdef _initialize_weights(self):for m in self.modules():  # 变量网络所有层if isinstance(m, nn.Conv2d):  # 是否为卷积层# 使用Kaiming初始化方法来初始化该层的权重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):  # 是否为Linear# 正太分布初始化全连接层nn.init.normal_(m.weight, 0, 0.01)# 将偏项设置为0nn.init.constant_(m.bias, 0)

2.加载数据集

这里数据集是自定义的5中花朵数据集,每个文件夹下对应着一种花。

这是总的数据集,但是我们还需要将数据集分为测试集和训练集,分类脚本如下

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 barprint()print("processing done!")

处理完后如下:

 然后就可以加载数据集和测试集,并进行相应的预处理操作。

    # 预处理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)),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}# 数据集根目录data_root = os.path.abspath(os.getcwd())print(os.getcwd())# 图片目录image_path = os.path.join(data_root, "data_set", "flower_data")print(image_path)assert os.path.exists(image_path), "{} path does not exit.".format(image_path)# 准备数据集train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),transform=data_transform["train"])train_num = len(train_dataset)validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),transform=data_transform["val"])val_num = len(validate_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())# 将字典转换为格式化的JSON字符串,每行缩进4个空格json_str = json.dumps(cla_dict, indent=4)# 打开名为 'class_indices.json' 的JSON文件,并将JSON字符串写入其中with open('class_indices.json', 'w') as json_file:json_file.write(json_str)batch_size = 32nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workersprint("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_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))

3.训练模型

数据集准备好后就可以实例化网络进行模型的训练。

    net = AlexNet(num_classes=5, init_weights=True)net.to(device)loss_function = nn.CrossEntropyLoss()optimizer = optim.Adam(net.parameters(), lr=0.0002)epochs = 200save_path = './AlexNet.pth'best_acc = 0.0train_steps = len(train_loader)for epoch in range(epochs):# 将神经网络设置为训练模式net.train()running_loss = 0.0# 使用 tqdm 创建一个进度条以显示训练进度# tqdm 用于包装 train_loader,使其在终端中显示一个进度条,以便用户可以实时查看训练进度。file=sys.stdout 参数将进度条输出到终端。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()# 累积损失值running_loss += loss.item()train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,epochs,loss)

4.测试模型

每训练完一个epoch,就进行一次测试,并将比较准确率大小,并根据当前模型的准确率保存模型,如果当前准确率优于之前的最佳准确率,则保存当前模型参数;否则,不变。

        # 将神经网络设置为评估模式net.eval()acc = 0.0    # 准确率# 在评估模式下不进行梯度计算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_numprint('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %(epoch + 1, running_loss / train_steps, val_accurate))# 如果当前模型的验证准确率优于之前的最佳准确率,则保存当前模型参数if val_accurate > best_acc:best_acc = val_accuratetorch.save(net.state_dict(), save_path)

三、实现图像分类

利用上述训练好的网络模型进行测试,验证是否能完成分类任务。

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))])# 加载图片img_path = 'tulips.jpg'assert os.path.exists(img_path), "file: '{}' does not exist.".format(img_path)image = Image.open(img_path)# img.show()image.show()# [N, C, H, W]img = data_transform(image)# 扩展维度img = torch.unsqueeze(img, dim=0)# 获取标签json_path = 'class_indices.json'assert os.path.exists(json_path), "file: '{}' does not exist.".format(json_path)with open(json_path, 'r') as f:# 使用json.load()函数加载JSON文件的内容并将其存储在一个Python字典中class_indict = json.load(f)# 加载网络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():# 对输入图像进行预测output = torch.squeeze(model(img.to(device))).cpu()# 对模型的输出进行 softmax 操作,将输出转换为类别概率predict = torch.softmax(output, dim=0)# 得到高概率的类别的索引predict_cla = torch.argmax(predict).numpy()res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())draw = ImageDraw.Draw(image)# 文本的左上角位置position = (10, 10)# fill 指定文本颜色draw.text(position, res, fill='red')image.show()for i in range(len(predict)):print("class: {:10}   prob: {:.3}".format(class_indict[str(i)], predict[i].numpy()))

预测结果:

 

结束语

感谢阅读吾之文章,今已至此次旅程之终站 🛬。

吾望斯文献能供尔以宝贵之信息与知识也 🎉。

学习者之途,若藏于天际之星辰🍥,吾等皆当努力熠熠生辉,持续前行。

然而,如若斯文献有益于尔,何不以三连为礼?点赞、留言、收藏 - 此等皆以证尔对作者之支持与鼓励也 💞。

愿尔之学习之路风平浪静,充满希望💐。再次感谢尔之阅读与关注,吾期望再次相见!


http://www.ppmy.cn/news/1124505.html

相关文章

数据结构与算法之集合: Leetcode 349. 两个数组的交集 (Typescript版)

两个数组的交集 https://leetcode.cn/problems/intersection-of-two-arrays/description/ 描述 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1 输入:nums1 [1,2,…

LRU、LFU 内存淘汰算法的设计与实现

1、背景介绍 LRU、LFU都是内存管理淘汰算法,内存管理是计算机技术中重要的一环,也是多数操作系统中必备的模块。应用场景:假设 给定你一定内存空间,需要你维护一些缓存数据,LRU、LFU就是在内存已经满了的情况下&#…

word怎么删除空白页?图文详解来了(3种方法)

word是最常用的文档编辑工具之一,但有时在编辑文档时,我们会遇到一个常见问题,那就是不小心插入了空白页。这些空白页可能会破坏文档的布局,使其看起来不专业。那word怎么删除空白页呢?幸运的是,word提供了…

mmm高可用

这里写目录标题 mmm高可用一.介绍1.关于 MMM 高可用架构的说明如下: 二.配置实现1.搭建 MySQL 多主多从模式2.安装配置 MySQL-MMM mmm高可用 一.介绍 MMM(Master-Master replication manager for MvSQL,MySQL主主复制管理器) 是…

汽车级应用MS913/914 25-100MHz 10/12 位用于平面显示器链路Ⅲ的具有直流平衡编码和双向控制通道的串化器和解串器

MS913/MS914 芯片组是 25MHz~100MHz 10 位/12 位 FPD Link III SER/DES(串化器/解串器),它提供高速 FPD-Link III 接口和高速正向通路以及用于差分对上数据发送的双向 控制通路。广泛应用于车载摄像,医疗设备,管道探测等领 域。 主要特点 …

maven中央仓库地址

aliyun仓库 <mirror><id>aliyunmaven</id><mirrorOf>*</mirrorOf><name>阿里云公共仓库</name><url>https://maven.aliyun.com/repository/public</url></mirror>maven中央仓库 <mirror><id>maven-de…

MTK联发科MT6853和MT6873安卓核心板性能参数对比

联发科MTK6853芯片&#xff0c;又名天玑720&#xff0c;是一款具有先天的节能创新的5G芯片。天玑800&#xff08;MTK6873&#xff09;和天玑720二款处理器都是使用于中高端手机中&#xff0c;那么天玑720和天玑800处理器到底哪个更好呢&#xff1f; 1、处理器性能对比 天玑7…

Crypto:MD5

题目 下载了题目给的压缩包解压后&#xff0c;打开文件 使用md5解码器解码后得到&#xff0c;即为flag