语义搜表情包背后的秘密:meme-search的pgvector向量搜索与嵌入模型实现原理详解
【免费下载链接】meme-searchThe open source Meme Search Engine and Finder. Free and built to self-host locally with Python, Ruby, and Docker.项目地址: https://gitcode.com/gh_mirrors/me/meme-search
meme-search 是一个开源、可免费自托管的表情包搜索引擎(Meme Search Engine),用 Python、Ruby 和 Docker 构建,能把你的表情包按“意思”搜出来。本文将带你弄懂它的语义搜索(向量搜索)是如何实现的:从图片描述生成、嵌入模型(embedding model),到 pgvector 向量数据库里的余弦距离排序,一条完整的链路讲清楚。
🖼️ 先看效果:一句话找到想要的表情包
在 meme-search 的 Web 界面中,搜索框输入 "bunny rabbit",即使你记得的不是原文而是“一只说 no 的兔子”这类大意,系统也能精准命中对应的图:
搜索结果返回的,就是下面这张“说 NO 的兔子”表情包(项目内置示例图):
同样的语义搜索能力也暴露给了本地命令行:通过 Search API v1 搜索后可以直接把图片抓取到本地文件:
那么问题来了:为什么输入的词和描述里的词不完全一样,也能搜到?答案藏在“嵌入模型 + pgvector”这两个关键词里。
🔍 为什么语义搜索比关键词搜索更懂你
meme-search 的搜索服务 image_search_query.rb 同时支持两种模式:
- 关键词模式(keyword):先去除 "the、a、is" 等停用词,再按字面匹配描述文本。简单直接,但换个说法就搜不到。
- 向量模式(vector):把你的查询词和每张表情包的描述都转换成一组数字(向量),然后在数学空间里找“距离最近”的描述。意思相近 = 距离近,所以"兔子说 no"能命中 "bunny rabbit saying the word 'no'"。
这个“把文字变成数字”的过程就叫嵌入(embedding),而负责高速计算向量距离的,是 PostgreSQL 的扩展pgvector。
⚙️ meme-search 向量搜索的完整流水线
整条链路可以拆成四步,全部在本地运行(嵌入与搜索 100% 本地完成)。
第一步:图生文——先让 AI “看懂”图片
表情包本身没法直接嵌入,meme-search 先用一个 Python 服务 image_to_text_generator 把图片转成文字描述。默认使用微软的 Florence-2-base 视觉语言模型,也支持 SmolVLM、Moondream2 等多种尺寸可选;如果你愿意,还可以换成 OpenAI 兼容的视觉 API 来生成描述,但嵌入和搜索仍然留在本地。
这一步的产出——一段自然语言描述(存在image_to_texts表)——就是后续语义搜索的“原料”。
第二步:嵌入模型——把描述变成 384 个数字
Rails 应用启动时,会在初始化器 model_setup.rb 中通过 Informers gem 加载嵌入模型:
$embedding_model = Informers.pipeline("embedding", "sentence-transformers/all-MiniLM-L6-v2")这是一个只有 22M 参数的轻量级 sentence-transformers 模型,能在普通 CPU 上快速把一句话压缩成384 维向量。核心逻辑在模型类 image_embedding.rb:
has_neighbors :embedding before_save :compute_embedding, if: -> { embedding.nil? } def compute_embedding self.embedding = $embedding_model.call(self.snippet) end也就是说:每条描述(snippet)在保存前自动调用嵌入模型,生成 384 维向量并随记录落库。
第三步:pgvector——向量直接住进 Postgres
meme-search 没有引入独立的向量数据库,向量就存在 Postgres 里。迁移文件 20241021223150_install_neighbor_vector.rb 只有一行关键操作:启用vector扩展,再配合 neighbor_vector 提供的has_neighbors声明,image_embeddings.embedding列就变成了一个支持近似最近邻查询的向量列。
一个数据库同时管“关系数据 + 向量检索”,部署和运维都省心,这正是自托管场景下的优雅选择。
第四步:搜索时——谁离得近,谁排前面
当你切到向量模式搜索时,image_search_query.rb 会:
- 为你的查询词临时创建一个
ImageEmbedding记录并算出它的向量; - 调用
nearest_neighbors(:embedding, distance: "cosine"),用余弦距离(衡量方向相近程度,天然适合文本语义)从所有描述向量中找最接近的; - 支持叠加标签(tag)过滤,默认返回 10 条、上限 20 条。
余弦距离越小 = 语义越接近,排序也就越靠前——这就是“背后的秘密”:没有魔法,只有向量空间里的距离计算。
🚀 如何快速上手 meme-search 向量搜索
本地部署只需 Docker:
git clone https://gitcode.com/gh_mirrors/me/meme-search cd meme-search docker compose up打开http://localhost:3000,拖拽或粘贴表情包完成索引,然后在搜索页把开关从 keyword 拨到 vector 即可体验语义搜索。命令行和脚本集成则通过 Search API v1 的mode=vector参数实现(Token 鉴权、60 次/分钟限流),完整接口定义见 search-api-openapi.yml。
📁 核心源码导航
| 模块 | 文件路径 |
|---|---|
| 嵌入模型加载 | meme_search/meme_search_app/config/initializers/model_setup.rb |
| 向量模型与余弦检索 | meme_search/meme_search_app/app/models/image_embedding.rb |
| 双模式搜索服务 | meme_search/meme_search_app/app/services/image_search_query.rb |
| pgvector 扩展迁移 | meme_search/meme_search_app/db/migrate/20241021223150_install_neighbor_vector.rb |
| 图生文 Python 服务 | meme_search/image_to_text_generator/ |
| Search API 文档 | docs/search-api.md |
小结
meme-search 的语义搜索链路非常干净:图片 → 本地视觉模型生成描述 → all-MiniLM-L6-v2 生成 384 维向量 → pgvector 按余弦距离排序。全程本地、免费、可自托管,没有额外组件。理解了这套“嵌入 + pgvector”的组合拳,你也能给自己的图片库、笔记库套上同样的语义搜索能力。
【免费下载链接】meme-searchThe open source Meme Search Engine and Finder. Free and built to self-host locally with Python, Ruby, and Docker.项目地址: https://gitcode.com/gh_mirrors/me/meme-search
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考