数据预处理方法

对于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里边可以实现减均值、除标准差、随机裁剪、旋转、翻转、放射变换等操作。

上一篇:CleanMyMac X2022新版Mac OS X 系统下知名系统清理软件清理工具


下一篇:OS-Mac OS 初入-Hackintosh(黑苹果)