写这期对比学习Contrastive Learning与自监督表示学习其实有点“蓄谋已久”。之前二十多期PyTorch实战一直在跟有监督任务打交道图像分类、目标检测、语义分割套路都很成熟模型扔上去标签给到位loss一降指标就涨。但实际做项目就会碰到一个尴尬问题——标注数据永远不够人工标一万张图可能就要一周更别提医疗影像、遥感图像这种需要专业知识的场景。对比学习恰恰是解决这个问题的思路之一不用标签让模型自己从数据里学特征。这篇文章我会用PyTorch从零搭建一个可复跑的对比学习训练流程讲清楚SimCLR这类框架的核心设计以及我在实际调参中踩过的坑和总结出的经验。适合已经会用ResNet做分类、想往自监督方向迈一步的读者。1. 自监督表示学习到底在学什么1.1 从“要标签”到“让数据自己说话”传统监督学习的训练信号来自标注给模型一张猫的图片告诉它“这是猫”。模型通过最小化预测与标签之间的交叉熵来调整参数本质是在拟合一个从图像到语义类别的映射。这种方式的优点是目标明确、收敛稳定缺点也非常直接标注成本高、类别体系固定、学到的东西往往高度任务化。自监督表示学习的思路完全不同——不依赖人工标签而是从数据本身构造监督信号。常见做法包括预测图像旋转角度、补全被遮挡的像素块MAE、预测上下文位置关系以及本文重点讲的对比学习。对比学习的核心逻辑是让模型学会“什么样的样本在语义上是相似的什么样的样本不相似”。一旦模型学会了这种判别能力它产出的特征向量就携带了丰富的语义信息之后只需要用少量标注数据微调就能适配下游任务。这里有个很关键的认知转变对比学习追求的不是“把图片分类正确”而是“把图片映射到特征空间后同类样本靠得近、异类样本离得远”。这个特征空间的质量直接决定了后续迁移效果。我见过不少新手把对比学习当成一个分类任务来调总觉得loss降到很低才满意其实更该关注的是特征分布是否合理、下游任务精度是否提升。1.2 一句话理解对比学习的正负样本机制对比学习的基本单元是“三元组”——锚点样本anchor、正样本positive、负样本negative。锚点和正样本是同一张图片经过不同数据增强得到的两个视图语义内容相同但外观不同负样本则是batch里其他图片的增强视图语义内容与锚点不同。训练目标用一句话概括拉近锚点与正样本在特征空间的距离同时推远锚点与所有负样本的距离。用公式表达就是InfoNCE损失我后面会给出具体的PyTorch实现。这个机制看起来简单但设计空间非常大正样本怎么构造、负样本从哪里来、特征空间用什么度量、损失函数的温度系数取多少每一个决策都会显著影响最终效果。打个生活化的比方你想让一个孩子认识“苹果”这个概念不是直接告诉他“这是苹果”而是给他看红苹果、绿苹果、不同光线下的苹果照片同时让他对比橘子、香蕉让他自己归纳出“苹果之所以是苹果”的视觉共性。对比学习做的事情就是这个只不过把“孩子”换成了神经网络把“照片”换成了增强视图。2. 三个主流框架的设计思路对比2.1 SimCLR靠大batch size硬扛负样本数量SimCLR是2020年Google提出的框架也是我第一个跑通的对比学习模型。它的结构非常直接一个编码器通常是ResNet把增强视图映射为特征向量再接一个两层的MLP投影头得到用于计算损失的嵌入向量。同一个batch里的所有样本互为负样本所以batch size越大负样本越丰富效果越好。SimCLR有三个关键设计值得留意。第一是数据增强组合很讲究包含随机裁剪、颜色抖动、灰度化、高斯模糊论文通过消融实验证明这些操作缺一不可。第二是投影头的存在非常重要——在投影后的低维空间计算损失比直接在特征空间计算效果好得多原因是投影头会过滤掉与语义无关的细节信息。第三是温度系数默认取0.07这个值控制了对难负样本的关注程度。我用SimCLR在CIFAR-10上做过预训练batch size设为256训练200个epoch线性评估精度大约能到82%左右。这个数字不算惊艳但要知道这是完全无监督的预训练只用了4万张无标签图片。这里也顺带提醒一句SimCLR对显存的消耗非常大因为每个样本要生成两个视图等于把batch翻倍了一个batch为256时实际前向传播的是512张图。2.2 MoCo用动量编码器和队列打破batch size限制SimCLR的一个实际痛点是小显存环境根本塞不下大batch。MoCoMomentum Contrast从工程角度解决了这个问题。它维护一个很大的负样本队列队列里存的是历史batch产生的特征当前batch只需要计算锚点和正样本的梯度负样本直接从队列里取。这样即使batch size只有64负样本数量也能轻松达到4096甚至更大。MoCo的另一个巧妙之处是动量编码器。负样本队列里的特征如果由同一个编码器产生会随着训练导致特征分布漂移如果完全冻结旧的编码器特征又太旧、与当前模型不匹配。MoCo的做法是让编码器的参数以很小的动量系数通常0.999缓慢更新既保持特征一致性又不至于严重滞后。这个思想很像我们在做滑动平均来平滑指标只不过作用在模型参数上。从实际效果看MoCo在ImageNet线性评估上略优于SimCLR而且对batch size不敏感更适合单卡训练。我在一张2080Ti上复现过MoCo v2batch为128、队列长度为4096训练200个epoch的线性评估精度能到60%左右ImageNet子集这个复现成本对个人开发者很友好。2.3 BYOL与SimSiam没有负样本也能学你可能想不到对比学习界后来还出现了一个“叛逆”的分支——不用负样本也能学到好特征。BYOL和SimSiam就是代表。它们的结构是学生网络和教师网络或称为在线网络和目标网络学生网络从增强视图A预测特征目标网络从增强视图B输出目标两者做均方误差损失。为了防止模型崩溃所有样本输出同一个向量它们引入了stop-gradient操作或者额外的预测头。为什么会崩溃因为如果没有负样本提供“排斥力”模型的最优解就是把所有输入映射到同一个点这样loss直接降为0但这显然不是我们想要的。BYOL用动量更新的目标网络来防止崩溃SimSiam则只靠stop-gradient就足够了这是2021年一个很有意思的发现。我个人建议初学者不要把BYOL作为第一个对比学习项目因为它的崩溃现象比较隐蔽一旦发生训练日志里的loss看起来也在下降但特征分布已经退化只有等下游评估时才发现问题严重。想先用最经典、最不容易出问题的框架入门SimCLR是最稳妥的选择。2.4 三个框架怎么选框架负样本来源batch size依赖核心组件显存压力推荐场景SimCLR当前batch内高越大越好增强投影头高有多个GPU或小数据集MoCo历史特征队列低64即可动量编码器队列中单卡、显存有限BYOL/SimSiam无负样本低目标网络stop-grad低特征稳定性要求高回到项目落地的话题如果你手头数据量不大几千到几万张用SimCLR完全够用如果数据量大且只有单卡MoCo性价比最高如果下游任务是检测分割这种对空间细节敏感的场景可以考虑BYOL学到的特征是否更平滑。没有绝对的好坏只有适合与不适合。3. 手写PyTorch实现数据增强、投影头与InfoNCE3.1 数据增强对比学习的灵魂所在我一直认为对比学习里最值得花时间调的不是网络结构而是数据增强策略。同一张原图经过两次不同的增强生成的两个视图如果“差别太小”模型很容易走捷径学不到本质特征如果“差别太大”语义信息可能被破坏正样本对变成了“伪负样本”。所以增强策略需要维持一个微妙的平衡。以SimCLR官方在ImageNet上用的增强组合为例包含以下操作随机裁剪并缩放到224x224裁剪面积比例范围是0.08到1.0随机水平翻转颜色抖动亮度、对比度、饱和度、色相灰度化高斯模糊用PyTorch的torchvision.transforms实现如下import torchvision.transforms as T class SimCLRTrainTransform: def __init__(self, size224): self.transform T.Compose([ T.RandomResizedCrop(size, scale(0.08, 1.0)), T.RandomHorizontalFlip(), T.RandomApply([T.ColorJitter(0.8, 0.8, 0.8, 0.2)], p0.8), T.RandomGrayscale(p0.2), T.RandomApply([T.GaussianBlur(kernel_size23)], p0.5), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def __call__(self, x): return self.transform(x), self.transform(x)注意一个细节高斯模糊的kernel_size通常建议取输入尺寸的1/10左右224x224对应23。如果图片本身分辨率不高比如CIFAR-10的32x32高斯模糊反而容易把图片糊成色块需要适当调小kernel或者干脆去掉。在CIFAR-10这类小图上我会把随机裁剪的scale改为(0.2, 1.0)因为32x32的图本来信息量就少裁剪面积太小会导致视图之间几乎没有重叠区域正样本对失去意义。这个改动看似不起眼但对最终精度影响很大。3.2 两段式网络编码器加投影头网络结构分为两部分编码器负责提取特征表示投影头负责把特征映射到对比学习使用的低维空间。编码器可以用任何分类骨干网络常见选择是ResNet-18、ResNet-50投影头则是两层MLP中间夹一个ReLU激活。为什么要加投影头这是SimCLR论文里明确验证过的结论去掉投影头、直接在ResNet的输出特征2048维上计算InfoNCE损失线性评估精度会下降超过10个百分点。原因在于高维特征里包含了太多与“实例区分”无关的信息投影头相当于一个信息瓶颈强制模型保留最核心的语义特征。import torch import torch.nn as nn import torchvision.models as models class SimCLRNetwork(nn.Module): def __init__(self, base_encoderresnet18, feat_dim128): super().__init__() # 去掉ResNet自带的全连接分类层 self.encoder models.__dict__[base_encoder](weightsNone) self.encoder.fc nn.Identity() # 获取编码器输出维度 if base_encoder resnet18: enc_dim 512 elif base_encoder resnet50: enc_dim 2048 else: raise ValueError(当前示例仅支持 resnet18 或 resnet50) # 两层MLP投影头 self.projection_head nn.Sequential( nn.Linear(enc_dim, enc_dim, biasFalse), nn.BatchNorm1d(enc_dim), nn.ReLU(inplaceTrue), nn.Linear(enc_dim, feat_dim, biasTrue) ) def forward(self, x): feat self.encoder(x) # [N, D] emb self.projection_head(feat) # [N, 128] return emb # 快速验证输出维度 net SimCLRNetwork(resnet18, feat_dim128) dummy torch.randn(4, 3, 224, 224) out net(dummy) print(out.shape) # torch.Size([4, 128])投影头的第一层加了BatchNorm这个细节我在跟别人交流时发现很多人会忽略。SimCLR原论文里确实用了BN因为BN可以平滑训练、提高稳定性。但也要注意如果batch size太小BN的统计量估计不准反而成为包袱这种情况下可以考虑去掉第一层BN。3.3 InfoNCE损失对比学习的核心公式InfoNCE损失可以理解为在N个样本中做“N-way分类”——给定锚点样本的嵌入模型需要从包含正样本在内的N个候选样本中挑出那个正样本。数学表达式为L -log( exp(sim(z_i, z_j)/τ) / Σ_{k1}^{N} exp(sim(z_i, z_k)/τ) )其中sim表示余弦相似度τ是温度系数。分子对应正样本对的相似度分母则是锚点与所有其他样本相似度的总和。这个形式跟softmax分类损失非常像只是这里的“类别”是每个样本自身的编号。温度系数τ值得多说几句。τ越小损失对相似度高的负样本惩罚越重模型越倾向于“抠细节”来区分难负样本τ越大分布越平滑模型对所有负样本一视同仁。SimCLR的默认值是0.07但我实际使用时发现这个值对数据集很敏感在CIFAR-10上0.1表现更好在ImageNet上0.05可能更合适。建议以0.1、0.07、0.05做一次网格搜索代价不高但收益明显。下面是我整理的PyTorch实现import torch.nn.functional as F def info_nce_loss(embeddings, batch_size, temperature0.07): embeddings: [2*N, D]同一batch内每张图有两个增强视图 batch_size: N原始样本数量不包含增强 temperature: 温度系数 # 余弦相似度矩阵 [2*N, 2*N] cos_sim F.cosine_similarity(embeddings.unsqueeze(1), embeddings.unsqueeze(0), dim2) # 构造掩码将对角线位置的相似度设为极小值 n 2 * batch_size mask torch.eye(n, dtypetorch.bool, deviceembeddings.device) cos_sim cos_sim.masked_fill(mask, -1e9) # 找到每个样本对应的正样本索引 # 排列规则第 i 个样本的正样本是 ibatch_size当ibatch_size或 i-batch_size当ibatch_size positive_idx torch.cat([ torch.arange(batch_size, 2 * batch_size), torch.arange(0, batch_size) ]).to(embeddings.device) # 取出正样本对的相似度 positive_sim cos_sim[torch.arange(n), positive_idx] # [2*N] # InfoNCE 损失-log(exp(pos/t) / sum(exp(all/t))) logits cos_sim / temperature # 将正样本位置也暴露给softmax但softmax会同时考虑所有负样本 # 更标准实现从分母中排除正样本使用 logsumexp 或直接统计 # 这里采用标准实现分母包含所有样本正样本的exp值也参与 log_probs -F.log_softmax(logits, dim1) loss log_probs[torch.arange(n), positive_idx].mean() return loss上面的实现有个细节要注意标准InfoNCE的分母应当包含负样本和正样本也就是对全部N个候选做softmax。PyTorch的log_softmax默认对整个维度归一化正样本的贡献也包含在分母里这与原始公式一致。计算梯度时模型会同时优化“提高正样本相似度”和“降低所有负样本相似度”两个方向。如果你觉得log_softmax的方式不够直观也可以用这种更“手写”的写法def info_nce_loss_v2(embeddings, batch_size, temperature0.07): n 2 * batch_size cos_sim F.cosine_similarity(embeddings.unsqueeze(1), embeddings.unsqueeze(0), dim2) positive_idx torch.cat([ torch.arange(batch_size, 2 * batch_size), torch.arange(0, batch_size) ]).to(embeddings.device) positive_sim cos_sim[torch.arange(n), positive_idx] mask torch.eye(n, dtypetorch.bool, deviceembeddings.device) neg_sim cos_sim.masked_fill(mask, -1e9) # 分母 所有负样本的exp总和 正样本的exp neg_exp_sum torch.exp(neg_sim / temperature).sum(dim1) pos_exp torch.exp(positive_sim / temperature) loss -torch.log(pos_exp / (neg_exp_sum pos_exp)).mean() return loss实际训练时我推荐用log_softmax版本数值稳定性更好而且代码简洁。手写版本方便理解公式但如果负样本数量很大比如几千指数运算容易溢出需要额外做数值保护。训练日志里看到loss变成NaN多半是这里出了问题。3.4 完整的SimCLR单batch训练流程把增强、编码器、投影头、InfoNCE串起来一个batch的训练流程如下def train_one_epoch(model, loader, optimizer, device, temperature0.07): model.train() total_loss 0.0 for images, _ in loader: images images.to(device) # [N, C, H, W] batch_size images.size(0) # 对同一batch生成两组增强视图并拼接 # 实际操作中更高效的做法在DataLoader里定义两次transform x1 aug_transform(images) x2 aug_transform(images) x torch.cat([x1, x2], dim0) # [2*N, C, H, W] embeddings model(x) # [2*N, D] loss info_nce_loss(embeddings, batch_size, temperature) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * batch_size return total_loss / len(loader.dataset)这里有个工程优化点如果aug_transform是在__call__里返回两个增强视图那么DataLoader的每个样本天然就带着两个视图不需要在训练循环里额外调用两次增强但要注意这样会一次性把两个视图都加载进来内存占用翻倍。对于大数据集我习惯让DataLoader只产出一张增强图然后在循环内用另一个transform生成第二张图。实际训练中我通常会把原始图片和增强后的图片保存在不同的目录下预处理时打印出来肉眼检查几组确认增强强度没有大到“认不出原图”。这一步不要省因为增强策略一旦不合适训练半天才发现效果差浪费时间。4. 预训练完成后的下游任务评估4.1 线性评估衡量特征质量的标准做法自监督预训练的效果不能只看训练loss因为loss降到你满意的程度并不代表特征好用。行业标准做法是“线性评估”把编码器的参数冻结只在特征向量后面接一个线性分类器在带标签的数据集上训练这个分类头用分类精度衡量特征质量。线性评估之所以是“金标准”是因为它排除了微调带来的干扰。如果特征真的学到了通用语义那么仅凭一个线性层就能把类别区分开如果特征质量差线性层再怎么调也无法提升精度。这与从头训练一个分类模型的对比能直观体现预训练的价值。def evaluate_linear(model, train_loader, test_loader, device, num_classes10, epochs30): model.eval() # 冻结所有参数 for param in model.parameters(): param.requires_grad False # 线性分类头 linear_head nn.Linear(512, num_classes).to(device) optimizer torch.optim.Adam(linear_head.parameters(), lr1e-3) for epoch in range(epochs): linear_head.train() for images, labels in train_loader: images, labels images.to(device), labels.to(device) with torch.no_grad(): features model.encoder(images) # 只取编码器输出 logits linear_head(features) loss F.cross_entropy(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() # 评估 acc 0.0 total 0 linear_head.eval() with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) features model.encoder(images) logits linear_head(features) pred logits.argmax(dim1) acc (pred labels).sum().item() total labels.size(0) print(fEpoch {epoch1}: Linear Acc {acc/total:.4f})注意这里model.encoder的输出维度是512ResNet-18如果你换用ResNet-50需要把linear_head的输入维度改成2048。此外冻结编码器后计算特征时用torch.no_grad()能省不少显存和计算时间如果内存充足你也可以提前把所有训练集特征提取出来存成矩阵之后训练线性层就不需要再过编码器了速度会快很多。4.2 kNN评估不需要训练的分类精度除了线性评估k近邻分类也是一种常用的特征质量衡量方法。做法很简单用训练集每张图的特征构建特征库测试时把测试图特征与特征库中所有特征计算余弦相似度取相似度最高的k个样本进行投票。它不需要任何训练计算效率高适合在预训练过程中快速验证模型有没有崩溃。def knn_evaluate(model, train_loader, test_loader, device, k20): model.eval() # 提取训练集特征 train_features, train_labels [], [] with torch.no_grad(): for images, labels in train_loader: images images.to(device) feat model.encoder(images) # [B, D] train_features.append(feat.cpu()) train_labels.append(labels) train_features torch.cat(train_features) train_labels torch.cat(train_labels) train_features F.normalize(train_features, dim1) acc 0.0 total 0 with torch.no_grad(): for images, labels in test_loader: images images.to(device) feat F.normalize(model.encoder(images), dim1).cpu() # 余弦相似度矩阵 [B_test, B_train] sim_matrix torch.mm(feat, train_features.t()) topk_vals, topk_idx sim_matrix.topk(k, dim1) topk_labels train_labels[topk_idx] # [B_test, k] # 投票 pred topk_labels.mode(dim1).values acc (pred labels).sum().item() total labels.size(0) return acc / totalkNN评估的一个优势是不需要为每个新数据集重新训练分类头调好k值后可以直接套用。我在实验时通常把k设为20因为对比学习特征在高维空间比较稀疏k太小容易受单个噪声样本影响k太大则会把不同类别的边界磨平。你可以在验证集上扫一下k值从5到50每隔5试一次选择一个峰值位置。4.3 迁移到检测、分割任务的注意事项如果下游任务是目标检测或语义分割情况会复杂一些。检测分割模型通常需要像素级别的空间信息而我们预训练时用全局平均池化把特征压缩成了一维向量这本身就会损失空间结构。所以迁移到检测分割时一般不用投影头而是直接用编码器的中间层特征图作为骨干网络初始化。具体操作是先完成自监督预训练拿到编码器的权重文件然后加载到Faster R-CNN、Mask R-CNN或者FCN的backbone部分继续在带标注的数据上微调整个模型。PyTorch官方检测模型用torchvision.models.detection加载时可以传入backbone参数来自定义预训练权重。这里有个常见误区有人直接把预训练好的ResNet权重赋给检测模型的backbone却忘了检测模型的backbone通常包含layer4甚至更高分辨率的特征输出如果预训练时的编码器结构不完全一致权重字典会不匹配。我用torch.load加载权重后总会先打印一下state_dict的键名对比一遍再赋值省得调试半天发现加载的是空壳。5. 常见问题排查与调参经验5.1 训练loss不降或者直接变NaNloss不降先检查数据增强是否存在问题。我之前在自定义数据集上训练时有个类别的图片颜色非常单一增强后高斯模糊把纹理全抹掉了导致正样本对太困难loss一直卡住不降。解决办法是降低颜色抖动强度、减小模糊概率。loss变成NaN通常有三种原因学习率过大、数值溢出、BN的batch太小。对比学习模型一般建议初始学习率在0.03左右batch为256时再配合余弦退火如果batch较小学习率要相应调低比如batch为64时学习率设为0.01。InfoNCE实现里温度系数如果太小比如0.01以下指数项可能出现极大值也会导致NaN。排查时建议先把损失函数单独拿出来用一小批随机特征做单元测试确认前向计算不会溢出然后再减小学习率看loss是否恢复正常。这种分而治之的排查思路在任何深度学习项目里都适用。5.2 显存不够怎么办SimCLR在显存方面的开销主要来自“每个样本生成两个视图”和“cosine similarity矩阵的计算”。如果你只有一张8G显存的显卡batch size设到256基本会爆显存。有几个替代方案降低batch size并配合梯度累积梯度累积模拟大batch但负样本数量仍然少了换用MoCo框架负样本来自队列对显存友好得多减少投影头维度比如从128降到64能省一点显存但效果可能略降使用混合精度训练torch.cuda.amp显存占用直接减半混合精度训练在对比学习里尤其值得推荐因为对比学习对计算精度不那么敏感但显存节省非常明显。我在2080Ti上用混合精度训练MoCobatch从128提到192几乎免费获得了一部分负样本增益。5.3 特征崩溃loss还在降但特征已经废了这是自监督学习最隐蔽的坑。loss还在下降但可视化特征分布后发现所有样本聚集在同一个点附近没有区分度。导致崩溃的常见原因有投影头太强把特征压缩成一团、负样本太少排斥力不足、增强强度过弱正样本对太好区分模型走捷径。检测崩溃最有效的手段就是前面提到的kNN评估因为它不需要训练分类头每几个epoch跑一次如果kNN精度掉到接近随机水平说明特征已经退化。否则等训练完才发现问题几百个epoch的时间就白费了。我自己遇到过一次典型的崩溃在自定义数据集上训练时把投影头的输出维度从128调到了16想着能加快训练结果跑了50个epoch后kNN精度从60%直接掉到12%。原因是16维空间太小不同样本的特征被压缩到了一起模型无法有效区分。后来把维度调回128问题消失。5.4 关于温度系数的调参心得温度系数是InfoNCE里最敏感的超参数。它的作用相当于调节损失对难负样本的关注程度温度越低模型越倾向于把相似度高的负样本推远相当于在“惩罚最像正样本的负样本”温度越高所有负样本的梯度贡献趋于均匀模型训练相对平稳但区分能力弱化。我做过一组对比实验CIFAR-10SimCLRbatch256200 epochs温度系数线性评估精度0.0379.8%0.0581.5%0.0782.0%0.182.4%0.280.1%从这个结果可以看到温度在0.07到0.1之间有一个比较宽的平坦区间但偏离太多比如0.03或0.2就会明显掉点。这个规律在不同数据集上基本成立只不过最优值的位置会有偏移。如果你没有精力做搜索直接用0.07作为起点再在每个方向上试探一次基本能拿到不错的效果。5.5 训练时间太长有没有快速验证的手段对比学习通常需要200到400个epoch才能充分收敛这在个人电脑上不太现实。我的做法是先用小规模数据子集比如1万张图快速验证pipeline的正确性训练50个epoch看趋势是否正常loss是否下降、kNN精度是否稳步上升。如果趋势正常再上全量数据跑长训练。另外一个有价值的小技巧是“早停预判”对比学习前20个epoch的kNN精度如果低于随机猜测太多说明增强策略或模型结构很可能有问题如果前10个epoch能看到kNN精度持续上升基本可以确定pipeline没问题后面就是时间换效果。用这个小指标能在一天内筛掉很多不靠谱的配置。6. 一点个人体会与后续扩展思路写到这里我想分享一个体会对比学习最让我着迷的地方在于它把“特征学习”从“任务学习”中剥离出来了。以前做分类特征是为标签服务的换个任务就得重新训练现在用对比学习预训练出来的特征可以迁移到分类、检测、分割、检索等多个任务这让模型有了点“通用理解”的味道。如果后续你自己上手建议沿着两条线扩展一条是把对比学习用到你的真实数据上先跑通SimCLR再换成MoCo或BYOL对比效果另一条是去看对比学习在跨模态领域的延伸比如CLIP用文本和图像做对比、语音和文本的对比学习思路都是相通的。最后再分享一个操作上的小习惯每次训练前我都会固定随机种子把DataLoader的shuffle也设置为固定的随机种子这样实验结果可复现调试的时候能确定改动是来自代码还是随机性。对比学习的训练随机性比监督学习大不少固定种子能帮你省下很多“跑了一遍结果不一样”的困惑。这一期就聊到这儿自己动手把代码跑一遍再调一调温度系数和数据增强你会对这个领域有比看文章更深的理解。
阅读完成 · 觉得有帮助?