《AI夏令营(第二期) - CV实践赛事》- Datawhale

目录

第一次打卡

实践任务:农民身份识别挑战赛

https://challenge.xfyun.cn/topic/info?type=peasant-identity&ch=ymfk4uU

时间规划

三环节

 赛题解读(What)

赛题本质:这是一个图像分类问题,需要根据图像序列中的农民特征,对农民进行身份识别。

身份识别优势(Why)

 任务(How)

baseline代码解读

图像特征统计(传统方法)

1.导入需要的库

 2.数据收集与准备

3.特征提取

np.where函数

4.模型训练、评估与优化

5.生成测试集提交结果submit.csv

进阶实践CNN(深度学习方法)

 1.导入模块——Pytorch版本

2.数据收集与准备

3.特征提取

 4.模型训练、评估与优化

5.结果输出


第一次打卡

实践任务:农民身份识别挑战赛

https://challenge.xfyun.cn/topic/info?type=peasant-identity&ch=ymfk4uU


时间规划

8月2日 开营

8月3日 - 6日 

  1. 理解赛题,跑通代码

  2. 提交任务一打卡,查看个人成绩排行榜 

8月8日 直播 竞赛进阶上分思路与技巧

8月7日-9日 

  1. 优化Baseline,尝试高阶技巧

  2. 提交任务二打卡,查看个人成绩排行榜

8月12日  直播  结营直播&优秀学习者颁奖

三环节

  1. 实践全流程体验
  2. 逐行代码精读
  3. 优化实践代码

 赛题解读(What)

赛题本质:这是一个图像分类问题,需要根据图像序列中的农民特征,对农民进行身份识别。

身份识别优势(Why)

  • 可以真实客观地记录农民的状态,为农场管理和农产品追溯提供真实的客观数据;
  • 较之直接存储视频,可以有效地降低存储空间;
  • 相比于比人工监管,能大幅度提高效率,减少人工成本。

 任务(How)

        图像分类是计算机视觉领域的一个重要任务,旨在将输入的图像分为不同的预定义类别,即让计算机能够理解和识别图像的内容,并将其归类到已知的类别中。流程图为(直接照搬了)

任务输入:图像序列,每个序列包含多张图像,每张图像中有一名农民。

任务输出:农民身份,每个身份对应一个类别编号,从0到24。

baseline代码解读

        这是我第一次参加CV方向的比赛,在参加比赛之前,我对相关代码了解很少,好在有机器学习的一点基础,但优化更是遥远。

  • 在图像分类中,常用的特征提取方法是图像特征统计 或 使用卷积神经网络(CNN)。

    • 图像特征统计是指对图像中的像素或图像区域进行统计分析,以提取和描述图像的特征信息

    • CNN是一种专门设计用于处理图像数据的神经网络架构,能够有效地从原始像素中学习和提取特征。

    • 通过一系列的卷积层、池化层和全连接层,CNN能够自动学习具有区分性的图像特征。

        所以本次实践给了2份代码分别采用了图像特征统计(传统方法 K近邻)和CNN(深度学习方法-随机森林)下面来分别进行解读,使用云环境-百度飞桨平台进行。

图像特征统计(传统方法)

1.导入需要的库

import os, sys, glob, argparse
from PIL import Image                  #用于处理图像文件
import cv2                             #常用的计算机视觉库,用于读取,显示,处理图像和视频
import pandas as pd                    #数据分析库,操作数据表格,读取和加载csv文件
import numpy as np                     #科学计算库,用于操作多维数组和矩阵,数值运算
from tqdm import tqdm                  #进度条库,用于显示循环的进度和时间
#sklearn:机器学习库,提供包括以下一些分类器和评估函数。
from sklearn.neighbors import KNeighborsClassifier    #K近邻算法
from sklearn.ensemble import RandomForestClassifier   #随机森林算法
from sklearn.model_selection import cross_val_predict #交叉验证,并返回每个样本的预测标签
from sklearn.metrics import classification_report     #包括准确率,召回率,F1分数等常用指标
  • os:操作系统相关的功能,例如创建和删除文件夹,获取和修改环境变量,执行系统命令等。
  • sys:与Python解释器相关的功能,例如获取和修改命令行参数,设置和获取标准输入输出错误流,退出程序等。
  • glob:与文件路径匹配相关的功能,例如根据通配符查找文件,返回匹配的文件列表等。
  • argparse:与命令行参数解析相关的功能,例如创建和配置参数解析器,添加和处理必选和可选参数,生成帮助信息等。

 2.数据收集与准备

# 读取数据集
train_path = glob.glob('./农民身份识别挑战赛公开数据/train/*') #路径获取
test_path = glob.glob('./农民身份识别挑战赛公开数据/test/*') #路径获取

#排序为了确保读取正确
train_path.sort()
test_path.sort()

#pandas读取csv文件,存入DataFrame对象中
train_df = pd.read_csv('农民身份识别挑战赛公开数据/train.csv')
train_df = train_df.sort_values(by='name') #排序为了确保读取正确
train_label = train_df['label'].values #获取训练label标签

3.特征提取

构建占比特征、边缘特征、纹理特征

# 读取图像特征
def image_feat(path):                  #封装接受图像文件的路径即参数path
    img = cv2.imread(path, 0)          #灰度模式读取图像文件
    img = img.astype(np.float32)       #数据类型转换为np.float32
    feat = [                           #feat的列表,统计以下8个元素
        (img != 0).sum(),              # 非零像素的数量
        (img == 0).sum(),              # 零像素的数量
        img.mean(),                    # 平均值
        img.std(),                     # 标准差
        len(np.where(img.mean(0))[0]), # 在列方向上平均值不为零的数量
        len(np.where(img.mean(1))[0]), # 在行方向上平均值不为零的数量
        img.mean(0).max(),             # 列方向上的最大平均值
        img.mean(1).max()              # 行方向上的最大平均值
    ]
    return feat

两个较为复杂的解释一下

 len(np.where(img.mean(0))[0])

        首先调用img.mean(0)函数,返回img在列方向上(即第0轴)的平均值,得到一个一维数组。然后使用np.where函数对该数组进行判断,它返回一个元组,其中第0个元素是一个数组,表示满足条件(默认为不为零)的元素在原数组中的索引。然后您使用len函数对该数组求长度,得到在列方向上平均值不为零的数量。

 len(np.where(img.mean(1))[0])

        调用img.mean(1)函数,它返回img在行方向上(即第1轴)的平均值,得到一个一维数组。然后您使用np.where函数对该数组进行判断,它返回一个元组,其中第0个元素是一个数组,表示满足条件(默认为不为零)的元素在原数组中的索引。然后您使用len函数对该数组求长度,得到在行方向上平均值不为零的数量。

np.where函数

np.where函数是一个numpy模块中的函数,可以返回数组中的元素或元素的索引。两种用法:

•  当有三个参数时,np.where(condition, x, y),表示根据条件condition,从x或y中选择元素返回。当condition为True时,返回x中的元素,否则返回y中的元素。x, y和condition需要可以广播到同一形状。

•  当只有一个参数时,np.where(condition),表示返回数组中满足条件的元素的索引。它相当于np.asarray(condition).nonzero(),返回一个元组,其中每个元素是一个一维数组,表示满足条件的元素在对应轴上的索引。

4.模型训练、评估与优化

采取K近邻的算法构建模型

# 训练集特征
train_feat = []                                        #存储训练集的图像特征
for path in tqdm(train_path):                          #进度条显示循环的进度和时间
    train_feat += [image_feat(path)]

# 测试集特征
test_feat = []
for path in tqdm(test_path):                           #存储测试集的图像特征
    test_feat += [image_feat(path)]                    #进度条显示循环的进度和时间
    
# 训练集交叉验证
train_pred = cross_val_predict(                        #交叉验证 样本的预测标签
    KNeighborsClassifier(),                            #k近邻算法
    np.array(train_feat),                              #输入
    train_label                                        #输入
)
print(classification_report(train_label, train_pred))  #打印准确率,召回率,F1分数
                              
# 模型训练与预测
model = KNeighborsClassifier()
model.fit(                                             #fit对模型进行训练
    np.array(train_feat),                              #输入
    train_label                                        #输入
)
test_pred = model.predict(np.array(test_feat))         #测试集的预测标签

5.生成测试集提交结果submit.csv

# 生成测试集提交结果
submit = pd.DataFrame(
    {
        'name': [x.split('/')[-1] for x in test_path],
        'label': test_pred
})
submit = submit.sort_values(by='name')
submit.to_csv('submit.csv', index=None)

进阶实践CNN(深度学习方法)

一些重复的部分就不再赘述了

       卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习模型,广泛用于图像识别、计算机视觉和模式识别任务中。CNN 在处理具有网格结构数据(如图像)时表现出色,它能够自动学习和提取图像中的特征,并在分类、定位和分割等任务表现优秀的性能。

 1.导入模块——Pytorch版本

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)                           
#设置随机数种子为0,为了保证每次运行代码时得到相同的结果。
torch.backends.cudnn.deterministic = False
#提高运行速度,但可能会导致结果不可复现
torch.backends.cudnn.benchmark = True
#让程序自动寻找最适合当前配置的高效算法,以加速运行。

#torchchvision是一个视觉相关的库,提供了一些预训练的模型,图像变换和数据集的功能
import torchvision.models as models          #预训练的深度学习模型-ResNet, VGG, AlexNet..
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")
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold

•  train_test_split函数是一个数据划分函数,它可以将数据集按照一定的比例随机划分为训练集和测试集,或者训练集,验证集和测试集。它有很多参数,可以指定划分的比例,随机数种子,是否保持类别平衡等。

•  StratifiedKFold函数是一个交叉验证函数,它可以将数据集按照一定的份数(称为折数)划分为不同的子集,每次使用一个子集作为测试集,其他子集作为训练集,进行多轮训练和测试,得到平均的评估结果。它可以保持每个子集中类别的比例与原始数据集相同,适用于类别不平衡的情况。

•  KFold函数是一个交叉验证函数,它与StratifiedKFold函数类似,但不保证每个子集中类别的比例与原始数据集相同,适用于类别平衡的情况。

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):    #继承自torch.utils.data.dataset模块中的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 
        else:
            self.transform = None
    def __getitem__(self, index):      #获取数据的索引
        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_path[index]是否在DATA_CACHE字典中,这是一个用于缓存已经读取过的图像的字典。如果在字典中,则从字典中获取图像,并赋值给img变量;否则,使用cv2模块的imread函数读取图像文件,并将其添加到字典中,并赋值给img变量。这样做可以避免重复读取相同的图像文件,提高运行效率。
        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)
#接着使用transpose方法对img变量进行转置操作,将其形状从(高度, 宽度, 通道数)变为(通道数, 高度, 宽度),以适应PyTorch框架对图像数据的要求。
        
import albumentations as A   #图像增强库
# 训练集 
# 三个数据加载器对象 train_loader val_loader test_loader
train_loader = torch.utils.data.DataLoader( #使用除了最后1000个以外的所有训练样本
    XunFeiDataset(train_path[:-1000], train_label[:-1000], 
            A.Compose([
            A.RandomRotate90(),      #随机旋转90度
            A.Resize(256, 256),      #缩放到256x256
            A.RandomCrop(224, 224),  #随机裁剪到224x224
            A.HorizontalFlip(p=0.5), #水平翻转
            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,     #每次加载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
)

3.特征提取

自定义CNN模型,本次采取ResNet18来提取图像特征,下次可以试试VGG, AlexNet

#创建自定义的神经网络模型
class XunFeiNet(nn.Module):  #继承自torch.nn模块中的Module类
    def __init__(self):
        super(XunFeiNet, self).__init__() #super函数调用父类的构造函数
        model = models.resnet18(True)     #下载并加载预训练的权重
#ResNet-18是一个深度残差网络,用于图像分类任务,具有18层卷积层和全连接
        model.avgpool = nn.AdaptiveAvgPool2d(1) 
#使用自适应平均池化层,将输出的特征图大小调整为1x1
        model.fc = nn.Linear(512, 25)
#使用一个全连接层,将输入的512维特征向量映射为25维的输出向量
#25是分类的类别数
        self.resnet = model
    def forward(self, img): #定义模型的前向传播逻辑的
        out = self.resnet(img)
        return out
        
model = XunFeiNet()
model = model.to(device) 
#有GPU设备,使用GPU加速计算;否则使用CPU进行计算
criterion = nn.CrossEntropyLoss().cuda() 
#创建了一个交叉熵损失函数对象,计算模型输出和真实标签之间的损失值,并进行反向传播和优化
optimizer = torch.optim.AdamW(model.parameters(), 0.001)
#创建了一个AdamW优化器,model.parameters()是对象优化器要更新的参数;还传入0.001作为参数,表示优化器的学习率

AdamW是一种改进的Adam算法,可以更好地处理权重衰减和学习率衰减等问题

 4.模型训练、评估与优化

生成测试集提交结果submit.csv

# 对模型进行一轮训练,并返回训练集的平均损失值
def train(train_loader, model, criterion, optimizer):
#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):
#val_loader是一个数据加载器对象,用于批量加载验证集的数据和标签;
    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)
#使用torch.argmax函数对output变量沿着第1个维度(即每个样本的输出向量)求最大值的索引,得到一个一维张量,表示每个样本的预测标签。然后使用torch.eq函数对预测标签和真实标签进行比较,得到一个布尔型张量,表示每个样本的预测是否正确。然后使用torch.sum函数对布尔型张量求和,得到一个标量,表示该批次的正确预测数。然后使用item方法将标量转换为Python数值,并累加到val_acc变量中,以便计算验证集或训练集的总正确预测数。   
            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 = []
#使用torch.no_grad()上下文管理器包裹一段代码块,在这个代码块中不会记录梯度信息,以节省内存和提高运行速度
    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())
#使用data属性获取output变量中的数据部分,并使用cpu方法将其移动到CPU设备上,然后使用numpy模块中的vstack函数将其垂直堆叠起来,并添加到test_pred列表中,以便存储测试集的预测结果。            
    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)

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)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾-光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值