【pytorch】土堆pytorch教程学习(四)Transforms 的使用

发布时间 2023-05-02 17:38:05作者: hzyuan

transforms 在工具包 torchvision下,用来对图像进行预处理:数据中心化、数据标准化、缩放、裁剪、旋转、翻转、填充、噪声添加、灰度变换、线性变换、仿射变换、亮度/饱和度/对比度变换等。

transforms 本质就是一个python文件,相当于一个工具箱,里面包含诸如 ResizeToTensorNormalize 等类,这些类就是我们需要用到的图像预处理工具。

transforms 的使用无非是将图像通过工具转换成我们需要的结果。

我们需要关注两个问题:

  1. 该如何使用 transforms 里的工具?
  2. 为什么需要 Tensor 数据类型?

工具的使用方法

我们先来看下 ToTensorResize 这两个工具的源码。

# ToTensor 源码
class ToTensor:
    """Convert a PIL Image or ndarray to tensor and scale the values accordingly.
    """

    def __init__(self) -> None:
        _log_api_usage_once(self)

    def __call__(self, pic):
        """
        Args:
            pic (PIL Image or numpy.ndarray): Image to be converted to tensor.

        Returns:
            Tensor: Converted image.
        """
        return F.to_tensor(pic)

# Resize 源码
class Resize(torch.nn.Module):
    """Resize the input image to the given size.
    """

    def __init__(self, size, interpolation=InterpolationMode.BILINEAR, max_size=None, antialias="warn"):
        super().__init__()
        _log_api_usage_once(self)
        if not isinstance(size, (int, Sequence)):
            raise TypeError(f"Size should be int or sequence. Got {type(size)}")
        if isinstance(size, Sequence) and len(size) not in (1, 2):
            raise ValueError("If size is a sequence, it should have 1 or 2 values")
        self.size = size
        self.max_size = max_size

        if isinstance(interpolation, int):
            interpolation = _interpolation_modes_from_int(interpolation)

        self.interpolation = interpolation
        self.antialias = antialias

    def forward(self, img):
        """
        Args:
            img (PIL Image or Tensor): Image to be scaled.

        Returns:
            PIL Image or Tensor: Rescaled image.
        """
        return F.resize(img, self.size, self.interpolation, self.max_size, self.antialias)

一目了然,这两个工具都没有一堆乱七八糟的方法迷惑我们,其核心功能代码在 __call__forward 里,这是想让我们通过可调用对象方式来使用工具。

使用 transforms 工具大致为以下两个步骤:

  1. 实例化工具
tensor_trans = transforms.ToTensor()
  1. 传入合适的参数调用实例化对象
img_path = 'dataset/train/ants/0013035.jpg'
img = Image.open(img_path)
# 输入什么类型的参数可以进到源码里看
tensor_img = tensor_trans(img)

我们需要关注三种图片类型,分别是 PIL.Imagenumpy.ndarrayTensor

  • Image.open(img_path) 得到 PIL.Image 类型
  • cv2.imread(img_path) 得到 numpy.ndarray 类型
  • transforms.ToTensor()(pic) 得到 Tensor 类型

Tensor 数据类型

from torchvision import transforms
from PIL import Image

img_path = 'dataset/train/ants/0013035.jpg'
img = Image.open(img_path)

tensor_trans = transforms.ToTensor()
tensor_img = tensor_trans(img)
print(tensor_img)

输出 Tensor 类型的数据,乍一看怎么和 numpy.ndarray 差不多。其实不然,进入断点调式里可以看到其还包装了神经网络所需要的理论基础参数,诸如gradgrad_fnis_cpu等。这就是我们需要 Tensor 数据类型的原因,一切都是为了神经网络而服务。

常用的工具

仅仅列出几个常用的工具的一些简单用法,以进一步体会tansforms工具的用法,其他详细信息还需关注官方文档和源码。

ToTensor

PIL.Imagenarray 类型的图像转换为 tensor 类型并相应地缩放值。

trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)

Normalize

用均值和标准差标准化张量图像。

逐channel地对图像进行标准化(均值变为0,标准差变为1),可以加快模型的收敛。

n个通道,给出 平均值: (mean[1],...,mean[n]) 和 标准差: (std[1],..,std[n])

output[channel] = (input[channel] - mean[channel]) / std[channel]

trans_normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_normalize = trans_normalize(img_tensor)

Resize

改变图像的大小

trans_resize = transforms.Resize((512,512))
img_resize = trans_resize(img)

Compose

将多个变换组合在一起。

trans_compose = transforms.Compose([
    transforms.Resize(512),
    transforms.ToTensor()
    transforms.Nomalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
img_resize = trans_compose(img)

简单理解 ToTense 和 Normalize

数值归一化处理是数据挖掘中的一项基本工作,不同的评价指标具有不同的量纲和量纲单位,如果根据原始数据进行分析的话会受到量纲的影响,为了消除指标之间的量纲影响,需要对原始数据进行标准化。

归一化也就是量纲统一化。目的就是为了把不同来源的数据统一到同一数量级(或者是一个参考坐标系)下,这样使得比较起来有意义。

归一化能提高梯度下降发求解最优解的速度

归一化方法:

  1. 线性归一化,也称min-max标准化、离差标准化;是对原始数据的线性变换,使得结果值映射到 [0, 1] 之间。转换函数如下:
x' = (x - min(x)) / (max(x) - min(x))
  1. 标准差归一化,,也叫Z-score标准化,这种方法给予原始数据的均值(mean,μ)和标准差(standard deviation,σ)进行数据的标准化。处理后的数据均值为0,标准差为1。转换函数如下:
x* = (x - μ) / σ
  1. 非线性归一化,这种方法一般使用在数据分析比较大的场景,有些数值很大,有些很小,通过一些数学函数,将原始值进行映射。一般使用的函数包括log、指数、正切等,需要根据数据分布的具体情况来决定非线性函数的曲线。

ToTensor 干了两件事:

  • PIL 图像或 numpy.narray 转换成 Tensor, 形状也从 HWC -> CHW
  • 如果 PIL 图像是属于 (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1) 中的一种模式,或者 numpy.ndarraydtypenp.uint8,还会将数值从 [0, 255] 归一化到 [0, 1]。

Normalize 是对数据按通道进行标准化,即均值为0,标准差为1。这能加快模型的收敛速度。

计算公式:

output[channel] = (input[channel] - mean[channel]) / std[channel]

实际应用中是需要我们自己计算出每个通道的均值和标准差,然后再传入 Normalize

'''
以 MNIST 数据集为例计算其均值和标准差
先查看 mnist 的最大值和最小值
mnist.data.float().min() # 0
mnist.data.float().max() # 255
可以看到数据范围是 [0, 255],在转成 Tensor 类型时会归一化到 [0, 1],故我们需要计算的是[0, 1]范围下的数据集的均值和标准差
'''
mean = (mnist.data.floatt()/255).mean()
std = (mnist.data.floatt()/255).std()

可能会有人疑问确定了数据集不也就确定了均值和标准差,为什么还要我们自己计算再传入呢?

这是因为数据集一般都是很庞大的,如果交给 Normalize 计算就会影响性能了。