对于自定义dataset和数据处理的一些讨论——以kaggle中的图像分类为例(一)

不知道在干啥的一学期过去了。想做做总结,把收获的一些零碎的东西,整理一下。供自己查看,当然,如果能帮助到一些人,也是我的荣幸。

我将使用kaggle平台一个简单的木薯叶分类项目作为承载,链接如下:Cassava Leaf Disease Classification | Kaggle

之所以选这个平台,还是看中它不需要自己配环境,也不用自己下数据集,还能白嫖显卡,属于是对新手非常的友好了。

让我们开始吧

对于自定义dataset和数据处理的一些讨论——以kaggle中的图像分类为例(一)_第1张图片

这个数据集的整体结构如上图。具体情况不介绍,平台项目中有,在这里,只用到了train_images和train_csv两个文件。

一.数据处理

在上面的大文件中,train_images里是jpg的本身,而train.csv则展示了每张照片的名称和对应的标签


1.对train_images文件中的图片进行分析

import os
import cv2

basic_root = '/kaggle/input/cassava-leaf-disease-classification'

#遍历文件夹
image_list = os.listdir(os.path.join(basic_root,'train_images'))
print('total number picture is ',len(image_list))

#每张图片的大小很重要,创建一个字典,返回图片的shape总共有多少类,每种类有多少张,这里为了避免卡顿,只查看了前300张
#提示 dictionary.get(a,b):查找a,若不存在,返回b
image_shape = {}
for image_name in image_list[0:300]:
    image = cv2.imread(os.path.join(basic_root,'train_images',image_name))
    image_shape[image.shape] = image_shape.get(image.shape,0)+1
print(image_shape)

​

2.接下来读取train.csv文件,把其变成dataframe的格式,应用pandas库中的不同方法,可以实现对数据的快速的清洗。

image_csv = pd.read_csv(os.path.join(basic_root,'train.csv'))
print(image_csv)

对于自定义dataset和数据处理的一些讨论——以kaggle中的图像分类为例(一)_第2张图片

label的代号与label的真实名字的对应,已经写在了json文件中,我们可以让csv文件变得更加充实一点。

import json
image_json = json.load(open(os.path.join(basic_root,'label_num_to_disease_map.json')))
print('image_json is:',image_json)
#我们的想法是利用map函数,把pdframe新增一列出来,用来存放数字label所对应的实际含义
#1.把json变成字典,记得将键的类型变成int型
image_json_tolabel = {int(i):j for i,j in image_json.items()}
print('image_json_tolabel is',image_json_tolabel)
#2.启动
image_csv['label_name'] =image_csv['label'].map(image_json_tolabel)
print(image_csv)

对于自定义dataset和数据处理的一些讨论——以kaggle中的图像分类为例(一)_第3张图片

csv变得顺眼多了以后,我们要看一看,里面总共有多少类,每一个类的图片的比例是多少

import seaborn as sn
import matplotlib.pyplot as plt
plt.figure(figsize = (6,6))
sn.countplot(y = 'label_name',data = image_csv)

对于自定义dataset和数据处理的一些讨论——以kaggle中的图像分类为例(一)_第4张图片

还可以有一些更为精细的可视化操作

import math
def visualize_batch(image_names,labels):
    plt.figure(figsize=(18,18))
    #有顺序地画出image_name对应的图片,将label作为图片的title
    for index,(image_name,label) in enumerate(zip(image_names,labels)):
        plt.subplot(int(math.sqrt(len(image_names))),int(math.sqrt(len(image_names))), index + 1)
        image = cv2.imread(os.path.join(basic_root,'train_images',image_name))
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        print(image.shape)
        plt.title(f"Class: {label}", fontsize=12)
        plt.imshow(image)

    plt.show()
#从原始csv中sample9个样本   
tmp_csv = image_csv.sample(9)
#注意,visualize_batch输入的参数,类型是list,dataframe操作中非常多种方法可以把某一行或者某一列变成list,或者array,
#这也是我个人觉得在处理数据中pd方便的原因
visualize_batch(tmp_csv['image_id'].values,tmp_csv['label_name'].values)

二.dataset 和dataloader构造方法探索

 把 csv中,’image_id'的一列的10个元素,以及‘label'的一列的10个元素,单独提取出来,组成一个新list,其实这些操作完全可以在dataset init方法中完成,只是为了让dataset更加简单和方便理解。

#下面开始构造dataset和dataloader
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader

data_image = image_csv['image_id'].values
target = image_csv['label'].values
data = list(zip(data_image[:10],target[:10]))

import cv2
from PIL import Image

当写到这里的时候,我产生了一个疑惑,PIL和cv2读取图片到底有什么区别。我在数据处理的时候用的是cv2,但是dataset好像大家一般都用PIL。通过查询资料,我写了一个dataset去看一下两者的关系。

from torchvision.transforms import transforms
class vision_dataset(Dataset):
    def __init__(self,data,use_cv2,transform = None):
        self.data = data
        self.transform = transforms.Compose([
            transforms.ToTensor()      # 这里仅以最基本的为例
        ])
        self.use_cv2 = use_cv2
    
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self,index):
        image = self.data[index][0]
        if self.use_cv2:
            image = cv2.imread(os.path.join(basic_root,'train_images',image))#读取的是BGR数据
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)#转成RGB模式
            #以上两个的顺序都是H,W,C。需要转化为C,H,W
            image = torch.from_numpy(image).permute(2, 0, 1)/255
        else:
            image = Image.open(os.path.join(basic_root,'train_images',image))  # 读取到的是RGB, W, H, C
            image = self.transform(image)   # transform转化image为:C, H, W

        label = self.data[index][1]
                
        return image,label
    
#实例化
pil_dataset = vision_dataset(data,use_cv2 = False)
cv2_dataset = vision_dataset(data,use_cv2 = True)

#取出两个不同的dataset序列的第一个组合。
image_cv2,label_cv2 = cv2_dataset[0]
image_pil,label_pil = pil_dataset[0]

print(image_pil)
print(image_cv2)
print(torch.sum(image_cv2-image_pil,dim = (0,1,2)))
tensor([[[0.1294, 0.1294, 0.1490,  ..., 0.5451, 0.2941, 0.2392],
         [0.1294, 0.1333, 0.1451,  ..., 0.1922, 0.1176, 0.1765],
         [0.1333, 0.1373, 0.1412,  ..., 0.1569, 0.2314, 0.3373],
         ...,
         [0.0510, 0.0471, 0.0471,  ..., 0.2745, 0.3098, 0.3294],
         [0.0510, 0.0471, 0.0549,  ..., 0.3098, 0.2980, 0.2980],
         [0.0510, 0.0471, 0.0549,  ..., 0.3176, 0.2588, 0.2275]],

        [[0.3451, 0.3490, 0.3490,  ..., 0.6471, 0.3882, 0.3333],
         [0.3412, 0.3451, 0.3490,  ..., 0.2824, 0.2039, 0.2588],
         [0.3373, 0.3412, 0.3451,  ..., 0.2275, 0.3020, 0.4118],
         ...,
         [0.1922, 0.1882, 0.1882,  ..., 0.3255, 0.3608, 0.3882],
         [0.1922, 0.1882, 0.1843,  ..., 0.3608, 0.3490, 0.3490],
         [0.1922, 0.1882, 0.1843,  ..., 0.3686, 0.2980, 0.2667]],

        [[0.4627, 0.4549, 0.4627,  ..., 0.4941, 0.2863, 0.2471],
         [0.4510, 0.4471, 0.4431,  ..., 0.1176, 0.0627, 0.1294],
         [0.4235, 0.4235, 0.4275,  ..., 0.0314, 0.0980, 0.1961],
         ...,
         [0.0039, 0.0000, 0.0000,  ..., 0.1059, 0.1490, 0.1725],
         [0.0039, 0.0000, 0.0000,  ..., 0.1490, 0.1412, 0.1412],
         [0.0039, 0.0000, 0.0000,  ..., 0.1569, 0.0941, 0.0706]]])
tensor([[[0.1294, 0.1294, 0.1490,  ..., 0.5451, 0.2941, 0.2392],
         [0.1294, 0.1333, 0.1451,  ..., 0.1922, 0.1176, 0.1765],
         [0.1333, 0.1373, 0.1412,  ..., 0.1569, 0.2314, 0.3373],
         ...,
         [0.0510, 0.0471, 0.0471,  ..., 0.2745, 0.3098, 0.3294],
         [0.0510, 0.0471, 0.0549,  ..., 0.3098, 0.2980, 0.2980],
         [0.0510, 0.0471, 0.0549,  ..., 0.3176, 0.2588, 0.2275]],

        [[0.3451, 0.3490, 0.3490,  ..., 0.6471, 0.3882, 0.3333],
         [0.3412, 0.3451, 0.3490,  ..., 0.2824, 0.2039, 0.2588],
         [0.3373, 0.3412, 0.3451,  ..., 0.2275, 0.3020, 0.4118],
         ...,
         [0.1922, 0.1882, 0.1882,  ..., 0.3255, 0.3608, 0.3882],
         [0.1922, 0.1882, 0.1843,  ..., 0.3608, 0.3490, 0.3490],
         [0.1922, 0.1882, 0.1843,  ..., 0.3686, 0.2980, 0.2667]],

        [[0.4627, 0.4549, 0.4627,  ..., 0.4941, 0.2863, 0.2471],
         [0.4510, 0.4471, 0.4431,  ..., 0.1176, 0.0627, 0.1294],
         [0.4235, 0.4235, 0.4275,  ..., 0.0314, 0.0980, 0.1961],
         ...,
         [0.0039, 0.0000, 0.0000,  ..., 0.1059, 0.1490, 0.1725],
         [0.0039, 0.0000, 0.0000,  ..., 0.1490, 0.1412, 0.1412],
         [0.0039, 0.0000, 0.0000,  ..., 0.1569, 0.0941, 0.0706]]])
tensor(0.)

感兴趣的可以看一看。读出来的据说还是略微有一些区别的(虽然我读出来的没啥区别),主要是库的原因。为了和其他人保持一致,也为了写起来方便。最重要的是传言PIL读出来的图片训练容易收敛。建议使用PIL方法。

注意在实际操作中,dataset还缺少一部分,就是在test_epoch的时候应该怎么写,在之后的test的构建中,dataset __getitem__不能返回label了,因为test_data一定是没有label的,不过无伤大雅,这个notebook的问题不是在于讨论这个,之后我会出一个比较完整的训练流程。

我刚刚接触深度学习的时,一直对dataset有种恐惧感,不知道它到底是什么,怎么使用,现在模模糊糊的对它有了一点感觉。

dataset是一个框架,对于一般的任务,只需要覆写Dataset的3个方法,就可以让输入的数据,按照你想要的方式,整整齐齐的走出来。

尤其是__getitem__方法,它决定了,你的数据是如何被喂给模型的。如果非让我用一个生活中的例子来表述的话,我会把它比成切猪肉,鉴于大家都喜欢买瘦肉,所以说,一刀下去,大范围的瘦肉(data)和小范围的肥肉(label)都被割了一块下来。

写完dataset,dataloader就是一行代码的事情。还是拿猪肉做比喻,这个loader相当于是在选择切多厚的肉片(batcg_size)。

dataloader = DataLoader(pil_dataset, batch_size=2, shuffle=False, num_workers=1)

在我看来,dataloader就是一个小型的dataset。从dataloader里面取数据,我见过的一般有两种,以取出label为例。

#1.
for i, (images, labels) in enumerate(dataloader):
    print(labels)
tensor([0, 3])
tensor([1, 1])
tensor([3, 3])
tensor([2, 0])
tensor([4, 3])
#2.
iter_dataloader = iter(dataloader)
for i in range(int(len(pil_dataset)/2)):
    images, labels = next(iter_dataloader)
    print(labels)
tensor([0, 3])
tensor([1, 1])
tensor([3, 3])
tensor([2, 0])
tensor([4, 3])

如果你记性不好的话,咱们可以把dataset中的label打印出来。看一下和dataloader的一样不一样。

for i in range(len(pil_dataset)):
    images,labels = pil_dataset[i]
    print(labels)
0
3
1
1
3
3
2
0
4
3

可以很清楚的看到,dataloader的label就是dataset的label以batch_size的大小成批输出的。类型略微有区别,dataloader输出的是tensor型的变量。

好困。。。。。下一篇我想讨论一个如何更加自由的定义一个dataset,今天就到这里了。

你可能感兴趣的:(人工智能,分类,pytorch)