文章目录
- (一)实验任务
- (二)数据处理
* - 1、读取和划分数据集
- 2、创建Dataset
- (三)手写实现二维卷积
* - 1、卷积操作的实现
- 2、多通道输入的卷积实现
- 3、多通道输入输出的卷积实现
- 4、将卷积运算封装成卷积层
- 5、二维卷积神经网络模型的构建
- (四)模型训练与测试
* - 1、模型训练
- 2、测试
- 3、绘制acc和loss曲线
- (五)实验结果
(一)实验任务
手写二维卷积的实现,并在车辆分类数据集上完成分类任务
数据集包含三个文件夹,里面分别是car、bus和truck的车辆图片,需要对数据集进行处理并且划分数据集。
(二)数据处理
数据形式如下
; 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曲线
Original: https://blog.csdn.net/cumina/article/details/119864376
Author: 番茄牛腩煲
Title: [PyTorch]手动实现二维卷积神经网络完成车辆分类任务
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/663303/
转载文章受原作者版权保护。转载请注明原作者出处!