农民身份识别挑战赛baseline(学习笔记)(二)

赛题地址:农民身份识别挑战赛
数据集如图:
在这里插入图片描述

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.determin
  • torch.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异曲同工,它重复预测三次,每次输入的图像都有一定概率在原图片的基础上进行一些变换,将预测结果相加,最后比较大小以确定最终预测结果。

猜你喜欢

转载自blog.csdn.net/U202113837/article/details/132198372