data格式转换

1
2
3
4
features.ToTensor() # 将PIL图像 or NumPy数组 -> PyTorch的Tensor格式, 像素值[0, 255] ->[0.0, 1.0]
features.cpu().numpy() # PyTorch 的 Tensor 转换成 NumPy 的 ndarray,如果 tensor 在 GPU 上,必须先 .cpu()
features.tolist() # 任意维度Tensor->Python list/int/float
features.item() # 单个元素Tensor->Python int/float

data维度转换

针对tensor

大多数 PyTorch 模型(比如 ResNet)期望输入 shape 为 [batch_size, channels, height, width],也就是 4 个维度。

1
2
3
.squeeze(0)  # 去除第 0 维(最前面)中大小为 1 的维度
.unsqueeze(0) # PyTorch 中 Tensor 的一个常用操作,在第 0 维添加一个维度
.view(batch_size, -1) # PyTorch 的张量重塑(reshape)函数。

针对数组和tensor

.flatten()

把多维数组“拉直”为一维向量 默认按行优先 C-style 顺序

1
2
3
4
5
6
7
8
# example:
x = torch.tensor([
[[1, 2], [3, 4]], # x[0]
[[5, 6], [7, 8]] # x[1]
]) # shape = [2, 2, 2]

x.flatten(start_dim=1) # tensor([1, 2, 3, 4, 5, 6, 7, 8])
x.reshape(-1) #等价

.reshape()

返回一个新的张量,具有不同的 shape,不要求连续内存。reshape 只关注总元素数一样

1
2
3
pos_embed.shape # torch.Size([1, 196, 768])
pos_embed.reshape(1, 14, 14, 768)
pos_embed.shape # torch.Size([1, 14, 14, 768])

.view()

返回一个相同数据的视图,具有不同的 shape

  • tensor 必须是 contiguous(内存连续)

  • 否则需要 .contiguous() 先复制为连续内存

1
2
3
pos_embed.shape # torch.Size([1, 14, 14, 768])
pos_embed.view(1, -1, 768)
pos_embed.shape # torch.Size([1, 196, 768])

.permute()

改变张量维度的顺序,不改变数据本身,也不改变 shape 中各维的大小

1
2
pos_embed.reshape(1, w0, h0, dim).permute(0, 3, 1, 2),  # → [1, dim, w0, h0]
pos_embed = pos_embed.contiguous() # permute返回非连续张量

.transpose( , )

仅能交换张量中的两个维度,返回一个新的张量,不改变数据本身,也不改变 shape 中各维的大小

1
2
q, k, v = qkv[0] * self.scale, qkv[1], qkv[2]
attn = q @ k.transpose(-2, -1) # 交换最后两个维度(-2表示倒数第2维,-1表示最后1维)

torch.stack(tensors, dim=0) torch.cat(tensors, dim=0)

将多个张量沿着一个新维度拼接

1
2
3
4
5
6
7
a = torch.tensor([1, 2])
b = torch.tensor([3, 4])
c = torch.tensor([5, 6])

torch.stack([a, b, c], dim=0) # tensor([[1, 2],[3, 4],[5, 6]])
torch.stack([a, b, c], dim=1) # tensor([[1, 3, 5],[2, 4, 6]]) 新增的维度在第1维
torch.cat([a, b, c], dim=0) # tensor([1, 2, 3, 4, 5, 6]) 在现有维度上拼接

.expand(x.shape[0], -1, -1)

  • x.shape[0] 替换了第一个维度,扩展成 batch size B
  • -1 表示保持原维度大小不变(第二和第三维不变)。
  • 由于第一维是1,.expand() 会沿第一维“复制” self.register_tokens,但实际上是共享内存的,不复制数据。

针对array

1
2
3
4
5
6
7
8
9
10
# 将one-hot编码的标签y转换为类别索引标签
y = np.argmax(y, axis=1) # 对每一行找出最大值的索引,得到类别标签的整数形式
# example:
y_train = np.array([
[0, 0, 1], # 类别 2
[1, 0, 0], # 类别 0
[0, 1, 0], # 类别 1
])

y_train = np.argmax(y_train, axis=1) # array([2, 0, 1])

ML model input

one-hot 接受情况

模型类别 模型名称/框架 是否接受 One-Hot 标签 正确标签格式
传统机器学习模型 LogisticRegression (sklearn) ❌ 不接受 整数类别标签(如 0,1,2)
SVM / SVC ❌ 不接受 整数类别标签
RandomForestClassifier ❌ 不接受 整数类别标签
KNeighborsClassifier ❌ 不接受 整数类别标签
DecisionTreeClassifier ❌ 不接受 整数类别标签
GaussianNB ❌ 不接受 整数类别标签
神经网络框架 PyTorch(自定义分类网络) ❌ 通常不接受 整数标签,用 CrossEntropyLoss
TensorFlow / Keras + SparseCategoricalCrossentropy ❌ 不接受 整数标签(如 0,1,2)
TensorFlow / Keras + CategoricalCrossentropy ✅ 接受 One-hot 标签
框架 常用损失函数 标签格式要求
PyTorch CrossEntropyLoss 整数标签([1, 2, 0, ...]
Keras SparseCategoricalCrossentropy 整数标签
Scikit-learn 所有分类模型 整数标签

模型训练相关

.detach() 作用

1
2
3
4
5
6
7
8
9
10
11
features = encoder(x)          # encoder前向,生成特征
output = task(features) # task用特征做预测
loss = loss_fn(output, target) # 计算损失
loss.backward() # 反向传播,默认梯度会传到encoder和task参数

# 但如果
features = encoder(x).detach() # 断开连接
output = task(features)
loss = loss_fn(output, target)
loss.backward()
# 梯度只会传到task参数,encoder参数不变

hydra

配置组合(Configuration Composition)

Hydra 允许你将多个配置文件(YAML)组合起来,这种方式称为配置组合(config composition)。你可以用模块化的方式组织配置,从而提高可维护性和重用性。

1
2
3
4
5
6
7
8
# config.yaml
defaults:
- model: resnet
- dataset: imagenet

# config/model/resnet.yaml
hidden_size: 256
layers: 50

命令行覆盖配置

Hydra 允许你在命令行中覆盖配置参数,无需修改代码或配置文件本身。

1
python train.py model.hidden_size=512 dataset=mnist

动态“添加”一个新配置组

1
2
python demo_forcefield.py \
+experiment=downstream_task/forcefield/gelsight_dino \ # 这个参数会被Hydra当作一个配置 override, 动态“添加”一个配置组项,名为 experiment(在 defaults: 中没有声明过)

多运行支持(Multirun)

通过 -m 选项,Hydra 可以自动运行多个配置组合,用于超参数搜索等任务。

1
python train.py -m model.hidden_size=128,256,512

💡使用示例

1
2
3
4
5
6
7
8
9
10
import hydra
from omegaconf import DictConfig

# @hydra.main(config_path="config", config_name="default")
@hydra.main(version_base="1.3", config_path="config") # 1.3以上,如果主配置目录中只有一个.yaml文件(比如 default.yaml),可以省略 config_name,Hydra 会自动使用它。
def my_app(cfg: DictConfig):
print(cfg)

if __name__ == "__main__":
my_app()
1
2
3
4
5
6
7
8
9
配置文件结构:
config/
├── default.yaml
├── model/
│ ├── resnet.yaml
│ └── vgg.yaml
└── dataset/
├── mnist.yaml
└── imagenet.yaml