Python----神经网络(基于AlexNet的猫狗分类项目)

一、基于AlexNet的猫狗分类

1.1、项目背景

猫和狗是我们生活中最常见的宠物,它们的图像数据大量存在于互联网上。对此进行分类不仅可以帮助开发自动化宠物识别应用,也可以应用于更广泛的计算机视觉领域。例如,训练良好的模型可以支持流浪动物的识别和归类、动物行为监测、虚拟宠物应用等方面。通过本项目,我们希望展现深度学习在实际应用中的有效性,以及其对社会的正面影响。

1.2、项目目的

在本项目中,我们的目标是构建一个深度学习模型,能够有效地识别和分类猫和狗的图像。通过使用卷积神经网络(CNN),尤其是著名的AlexNet架构,我们希望实现高精度的图像分类。完成该项目后,用户能够方便地将图像上传至应用程序,并获得该图像是猫还是狗的预测结果。

1.3、网络描述

AlexNet 是由Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在2012年提出的深度学习模型。它在ImageNet大规模视觉识别挑战赛中取得了显著的成功,显著提高了图像分类的准确率。该模型采用了多个卷积层、ReLU激活函数、全连接层和Dropout正则化方法,以有效防止过拟合。AlexNet的成功不仅证明了深度学习在计算机视觉领域的潜力,还推动了后续更多深度学习模型的研究和应用。

1.4、数据集

1.5、构建随机数种子

python 复制代码
import os
from random import random
import numpy as np
import torch

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 为确定性算法

1.6、 CUDA检测

python 复制代码
import torch
# 检查是否有可用的 GPU,如果有则使用 GPU,否则使用 CPU
if torch.cuda.is_available():
    device = torch.device("cuda")  # 使用 GPU
    print("CUDA is available. Using GPU.")
else:
    device = torch.device("cpu")  # 使用 CPU
    print("CUDA is not available. Using CPU.")

1.7、读取数据

python 复制代码
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
transform = {
    "train": transforms.Compose([transforms.RandomResizedCrop(224), transforms.ToTensor(),
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
    "test": transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(),
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
}

train_dataset = datasets.ImageFolder("./dataset/train", transform=transform["train"])
test_dataset = datasets.ImageFolder("./dataset/test", transform=transform["test"])

train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)

1.8、显示图像

python 复制代码
import matplotlib.pyplot as plt
import numpy as np
# 打印图片
examples = enumerate(test_dataloader)
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()

1.9、构建网络

python 复制代码
import torch
from torch import nn


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

1.10、损失函数和优化器

python 复制代码
cri = torch.nn.CrossEntropyLoss()
optomizer = torch.optim.Adam(model.parameters(), lr=0.0001)

1.11、训练模型

python 复制代码
epoches = 100
for epoch in range(epoches):
    model.train()
    total_loss = 0
    for i, (images, labels) in enumerate(train_dataloader):
        # 数据放在设备上
        images = images.to(device)
        labels = labels.to(device)

        # 前向传播
        outputs = model(images)
        loss = cri(outputs, labels)

        # 反向传播
        optomizer.zero_grad()
        loss.backward()
        optomizer.step()
        total_loss += loss
        print(f"Epoch [{epoch + 1}/{epoches}], Iter [{i}/{len(train_dataloader)}], Loss {loss:.4f}")
    avg_loss = total_loss / len(train_dataloader)
    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")

1.12、 验证模型

python 复制代码
print("开始验证/评估模型:")
model.load_state_dict(torch.load("./model_best.pth",weights_only=True))
model.eval()
total = 0
correct = 0
predicted_labels = []
true_labels = []
with torch.no_grad():
    for images, labels in test_dataloader:
        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()

1.13、完整代码

python 复制代码
import os
import random
import numpy as np
import torch
from torch import nn  # 导入神经网络模块
from torch.utils.data import DataLoader
from torchvision import datasets, transforms


# 设置随机种子以保证结果的可重复性
def setup_seed(seed):
    np.random.seed(seed)  # 设置 Numpy 随机种子,用于控制 Numpy 相关的随机操作
    random.seed(seed)  # 设置 Python 内置随机种子,用于控制 Python 内置的随机操作
    os.environ['PYTHONHASHSEED'] = str(seed)  # 设置 Python 哈希种子,用于控制哈希操作的随机性,保证实验的可复现性
    torch.manual_seed(seed)  # 设置 PyTorch 的 CPU 随机种子,用于控制 PyTorch 中 CPU 相关的随机操作
    if torch.cuda.is_available():  # 检查是否有可用的 CUDA (GPU)
        torch.cuda.manual_seed(seed)  # 设置当前 GPU 的随机种子
        torch.cuda.manual_seed_all(seed)  # 设置所有 GPU 的随机种子(如果有多个 GPU)
        torch.backends.cudnn.benchmark = False  # 关闭 cuDNN 的 benchmark 模式。benchmark 模式会自动寻找适合当前配置的高效算法,但可能牺牲可重复性
        torch.backends.cudnn.deterministic = True  # 设置 cuDNN 使用确定性算法,保证在相同的输入和权重下,输出是相同的,进一步提高可重复性


# 设置随机种子
setup_seed(0)
# 检查是否有可用的 GPU,如果有则使用 GPU,否则使用 CPU
if torch.cuda.is_available():
    device = torch.device("cuda")  # 将设备设置为 CUDA (GPU)
    print("CUDA is available. Using GPU.")
else:
    device = torch.device("cpu")  # 将设备设置为 CPU
    print("CUDA is not available. Using CPU.")


# 定义数据预处理的转换
transform = {
    "train": transforms.Compose([  # 训练集的数据预处理
        transforms.RandomResizedCrop(224),  # 随机裁剪图片到 224x224 的大小,并进行缩放
        transforms.ToTensor(),  # 将 PIL 图像或 NumPy 数组转换为 PyTorch 张量,并将像素值缩放到 [0, 1]
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 对图像的每个通道进行标准化,使其均值为 0.5,标准差为 0.5
    ]),
    "test": transforms.Compose([  # 测试集的数据预处理
        transforms.Resize((224, 224)),  # 将图片缩放至 224x224 的大小
        transforms.ToTensor(),  # 将 PIL 图像或 NumPy 数组转换为 PyTorch 张量,并将像素值缩放到 [0, 1]
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 对图像的每个通道进行标准化,使其均值为 0.5,标准差为 0.5
    ]),
}

# 加载训练数据集,"./dataset/train" 是训练集图片所在的文件夹,transform 指定了数据预处理方式
train_dataset = datasets.ImageFolder("./dataset/train", transform=transform["train"])
# 加载测试数据集,"./dataset/test" 是测试集图片所在的文件夹,transform 指定了数据预处理方式
test_dataset = datasets.ImageFolder("./dataset/test", transform=transform["test"])

# 创建训练数据的数据加载器,batch_size 定义了每个批次的大小,shuffle=True 表示在每个 epoch 开始时打乱数据
train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# 创建测试数据的数据加载器,batch_size 定义了每个批次的大小,shuffle=False 表示不打乱数据
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)


# 定义 AlexNet 模型结构
class AlexNet(nn.Module):
    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        # 特征提取层
        self.features = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),  # 卷积层1:输入通道3,输出通道96,卷积核11x11,步长4,填充2
            nn.ReLU(inplace=True),  # ReLU 激活函数,inplace=True 表示直接修改输入
            nn.MaxPool2d(kernel_size=3, stride=2),  # 最大池化层1:池化窗口3x3,步长2
            nn.Conv2d(96, 256, kernel_size=5, padding=2),  # 卷积层2:输入通道96,输出通道256,卷积核5x5,填充2
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),  # 最大池化层2:池化窗口3x3,步长2
            nn.Conv2d(256, 384, kernel_size=3, padding=1),  # 卷积层3:输入通道256,输出通道384,卷积核3x3,填充1
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 384, kernel_size=3, padding=1),  # 卷积层4:输入通道384,输出通道384,卷积核3x3,填充1
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),  # 卷积层5:输入通道384,输出通道256,卷积核3x3,填充1
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),  # 最大池化层3:池化窗口3x3,步长2
        )
        # 分类器
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),  # Dropout 层1:以 50% 的概率随机将一部分神经元的输出置为 0,防止过拟合
            nn.Linear(256 * 6 * 6, 4096),  # 全连接层1:输入特征数 256 * 6 * 6,输出特征数 4096
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),  # Dropout 层2
            nn.Linear(4096, 4096),  # 全连接层2:输入特征数 4096,输出特征数 4096
            nn.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 模型,num_classes=2 表示分类的类别数为 2,并将模型移动到指定的设备 (GPU 或 CPU) 上
model = AlexNet(num_classes=2).to(device)
# 定义损失函数为交叉熵损失,常用于多分类任务
cri = torch.nn.CrossEntropyLoss()
# 定义优化器为 Adam 优化器,model.parameters() 指定了需要优化的模型参数,lr=0.0001 是学习率
optomizer = torch.optim.Adam(model.parameters(), lr=0.0001)

# 定义训练的 epoch 数
epoches = 100
# 开始训练循环
for epoch in range(epoches):
    most_acc = 0  # 初始化最佳的测试集准确率
    model.train()  # 将模型设置为训练模式,启用 dropout 和 batch normalization
    total_loss = 0  # 初始化每个 epoch 的总损失
    # 遍历训练数据加载器中的每个批次
    for i, (images, labels) in enumerate(train_dataloader):
        # 将图像和标签移动到指定的设备 (GPU 或 CPU) 上
        images = images.to(device)
        labels = labels.to(device)
        # 前向传播:将图像输入模型,得到模型的输出
        outputs = model(images)
        # 计算损失:将模型的输出和真实的标签输入损失函数,得到损失值
        loss = cri(outputs, labels)
        # 反向传播之前将优化器的梯度清零
        optomizer.zero_grad()
        # 反向传播:计算损失相对于模型参数的梯度
        loss.backward()
        # 更新模型参数:根据计算得到的梯度和优化器的规则更新模型参数
        optomizer.step()
        # 累加每个批次的损失值
        total_loss += loss
        # 打印训练过程中的信息,包括当前的 epoch、迭代次数和当前的损失值
        print(f"Epoch [{epoch + 1}/{epoches}], Iter [{i}/{len(train_dataloader)}], Loss {loss:.4f}")
    # 计算一个 epoch 的平均训练损失
    avg_loss = total_loss / len(train_dataloader)
    # 打印每个 epoch 的平均训练损失
    print(f"Train Data: Epoch [{epoch + 1}/{epoches}], Loss {avg_loss:.4f}")

    # 进入模型评估模式,禁用 dropout 和 batch normalization
    model.eval()
    total, correct, test_loss, total_loss = 0, 0, 0, 0  # 初始化测试过程中的总样本数、正确样本数、批次损失和总损失
    # 在评估模式下,不计算梯度
    with torch.no_grad():
        # 遍历测试数据加载器中的每个批次
        for images, labels in test_dataloader:
            # 将图像和标签移动到指定的设备 (GPU 或 CPU) 上
            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(test_dataloader)
    # 计算测试集的准确率
    acc = correct / total
    # 打印每个 epoch 的测试损失和准确率
    print(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/model_best.pth")
        most_acc = acc
    # 每隔 10 个 epoch 保存一次模型的权重
    if (epoch+1) % 10 == 0:
        torch.save(model.state_dict(), f"./model/model_{epoch+1}.pth")
相关推荐
QUST-Learn3D7 分钟前
OpenCV提取图像中的暗斑/亮斑
人工智能·opencv·计算机视觉
小雅痞11 分钟前
[Java][Leetcode middle] 80. 删除有序数组中的重复项 II
java·python·leetcode
大叔_爱编程13 分钟前
p020基于Django的4S店客户管理系统
vue.js·python·django·毕业设计·源码·课程设计·4s店客户管理系统
小Tomkk21 分钟前
2025年5月15日前 免费考试了! Oracle AI 矢量搜索专业认证
数据库·人工智能·oracle
多巴胺与内啡肽.28 分钟前
OpenCV进阶操作:指纹验证、识别
人工智能·opencv·计算机视觉
yorushika_36 分钟前
python打卡训练营打卡记录day22
开发语言·python·机器学习
代码的乐趣42 分钟前
支持selenium的chrome driver更新到136.0.7103.92
chrome·python·selenium
wzx_Eleven42 分钟前
【论文阅读】Efficient and secure federated learning against backdoor attacks
论文阅读·人工智能·机器学习·云计算
带鱼工作室1 小时前
通义读光系列文字检测+识别模型端到端OCR应用
python·opencv·计算机视觉·ocr
安特尼1 小时前
招行数字金融挑战赛数据分析赛带赛题二
python·算法·机器学习·金融·数据分析