开场 3 秒
“我只是想把灰度图丢进神经网络,结果教程里突然冒出两行代码:
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
——这俩 0.5 是干嘛的?能不能不写?”
如果你有同样的灵魂发问,今天 5 分钟,咱们把它聊成大白话。
一、ToTensor 先把图像“压扁”成 0~1
- 照片在电脑里其实是 0~255 的整数。
- ToTensor() 一做除法:像素值 ÷ 255,全部变成 0~1 之间的小数。
- 对灰度图来说,最后得到的是一个 1×H×W 的张量,数值范围 [0, 1]。
二、Normalize 再把 0~1 变成“标准身材”
- 神经网络喜欢“均值为 0、方差为 1”的数据,训练更稳、收敛更快。
- Normalize 的公式就一句:
output = (input – mean) ÷ std - 灰度图只有一个通道,所以只传一个数:
transforms.Normalize(mean=(0.5,), std=(0.5,))
含义:
mean=0.5 → 把刚才的 0~1 数据减去 0.5,中心移到 -0.5~0.5
std =0.5 → 再除以 0.5,范围放大到 -1~1
整幅图被“拍扁”到以 0 为中心、左右对称的 [-1, 1] 区间。就是于
三、为什么偏偏选 0.5,不选 0.3 或 0.8?
- 0~1 的中点,减完刚好对称。就是简单粗暴:0.5
- 除 0.5 相当于乘 2,口算就能想到范围变成 -1~1,不用查表。
- 对灰度图来说,这个“(-1,1)”区间足够让大多数 CNN 开心,且不容易饱和(掉进梯度消失区)。
- 不是玄学,也不是银弹——只是“经验值 + 计算方便”。要是材料集整体偏暗/偏亮,完全可以自己统计 mean/std 再替换。
四、彩色图怎么玩?
彩色图有 RGB 三个通道,就要给三个数:
transforms.Normalize(mean=(0.5, 0.5, 0.5),
std =(0.5, 0.5, 0.5))
原理一模一样:每个通道分别 (x-0.5)/0.5,最终全部落到 [-1,1]。
五、不写 Normalize 行不行?
行,但:
- 输入范围 0~1 时,ReLU 输出全正,后续层容易“偏移”到正区间,收敛慢。
- BatchNorm 层会帮你再归一化一次,可那得等网络自己学,训练初期波动大。
- 统一先归一化到 [-1,1],相当于给模型“提前铺好铁轨”,省事又稳。
六、总结(建议收藏)
[0,255] 原图
↓ ToTensor ÷255
[0,1] 浮点张量
↓ Normalize (0.5, 0.5)
(x-0.5)/0.5
[-1,1] 模型最爱
七、动手实验 30 秒
把下面代码粘进 Jupyter,打印前后数值,肉眼可见变化:
from torchvision import transforms
from PIL import Image
import torch
img = Image.open('test_gray.jpg').convert('L')
trans = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
t = trans(img)
print('最小值:', t.min(), '最大值:', t.max())
运行结果:最小值 ≈ -1,最大值 ≈ 1,目标达成!
八、常见小白 Q&A
Q1: 减 0.5 后像素变成负数,网络不会“懵”吗?
A: 不会。神经网络的权重可正可负,负数一样能乘,反而零均值能让梯度对称更新。
Q2: 我想把范围搞到 0~1,又不想负值怎么办?
A: 把 mean=0, std=1 就相当于不做 Normalize;或者直接用 ToTensor() 后不再归一化即可。
Q3: 均值和标准差必须自己算吗?
A: 生产环境最好跑一遍数据集统计真实 mean/std(PyTorch 官方脚本有),但 0.5/0.5 在大多数视觉任务里已“够用”。
结尾彩蛋
“0.5 不是魔法,只是让像素值从 0~1 搬家到 -1~1 的搬家公司。”
下次再见到这行代码,希望能会心一笑:
“哦,就是先减一半,再放大两倍,走了!”
要是这篇小文帮你扫清盲区,记得点个“赞”和“在看”,转给同样被 0.5 吓到的小伙伴吧~
(完)