之前文章已经实现了一个自定义的类,制定了自己的数据集。其实,细看代码,可传入的参数过多,有些简单问题复杂化,实现上不是那么简洁。
既然是自己定义的数据集,那么许多功能是自己写好的,如何写,为什么这样写都是已知的。
这里,我们考虑让自己定义的Dataset类简单化,并由此完成全部数据准备工作。
图片名处理
在实现Dataset类之前,图片准备工作是必须要进行的,或者说是对我们收集到的图片进行改名。就像之前那篇文章所写的,写个小程序批量改变图片命名。
就像这样:
import os
path="E:\\3-10\\dogandcats\\train\\cat\\"#猫图片路径
filenames=[name for name in os.listdir(path)]
for i,filename in enumerate(filenames):
src=path+filename
dst=path+path[-4:-1]+str(i+1)+'.0'+".jpg"
os.rename(src,dst)
同样,我们类似的可以处理狗图片,此时仅需修改路径名以及将
dst=path+path[-4:-1]+str(i+1)+'.0'+".jpg"
修改为:
dst=path+path[-4:-1]+str(i+1)+'.1'+".jpg"
处理其它类的图片同样如此。最终是这种形式:
cat1.0.jpg
cat2.0.jpg
...
dog1.1.jpg
dog2.1.jpg
...
需要强调的是这里的cat1,dog1...,jpg格式仅仅是一个习惯,没有太多的含义,真正重要的是0,1,...,这是个标签,指明了这张图片的类别。0代表cat,而1代表dog...
这里,图片准备工作已经完成!
之后,我们将处理后的所有用于训练的图片放于此路径:
E:\3-10\dogandcats\train
Dataset实现
import torch
import os
from torch.utils.data import Dataset
from torchvision import transforms
from PIL import Image
import numpy as np
import torch
class MyDataset(Dataset):
def __init__(self, path_file,transform=None):
self.path_file=path_file
self.imgs=[name for name in os.listdir(path_file)]
self.transform=transform
def __len__(self):
return len(self.imgs)
def __getitem__(self, idx):
#get the image
img_path = os.path.join(self.path_file,self.imgs[idx])
image=Image.open(img_path)
image=image.resize((28,28))#修改图片大小,默认大小
#image = np.array(image)
#image=torch.tensor(image,dtype=torch.float32)#将numpy数组转化为数据类型为float32的张量
#image=image.permute(2,0,1)#将HxWxC变为CxHxW
#以上三行所起的功能可以用transforms.ToTensor()代替(差别仅仅在于后者将像素全部归为[0,1]。
if self.transform:
image = self.transform(image)
#get the label
str1=self.imgs[idx].split('.')
label=torch.tensor(eval(str1[1]))
return image, label
path="E:\\3-10\\dogandcats\\train"
training_data=MyDataset(path,transform=transforms.Compose([transforms.ToTensor()]))
train_dataloader = torch.utils.data.DataLoader(training_data, batch_size=4, shuffle=True)
让我们run一下:
>>> BatchImg,Label=next(iter(train_dataloader))
>>> BatchImg.size()
torch.Size([4, 3, 28, 28])
>>> Label.size()
torch.Size([4])
均值和标准差,归一化
然后我们尝试获取所有图片的三个通道的均值和标准差,最后让图片数据变为均值为0,标准差为1的新数据。
>>> IMGEND=torch.stack([ig for ig,_ in training_data],dim=0)#堆叠tensor
>>> IMGEND.size()
torch.Size([17, 3, 28, 28])
此时,可以看到我们已经获得了所有图片(共17张)的数据
>>> mean=torch.mean(IMGEND,dim=(0,2,3))#获得均值
>>> mean
tensor([0.6479, 0.6043, 0.5521])
>>> std=torch.std(IMGEND,dim=(0,2,3))#获得标准差
>>> std
tensor([0.2343, 0.2485, 0.2925])
好吧,这里,借助:transforms.Normalize()方法,我们可以很容易对所有图片数据进行变换,最终获得均值为0,标准差为1的数据。
就像这样:
training_data1=MyDataset(path,transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.6479, 0.6043, 0.5521],[0.2343, 0.2485, 0.2925])]))
接着我们就可以将training_data1传入Dataloader:
train_dataloader1 = torch.utils.data.DataLoader(training_data1, batch_size=4, shuffle=True)
此时,对于图片数据我们完成了归一化操作,将数据分布变为了均值为0,标准差为1的分布,training_data1就是之后在神经网络用于训练的数据集。
检验training_data1数据,就像之前那个计算均值和标准差的过程:
>>> IMGEND1=torch.stack([ig for ig,_ in training_data1],dim=0)
>>> IMGEND1.size()
torch.Size([17, 3, 28, 28])
>>> mean1=torch.mean(IMGEND1,dim=(0,2,3))
>>> mean1
tensor([ 1.1959e-04, -3.3379e-05, -1.4900e-04])
>>> std=torch.std(IMGEND1,dim=(0,2,3))
>>> std
tensor([1.0001, 0.9998, 1.0001])
总结
总的来看,我们实现Dataset类时,完成了对数据的归一化,以及将Dataset数据传入了Dataloader。
一些操作可以使用transforms模块所提供的方法以简化步骤,当然,算数据的均值和标准差用于归一化仍然还是比较繁琐的。
到这里,数据的预处理工作基本结束!