• transforms.Normalize()


    1、定义:数据标准化处理:transforms.Normalize():transforms.Normalize:数据标准化,即均值为0,标准差为1。

    简单来说就是将数据按通道进行计算,将每一个通道的数据先计算出其方差与均值,然后再将其每一个通道内的每一个数据减去均值,再除以方差,得到归一化后的结果。
    在深度学习图像处理中,标准化处理之后,可以使数据更好的响应激活函数,提高数据的表现力,减少梯度爆炸和梯度消失的出现。

    Normalize a tensor image with mean and standard deviation. this transform will normalize each channel of the input torch.*Tensor i.e., output[channel] = (input[channel] - mean[channel]) / std[channel]
    先看示例代码:

    1. import torchvision.transforms as transforms
    2. train_transforms = transforms.Compose(
    3. [transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)), # 随机裁剪到256*256
    4. transforms.RandomRotation(degrees=15), # 随机旋转
    5. transforms.RandomHorizontalFlip(), # 随机水平翻转
    6. transforms.CenterCrop(size=224), # 中心裁剪到224*224
    7. # 转化成张量,#归一化[0,1](是将数据除以255),
    8. # transforms.ToTensor()会把HWC会变成C *H *W(拓展:格式为(h,w,c),像素顺序为RGB)
    9. transforms.ToTensor(),
    10. transforms.Normalize([0.485, 0.456, 0.406],
    11. [0.229, 0.224, 0.225]) # 标准化
    12. ])

    1.1 使用PyTorch计算图像数据集的均值和方差(推荐)
    Pytorch图像预处理时,通常使用transforms.Normalize(mean, std)对图像按通道进行标准化,即减去均值,再除以方差。这样做可以加快模型的收敛速度。其中参数mean和std分别表示图像每个通道的均值和方差序列。

    Imagenet数据集的均值和方差为:mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225),因为这是在百万张图像上计算而得的,所以我们通常见到在训练过程中使用它们做标准化。而对于特定的数据集,选择这个值的结果可能并不理想。接下来给出计算特定数据集的均值和方差的方法。

    1. import torch
    2. from torchvision.datasets import ImageFolder
    3. def getStat(train_data):
    4. '''
    5. Compute mean and variance for training data
    6. :param train_data: 自定义类Dataset(或ImageFolder即可)
    7. :return: (mean, std)
    8. '''
    9. print('Compute mean and variance for training data.')
    10. print(len(train_data))
    11. train_loader = torch.utils.data.DataLoader(
    12. train_data, batch_size=1, shuffle=False, num_workers=0,
    13. pin_memory=True)
    14. mean = torch.zeros(3)
    15. std = torch.zeros(3)
    16. for X, _ in train_loader:
    17. for d in range(3):
    18. mean[d] += X[:, d, :, :].mean()
    19. std[d] += X[:, d, :, :].std()
    20. mean.div_(len(train_data))
    21. std.div_(len(train_data))
    22. return list(mean.numpy()), list(std.numpy())
    23. if __name__ == '__main__':
    24. train_dataset = ImageFolder(root=r'./data/food/', transform=None)
    25. print(getStat(train_dataset))

    2. 图像预处理Transforms(主要讲解数据标准化)
    2.1 理解torchvision
    transforms属于torchvision模块的方法,它是常见的图像预处理的方法
    在这里贴上别人整理的transforms运行机制:

     可以看出torchvision工具包中包含三个主要模块,主要讲解学习transforms

    torchvision.transforms:常用的数据预处理方法,提升泛化能力
    包括:数据中心化、数据标准化、缩放、裁剪、旋转、翻转、填充、噪声添加、灰度变换、线性变换、仿射变换、亮度、饱和度及对比度变换等

    2.2 数据标准化——transforms.normalize()
    功能:逐channel的对图像进行标准化(均值变为0,标准差变为1),可以加快模型的收敛
    output = (input - mean) / std
    mean:各通道的均值
    std:各通道的标准差
    inplace:是否原地操作
    思考:
    (1)据我所知,归一化就是要把图片3个通道中的数据整理到[-1, 1]区间。
    x = (x - mean(x))/std(x)
    只要输入数据集x确定了,mean(x)和std(x)也就是确定的数值了,为什么Normalize()函数还需要输入mean和std的数值呢????

    (2)RGB单个通道的值是[0, 255],所以一个通道的均值应该在127附近才对。
    如果Normalize()函数去计算 x = (x - mean)/std ,因为RGB是[0, 255],算出来的x就不可能落在[-1, 1]区间了。

    (3)在我看的了论文代码里面是这样的:
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    为什么就确定了这一组数值,这一组数值是怎么来的? 为什么这三个通道的均值都是小于1的值呢?
    理解:
    (1)针对第一个问题,mean 和 std 肯定要在normalize()之前自己先算好再传进去的,不然每次normalize()就得把所有的图片都读取一遍算这两个
    (2)针对第二个问题,有两种情况
    (a )如果是imagenet数据集,那么ImageNet的数据在加载的时候就已经转换成了[0, 1].
    (b) 应用了torchvision.transforms.ToTensor,其作用是将数据归一化到[0,1](是将数据除以255),transforms.ToTensor()会把HWC会变成C *H *W(拓展:格式为(h,w,c),像素顺序为RGB)
    (3)针对第三个问题:[0.485, 0.456, 0.406]这一组平均值是从imagenet训练集中抽样算出来的。
    继续有疑问:
    ToTensor 已经[0,1]为什么还要[0.485, 0.456, 0.406]?那么归一化后为什么还要接一个Normalize()呢?Normalize()是对数据按通道进行标准化,即减去均值,再除以方差
    解答:
    别人的解答:数据如果分布在(0,1)之间,可能实际的bias,就是神经网络的输入b会比较大,而模型初始化时b=0的,这样会导致神经网络收敛比较慢,经过Normalize后,可以加快模型的收敛速度。
    因为对RGB图片而言,数据范围是[0-255]的,需要先经过ToTensor除以255归一化到[0,1]之后,再通过Normalize计算过后,将数据归一化到[-1,1]。
    是否可以这样理解:
    [0,1]只是范围改变了, 并没有改变分布,mean和std处理后可以让数据正态分布😂
    参考:
    pytorch torchvision.transforms.Normalize()中的mean和std参数—解惑
    pytorch的transform中ToTensor接着Normalize
    另外这篇包含数据增强部分:
    Pytorch框架学习(6)——transforms与normalize
    拓展:
    数据增强又称为数据增广,数据扩增,它是对训练集进行变换,使训练集更丰富,从而让模型更具泛化能力。

    3、通过例子解读:

    问题:

    1. transform.ToTensor(),
    2. transform.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))

    究竟是什么意思?(0.5,0.5,0.5),(0.5,0.5,0.5)又是怎么来的呢?

    transform.ToTensor()

    1. 是将输入的数据shape W,H,C ——> C,W,H
    2. 将所有数除以255,将数据归一化到【0,1】

    ToTensor的作用是将导入的图片转换为Tensor的格式,导入的图片为PIL image 或者 numpy.nadrry格式的图片,其shape为(H x W x C)数值范围在[0,255],转换之后shape为(C x H x W),数值范围在[0,1]。
     

    代码示例

    1. import torch
    2. import numpy as np
    3. from torchvision import transforms
    4. import cv2
    5. #自定义图片数组,数据类型一定要转为‘uint8’,不然transforms.ToTensor()不会归一化
    6. data = np.array([
    7. [[1,1,1],[1,1,1],[1,1,1],[1,1,1],[1,1,1]],
    8. [[2,2,2],[2,2,2],[2,2,2],[2,2,2],[2,2,2]],
    9. [[3,3,3],[3,3,3],[3,3,3],[3,3,3],[3,3,3]],
    10. [[4,4,4],[4,4,4],[4,4,4],[4,4,4],[4,4,4]],
    11. [[5,5,5],[5,5,5],[5,5,5],[5,5,5],[5,5,5]]
    12. ],dtype='uint8')
    13. print(data)
    14. print(data.shape) #(5,5,3)
    15. data = transforms.ToTensor()(data)
    16. print(data)
    17. print(data.shape) #(3,5,5)
    18. 输出:
    19. tensor([[[0.0039, 0.0039, 0.0039, 0.0039, 0.0039],
    20. [0.0078, 0.0078, 0.0078, 0.0078, 0.0078],
    21. [0.0118, 0.0118, 0.0118, 0.0118, 0.0118],
    22. [0.0157, 0.0157, 0.0157, 0.0157, 0.0157],
    23. [0.0196, 0.0196, 0.0196, 0.0196, 0.0196]],
    24. [[0.0039, 0.0039, 0.0039, 0.0039, 0.0039],
    25. [0.0078, 0.0078, 0.0078, 0.0078, 0.0078],
    26. [0.0118, 0.0118, 0.0118, 0.0118, 0.0118],
    27. [0.0157, 0.0157, 0.0157, 0.0157, 0.0157],
    28. [0.0196, 0.0196, 0.0196, 0.0196, 0.0196]],
    29. [[0.0039, 0.0039, 0.0039, 0.0039, 0.0039],
    30. [0.0078, 0.0078, 0.0078, 0.0078, 0.0078],
    31. [0.0118, 0.0118, 0.0118, 0.0118, 0.0118],
    32. [0.0157, 0.0157, 0.0157, 0.0157, 0.0157],
    33. [0.0196, 0.0196, 0.0196, 0.0196, 0.0196]]])

    transforms.Normalize()

    看了许多文章,都是说:transform.Normalize()通过公式

    x = (x - mean) / std
    

    即同一纬度的数据减去这一维度的平均值,再除以标准差,将归一化后的数据变换到【-1,1】之间。可真是这样吗??

    求解mean和std

    我们需要求得一批数据的mean和std,代码如下:

    1. import torch
    2. import numpy as np
    3. import torchvision.transforms as transforms
    4. # 这里以上述创建的单数据为例子
    5. data = np.array([
    6. [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
    7. [[2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]],
    8. [[3, 3, 3], [3, 3, 3], [3, 3, 3], [3, 3, 3], [3, 3, 3]],
    9. [[4, 4, 4], [4, 4, 4], [4, 4, 4], [4, 4, 4], [4, 4, 4]],
    10. [[5, 5, 5], [5, 5, 5], [5, 5, 5], [5, 5, 5], [5, 5, 5]]
    11. ], dtype='uint8')
    12. # 将数据转为C,W,H,并归一化到[0,1]
    13. data = transforms.ToTensor()(data)
    14. # 需要对数据进行扩维,增加batch维度
    15. data = torch.unsqueeze(data, 0) # tensor([1, 3, 5, 5])
    16. nb_samples = 0.
    17. # 创建3维的空列表
    18. channel_mean = torch.zeros(3) # tensor([0., 0., 0.])
    19. channel_std = torch.zeros(3) # tensor([0., 0., 0.])
    20. print(data.shape) # torch.Size([1, 3, 5, 5])
    21. N, C, H, W = data.shape[:4] # shape是一个列表, N:1 C:3 H:5 W:5
    22. data = data.view(N, C, -1) # 将w,h维度的数据展平,为batch,channel,data,然后对三个维度上的数分别求和和标准差
    23. print(data.shape) # tensor([1, 3, 25])
    24. # 展平后,w,h属于第二维度,对他们求平均,sum(0)为将同一纬度的数据累加
    25. channel_mean += data.mean(2).sum(0)
    26. # 展平后,w,h属于第二维度,对他们求标准差,sum(0)为将同一纬度的数据累加
    27. channel_std += data.std(2).sum(0)
    28. # 获取所有batch的数据,这里为1
    29. nb_samples += N
    30. # 获取同一batch的均值和标准差
    31. channel_mean /= nb_samples
    32. channel_std /= nb_samples
    33. print(channel_mean, channel_std)
    34. # tensor([0.0118, 0.0118, 0.0118]) tensor([0.0057, 0.0057, 0.0057])

    以此便可求得均值和标准差。我们再带入公式:

    x = (x - mean) / std
    

    自己实现

    1. data = np.array([
    2. [[1,1,1],[1,1,1],[1,1,1],[1,1,1],[1,1,1]],
    3. [[2,2,2],[2,2,2],[2,2,2],[2,2,2],[2,2,2]],
    4. [[3,3,3],[3,3,3],[3,3,3],[3,3,3],[3,3,3]],
    5. [[4,4,4],[4,4,4],[4,4,4],[4,4,4],[4,4,4]],
    6. [[5,5,5],[5,5,5],[5,5,5],[5,5,5],[5,5,5]]
    7. ],dtype='uint8')
    8. data = transforms.ToTensor()(data)
    9. for i in range(3):
    10. data[i,:,:] = (data[i,:,:] - channel_mean[i]) / channel_std[i]
    11. print(data)
    12. 输出:
    13. tensor([[[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    14. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    15. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    16. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    17. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]],
    18. [[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    19. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    20. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    21. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    22. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]],
    23. [[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    24. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    25. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    26. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    27. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]]])

    官方实现

    1. data = np.array([
    2. [[1,1,1],[1,1,1],[1,1,1],[1,1,1],[1,1,1]],
    3. [[2,2,2],[2,2,2],[2,2,2],[2,2,2],[2,2,2]],
    4. [[3,3,3],[3,3,3],[3,3,3],[3,3,3],[3,3,3]],
    5. [[4,4,4],[4,4,4],[4,4,4],[4,4,4],[4,4,4]],
    6. [[5,5,5],[5,5,5],[5,5,5],[5,5,5],[5,5,5]]
    7. ],dtype='uint8')
    8. data = transforms.ToTensor()(data)
    9. data = transforms.Normalize(channel_mean, channel_std)(data)
    10. print(data)
    11. 输出:
    12. tensor([[[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    13. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    14. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    15. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    16. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]],
    17. [[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    18. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    19. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    20. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    21. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]],
    22. [[-1.3856, -1.3856, -1.3856, -1.3856, -1.3856],
    23. [-0.6928, -0.6928, -0.6928, -0.6928, -0.6928],
    24. [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
    25. [ 0.6928, 0.6928, 0.6928, 0.6928, 0.6928],
    26. [ 1.3856, 1.3856, 1.3856, 1.3856, 1.3856]]])

    我们观察数据发现,通过求解的均值和标准差,求得的标准化的值,并非在【-1,1】,( 借此谴责部分博客 )。

    结论
    经过这样处理后将数据标准化,即均值为0,标准差为1。使模型更容易收敛。并非是归于【-1,1】!!

    很多人纠结经过transforms.Normalize数据是否服从正态分布。
    我这里简单的说一下经过transforms.Normalize数据不一定服从正态分布!
    这里减去均值,除以标准差只是将数据进行标准化处理,并不会改变原始数据的分布!
    另外是每一个channels上所有batch的数据服从均值为0,标准差为1。
    如有疑问,请及时留言。
    ————————————————
    参考文章:
    1、pytorch中归一化transforms.Normalize的真正计算过程_月光下的小趴菜的博客-CSDN博客
    2、数据归一化处理transforms.Normalize()_幼稚园的扛把子~的博客-CSDN博客_transforms.normalize

  • 相关阅读:
    【批处理DOS-CMD命令-汇总和小结】-环境变量、路径变量、搜索文件位置相关指令——set、path、where
    运行proto文件踩坑历程
    C- ssize_t & size_t
    Linux笔记
    Python文件操作之常用的函数合集
    【飞控开发基础教程7】疯壳·开源编队无人机-SPI(气压计数据获取)
    面试问JUC(java.util.concurrent)的常见类你能答出来几句?
    直播录屏没有声音?解决方案来了!
    一阶&二阶数字滤波器笔记
    DAMA-CDGA/CDGP数据治理认证包括哪几个方面?
  • 原文地址:https://blog.csdn.net/qimo601/article/details/126948204