动量(Momentum)优化的过程:从直觉到公式

副标题 / 摘要 动量通过累积历史梯度“惯性”来加速收敛、减少震荡。本文用 ACERS 框架拆解动量更新过程、公式与工程场景,并提供最小 PyTorch 示例。 预计阅读时长:12~16 分钟 标签:momentum、sgd、optimizer SEO 关键词:动量, Momentum, SGD, 优化器 元描述:系统讲清动量优化的更新过程与工程实践。 目标读者 想理解动量优化机制的入门读者 需要解决训练震荡与收敛慢问题的工程实践者 关注优化器调参的开发者 背景 / 动机 纯 SGD 在陡峭方向上容易震荡、在平缓方向上推进缓慢。 动量引入“速度”概念,让更新方向更稳定、收敛更快。 它是许多优化器(如 Adam)的核心组件之一。 核心概念 速度(Velocity):累计梯度形成的方向与幅度。 动量系数:控制历史梯度影响程度。 平滑更新:减少梯度噪声带来的震荡。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 动量可以理解为: 每一步不仅看当前梯度,还看过去的梯度方向。 像滚小球一样,惯性会让它更容易越过浅坑。 基础示例(1) 在狭长“谷地”里,纯 SGD 左右摆动,而动量能沿谷底快速前进。 基础示例(2) 在噪声梯度场景,动量能平均掉噪声,方向更稳定。 实践指南 / 步骤 选择 momentum(常见 0.9)。 如果震荡明显,适当提高动量或降低学习率。 观察训练/验证曲线,确认收敛速度。 可运行示例(最小 PyTorch 动量更新) import torch torch.manual_seed(42) w = torch.tensor([5.0], requires_grad=True) velocity = torch.zeros_like(w) lr = 0.1 mu = 0.9 for _ in range(5): loss = (w - 1.0).pow(2) loss.backward() with torch.no_grad(): velocity = mu * velocity + w.grad w -= lr * velocity w.grad.zero_() print(w.item()) 解释与原理 速度累积让更新方向“更平滑”。 在弯曲损失面上,动量减少横向摆动。 学习率与动量需要联合调参。 C — Concepts(核心思想) 方法类型 动量属于一阶优化增强策略,通过历史梯度平滑更新。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

优化器的了解:从 SGD 到 Adam 的工程取舍

副标题 / 摘要 优化器决定训练速度、稳定性与最终泛化。本文按 ACERS 框架对比 SGD、Momentum、Adam、AdamW 等主流优化器,并给出最小可运行示例与工程实践建议。 预计阅读时长:15~18 分钟 标签:optimizer、sgd、adam、adamw SEO 关键词:优化器, SGD, Adam, AdamW 元描述:对比主流优化器原理与工程场景,给出可运行示例。 目标读者 刚入门深度学习训练的读者 需要在速度与泛化之间权衡的工程实践者 想系统理解优化器选择的开发者 背景 / 动机 在训练大模型时,损失函数不是唯一关键,优化器同样决定成败。 同一模型下,不同优化器会带来完全不同的收敛曲线与最终效果。 理解优化器差异,是做出稳定工程方案的前提。 核心概念 梯度下降:沿损失函数梯度方向更新参数。 动量(Momentum):引入历史梯度方向,减少震荡。 自适应学习率:为不同参数分配不同步长。 权重衰减(Weight Decay):控制参数规模,提升泛化。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 SGD:每次更新都沿着当前梯度方向。 Momentum:带“惯性”的 SGD,加速收敛。 Adam:对每个参数自适应调整学习率。 AdamW:把权重衰减从 Adam 的梯度中解耦。 基础示例(1) SGD 在陡峭峡谷会来回震荡。 Adam 会自动缩小震荡方向的步长。 基础示例(2) Adam 收敛快但可能泛化弱。 SGD 收敛慢但往往更稳。 实践指南 / 步骤 快速验证模型可行性 → Adam/AdamW。 追求最终泛化性能 → SGD + 动量。 训练大模型时优先 AdamW。 用验证集曲线而非训练 loss 评估。 可运行示例(最小 PyTorch 对比) import torch import torch.nn as nn torch.manual_seed(42) x = torch.randn(256, 10) y = torch.randn(256, 1) model = nn.Linear(10, 1) loss_fn = nn.MSELoss() # SGD sgd = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9) for _ in range(5): pred = model(x) loss = loss_fn(pred, y) sgd.zero_grad() loss.backward() sgd.step() # AdamW adamw = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01) for _ in range(5): pred = model(x) loss = loss_fn(pred, y) adamw.zero_grad() loss.backward() adamw.step() print("done") 解释与原理 Adam 引入一阶与二阶动量,提升收敛速度。 AdamW 通过“解耦权重衰减”更稳定。 SGD 的优势在于更好的泛化表现。 C — Concepts(核心思想) 方法类型 优化器属于数值优化方法,核心目标是稳定、快速、可泛化地找到最优解。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

BN 与 Dropout:训练与推理时的关键区别

副标题 / 摘要 BatchNorm 在训练使用 batch 统计、推理使用滑动均值方差;Dropout 训练时随机失活、推理时关闭。本文用 ACERS 框架解释两者差异并给出最小 PyTorch 示例。 预计阅读时长:12~16 分钟 标签:batchnorm、dropout、training SEO 关键词:BatchNorm, Dropout, 训练, 推理 元描述:对比 BN 与 Dropout 在训练与推理阶段的行为与工程取舍。 目标读者 想系统理解 BN/Dropout 差异的入门读者 需要调试训练/推理不一致问题的工程实践者 关注模型稳定性与泛化的开发者 背景 / 动机 很多线上问题来自“训练正常、推理异常”。 BN 与 Dropout 在训练/推理阶段的行为不同,是常见根因。 理解它们的机制差异,能显著减少定位成本。 核心概念 BatchNorm:用 batch 统计归一化特征,并维护 running mean/var。 Dropout:训练时随机失活部分神经元以正则化。 Train/Eval 模式:控制 BN/Dropout 行为的关键开关。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 BN 训练时用当前 batch 的均值与方差;推理时用历史统计。 Dropout 训练时随机丢弃;推理时关闭、输出稳定。 基础示例(1) BN:小 batch 训练可能统计不稳定,推理偏移明显。 基础示例(2) Dropout:训练输出有噪声,推理输出确定。 实践指南 / 步骤 训练时使用 model.train()。 推理时使用 model.eval()。 如果 batch 很小,考虑替代 BN(LayerNorm/GroupNorm)。 可运行示例(最小 PyTorch 对比) import torch import torch.nn as nn torch.manual_seed(42) model = nn.Sequential( nn.Linear(4, 4), nn.BatchNorm1d(4), nn.Dropout(p=0.5), ) x = torch.randn(3, 4) model.train() train_out1 = model(x) train_out2 = model(x) model.eval() eval_out1 = model(x) eval_out2 = model(x) print(torch.allclose(train_out1, train_out2)) # False (Dropout) print(torch.allclose(eval_out1, eval_out2)) # True 解释与原理 BN 在训练中依赖 batch 统计,推理依赖 running 统计。 Dropout 在训练中丢弃神经元以提升泛化,推理关闭以稳定输出。 C — Concepts(核心思想) 方法类型 BN 属于归一化技术,Dropout 属于正则化技术。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

Transformer 中可以用 BatchNorm 吗?

副标题 / 摘要 Transformer 默认使用 LayerNorm,但在某些视觉模型中也能看到 BatchNorm。本文解释 BN 在 Transformer 中的可行性、限制与适用场景,并提供最小 PyTorch 示例。 预计阅读时长:14~18 分钟 标签:transformer、batchnorm、layernorm SEO 关键词:BatchNorm, Transformer, LayerNorm 元描述:分析 Transformer 中使用 BatchNorm 的利弊与工程建议。 目标读者 想理解归一化策略差异的入门读者 需要提升训练稳定性的工程实践者 从事 NLP/视觉 Transformer 研发的开发者 背景 / 动机 Transformer 结构中常用 LayerNorm,但很多工程师会问:能不能用 BN? BN 在 CNN 中非常有效,但在序列模型上常受 batch 维度影响。 理解其差异能帮助你在不同场景下做更合理的选择。 核心概念 BatchNorm(BN):按 batch 维度归一化。 LayerNorm(LN):按特征维度归一化。 统计依赖:BN 依赖 batch 统计,LN 不依赖。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 BN 会把“整批样本”的均值/方差作为归一化基准。 LN 只看单个样本内部特征,更稳定。 基础示例(1) 小 batch 训练时,BN 的均值/方差噪声大,容易不稳定。 基础示例(2) CV Transformer 大 batch 训练时,BN 有时能提供更快收敛。 实践指南 / 步骤 NLP/小 batch → LN 更稳。 CV/大 batch → 可尝试 BN。 先做对比实验,再决定归一化方案。 可运行示例(最小 PyTorch 对比) import torch import torch.nn as nn torch.manual_seed(42) x = torch.randn(4, 8, 16) # batch, seq, dim # LayerNorm:按特征维度 ln = nn.LayerNorm(16) out_ln = ln(x) # BatchNorm:需要把特征维度转为 channel bn = nn.BatchNorm1d(16) out_bn = bn(x.transpose(1, 2)).transpose(1, 2) print(out_ln.mean(dim=-1).shape) print(out_bn.mean(dim=-1).shape) 解释与原理 BN 依赖 batch 统计,推理时使用滑动均值/方差。 LN 不依赖 batch,训练/推理一致。 Transformer 多用 LN 是为了适配小 batch 与序列任务。 C — Concepts(核心思想) 方法类型 BN/LN 都属于归一化方法,用于稳定训练与加速收敛。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

残差连接的作用:为什么深度网络离不开它

副标题 / 摘要 残差连接通过“旁路”让梯度更容易传播,是深层网络可训练的关键。本文从原理到工程实践梳理残差的作用,并给出最小 PyTorch 示例。 预计阅读时长:12~16 分钟 标签:residual、skip-connection、transformer SEO 关键词:残差连接, ResNet, Transformer 元描述:系统解释残差连接为何能提升深度网络训练稳定性,并给出可运行示例。 目标读者 想理解残差连接价值的入门读者 在深层网络训练中遇到不稳定的工程实践者 关注 Transformer/ResNet 结构设计的开发者 背景 / 动机 深层网络容易梯度消失或爆炸,训练难以收敛。 残差连接通过“恒等映射”提供一条更短的梯度通道,使深层网络可训练。 它也是 ResNet 与 Transformer 的基础结构之一。 核心概念 残差连接(Skip/Residual):输出 = 输入 + 子层变换。 恒等映射:让网络学习“增量”而非全部映射。 梯度流动:减少梯度衰减,提高可训练性。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 残差连接的思路是: 如果一个深层网络难以直接学习映射 H(x),那就让它学习 F(x) = H(x) - x。 这样输出变成 x + F(x),训练更容易。 基础示例(1) 深层 MLP 加残差后 loss 更稳定、收敛更快。 基础示例(2) Transformer 每个子层都带残差,保证梯度可传播。 实践指南 / 步骤 在深层块中加入 x + f(x) 结构。 若维度不一致,用线性投影对齐。 配合 LayerNorm/RMSNorm 提升稳定性。 可运行示例(最小残差对比) import torch import torch.nn as nn torch.manual_seed(42) class PlainMLP(nn.Module): def __init__(self, dim=64, depth=6): super().__init__() layers = [] for _ in range(depth): layers += [nn.Linear(dim, dim), nn.ReLU()] self.net = nn.Sequential(*layers) def forward(self, x): return self.net(x) class ResMLP(nn.Module): def __init__(self, dim=64, depth=6): super().__init__() self.blocks = nn.ModuleList([ nn.Sequential(nn.Linear(dim, dim), nn.ReLU()) for _ in range(depth) ]) def forward(self, x): for block in self.blocks: x = x + block(x) return x x = torch.randn(8, 64) plain = PlainMLP() res = ResMLP() print(plain(x).shape, res(x).shape) 解释与原理 残差提供恒等路径,使梯度能绕过非线性层。 深层网络更容易学习“增量”,降低优化难度。 在 Transformer 中,残差 + 归一化是稳定训练核心。 C — Concepts(核心思想) 方法类型 残差连接属于架构层面的优化技巧,目的是改善训练稳定性与可扩展性。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

SGD vs Adam:优化器原理与工程取舍

副标题 / 摘要 SGD 简洁稳定,Adam 自适应学习率收敛更快。本文用 ACERS 框架对比两者原理与工程取舍,并给出最小 PyTorch 示例。 预计阅读时长:14~18 分钟 标签:sgd、adam、optimizer SEO 关键词:SGD, Adam, 优化器, 动量 元描述:对比 SGD 与 Adam 的训练特性与使用场景。 目标读者 想理解优化器差异的入门读者 需要做训练稳定性与收敛速度取舍的工程实践者 想掌握常见调参策略的开发者 背景 / 动机 优化器决定训练速度与最终性能。 SGD 以稳定著称,Adam 以快速收敛著称。 理解两者差异有助于在不同任务中做更合理的选择。 核心概念 SGD:基于当前梯度更新参数。 Momentum:引入历史梯度方向,加速收敛。 Adam:结合动量与 RMSProp,自适应学习率。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 SGD:每步朝“当前梯度方向”走。 Adam:用历史梯度估计方向,同时对每个参数自适应调节步长。 基础示例(1) SGD 在噪声大时会“抖动”,收敛慢但稳定。 基础示例(2) Adam 在稀疏梯度场景(NLP)通常收敛更快。 实践指南 / 步骤 快速验证效果 → Adam。 追求最终泛化 → SGD + 动量。 对比验证集曲线,而非只看训练 loss。 可运行示例(最小 PyTorch 对比) import torch import torch.nn as nn torch.manual_seed(42) x = torch.randn(128, 10) y = torch.randn(128, 1) model = nn.Linear(10, 1) loss_fn = nn.MSELoss() sgd = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9) for _ in range(5): pred = model(x) loss = loss_fn(pred, y) sgd.zero_grad() loss.backward() sgd.step() adam = torch.optim.Adam(model.parameters(), lr=1e-2) for _ in range(5): pred = model(x) loss = loss_fn(pred, y) adam.zero_grad() loss.backward() adam.step() print("done") 解释与原理 SGD 只依赖当前梯度,步长固定。 Adam 用一阶/二阶动量估计,使得学习率对每个参数自适应。 C — Concepts(核心思想) 方法类型 SGD 是一阶优化基线,Adam 是自适应学习率优化。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

CLIP 系列(2/3):PyTorch 完整可复现实战——从数据到训练闭环

副标题 / 摘要 这篇文章给出一个“最小但完整”的 CLIP 训练闭环:CIFAR-10 图像 + 文本提示,配套可直接运行的 PyTorch 脚本,确保你可以本地复现训练与零样本分类。 预计阅读时长:20~25 分钟 标签:clip、pytorch、reproducible、cifar10 SEO 关键词:CLIP, PyTorch, 可复现, CIFAR10, 对比学习 元描述:从数据准备到训练与评估,给出完整可复现的 CLIP PyTorch 实战脚本。 系列导航 (1/3)原理与对比学习公式 (2/3)PyTorch 完整可复现实战(本文) (3/3)工程化与优化 目标读者 想跑通 CLIP 训练闭环的初学者 需要可复现实验模板的工程实践者 希望基于 PyTorch 做多模态原型验证的读者 背景 / 动机 CLIP 的训练流程看起来简单,但“可复现”很难: 缺数据、缺脚本、缺评估,导致很多实验停在“理论上懂了”。 本篇用一个小数据集闭环复现,优先保证你能在本地跑起来。 核心概念 可复现性:固定随机种子、控制数据划分与预处理。 弱标注文本:用类名构造文本提示,模拟图文对齐。 对比损失:双向交叉熵 + 温度参数。 零样本评估:用文本提示作为“类别描述”进行分类。 A — Algorithm(题目与算法) 训练闭环的核心流程 为每张图像生成文本提示(如 a photo of a cat)。 图像与文本分别编码成向量并归一化。 计算相似度矩阵并用对比损失训练。 推理时用“文本提示集合”做零样本分类。 基础示例(1) 图像:一只猫 文本提示集合:cat, dog, car 目标:相似度最高的提示即为预测类别 基础示例(2) 同一 batch 内,对角线是“正确图文对” 训练目标:对角线最大化,非对角线最小化 实践指南 / 步骤 创建环境并安装依赖: python -m venv .venv source .venv/bin/activate pip install torch torchvision tqdm 把下面脚本保存为 clip_cifar10.py。 运行训练(推荐 GPU): python clip_cifar10.py --epochs 10 --batch-size 256 --device cuda 观察输出:loss 逐步下降,零样本准确率逐步上升。 可运行示例(完整 PyTorch 脚本) import argparse import math import random import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset, DataLoader from torchvision import datasets, transforms, models from tqdm import tqdm CIFAR10_CLASSES = [ "airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck" ] def set_seed(seed: int) -> None: random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False class SimpleTokenizer: def __init__(self, texts): self.pad_token = "<pad>" self.unk_token = "<unk>" self.bos_token = "<bos>" self.eos_token = "<eos>" vocab = { self.pad_token: 0, self.unk_token: 1, self.bos_token: 2, self.eos_token: 3, } for text in texts: for token in text.lower().split(): if token not in vocab: vocab[token] = len(vocab) self.stoi = vocab self.itos = {i: t for t, i in vocab.items()} self.pad_id = self.stoi[self.pad_token] self.unk_id = self.stoi[self.unk_token] self.bos_id = self.stoi[self.bos_token] self.eos_id = self.stoi[self.eos_token] def encode(self, text, max_len=16): tokens = text.lower().split() ids = [self.bos_id] ids.extend(self.stoi.get(t, self.unk_id) for t in tokens) ids.append(self.eos_id) if len(ids) > max_len: ids = ids[:max_len] ids[-1] = self.eos_id return ids def pad_tokens(token_lists, pad_id): max_len = max(len(t) for t in token_lists) tokens = torch.full((len(token_lists), max_len), pad_id, dtype=torch.long) attn = torch.zeros((len(token_lists), max_len), dtype=torch.bool) for i, ids in enumerate(token_lists): tokens[i, : len(ids)] = torch.tensor(ids, dtype=torch.long) attn[i, : len(ids)] = True return tokens, attn class CIFAR10Text(Dataset): def __init__(self, root, train, transform, tokenizer, max_len=16): self.ds = datasets.CIFAR10(root=root, train=train, download=True, transform=transform) self.prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.ds) def __getitem__(self, idx): image, label = self.ds[idx] text = self.prompts[label] token_ids = self.tokenizer.encode(text, max_len=self.max_len) return image, token_ids, label def collate_fn(batch, pad_id): images, token_lists, labels = zip(*batch) images = torch.stack(images) tokens, attn = pad_tokens(token_lists, pad_id) labels = torch.tensor(labels, dtype=torch.long) return images, tokens, attn, labels class ImageEncoder(nn.Module): def __init__(self, embed_dim): super().__init__() self.backbone = models.resnet18(weights=None) self.backbone.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.backbone.maxpool = nn.Identity() self.backbone.fc = nn.Linear(self.backbone.fc.in_features, embed_dim) def forward(self, x): x = self.backbone(x) return F.normalize(x, dim=-1) class TextEncoder(nn.Module): def __init__(self, vocab_size, embed_dim, width=256, layers=2, heads=4, max_len=16): super().__init__() self.token = nn.Embedding(vocab_size, width) self.pos = nn.Embedding(max_len, width) encoder_layer = nn.TransformerEncoderLayer( d_model=width, nhead=heads, dim_feedforward=width * 4, dropout=0.1, batch_first=True, ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=layers) self.proj = nn.Linear(width, embed_dim) def forward(self, token_ids, attn_mask): bsz, seq_len = token_ids.shape pos_ids = torch.arange(seq_len, device=token_ids.device).unsqueeze(0).expand(bsz, -1) x = self.token(token_ids) + self.pos(pos_ids) x = self.encoder(x, src_key_padding_mask=~attn_mask) attn = attn_mask.unsqueeze(-1) x = (x * attn).sum(dim=1) / attn.sum(dim=1).clamp(min=1) x = self.proj(x) return F.normalize(x, dim=-1) class CLIPModel(nn.Module): def __init__(self, vocab_size, embed_dim=256, max_len=16): super().__init__() self.image_encoder = ImageEncoder(embed_dim) self.text_encoder = TextEncoder(vocab_size, embed_dim, max_len=max_len) self.logit_scale = nn.Parameter(torch.tensor(math.log(1 / 0.07))) def forward(self, images, token_ids, attn_mask): image_features = self.image_encoder(images) text_features = self.text_encoder(token_ids, attn_mask) logit_scale = self.logit_scale.exp().clamp(max=100) logits = logit_scale * image_features @ text_features.T return logits def clip_loss(logits): labels = torch.arange(logits.size(0), device=logits.device) loss_i = F.cross_entropy(logits, labels) loss_t = F.cross_entropy(logits.T, labels) return (loss_i + loss_t) / 2 @torch.no_grad() def zero_shot_accuracy(model, loader, tokenizer, device, max_len=16): model.eval() prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] token_lists = [tokenizer.encode(p, max_len=max_len) for p in prompts] tokens, attn = pad_tokens(token_lists, tokenizer.pad_id) tokens = tokens.to(device) attn = attn.to(device) text_features = model.text_encoder(tokens, attn) correct = 0 total = 0 for images, _, _, labels in loader: images = images.to(device) image_features = model.image_encoder(images) logits = image_features @ text_features.T preds = logits.argmax(dim=1).cpu() correct += (preds == labels).sum().item() total += labels.size(0) return correct / max(total, 1) def main(): parser = argparse.ArgumentParser() parser.add_argument("--epochs", type=int, default=10) parser.add_argument("--batch-size", type=int, default=256) parser.add_argument("--embed-dim", type=int, default=256) parser.add_argument("--max-len", type=int, default=16) parser.add_argument("--lr", type=float, default=3e-4) parser.add_argument("--seed", type=int, default=42) parser.add_argument("--num-workers", type=int, default=2) parser.add_argument("--device", type=str, default="cuda") parser.add_argument("--data-root", type=str, default="./data") args = parser.parse_args() device = args.device if torch.cuda.is_available() and args.device == "cuda" else "cpu" set_seed(args.seed) prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] tokenizer = SimpleTokenizer(prompts) train_tf = transforms.Compose( [ transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ] ) test_tf = transforms.Compose( [ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ] ) train_ds = CIFAR10Text(args.data_root, True, train_tf, tokenizer, args.max_len) test_ds = CIFAR10Text(args.data_root, False, test_tf, tokenizer, args.max_len) train_loader = DataLoader( train_ds, batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=lambda b: collate_fn(b, tokenizer.pad_id), ) test_loader = DataLoader( test_ds, batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers, collate_fn=lambda b: collate_fn(b, tokenizer.pad_id), ) model = CLIPModel(len(tokenizer.stoi), embed_dim=args.embed_dim, max_len=args.max_len).to(device) optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs) for epoch in range(1, args.epochs + 1): model.train() total_loss = 0.0 for images, tokens, attn, _ in tqdm(train_loader, desc=f"Epoch {epoch}"): images = images.to(device) tokens = tokens.to(device) attn = attn.to(device) logits = model(images, tokens, attn) loss = clip_loss(logits) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) scheduler.step() avg_loss = total_loss / len(train_ds) acc = zero_shot_accuracy(model, test_loader, tokenizer, device, args.max_len) print(f"Epoch {epoch}: loss={avg_loss:.4f}, zero-shot acc={acc:.4f}") if __name__ == "__main__": main() C — Concepts(核心思想) 方法类型 CLIP 属于对比学习 + 多模态表示学习范式,采用图文双塔编码器对齐语义空间。 ...

2026年1月24日 · 5 分钟 · map[name:Jeanphilo]