对于pytorch框架来说数据预处理可以使用transforms
函数
from torchvision import datasets, transforms
pipline_train = transforms.Compose([ #随机旋转图片 transforms.RandomHorizontalFlip(), #将图片尺寸resize到32x32 transforms.Resize((32,32)), #将图片转化为Tensor格式 transforms.ToTensor(), #正则化(当模型出现过拟合的情况时,用来降低模型的复杂度) transforms.Normalize((0.1307,),(0.3081,)) ]) pipline_test = transforms.Compose([ #将图片尺寸resize到32x32 transforms.Resize((32,32)), transforms.ToTensor(), transforms.Normalize((0.1307,),(0.3081,)) ])
#下载数据集 train_set = datasets.MNIST(root="./data", train=True, download=True, transform=pipline_train) test_set = datasets.MNIST(root="./data", train=False, download=True, transform=pipline_test) #加载数据集 trainloader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True) testloader = torch.utils.data.DataLoader(test_set, batch_size=32, shuffle=False)
测试集不需要进行训练,因此不需要随即旋转等操作增加提升模型泛化能力。
张量的转换方式也可以直接通过from_numpy直接转换。这里采用的是csv数据集,与上面的图片数据集处理方式不同,具体例子可以参照 用PyTorch构建第一个神经网络
划分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=RANDOM_SEED) X_train = torch.from_numpy(X_train.to_numpy()).float() #去掉维度为1的维度 y_train = torch.squeeze(torch.from_numpy(y_train.to_numpy()).float()) X_test = torch.from_numpy(X_test.to_numpy()).float() y_test = torch.squeeze(torch.from_numpy(y_test.to_numpy()).float())
这里要解释一下Pytorch MNIST数据集标准化为什么是transforms.Normalize((0.1307,), (0.3081,))?
标准化(Normalization) 是神经网络对数据的一种经常性操作。标准化处理指的是:样本减去它的均值,再除以它的标准差,最终样本将呈现均值为0方差为1的数据分布。
神经网络模型偏爱标准化数据,原因是均值为0方差为1的数据在sigmoid、tanh经过激活函数后求导得到的导数很大,反之原始数据不仅分布不均(噪声大)而且数值通常都很大(本例中数值范围是0~255),激活函数后求导得到的导数则接近与0,这也被称为梯度消失。前文已经分析,神经网络是根据函数对权值求导的导数来调整权值,导数越大,调整幅度越大,越快逼近目标函数,反之,导数越小,调整幅度越小,所以说,数据的标准化有利于加快神经网络的训练。
除此之外,还需要保持train_set、val_set和test_set标准化系数的一致性。标准化系数就是计算要用到的均值和标准差,在本例中是((0.1307,), (0.3081,)),均值是0.1307,标准差是0.3081,这些系数都是数据集提供方计算好的数据。不同数据集就有不同的标准化系数,例如([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])就是ImageNet dataset的标准化系数(RGB三个通道对应三组系数),当需要将imagenet预训练的参数迁移到另一神经网络时,被迁移的神经网络就需要使用imagenet的系数,否则预训练不仅无法起到应有的作用甚至还会帮倒忙。
从上面可以看出不同数据集处理的方式可以选择不同的方式,当然图像数据集也可以先下载数据集再处理数据,也可以事先设置数据集格式再下载。
tensorflow处理数据集
如果是导入常见的数据集,可以导入以下包,其它也可以用切片的方法tf.data.Dataset.from_tensor_slices((X, Y))
import tensorflow as tf import tensorflow_datasets as tfds
# 使用 TessorFlow Datasets 载入“tf_flowers”数据集,这里也可以载入‘mninst’,‘cats_vs_dogs’等数据集,替换一下就可以。 dataset = tfds.load("tf_flowers", split=tfds.Split.TRAIN, as_supervised=True) # 对 dataset 进行大小调整、打散和分批次操作 dataset = dataset.map(lambda img, label: (tf.image.resize(img, [224, 224]) / 255.0, label)) \ .shuffle(1024) \ .batch(32) # 迭代数据 for images, labels in dataset: # 对images和labels进行操作
除了利用lambda进行数据处理,也可以建立函数将操作打包用map函数调用,比如
def rot90(image, label): image = tf.image.rot90(image) return image, label mnist_dataset = mnist_dataset.map(rot90) for image, label in mnist_dataset: plt.title(label.numpy()) plt.imshow(image.numpy()[:, :, 0]) plt.show()
具体可以参考简单粗暴 TensorFlow 2.0
最后说一下自制数据集
对于训练集和测试集,要分别制作对应的图片数据索引,即train.txt和test.txt两个文件,每个txt中包含每个图片的目录和对应类别class。示意图如下:
制作图片数据索引的python脚本程序如下:
import os train_txt_path = os.path.join("data", "LEDNUM", "train.txt") train_dir = os.path.join("data", "LEDNUM", "train_data") valid_txt_path = os.path.join("data", "LEDNUM", "test.txt") valid_dir = os.path.join("data", "LEDNUM", "test_data") def gen_txt(txt_path, img_dir): f = open(txt_path, 'w') for root, s_dirs, _ in os.walk(img_dir, topdown=True): # 获取 train文件下各文件夹名称 for sub_dir in s_dirs: i_dir = os.path.join(root, sub_dir) # 获取各类的文件夹 绝对路径 img_list = os.listdir(i_dir) # 获取类别文件夹下所有png图片的路径 for i in range(len(img_list)): if not img_list[i].endswith('jpg'): # 若不是png文件,跳过 continue label = img_list[i].split('_')[0] img_path = os.path.join(i_dir, img_list[i]) line = img_path + ' ' + label + '\n' f.write(line) f.close() if __name__ == '__main__': gen_txt(train_txt_path, train_dir) gen_txt(valid_txt_path, valid_dir)
运行脚本之后就在./data/LEDNUM/目录下生成train.txt和test.txt两个索引文件。
其中,要想实现上面的结果,首先图片数据集需要预先处理成以下方式,这里以 ‘mnist ’ 的train数据集为例,分为10个类别,每个类别包含一定的图片,测试集也是这样,也就是train_data与test_data是已经存在的才能经过处理得到train.txt和test.txt,因为pytorch等框架是通过索引txt文件进行访问图片,不是直接访问图片,可以理解为提高计算速度,降低缓存吧。
对于没有事先对图像数据集划分为train_data与test_data,其划分代码可以用以下代码
# -*- coding: utf-8 -*- """ 将数据集划分为训练集,验证集,测试集 """ import os import random import shutil # 创建保存图像的文件夹 def makedir(new_dir): if not os.path.exists(new_dir): os.makedirs(new_dir) random.seed(1) # 随机种子 # 1.确定原图像数据集路径 dataset_dir = "D:/test2021/train_val_test0811/" ##原始数据集路径 # 2.确定数据集划分后保存的路径 split_dir = "D:/test2021/after0811/" ##划分后保存路径 train_dir = os.path.join(split_dir, "train") valid_dir = os.path.join(split_dir, "val") test_dir = os.path.join(split_dir, "test") # 3.确定将数据集划分为训练集,验证集,测试集的比例 train_pct = 0.9 valid_pct = 0.1 test_pct = 0 # 4.划分 for root, dirs, files in os.walk(dataset_dir): for sub_dir in dirs: # 遍历0,1,2,3,4,5...9文件夹 imgs = os.listdir(os.path.join(root, sub_dir)) # 展示目标文件夹下所有的文件名 imgs = list(filter(lambda x: x.endswith('.png'), imgs)) # 取到所有以.png结尾的文件,如果改了图片格式,这里需要修改 random.shuffle(imgs) # 乱序图片路径 img_count = len(imgs) # 计算图片数量 train_point = int(img_count * train_pct) # 0:train_pct valid_point = int(img_count * (train_pct + valid_pct)) # train_pct:valid_pct for i in range(img_count): if i < train_point: # 保存0-train_point的图片到训练集 out_dir = os.path.join(train_dir, sub_dir) elif i < valid_point: # 保存train_point-valid_point的图片到验证集 out_dir = os.path.join(valid_dir, sub_dir) else: # 保存valid_point-结束的图片到测试集 out_dir = os.path.join(test_dir, sub_dir) makedir(out_dir) # 创建文件夹 target_path = os.path.join(out_dir, imgs[i]) # 指定目标保存路径 src_path = os.path.join(dataset_dir, sub_dir, imgs[i]) #指定目标原图像路径 shutil.copy(src_path, target_path) # 复制图片 print('Class:{}, train:{}, valid:{}, test:{}'.format(sub_dir, train_point, valid_point-train_point, img_count-valid_point))
对于少量数据集可以手动操作,如果数据集量庞大建议采用这种方法,如果一开始训练集与测试集(这里指的是csv等文本数据集格式)没有事先划分,可以利用train_test_split()函数来划分数据集
pytorch 加载自己的数据集,需要写一个继承自torch.utils.data中Dataset类,并修改其中的__init__方法、__getitem__方法、__len__方法。默认加载的都是图片,__init__的目的是得到一个包含数据和标签的list,每个元素能找到图片位置和其对应标签。然后用__getitem__方法得到每个元素的图像像素矩阵和标签,返回img和label。
from PIL import Image from torch.utils.data import Dataset class MyDataset(Dataset): def __init__(self, txt_path, transform = None, target_transform = None): fh = open(txt_path, 'r') imgs = [] for line in fh: line = line.rstrip() words = line.split() imgs.append((words[0], int(words[1]))) self.imgs = imgs self.transform = transform self.target_transform = target_transform def __getitem__(self, index): fn, label = self.imgs[index] #img = Image.open(fn).convert('RGB') img = Image.open(fn) if self.transform is not None: img = self.transform(img) return img, label def __len__(self): return len(self.imgs)
getitem是核心函数。self.imgs是一个list,self.imgs[index]是一个str,包含图片路径,图片标签,这些信息是从上面生成的txt文件中读取;利用Image.open对图片进行读取,注意这里的img是单通道还是三通道的;self.transform(img)对图片进行处理,这个transform里边可以实现减均值、除标准差、随机裁剪、旋转、翻转、放射变换等操作。