数据科学这个圈子有个很现实的问题大多数团队不缺数据缺的是能用起来的数据。标注成本一年比一年高业务方又不愿意等三个月等一批高质量标注于是很多人开始盯上无监督方案。我大概在一年多以前开始认真研究对比学习当时纯粹是被逼的——项目组拿不到足够的标注样本又希望做一个通用的商品表征服务能够同时支撑检索、聚类、相似推荐好几个下游任务。试了一圈之后我确认对比学习确实是当下无监督大数据表征里最值得投入的方向之一它不依赖标签不需要针对某个细分任务单独调模型训练完之后可以直接迁移到多个场景。这篇文章我不打算从原理教科书的角度来写而是按照我实际落地一个对比学习项目的过程来拆核心思想、关键组件、训练参数、评估方法、踩过的坑一条线讲下来。如果你正准备在团队里引入对比学习或者已经在跑但效果不理想这篇多少能帮你少走一点弯路。内容会比较长重点部分我会尽量展开。1. 对比学习到底在学什么从一个没有标签的困局说起1.1 表征学习的前提让数据自己说话先聊一个最基本的背景。过去我们做有监督训练本质上是让模型通过输入x对应标签y的映射来学到特征。这种学法的上限很高代价也很大。一张图要不要打标、一段文本要不要分类都需要人在前面把语义定好。如果样本量是百万级标注成本和时间成本很快就失控了。于是大家开始琢磨一个更根本的问题模型能不能不依赖标签直接从数据本身的结构里学到一种通用表示这种通用表示就是表征。理想情况下一个训练好的编码器应该做到语义上相似的内容在表征空间里距离近语义上不相似的内容距离远。一旦得到这样的表征下游任务就变得很简单——加一个线性分类头就能做分类算一下向量距离就能做检索和聚类完全不需要为每个任务重新训练一个深度模型。对比学习的核心思路就是这么来的不告诉模型这张图是猫而是告诉模型这张图和另一张图是同一类跟其他图不是同一类。让模型在大量的相似/不相似配对中自己去刻画数据的语义结构。听起来像是降级版的监督学习但在实际实验里只要数据量够大、增强方式设计得好学出来的表征甚至可以逼近有监督的预训练模型。1.2 一个类比从脸盲到人脸识别我经常跟团队里的新同学用一个类比对比学习有点像你刚到一个新城市谁也不认识但你能慢慢认出同一个小区的邻居和完全陌生的路人。区分熟悉的邻居并不需要你知道每个人的名字你只需要知道见过这个人这个人跟那个人一起走这两个人看起来有关系。模型也是一样。给它一批图片它不知道里面谁是猫谁是狗但如果构造出一种规则让模型知道同一只猫的两个不同角度的照片应该接近模型就会逼着自己去学习猫背后那些稳定的视觉因素——毛发纹理、姿态、颜色分布——而不是盯着某一个像素。这个去除细枝末节、抓住核心语义的过程其实就是特征提取器逐渐变强的过程。有监督学习给的是标准答案对比学习给的则是关系线索。线索虽然没有答案那么直接但只要线索足够多、设计得足够细模型照样能把底层结构学出来。1.3 对比学习解决的核心问题展开来看对比学习解决的是三个层面的问题。第一冷启动问题。新业务积累不了标注数据但原始数据往往早就躺在那了。对比学习可以直接用这些原始数据预训练一个底座等后续有少量标注之后再加微调启动成本非常低。第二通用性问题。传统有监督模型往往是一任务一模型换个分类目标就要重新训练。对比学习学的是通用表征任何一个需要向量表示的下游任务都可以直接复用省掉了大量的重复训练开销。第三语义连续性问题。现实中很多业务场景的语义并不是几个离散类别能概括的比如电商商品包含颜色、款式、材质、适用人群等多重属性人和商品之间的相似度也不是简单属于同一类。对比学习在连续的表征空间里表达相似关系这种表达方式比分类标签灵活得多也更贴近真实业务。2. 核心组件拆解正样本、负样本、损失函数、温度参数与投影头理解了思想之后就到了真正动手前必须搞清楚的五个关键设计。对比学习看起来只是一个损失函数的事但它的效果好坏几乎完全取决于这几个组件怎么配合。我逐个说下我的理解。2.1 正样本对数据的两个视角对比学习的第一个关键设计是怎么定义正样本对。没有标签的情况下我们只能手动制造语义一致的样本对。最常用的做法是数据增强对同一个训练样本做两次不同的随机变换得到两个视角。比如一张商品图一次做随机裁剪、颜色抖动另一次做旋转、模糊这两张图内容不同但语义一致就被当成一个正样本对。数据增强的选择直接决定了模型学到的是语义还是像素。如果增强太弱两张视角几乎一模一样模型走个捷径就能把损失降到很低根本接触不到语义层面的差异如果增强太强正样本对的语义都被破坏了模型又会觉得这俩根本不是同一个东西训练起来很困难。这里需要在够难和别太离谱之间找一个平衡点等讲踩坑时我会再细说。2.2 负样本从Batch内构造负样本的定义通常比正样本简单粗暴一些在一个训练批次里除了样本自己对应的那个增强视角之外其他所有样本都算是它的负样本。也就是同一个batch里N个样本各自生成两个视角共2N条数据对于其中任何一条数据来说它的正样本是唯一的另一半视角剩下的2N-2条都是负样本。这个设计的精妙之处在于它不需要额外的采样逻辑只要batch内样本分布够随机每个样本天然能拿到一大批有区分度的负样本。batch size越大负样本越多模型面对的区分难度越大学到表征的判别力也越强。这也是为什么对比学习普遍推荐用大batch训练后面我会给具体参数。2.3 损失函数拉近正样本、推开负样本损失函数是整个训练的指挥棒。行业内最通用的是InfoNCE家族系列的变体核心逻辑就两件事让正样本对的表征相似度尽量高让负样本对的表征相似度尽量低。相似度一般用余弦相似度来算取值在-1到1之间正好适合这种做远近度量的场景。如果把训练目标翻译成一句话在给定一个锚点样本的情况下模型要从2N个候选里把那个唯一的正样本准确挑出来。这样一来对比学习就被定义成了一个极端多分类问题。相比于让模型直接回归某个相似度数值这种从人群中认出你的方式学到的特征往往更鲁棒因为负样本的分布范围广、难度各异模型必须真正理解什么是核心语义才能稳定地做对选择。2.4 温度参数控制惩罚力度的旋钮温度参数t是InfoNCE里最敏感的旋钮之一作用是把相似度分数做非线性的缩放从而改变模型对难分样本的注意力。形象一点说温度参数决定了分数差距要多大的时候模型才会认真去区分它们。t值设得小比如0.07相似度分数被拉大模型会非常严厉地惩罚那些稍微相似的负样本逼迫它把特征分布压得非常集中类内间距小、类间间距明显但训练也容易变得不稳定t值设得大比如0.5模型对难分样本就没那么敏感训练过程平稳但学出来的表征可能比较松散判别力不足。我在实验里对比过一组数据同一个模型在t0.07和t0.5下下游分类准确率能差出8个点以上。所以这个参数值得专门做一轮搜索别直接用默认值。2.5 投影头训练完就扔掉的那一层很多第一次接触对比学习的人都会对网络结构里的projection head感到困惑。实际上这个设计非常有讲究编码器负责把原始输入转成特征向量投影头再接一层MLP把特征向量映射到另一个低维空间训练的时候用投影后的向量来算对比损失训练完只保留编码器投影头直接舍弃。为什么要多此一举因为直接约束编码器输出会损伤它的泛化能力。想象一下如果你要求特征在训练时已经挤得非常紧编码器学到的就很难迁移到新的下游任务。而接一个投影头作为缓冲层让编码器只负责提取那些通用的、结构化的信息让投影头去完成那些跟训练任务绑定的紧凑化要求最终保留下来的是一个干净、通用的表征。这是我在实践中觉得非常反直觉但就是有效的设计之一。3. 真实场景实操某电商商品表征项目的训练与调优3.1 项目背景和数据管道设计我实际跑对比学习的场景是给一个电商策略团队做商品通用表征。当时手里的数据大概五千万条商品记录每条包含主图、标题、类目和几个关键属性标签很少且质量参差不齐。做有监督模型基本没戏所以我把宝压在无监督对比学习上。数据管道的第一步是构建输入对。我选择的方案是图文分开各自做增强图像随机裁剪、颜色扰动、旋转文本用随机删除、同义词替换、词序打乱。然后把图像和文本向量在某个特征层拼接起来作为编码器的输入。这样一个商品就成了两条增强后的数据形成正样本对。因为涉及多模态数据管道会比纯视觉复杂一些清洗逻辑也要特别注意图片损坏的记录直接丢弃文本清洗掉乱码和全数字标题避免模型学到异常分布。这里有一个经验清洗质量对无监督学习的影响比对有监督学习更大。有监督场景里少量脏数据可能被正确的标注稀释掉无监督场景里没有标签兜底脏数据形成的错误结构会被对比学习放大最终污染表征空间。3.2 模型结构与关键参数选择在框架上我试过几种设计最终采用的是双塔结构加一个共享编码器的方案。双塔的直观理解是两侧输入分别走各自的编码器得到一个向量共享编码器则是两边用同一套参数好处是大幅减少了参数量而且迫使模型忽略输入的模态差异更关注语义对齐。做多模态的时候共享编码器比完全独立的双塔更容易训练这是我自己的结论。训练参数上我直接给了以下这组经过几轮调优后的配置供参考参数设定值说明编码器结构12层Transformer处理文本序列和图像patch序列projection维度128投影头输出维度不算太大但足够用于训练batch size2048单机多卡每卡256共8卡优化器AdamW权重衰减0.01beta设为(0.9, 0.999)学习率1e-3采用warmup策略前10个epoch线性升到峰值训练epoch60超过60个epoch后增益不明显开始过拟合温度参数t0.1经过0.05到0.5的搜索0.1附近效果最稳图增强裁剪幅度0.2到1.0裁剪比例下限设0.2太小的裁剪会切掉主体文本删除概率0.1逐词随机删除删除太多会丢失关键信息你可能会问为什么batch size要开这么大。前面说过对比学习中负样本来自batch内部batch越大负样本数量越大模型就近看到的硬负样本就越多训练效率越高。如果显存不够大可以把批次分成多个mini-batch用梯度累积来模拟更大的batch不过实际效果还是会比真大batch差一点因为负样本池在反向传播前就被隔离了。3.3 训练过程中的loss曲线变化训练刚开始的五到十个epochloss会下降得很快这是正常现象。模型正在快速学会区分那些最粗糙的差异比如颜色、背景、文本长度这些浅层特征。之后loss下降速度会明显放缓这时候模型才开始慢慢琢磨那些细粒度的语义差异需要一些耐心。我在这个阶段经常去看的是hard negative的表现。简单说就是那些表面上很像、实际语义不同的负样本对比如两个不同品牌但款式外观接近的商品。如果训练后期模型在这些hard negative上的相似度排名中依然频繁犯错说明表征还没有学到足够细致的语义信息可以考虑继续训练或用更强的数据增强。训练进行到40个epoch左右我观察过loss和下游任务准确率的对照发现loss并不是越低越好。某个检查点上loss继续下降但检索质量指标反而掉了。这说明模型开始过拟合到增强模式下的一些伪规律而不是真正的语义结构。所以确定epoch数量不能只盯训练loss一定要结合下游任务指标来综合判断。4. 效果好不好不能靠感觉无监督表征的评估方法4.1 为什么需要单独设计评估方案无监督模型的评估比有监督要难一个层次。没有标注没有验证集loss又是一条平滑下降的曲线你很难判断当前模型是已经学得很好了还是根本没学会但在硬拟合。我一开始也犯过这个错训练了三十个epoch自我感觉良好结果一下游任务完全不行才知道模型学到的东西根本不能迁移。后来我建立了一套多层次的评估方案每训练几个epoch就跑一轮用不同的视角来检验模型状态。这套方案不新鲜但很有效尤其适合团队里没有专职算法评估人员的场景。4.2 线性探测Linear Probing判断表征质量的硬指标线性探测的思路是把预训练编码器输出的特征固定住只在上面加一层线性分类器用少量有标签数据训练这个分类器然后看它的准确率。因为线性分类器非常弱它只能利用特征本身已经包含的线性可分的语义信息所以准确率越高说明表征质量越好。我们当时用了一万条人工复盘过的商品小样本作为标记集用线性探测跑了三分类、五分类和十分类三个任务。对比学习预训练得到的表征线性探测准确率比一个从零训练的有监督小模型还要高。这一点让我彻底信了无监督表征的价值它的确学出了一些有监督小样本学不到的通用结构。关于线性探测有个细节值得注意线性分类器训练时的学习率不能照搬预训练参数一般要单独搜索一轮使用很小的学习率例如1e-3之一训练10到20个epoch就足够否则会把编码器学到的底层特征覆盖掉。有人在这里翻过车把编码器一起微调了测出来的结果虚高下游迁移时复制不出来。4.3 检索评测贴近业务场景的验收方式由于电商业务的核心诉求是找相似商品检索质量是最贴近业务的验收标准。我构建了一个小规模的检索评测集人工标注了500组商品对每组里面一个查询商品若干候选商品人工给出相似程度的排名。然后用模型输出的表征做余弦相似度排序计算NDCG和Recall10。这个评测的妙处是即使没有完整标注只要每组有几百条相对顺序就能得到一个相当稳定的质量评估。NDCG到了0.83之后我在可视化工具里抽样看了一些case发现模型确实能跳出同颜色同款式这些显式特征开始捕捉这种设计风格适合什么人群这种更抽象的东西效果比较让人放心。如果只做离线指标不抽样看case很容易被数字欺骗。线上用户关心的是看起来像不像一个东西这些往往很难用单一的离线指标完全反映。所以哪怕只是随机抽200个case人工过一眼都比只看指标要可靠。4.4 特征可视化给表征一个直觉印象特征可视化主要是辅助手段但能帮团队快速建立信心。我一般用t-SNE把编码器输出的高维表征降到二维平面然后把商品样本按类目着色看不同类目在平面上的分布是否自然成簇。有监督模型很容易形成一个个边界清晰的聚类无监督模型如果学得好聚类结构会存在但边界往往是渐变的中间会有大量过渡样本。这个特点不是缺陷反而说明模型捕捉到了语义的连续性比如从运动鞋过渡到休闲鞋再过渡到皮鞋这是一个连续的语义光谱。所以我特别反对拿聚类纯度去衡量无监督表征。聚类本身不是对比学习的目标表征的自然聚簇只是它学到语义结构后的副产品。我更看重特征可视化后是否呈现有意义的组织形态以及是否跟业务认知一致。5. 实战中遇到的两个典型问题与排查过程5.1 难题一loss不降模型完全学不动第一次训练到第三个epoch时我发现loss几乎不再下降卡在一个数值附近抖动。当时第一反应是学习率太大导致震荡但降了学习率后loss更是纹丝不动。继续排查后我发现问题出在数据增强上。那一版代码里我用了过强的图像裁剪随机裁剪范围最低到了原始图片的0.08导致很多正样本对里两张图根本看不到同一个主体。比如一双鞋被裁出了鞋带和鞋底另外一边裁出了鞋帮这两个视角虽然来自同一商品但视觉特征已经没有共同点了模型怎么学都学不出这俩是同一东西。把裁剪范围的下限从0.08提到0.2之后loss在两个epoch内就开始正常下降。那次经历给我的教训是对比学习的无监督并不等于免设计数据增强本身就是注入到模型里的最核心的先验知识。5.2 难题二训练正常但下游任务就是不行loss曲线一切正常线性探测却始终上不去这是比loss不降更让人头疼的情况。我排查了很久最后定位到一个很隐蔽的问题训练数据里存在大量的重复或近似重复商品。电商系统里同一款商品经常有多个商家上架图片分辨率、拍摄角度、标题措辞都不同但语义完全一致。对底层的对比学习来说这些同一个语义的记录在batch里经常被当成负样本对模型不断被告知这两个看起来一样的东西并不一样语义结构被严重干扰。为了解决这个问题我对训练数据做了基于minHash的近似去重先对商品标题做shingle切分再用minHash签名按概率把疑似重复的商品归到同一组每组只保留一条代表性样本。去重之后线性探测准确率提升了大概6个点。有时候问题不在模型而在喂给模型的数据本身这个排查思路值得记录下来。5.3 一些小参数调对了有大效果最后说几个我实验过、效果明显的小参数。投影头的输出维度不宜过大我用128维比512维效果更好原因可能是低维空间里对比损失的约束更有力模型不能靠嘈杂的特征混过去必须提炼出真正核心的信息。学习率和batch size要联动调整。batch从1024提到2048时学习率也要跟着调高一倍左右否则训练速度会明显变慢loss曲线变得拖沓。warmup的epoch数要与总epoch成比例我在60个epoch的总长度下warmup设为10个epoch太长的warmup会浪费训练时间太短则容易在初期就陷入糟糕的局部结构。多模态场景下不同模态之间的增强强度不能一刀切。图像增强的影响比文本增强更剧烈因为视觉特征的底层变化空间比文本的词序变化大得多。我在两组模态各自的增强强度上分别调过参数最后图像裁剪比例取0.2下限文本删除只取0.1比例高了都会显著降低最终表征质量。对比学习做下来我的整体感觉是它在无监督大数据表征这个问题上是当前最能打的方案之一但它要求你付出额外的设计成本。你要精心构造数据增强耐心稳定动态对比细心监控评估指标。那些宣称丢进去就能自己学的说法至少在我自己的实践里没有出现过。每一步都需要你回来盯数据、看loss、翻case但当你看到模型在没有标签的情况下自己把语义结构组织得井井有条时那种感觉确实值得。最后分享一个小技巧每次调整参数记得把模型在不同epoch下的表征都存一个副本后面做对比分析时这些过程版本会成为你排查问题最有力的依据。