一句话解释
Data does not always come in its final processsed form that is required for training machine learning algorithms. We use transforms to perform some manipulation of the data and make it suitable for training. 数据并不总是以训练机器学习算法所需的最终处理形式出现。我们使用Transforms对数据进行一些操作,使其适合于训练。
最小例子
import torch
import torch.nn.functional as F
from torchvision import datasets
from torchvision.transforms import v2
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32, scale=True)]),
target_transform=v2.Lambda(
lambda y: F.one_hot(torch.tensor(y), num_classes=10).float()
),
)
The FashionMNIST features are in PIL Image format, and the labels are integers. For training, we need the features as normalized tensors, and the labels as one-hot encoded tensors. To make these transformations, we use the torchvision.transforms.v2 API along with torch.nn.functional.one_hot.
transform和target_transform具体做了什么?
transform
transform=v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
])
v2.Compose 表示依次执行其中的转换.
v2.ToImage()
FashionMNIST 原始图片是 PIL Image。它将图片转换为 PyTorch 图像 Tensor:
转换前:PIL Image,大小 28×28
转换后:Image Tensor,形状 (1, 28, 28);
转换后的数分别代表:
1 → 灰度通道数
28 → 高度
28 → 宽度
转换后通常还是:dtype=torch.uint8
像素范围:0~255
v2.ToDtype(torch.float32, scale=True)
它完成两件事: uint8 → float32 0~255 → 0.0~1.0 例如: 0 → 0.0 128 → 约 0.502 255 → 1.0 最终图片通常是:
image.shape
# torch.Size([1, 28, 28])
image.dtype
# torch.float32
image.min(), image.max()
# 范围大致在 0~1
官方教程把它称为 normalized tensor,但更准确地说,这是缩放(rescaling)到 [0, 1]。 严格意义上的标准化一般还要使用:
v2.Normalize(mean=[...], std=[...])
把数据转换成近似零均值、单位标准差。
target_transform
target_transform=v2.Lambda(
lambda y: F.one_hot(
torch.tensor(y),
num_classes=10,
).float()
)
可以拆成4步:
# 假设原始标签是:y = 3
y = 3
# 第一步,把Python整数转成Tensor:
torch.tensor(y) #tensor(3)
# 第二步,进行one-hot编码:
F.one_hot(torch.tensor(3), num_classes=10)
# 结果是:
tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
# 第三步,转换为浮点类型:
.float()
# 得到:
tensor([0., 0., 0., 1., 0., 0., 0., 0., 0., 0.])
# 第四步,v2.Lambda 把这段自定义函数包装成一个 transform。
为什么这里我们要把features转换成normalized tensors?
主要有三个原因:
- 神经网络层使用 Tensor 计算;
- GPU/MPS 可以直接处理 Tensor;
- 模型参数通常是浮点数,因此输入也通常转换为浮点数。 将像素从 [0, 255] 缩放到 [0, 1],则是为了减小输入数值范围,使模型优化过程通常更加稳定。 这并不是说神经网络绝对不能处理 [0, 255],而是尺度统一、数值较小的输入一般更容易训练。
什么是one-hot encoded tensors?
one-hot 使用一个向量表示类别:
- 向量长度等于类别数;
- 正确类别对应的位置为 1;
- 其他位置全部为 0。 FashionMNIST 有 10 个类别:
标签 0 → [1,0,0,0,0,0,0,0,0,0]
标签 1 → [0,1,0,0,0,0,0,0,0,0]
标签 3 → [0,0,0,1,0,0,0,0,0,0]
标签 9 → [0,0,0,0,0,0,0,0,0,1]
它把一个类别编号转换成一个长度为 10 的类别向量。
为什么有时要使用one-hot?
模型最后通常为每个类别输出一个值:
模型输出:[2.1, -0.4, 0.7, 3.2, ..., 0.1]
标签向量:[0, 0, 0, 1, ..., 0]
当损失函数要求目标也是一个向量或概率分布时,就需要 one-hot 或类似形式,例如:
- 使用软标签;
- label smoothing;
- mixup 等混合标签;
- 某些自定义损失函数;
- 多标签分类中的 multi-hot 标签。
FashionMNIST 必须使用 one-hot 吗?
不必须。 对于普通单标签分类,通常使用:
loss_fn = torch.nn.CrossEntropyLoss()
这时标签保留为类别编号即可: 3 而不需要转换成: [0,0,0,1,0,0,0,0,0,0] 推荐的数据集写法是:
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
]),
)
不传 target_transform,DataLoader 组成 batch 后,标签通常是:
labels.shape
# torch.Size([batch_size])
labels.dtype
# torch.int64
而模型输出是:
logits.shape
# torch.Size([batch_size, 10])
两者可以直接计算:
loss = F.cross_entropy(logits, labels)
当前 CrossEntropyLoss 也支持 one-hot 形式的浮点目标,但官方文档说明:对普通单类别标签,直接使用类别索引通常计算效率更高。PyTorch CrossEntropyLoss 文档
所以官方 transforms 页面中的 one-hot 代码,更适合理解为:
演示如何使用
target_transform改变标签形式。
而不是:
所有 FashionMNIST 训练都必须使用 one-hot 标签。
简要总结:
ToImage()
PIL 图片 → 图像 Tensor
ToDtype(float32, scale=True)
uint8 的 0~255 → float32 的 0~1
one_hot()
整数类别 → 长度为 10 的类别向量
普通 FashionMNIST + CrossEntropyLoss
通常保留整数标签,不需要 one-hot
小巧思
为什么损失函数要求目标也是向量或者概率分布的时候需要用one-hot或类似形式?我可以理解为one-hot是为了满足模型输出和label可以直接计算出现的吗? logits是什么?有什么作用? cross_entropy是什么?有什么作用? 模型为每个类别输出的值是识别可能性吗(还是并不重要)? 模型一般输出是什么?
可以把整个分类训练过程先理解成:
图片
↓
模型
↓
logits:每个类别的原始分数
↓
CrossEntropyLoss(logits, label)
↓
一个标量损失 loss
↓
反向传播,更新模型参数
1. one-hot 是为了让输出和标签可以直接计算吗?
可以部分这样理解,但需要加一个限制:
one-hot 把“正确类别”表示成与模型输出长度相同的向量,使某些损失函数能逐类别比较模型输出和目标。
例如有三个类别:
0 = 猫
1 = 狗
2 = 鸟
模型为一张图片输出三个值:
logits = tensor([2.0, 0.5, -1.0])
如果正确类别是猫,可以用整数表示:
label = 0
也可以用 one-hot 表示:
label = tensor([1., 0., 0.])
one-hot 与模型输出具有相同的类别维度:
模型: [2.0, 0.5, -1.0]
目标: [1.0, 0.0, 0.0]
↑
正确类别
但是这并不意味着损失函数一定要直接做减法。不同损失函数有不同的计算方式。
CrossEntropyLoss 可以直接接收整数标签:
loss = F.cross_entropy(logits, torch.tensor(0))
它会在内部利用标签 0 找到正确类别对应的预测。数学效果与使用 one-hot 表示硬标签相同,但类别索引通常更简洁、高效。
只有当目标本身是一个概率分布时,向量形式才真正不可替代。例如软标签:
target = tensor([0.8, 0.15, 0.05])
它表达的不是“百分之百是猫”,而是:
猫:80%
狗:15%
鸟:5%
这种信息无法用一个整数类别表示。
2. logits 是什么?
logits 是模型最后一层输出的原始类别分数。
例如 FashionMNIST 有 10 个类别,模型最后一层可能是:
nn.Linear(512, 10)
每张图片就会得到 10 个 logits:
logits = tensor([
2.1, # T-shirt
-0.5, # Trouser
0.3, # Pullover
4.2, # Dress
1.1, # Coat
-2.0, # Sandal
0.8, # Shirt
-1.2, # Sneaker
0.1, # Bag
-0.7, # Ankle boot
])
最大值是:
4.2 → Dress
所以模型预测类别为 Dress。
可以直接获得类别索引:
predicted_class = logits.argmax()
# tensor(3)
logits 是概率吗?
不是。logits 可以:
- 小于 0;
- 大于 1;
- 不需要加起来等于 1。
例如:
tensor([2.0, 1.0, -3.0])
这不是概率,但可以理解为模型对各类别的相对偏好:
2.0 → 最偏向这个类别
1.0 → 次之
-3.0 → 很不偏向这个类别
将 logits 经过 softmax 后,才能得到总和为 1 的概率形式:
probabilities = F.softmax(logits, dim=0)
例如:
logits: [2.0, 1.0, 0.0]
softmax 后: [0.665, 0.245, 0.090]
这些值满足:
每个值在 0~1 之间
总和等于 1
不过它更准确地说是“模型预测的概率分布”,不一定等于现实世界中完全可靠、经过校准的真实概率。
3. logits 的大小有什么意义?
相对大小非常重要:
logit 越大 → 模型越倾向对应类别
而且不同类别之间的差距会影响模型的“信心”。
例如:
[2.0, 1.9, 1.8] → 第一类最大,但优势很小
[8.0, 0.0, -2.0] → 第一类明显占优
经过 softmax 后,第二组会产生更集中的概率分布。
但 logits 的绝对值不是独立解释的概率。例如给所有 logits 都加上同一个数:
[2, 1, 0]
[12, 11, 10]
softmax 结果相同。真正重要的是类别分数之间的差异。
4. cross-entropy 是什么?
交叉熵是分类任务中常用的损失函数,用来衡量:
模型预测的类别分布与正确答案之间有多大差异。
如果正确类别是猫,模型预测为:
猫:0.90
狗:0.08
鸟:0.02
预测很好,损失较小。
如果预测为:
猫:0.01
狗:0.89
鸟:0.10
模型给正确类别的概率很低,损失会很大。
对于 one-hot 目标,交叉熵可以写成:
loss = -Σ target[c] × log(probability[c])
因为 one-hot 中只有正确类别的位置是 1,所以实际上等价于:
loss = -log(正确类别的预测概率)
例如正确类别概率是 0.9:
loss = -log(0.9) ≈ 0.105
正确类别概率是 0.01:
loss = -log(0.01) ≈ 4.605
所以:
正确类别概率越高 → loss 越小
正确类别概率越低 → loss 越大
训练的目标就是通过更新模型参数,让 loss 逐渐下降。
5. CrossEntropyLoss 为什么直接接收 logits?
PyTorch 的:
nn.CrossEntropyLoss()
内部已经完成了类似以下计算:
logits
↓
log_softmax
↓
取正确类别的负对数概率
所以应该直接传入 logits:
loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(logits, labels)
通常不要先手动执行 softmax:
# 通常不要这样
probabilities = F.softmax(logits, dim=1)
loss = loss_fn(probabilities, labels)
CrossEntropyLoss 直接处理 logits 在数值上更稳定。
普通 FashionMNIST 分类通常是:
logits.shape
# (batch_size, 10)
labels.shape
# (batch_size,)
labels.dtype
# torch.int64
计算:
loss = F.cross_entropy(logits, labels)
官方文档也说明,CrossEntropyLoss 同时支持类别索引和类别概率,但普通单标签分类使用类别索引通常更高效:PyTorch CrossEntropyLoss。
6. 模型为每个类别输出的值是识别可能性吗?
可以将 logits 理解为“模型对各类别的原始倾向分数”,但不能直接叫作概率:
logits → 原始分数
softmax(logits) → 概率形式
argmax(logits) → 最终预测类别
因为 softmax 不会改变最大值所在的位置:
logits.argmax(dim=1)
和:
F.softmax(logits, dim=1).argmax(dim=1)
得到的预测类别相同。
因此:
- 只想取得预测类别:直接对 logits 使用
argmax(); - 想显示概率形式:使用
softmax(); - 计算交叉熵损失:直接把 logits 传给
CrossEntropyLoss。
7. 模型一般输出什么?
取决于任务。
| 任务 | 常见模型输出 |
|---|---|
| 多类别分类 | 每个类别一个 logit |
| 二分类 | 一个 logit,或两个类别 logits |
| 多标签分类 | 每个标签一个 logit |
| 回归 | 一个或多个连续数值 |
| 图像分割 | 每个像素、每个类别的 logits |
| 目标检测 | 类别分数、边界框坐标 |
| 文本生成 | 每个位置对词表中每个词的 logits |
| 特征提取 | embedding 特征向量 |
例如 FashionMNIST:
输入一张 28×28 图片
↓
模型
↓
输出 10 个 logits
↓
分别对应 10 个服装类别
批量输入时:
images.shape
# (32, 1, 28, 28)
logits = model(images)
logits.shape
# (32, 10)
表示:
32 张图片
每张图片有 10 个类别分数
最核心的关系是:
模型输出 logits
↓
CrossEntropyLoss 使用标签判断哪些分数应该提高、哪些应该降低
↓
产生一个 loss
↓
backward() 根据 loss 计算梯度
↓
优化器更新模型参数
lambda transforms
target_transform = v2.Lambda(
lambda y: F.one_hot(torch.tensor(y), num_classes=10).float()
)
Lambda transforms apply any user-defined lambda function. Here, we use torch.nn.functional.one_hot to turn the integer label into a one-hot encoded tensor of size 10 (the number of labels in our dataset), then cast it to float to match the expected dtype.
Lambda transform是什么?lambda function又是什么?
这里有两个不同的概念:
lambda function → Python 的匿名函数
v2.Lambda → 把一个函数包装成 torchvision transform
1. 什么是 lambda function?
Python 的 lambda 是一种简短的匿名函数写法。
普通函数:
def add_one(x):
return x + 1
可以简写为:
lambda x: x + 1
两者的作用基本相同:
add_one = lambda x: x + 1
print(add_one(5))
# 6
lambda 的语法是:
lambda 参数: 返回值表达式
例如:
lambda x: x * 2
lambda x, y: x + y
lambda name: name.upper()
分别相当于:
def double(x):
return x * 2
def add(x, y):
return x + y
def uppercase(name):
return name.upper()
lambda 通常只适合非常简单、只需要一个表达式的函数。复杂逻辑应该使用普通 def。
2. 代码中的 lambda 做了什么?
lambda y: F.one_hot(
torch.tensor(y),
num_classes=10,
).float()
它相当于:
def encode_label(y):
label_tensor = torch.tensor(y)
one_hot_label = F.one_hot(
label_tensor,
num_classes=10,
)
return one_hot_label.float()
执行过程如下。假设输入标签是:
y = 3
第一步:
torch.tensor(y)
# tensor(3)
第二步:
F.one_hot(torch.tensor(3), num_classes=10)
# tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
第三步:
.float()
# tensor([0., 0., 0., 1., 0., 0., 0., 0., 0., 0.])
所以它完成:
整数标签 3
↓
长度为 10 的 one-hot Tensor
↓
转换为 float32
3. 什么是 v2.Lambda transform?
v2.Lambda 是 torchvision 提供的一个包装器,用来把自定义函数包装成 transform:
target_transform = v2.Lambda(
lambda y: ...
)
它本身不知道应该如何转换数据。真正的转换逻辑来自传入的函数:
lambda y: F.one_hot(...).float()
可以理解为:
v2.Lambda(自定义函数)
↓
创建一个可以作为 transform 使用的对象
然后把它交给 Dataset:
ds = datasets.FashionMNIST(
...,
target_transform=target_transform,
)
当读取样本时:
image, label = ds[0]
Dataset 内部会做类似的事情:
label = target_transform(label)
也就是:
label = target_transform(原始整数标签)
4. 为什么不直接传 lambda?
从“可调用”的角度,很多 Dataset 中直接传函数也可以:
target_transform=lambda y: F.one_hot(
torch.tensor(y),
num_classes=10,
).float()
使用 v2.Lambda 的意义主要是把普通函数明确包装成 torchvision 的 transform 对象,方便与其他 transform 统一组合和表达。
例如:
target_transform = v2.Compose([
v2.Lambda(lambda y: torch.tensor(y)),
v2.Lambda(lambda y: F.one_hot(y, num_classes=10)),
v2.Lambda(lambda y: y.float()),
])
不过这个例子拆得过细,实际使用原来的单个函数更简洁。
5. Lambda 和 lambda 不是同一个东西
注意大小写:
v2.Lambda(...)
是 torchvision 中的一个类。
lambda y: ...
是 Python 语言创建匿名函数的语法。
把它们组合起来:
v2.Lambda(
lambda y: y + 1
)
意思是:
创建一个 torchvision transform,它使用“输入加一”这个匿名函数处理数据。
6. 什么时候应该用普通函数?
如果转换逻辑稍微复杂,最好使用普通函数:
def encode_label(y):
y = torch.tensor(y)
y = F.one_hot(y, num_classes=10)
return y.float()
target_transform = v2.Lambda(encode_label)
它比长 lambda 更容易:
- 阅读;
- 调试;
- 添加类型提示;
- 编写测试;
- 在多进程 DataLoader 中序列化。
尤其使用:
DataLoader(dataset, num_workers=...)
时,匿名 lambda 在某些多进程启动方式下可能无法被序列化。普通的模块顶层函数更稳妥。
简单总结:
lambda function
→ Python 的简短匿名函数
v2.Lambda
→ 将自定义函数包装成 torchvision transform
这段代码
→ 将整数标签转换为长度为 10 的浮点 one-hot Tensor
怎么写transform
如何写好一个transform?是说我们首先要知道我们数据集中数据“形状”是什么样的,并且知道我们训练所需的数据“形状”是什么样的,然后对应转换就好了吗?那么如何确定这两者呢? 你的理解基本正确,但要把“形状”扩展成更完整的概念:
写 transform,就是把原始样本的数据格式,转换成模型和损失函数所要求的格式。
这里的“格式”不只包括 shape,还包括:
- 数据类型:PIL Image、NumPy、Tensor
shape:(H, W)、(C, H, W)等dtype:uint8、float32、int64- 数值范围:
[0, 255]、[0, 1] - 维度含义:哪个是通道、高度、宽度
- 标签形式:类别索引、one-hot、边界框、掩码
- 数据语义:翻转图片时,标签或边界框是否也要改变
可以把它看成一份数据契约:
原始数据格式
↓ transform
模型和损失函数要求的格式
1. 如何确定原始数据是什么样的?
最可靠的方法是:先不使用 transform,直接取一个样本检查。
例如 FashionMNIST:
from torchvision import datasets
raw_ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=None,
target_transform=None,
)
image, label = raw_ds[0]
print(type(image))
print(image.size)
print(image.mode)
print(type(label))
print(label)
可能输出:
<class 'PIL.Image.Image'>
(28, 28)
L
<class 'int'>
9
这说明:
图片类型:PIL Image
图片大小:28×28
图片模式:L,即单通道灰度图
标签类型:Python int
标签取值:0~9
如果原始数据已经是 Tensor,可以检查:
print(type(image))
print(image.shape)
print(image.dtype)
print(image.min(), image.max())
因此,面对陌生数据集时,至少检查:
sample = dataset[0]
print(type(sample))
print(sample)
并进一步检查其中每个字段的:
type
shape
dtype
min/max
除此之外,还应该查看数据集文档,确认每个维度和标签具体表示什么。仅仅看到 (3, 224, 224),并不能自动确定它是 (C, H, W),还是其他含义。
2. 如何确定模型需要什么数据?
主要看三个地方:
- 模型第一层或模型文档;
- 模型的
forward(); - 使用的损失函数。
根据模型第一层判断
例如:
nn.Conv2d(
in_channels=1,
out_channels=32,
kernel_size=3,
)
说明模型希望单张图片的形状类似:
(C, H, W) = (1, H, W)
DataLoader 组成 batch 后则是:
(N, C, H, W)
例如:
(32, 1, 28, 28)
其中 N=32 是 batch size。这个 batch 维通常由 DataLoader 自动添加,transform 不需要处理。
如果第一层是:
nn.Linear(784, 512)
说明进入这个线性层的数据最后一维需要有 784 个特征。FashionMNIST 的:
1 × 28 × 28 = 784
因此模型中通常会先执行:
x = torch.flatten(x, start_dim=1)
把:
(N, 1, 28, 28)
变成:
(N, 784)
这个 Flatten 可以写在模型中,不一定要写在 transform 中。
根据损失函数判断标签格式
例如:
loss_fn = nn.CrossEntropyLoss()
普通单标签分类推荐的格式是:
模型输出:float32,形状 (N, C)
标签: int64,形状 (N,)
其中:
N是 batch size;C是类别数。
例如 FashionMNIST:
logits.shape = (32, 10)
labels.shape = (32,)
labels.dtype = torch.int64
如果使用:
nn.BCEWithLogitsLoss()
目标通常需要和模型输出具有相同形状,并且是浮点数:
logits.shape = (N, C)
targets.shape = (N, C)
targets.dtype = float32
所以标签是否需要 one-hot,主要由损失函数要求决定,而不是由数据集本身决定。
3. FashionMNIST 应该怎样设计 transform?
如果使用普通模型和 CrossEntropyLoss,最小转换就足够了:
transform = v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
])
它完成:
PIL Image
↓ ToImage
形状为 (1, 28, 28) 的图像 Tensor
↓ ToDtype
float32、像素范围 [0, 1]
标签不需要转换:
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=transform,
)
如果模型要求固定尺寸,例如 (1, 32, 32),再增加:
transform = v2.Compose([
v2.ToImage(),
v2.Resize((32, 32)),
v2.ToDtype(torch.float32, scale=True),
])
如果预训练模型要求 3 通道、224×224 和特定均值方差,那么转换会更加完整:
transform = v2.Compose([
v2.ToImage(),
v2.Resize((224, 224)),
v2.Grayscale(num_output_channels=3),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
),
])
这些步骤不是越多越好,而是由模型的数据契约决定。
4. 怎样验证 transform 写对了?
先检查一个转换后的样本:
image, label = ds[0]
print("image type:", type(image))
print("image shape:", image.shape)
print("image dtype:", image.dtype)
print("image range:", image.min().item(), image.max().item())
print("label type:", type(label))
print("label:", label)
FashionMNIST 应该类似:
image shape: torch.Size([1, 28, 28])
image dtype: torch.float32
image range: 0.0 1.0
label: 9
然后检查一个 batch:
from torch.utils.data import DataLoader
loader = DataLoader(ds, batch_size=32)
images, labels = next(iter(loader))
print(images.shape)
print(images.dtype)
print(labels.shape)
print(labels.dtype)
对于普通 FashionMNIST 分类,预期是:
images.shape = (32, 1, 28, 28)
images.dtype = float32
labels.shape = (32,)
labels.dtype = int64
最后进行一次完整试运行:
logits = model(images)
loss = loss_fn(logits, labels)
print(logits.shape)
print(loss)
如果能够完成一次:
取样本 → 组成 batch → 模型前向传播 → 计算 loss
就说明最基本的数据格式是匹配的。
也可以增加断言:
assert images.ndim == 4
assert images.shape[1] == 1
assert images.dtype == torch.float32
assert labels.dtype == torch.int64
assert logits.shape == (32, 10)
5. 一个实用的 transform 设计流程
可以按照以下顺序:
1. 明确任务
分类、回归、检测还是分割?
2. 检查原始样本
type、shape、dtype、数值范围、标签含义
3. 查看模型输入要求
通道数、尺寸、维度顺序、数值范围
4. 查看损失函数要求
标签是整数索引还是浮点向量?
5. 编写最小 transform
只添加真正需要的转换
6. 检查转换后的单个样本
7. 检查 DataLoader 生成的 batch
8. 完成一次 forward 和 loss 试运行
6. 写 transform 时还应注意什么?
训练集可以使用随机数据增强:
train_transform = v2.Compose([
v2.ToImage(),
v2.RandomHorizontalFlip(),
v2.ToDtype(torch.float32, scale=True),
])
验证集和测试集通常不要使用随机增强:
eval_transform = v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
])
因为测试时希望同一张图片每次得到一致结果。
对于检测和分割任务,还要同步转换图片和目标。例如水平翻转图片时:
- 边界框也必须翻转;
- 分割 mask 也必须翻转;
- 关键点坐标也必须改变。
所以最准确的总结是:
写好 transform,首先要明确原始样本的数据契约,再明确模型和损失函数的输入契约,最后用尽可能少的转换连接两者,并通过单样本、batch、forward 和 loss 四层检查进行验证。
仍未解决的问题:
软标签是什么? 所有任务的模型的最后一层输出的结果都是“原始类别分数”-logits吗? softmax怎么计算的?为什么给所有logits加上同一个数softmax结果相同? 模型是怎么输出的?或者说数据是怎么在模型里面流转的?每种模型都不一样吗? 随机数据增强是什么?
Conversation
评论
欢迎交流这篇文章。评论公开保存在 GitHub Discussions,发表前需要登录 GitHub。