不知道在干啥的一学期过去了。想做做总结,把收获的一些零碎的东西,整理一下。供自己查看,当然,如果能帮助到一些人,也是我的荣幸。
我将使用kaggle平台一个简单的木薯叶分类项目作为承载,链接如下:Cassava Leaf Disease Classification | Kaggle
之所以选这个平台,还是看中它不需要自己配环境,也不用自己下数据集,还能白嫖显卡,属于是对新手非常的友好了。
让我们开始吧
这个数据集的整体结构如上图。具体情况不介绍,平台项目中有,在这里,只用到了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)
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)
csv变得顺眼多了以后,我们要看一看,里面总共有多少类,每一个类的图片的比例是多少
import seaborn as sn
import matplotlib.pyplot as plt
plt.figure(figsize = (6,6))
sn.countplot(y = 'label_name',data = image_csv)
还可以有一些更为精细的可视化操作
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,今天就到这里了。