图像分类任务-猫狗大战(计算机视觉领域区分猫和狗照片)
相关:
https://blog.csdn.net/weixin_74009895/article/details/143255069
代码:
#第一部分:导包
# 导入必要的库
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets, transforms, models
import numpy as np
import matplotlib.pyplot as plt
from tqdm import *
from torchinfo import summary
import os
import sys
from PIL import Image # pip install Pillow
#第二部分:加载数据集
# 原始数据路径
data_path = "data/dogs-vs-cats/"
# 指定训练数据集的路径并获取该路径下所有文件的文件名列表
train_folder = data_path + 'train/'
train_filelist = os.listdir(train_folder)
# 输出训练数据集的文件数量和前20个文件名
print("train: {0} {1} \n".format(len(train_filelist), train_filelist[:20]))
# 指定测试数据集的路径并获取该路径下所有文件的文件名列表
test_folder = data_path + 'test/'
test_filelist = os.listdir(test_folder)
# 输出测试数据集的文件数量和前20个文件名
print("test: {0} {1} \n".format(len(test_filelist), test_filelist[:20]))
#第三部分;自定义数据集
# 自定义数据集
class DogsVSCats(Dataset):
def __init__(self, data_dir, split="train", transform=None):
# 拼接数据集中所有图片路径并计算总数
imgs = [os.path.join(data_dir, img) for img in os.listdir(data_dir)]
imgs_num = len(imgs)
# 根据数据集划分(train/val/test),选择不同数据
# 训练集:验证集设为8:2
if split == 'train':
self.image_paths = imgs[:int(0.8 * imgs_num)]
elif split == 'val':
self.image_paths = imgs[int(0.8 * imgs_num):]
else:
self.image_paths = imgs
self.split = split
# 根据数据集划分(train/val/test)定义默认数据转换方法
if transform is None:
if split == 'train':
self.transform = transforms.Compose([
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomRotation(10), # 随机旋转
transforms.Resize((256, 256)), # 调整图像大小
transforms.RandomCrop(224), # 从随机位置裁剪指定大小
transforms.ToTensor(), # 将数据转换为张量
# 对三通道数据进行归一化(均值,标准差),数值是从 ImageNet 数据集的百万张图片中随机抽样计算得到
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
else:
self.transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.ToTensor(), # 将数据转换为张量
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 归一化
])
else:
self.transform = transform
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
# 加载图像数据并对图像数据进行转换
image = Image.open(self.image_paths[idx]).convert('RGB')
image = self.transform(image)
# 从图片路径中提取标签信息并转换为张量数据
filename = self.image_paths[idx].split("/")[-1]
if self.split == 'test':
label = int(filename.split('.')[0])
else:
label = 1 if 'dog' in filename else 0
label = torch.tensor(label, dtype=torch.long)
return image, label
#第四部分:验证数据集是否可用
dataset = DogsVSCats(
'data/dogs-vs-cats/train/',
transform=transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
)
#第五部分:定义一个根据数据集的绘图函数
# 定义绘图函数,传入dataset即可
def plot(dataset, shuffle=False, cmap=None):
# 创建数据加载器
dataloader = DataLoader(dataset, batch_size=16, shuffle=shuffle)
# 取出一组数据
images, labels = next(iter(dataloader))
# 将通道维度(C)移动到最后一个维度,方便使用matplotlib绘图
images = np.transpose(images, (0, 2, 3, 1))
# 创建2x8的子图对象
fig, axes = plt.subplots(nrows=4, ncols=4, figsize=(8, 8))
# 遍历每个子图,绘制图像并添加子图标题
for i, ax in enumerate(axes.flat):
ax.imshow(images[i], cmap=cmap)
ax.axis('off') # 隐藏坐标轴
if hasattr(dataset, 'classes'): # 如果数据集有预定义的类别名称,使用该名称作为子图标题
ax.set_title(dataset.classes[labels[i]], fontsize=12)
else: # 否则使用类别索引作为子图标题
ax.set_title(labels[i], fontsize=12)
plt.show()
plot(dataset)
#第六部分:分割数据集
# 依次定义训练集、验证集、测试集
train_dataset = DogsVSCats('data/dogs-vs-cats/train/', split='train')
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
val_dataset = DogsVSCats('data/dogs-vs-cats/train/', split='val')
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=4)
test_dataset = DogsVSCats('data/dogs-vs-cats/test/', split='test')
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)
# 查看数据集样本数量
print(len(train_dataset))
print(len(val_dataset))
print(len(test_dataset))
#第七部分:定义 ResNet50 模型
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
# 获取全连接层的输入特征数
num_features = model.fc.in_features
# 将全连接层替换成一个新的全连接层
model.fc = nn.Linear(in_features=num_features, out_features=2, bias=True)
#第八部分:模型训练
class Trainer:
def __init__(self, model, train_loader, val_loader):
# 初始化训练数据集和验证数据集的dataloader
self.train_loader = train_loader
self.val_loader = val_loader
# 判断用的设备是 CPU 还是 GPU,并将模型移动到对应的计算资源设备上
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = model.to(self.device)
# 定义优化器、损失函数学习率调整器
self.optimizer = optim.SGD(self.model.parameters(), lr=0.001)
self.criterion = nn.CrossEntropyLoss()
self.scheduler = optim.lr_scheduler.ExponentialLR(self.optimizer, gamma=0.95)
# 记录训练过程中损失和验证过程中的准确率
self.train_losses = []
self.val_accuracy = []
def train(self, num_epochs):
# tqdm用于显示进度条并评估任务的时间开销
for epoch in tqdm(range(num_epochs), file=sys.stdout):
# 记录损失值
total_loss = 0
# 批量训练
self.model.train()
for images, labels in self.train_loader:
# 将数据转移到指定计算资源设备上
images = images.to(self.device)
labels = labels.to(self.device)
# 预测、损失函数、反向传播
self.optimizer.zero_grad()
outputs = self.model(images)
loss = self.criterion(outputs, labels)
loss.backward()
self.optimizer.step()
# 记录训练集loss
total_loss += loss.item()
# 更新优化器的学习率
self.scheduler.step()
# 计算验证集的准确率
accuracy = self.validate()
# 记录训练集损失和验证集准确率
self.train_losses.append(np.log10(total_loss)) # 由于数值有时较大,这里取对数
self.val_accuracy.append(accuracy)
# 打印中间值
if epoch % 5 == 0:
tqdm.write("Epoch: {0} Loss: {1} Acc: {2}".format(
epoch, self.train_losses[-1], self.val_accuracy[-1]))
def validate(self):
# 测试模型,不计算梯度
self.model.eval()
total = 0
correct = 0
with torch.no_grad():
for images, labels in self.val_loader:
# 将数据转移到指定计算资源设备上
images = images.to(self.device)
labels = labels.to(self.device)
# 预测
outputs = self.model(images)
# 记录验证集总数和预测正确数量
total += labels.size(0)
correct += (outputs.argmax(1) == labels).sum().item()
# 返回准确率
accuracy = correct / total
return accuracy
# 创建一个 Trainer 类的实例
trainer = Trainer(model, train_loader, val_loader)
# 训练模型,迭代 20 个周期
trainer.train(num_epochs=20)
#第九部分:输出结果并绘图
# 最终准确率
print("准确率为:",trainer.val_accuracy[-1])
# 使用Matplotlib绘制损失和准确率的曲线图
plt.plot(trainer.train_losses, label='loss')
plt.plot(trainer.val_accuracy, label='accuracy')
plt.legend()
plt.show()
#第十部分:模型预测
def inference(trainer, test_loader):
# 设置模型为评估模式
trainer.model.eval()
# 记录测试样本的id和模型预测结果
ids = []
predictions = []
with torch.no_grad():
# 遍历测试集,这里的labels其实是id
for images, labels in test_loader:
# 将数据转移到指定计算资源设备上
images = images.to(trainer.device)
# 对图片进行预测
outputs = trainer.model(images)
# 获取预测结果,torch.max函数返回值中的最大值项
_, predicted = torch.max(outputs.data, 1)
# 将测试样本的 ID 添加到列表中
ids.extend(labels.numpy().tolist())
# 将预测结果添加到列表中
predictions.extend(predicted.cpu().numpy().tolist())
return ids, predictions
# 对测试集进行预测,获得预测结果
ids, predictions = inference(trainer, test_loader)
# 输出前 20 个样本的预测结果
predictions[:20]
# [0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0]
# 在分类正确的情况下,0.995和1.0的评估差异不大
print(F.binary_cross_entropy(torch.tensor([1.0]), torch.tensor([1.0])))
print(F.binary_cross_entropy(torch.tensor([0.995]), torch.tensor([1.0])))
#第十一部分:保存训练好的模型
# 保存整个模型
torch.save(model, 'resnet50_model.pth')
本博客是博主个人学习时的一些记录,不保证是为原创,个别文章加入了转载的源地址,还有个别文章是汇总网上多份资料所成,在这之中也必有疏漏未加标注处,如有侵权请与博主联系。
如果未特殊标注则为原创,遵循 CC 4.0 BY-SA 版权协议。
posted on 2026-03-05 22:56 Angry_Panda 阅读(11) 评论(0) 收藏 举报
浙公网安备 33010602011771号