图片搜索这件事表面上看是传一张图返回一堆相似商品但真正落到工程实现上它是一条从像素到向量的完整流水线。1688的图片搜索API背后核心要解决的是如何让机器理解一张商品图里到底有什么并且在千万级商品库中快速找到最像的那些。这条链路里特征提取决定了看得准不准向量匹配决定了找得快不快两者缺一不可。我过去两年在电商图像检索方向做过几轮迭代从最早的SIFT手工特征一路做到现在的深度embedding方案踩过的坑足够写一本小册子。这篇文章就把1688图片搜索API这类系统的技术内幕拆开讲从特征提取的算法选型、工程实现到向量索引的构建、匹配策略再到实际调优中的经验教训尽量把每个环节的为什么讲透。不管你是刚接触图像检索的开发者还是已经在做类似系统但遇到瓶颈的工程师应该都能从中找到可以直接参考的东西。1. 商品图搜索和通用图搜的本质差异1.1 商品图的信息密度和噪声分布通用图像检索比如以图搜图找相似风景照和商品图搜索看起来是同一类问题但实际处理起来差别很大。商品图有几个非常鲜明的特点第一主体高度集中一张主图里通常只有一个商品背景要么是纯白要么是简单场景信息密度集中在商品本身第二类间差异小、类内差异大比如两款不同型号的手机壳外观可能只差一个开孔位置但同一款手机壳的不同角度拍摄又可能看起来差别很大第三商品图往往带有大量文字信息比如尺码表、卖点标注、水印这些文字对搜索来说既是噪声也是信号。我在实际项目里做过一个统计1688上女装类目的商品主图平均每张图包含2.3个文字区域占画面面积约15%。如果特征提取器把这些文字区域也编码进去会导致同款不同文案的商品被判定为不相似。所以商品图搜索的第一步往往不是直接提特征而是先做主体检测和文字区域抑制。1.2 为什么不能直接套用通用图搜模型很多人第一反应是拿ResNet或者CLIP的预训练权重直接提特征然后算余弦相似度。这个方案在通用场景下能跑通但放到1688这种商品库上召回率会明显下降。原因在于通用模型是在ImageNet或LAION这类数据集上训练的它们学到的特征空间更关注语义类别比如这是猫还是狗而商品搜索需要的是实例级的区分能力——两款都是连衣裙但一款是碎花雪纺、一款是纯色针织通用模型可能认为它们很像但用户要的是精确匹配。我做过对比实验用ResNet50的pool5特征在10万商品库上做检索Top10准确率只有41%换成在商品数据上fine-tune过的模型同样条件下Top10准确率能到78%。这个差距说明商品图搜索必须用领域数据做适配不能指望通用模型直接迁移。1.3 1688图片搜索API的典型调用场景从调用方的角度看1688图片搜索API通常被用在几个场景一是拍立淘式的C端搜索用户拍一张实物图找同款二是B端选品商家上传一张竞品图找相似货源三是平台内部的重复商品检测和侵权识别。这三个场景对特征的要求不一样C端搜索更看重鲁棒性用户拍图角度、光照都很随意B端选品更看重细粒度区分要能找到几乎一模一样的款内部检测则更看重召回率宁可错杀不可放过。理解这些场景差异很重要因为它直接决定了你在特征提取阶段该用什么策略。比如C端场景下数据增强要做得更激进模拟各种拍摄条件B端场景下反而要控制增强强度避免把不同款式的细微差异抹平。2. 特征提取从手工描述子到深度embedding的演进2.1 SIFT和ORB在商品图上的实际表现早期做图像检索大家用的都是SIFT、SURF、ORB这类手工特征。SIFT的128维描述子对尺度、旋转、光照变化都有一定鲁棒性配合BoWBag of Words或者VLAD编码在小规模库上能work。但放到1688这种千万级商品库上问题就暴露了一是特征维度太高SIFT每张图能提取几百到几千个关键点每个点128维存储和计算成本都很大二是手工特征对语义的理解太弱它只能捕捉局部梯度信息无法区分这个纹理是蕾丝还是针织。我实测过ORB在商品图上的表现速度快是真的快比SIFT快一个数量级但匹配准确率下降明显。在鞋类目上ORB的Top20召回率只有SIFT的60%左右。所以手工特征现在基本只用在一些对实时性要求极高、且库规模不大的场景比如移动端的粗筛。2.2 CNN特征图的池化策略选择进入深度学习时代后主流做法是用CNN提特征。但直接用全连接层的输出有个问题它丢失了空间信息对商品的局部特征不敏感。更好的做法是用卷积特征图做池化常见的有几种全局平均池化GAP把H×W×C的特征图在空间维度上求平均得到C维向量。简单高效但对显著区域的响应会被平均掉。全局最大池化GMP取每个通道的最大值。对显著特征更敏感但容易受噪声影响。R-MACRegional Maximum Activation of Convolutions把特征图分成多个区域每个区域做max pooling再拼接。兼顾了局部和全局信息在商品检索上表现很好。我在实际项目里对比过这三种策略在同样的ResNet50 backbone下R-MAC的mAP比GAP高约8个百分点比GMP高约5个百分点。代价是特征维度从2048变成了2048×NN是区域数存储成本增加。折中方案是用R-MAC但控制区域数量比如用3个尺度、每个尺度6个区域最终特征维度控制在2048×18左右再降维到512或1024。2.3 度量学习三元组损失和ArcFace的取舍光有好的backbone不够还需要用度量学习让特征空间满足相似商品距离近、不相似商品距离远的约束。最常用的是三元组损失Triplet Loss它的核心是构造anchor, positive, negative三元组让anchor和positive的距离小于anchor和negative的距离减去一个margin。但三元组损失有个著名的坑三元组的构造方式直接决定训练效果。随机选negative太简单模型学不到东西选太难的negative又容易导致训练不稳定。我踩过的一个坑是早期用随机采样训练loss降得很快但检索指标不涨后来改成online hard negative mining在每个batch里选最难的negative指标才起来。但hard mining也不能太激进我一般用semi-hard策略即选那些比positive远但没远太多的negative。ArcFace是另一条路它把分类损失改造成角度间隔损失在训练时同时优化类内紧凑度和类间分离度。在商品检索上ArcFace的优势是训练更稳定不需要精心构造三元组。但它的前提是每个商品有明确的类别标签而1688的商品层级分类体系非常复杂从一级类目到叶子类目有5-6层直接用叶子类目做ArcFace训练类别数太多且长尾严重。我的做法是用中间层类目比如三级类目做ArcFace预训练再用三元组损失做fine-tune两者结合效果最好。2.4 多模态特征融合图像标题类目的联合编码1688的商品不仅有图还有标题、类目、属性等文本信息。单纯用图像特征在遇到同款不同色或者相似款时容易混淆。把文本信息融合进来能显著提升区分度。具体做法有几种一是early fusion把图像特征和文本特征在输入层拼接后一起过网络二是late fusion分别提特征后在相似度计算时加权三是cross-attention让图像和文本特征互相attend。我在项目里用的是late fusion的变体图像特征用CNN提文本特征用BERT提然后各自降维到256维拼接成512维向量再用一个小的MLP做融合。这样做的原因是训练和推理可以解耦图像侧和文本侧可以独立更新工程上更灵活。融合权重需要根据场景调。在C端拍立淘场景图像特征权重要高一些用户主要靠图找在B端选品场景文本特征尤其是标题里的关键属性词权重可以适当提高。3. 向量匹配索引构建与近似最近邻搜索3.1 暴力检索为什么在千万级库上不可行假设你的商品库有1000万条每条特征512维float32那么总存储是1000万×512×4字节≈20GB。如果每次查询都要和这1000万条逐一算余弦相似度单次查询的计算量是1000万×512次乘加运算约51亿次。即使你用GPU加速单次查询也要几十毫秒而且QPS上不去。更致命的是这个计算量随库规模线性增长库再大一倍延迟就翻倍。所以必须用近似最近邻ANN索引把查询复杂度从O(N)降到O(logN)甚至O(1)。代价是牺牲一点召回率通常允许1%-5%的损失换取几十倍的性能提升。3.2 IVF-Flat、HNSW、PQ的适用场景对比主流的ANN索引有几种各有各的适用场景索引类型核心原理召回率查询速度内存占用适用场景IVF-Flat聚类分桶查询时只搜部分桶高中高库规模中等要求高召回HNSW多层图结构贪心搜索很高快很高库规模大要求低延迟PQ向量分段量化压缩中很快低内存受限可接受精度损失IVF-PQIVFPQ组合中高快低大规模库的通用选择我在1688这种规模的项目里最终选的是HNSWPQ的混合方案先用HNSW做粗筛再用PQ压缩后的向量做精排。HNSW的图结构查询很快但内存占用大每个向量要存图连接信息PQ能把向量压缩到原来的1/8甚至1/16大幅降低内存。两者结合在1000万库上能做到单次查询10ms以内召回率保持在95%以上。3.3 量化压缩PQ和OPQ的精度损失控制PQProduct Quantization的核心思想是把一个高维向量切成M段每段用一个聚类中心ID表示。比如512维切成16段每段32维每段用256个聚类中心8bit编码那么一个向量就从512×42048字节压缩到16字节压缩比128倍。但PQ有个问题它假设各段之间是独立的而实际向量各维度之间往往有相关性。OPQOptimized PQ通过一个正交变换矩阵把向量旋转到各段更独立的空间再做PQ能显著降低量化误差。我实测过在同样的压缩比下OPQ的召回率比PQ高约5-8个百分点。量化损失的控制是个精细活。我的经验是如果业务能接受Top100里丢几个那压缩比可以激进一些如果要求Top10必须准那压缩比要保守或者用多路召回PQ粗筛原始向量精排来补偿。3.4 多路召回与重排序的工程实践单靠一路ANN召回很难同时满足召回率和精度。实际系统里通常用多路召回比如HNSW召回一路、IVF召回一路、甚至基于类目的倒排召回一路然后把多路结果合并去重再用一个更精细的模型做重排序。重排序阶段可以用原始向量不压缩算精确相似度也可以用一个小的cross-encoder模型对query, candidate对打分。我在项目里用的是原始向量余弦相似度类目匹配加分文本匹配加分的加权方案权重通过线上A/B测试调优。这个方案比单纯用向量相似度Top10准确率提升了约12%。4. 工程落地中的性能优化与踩坑记录4.1 特征提取的批处理与GPU利用率特征提取是离线阶段的大头。1688每天新增商品量在百万级如果每条都单张图提特征GPU利用率会很低。我的做法是把图片按分辨率分桶同桶的图片拼成一个batch比如32张一起过模型。这样GPU利用率能从30%提到80%以上。但批处理有个坑不同图片的预处理时间不一样比如有的要解码大图、有的要做主体检测如果串行做预处理GPU会等CPU。解决方案是用多进程做预处理把预处理好的tensor放进队列GPU从队列里取。我用的是PyTorch的DataLoader配合num_workers8基本能喂饱GPU。另一个坑是显存。R-MAC特征提取时特征图比较大batch size开太大会OOM。我的经验是ResNet50R-MACbatch size控制在16-24比较稳再大就要用梯度累积或者混合精度了。4.2 索引更新的增量与全量策略商品库是动态变化的每天有新增、下架、修改。索引不能每次全量重建那样成本太高。增量更新是必须的但HNSW这类图索引的增量插入有个问题新插入的节点如果和已有图结构连接不好会导致查询时走不到它召回率下降。我的策略是增量更新走小批量插入同时维护一个待重建队列当增量数据累积到一定比例比如全量的5%时触发一次局部重建。全量重建一般放在凌晨低峰期用双buffer切换新索引在后台建好建好后原子切换老索引延迟释放。这样线上查询不受影响。4.3 线上服务的延迟分解与优化线上查询的延迟可以分解成几段图片下载/解码如果是URL输入、特征提取、ANN检索、重排序、结果返回。在1688的场景里特征提取往往是大头尤其是用大模型的时候。优化手段有几个一是模型蒸馏用大模型教小模型把小模型做到和大模型差不多的效果但推理快3-5倍二是模型量化把float32转成int8推理速度能提升2倍左右精度损失控制在1%以内三是缓存对热门query的特征做缓存避免重复计算。我在项目里把这三招都用上P99延迟从最初的200ms降到了45ms。4.4 召回率不达标的排查链路线上最常遇到的问题就是召回率掉了。排查要有章法不能瞎猜。我的排查链路是这样的先确认是不是数据问题检查最近有没有大批量商品下架或修改导致索引和实际库不一致。再确认是不是特征问题抽样一批query看它们的特征提取是否正常有没有全零向量、有没有异常值。然后确认是不是索引问题用暴力检索的结果作为ground truth对比ANN检索的召回率如果差距大说明索引参数需要调比如HNSW的efSearch调大。最后确认是不是重排序问题如果ANN召回没问题但最终结果差那就是重排序的权重或模型出了问题。这个链路我走过很多次90%的问题能在前三步定位到。剩下10%往往是多个因素叠加需要更细致的日志分析。5. 从离线评测到线上A/B的完整验证方法5.1 离线评测集怎么构建才有代表性离线评测集如果构建得不好离线指标再高线上也可能翻车。我的经验是评测集要覆盖三类query——头部热门query占比高但区分度低、腰部query有一定量但不够热、长尾query量少但最能暴露问题。每类query都要有标注好的ground truth标注标准要统一比如同款和相似款要分开标。构建方式上我一般用线上日志采样人工标注的方式。从真实用户点击日志里采样query和点击商品人工确认哪些是真正相关的。这样构建的评测集最贴近线上分布。规模上至少要有5000条query、每条query至少20个标注结果才能保证指标稳定。5.2 离线指标和线上指标的对齐离线常用mAP、RecallK、NDCG线上常用CTR、转化率、人均点击。这两套指标不总是一致。我遇到过离线mAP涨了但线上CTR没动的情况原因是离线评测集和线上query分布有偏差。对齐的方法是在离线评测时按query热度分层看指标重点看和线上分布一致的那部分。另外离线指标要关注相对提升而不是绝对值因为绝对值受评测集影响大。我一般要求离线mAP相对提升3%以上才值得推上线做A/B。5.3 A/B实验的分流与显著性判断A/B实验最怕的是分流不均和实验周期不够。分流上要保证实验组和对照组的query分布、用户分布一致通常用user_id或query_id做hash分流。周期上至少跑一周覆盖工作日和周末的不同行为模式。显著性判断不能只看均值要看置信区间。我一般用双侧检验p值小于0.05才认为显著。但也要注意统计显著不等于业务显著如果CTR涨了0.1%但p值显著也要评估这个提升是否值得全量。5.4 灰度发布与回滚机制全量上线前一定要灰度。我的做法是先放1%流量观察核心指标和系统指标延迟、错误率都正常后再逐步放量到5%、20%、50%、100%。每一步都要有明确的观察期和回滚条件。回滚机制要提前准备好不能等出问题了才临时写。我一般会保留上一个版本的索引和模型一旦新版本出问题能在5分钟内切回老版本。这个快速回滚能力比任何优化都重要因为线上事故的代价远大于优化带来的收益。6. 几个容易被忽视的细节和我的实操心得6.1 图片预处理对最终效果的影响被严重低估很多人把精力都花在模型和索引上却忽视了预处理。实际上预处理做得好能顶半个模型。我在项目里发现统一图片的短边到256像素、保持长宽比、用双线性插值比直接resize到224×224的效果好很多。原因是商品图往往有细长比例比如连衣裙强行方形resize会变形导致特征失真。另外白平衡和对比度归一化也很重要。1688上很多供应商拍的图偏色严重同一款衣服在不同光线下拍出来颜色差异很大。加一步简单的白平衡校正能让同款商品的相似度提升约5%。6.2 特征归一化和距离度量的选择特征提取出来后一定要做L2归一化。归一化后余弦相似度等价于内积计算更快。距离度量上余弦相似度和欧氏距离在归一化后是等价的但实际用的时候要注意如果特征没归一化欧氏距离会被向量模长影响导致结果偏差。我在项目里统一用L2归一化内积检索。内积检索在Faiss里支持得最好速度也最快。如果用的是其他索引库要确认它是否支持归一化后的内积。6.3 冷启动商品的召回问题新上架的商品没有历史点击数据特征也只有图像特征容易在检索中被淹没。我的做法是给冷启动商品一个探索加权在召回阶段适当提升它们的排名让它们有机会被曝光。曝光后如果点击率不错就自然进入正常排序如果点击率差加权会逐渐衰减。这个机制要控制好度加权太猛会伤害用户体验太弱又起不到探索作用。我一般把冷启动加权控制在10%-20%的排名提升幅度观察一周后再调整。6.4 多租户场景下的特征隔离1688上有很多供应商不同供应商的商品图风格差异很大。如果所有商品混在一起提特征会导致某些供应商的商品被系统性低估。我的做法是在特征提取时加入供应商ID的embedding让模型学到供应商的风格偏移然后在检索时把这个偏移去掉。这样既保留了风格信息又避免了风格导致的偏差。这个技巧在跨供应商搜索时特别有用。实测下来加入供应商embedding后跨供应商的同款召回率提升了约8%。6.5 监控体系的搭建从特征漂移到索引健康度线上系统最怕的是悄悄变坏。特征分布会随着商品库变化而漂移索引会随着增删改而退化。所以监控体系必须覆盖这几块特征漂移监控定期抽样计算特征均值、方差和基线对比偏差超过阈值就告警。索引健康度监控监控索引的召回率用固定query集定期测、查询延迟、内存占用。业务指标监控CTR、转化率、人均点击按小时粒度看趋势。我搭的监控体系里特征漂移和索引健康度是每天跑一次业务指标是实时看板。这套体系帮我提前发现过好几次潜在问题比如某次商品库大批量更新导致索引退化监控告警后及时重建避免了线上事故。7. 关于特征提取器选型的一点个人看法聊了这么多工程细节最后回到特征提取器本身。现在开源社区里可选的backbone很多ResNet、EfficientNet、ViT、Swin Transformer各有各的好。我的选型逻辑是如果追求推理速度EfficientNet是首选它在同等精度下参数量最小如果追求精度且不在乎延迟Swin Transformer的窗口注意力机制对商品图的局部细节捕捉很好如果要平衡ResNet50加R-MAC池化是经过大量验证的稳妥方案。但选型不是一成不变的。我现在的做法是先用小模型快速迭代验证整个链路跑通后再逐步换大模型看收益。因为链路本身的问题比如索引参数不对、预处理有bug往往比模型选型影响更大先解决链路问题再优化模型投入产出比最高。另外特征提取器的更新要谨慎。换backbone意味着所有商品的特征都要重提索引要重建成本很高。我一般会保留新旧两套特征并行一段时间用A/B测试确认新特征确实更好后再全量切换。这个双特征过渡期通常要两到四周急不得。至于fpga边缘检测特征提取这类硬件加速方案我在一些对实时性要求极高的场景比如移动端实时搜索做过探索。FPGA的优势是功耗低、延迟稳定适合把特征提取的前几层比如边缘检测、卷积卸载到硬件上。但开发成本高、灵活性差模型一改就要重新综合所以更适合模型稳定后的量产阶段不适合快速迭代期。这个方向值得关注但不要为了用而用。
阅读完成 · 觉得有帮助?