(动手学习深度学习)第13章 实战kaggle竞赛:树叶分类

news2024/11/18 3:45:46

文章目录

  • 实战kaggle比赛:树叶分类
      • 1. 导入相关库
      • 2. 查看数据格式
      • 3. 制作数据集
      • 4. 数据可视化
      • 5. 定义网络模型
      • 6. 定义超参数
      • 7. 训练模型
      • 8. 测试并提交文件
  • 竞赛技术总结
      • 1. 技术分析
      • 2. 数据方面
      • 模型方面
      • 3. AutoGluon
      • 4. 总结

实战kaggle比赛:树叶分类

kaggle竞赛链接

数据集格式如下

  • image文件夹:27153张叶子图片,编号为: 0到27152
  • sample_submission.csv(提交文件): 有8800个样本(18353到27152),2列(图片名称、预测类别)
  • test.csv(测试文件):有8800个样本(18353到27152),1列(图片名称)
  • train.csv(训练文件): 有18353个样本(0到18352),2列(图片名称,所属类别)

解题思路

  • 首先数据集是打乱随机分布,要通过train.csv将iamge的所有图片按照不同类别分配所属的文件夹
  • 然后数据增强、设计模型、训练模型

1. 导入相关库

import torch
import torch.nn as nn
import pandas as pd
import numpy as np
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os
import matplotlib.pyplot as plt
import torchvision.models as models
# 下面时用来画图和显示进度条的两个库
from tqdm import tqdm  # 一个用于迭代过程中显示进度条的工具库
import seaborn as sns  # 在matplotlib基础上面的封装库,方便直接传参数调用

2. 查看数据格式

# 查看label文件格式
labels_dataframe = pd.read_csv("E:\\219\\22chenxiaoda\\experiment\\pythonProject\\data\\classify-leaves\\classify-leaves/train.csv")
labels_dataframe.head()

在这里插入图片描述

# 查看labels摘要:数值列的统计汇总信息
labels_dataframe.describe()

在这里插入图片描述
可视化数据集不同类别的样本数

# 用横向柱状图可视化不同类别中图片个数
def barw(ax):
    for p in ax.patches:
        val = p.get_width()  # 柱状图的高度即种类钟图片的数量
        x = p.get_x() + p.get_width()  # x位置
        y = p.get_y() + p.get_height()  # y位置
        ax.annotate(round(val, 2), (x, y))  # 注释文本的内容,被注释的坐标点
plt.figure(figsize=(15, 30))
# sns.countplot()函数: 以bar的形式展示每个类别的数量
ax0 = sns.countplot(y=labels_dataframe['label'], order=labels_dataframe['label'].value_counts().index)
barw(ax0)
plt.show()

在这里插入图片描述
将176个英文类别转换成对应的数据标签,方便训练。

# 将label文件排序
# set():函数创建一个无序不重复元素集
# list():创建列表
# sorted():返回一个排序后的新序列,不改变原始序列(默认按照字母升序)
leaves_labels = sorted(list(set(labels_dataframe['label'])))
n_classes = len(leaves_labels)
print(n_classes)
leaves_labels[:5]

在这里插入图片描述

# 将label文件排序
# set():函数创建一个无序不重复元素集
# list():创建列表
# sorted():返回一个排序后的新序列,不改变原始序列(默认按照字母升序)
leaves_labels = sorted(list(set(labels_dataframe['label'])))
n_classes = len(leaves_labels)
print(n_classes)
leaves_labels[:5]

在这里插入图片描述
再将数字转换成对应的标签:方便最后预测的时候应用

# 再将数字转换成对应的标签:方便最后预测的时候应用
num_to_class = {v : k for k,v in class_to_num.items()}
num_to_class

3. 制作数据集

# 继承pytorch的dataset,创建自己的
class LeavesData(DataLoader):
    def __init__(self, csv_path, file_path, mode='train', valid_ratio=0.2, resize_height=256, resize_with=256):
        """
        :param csv_path: csv文件路径
        :param file_path: 图像文件所在路径
        :param valid_ratio: 验证集比例
        :param resize_height:
        :param resize_with:
        """
        self.resize_height = resize_height
        self.resize_weight = resize_with
        self.file_path = file_path
        self.mode = mode

        # 读取csv文件
        # 利用pandas读取csv文件
        # pandas.read_csv(“data.csv”)默认情况下,会把数据内容的第一行默认为字段名标题。
        # 添加“header=None”,告诉函数,我们读取的原始文件数据没有列索引。因此,read_csv为自动加上列索引。
        # self.data_info = pd.read_csv(csv_path, header=None)
        self.data_info = pd.read_csv(csv_path)
        # 计算length
        self.data_len = len(self.data_info.index)
        self.train_len = int(self.data_len * (1 - valid_ratio))

        if mode == 'train':
            # 第一列包含图像文件的名称
            # 数据源是ndarray时,array仍然会copy出一个副本,占用新的内存,但asarray不会。
            self.train_image = np.asarray(self.data_info.iloc[0: self.train_len, 0])
            self.train_label = np.asarray(self.data_info.iloc[0:self.train_len, 1])
            self.image_arr = self.train_image
            self.label_arr = self.train_label
        elif mode == 'valid':
            self.valid_image = np.asarray(self.data_info.iloc[self.train_len:, 0])
            self.valid_label = np.asarray(self.data_info.iloc[self.train_len:, 1])
            self.image_arr = self.valid_image
            self.label_arr = self.valid_label
        elif mode == 'test':
            self.test_image = np.asarray(self.data_info.iloc[0:, 0])
            self.image_arr = self.test_image

        self.real_len = len(self.image_arr)


        print(f' Finished reading the {mode} set of Leaves Dataset ({self.real_len} samples found)')

    def __getitem__(self, index):
        # 从image_arr中得到索引对应的文件名
        single_image_name = self.image_arr[index]

        # 读取图像文件
        img_as_img = Image.open(self.file_path + single_image_name)

        # 设置好需要转换的变量, 还包括一系列的normalize等操作
        if self.mode == 'train':
            transform = transforms.Compose([
                transforms.Resize(224),
                transforms.RandomHorizontalFlip(),
                transforms.ToTensor(),
                transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
            ])
        else:
            transform = transforms.Compose([
                transforms.Resize(224),
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
            ])
        img_as_img = transform(img_as_img)

        if self.mode == 'test':
            return img_as_img
        else:
            # 得到train和valid的字符串label
            label = self.label_arr[index]
            # 字符串label-->数字label
            number_label = class_to_num[label]

            return img_as_img, number_label   # 返回每一个index对应的照片数据和对应的label

    def __len__(self):
        return self.real_len
train_path = "E:\\219\\22chenxiaoda\\experiment\\pythonProject\\data\\classify-leaves\\classify-leaves/train.csv"
test_path = "E:\\219\\22chenxiaoda\\experiment\\pythonProject\\data\\classify-leaves\\classify-leaves/test.csv"
# csv文件中已经定义到image的路径, 因此这里知道上一级目录
img_path = 'E:\\219\\22chenxiaoda\\experiment\\pythonProject\\data\\classify-leaves\\classify-leaves/'

train_dataset = LeavesData(train_path, img_path, mode='train')
val_dataset = LeavesData(train_path, img_path, mode='valid')
test_dataset = LeavesData(test_path, img_path, mode='test')


print(train_dataset)
print(val_dataset)
print(test_dataset)

在这里插入图片描述

# 定义dataloader
train_loader = torch.utils.data.DataLoader(
    dataset=train_dataset, batch_size=32, shuffle=True
)
val_loader = torch.utils.data.DataLoader(
    dataset=val_dataset, batch_size=32,shuffle=False
)
test_loader = torch.utils.data.DataLoader(
    dataset=test_dataset, batch_size=32, shuffle=False
)

4. 数据可视化

# 展示数据
def im_covert(tensor):
    """展示数据"""
    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1, 2, 0)
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))  # 还原标准化,先乘再加
    image = image.clip(0, 1)

    return image

fig = plt.figure(figsize=(20, 12))
columns = 4
rows = 2

dataiter = iter(val_loader)
inputs, classes = dataiter.next()

for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    ax.set_title(num_to_class[int(classes[idx])])
    plt.imshow(im_covert(inputs[idx]))
plt.show()

在这里插入图片描述

5. 定义网络模型

# 是否使用GPU来训练
def get_device():
    return 'cuda' if torch.cuda.is_available() else 'cpu'

device = get_device()
print(device)
# 是否要冻住模型的前面一些层
def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        model = model
        for param in model.parameters():
            param.requires_grad = False
# 选用resnet34模型
# 是否要冻住模型的前面一些层
def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        model = model
        for param in model.parameters():
            param.requires_grad = False
# 使用resnet34模型
def res_model(num_classes, feature_extract=False):
    model_ft = models.resnet34(weights=models.ResNet34_Weights.IMAGENET1K_V1)
    set_parameter_requires_grad(model_ft, feature_extract)
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Linear(num_ftrs, num_classes)

    return model_ft
# 模型初始化
model = res_model(176)
model = model.to(device)
model.device = device
model

6. 定义超参数

learning_rate = 3e-4
weight_decay = 1e-3
num_epoch = 50
model_path = './pre_res_model_32.ckpt'
criterion = nn.CrossEntropyLoss()

不冻住前面的预训练层
- 对预训练层, 使用较小的学习率训练
- 对自定义的分类输出层, 使用较大的学习率

# 对最后定义的全连接层和之前的层采用不同的学习率训练
params_1x = [param for name, param in model.named_parameters()
             if name not in ['fc.weight', 'fc.bias']]
optimizer = torch.optim.Adam(
    # model.parameters(),
    [{'params': params_1x}, {'params': model.fc.parameters(), 'lr': learning_rate * 10}],
    lr=learning_rate, weight_decay=weight_decay
)

7. 训练模型

import time

# 在开头设置开始时间
start = time.perf_counter()  # start = time.clock() python3.8之前可以

best_acc, best_epoch = 0.0, 0
train_loss, train_accs = [], []
valid_loss, valid_accs = [], []

for epoch in range(num_epoch):

    # -----------训练-----------
    model.train()
    train_loss = []
    train_accs = []

    for imgs, labels in tqdm(train_loader):
        #  一个batch由imgs和相应的labels组成。
        imgs = imgs.to(device)
        labels = labels.to(device)
        # 前向传播
        predicts = model(imgs)
        # 计算损失
        loss = criterion(predicts, labels)
        # 梯度清空
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 梯度更新
        optimizer.step()

        # 计算当前batch的精度
        # 转为float就是把true变成1,false变成0;
        # 然后mean就是求这个向量的均值,也就是true的数目除以总样本数,得到acc。
        acc =(predicts.argmax(dim=1) == labels).float().mean()

        # 记录训练损失和精度
        train_loss.append(loss.item())
        train_accs.append(acc)

    # 训练集的平均损失和准确性是一个batch的平均值
    train_loss = sum(train_loss) / len(train_loss)
    train_acc = sum(train_accs) / len(train_accs)

    # 打印训练损失和精度
    print(f'[Train | {epoch + 1 :03d} / {num_epoch:03d}] Train loss = {train_loss:.5f},  Train acc={train_acc:.5f}')

    # --------验证--------
    model.eval()
    valid_loss = []
    valid_accs = []

    for batch in tqdm(val_loader):
        imgs, labels = batch

        # 前向传播
        # 验证不需要计算梯度
        # 使用torch.no_grad()不计算梯度,能加速前向传播过程
        with torch.no_grad():
            predicts = model(imgs.to(device))

        # 计算损失
        loss = criterion(predicts, labels.to(device))
        # 计算精度
        acc = (predicts.argmax(dim=-1) == labels.to(device)).float().mean()

        # 记录验证损失和精度
        valid_loss.append(loss.item())
        valid_accs.append(acc)

    # 跟训练集一样: 验证集的平均损失和准确性是一个batch的平均值
    valid_loss = sum(valid_loss) / len(valid_loss)
    valid_acc = sum(valid_accs) / len(valid_accs)

    # 打印验证损失和精度
    print(f'[Valid | {epoch + 1:03d} / {num_epoch:03d}] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}')

    # 保存迭代过程中最优的模型参数
    if valid_acc > best_acc:
        best_acc = valid_acc
        best_epoch = epoch
        torch.save(model.state_dict(), model_path)
        print(f'Save model with acc{best_acc:.3f}, it is the {epoch} epoch')

print(f'The best model with acc{best_acc:.3f}, it is the {best_epoch} epoch')

# 在程序运行结束的位置添加结束时间
end = time.perf_counter()  # end = time.clock()  python3.8之前可以

# 再将其进行打印,即可显示出程序完成的运行耗时
print(f'运行耗时{(end-start):.4f}')

在这里插入图片描述

8. 测试并提交文件

# 提交文件
saveFileName = './submission32.csv'
# 预测
model = res_model(176)

# 利用前面训练好的模型参数进行预测
model = model.to(device)
model.load_state_dict(torch.load(model_path))

# 模型预测
model.eval()

# 保存预测结果
predictions = []

# 迭代测试集
for batch in tqdm(test_loader):
    imgs = batch
    with torch.no_grad():
        logits = model(imgs.to(device))
    
    # 保存预测结果
    predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())

preds = []
for i in predictions:
    # 将数字标签转换为对应的字符串标签
    preds.append(num_to_class[i])

test_data = pd.read_csv(test_path)
test_data['label'] = pd.Series(preds)
submission = pd.concat([test_data['image'], test_data['label']], axis=1)
submission.to_csv(saveFileName, index=False)
print('Done!!!!!') 

在这里插入图片描述

竞赛技术总结

1. 技术分析

相比于课程介绍的代码,大家主要做了下面这些加强

  • 数据增强,在测试时多次使用稍弱的增强然后取平均
  • 使用多个模型预测,最后结果加权平均
    • 有使用10种模型的,也有使用单一模型的
  • 训练算法和学习率
  • 清理数据

2. 数据方面

  • 有重复图片,可以手动去除
  • 图片背景较多,而且树叶没有方向性,可以做更多数据增强
    • 随机旋转、更大的剪裁
  • 跨图片增强:
    • Mixup: 随机叠加两张图片
    • CutMix:随机组合来自不同图片的块

模型方面

  • 模型多为ResNet变种
    • DenseN儿童, ResNeXt, ResNeSt,···
    • EfficientNet
  • 优化算法多为Adam或其变种
  • 学习率一般是Cosine或者训练不动时往下调

3. AutoGluon

  • 15行代码,安装加训练花时100分钟
    • AutoGluon链接
  • 精度96%
    • 可以通过定制化提升精度
    • 下一个版本将搜索更多的模型超参数
    • AG目前主要仍是关注工业界应用上,非比赛

4. 总结

  • 提升精度思路:根据数据挑选增强,使用新模型、新优化算法,多模型融合,测试时使用增强
  • 数据相对简单,排名有相对随机性
  • 在工业界应用中:
    • 少使用模型融合和测试时增强,计算代价过高
    • 通常固定模型超参数,而将精力主要花在提升数据质量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1270341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

目标检测常用评价指标

1 基本概念 1.1 IOU(Intersection over Union) 1.2 TP TN FP FN 2. 各种率 3. PR曲线 4. mAP的计算 4.1 AP的计算 4.2 mAP 4.3 mAP0.5和mAP0.5:0.95 1.1 IOU(Intersection over Union) 1.2 TP TN FP FN TP(Truth Positive): 预测正类,实际正类&#x…

JIT精益理念下SMT物料配送模式的智能化创新

纬湃汽车电子,作为现代汽车电子行业的佼佼者,专注于为全球汽车制造商提供高品质的电子组件。公司致力于通过采用最先进的技术和流程,持续提升产品质量和生产效率。在这一背景下,纬湃汽车电子的SMT车间的转型升级尤为引人关注。 项…

【C++高阶(六)】哈希的应用--位图布隆过滤器

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C   🔝🔝 哈希的应用 1. 前言2. 位图的概念以及定义3. 位…

WEB渗透—反序列化(九)

Web渗透—反序列化 课程学习分享(课程非本人制作,仅提供学习分享) 靶场下载地址:GitHub - mcc0624/php_ser_Class: php反序列化靶场课程,基于课程制作的靶场 课程地址:PHP反序列化漏洞学习_哔哩哔_…

yolov8 原木识别模型

一、模型介绍 模型基于 yolov8数据集采用SKU-110k,这数据集太大了十几个 G,所以只训练了 10 轮左右就拿来微调了原木数据微调:纯手工标注 200 张左右原木图片,训练 20 轮的效果 PS:因为训练时间比较长 Google 的 Cola…

贪心算法的介绍

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解&#…

开放远程访问MySQL的权限

访问远程数据库时,产生Access denied for user ‘root‘‘xxx.xxx.xxx.xxx‘ (using password: YES)异常的解决办法 一. 异常现象 我编写了一个SpringBoot项目,项目中连接的数据库服务器地址是192.168.87.107,然后打包生成了对应的jar包&am…

【开源】基于Vue+SpringBoot的创意工坊双创管理系统

项目编号: S 049 ,文末获取源码。 \color{red}{项目编号:S049,文末获取源码。} 项目编号:S049,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员端2.2 Web 端2.3 移动端 三、…

EXCEL一对多关系将结果合并到一个单元格

EXCEL一对多关联结果,合并到1个单元格,变成一对一 需求说明 举例说明 假设给出国家省和国家市的对应表,因为每个省都有很多个城市(如图1,截取了部分),属于一对多的情况; 如何将同…

NX二次开发UF_CURVE_create_conic 函数介绍

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_create_conic Defined in: uf_curve.h int UF_CURVE_create_conic(UF_CURVE_conic_p_t conic_data, tag_t * conic ) overview 概述 Creates a conic curve. See the des…

如何成为一名高效的前端开发者(10X开发者)

如今,每个人都想成为我们所说的“10倍开发者”。然而,这个术语经常被误解和高估。 本质上,一个高效或者10倍开发者,在我看来,是指那些能够充分利用所有可用工具的人,通过让这些工具处理冗余和重复的任务&am…

数据库系统概述之数据库优化

为什么需要进行优化? 数据库性能瓶颈 数据库服务器的性能受许多因素影响,包括硬件能力、系统规模、业务模型及架构、代码设计、数据库表设计、系统环境等。 因此,可以从几个方面进行数据库优化 喜欢点赞收藏,如有疑问&#xff…

建设“参与城市”大学--SMU在2023年绿色金融全球论坛上分享观点

2023年11月21日,由新加坡管理大学(SMU,简称新大)和中国人民大学(RUC,简称人大)联合主办的“绿色金融与治理:从承诺到行动”全球论坛在北京召开。论坛汇集了来自新加坡、中国及世界各…

内衣洗衣机和手洗哪个干净?内衣洗衣机便宜好用的牌子推荐

单纯的用手清洗内衣,是很难的清洁到内衣物上的每一个角落的污渍。另外,手洗时所用的水以及香皂并不能彻底杀死衣物上的细菌,反而会在内衣物上滋生细菌。长时间穿这种内衣,对身体有潜在的危害。相比较而言,专用的内衣洗…

亚马逊产品如何在 TikTok 上推广?

对亚马逊卖家而言,TikTok是提升品牌社交媒体影响力的理想平台。该平台在过去一年中实现了飞速增长,使得营销变得既快捷又有趣,且高效。本文将详细阐述如何在TikTok推广亚马逊产品,并如何策划更强大的品牌营销活动。 各大品牌纷纷…

C++基础 -20- 基类覆盖父类

引用的方式覆盖 #include "iostream" using namespace std; class base { public:base() {}base(int a, int b) : a(a), b(b){}int a;int b; }; class step1 : public ::base { public:step1() {} };int main() {step1 rlxy;rlxy.a 100;rlxy.b 200;cout <<…

【趣味篇】Scratch之windows11系统

【作品展示】windows11系统 操作&#xff1a;点击小绿旗进入windows11主页面&#xff0c;不仅是能打开浏览器&#xff0c;还可以进行背景切换等功能。

身份证mod11-2校验规则

这几天碰到一个需求是实现身份证最后一位的校验&#xff0c;需求文档里面写了个公式&#xff0c;没看懂&#xff08;数学早就还给老师了&#xff09;&#xff0c;于是各种查资料&#xff0c;发现网上的资料要么只给了说明&#xff0c;要么给了个固定的代码&#xff0c;但是写的…

eNSP实验

前言 本文记录了使用eNSP进行组网&#xff0c;学习、巩固一些之前学的网络基础知识和协议。 一&#xff1a;同网段、网关互通 网络拓扑如下&#xff1a; AR1的配置&#xff1a; interface G0/0/0 ip address 192.168.10.1 24 PC1和PC2的配置(IP地址和网关设置) 最终实现PC1…

指纹芯片的工作原理及应用领域详解

指纹芯片是一种利用指纹识别技术的电子设备,可以通过扫描人体指纹的纹理特征,将其转化为数字化信息并进行存储和识别。指纹芯片广泛应用于各个领域,包括智能手机、银行和金融、门禁系统、身份验证等,因其高度准确、快速便捷的特点,得到了广大用户的青睐。 指纹芯片的原理是基于…