Python----神经网络(基于Alex Net的花卉分类项目)
一、基于Alex Net的花卉分类
1.1、项目背景
在当今快速发展的科技领域,计算机视觉已成为一个备受关注的研究方向。随着深度学习技术的不断进步,图像识别技术得到了显著提升,广泛应用于医疗、安防、自动驾驶等多个领域。其中,花卉分类作为计算机视觉中的一个重要应用,不仅能帮助园艺师和农业生产者优化作物管理,还能在生态研究、植物保护等方面发挥重要作用。
传统的花卉分类方法依赖于人工特征提取和机器学习算法,虽然在特定条件下能够取得较好效果,但在面对复杂的自然环境和大量不同花卉品种时,仍显得力不从心。而深度学习,尤其是卷积神经网络(CNN),凭借其强大的特征学习能力和卓越的表现,逐渐取代了传统方法。其中,AlexNet作为第一款在大规模图像分类竞赛中获得突破性成果的深度学习模型,广泛应用于各类图像识别任务,受到高度重视。
1.2、项目目的
在本项目中,我们的目标是构建一个深度学习模型,能够有效地识别和分类花卉的图像。通过使用卷积神经网络(CNN),尤其是著名的AlexNet架构,我们希望实现高精度的图像分类。完成该项目后,用户能够方便地将图像上传至应用程序,并获得该图像是何种花卉的预测结果。
1.3、网络描述
AlexNet 是由Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在2012年提出的深度学习模型。它在ImageNet大规模视觉识别挑战赛中取得了显著的成功,显著提高了图像分类的准确率。该模型采用了多个卷积层、ReLU激活函数、全连接层和Dropout正则化方法,以有效防止过拟合。AlexNet的成功不仅证明了深度学习在计算机视觉领域的潜力,还推动了后续更多深度学习模型的研究和应用。
1.4、数据集
examples = enumerate(val_loader)
batch_idx, (imgs, labels) = next(examples)
for i in range(4):mean = np.array([0.5, 0.5, 0.5])std = np.array([0.5, 0.5, 0.5])image = imgs[i].numpy() * std[:, None, None] + mean[:, None, None]# 将图片转成numpy数组,主要是转换通道和宽高位置image = np.transpose(image, (1, 2, 0))plt.subplot(2, 2, i+1)plt.imshow(image)plt.title(f"Truth: {labels[i]}")
plt.show()
二、设计思路
import os
import random
import numpy as np
import torch
from torch import nn
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
2.1、构建随机种子
# 设置随机种子以保证结果的可重复性
def setup_seed(seed):np.random.seed(seed) # 设置 Numpy 随机种子random.seed(seed) # 设置 Python 内置随机种子os.environ['PYTHONHASHSEED'] = str(seed) # 设置 Python 哈希种子torch.manual_seed(seed) # 设置 PyTorch 随机种子if torch.cuda.is_available():torch.cuda.manual_seed(seed) # 设置 CUDA 随机种子torch.cuda.manual_seed_all(seed)torch.backends.cudnn.benchmark = False # 关闭 cudnn 加速torch.backends.cudnn.deterministic = True # 设置 cudnn 为确定性算法
# 设置随机种子
setup_seed(0)
2.2、检测显卡还是cpu
# 检查是否有可用的 GPU,如果有则使用 GPU,否则使用 CPU
if torch.cuda.is_available():device = torch.device("cuda") # 使用 GPUprint("CUDA is available. Using GPU.")
else:device = torch.device("cpu") # 使用 CPUprint("CUDA is not available. Using CPU.")
2.3、数据预处理和加载数据集
transform={'train':transforms.Compose([transforms.RandomResizedCrop(244),transforms.RandomVerticalFlip(p=0.5),transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]),'val':transforms.Compose([transforms.Resize((224,224)),transforms.RandomVerticalFlip(p=0.5),transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
}
train_loader=DataLoader(train_dataset,batch_size=32,shuffle=True)
val_loader=DataLoader(val_dataset,batch_size=32,shuffle=False)
2.4、构建Alex Net网络
class AlexNet(nn.Module):def __init__(self, num_classes=1000):super(AlexNet, self).__init__()self.features = nn.Sequential(nn.Conv2d(3, 96, 11, 4, 2),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2),nn.Conv2d(96, 256, 5, 1, 2),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2),nn.Conv2d(256, 384, 3, 1, 1),nn.ReLU(inplace=True),nn.Conv2d(384, 384, 3, 1, 1),nn.ReLU(inplace=True),nn.Conv2d(384, 256, 3, 1, 1),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2),)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),)def forward(self, x):x = self.features(x)x = torch.flatten(x, 1)x = self.classifier(x)return x
model=AlexNet().to(device)
2.5、损失函数优化器
cri=torch.nn.CrossEntropyLoss()
optim=torch.optim.Adam(model.parameters(),lr=0.001)
2.6、训练模型
epoches = 100
for epoch in range(epoches):model.train()total_loss = 0for i, (images, labels) in enumerate(train_loader):# 数据放在设备上images = images.to(device)labels = labels.to(device)# 前向传播outputs = model(images)loss = cri(outputs, labels)# 反向传播optim.zero_grad()loss.backward()optim.step()total_loss += lossprint(f"Epoch [{epoch + 1}/{epoches}], Iter [{i}/{len(train_loader)}], Loss {loss:.4f}")avg_loss = total_loss / len(train_loader)print(f"Epoch [{epoch + 1}/{epoches}], Loss {avg_loss:.4f}")if (epoch+1) % 10 == 0:torch.save(model.state_dict(), f"./model/model_{epoch}.pth")
2.7、验证模型
model.load_state_dict(torch.load("model_best.pth"))
model.eval()
total = 0
correct = 0
predicted_labels = []
true_labels = []
with torch.no_grad():for images, labels in val_loader:images = images.to(device)labels = labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()predicted_labels.extend(predicted.cpu().numpy())true_labels.extend(labels.cpu().numpy())print(f"ACC {correct / total * 100}%")# 生成混淆矩阵
conf = confusion_matrix(true_labels, predicted_labels)
# 可视化
sns.heatmap(conf, annot=True, fmt="d", cmap="Blues")
plt.xlabel("predict")
plt.ylabel("true")
plt.show()
2.8、完整代码
import os
import random
import numpy as np
import torch
from torch import nn
from torchvision import datasets,transforms
from torch.utils.data import DataLoader# 设置随机种子,保证实验的可重复性
def setup_seed(seed):np.random.seed(seed)random.seed(seed)os.environ['PYTHONHASHSEED']=str(seed)torch.manual_seed(seed)if torch.cuda.is_available():torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.benchmark=False # 关闭cudnn的benchmark模式,保证每次运行结果一致torch.backends.cudnn.deterministic=True # 使cudnn的卷积计算具有确定性setup_seed(42) # 设置随机种子为42# 判断是否有CUDA可用,并设置设备
if torch.cuda.is_available():device=torch.device('cuda')print('cuda is available')
else:device=torch.device('cpu')print('cuda is not available')# 定义数据预处理的转换
transform={'train':transforms.Compose([transforms.RandomResizedCrop(224), # 随机裁剪到224x224transforms.RandomHorizontalFlip(p=0.5), # 以0.5的概率进行水平翻转transforms.ToTensor(), # 将PIL图像或NumPy数组转换为Tensortransforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 对图像进行标准化,均值为0.5,标准差为0.5]),'val':transforms.Compose([transforms.Resize((224,224)), # 调整图像大小到224x224transforms.RandomHorizontalFlip(p=0.5), # 以0.5的概率进行水平翻转transforms.ToTensor(), # 将PIL图像或NumPy数组转换为Tensortransforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 对图像进行标准化,均值为0.5,标准差为0.5])
}# 加载训练数据集
train_dataset=datasets.ImageFolder('./flower_data/train', # 训练数据文件夹路径transform=transform['train'] # 应用训练集的数据预处理
)# 加载验证数据集
val_dataset=datasets.ImageFolder('./flower_data/val', # 验证数据文件夹路径transform=transform['val'] # 应用验证集的数据预处理
)# 创建训练数据加载器
train_loader=DataLoader(train_dataset,batch_size=32,shuffle=True) # 批量大小为32,打乱数据# 创建验证数据加载器
val_loader=DataLoader(val_dataset,batch_size=32,shuffle=False) # 批量大小为32,不打乱数据# 定义AlexNet模型
class AlexNet(nn.Module):def __init__(self,num_classes=1000): # num_classes为分类的类别数,默认为1000super().__init__()self.features=nn.Sequential(nn.Conv2d(3,48*2,kernel_size=11,stride=4,padding=2), # 卷积层1:输入通道3,输出通道96,卷积核11x11,步长4,填充2nn.ReLU(inplace=True), # ReLU激活函数,inplace=True表示直接修改输入nn.MaxPool2d(kernel_size=3,stride=2), # 最大池化层1:池化窗口3x3,步长2nn.Conv2d(48*2,128*2,kernel_size=5,padding=2), # 卷积层2:输入通道96,输出通道256,卷积核5x5,填充2nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3,stride=2), # 最大池化层2:池化窗口3x3,步长2nn.Conv2d(128*2,192*2,kernel_size=3,padding=1), # 卷积层3:输入通道256,输出通道384,卷积核3x3,填充1nn.ReLU(inplace=True),nn.Conv2d(192*2,192*2,kernel_size=3,padding=1), # 卷积层4:输入通道384,输出通道384,卷积核3x3,填充1nn.ReLU(inplace=True),nn.Conv2d(192*2,128*2,kernel_size=3,padding=1), # 卷积层5:输入通道384,输出通道256,卷积核3x3,填充1nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3,stride=2) # 最大池化层3:池化窗口3x3,步长2)self.classifier=nn.Sequential(nn.Dropout(p=0.5), # Dropout层1:丢弃概率为0.5nn.Linear(128*2*6*6,2048*2), # 全连接层1:输入特征数量为最后一个卷积层的输出大小,输出特征数量为4096nn.ReLU(inplace=True),nn.Dropout(p=0.5), # Dropout层2:丢弃概率为0.5nn.Linear(4096,4096), # 全连接层2:输入特征数量为4096,输出特征数量为4096nn.ReLU(inplace=True),nn.Linear(4096,num_classes) # 全连接层3:输出层,输出特征数量为类别数)def forward(self,x):x=self.features(x) # 通过特征提取层x=torch.flatten(x,1) # 将多维特征展平成一维x=self.classifier(x) # 通过分类器层return x# 实例化AlexNet模型并将其移动到指定的设备上
model=AlexNet(num_classes=len(train_dataset.classes)).to(device) # 根据训练集类别数设置输出类别数# 定义损失函数为交叉熵损失
cri=torch.nn.CrossEntropyLoss()# 定义优化器为Adam优化器,学习率为0.001
optim=torch.optim.Adam(model.parameters(),lr=0.001)# 设置训练的轮数
epoches=200# 开始训练循环
for epoch in range(epoches):most_acc = 0 # 初始化最佳验证集准确率model.train() # 设置模型为训练模式total_loss = 0 # 初始化训练总损失# 遍历训练数据加载器for i, (images, labels) in enumerate(train_loader):# 将数据移动到指定的设备上images = images.to(device)labels = labels.to(device)# 前向传播,计算模型输出outputs = model(images)# 计算损失loss = cri(outputs, labels)# 反向传播前清空梯度optim.zero_grad()# 反向传播,计算梯度loss.backward()# 更新模型参数optim.step()# 累加每个批次的损失total_loss += loss# 打印训练信息print(f"Epoch [{epoch + 1}/{epoches}], Iter [{i}/{len(train_loader)}], Loss {loss:.4f}")# 计算平均训练损失avg_loss = total_loss / len(train_loader)print(f"Train Data: Epoch [{epoch + 1}/{epoches}], Loss {avg_loss:.4f}")# 进入评估模式model.eval()total, correct, test_loss, total_loss= 0, 0, 0, 0# 在验证集上进行评估,禁用梯度计算with torch.no_grad():# 遍历验证数据加载器for images, labels in val_loader:# 将数据移动到指定的设备上images = images.to(device)labels = labels.to(device)# 前向传播,计算模型输出outputs = model(images)# 计算验证损失test_loss = cri(outputs, labels)# 累加每个批次的验证损失total_loss += test_loss# 获取预测结果_, predicted = torch.max(outputs.data, 1)# 统计总样本数total += labels.size(0)# 统计预测正确的样本数correct += (predicted == labels).sum().item()# 计算平均验证损失avg_test_loss = total_loss / len(val_loader)# 计算验证准确率acc = correct / totalprint(f"Test Data: Epoch [{epoch+1}/{epoches}], Loss {avg_test_loss:.4f}, Accuracy {acc * 100}%")# 如果当前验证准确率是最高的,则保存模型if acc > most_acc:torch.save(model.state_dict(), f"model_best.pth")most_acc = acc# 每隔10个epoch保存一次模型if (epoch+1) % 10 == 0:torch.save(model.state_dict(), f"model_{epoch+1}.pth")