神经网络学习笔记(8)- 图像预处理

CNN(卷积神经网络)主要用于图像的训练。在介绍CNN之前,首先面临的问题是,如何来表示图像,以及有哪些预处理操作,能帮助更有效地进行CNN训练。

图像的Tensor表示

一张图可以表示为一个像素矩阵。我们知道计算机表示彩色图像的三原色:RGB,代表红、绿、蓝三个通道。它们的值域都为0~255。任何颜色都可以表示为这三个通道的组合。如(255,0,0)表示为纯红,(0,255,0)表示为纯绿。
通常,在训练CNN前,需要把这三个通道的值映射到-1~1的区域,以便能更高效地进行训练。
有时,色彩的信息并不重要,为了简化模型,则只需要一个灰度通道,即每个像素均对应一个0~1的值。
因此,一张2维图像,可以用3维的Tensor来表示。图像的长宽分别占2维,最后一维是颜色通道。灰度图像的第3维长度为1,彩色图像的第3维长度为3。
在深度神经网络,通常一次性批量地输入数据,因此一次输入的数据维度为4,代表图像的索引维。当然,一次训练中的图像必须在其他3维上满足相同的尺寸。

预处理

图像的处理一般遵循以下的顺序,当然也并非每一步都是必要的。
裁减 大多数训练模型是基于正方形的图片。对于不规整的原始图片,需要进行裁减成模型所需的图像比例。
调整尺寸 模型对像素的个数也会有要求,需将图像缩放成所需的像素尺寸。
数据增强 为了增强模型的鲁棒性,可以通过一些基本的图片变换操作,如缩放、旋转等,生成新的样本。这能有助于缓解模型的过拟合。
标准化 对所有像素进行统计学上的标准化操作(减去均值后除以标准差)。这能保证所有图像具有类似的像素分布(标准正态分布)。
降维 可以将RGB表示的彩色图片转化为只有一个通道的灰度图片。
白化 白化能使特征之间降低相关性。常用的有PCA和ZCA方法。
示例:数据的矩阵格式转换、标准化和白化算法


import numpy as np

X = data 
#数据源为10000张图片,每张图是3x32x32=3072的一维表示。 X.shape = (10000, 3072)。
#而模型需要的格式为(样本数,32x32x3),注意到颜色通道的维度的顺序不同。

#因为数据源将所有图片信息放在一维列表里,需要先reshape成(10000, 3, 32, 32)。可以有一维为-1,表示自适应。
X = X.reshape((-1, 3, 32, 32))

#转置成(10000, 32, 32, 3)的顺序
X = X.transpose(0, 2, 3, 1)    

#再次把剩下的3维压成1维
X = X.reshape(-1, 3 * 32 * 32)

#标准化
X = X - X.mean(axis=0)
X = X / np.std(X, axis=0)

#白化ZCA算法
X_subset = X[:1000] #取前1000个样本
cov = np.cov(X_subset, rowvar=True) #计算协方差矩阵。cov.shape=(1000, 1000)
U, S, V = np.linalg.svd(cov) #奇异值分解 尺寸分别为:(1000, 1000), (1000,), (1000, 1000)
epsilon = 1e-5
zca_matrix = np.dot(U, np.dot(np.diag(1.0 / np.sqrt(S + epsilon)), U.T))#尺寸为(1000, 1000)
zca = np.dot(zca_matrix, X_subset) #zca结果与原样本尺寸相同 (1000, 3072)

示例:用PyTorch处理数据集CIFAR10。包括对batch化的大数据样本进行裁减、缩放、数据增强、标准化等。

import torch
import torchvision
import torchvision.transforms as transforms      

#计算所有样本的均值和标准差
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.ToTensor()
])

dataset = torchvision.datasets.CIFAR10(root='./datasets/cifar10/train', download=True, transform=transform)
dataloader = torch.utils.data.DataLoader(dataset, 
                                         batch_size=16,
                                         shuffle=True, 
                                         num_workers=2)

pop_mean = []
pop_std = []
for i, data in enumerate(dataloader, 0):
    
    # shape (batch_size, 3, height, width)
    numpy_image = data[0].numpy() 
    
    #聚合0,2,3维 => shape (3,)
    batch_mean = np.mean(numpy_image, axis=(0, 2, 3))
    batch_std = np.std(numpy_image, axis=(0, 2, 3))
    
    pop_mean.append(batch_mean)
    pop_std.append(batch_std)
    
pop_mean = np.array(pop_mean)
pop_std = np.array(pop_std)

# shape (迭代次数, 3) -> 在第0维上聚合 -> shape (3,)
pop_mean = pop_mean.mean(axis=0)
pop_std = pop_std.mean(axis=0) #注:这里将每个batch的标准差计算均值作为总体的标准差,并非精确,仅为近似值。

#进行实际转换。注意到ToTensor后才能标准化,与上面计算均值与方差的过程相同。但是标准化后,必定会产生负数,换句话说,该图像无法被直接显示。
transform = transforms.Compose([
            transforms.Resize(256),           #缩放
            transforms.RandomResizedCrop(224),#截取
            transforms.ColorJitter(),         #变色(数据增强)
            transforms.RandomHorizontalFlip(),#翻折(数据增强)
            transforms.ToTensor(),            #伴随着映射到0~1区间  
            transforms.Normalize(pop_mean,    #标准化
                                 pop_std)
            ])
trainset = torchvision.datasets.CIFAR10(root='./datasets/cifar10/train', train=True, download=True, transform=transform)  
trainloader = torch.utils.data.DataLoader(trainset, batch_size=16,
                                          shuffle=True, num_workers=2)
images_batch, labels_batch = iter(trainloader).next()