赛题地址:农民身份识别挑战赛
数据集如图:
1 CNN(深度学习方法)个人解读
1.1 导入模块
导入所需的模块。
import os, sys, glob, argparse
import pandas as pd
import numpy as np
from tqdm import tqdm
import cv2, time
from PIL import Image
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
# Check if GPU is available
if torch.cuda.is_available():
device = torch.device("cuda")
else:
device = torch.device("cpu")
torch.manual_seed(0)
:设置了PyTorch的随机种子为0,对于需要可重复性的实验比较重要。随机种子:random.seed()和torch.manual_seed()的使用与不同torch.backends.cudnn.deterministic = False
:每次返回的卷积算法将是确定的,即默认算法。 配合上设置 Torch 的随机种子为固定值的话,应该可以保证每次运行网络的时候相同输入的输出是固定的。随机种子、torch.backends.cudnn.benchmark.deterministic_torch.backends.cudnn.determintorch.backends.cudnn.benchmark = True
:设置为True时,再Pytorch中对每一个卷积层测试cuDNN提供的所有实现卷积的算法,并选择最快的,以此大幅度减少训练时间。torch.backends.cudnn.benchmark ?! - 知乎 (zhihu.com)torchvision.models
提供了一些预训练的计算机视觉模型,例如ResNet、VGG等。这些模型可以直接用于图像分类、目标检测等任务。torchvision.transforms
定义了一些常用的图像变换操作,例如裁剪、缩放、翻转等。这些变换操作可以用于数据预处理,以提高模型的性能和泛化能力。torchvision.datasets
提供了一些常用的计算机视觉数据集,例如CIFAR-10、ImageNet等。这些数据集包含了大量的图像数据,可以用于训练和测试计算机视觉模型。PyTorch 笔记(20)— torchvision 的 datasets、transforms 数据预览和加载、模型搭建(torch.nn.Conv2d/MaxPool2d/Dropout)_wohu007的博客-CSDN博客torch.nn
定义了PyTorch中的神经网络模块,包括卷积层、池化层、全连接层等。这些模块可以用于构建自定义的神经网络模型。torch.nn.functional
提供了一些常用的神经网络功能函数,例如激活函数、池化函数等。这些函数可以用于加速神经网络的计算过程。torch.optim
定义了PyTorch中的优化器模块,例如SGD、Adam等。这些优化器可以用于更新神经网络的参数,以提高模型的性能和泛化能力。torch.autograd
提供了自动求导功能,可以用于计算神经网络的梯度和反向传播。torch.utils.data.dataset
是PyTorch中数据集接口的基类,所有的数据集都需要继承这个类并实现其中的抽象方法。
1.2 数据收集与准备
在赛题主页下载数据,读取数据集,自定义数据集(带有图片缓存的逻辑),定义训练集,验证集,测试集。
# 读取数据集
train_path = glob.glob('./农民身份识别挑战赛公开数据/train/*')
test_path = glob.glob('./农民身份识别挑战赛公开数据/test/*')
train_path.sort()
test_path.sort()
# 依据相同指标进行排序,使得标签与数据一一对应的方法,免去了一个一个查找来匹配
train_df = pd.read_csv('农民身份识别挑战赛公开数据/train.csv')
train_df = train_df.sort_values(by='name')
train_label = train_df['label'].values
# 自定义数据集
# 带有图片缓存的逻辑
DATA_CACHE = {}
class XunFeiDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
self.img_path = img_path
self.img_label = img_label
if transform is not None: # 不明白为什么不直接self.transform = transform,非得写个判断句
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):# 把index及对应的图片存到DATA_CACHE字典里面
if self.img_path[index] in DATA_CACHE:
img = DATA_CACHE[self.img_path[index]]
else:
img = cv2.imread(self.img_path[index])
DATA_CACHE[self.img_path[index]] = img
if self.transform is not None:
img = self.transform(image = img)['image']
img = img.transpose([2,0,1])
return img, torch.from_numpy(np.array(self.img_label[index]))
def __len__(self):
return len(self.img_path)
import albumentations as A
# 训练集
train_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[:-1000], train_label[:-1000],
A.Compose([# 即self.transform被赋值A.Compose
A.RandomRotate90(),
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),# p表概率
A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=True, num_workers=1, pin_memory=False
)
# 验证集
val_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[-1000:], train_label[-1000:],
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=False, num_workers=1, pin_memory=False
)
# 测试集
test_loader = torch.utils.data.DataLoader(
XunFeiDataset(test_path, [0] * len(test_path),
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),
A.RandomContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=2, shuffle=False, num_workers=1, pin_memory=False
)
img = self.transform(image = img)['image']
:这句不是很理解,得后面试验一下。img = img.transpose([2,0,1])
:将img
的第3个维度(索引为2)作为新的第1个维度,原来的第1个维度(索引为0)作为新的第2个维度,原来的第2个维度(索引为1)保持不变。这样就实现了通道维度的转置操作。通常将图像的通道维度放在最后一个维度,即高度、宽度和通道数组成的形状为[height, width, channels]
的三维张量中。通过转置操作可以将通道维度从最后一个维度变为第一个维度,方便后续的处理和分析。torch.from_numpy(np.array(self.img_label[index]))
:np.array(self.img_label[index])
将self.img_label[index]
转换为NumPy数组,然后torch.from_numpy()
函数将该数组转换为PyTorch张量。__init__()
初始化;__getitem__()
:通过索引获取属性,通过fun[index]调用;__len__()
:获取长度,通过len(fun)调用。- Albumentations是一个给予OpenCV的快速训练数据增强库,拥有非常简单且强大的可以用于多种任务(分割、检测)的接口,易于定制且添加其他框架非常方便。它可以对数据集进行逐像素的转换,如模糊、下采样、高斯造点、高斯模糊、动态模糊、RGB转换、随机雾化等;也可以进行空间转换(同时也会对目标进行转换),如裁剪、翻转、随机裁剪等。albumentations 数据增强工具的使用 - 知乎 (zhihu.com)
1.3 特征提取
自定义CNN模型,我们本次采取ResNet18来提取图像特征。
class XunFeiNet(nn.Module):
def __init__(self):
super(XunFeiNet, self).__init__()
model = models.resnet18(True)
model.avgpool = nn.AdaptiveAvgPool2d(1)
model.fc = nn.Linear(512, 25)
self.resnet = model
def forward(self, img):
out = self.resnet(img)
return out
model = XunFeiNet()
model = model.to(device)
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.AdamW(model.parameters(), 0.001)
class XunFeiNet(nn.Module):
定义一个神经网络模型,继承自PyTorch的nn.Module类。super(XunFeiNet, self).__init__()
:调用父类的初始化函数。model = models.resnet18(True)
: 创建一个ResNet18模型,并设置参数为True表示使用批标准化。model.avgpool = nn.AdaptiveAvgPool2d(1)
将ResNet18模型的平均池化层替换为自适应平均池化层,大小为1x1。model.fc = nn.Linear(512, 25)
将ResNet18模型的全连接层替换为线性层,输入维度为512,输出维度为25。criterion = nn.CrossEntropyLoss().cuda()
定义交叉熵损失函数,并将其转移到GPU上。optimizer = torch.optim.AdamW(model.parameters(), 0.001)
定义AdamW优化器,并将模型的参数传递给它。学习率为0.001。
1.4 模型训练、评估与优化
生成测试集提交结果,输出submit.csv。
# 模型训练
def train(train_loader, model, criterion, optimizer):
model.train()
train_loss = 0.0
for i, (input, target) in enumerate(train_loader):
input = input.to(device)
target = target.to(device)
output = model(input)
loss = criterion(output, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i % 100 == 0:
print('Train loss', loss.item())
train_loss += loss.item()
return train_loss/len(train_loader)
# 模型验证
def validate(val_loader, model, criterion):
model.eval()
val_acc = 0.0
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(val_loader):
input = input.to(device)
target = target.to(device)
output = model(input)
loss = criterion(output, target)
val_acc += (output.argmax(1) == target).sum().item()
return val_acc / len(val_loader.dataset)
# 模型预测
def predict(test_loader, model, criterion):
model.eval()
val_acc = 0.0
test_pred = []
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(test_loader):
input = input.to(device)
target = target.to(device)
output = model(input)
test_pred.append(output.data.cpu().numpy())
return np.vstack(test_pred)
for _ in range(1):
train_loss = train(train_loader, model, criterion, optimizer)
val_acc = validate(val_loader, model, criterion)
train_acc = validate(train_loader, model, criterion)
print(train_loss, train_acc, val_acc)
output = model(input)
:使用模型对输入数据进行前向传播,得到预测结果。这是神经网络的基本操作之一,通过输入数据经过一系列的层和激活函数,最终得到输出结果。loss = criterion(output, target)
:计算预测结果与目标数据的损失值。损失值是衡量模型预测结果与真实值之间差距的一种指标,通常用于优化模型参数。optimizer.zero_grad()
:清空优化器的梯度缓存。在反向传播过程中,需要计算每个参数的梯度,并根据梯度更新参数。为了避免重复计算,优化器会缓存之前计算的梯度,清空缓存可以保证从头开始计算梯度。loss.backward()
:对损失值进行反向传播,计算各个参数的梯度。反向传播是一种数学方法,通过计算损失函数对各个参数的偏导数,来确定每个参数对损失的贡献程度,进而计算出每个参数的梯度。optimizer.step()
:根据计算出的梯度,更新模型的参数。这一步是实际执行模型参数更新的操作,通常采用的是随机梯度下降法等优化算法。
1.5 结果输出
# 对测试集多次预测
pred = None
for _ in range(3):
if pred is None:
pred = predict(test_loader, model, criterion)
else:
pred += predict(test_loader, model, criterion)
submit = pd.DataFrame(
{
'name': [x.split('/')[-1] for x in test_path],
'label': pred.argmax(1)
})
# 生成提交结果
submit = submit.sort_values(by='name')
submit.to_csv('submit.csv', index=None)
- 重复预测三次,每一次预测结果形式如下:
[[-17.382252 -12.879019 -5.4706874 ... -14.823031 -5.7047276
-12.268326 ]
[-12.8921795 -10.931914 -4.1398892 ... -9.630869 -8.415216
-7.32834 ]
[-15.598559 -18.272264 -5.559568 ... -7.338052 -7.785285
-9.979665 ]
...
[-15.809991 -8.222196 -11.108756 ... -10.520618 -6.322539
-13.858497 ]
[-14.019038 -3.2318373 -12.087874 ... -10.996267 -8.175714
-15.657203 ]
[-11.343989 -8.790283 -14.934624 ... -15.745941 -4.116088
-14.211422 ]]
- 将三次预测结果对应相加,然后以
pred.argmax(1)
得到每一行最大值对应的索引,也即预测类别。 pred.argmax(1)
:argmax()
函数用于返回输入张量沿指定维度的最大值的索引。
当pred
是一个二维张量时,pred.argmax(1)
将返回每一行中最大值的索引。
2 个人总结
- 主要学到了利用pytorch框架构建神经网络模型、定义损失函数、定义优化器的方法,这是我此一次用,比之前自己从神经元开始写起确实方便很多很多。
- 在训练和预测的时候,感觉这里用到的方法和TTA异曲同工,它重复预测三次,每次输入的图像都有一定概率在原图片的基础上进行一些变换,将预测结果相加,最后比较大小以确定最终预测结果。