图像的归一化是将不同量纲的特征归一化到指定的范围内,可以消除不同特征量纲的影响。图像规范化则是将图像规范化到相同的分布。两者都有利于加快模型训练、收敛。
- 归一化 {#title-0} =================
图像的每个像素值的范围是 [0, 255],我们在将图像送入网络之前会将其归一化到 [0, 1] 或者 [-1, 1] 之间。
- 归一化到 [0, 1] 之间:
像素值 / 255
- 归一化到 [-1, 1] 之间:
像素值 / 127.5 - 1
我们知道,如果某个特征的取值范围很大,该特征很可能会对模型产生主导作用,而其他较小取值范围的特征则对模型影响可能很小。这个取值范围不同,可以理解为量纲不同。
比如:输入图像有 3 个通道,第一个通道 [3, 15],第二个通道 [50, 100],第三个通道 [180, 255],通道的取值范围差距很大,当把所有像素点除以 255 之后,不同通道的数值范围都在 [0, 1] 之间,可以理解为统一量纲。
示例代码:
import torch
from torchvision.transforms import Normalize
import matplotlib.pyplot as plt
import math
import torchvision.transforms.functional as TF
def show_image(images):
# 图像绘制几行几列
img_num = len(images)
row_num = math.ceil(img_num / 3)
col_num = 3 if img_num > 3 else img_num
for idx, img in enumerate(images):
# Tensor 转换为 PIL
pil = TF.to_pil_image(img)
plt.subplot(row_num, col_num, idx + 1)
plt.imshow(pil)
plt.xlim(0, img.size(2))
plt.ylim(img.size(1), 0)
plt.title('原图' if not idx else '归一化')
plt.tight_layout()
plt.show()
def test01():
torch.manual_seed(0)
batch_inputs = torch.randint(0, 255, size=[3, 4, 4]).float()
print(batch_inputs)
# 1. 第一种方式: 规范化到 [0, 1] 之间
batch_inputs1 = batch_inputs / 255
print(batch_inputs1)
# 2. 第二种方式: 规范化到 [-1, 1] 之间
batch_inputs2 = (batch_inputs / 127.5) - 1.0
print(batch_inputs2)
# torchvision.transforms.ToTensor 使用的是第一种方式
show_image([batch_inputs, batch_inputs1, batch_inputs2])
if __name__ == '__main__':
test01()
程序执行结果:
在 torchvision.transforms.ToTensor 中是通过将像素值除以 255 归一化到 [0, 1] 之间。
- 规范化 {#title-1} =================
规范化以通道为单位,不同的通道会具有不同的均值和标准差。将某个通道内所有像素值减去通道的均值,再除以该通道的标准差。
这里均值和方差并不是单独计算某个样本的不同通道的均值和标准差实现对该样本特征的规范化,而是根据输入的整个数据集来计算得到不同通道的均值和标准差。
比如:我们有 10000 张图片,图片有 3 个通道,则均值会有 3 个,标准差也会有 3 个。均值和标准差是基于 1000 图片或者采样来计算每个通道。
从直观上理解,规范化使得所有的图片都满足相同的分布,这也有利于模型的训练。
示例代码:
import torch
from torchvision.transforms import Normalize
def test02():
torch.manual_seed(0)
batch_inputs = torch.randint(0, 5, size=[2, 3, 4, 4]).float()
print(batch_inputs)
# 实例化 Normalize 对象
normalization = Normalize(mean=[1, 2, 3], std=[2, 3, 4])
# 规范化输入图像的每一个通道
batch_inputs_normalized = normalization(batch_inputs)
print('-' * 50)
print(batch_inputs_normalized)
if __name__ == '__main__':
test02()
程序执行结果:
tensor([[[[4., 4., 3., 0.],
[3., 4., 2., 3.],
[2., 3., 1., 1.],
[1., 4., 3., 1.]],
[[1., 3., 4., 3.],
[1., 4., 1., 4.],
[4., 1., 4., 4.],
[4., 0., 1., 2.]],
[[3., 0., 0., 0.],
[2., 4., 1., 3.],
[3., 3., 1., 4.],
[1., 2., 3., 0.]]],
[[[2., 1., 0., 4.],
[3., 1., 1., 0.],
[3., 1., 1., 2.],
[4., 1., 3., 4.]],
[[0., 0., 3., 2.],
[3., 2., 2., 0.],
[0., 0., 3., 1.],
[4., 1., 1., 0.]],
[[2., 4., 4., 3.],
[4., 3., 4., 3.],
[4., 3., 0., 3.],
[2., 3., 0., 1.]]]])
--------------------------------------------------
tensor([[[[ 1.5000, 1.5000, 1.0000, -0.5000],
[ 1.0000, 1.5000, 0.5000, 1.0000],
[ 0.5000, 1.0000, 0.0000, 0.0000],
[ 0.0000, 1.5000, 1.0000, 0.0000]],
[[-0.3333, 0.3333, 0.6667, 0.3333],
[-0.3333, 0.6667, -0.3333, 0.6667],
[ 0.6667, -0.3333, 0.6667, 0.6667],
[ 0.6667, -0.6667, -0.3333, 0.0000]],
[[ 0.0000, -0.7500, -0.7500, -0.7500],
[-0.2500, 0.2500, -0.5000, 0.0000],
[ 0.0000, 0.0000, -0.5000, 0.2500],
[-0.5000, -0.2500, 0.0000, -0.7500]]],
[[[ 0.5000, 0.0000, -0.5000, 1.5000],
[ 1.0000, 0.0000, 0.0000, -0.5000],
[ 1.0000, 0.0000, 0.0000, 0.5000],
[ 1.5000, 0.0000, 1.0000, 1.5000]],
[[-0.6667, -0.6667, 0.3333, 0.0000],
[ 0.3333, 0.0000, 0.0000, -0.6667],
[-0.6667, -0.6667, 0.3333, -0.3333],
[ 0.6667, -0.3333, -0.3333, -0.6667]],
[[-0.2500, 0.2500, 0.2500, 0.0000],
[ 0.2500, 0.0000, 0.2500, 0.0000],
[ 0.2500, 0.0000, -0.7500, 0.0000],
[-0.2500, 0.0000, -0.7500, -0.5000]]]])
两张图片的第一个通道使用 mean=1, std=2,第二个通道使用 mean=2, std=3 ... 以此类推。
归一化是从量纲角度来考虑样本数值对模型的影响,规范化是从分布角度来考虑数值对模型的影响。先归一化,再标准化,可以使得图像在量纲统一的基础上将所有的图片规范化到相同的分布。如果直接标准化,而没有归一化,则可以理解为,在不同量纲的影响下规范化到相同的分布。那么,可能出现,不同的特征图分布差异会很大。而感性理解上,不同的特征图应该是分布差不多,不应该差异那么大。