File size: 3,963 Bytes
32fb2ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73526b1
32fb2ef
 
 
 
 
 
 
 
 
73526b1
32fb2ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73526b1
32fb2ef
73526b1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env python3
"""Generate SimCLR notebook."""

import nbformat as nbf

nb = nbf.v4.new_notebook()
nb.metadata = {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"},"language_info": {"name": "python", "version": "3.12.0"}}

cells = []
def md(s): cells.append(nbf.v4.new_markdown_cell(s))
def code(s): cells.append(nbf.v4.new_code_cell(s))

md("# SimCLR: Contrastive Learning\n\nSelf-supervised representation learning with NT-Xent loss on CIFAR-10.")

md("""## 背景

SimCLR(Chen et al. 2020)通过**对比学习**在没有标签的情况下学习图像表示。
核心思路:同一张图的不同增强视图应该得到相似的表示,不同图的视图应该不同。

关键组件:
- **数据增强**:随机裁剪、颜色抖动、高斯模糊、灰度化
- **Encoder**:ResNet18(去掉最后一层)
- **Projector**:MLP 将表示投影到对比空间
- **NT-Xent Loss**:归一化温度标度的交叉熵损失

训练完成后,encoder 可以迁移到下游分类任务,只需加一个线性分类器。
""")

md("""## 数学原理

### NT-Xent Loss

对每个批次 $N$ 张图,生成两个增强视图,共 $2N$ 个样本:

$$\ell(i, j) = -\\log \\frac{\\exp(\\text{sim}(z_i, z_j) / \\tau)}{\\sum_{k=1}^{2N} \\mathbb{1}_{[k \\neq i]} \\exp(\\text{sim}(z_i, z_k) / \\tau)}$$

其中 $\\text{sim}(u, v) = \\frac{u^\\top v}{\\|u\\|\\|v\\|}$ 是余弦相似度,$(i, j)$ 是一对正样本(同一图的两种增强)。
""")

code("""\
import torch
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import transforms
from datasets import load_dataset

from gen.simclr.model import SimCLR
from utils.config import load_config
from utils.seed import set_seed
from utils.device import get_device

device = get_device()
print(f"Device: {device}")
""")

code("""\
from gen.simclr.data import SimCLRTransform, load_cifar10_simclr

loader = load_cifar10_simclr(batch_size=256, num_workers=4)
print(f"Batches per epoch: {len(loader)}")
""")

code("""\
model = SimCLR(project_dim=128, temperature=0.5).to(device)
print(f"Parameters: {model.num_params():,}")

# Count encoder vs projector params
enc = sum(p.numel() for p in model.encoder.parameters() if p.requires_grad)
proj = sum(p.numel() for p in model.projector.parameters() if p.requires_grad)
print(f"  Encoder (ResNet18): {enc:,}")
print(f"  Projector (MLP):    {proj:,}")
""")

md("""## 训练

> ⏱ 预估耗时:**100 epoch × ~40s/epoch ≈ 1 小时**(M4 Max, batch_size=256)
> 如果太久,把下面 `NUM_EPOCHS` 改到 10 先看 loss 趋势。
""")

code("""\
NUM_EPOCHS = 100
LR = 0.0003

optimizer = optim.Adam(model.parameters(), lr=LR)
loss_hist = []

for epoch in range(1, NUM_EPOCHS + 1):
    model.train()
    total_loss = 0.0
    num_batches = 0
    for batch in loader:
        x1, x2 = batch["view1"].to(device), batch["view2"].to(device)
        z1, z2 = model(x1), model(x2)
        loss = model.nt_xent_loss(z1, z2)
        optimizer.zero_grad(); loss.backward(); optimizer.step()
        total_loss += loss.item(); num_batches += 1

    avg = total_loss / num_batches
    loss_hist.append(avg)
    print(f"Epoch [{epoch:2d}/{NUM_EPOCHS}]  Loss: {avg:.4f}")
""")

md("""## Loss 曲线""")

code("""\
import matplotlib.pyplot as plt
plt.plot(loss_hist)
plt.xlabel("Epoch"); plt.ylabel("Loss"); plt.title("SimCLR Contrastive Loss"); plt.grid(True)
plt.show()
""")

md("""\
## 思考题

1. SimCLR 为什么需要 Projector?直接用 encoder 的输出做对比学习效果会差吗?
2. 数据增强的质量对对比学习有多重要?如果只用翻转,loss 会怎样?
3. NT-Xent 中的 temperature $\\tau$ 起什么作用?增大/减小各有什么影响?
4. SimCLR 为什么需要大 batch size?(提示:负样本数量)
""")

nb.cells = cells
with open("gen/simclr/simclr.ipynb", "w") as f:
    nbf.write(nb, f)
print("Generated gen/simclr/simclr.ipynb")