SPX-Algorithm:构建多模态搜索服务的一些心得
项目背景和定位
SPX-Algorithm 本质上是一个图像搜索推荐模块。用户输入文本描述,系统从图片库里找出最相关的结果。听起来简单,但实际做起来发现挑战不少。
传统的图片搜索主要靠标签匹配,但这种方式局限性很明显。图片的标签往往不够丰富,用户的表达又很灵活。所以我的目标很明确:用AI来理解文本和图片的语义,让搜索更智能。
整体架构思路
经过一段时间的摸索,最终采用了分层架构设计:
API层 ← 对外接口,简洁易用
协调层 ← 编排各种算法模块
服务层 ← 图文匹配 + 重排序
数据层 ← 向量存储 + 用户反馈数据存储
为什么选择分层架构?
1. 职责清晰:每一层只关心自己的事情,降低耦合
2. 易于扩展:加新功能时只需要在对应层面改动
3. 便于测试:每层都可以独立测试和调试
4. 未来团队协作:不同人可以专注不同层面的开发
核心模块设计
图文匹配服务
这是算法实现的核心模块,负责理解用户查询并召回相关图片。
选择 CLIP[2] 模型主要是因为它能把文本和图片映射到同一个语义空间。我们用的是 OpenCLIP[3] 的 ViT-B-32,512 维向量输出。这个选择是经过权衡的:
• 效果足够好,能理解复杂的语义关系
• 推理速度快,满足线上服务需求
• 社区成熟,资料多坑少
向量存储用的 Milvus[4],主要看中它对大规模向量检索的优化。
重排序服务
图文匹配召回的结果虽然相关,但排序质量还有提升空间。这时候就需要重排序来精调。
我们采用 LTR[5] 的思路,基于用户的真实反馈来训练排序模型。整个设计围绕"用户反馈闭环"展开:
1. 反馈收集:用户每次搜索后的点击行为都会被记录下来
2. 训练数据构建:从点击行为中提取 pairwise 训练样本,被选中的图片作为正样本,未选中的作为负样本
3. 模型训练:使用神经网络学习用户偏好模式
4. 在线重排序:训练好的模型对搜索结果进行重新排序
这种设计的好处是能够持续学习用户偏好,越用越准。但也有挑战,比如冷启动问题、反馈噪声处理等。
搜索协调器
为了统一管理多个算法服务,我们设计了SearchCoordinator[6]。它的作用是编排整个搜索流程:
1. 调用图文匹配服务进行粗排召回
2. 如果启用了重排序,调用重排序服务进行细排
3. 返回最终的排序结果
这个设计让我们能够灵活控制算法流程,比如可以随时开关重排序功能,方便做A/B测试。
API设计理念
在接口设计上,我们遵循了 “对外简洁,对内丰富” 的原则,主要分为三类接口:
资源管理接口 (/v1/resource/)
• POST /add:添加单个资源
• POST /batch:批量添加资源
• POST /search:基于文本的语义搜索
• POST /match:基于图片的语义搜索
用户反馈接口 (/v1/feedback/)
• POST /submit:提交用户点击反馈
• GET /stats:查看反馈统计信息
• GET /recent:获取最近的反馈数据
• POST /train:触发模型训练
• GET /model/status:查看模型状态
• POST /model/enable|disable:启用/禁用重排序
内部调试接口 (/v1/internal/)
• 向量数据管理:查看、搜索向量数据
• 资源管理:列表查询、删除操作
• 数据库状态:详细的系统状态信息
这样设计的好处是业务方使用简单(只需要关心 resource 接口),反馈收集独立成模块(便于数据科学团队使用),同时我们内部有足够的工具来监控和调试系统。
踩过的坑和解决方案
SVG 处理问题
我们的图片库有很多 SVG 格式的图片,但 CLIP 模型只能处理位图。最初直接用 Pillow[7] 转换,发现效果很差—— SVG的矢量信息丢失了。
后来改用 CairoSVG[8] 来处理,质量明显提升。
这个经历让我意识到:看似简单的问题往往有很多细节。
向量存储优化
最初我们把所有向量数据都放在内存里,随着数据量增长很快就撑不住了。
迁移到 Milvus 后,发现索引类型的选择很关键。我们试了 IVF、HNSW[9] 等几种索引,最终选择 HNSW 是因为它在我们的数据规模下召回率和速度表现最均衡。
重排序特征设计
重排序模块我们改了好几版。最初用 10 维手工特征,包括余弦相似度、欧氏距离、向量模长等。后来发现 CLIP 向量已经归一化了,模长特征基本没用。
最终改成了神经网络方案,用 1500 多维的丰富特征,包括原始向量、统计特征等。虽然维度高了很多,但信息保留更完整。
配置管理混乱
项目初期配置管理比较随意,开发、测试、生产环境的配置经常搞混。后来统一用 dataclass[10] 重新设计了配置系统,按环境分离,清爽了很多。
一些工程实践心得
日志和监控
每个关键模块都加了详细的日志,包括:
• 请求响应时间
• 数据库操作耗时
• 搜索结果统计
• 异常情况记录
这些数据在出问题时特别有用,能快速定位根因。
错误处理
对于算法服务来说,容错很重要。我们的策略是:
• 单个服务出错不影响整体功能
• 重排序失败时返回粗排结果
• 向量服务异常时走降级逻辑
测试策略
除了常规的单元测试,我们还设计了端到端的功能测试。特别是对于机器学习模块,我们会构造一些已知结果的测试用例,确保算法逻辑正确。
未来的一些想法
1. 多模态融合:除了 CLIP,考虑加入其他视觉理解能力
2. 个性化搜索:结合用户历史行为做个性化排序
3. 特征学习:让模型能更好地适应用户偏好
4. 效果评估:建立更完善的线上效果评估体系
总结
做 SPX-Algorithm 这个项目最大的收获是:好的架构设计比炫酷的算法更重要。
技术选型要务实,不要为了用新技术而用新技术。我们选择 CLIP、Milvus 这些相对成熟的方案,主要考虑的是稳定性和可维护性。
工程实践要细致,很多看起来简单的问题往往有很多坑。配置管理、错误处理、日志监控这些"脏活累活"往往决定了系统能不能稳定运行。
最后,用户反馈闭环很重要。我们通过收集用户的真实行为数据来持续优化算法效果,这比闭门造车要有效得多。
希望这些分享对大家有用。如果有问题欢迎交流讨论!
引用链接
[1] SPX-Algorithm: https://github.com/goplus/builder/tree/trailhead_copilot/spx-algorithm
[2] CLIP: https://openai.com/index/clip/
[3] OpenCLIP: https://github.com/mlfoundations/open_clip
[4] Milvus: https://github.com/milvus-io/milvus
[5] LTR: https://en.wikipedia.org/wiki/Learning_to_rank
[6] SearchCoordinator: https://github.com/goplus/builder/blob/trailhead_copilot/spx-algorithm/coordinator/search_coordinator.py
[7] Pillow: https://github.com/python-pillow/Pillow
[8] CairoSVG: https://github.com/Kozea/CairoSVG
[9] HNSW: https://milvus.io/docs/zh/hnsw.md
[10] dataclass: https://github.com/goplus/builder/blob/trailhead_copilot/spx-algorithm/config/base.py