OK了家人们,早上起来神清气爽,让我们继续探究如何更加自由的定义dataset,那就到了我们今天的主题。
书接上回。
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。