《kaggle》--Classify leaves--baseline代码分析

一、任务

任务是预测叶子图像的类别。该数据集包含 176 个类别、18353 张训练图像、8800 张测试图像。每个类别至少有 50 张图像用于训练

二、数据集分析

从kaggle官网下载来的数据信息有train.csv,test.csv,以及images文件夹
train.csv包括18353张图片路径以及对应标签
test.csv包含8800张图片路径
images文件夹下是所有图片.jpg

三、导入包

1.pandas用于处理csv文件
2.Dataset,DataLoader用于处理自定义数据集
3.transforms是图片增强(待改进,比如用cutmix)
4.models提供了resnet等常用的网络结构,可直接调用
5.tqdm 进度条可视化
6.seaborn 非常丰富的可视化工具

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

四、观察数据集

# ./代表当前目录
# ../代表上级目录
labels_dataframe = pd.read_csv('./train.csv',header=0)
labels_dataframe.head(5)

在这里插入图片描述

#使用describe()作数据统计
labels_dataframe.describe()

使用pd.describe()函数生成描述性统计数据,统计数据集的集中趋势,分散和行列的分布情况,不包括 NaN值。可以看到训练集总共有18353张图片,标签有176类。
在这里插入图片描述

# 显示出每种类别的叶子有多少个图 min:51 max:353
def barw(ax):
    
    for p in ax.patches:
        val = p.get_width() #height of the bar
        x = p.get_x()+ p.get_width() # x- position 
        y = p.get_y() + p.get_height()/2 #y-position
        ax.annotate(round(val,2),(x,y))
        

plt.figure(figsize = (15,30))
# sns.countplot() 用于画类别特征的频数条形图
#(x轴是count数,y轴是类别。)
ax0 =sns.countplot(y=labels_dataframe['label'],order=labels_dataframe['label'].value_counts().index)
barw(ax0)
plt.show()

在这里插入图片描述

把label标签按字母排个序,这里仅显示前10个。
先用set()对label去重,然后转成list再排序

leaves_labels = sorted(list(set(labels_dataframe['label'])))
n_class = len(leaves_labels)
print(n_class)
leaves_labels[:10]

在这里插入图片描述

编写字典 {label:num}
num用range()生成
先用zip()捆绑label:num键值对,再用dict()生成字典

class_to_num = dict(zip(leaves_labels,range(n_class)))
class_to_num

在这里插入图片描述

再转换回来,方便最后预测的时候使用
items()同时返回字典键值对


num_to_class = {v : k for k ,v in class_to_num.items()}

五、处理数据集 (使用class)

现在已经制作了种类字典信息,但数据集还没有划分训练集,测试集以及验证集。接下来的代码要逐步弄清楚
我会分解LeavesData类的函数并解析代码

1.先声明类并继承Dataset

# 继承pytorch的dataset,手动制作自己的数据集
class LeavesData(Dataset):

2.init()

init()会根据mode决定生成是“训练数据集”还是“验证数据集”还是“测试数据集”,三者合一

参数

	def __init__(self,csv_path,file_path,mode='train',valid_ratio=0.2,resize_height=256,resize_width=256):
		self.resize_height = resize_height
	    self.resize_width = resize_width       
	    self.file_path = file_path
	    self.mode = mode
	    # 读取 csv 文件
	    # 利用pandas读取csv文件
	    self.data_info = pd.read_csv(csv_path,header=None) #header=None是去掉表头部分
	    
	    # 计算length
	    self.data_len = len(self.data_info.index) - 1
	    #训练集长度  = 0.8 倍数据集
	    #该长度用于后面iloc切数据
	    self.train_len = int(self.data_len * (1 - valid_ratio))

参数解析:
csv_path是表格文件路径,目的是得到图片路径字符串
file_path是图片真正路径,相当于拿到真实图片
mode决定是“训练数据集”还是“验证数据集”还是“测试数据集”
valid_ratio划分验证集占训练集的比例
resize_height,resize_width规范化图片尺寸
利用read_csv获取表格,去掉表头,因为第一行不是数据

根据mode生成对应的数据集

这里为了统一描述三种数据集的image和label(测试集没有label),定义了image_arrlabel_arr

mode == ‘train’
		if mode == 'train':
	        #第一步,取图片和标签
	           # train_image 第一列包含图像文件的名称 
	         
	        self.train_image = np.asarray(self.data_info.iloc[1:self.train_len,0])
	        self.train_label = np.asarray(self.data_info.iloc[1:self.train_len,1])
	        
	        self.image_arr = self.train_image
	        self.label_arr = self.train_label

iloc()是切片函数,先切行再切列
asarray()将csv变成ndarray
[1:self.train_len]是训练集长度

mode == ‘valid’
	 	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

[self.train_len:]是测试集长度

mode == ‘test’

测试集没有label只有image

		elif mode == 'test':
            self.test_image = np.asarray(self.data_info.iloc[1:,0])
            self.image_arr = self.test_image
打印

这里定义了real_len表示数据集长度

	 	self.real_len = len(self.image_arr)
	    print('Finished reading the {} set of Leaves Dataset ({} samples found)'.format(mode, self.real_len))

3、getitem()

关于__getitem__用法的详解,通俗点讲就是可以用索引访问元素,这里return每一个index对应的image数据(图片增强处理)和对应的label

读取图像

single_image_name image_arr(保存的是数据集图片路径)中得到index对应图像的路径名
img_as_img通过Image.open()读取图像

def __getitem__(self,index):
        # 从 image_arr中得到索引对应的文件名
        single_image_name = self.image_arr[index]
        
        # 读取图像文件 
        img_as_img = Image.open(self.file_path + single_image_name)

图片增强

通常训练集需要做图像增强处理,而验证集和测试集不需要
transforms.Compose([])是对图片进行变换操作
Resize()调整图像尺寸
RandomHorizontalFlip()随机水平翻转,图像增强的操作之一
ToTensor(),转化成张量形式

 #设置好需要转换的变量,还可以包括一系列的nomarlize等等操作
        if self.mode =='train':
            transform = transforms.Compose([
                transforms.Resize((224,224)),
                #train_set 作简单的数据增强
                transforms.RandomHorizontalFlip(p=0.5),  #随机水平翻转 选择一个概率
                transforms.ToTensor()
            ])
        else:
            # valid和test不做数据增强
            transform = transforms.Compose([
                transforms.Resize((224, 224)),
                transforms.ToTensor()
            ])
            
        img_as_img = transform(img_as_img)
        
        if self.mode == 'test':
            return img_as_img
        else:
            # 得到图像的 string label
            label = self.label_arr[index]
             # number label
            number_label = class_to_num[label]
            
            return img_as_img,number_label #返回每一个index对应的图片数据和对应的label

4、len():

 return self.real_len

5、调用LeavesData类生成数据集

定义一下不同数据集的csv_path,并通过更改mode修改数据集类的实例对象
可以看到训练集有14681张图片

train_path = './train.csv'
test_path = './test.csv'

img_path = './'# csv文件中路径已经包含images了,因此这里只到当前目录

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

Finished reading the train set of Leaves Dataset (14681 samples found)
Finished reading the valid set of Leaves Dataset (3672 samples found)
Finished reading the test set of Leaves Dataset (8800 samples found)
<main.LeavesData object at 0x00000163C45E5A60>
<main.LeavesData object at 0x00000163C45E5AC0>
<main.LeavesData object at 0x00000163C45E5730>

6、定义DataLoader用于训练模型

调用torch.utils.data.DataLoader
dataset:数据集名称
batch_size:批量大小
shuffle:是否打乱数据
num_workers经验设置值是自己电脑/服务器的CPU核心数

# 定义data loader
train_loader = torch.utils.data.DataLoader(
        dataset=train_dataset,
        batch_size=8,
        shuffle=False,
        num_workers=5)

val_loader = torch.utils.data.DataLoader(
        dataset=val_dataset,
        batch_size=8,
        shuffle=False,
        num_workers=5)

test_loader = torch.utils.data.DataLoader(
        dataset=test_dataset,
        batch_size=8,
        shuffle=False,
        num_workers=5)

六、定义模型

使用GPU运行

torch.cuda.is_available()验证GPU是否可用

# 看一下是在cpu还是GPU上
def get_device():
    return 'cuda' if torch.cuda.is_available() else 'cpu'
device = get_device()
print(device)

cuda

冻结模型前面的层

只训练顶层的模型参数
精确冻结预训练模型某些层

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

ResNet34预训练模型

num_classes是输出层维度,在该项目应为176
fc是full connect全连接层
fc.in_features参考该链接中 二、

def res_model(num_classes, feature_extract = False, use_pretrained=True):
    model_ft = models.resnet34(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

定义超参数

weight_decay 权重衰减【防止过拟合】

# 超参数
learning_rate = 3e-4
weight_decay = 1e-3
num_epoch = 50
model_path = './pre_res_model.ckpt'

把模型放在GPU上

model = res_model(176)# 176种叶子
model = model.to(device)
model.device = device

定义loss以及optim

criterion = nn.CrossEntropyLoss()
# weight_decay 权重衰减
optimizer = torch.optim.Adam(model.parameters(),lr = learning_rate,weight_decay=weight_decay)

训练网络

下列代码逻辑务必熟练掌握

1.请始终记得DataLoader迭代器返回的是什么?
2.数据要先放在GPU再使用
3.forward() —> loss() —> zero_grad() —>backward() —> step()
4.loss和acc如何直观打印出来?
5.验证集和测试集要固定模型参数with torch.no_grad(),因为不能再训练model.train()和model.eval()的区别主要在有无dropoutBN
6. model.state_dict()模型一旦进化就会被保存,保存的是模型参数

n_epochs = num_epoch

best_acc = 0.0 # 判断模型是否进化 
# 训练50轮
for epoch in range(n_epochs):
    # ---------- Training ----------
    # Make sure the model is in train mode before training.
    model.train()
    train_loss = []
    train_accs = []
    # 迭代batch训练
    for batch in tqdm(train_loader): # 进度条可视化库
        imgs, labels = batch
        # 数据要放在GPU上
        imgs = imgs.to(device)
        labels = labels.to(device)
        
        imgs_hat = model(imgs)
        loss = criterion(imgs_hat,labels)
        optimizer.zero_grad()
        loss.backard()
        optimizer.step()
        # dim = -1 表示最里层
        acc = (imgs_hat.argmax(dim=-1) == labels).float().mean()
        
        train_loss.append(loss.item())
        train_accs.append(acc)
        
    train_loss = sum(train_loss) / len(train_loss)
    train_acc = sum(train_accs) / len(train_accs)    
    print(f"[ Train | {epoch+1:03d}/{n_epochs:03d}] loss = {train_loss:.5f}, acc = {train_acc:.5f}")
    
    
     # ---------- Validation ----------
    model.eval()
    valid_loss = []
    valid_accs = []
    for batch in tqdm(val_loader):
       
        imgs, labels = batch
        # 验证集不需要更新参数
        with torch.no_grad():
            imgs_hat = model(imgs.to(device))
            
        loss = criterion(imgs_hat,labels.to(device))
        
        acc = (imgs_hat.argmax(dim=-1) == labels.to(device)).float().mean()
        valid_loss.append(loss.item())
        valid_accs.append(acc)
    valid_loss = sum(valid_loss) / len(valid_loss)
    valid_accs = sum(valid_accs) / len(valid_accs)
    
    print(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
   
    # if the model improves, save a checkpoint at this epoch
    if valid_accs > best_acc:
        best_acc = valid_accs
        torch.save(model.state_dict(),model_path)
        print('saving model with acc {:.3f}'.format(best_acc))

七、模型评估(预测数据)

导入训练好的模型

手法:先实例化一个model对象,再把训练好的参数放进去

model = res_model(176)
model = model.to(device)
model.load_state_dict(torch.load(model_path))
model.eval()

预测

saveFileName = './submission.csv'
predictions = []

for batch in tqdm(test_loader):
    imgs = batch
    with torch.no_grad():
        imgs_hat = model(imgs.to(device))
    predictions.extend(imgs_hat.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!!!!!!!!!!!!!!!!!!!!!!!!!!!")

八、参考资料

【Kaggle竞赛树叶分类Baseline】上万片树叶分为一百七十六类

nekokiku大佬的baseline

小白一枚,好多手法和技巧不熟练。加油!!!

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Python和Keras库来解决Kaggle Digit Recognizer比赛的代码示例: 首先,导入必要的库: ```python import pandas as pd import numpy as np from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D from keras.optimizers import RMSprop from keras.preprocessing.image import ImageDataGenerator from sklearn.model_selection import train_test_split ``` 然后,读取和处理训练数据和测试数据: ```python train_data = pd.read_csv('train.csv') test_data = pd.read_csv('test.csv') # 将数据分成输入和输出 X_train = train_data.drop(['label'], axis=1) y_train = train_data['label'] # 将输入数据重塑为28x28像素 X_train = X_train.values.reshape(-1, 28, 28, 1) test_data = test_data.values.reshape(-1, 28, 28, 1) # 将像素值转换为浮点数并归一化 X_train = X_train.astype('float32') / 255 test_data = test_data.astype('float32') / 255 # 将输出数据转换为独热编码 y_train = pd.get_dummies(y_train).values ``` 接着,将数据分成训练集和验证集,设置数据增强器并构建卷积神经网络模型: ```python # 将数据分成训练集和验证集 X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.1) # 设置数据增强器 datagen = ImageDataGenerator( rotation_range=10, zoom_range = 0.1, width_shift_range=0.1, height_shift_range=0.1) # 构建卷积神经网络模型 model = Sequential() model.add(Conv2D(filters=32, kernel_size=(5,5), padding='Same', activation='relu', input_shape=(28,28,1))) model.add(Conv2D(filters=32, kernel_size=(5,5), padding='Same', activation='relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same', activation='relu')) model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same', activation='relu')) model.add(MaxPool2D(pool_size=(2,2), strides=(2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(256, activation="relu")) model.add(Dropout(0.5)) model.add(Dense(10, activation="softmax")) # 定义优化器和损失函数 optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0) model.compile(optimizer=optimizer, loss="categorical_crossentropy", metrics=["accuracy"]) ``` 最后,使用训练集和验证集来训练和评估模型,并对测试数据进行预测: ```python # 训练模型 history = model.fit_generator(datagen.flow(X_train, y_train, batch_size=64), epochs=30, validation_data=(X_val, y_val), verbose=2) # 在验证集上评估模型 score = model.evaluate(X_val, y_val, verbose=0) print("Validation loss:", score[0]) print("Validation accuracy:", score[1]) # 对测试数据进行预测 predictions = model.predict(test_data) ``` 这就是一个简单的使用卷积神经网络和数据增强器来解决Kaggle Digit Recognizer比赛的代码示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值