玩转Kaggle:Classify Leaves(叶子分类)——模型设计与训练

文章目录

*
一、数据加载
二、模型构建与训练

+ 1. resnet
+
* 1.1 ResNet-50模型微调+冻结
* 1.2 五折交叉验证
* 1.3 模型预测
* 1.4 利用K-Fold结果投票获取最终提交数据
* 1.5 kaggle提交
+ 2. resnext
+
* 2.1 resnext50_32x4d模型微调+冻结层
* 2.2 五折交叉验证
* 2.3 Resnext模型预测
* 2.4 K-Fold模型投票获取最优解
三、总结

趁假期的最后一天填上kaggle叶子分类的坑,本来想把这个比赛的成绩刷到95%以上再写这篇Blog记录的,但是后面学习工作比较多,刷比赛可能并不是很划算。这里基于 Charlesyyun提供的baseline代码进行了模型设计和训练。主体思路其实没什么多说的,只是通过比赛可以将之前学到的东西融合整理起来,这能够较大地提高动手的能力。

7th: ResNeSt+ResNeXt+DenseNet (0.98840)

数据分析请参考前一篇Blog: 玩转Kaggle:Classify Leaves(叶子分类)——数据分析篇

一、数据加载

import torch.utils.data as Data
from torchvision import transforms
import torchvision
from PIL import Image
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder,OneHotEncoder
import torch
from torch import nn
import d2l.torch as d2l
from sklearn.model_selection import train_test_split,KFold
from torch.optim.lr_scheduler import CosineAnnealingLR
from tqdm import tqdm
import ttach as tta
  1. 首先构建自己的数据加载器,基于pytorch的dataset类进行复写是个不错的方法
  2. 由于训练数据的标签是用得字符表示,在训练的时候我们需要将其映射到对应的数值上,使用 labelencoder = LabelEncoder()可以得到这样一个映射表
  3. 由于我们的训练数据集太小,需要适当地对数据进行数据增强。由于我是在我个人电脑上进行跑模型,GPU算力有限,所以为了保证内存不溢出,我将图片裁减到了 128*128大小,这极大地影响了我最后结果地精度,如果有条件,请裁减到 224*224比较合适
class LeavesSet(Data.Dataset):
"""
        construct the dataset
"""
    def __init__(self,images_path,images_label,transform=None,train=True):
        self.imgs = [os.path.join('/data/liufeng/project/MachineLearning/LiMu/data/classify-leaves/',"".join(image_path)) for image_path in images_path]

        if train:
            self.train = True
            self.labels = images_label
        else:
            self.train = False

        self.transform = transform

    def __getitem__(self,index):
        image_path = self.imgs[index]
        pil_img = Image.open(image_path)
        if self.transform:
            transform = self.transform
        else:

            transform = transforms.Compose([
                transforms.Resize((224,224)),
                transforms.ToTensor()
                ])
        data = transform(pil_img)
        if self.train:
            image_label = self.labels[index]
            return data,image_label
        else:
            return data

    def __len__(self):
        return len(self.imgs)

def load_data_leaves(train_transform=None,test_transform=None):
    train_data = pd.read_csv('./data/classify-leaves/train.csv')
    test_data = pd.read_csv('./data/classify-leaves/test.csv')

    labelencoder = LabelEncoder()
    labelencoder.fit(train_data['label'])
    train_data['label'] = labelencoder.transform(train_data['label'])
    label_map = dict(zip(labelencoder.classes_,labelencoder.transform(labelencoder.classes_)))
    label_inv_map = {v:k for k,v in label_map.items()}

    train_dataSet = LeavesSet(train_data['image'],train_data['label'],transform=train_transform,train=True)
    test_dataSet = LeavesSet(test_data['image'],images_label=0,transform=test_transform,train=False)

    return (
            train_dataSet,
            test_dataSet ,
            label_map,
            label_inv_map,
           )

定义一下训练数据和测试数据需要进行怎样的数据转换/增强。需要注意一点:一般情况下validation data 应该和test data 使用相同的transform,但是我这里发现的时候已经写好了后面的内容,偷懒不想更改了也就将错就错了。如果有人复写,可以自己考虑一下,其实也不难。


train_transform = transforms.Compose([

    transforms.RandomResizedCrop(128, scale=(0.08, 1.0), ratio=(3.0 / 4.0, 4.0 / 3.0)),
    transforms.RandomHorizontalFlip(),

    transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),

    transforms.ToTensor(),

    transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])])

test_transform = transforms.Compose([
    transforms.Resize(256),

    transforms.CenterCrop(128),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])])

train_dataset,test_dataset,label_map,label_inv_map = load_data_leaves(train_transform,test_transform)

二、模型构建与训练

1. resnet

1.1 ResNet-50模型微调+冻结

微调+冻结部分层:


def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        model = model
        for param in model.parameters():
            param.requires_grad = False

def resnet_model(num_classes, feature_extract = False):
    model_ft = torchvision.models.resnet50(pretrained=True)
    set_parameter_requires_grad(model_ft, feature_extract)
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, num_classes))

    return model_ft

定义超参数


k_folds = 5
num_epochs = 20
learning_rate = 1e-4
weight_decay = 1e-3
loss_function = nn.CrossEntropyLoss()

results = {}

torch.manual_seed(1)

device = d2l.try_gpu()

kfold = KFold(n_splits=k_folds, shuffle=True)

1.2 五折交叉验证


print('--------------------------------------')

for fold, (train_ids,valid_ids) in enumerate(kfold.split(train_dataset)):

    print(f'FOLD {fold}')
    print('--------------------------------------')

    train_subsampler = torch.utils.data.SubsetRandomSampler(train_ids)
    valid_subsampler = torch.utils.data.SubsetRandomSampler(valid_ids)

    trainloader = torch.utils.data.DataLoader(train_dataset,
                        batch_size=64, sampler=train_subsampler, num_workers=4)
    validloader = torch.utils.data.DataLoader(train_dataset,
                        batch_size=64, sampler=valid_subsampler, num_workers=4)

    model = resnet_model(176)
    model = model.to(device)
    model.device = device

    optimizer = torch.optim.Adam(model.parameters(),lr=learning_rate,weight_decay= weight_decay)

    scheduler = CosineAnnealingLR(optimizer,T_max=10)

    for epoch in range(0,num_epochs):
        model.train()

        print(f'Starting epoch {epoch+1}')

        train_losses = []
        train_accs = []

        for batch in tqdm(trainloader):

            imgs, labels = batch
            imgs = imgs.to(device)
            labels = labels.to(device)

            logits = model(imgs)

            loss = loss_function(logits,labels)

            optimizer.zero_grad()

            loss.backward()

            optimizer.step()

            acc = (logits.argmax(dim=-1) == labels).float().mean()

            train_losses.append(loss.item())
            train_accs.append(acc)

        scheduler.step()

    train_loss = np.sum(train_losses) / len(train_losses)
    train_acc = np.sum(train_accs) / len(train_accs)

    print(f"[ Train | {epoch + 1:03d}/{num_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")

    print('Training process has finished. Saving trained model.')
    print('Starting validation')

    print('saving model with loss {:.3f}'.format(train_loss))
    save_path = f'./model/leaf/resnet-fold-{fold}.pth'
    torch.save(model.state_dict(),save_path)

    model.eval()
    valid_losses = []
    valid_accs = []
    with torch.no_grad():
        for batch in tqdm(validloader):
            imgs, labels = batch

            logits = model(imgs.to(device))
            loss = loss_function(logits,labels.to(device))
            acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()

            valid_losses.append(loss.item())
            valid_accs.append(acc)

        valid_loss = np.sum(valid_losses)/len(valid_losses)
        valid_acc = np.sum(valid_accs)/len(valid_accs)
        print(f"[ Valid | {epoch + 1:03d}/{num_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
        print('Accuracy for fold %d: %d' % (fold, valid_acc))
        print('--------------------------------------')
        results[fold] = valid_acc

print(f'K-FOLD CROSS VALIDATION RESULTS FOR {k_folds} FOLDS')
print('--------------------------------')
total_summation = 0.0
for key, value in results.items():
    print(f'Fold {key}: {value} ')
    total_summation += value
print(f'Average: {total_summation/len(results.items())} ')
...

Fold 0: 0.7646294236183167
Fold 1: 0.772324800491333
Fold 2: 0.7688811421394348
Fold 3: 0.7619268894195557
Fold 4: 0.7501959204673767
Average: 0.7635916471481323

1.3 模型预测

使用训练得到的五个模型进行预测结果,并将结果分别保存好

testloader = torch.utils.data.DataLoader(test_dataset,batch_size=64, num_workers=4)

model = resnet_model(176)

model = model.to(device)

for test_fold in range(k_folds):
    model_path = f'./model/leaf/model-fold-{test_fold}.pth'
    saveFileName = f'./kaggle_submission/leaf/submission-fold-{test_fold}.csv'
    model.load_state_dict(torch.load(model_path))

    model.eval()
    tta_model = tta.ClassificationTTAWrapper(model, tta.aliases.five_crop_transform(200,200))

    predictions = []

    for batch in tqdm(testloader):
        imgs = batch
        with torch.no_grad():
            logits = tta_model(imgs.to(device))

            predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())
    preds = []
    for i in predictions:
        preds.append(label_inv_map[i])

    test_data = pd.read_csv('./data/classify-leaves/test.csv')
    test_data['label'] = pd.Series(preds)
    submission = pd.concat([test_data['image'], test_data['label']], axis=1)
    submission.to_csv(saveFileName, index=False)
    print("ResNeSt Model Results Done!!!!!!!!!!!!!!!!!!!!!!!!!!!")
100%|&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;| 138/138 [01:08<00:00, 2.02it s] resnest model results done!!!!!!!!!!!!!!!!!!!!!!!!!!! < code></00:00,>

1.4 利用K-Fold结果投票获取最终提交数据

加载预测结果


df0 = pd.read_csv('./kaggle_submission/leaf/submission-fold-0.csv')
df1 = pd.read_csv('./kaggle_submission/leaf/submission-fold-1.csv')
df2 = pd.read_csv('./kaggle_submission/leaf/submission-fold-2.csv')
df3 = pd.read_csv('./kaggle_submission/leaf/submission-fold-3.csv')
df4 = pd.read_csv('./kaggle_submission/leaf/submission-fold-4.csv')

list_num_label0,list_num_label1,list_num_label2,list_num_label3,list_num_label4 = [],[],[],[],[]
for i in range(len(df0)):
    list_num_label0.append(label_map[df0['label'][i]])
    list_num_label1.append(label_map[df1['label'][i]])
    list_num_label2.append(label_map[df2['label'][i]])
    list_num_label3.append(label_map[df3['label'][i]])
    list_num_label4.append(label_map[df4['label'][i]])

df_all = df0.copy()
df_all.drop(['label'],axis=1,inplace=True)
df_all['num_label0'] = list_num_label0
df_all['num_label1'] = list_num_label1
df_all['num_label2'] = list_num_label2
df_all['num_label3'] = list_num_label3
df_all['num_label4'] = list_num_label4
df_all.head()

imagenum_label0num_label1num_label2num_label3num_label40images/18353.jpg22222222221images/18354.jpg261692926262images/18355.jpg1201201201201203images/18356.jpg1021021121021024images/18357.jpg121120120121120


df_all_transpose = df_all.copy().drop(['image'],axis=1).transpose()
df_all_transpose.head()

0123456789…8790879187928793879487958796879787988799num_label02226120102121748491165169…11953117537311953777345num_label122169120102120748391165170…119531105373110481177373num_label22229120112120748391165169…11753117537377531177373num_label32226120102121745091165169…117531175373117531177373num_label42226120102120748391165169…119531195346119481174673

5 rows × 8800 columns


df_mode = df_all_transpose.mode().transpose()
df_mode.head()

01234022.0NaNNaNNaNNaN126.0NaNNaNNaNNaN2120.0NaNNaNNaNNaN3102.0NaNNaNNaNNaN4120.0NaNNaNNaNNaN


voting_class = []
for each in df_mode[0]:
    voting_class.append(label_inv_map[each])
df_all['label'] = voting_class
df_all.head()

imagenum_label0num_label1num_label2num_label3num_label4label0images/18353.jpg2222222222asimina_triloba1images/18354.jpg26169292626betula_nigra2images/18355.jpg120120120120120platanus_acerifolia3images/18356.jpg102102112102102pinus_bungeana4images/18357.jpg121120120121120platanus_acerifolia


df_submission = df_all[['image','label']].copy()

df_submission.to_csv('./kaggle_submission/leaf/submission-resnet.csv', index=False)
print('Voting results of resnest successfully saved!')
Voting results of resnest successfully saved!

1.5 kaggle提交

玩转Kaggle:Classify Leaves(叶子分类)——模型设计与训练

; 2. resnext

2.1 resnext50_32x4d模型微调+冻结层


def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        model = model
        for param in model.parameters():
            param.requires_grad = False

def resnext_model(num_classes, feature_extract = False, use_pretrained=True):
    model_ft = torchvision.models.resnext50_32x4d(pretrained=use_pretrained)
    set_parameter_requires_grad(model_ft, feature_extract)
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, num_classes))

    return model_ft


k_folds = 5
num_epochs = 30
learning_rate = 1e-3
weight_decay = 1e-3
loss_function = nn.CrossEntropyLoss()

results = {}

torch.manual_seed(42)

kfold = KFold(n_splits=k_folds, shuffle=True)

2.2 五折交叉验证


print('--------------------------------------')

for fold, (train_ids,valid_ids) in enumerate(kfold.split(train_dataset)):

    print(f'FOLD {fold}')
    print('--------------------------------------')

    train_subsampler = torch.utils.data.SubsetRandomSampler(train_ids)
    valid_subsampler = torch.utils.data.SubsetRandomSampler(valid_ids)

    trainloader = torch.utils.data.DataLoader(train_dataset,
                        batch_size=64, sampler=train_subsampler, num_workers=4)
    validloader = torch.utils.data.DataLoader(train_dataset,
                        batch_size=64, sampler=valid_subsampler, num_workers=4)

    model = resnext_model(176)
    model = model.to(device)
    model.device = device

    optimizer = torch.optim.Adam(model.parameters(),lr=learning_rate,weight_decay= weight_decay)

    scheduler = CosineAnnealingLR(optimizer,T_max=10)

    for epoch in range(0,num_epochs):
        model.train()

        print(f'Starting epoch {epoch+1}')

        train_losses = []
        train_accs = []

        for batch in tqdm(trainloader):

            imgs, labels = batch
            imgs = imgs.to(device)
            labels = labels.to(device)

            logits = model(imgs)

            loss = loss_function(logits,labels)

            optimizer.zero_grad()

            loss.backward()

            optimizer.step()

            acc = (logits.argmax(dim=-1) == labels).float().mean()

            train_losses.append(loss.item())
            train_accs.append(acc)

        scheduler.step()

    train_loss = np.sum(train_losses) / len(train_losses)
    train_acc = np.sum(train_accs) / len(train_accs)

    print(f"[ Train | {epoch + 1:03d}/{num_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")

    print('Training process has finished. Saving trained model.')
    print('Starting validation')

    print('saving model with loss {:.3f}'.format(train_loss))
    save_path = f'./model/leaf/resneXt-fold-{fold}.pth'
    torch.save(model.state_dict(),save_path)

    model.eval()
    valid_losses = []
    valid_accs = []
    with torch.no_grad():
        for batch in tqdm(validloader):
            imgs, labels = batch

            logits = model(imgs.to(device))
            loss = loss_function(logits,labels.to(device))
            acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()

            valid_losses.append(loss.item())
            valid_accs.append(acc)

        valid_loss = np.sum(valid_losses)/len(valid_losses)
        valid_acc = np.sum(valid_accs)/len(valid_accs)
        print(f"[ Valid | {epoch + 1:03d}/{num_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
        print('Accuracy for fold %d: %d' % (fold, valid_acc))
        print('--------------------------------------')
        results[fold] = valid_acc

print(f'K-FOLD CROSS VALIDATION RESULTS FOR {k_folds} FOLDS')
print('--------------------------------')
total_summation = 0.0
for key, value in results.items():
    print(f'Fold {key}: {value} ')
    total_summation += value
print(f'Average: {total_summation/len(results.items())} ')
...

Fold 0: 0.7881605625152588
Fold 1: 0.7917798757553101
Fold 2: 0.807229220867157
Fold 3: 0.7923932075500488
Fold 4: 0.7697393894195557
Average: 0.7898604273796082

2.3 Resnext模型预测

testloader = torch.utils.data.DataLoader(test_dataset,batch_size=64, num_workers=4)

model = resnext_model(176)

model = model.to(device)

for test_fold in range(k_folds):
    model_path = f'./model/leaf/resneXt-fold-{test_fold}.pth'
    saveFileName = f'./kaggle_submission/leaf/resneXt-submission-fold-{test_fold}.csv'
    model.load_state_dict(torch.load(model_path))

    model.eval()
    tta_model = tta.ClassificationTTAWrapper(model, tta.aliases.five_crop_transform(200,200))

    predictions = []

    for batch in tqdm(testloader):
        imgs = batch
        with torch.no_grad():
            logits = tta_model(imgs.to(device))

            predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())
    preds = []
    for i in predictions:
        preds.append(label_inv_map[i])

    test_data = pd.read_csv('./data/classify-leaves/test.csv')
    test_data['label'] = pd.Series(preds)
    submission = pd.concat([test_data['image'], test_data['label']], axis=1)
    submission.to_csv(saveFileName, index=False)
    print("ResNeSt Model Results Done!!!!!!!!!!!!!!!!!!!!!!!!!!!")
100%|&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;| 138/138 [01:45<00:00, 1.31it s] resnest model results done!!!!!!!!!!!!!!!!!!!!!!!!!!! < code></00:00,>

2.4 K-Fold模型投票获取最优解


df0 = pd.read_csv('./kaggle_submission/leaf/resneXt-submission-fold-0.csv')
df1 = pd.read_csv('./kaggle_submission/leaf/resneXt-submission-fold-1.csv')
df2 = pd.read_csv('./kaggle_submission/leaf/resneXt-submission-fold-2.csv')
df3 = pd.read_csv('./kaggle_submission/leaf/resneXt-submission-fold-3.csv')
df4 = pd.read_csv('./kaggle_submission/leaf/resneXt-submission-fold-4.csv')

list_num_label0,list_num_label1,list_num_label2,list_num_label3,list_num_label4 = [],[],[],[],[]
for i in range(len(df0)):
    list_num_label0.append(label_map[df0['label'][i]])
    list_num_label1.append(label_map[df1['label'][i]])
    list_num_label2.append(label_map[df2['label'][i]])
    list_num_label3.append(label_map[df3['label'][i]])
    list_num_label4.append(label_map[df4['label'][i]])

df_all = df0.copy()
df_all.drop(['label'],axis=1,inplace=True)
df_all['num_label0'] = list_num_label0
df_all['num_label1'] = list_num_label1
df_all['num_label2'] = list_num_label2
df_all['num_label3'] = list_num_label3
df_all['num_label4'] = list_num_label4
df_all.head()

imagenum_label0num_label1num_label2num_label3num_label40images/18353.jpg22222222221images/18354.jpg2612126151212images/18355.jpg1201201201201203images/18356.jpg1021021021021024images/18357.jpg120120120120120


df_all_transpose = df_all.copy().drop(['image'],axis=1).transpose()
df_all_transpose.head()

0123456789…8790879187928793879487958796879787988799num_label02226120102120748391165169…117531175373119531177373num_label122121120102120748391165169…117531175373117531177373num_label22226120102120745091165169…117531175373117531177373num_label32215120102120748391165169…117531175373117531177373num_label422121120102120748391165169…117531175373119531127373

5 rows × 8800 columns


df_mode = df_all_transpose.mode().transpose()
df_mode.head()

01234022.0NaNNaNNaNNaN126.0121.0NaNNaNNaN2120.0NaNNaNNaNNaN3102.0NaNNaNNaNNaN4120.0NaNNaNNaNNaN


voting_class = []
for each in df_mode[0]:
    voting_class.append(label_inv_map[each])
df_all['label'] = voting_class
df_all.head()

imagenum_label0num_label1num_label2num_label3num_label4label0images/18353.jpg2222222222asimina_triloba1images/18354.jpg261212615121betula_nigra2images/18355.jpg120120120120120platanus_acerifolia3images/18356.jpg102102102102102pinus_bungeana4images/18357.jpg120120120120120platanus_acerifolia


df_submission = df_all[['image','label']].copy()

df_submission.to_csv('./kaggle_submission/leaf/submission-resneXt.csv', index=False)
print('Voting results of resnest successfully saved!')
Voting results of resnest successfully saved!

一言难尽这个模型的精度还没有resnet高,就不贴出来了

三、总结

  1. 数据增强对于数据量小的数据集训练特别重要
  2. 由于GPU受限,对原始数据做了小裁减很大程度上影响了我最终的精度。
  3. 对于数据的增强采用cutmix也是很好的方法
  4. resnet和resnext模型对于图片分类是应用非常广的模型。
  5. 微调模型是我们常用的方法,将类似的数据集中训练好的模型直接迁移过来可以极大地节省训练的时间和消耗,同时初始值更加符合数据的分布,更容易获得全局最优解。
  6. 使用K-Fold交叉验证,然后进行模型结果投票,得到的结果更加准确。当然也可以使用多个不同类型的模型的结果进行投票,这样得到的结果往往误差更小。

Original: https://blog.csdn.net/jerry_liufeng/article/details/120106532
Author: 留小星
Title: 玩转Kaggle:Classify Leaves(叶子分类)——模型设计与训练

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/665824/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球