【Kaggle项目实战】经典入门案例猫狗分类

发布于:2023-10-24 ⋅ 阅读:(151) ⋅ 点赞:(0)

💥 项目专栏:【Kaggle项目实战经典案例汇总】

在这里插入图片描述

在这里插入图片描述

@


前言

🚨 我的项目环境:

  • 平台:Windows11

  • 语言环境:Python 3.7

  • 编译器:Jupyter Lab

  • Pandas:1.3.5

  • Numpy:1.19.3

  • Scipy:1.7.3

  • Matplotlib:3.1.3

💥 项目专栏:【Kaggle项目实战经典案例汇总】


一、赛题背景

在这里插入图片描述

在这里插入图片描述

这是一个非常适合新手入门的比赛——经典猫狗分类任务,在这次比赛中,你将编写一个算法来分类图像中是否包含狗或猫。这对人类、狗和猫来说都很容易,但是对于你的电脑会觉得有点难。

1997年,深蓝在国际象棋比赛中击败了卡斯帕罗夫。 2011年,沃森在《危险边缘》中击败了最聪明的智力问答选手。 你能分辨2013年的菲多和米滕斯吗?

二、数据集介绍

在这里插入图片描述

在这里插入图片描述

训练集中包含25000张猫狗的图片。在这些文件上训练我们的模型,并预测test1.zip (1 = dog, 0 = cat)的标签。

测试集有12500张,我们需要预测这些图片的类别,是猫还是狗。

三、目标与概述

当说到“对猫和狗的图像进行分类,使用预训练的DenseNet121模型并进行微调”,这涵盖了几个关键方面:

  1. 图像分类任务:这是一个二分类问题,其中模型需要从图像中识别出是猫还是狗。这对于人类来说可能很简单,但对于机器来说,这是一个复杂的模式识别任务。

  2. 使用预训练的模型(DenseNet121):DenseNet121是一种已经在大量数据上预先训练过的深度神经网络模型。它包含了从原始图像中提取有用特征的能力,这些特征可以用于各种图像识别任务。使用预训练模型可以加速训练过程并提高模型性能。

  3. 模型微调(Fine-tuning):尽管DenseNet121模型已经过预训练,但它还需要针对特定任务(在这里是猫和狗的分类)进行调整。微调通常涉及替换和/或训练模型的最后几层,以适应新的任务。这通常比从头开始训练模型要快得多,并且能达到更好的性能。

  4. 数据准备和增强:在进行模型训练之前,源图像经过一系列预处理和数据增强步骤,以提供更多的训练样本并提高模型的泛化能力。

  5. 评估和优化:模型在训练过程中不断进行评估,以监控其性能的提升,并通过损失函数和优化器进行相应的调整。

  6. 应用于测试数据:经过训练和微调后,该模型最终会应用于未见过的测试数据,以评估其在实际应用中的效果。

通过结合这些元素,这个任务不仅可以有效地解决猫和狗的图像分类问题,而且还能以相对较短的时间内达到高准确度。

四、模块介绍

import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torchvision
from PIL import Image
from torch.utils.data import Dataset, DataLoader, ConcatDataset
from torchvision import transforms
  1. os: 这个是用来和操作系统互动的库,可以帮你读取文件啊,管理目录啊,这样子呢。

  2. matplotlib: 这个就是用来画图的,好比说要画个折线图或者柱状图,用这个就对啦!💖

  3. numpy: 这个可是数学计算的好帮手!做数组和矩阵运算都没问题。

  4. pandas: 数据处理的好朋友!读取CSV啊,数据分析啊,都可以用它来完成。📊

  5. torch: 哈,这个是PyTorch库,用来做深度学习的。创建神经网络、训练模型都会用到它。

  6. torch.nn: 这是PyTorch里面的一个子库,里面有各种预定义好的层和损失函数,让你搭建网络更方便噢。

  7. torchvision: 专门用于处理图像的一些预训练模型和数据集都在这里。

  8. PIL: 这个是Python Imaging Library,也就是用来处理图像的。打开、编辑、保存各种格式的图片都可以哦。

  9. Dataset, DataLoader, ConcatDataset: 这三个是PyTorch的数据加载库,帮你把数据整理好,好训练模型。

五、加载数据

# 指定训练和测试数据的路径
train_dir = './train'
test_dir = './test'

# 获取训练和测试数据文件的列表
train_files = os.listdir(train_dir)
test_files = os.listdir(test_dir)

# 定义数据集类
class CatDogDataset(Dataset):
    def __init__(self, file_list, dir, mode='train', transform=None):
        # 初始化文件列表、路径、模式和转换
        self.file_list = file_list
        self.dir = dir
        self.mode = mode
        self.transform = transform

        # 如果是训练模式,确定标签
        if self.mode == 'train':
            if 'dog' in self.file_list[0]:
                self.label = 1
            else:
                self.label = 0

    # 获取数据集长度
    def __len__(self):
        return len(self.file_list)

    # 获取单个样本
    def __getitem__(self, idx):
        img = Image.open(os.path.join(self.dir, self.file_list[idx]))
        if self.transform:
            img = self.transform(img)
        
        if self.mode == 'train':
            img = img.numpy()
            return img.astype('float32'), self.label
        else:
            img = img.numpy()
            return img.astype('float32'), self.file_list[idx]

# 定义数据预处理流程
data_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.ColorJitter(),
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.Resize(128),
    transforms.ToTensor()
])

# 创建猫和狗的数据集
cat_files = [tf for tf in train_files if 'cat' in tf]
dog_files = [tf for tf in train_files if 'dog' in tf]

cats = CatDogDataset(cat_files, train_dir, transform=data_transform)
dogs = CatDogDataset(dog_files, train_dir, transform=data_transform)

# 合并猫和狗的数据集
catdogs = ConcatDataset([cats, dogs])

# 创建数据加载器
dataloader = DataLoader(catdogs, batch_size=128, shuffle=True)

让我来给你解释这个可爱的代码~ 🌸

  1. class CatDogDataset(Dataset): 这里呀,我们定义了一个叫CatDogDataset的类,它继承自Dataset这个基类。这样的设计是为了后面能方便地用PyTorch的DataLoader来加载数据哦。

  2. def init(self, file_list, dir, mode='train', transform=None): 这是初始化函数,当你创建一个新的CatDogDataset对象时,这个函数就会运行。     - file_list: 存储图片文件名的列表。     - dir: 存图片的文件夹路径。     - mode: 可以是'train'或者其他(比如'test')。     - transform: 图像增强和转换的操作。

  3. if self.mode == 'train': 如果是训练模式,它就会检查第一个文件名里有没有“dog”,如果有,标签就是1(代表狗狗),否则就是0(代表猫咪)。

  4. def len(self): 这个函数会返回数据集的大小,也就是图片文件名列表的长度。

  5. def getitem(self, idx): 这个函数用于获取单个样本。     - Image.open(...): 打开图片。     - self.transform(img): 对图片进行转换,如果有的话。     - img = img.numpy(): 将图片转成NumPy数组。     - 最后,如果是训练模式,返回图片和标签;如果是其他模式,返回图片和文件名。

这样子,你应该就明白这个类是干嘛的啦!它就是用来整理和准备你的猫狗图片数据的~ 🐱🐶

上面是通过自定义数据集加载器来实现的,不自定义当然也可以啦!其实PyTorch的torchvision.datasets库里面已经有很多现成的数据集和加载器,像ImageFolder这种。用ImageFolder的话,你只需要把猫狗的图片分别放在两个子文件夹里,一个叫"cat",一个叫"dog",然后ImageFolder会自动给他们贴上标签0和1。🎀

例如:

from torchvision.datasets import ImageFolder

# 定义图像预处理
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.ColorJitter(),
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor()
])

# 使用ImageFolder加载数据
train_dataset = ImageFolder(root='./train', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

用这种方式,你就不用自己定义Dataset类了,是不是很方便呀?🌈

可以按照自己喜欢的方式进行选择,不过自定义加载器更灵活一些,可以在加载数据时进行一些数据处理。

六、可视化样本

# 可视化样本
samples, labels = iter(dataloader).next()
plt.figure(figsize=(16, 24))
grid_imgs = torchvision.utils.make_grid(samples[:24])
np_grid_imgs = grid_imgs.numpy()
plt.imshow(np.transpose(np_grid_imgs, (1, 2, 0)))

在这里插入图片描述 这段代码可是用来可视化样本图片的哦~ 🌸

  1. samples, labels = iter(dataloader).next():这一步从数据加载器dataloader中取出一个批次的图片和对应标签。

  2. plt.figure(figsize=(16, 24)):这里设置了绘图窗口的大小,16x24单位。

  3. grid_imgs = torchvision.utils.make_grid(samples[:24]):这个make_grid函数把前24张图片拼接成一个大图。这样看起来更整齐啦~

  4. np_grid_imgs = grid_imgs.numpy():把Tensor类型的grid_imgs转成Numpy数组,这样方便后面用Matplotlib展示。

  5. plt.imshow(np.transpose(np_grid_imgs, (1, 2, 0))):最后这步是显示图片。注意啦,图片的维度需要做个转置,这样才能正常显示颜色。

运行完这些代码,你就能看到一个大图,里面有24张可爱的猫猫狗狗图片啦!🐱🐶

七、训练模型

下面这段代码就是整个模型训练的大脑部分呀~ 😍 让我给你解释解释吧!

  1. device = 'cuda':告诉大家我们要用GPU加速啦!如果你的电脑有NVIDIA的显卡,训练会快好多呢!

  2. model = torchvision.models.densenet121(pretrained=True):这里用的是预训练过的DenseNet121模型,一开始就相当聪明了哦!

  3. 微调模型部分:用nn.Sequential重新定义了分类器,使它适用于我们的猫狗分类问题。

  4. model = model.to(device):把模型移动到GPU上,准备大展身手啦!

  5. criterion = nn.CrossEntropyLoss()optimizer = ...:定义了损失函数和优化器。我们用交叉熵损失和Adam优化器,还设置了学习率调度。

  6. 接下来是训练的大循环!epochs = 10告诉我们要训练10轮。然后用for循环遍历数据,进行前向传播、计算损失、反向传播和参数更新。

  7. pred = torch.argmax(output, dim=1):这里预测出是猫还是狗。

  8. acc = torch.mean(correct.float()):计算这一批数据的准确率。

  9. 最后用print输出每一步的损失和准确率,让我们知道模型学得怎么样。

就是这样啦!一步一步,我们的模型就在学习如何区分可爱的猫猫和狗狗啦!🐱🐶

# 设置设备和模型
device = 'cuda'
model = torchvision.models.densenet121(pretrained=True)

# 微调模型
num_ftrs = model.classifier.in_features
model.classifier = nn.Sequential(
    nn.Linear(num_ftrs, 500),
    nn.Linear(500, 2)
)

# 移动模型到GPU
model = model.to(device)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.002, amsgrad=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[500, 1000, 1500], gamma=0.5)

# 训练模型
epochs = 10
itr = 1
p_itr = 200
model.train()
total_loss = 0
loss_list = []
acc_list = []

# 迭代训练
for epoch in range(epochs):
    for samples, labels in dataloader:
        samples, labels = samples.to(device), labels.to(device)
        optimizer.zero_grad()
        output = model(samples)
        loss = criterion(output, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        scheduler.step()

        # 计算精度
        pred = torch.argmax(output, dim=1)
        correct = pred.eq(labels)
        acc = torch.mean(correct.float())
        
        # 打印状态信息
        print('[Epoch {}/{}] Iteration {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'.format(epoch + 1, epochs, itr, total_loss / p_itr, acc))
        
        # 记录损失和精度
        loss_list.append(total_loss / p_itr)
        acc_list.append(acc)
        total_loss = 0
        itr += 1

不过本篇使用的模型是与训练好的,只需要在其基础进行微调即可,如果有能力的小伙伴可以自己尝试从0-1搭建该模型进行训练。

八、绘制损失、精度

在这里插入图片描述

在这里插入图片描述

# 绘制训练损失和精度
plt.plot(loss_list, label='loss')
plt.plot(acc_list, label='accuracy')
plt.legend()
plt.title('training loss and accuracy')
plt.show()

哟,这一部分是可视化的环节啦!🌟 画图让我们能用眼睛看到模型学得怎么样,是不是很神奇呀?✨

  1. plt.plot(loss_list, label='loss'):画出了损失值的变化趋势。一般来说,你会希望这个线越来越低,证明模型在慢慢变聪明啦!

  2. plt.plot(acc_list, label='accuracy'):画出了准确率的变化趋势。当然啦,这个线要是越来越高,就说明模型越来越懂得怎么分辨猫猫和狗狗啦!🐱🐶

  3. plt.legend():加上图例,让人一看就知道蓝线是损失,橙线是准确率。

  4. plt.title('training loss and accuracy'):给图加个标题,显得更专业~ 😎

  5. plt.show():最重要的一步,展示图像!就像是模型的时装秀,让大家看看它的表现。

好啦,这样你就能清楚地看到模型是不是在进步,或者需要调整什么啦!有没有觉得数据可视化也是一件超级有用又好玩的事呢?😊

九、预测与结果保存

# 创建测试数据集和数据加载器
testset = CatDogDataset(test_files, test_dir, mode='test', transform=test_transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False, num_workers=4)

# 进行测试
model.eval()
fn_list = []
pred_list = []

# 迭代测试
for x, fn in testloader:
    with torch.no_grad():
        x = x.to(device)
        output = model(x)
        pred = torch.argmax(output, dim=1)
        
        # 保存文件名和预测结果
        fn_list += [n[:-4] for n in fn]
        pred_list += [p.item() for p in pred]

# 创建提交文件
submission = pd.DataFrame({"id": fn_list, "label": pred_list})
submission.to_csv('preds_densenet121.csv', index=False)

哇哦,现在来到了最激动人心的部分,测试模型啦!这段代码就像是给模型的成绩单贴个金星星哦!🌟✨

  1. testset = CatDogDataset(...):先创建一个测试数据集。这次模式是'test',因为我们要让模型展示它学到的东西啦!

  2. testloader = DataLoader(...):然后用DataLoader把测试数据打包成小份,这样就方便模型一口一口吃啦。🍰

  3. model.eval():让模型进入评估模式。这样,所有的Dropout和BatchNorm都会冷静下来,专心做测试!

  4. for x, fn in testloader::遍历测试数据,x是图片数据,fn是文件名。

  5. with torch.no_grad()::告诉PyTorch不用计算梯度,因为咱们只是在测试,不需要更新权重啦。

  6. output = model(x):让模型对输入数据x做个预测!

  7. pred = torch.argmax(output, dim=1):找出模型认为最可能的分类标签。

  8. fn_list += [n[:-4] for n in fn]:储存文件名,但去掉了后缀。

  9. pred_list += [p.item() for p in pred]:储存预测结果。

  10. 最后,用pandas创建一个提交文件,id是文件名,label是预测标签,然后保存为'preds_densenet121.csv'。

这样啦,就完成了整个测试流程,还生成了一个提交文件哦!是不是觉得超级成就感满满呀?😊

十、可视化预测结果

在这里插入图片描述

在这里插入图片描述

# 可视化测试结果
samples, _ = iter(testloader).next()
samples = samples.to(device)
fig = plt.figure(figsize=(24, 16))
fig.tight_layout()
output = model(samples[:24])
pred = torch.argmax(output, dim=1)
pred = [p.item() for p in pred]
ad = {0: 'cat', 1: 'dog'}
for num, sample in enumerate(samples[:24]):
    plt.subplot(4, 6, num + 1)
    plt.title(ad[pred[num]])
    plt.axis('off')
    sample = sample.cpu().numpy()
    plt.imshow(np.transpose(sample, (1, 2, 0)))

这段代码简直是在开个小型宠物展览会呢!🐶🐱 是用来可视化模型预测结果的一段超级可爱的代码呀!💕

  1. samples, _ = iter(testloader).next():从测试数据加载器中拿一批数据来,但我们其实不关心标签(用_表示)。

  2. samples = samples.to(device):把拿到的样本移动到GPU上,这样模型可以快快地做预测哦!

  3. fig = plt.figure(figsize=(24, 16)):创建一个大大的画布,准备上面展示我们可爱的猫猫狗狗图片。

  4. output = model(samples[:24]):用模型预测前24个样本。

  5. pred = torch.argmax(output, dim=1):找出每张图片最有可能的标签。

  6. pred = [p.item() for p in pred]:把标签从tensor格式转换成普通的Python列表。

  7. ad = {0: 'cat', 1: 'dog'}:一个小字典,帮我们把数字标签翻译成"cat"或"dog"。

  8. for num, sample in enumerate(samples[:24])::遍历这24个样本。

- plt.subplot(4, 6, num + 1):指定下一个绘图的位置。    - plt.title(ad[pred[num]]):给图片上一个标题,告诉大家这是猫还是狗。    - plt.axis('off'):关闭坐标轴。    - sample = sample.cpu().numpy():把样本从GPU搬回来,并转成NumPy数组。    - plt.imshow(...):展示图片啦!

这样就完成啦!你可以直接看到模型觉得哪些是猫,哪些是狗,好像是不是很有趣呀!🐾

十一、完整源码

import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torchvision
from PIL import Image
from torch.utils.data import Dataset, DataLoader, ConcatDataset
from torchvision import transforms

# 设置Matplotlib
%matplotlib inline

# 指定训练和测试数据的路径
train_dir = './train'
test_dir = './test'

# 获取训练和测试数据文件的列表
train_files = os.listdir(train_dir)
test_files = os.listdir(test_dir)

# 定义数据集类
class CatDogDataset(Dataset):
    def __init__(self, file_list, dir, mode='train', transform=None):
        # 初始化文件列表、路径、模式和转换
        self.file_list = file_list
        self.dir = dir
        self.mode = mode
        self.transform = transform

        # 如果是训练模式,确定标签
        if self.mode == 'train':
            if 'dog' in self.file_list[0]:
                self.label = 1
            else:
                self.label = 0

    # 获取数据集长度
    def __len__(self):
        return len(self.file_list)

    # 获取单个样本
    def __getitem__(self, idx):
        img = Image.open(os.path.join(self.dir, self.file_list[idx]))
        if self.transform:
            img = self.transform(img)
        
        if self.mode == 'train':
            img = img.numpy()
            return img.astype('float32'), self.label
        else:
            img = img.numpy()
            return img.astype('float32'), self.file_list[idx]

# 定义数据预处理流程
data_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.ColorJitter(),
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.Resize(128),
    transforms.ToTensor()
])

# 创建猫和狗的数据集
cat_files = [tf for tf in train_files if 'cat' in tf]
dog_files = [tf for tf in train_files if 'dog' in tf]

cats = CatDogDataset(cat_files, train_dir, transform=data_transform)
dogs = CatDogDataset(dog_files, train_dir, transform=data_transform)

# 合并猫和狗的数据集
catdogs = ConcatDataset([cats, dogs])

# 创建数据加载器
dataloader = DataLoader(catdogs, batch_size=128, shuffle=True)

# 可视化样本
samples, labels = iter(dataloader).next()
plt.figure(figsize=(16, 24))
grid_imgs = torchvision.utils.make_grid(samples[:24])
np_grid_imgs = grid_imgs.numpy()
plt.imshow(np.transpose(np_grid_imgs, (1, 2, 0)))

# 设置设备和模型
device = 'cuda'
model = torchvision.models.densenet121(pretrained=True)

# 微调模型
num_ftrs = model.classifier.in_features
model.classifier = nn.Sequential(
    nn.Linear(num_ftrs, 500),
    nn.Linear(500, 2)
)

# 移动模型到GPU
model = model.to(device)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.002, amsgrad=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[500, 1000, 1500], gamma=0.5)

# 训练模型
epochs = 10
itr = 1
p_itr = 200
model.train()
total_loss = 0
loss_list = []
acc_list = []

# 迭代训练
for epoch in range(epochs):
    for samples, labels in dataloader:
        samples, labels = samples.to(device), labels.to(device)
        optimizer.zero_grad()
        output = model(samples)
        loss = criterion(output, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        scheduler.step()

        # 计算精度
        pred = torch.argmax(output, dim=1)
        correct = pred.eq(labels)
        acc = torch.mean(correct.float())
        
        # 打印状态信息
        print('[Epoch {}/{}] Iteration {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'.format(epoch + 1, epochs, itr, total_loss / p_itr, acc))
        
        # 记录损失和精度
        loss_list.append(total_loss / p_itr)
        acc_list.append(acc)
        total_loss = 0
        itr += 1

# 绘制训练损失和精度
plt.plot(loss_list, label='loss')
plt.plot(acc_list, label='accuracy')
plt.legend()
plt.title('training loss and accuracy')
plt.show()

# 保存模型
filename_pth = 'ckpt_densenet121_catdog.pth'
torch.save(model.state_dict(), filename_pth)

# 定义测试集预处理
test_transform = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor()
])

# 创建测试数据集和数据加载器
testset = CatDogDataset(test_files, test_dir, mode='test', transform=test_transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False, num_workers=4)

# 进行测试
model.eval()
fn_list = []
pred_list = []

# 迭代测试
for x, fn in testloader:
    with torch.no_grad():
        x = x.to(device)
        output = model(x)
        pred = torch.argmax(output, dim=1)
        
        # 保存文件名和预测结果
        fn_list += [n[:-4] for n in fn]
        pred_list += [p.item() for p in pred]

# 创建提交文件
submission = pd.DataFrame({"id": fn_list, "label": pred_list})
submission.to_csv('preds_densenet121.csv', index=False)

# 可视化测试结果
samples, _ = iter(testloader).next()
samples = samples.to(device)
fig = plt.figure(figsize=(24, 16))
fig.tight_layout()
output = model(samples[:24])
pred = torch.argmax(output, dim=1)
pred = [p.item() for p in pred]
ad = {0: 'cat', 1: 'dog'}
for num, sample in enumerate(samples[:24]):
    plt.subplot(4, 6, num + 1)
    plt.title(ad[pred[num]])
    plt.axis('off')
    sample = sample.cpu().numpy()
    plt.imshow(np.transpose(sample, (1, 2, 0)))

网站公告

今日签到

点亮在社区的每一天
去签到