我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

文章目录

1.数据集准备

本例采用了pytorch教程提供的蜜蜂、蚂蚁二分类数据集(点击可直接下载)。该数据集的文件夹结构如下图所示。 这里面有些黑白的照片,我把它们删掉了,因为黑白照片的通道数是1,会造成Tensor的维度不一致。可以看出数据集分为训练集和测试集,训练集用于训练模型,测试集用于测试模型的泛化能力。在训练集和测试集下又包含了”ants”和”bees”两个文件夹,这两个文件夹的名称即图片的标签,在加载数据的时候需要用到这一点。有了数据,我们就想办法把这些数据处理成pytorch框架下的Dataset需要的格式。

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

; 2.pytorch Dataset 处理图片数据

pytorch为我们处理数据提供了一个模板,这个模板就是Dataset,我们在处理数据时继承这个类。在处理数据时要注意以下几点:

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型
*32的图片了。
2. “ants”和”bees”两个文件夹的名称就是图片的标签,但是__getitem__的返回值应该是一个值。在这里”ants”标签返回0,”bees”标签返回1。
3. 看数据的预处理对不对,可以用一段代码测试一下,将数据加载到DataLoader,然后循环取出数据,并把这些数据及其标签打印出来,或者记录到tensorboard上去,看每一次迭代返回的数据是否和自己预想的一样。

下面是代码,保存在dataProcess.py文件中。

rom torch.utils.data import Dataset
from torch.utils.data import DataLoader
from PIL import Image
import os
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

class MyData(Dataset):

    def __init__(self, root_dir, label_dir):
        self.root_dir = root_dir
        self.label_dir = label_dir

        self.path = os.path.join(self.root_dir, self.label_dir)

        self.img_names = os.listdir(self.path)

    def __getitem__(self, idx):
        img_name = self.img_names[idx]
        img_item_path = os.path.join(self.root_dir, self.label_dir, img_name)
        img = Image.open(img_item_path)

        trans = transforms.Compose(
            [
                transforms.ToTensor(),
                transforms.Resize((32, 32))
            ])
        img_tensor = trans(img)

        label = 0 if self.label_dir == "ants" else 1
        return img_tensor,  label

    def __len__(self):
        return len(self.img_names)

if __name__ == "__main__":

    root_dir = "hymenoptera_data/train"
    ants_label = "ants"
    bees_label = "bees"

    ants_dataset = MyData(root_dir, ants_label)

    bees_dataset = MyData(root_dir, bees_label)

    train_dataset = ants_dataset + bees_dataset

    train_dataloader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)

    writer = SummaryWriter("logs")
    for step, train_data in enumerate(train_dataloader):
        imgs, targets = train_data

        writer.add_images("test", imgs, step)

        print(targets)
    writer.close()

在测试时tensorboard记录的信息在logs文件夹,在terminal输入tensorboard –logdir=logs启动tensorboard,将tensorboard给出的网址输入到网页,可以看到每一个batch的图片。下图展示了第一个batch的图片。可以看到,取出了64张图片,和batchsize=64是对应的。另外可以看到,把图片压缩成32*32后,确实很模糊了,人眼都很难看出哪个是蚂蚁,哪个是蜜蜂。

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

下面这个图展示了第一个batch所有图片的标签,0表示蚂蚁,1表示蜜蜂,仔细看一下图片和标签应该是对应的。

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

3.网络模型设计

我们把图片处理成33232的tensor了,用如下图所示的卷积神经网络模型。第一层卷积网络采用5*5的卷积核,stride=1,pading=2。第一层卷积的代码是:nn.Conv2d(3, 32, 5, 1, 2),第一个参数3是输入的通道数,第二个参数32是输出的通道数,第三个参数5是卷积核的大小,第四个参数1是stride,第五个参数2是padding。

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

输出高H,和宽度W计算公式如下所示(注意dilation默认为0)。

H o u t = ⌊ H i n + 2 × padding [ 0 ] − dilation [ 0 ] × ( kernel_size [ 0 ] − 1 ) − 1 stride [ 0 ] + 1 ⌋ H_{out} = \left\lfloor\frac{H_{in} + 2 \times \text{padding}[0] – \text{dilation}[0] \times (\text{kernel_size}[0] – 1) – 1}{\text{stride}[0]} + 1\right\rfloor H o u t ​=⌊stride [0 ]H i n ​+2 ×padding [0 ]−dilation [0 ]×(kernel_size [0 ]−1 )−1 ​+1 ⌋
W o u t = ⌊ W i n + 2 × padding [ 1 ] − dilation [ 1 ] × ( kernel_size [ 1 ] − 1 ) − 1 stride [ 1 ] + 1 ⌋ W_{out} = \left\lfloor\frac{W_{in} + 2 \times \text{padding}[1] – \text{dilation}[1] \times (\text{kernel_size}[1] – 1) – 1}{\text{stride}[1]} + 1\right\rfloor W o u t ​=⌊stride [1 ]W i n ​+2 ×padding [1 ]−dilation [1 ]×(kernel_size [1 ]−1 )−1 ​+1 ⌋
因此,通过第一层卷积后,高度H为,
H o u t = 32 + 2 × 2 − 1 × ( 5 − 1 ) − 1 1 + 1 = 32 H_{out}=\frac{32+2 \times 2 -1\times(5-1)-1}{1}+1=32 H o u t ​=1 3 2 +2 ×2 −1 ×(5 −1 )−1 ​+1 =3 2
同理宽度W也为32。所以输出的大小就323232。接下来,再用一个max-Pooling进行一次池化,池化核的大小是22。该池化层的代码是nn.MaxPool2d(2)。池化输出高H,和宽度W计算公式和卷积计算方式一摸一样。在默认的情况下,stride和池化和的大小一样,pading=0,dilation=0。所以第一次池化后,输出的高度H为,
H o u t = 32 + 2 × 0 − 1 × ( 2 − 1 ) − 1 2 + 1 = 16 H_{out}=\frac{32+2 \times 0 -1\times(2-1)-1}{2}+1=16 H o u t ​=2 3 2 +2 ×0 −1 ×(2 −1 )−1 ​+1 =1 6
同理,输出的宽度H为16。因此,输出的维度是32
1616。
后面的输出维度计算方式同上,不再罗嗦了。然后再通过两次卷积和两次池化,后面的输出维度计算方式同上,不再罗嗦了,最终得到一个维度为64
4*4的特征。在做分类之前,首先要把这个三维Tensor拉直成一维Tensor,代码是nn.Flatten()。拉直之后的一维Tensor大小就是64 × 4 × 4 = 1024 64\times4\times4=1024 6 4 ×4 ×4 =1 0 2 4。最后通过一个全连接层完成分类任务,全连接层的输入大小是1024,输出的大小是类别的个数,即2,代码是nn.Linear(64 * 4 * 4, 2)。

当完成所有模型的构建后,可以用一段代码来测试一下模型是否有误。例如这里模型的输入在[3,32,32]Tensor的基础上,还需要再增加一维batchsize,所以输入的维度应该是[batchsize,3,32,32]。我们可以生成一个这样维度的数据,例如假设batchsize=3,可以这样生成一个输入:x = torch.ones((3, 3, 32, 32))。然后把x送给模型,看模型是否能正常输出,输出的维度是否是我们预期的。我们还可以借助于Tensorboard来将模型可视化,通过界面把模型展开,看是否正确。
下面是所有的代码,保存在model.py文件中。

from torch import nn
import torch
from torch.utils.tensorboard import SummaryWriter

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 2)
        )

    def forward(self, x):
        x = self.model(x)
        return x

if __name__ == "__main__":
    my_model = MyModel()
    x = torch.ones((3, 3, 32, 32))
    y = my_model(x)
    print(y.shape)

    writer = SummaryWriter("graph_logs")
    writer.add_graph(my_model, x)
    writer.close()

模型测试代码打印的输出维度是[3,2],3是batchsize,2是全连接层最后的输出维度,和类别的个数是一致的。利用Tensorboard将模型可视化后,如下图所示,还可以进一步展开。

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

4.模型的训练与测试

模型的训练与测试就不细讲了,和其他模型训练的套路一样的,基本思路可以看我的第一篇pytorch入门文章。下面直接给出代码。

from model import *
from dataProcess import *
import matplotlib.pyplot as plt
import time

train_root_dir = "hymenoptera_data/train"
train_ants_label = "ants"
train_bees_label = "bees"
train_ants_dataset = MyData(train_root_dir, train_ants_label)
train_bees_dataset = MyData(train_root_dir, train_bees_label)
train_dataset = train_ants_dataset + train_bees_dataset
train_data_loader = DataLoader(dataset=train_dataset, batch_size=128, shuffle=True)
train_data_len = len(train_dataset)

test_root_dir = "hymenoptera_data/val"
test_ants_label = "ants"
test_bees_label = "bees"
test_ants_dataset = MyData(test_root_dir, test_ants_label)
test_bees_dataset = MyData(test_root_dir, test_bees_label)
test_dataset = test_ants_dataset + test_bees_dataset
test_data_loader = DataLoader(dataset=test_dataset, batch_size=256, shuffle=True)
test_data_len = len(test_dataset)
print(f"训练集长度:{train_data_len}")
print(f"测试集长度:{test_data_len}")

my_model = MyModel()

loss_fn = nn.CrossEntropyLoss()

learning_rate = 5e-3
optimizer = torch.optim.SGD(my_model.parameters(), lr=learning_rate)

total_train_step = 0

total_test_step = 0
step = 0
epoch = 500

writer = SummaryWriter("logs")
train_loss_his = []
train_totalaccuracy_his = []
test_totalloss_his = []
test_totalaccuracy_his = []
start_time = time.time()
my_model.train()
for i in range(epoch):
    print(f"-------第{i}轮训练开始-------")
    train_total_accuracy = 0
    for data in train_data_loader:
        imgs, targets = data
        writer.add_images("tarin_data", imgs, total_train_step)
        output = my_model(imgs)
        loss = loss_fn(output, targets)
        train_accuracy = (output.argmax(1) == targets).sum()
        train_total_accuracy = train_total_accuracy + train_accuracy
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_train_step = total_train_step + 1
        train_loss_his.append(loss)
        writer.add_scalar("train_loss", loss.item(), total_train_step)
    train_total_accuracy = train_total_accuracy / train_data_len
    print(f"训练集上的准确率:{train_total_accuracy}")
    train_totalaccuracy_his.append(train_total_accuracy)

    total_test_loss = 0
    my_model.eval()
    test_total_accuracy = 0
    with torch.no_grad():
        for data in test_data_loader:
            imgs, targets = data
            output = my_model(imgs)
            loss = loss_fn(output, targets)
            total_test_loss = total_test_loss + loss
            test_accuracy = (output.argmax(1) == targets).sum()
            test_total_accuracy = test_total_accuracy + test_accuracy
        test_total_accuracy = test_total_accuracy / test_data_len
        print(f"测试集上的准确率:{test_total_accuracy}")
        print(f"测试集上的loss:{total_test_loss}")
        test_totalloss_his.append(total_test_loss)
        test_totalaccuracy_his.append(test_total_accuracy)
        writer.add_scalar("test_loss", total_test_loss.item(), i)
end_time = time.time()
total_train_time = end_time-start_time
print(f'训练时间: {total_train_time}秒')
writer.close()
plt.plot(train_loss_his, label='Train Loss')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()
plt.plot(test_totalloss_his, label='Test Loss')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()

plt.plot(train_totalaccuracy_his, label='Train accuracy')
plt.plot(test_totalaccuracy_his, label='Test accuracy')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()

通过上述代码,训练得到的结果如下图所示,

我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

结果虽然不是很好,但是我觉得已经很不多了,在测试集上的准确率差不多达到0.7了。为了节省计算资源,我把图片压缩成32*32,连我们人眼都很难分辨出哪个是蚂蚁,哪个是蜜蜂。另外,我这个模型是完全从0开始训练的,隔壁在预训练模型的基础上进行训练得到的效果好像没好多少。。。

Original: https://blog.csdn.net/oXiLang/article/details/123414347
Author: 偶尔写一写
Title: 我的实践:通过蚂蚁、蜜蜂二分类问题了解如何基于Pytorch构建分类模型

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/663383/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球