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

OK了家人们,早上起来神清气爽,让我们继续探究如何更加自由的定义dataset,那就到了我们今天的主题。

自定义dataset中的sampler。

书接上回。

sampler顾名思义,就是一个采样器,决定着dataloader在batch_size固定的情况下,取哪几个数据,在简单的情况下,sampler都不需要自己定义,因为pytoch自己本身就给我们提供了两种sampler,一种是顺序采样器,一种就是随机采样器。随着dataloader中shuffle的参数变化而变化。

default_sampler = dataloader.sampler
for i in default_sampler:
    print(i)
0
1
2
3
4
5
6
7
8
9

如果你记性足够好的话,你就会记得,我们的dataset中就是包含了10个图片和其对应的label,而且我们实例化dataloader的时候,shuffle是为False的。顺序采样器就是按照内部的序号,依次取出dataset的数据。

让我们再来看看随机采样器。

dataloader = DataLoader(pil_dataset, batch_size=2, shuffle=True, num_workers=1)
default_sampler = dataloader.sampler
for i in default_sampler:
    print(i)
9
8
3
4
5
2
1
0
7
6

所以说,shuffle这个参数,它与其说是把dataset里的数据打乱顺序,倒不如说是利用sampler将数据的序号打乱,然后变成新队列,然后输出。

问题在这里出现,如果我不想用顺序,也不想用乱序,我想用我定义的规律来去实现数据的输出呢,比如我非常拧巴,我一定要让后一半的数据先进来,前一半的数据后进来呢?

import random
from torch.utils.data.sampler import Sampler

class mysampler(Sampler):
    def __init__(self,dataset):
        halfway_point = int(len(dataset)/2)
        self.first = list(range(halfway_point))
        self.second = list(range(halfway_point,len(dataset)))

    def __iter__(self):
        random.shuffle(self.first)
        random.shuffle(self.second)
        return iter(self.second+self.first)

    def __len__(self):
        return len(dataset)

oursampler = mysampler(pil_dataset)
for i in oursampler:
    print(i)
9
5
8
7
6
4
3
2
0
1

把sampler放在dataloader中

dataloader_shuflle_half = DataLoader(pil_dataset,sampler=oursampler,batch_size=3)
for i,(images,labels) in enumerate(dataloader_shuflle_half):
    print(labels)
tensor([3, 0, 2])
tensor([4, 3, 3])
tensor([0, 1, 3])
tensor([1])

这里label没有提前控制好,每个图片的label设置的不一样的话可能会更加直观。其实看上一段代码我觉得已经说的很清楚了。

这里有一个小bug,我是把后半段打乱,前半段打乱,加到一起,作为输出,5个后半段序号,5个前半段序号,如果输出的时候,batch_size为2时,在第三个batch中,一定会有一个后半段的序号和前半段的序号一块输入进来。那应该怎么办呢,所以我们需要更精细的sampler,那就是batch_sampler。

def chunk(indices,chunk_size):
    return torch.split(torch.tensor(indices),chunk_size)

class batch_samplers(Sampler):
    def __init__(self, dataset, batch_size):
        half_way_point = int(len(dataset)/2)
        self.first = list(range(half_way_point))
        self.second = list(range(half_way_point,len(dataset)))
        self.batch_size = batch_size

    def __iter__(self):
        random.shuffle(self.first)
        random.shuffle(self.second)
        first_batch = chunk(self.first,self.batch_size)
        second_batch = chunk(self.second,self.batch_size)
        combined = list(first_batch+second_batch)
        combined = list(batch.tolist() for batch in combined)
        #random.shuffle(combined)
        return iter(combined)

    def __len__(self):
        return len(dataset)/batch_size

batch_size = 2
my_batch_sampler = batch_samplers(pil_dataset,batch_size)
for x in my_batch_sampler:
    print(x)
[4, 2]
[0, 1]
[3]
[6, 7]
[9, 5]
[8]

这样操作,以后每个batch里面只有前半段的序号,或者后半段的序号,两者不会一同出现。

dataloader_shuflle_half = DataLoader(pil_dataset,batch_sampler=my_batch_sampler)
for i,(images,labels) in enumerate(dataloader_shuflle_half):
    print(labels)
tensor([3, 3])
tensor([1, 0])
tensor([1])
tensor([4, 3])
tensor([2, 3])
tensor([0])

下面自定义一个比较有意义的sampler,在有一些深度学习方法中,比如通过生成新样本来平衡数据集的时候,在batch层面操作的时候,一般对batch的数据内容是有要求的。要么提前把数据整理的特别符合要求。要么就用自定义sampler了。

已经该数据集的label总共有5类,如果我想要每一个batch的size为5,且每一个batch的数据刚好包含了这5种,一种一个(有fewshot那味了)。那应该怎么做呢?

from collections import OrderedDict
import random
#把csv的所有的数据全部变成data
data_image = image_csv['image_id'].values
target = image_csv['label'].values
data = list(zip(data_image,target))
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)

class N_Way_K_Shot_BatchSampler(Sampler):
    def __init__(self, data, max_iter):
        _,self.y = zip(*data)
        self.y = list(self.y)
        self.max_iter = max_iter
        self.label_dict = self.build_label_dict()
        self.unique_classes_from_y = list(set(self.y))
    #构建一个字典,key值为label,value值为拥有相同label的样本在dataset中的序号的集合
    def build_label_dict(self):
        label_dict = OrderedDict()
        for i, label in enumerate(self.y):
            if label not in label_dict:
                label_dict[label] = [i]
            else:
                label_dict[label].append(i)
        #print(label_dict)
        return label_dict
    #从字典中随机选取一个key值为cls的value(即序号)
    def sample_examples_by_class(self, cls):
        if cls not in self.unique_classes_from_y:
            return []
        sampled_examples = random.sample(self.label_dict[cls],1)  # sample without replacement
      
        return sampled_examples
    #构造一个迭代器,通过调用next()方法不断生成符合条件的序号
    def __iter__(self):
        for _ in range(self.max_iter):
            batch = []
            classes = self.unique_classes_from_y
            for cls in classes:
                samples_for_this_class = self.sample_examples_by_class(cls)
                batch.extend(samples_for_this_class)
            yield batch

    def __len__(self):
        return self.max_iter
    
n_way_k_shot_sampler = N_Way_K_Shot_BatchSampler(data,10000)
# for i in n_way_k_shot_sampler:
#     print(i)

dataloader_complex = DataLoader(pil_dataset,batch_sampler=n_way_k_shot_sampler)
iter_dataloader = iter(dataloader_complex)


#迭代两次
for i in range(2):
    images,labels = next(iter_dataloader)
    #把tensor(【5,3,600,800】)重新转化为图片
    #改变维度顺序
    batch_images = images.permute(0, 2, 3, 1)

    # 设置子图布局
    fig, axes = plt.subplots(1, 5, figsize=(15, 3))

    # 循环显示每张图片
    for j in range(5):
        axes[j].imshow(batch_images[j].numpy())
        axes[j].axis('off')  # 可选:关闭坐标轴
        axes[j].set_title(str(labels[j].item()))

    plt.show()

结束,收工。

notebook我放在比赛的公共code了,名字是leaf_cls.demo。

有兴趣的可以在上面调试着玩一玩,可劲造,不用担心自己电脑里出现什么奇怪的东西,也不用去费劲地调试环境,吭哧吭哧下数据。非常的nice。

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

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