[PyTorch]手动实现二维卷积神经网络完成车辆分类任务

文章目录

(一)实验任务

手写二维卷积的实现,并在车辆分类数据集上完成分类任务
数据集包含三个文件夹,里面分别是car、bus和truck的车辆图片,需要对数据集进行处理并且划分数据集。

(二)数据处理

数据形式如下

[PyTorch]手动实现二维卷积神经网络完成车辆分类任务
[PyTorch]手动实现二维卷积神经网络完成车辆分类任务

; 1、读取和划分数据集

使用PIL读取图片。

import os
import torch.nn.functional as F
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import torch
from torch.utils.data import Dataset
from torchvision import transforms
import random

num_classes = 3
batch_size = 256
num_epochs = 10
lr = 0.02
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

file_root = "D:/1MyProjects/python/pytorch_1/实验三数据集/车辆分类数据集"
classes = ['bus', 'car', 'truck']
nums = [218, 779, 360]

def read_data(path):
    file_name = os.listdir(path)
    train_data = []
    train_labels = []
    test_data = []
    test_labels = []

    train_num = [int(num * 4 / 5) for num in nums]
    test_num = [nums[i] - train_num[i] for i in range(len(nums))]

    for idx, f_name in enumerate(file_name):
        im_dirs = path + '/' + f_name
        im_path = os.listdir(im_dirs)

        index = list(range(len(im_path)))
        random.shuffle(index)
        im_path_ = list(np.array(im_path)[index])
        test_path = im_path_[:test_num[idx]]
        train_path = im_path_[test_num[idx]:]

        for img_name in train_path:

            if img_name == 'desktop.ini':
                continue
            img = Image.open(im_dirs + '/' + img_name)

            img = img.resize((32, 32), Image.ANTIALIAS)
            train_data.append(img)
            train_labels.append(idx)

        for img_name in test_path:

            if img_name == 'desktop.ini':
                continue
            img = Image.open(im_dirs + '/' + img_name)

            img = img.resize((32, 32), Image.ANTIALIAS)
            test_data.append(img)
            test_labels.append(idx)

    print('训练集大小:', len(train_data), ' 测试集大小:', len(test_data))

    return train_data, train_labels, test_data, test_labels

train_data, train_labels, test_data, test_labels = read_data(file_root)

2、创建Dataset

首先需要定义一个Transform操作,将PIL格式的数据转为Tensor并且归一化标准化。


transform = transforms.Compose(
    [transforms.ToTensor(),

     transforms.Normalize(mean=[0.4686, 0.4853, 0.5193], std=[0.1720, 0.1863, 0.2175])
     ]
)

然后自定义Dataset类,每次取出样本都要先经过Transform转为Tensor。


class MyDataset(Dataset):
    def __init__(self, data, label, trans):
        self.len = len(data)
        self.data = data
        self.label = label
        self.trans = trans

    def __getitem__(self, index):
        return self.trans(self.data[index]), self.label[index]

    def __len__(self):
        return self.len

train_dataset = MyDataset(train_data, train_labels, transform)
test_dataset = MyDataset(test_data, test_labels, transform)

最后生成data_loader


train_iter = torch.utils.data.DataLoader(
    dataset=train_dataset,
    batch_size=batch_size,
    shuffle=True,
    num_workers=0
)
test_iter = torch.utils.data.DataLoader(
    dataset=test_dataset,
    batch_size=batch_size,
    shuffle=False,
    num_workers=0
)

(三)手写实现二维卷积

1、卷积操作的实现

def conv2d(X, K):
    '''
    :param X: 样本输入,shape(batch_size,H,W)
    :param K: 卷积核,shape(k_h,k_w)
    :return: Y 卷积结果,shape(batch_size, H-k_h+1, W-k_w+1)
    '''
    batch_size, H, W = X.shape
    k_h, k_w = K.shape

    Y = torch.zeros((batch_size, H - k_h + 1, W - k_w + 1)).to(device)
    for i in range(Y.shape[1]):
        for j in range(Y.shape[2]):
            Y[:, i, j] = (X[:, i: i + k_h, j:j + k_w] * K).sum(dim=2).sum(dim=1)
    return Y

2、多通道输入的卷积实现

def conv2d_multi_in(X, K):
    '''
    :param X: (batch_size, C_in,H,W)代表有C个输入通道
    :param K: (C_in, k_h, k_w)
    :return: (batch_size, H_out, W_out)
    '''
    res = conv2d(X[:, 0, :, :], K[0, :, :])
    for i in range(1, X.shape[1]):
        res += conv2d(X[:, i, :, :], K[i, :, :])
    return res

3、多通道输入输出的卷积实现


def conv2d_multi_in_out(X, K):
    '''
    :param X: (batch_size, C_in,H,W)代表有C个输入通道
    :param K: (K_num, C_in, k_h, k_w) k_num表示卷积核的个数
    :return: (batch_size, K_num, H_out, W_out)
    '''
    return torch.stack([conv2d_multi_in(X, k) for k in K], dim=1)

4、将卷积运算封装成卷积层

class MyConv2D(torch.nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size):
        super(MyConv2D, self).__init__()

        if isinstance(kernel_size, int):
            kernel_size = (kernel_size, kernel_size)

        self.weight = torch.nn.Parameter(torch.randn((out_channels, in_channels) + kernel_size))
        self.bias = torch.nn.Parameter(torch.randn(out_channels, 1, 1))

    def forward(self, x):
        '''
        :param x:
        :return:
        '''
        return conv2d_multi_in_out(x, self.weight) + self.bias

5、二维卷积神经网络模型的构建

class MyConvModule(torch.nn.Module):
    def __init__(self):
        super(MyConvModule, self).__init__()

        self.conv = torch.nn.Sequential(
            MyConv2D(in_channels=3, out_channels=32, kernel_size=3),
            torch.nn.BatchNorm2d(32),
            torch.nn.ReLU(inplace=True)
        )

        self.fc = torch.nn.Linear(32, num_classes)

    def forward(self, X):

        out = self.conv(X)

        out = F.avg_pool2d(out, 30)

        out = out.squeeze()

        out = self.fc(out)
        return out

net = MyConvModule()
net.to(device)

loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=lr)

(四)模型训练与测试

1、模型训练

def train(net, data_loader, device):
    net.train()
    train_batch_num = len(data_loader)
    total_loss = 0.0
    correct = 0
    sample_num = 0

    for data, target in data_loader:

        data = data.to(device)
        target = target.to(device)

        optimizer.zero_grad()

        y_hat = net(data)

        loss_ = loss(y_hat, target)

        loss_.backward()
        optimizer.step()
        total_loss += loss_.item()
        cor = (torch.argmax(y_hat, 1) == target).sum().item()
        correct += cor

        sample_num += target.shape[0]
        print('loss: %.4f  acc: %.4f' % (loss_.item(), cor/target.shape[0]))

    loss_ = total_loss / train_batch_num
    acc = correct / sample_num
    return loss_, acc

2、测试


def test(net, data_loader, device):
    net.eval()
    test_batch_num = len(data_loader)
    total_loss = 0
    correct = 0
    sample_num = 0

    with torch.no_grad():
        for data, target in data_loader:
            data = data.to(device)
            target = target.to(device)
            output = net(data)
            loss_ = loss(output, target)
            total_loss += loss_.item()
            correct += (torch.argmax(output, 1) == target).sum().item()
            sample_num += target.shape[0]
    loss_ = total_loss / test_batch_num
    acc = correct / sample_num
    return loss_, acc

train_loss_list = []
train_acc_list = []
test_loss_list = []
test_acc_list = []

for epoch in range(num_epochs):

    train_loss, train_acc = train(net, data_loader=train_iter, device=device)

    test_loss, test_acc = test(net, data_loader=test_iter, device=device)

    train_loss_list.append(train_loss)
    train_acc_list.append(train_acc)
    test_loss_list.append(test_loss)
    test_acc_list.append(test_acc)
    print('epoch %d, train loss: %.4f, train acc: %.3f' % (epoch+1, train_loss, train_acc))
    print('test loss: %.4f, test acc: %.3f' % (test_loss, test_acc))

3、绘制acc和loss曲线


def draw_(x, train_Y, test_Y, ylabel):
    plt.plot(x, train_Y, label='train_' + ylabel, linewidth=1.5)
    plt.plot(x, test_Y, label='test_' + ylabel, linewidth=1.5)
    plt.xlabel('epoch')
    plt.ylabel(ylabel)
    plt.legend()
    plt.show()

x = np.linspace(0, len(train_loss_list), len(train_loss_list))
draw_(x, train_loss_list, test_loss_list, 'loss')
draw_(x, train_acc_list, test_acc_list, 'accuracy')

(五)实验结果

训练期间输出:

epoch 8, train loss: 0.9420, train acc: 0.574
test loss: 1.0830, test acc: 0.574
loss: 0.9440  acc: 0.5820
loss: 0.9627  acc: 0.5430
loss: 0.9615  acc: 0.5703
loss: 0.9169  acc: 0.6094
loss: 0.9855  acc: 0.5410
epoch 9, train loss: 0.9541, train acc: 0.574
test loss: 1.0850, test acc: 0.574
loss: 0.9088  acc: 0.6211
loss: 0.9370  acc: 0.5703
loss: 0.9487  acc: 0.5781
loss: 0.9783  acc: 0.5391
loss: 0.9872  acc: 0.5246
epoch 10, train loss: 0.9520, train acc: 0.574
test loss: 1.0850, test acc: 0.574

训练和测试的acc曲线和loss曲线

[PyTorch]手动实现二维卷积神经网络完成车辆分类任务
[PyTorch]手动实现二维卷积神经网络完成车辆分类任务

Original: https://blog.csdn.net/cumina/article/details/119864376
Author: 番茄牛腩煲
Title: [PyTorch]手动实现二维卷积神经网络完成车辆分类任务

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

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

(0)

大家都在看

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