从「关键词匹配」到「语义理解」:我是如何用 Embedding 让搜索「听懂人话」的?

大家好,我是掘金的一位创作者FogLetter。今天想和大家聊聊一个看似枯燥、实则非常有趣的技术------Embedding 向量化搜索

如果你也曾经为「搜索功能不够智能」而头疼,或者好奇为什么现在的搜索引擎能「猜」到你想要什么,那么这篇笔记一定会让你有所收获。


一、从「机械匹配」到「语义理解」的进化

还记得早期的搜索功能吗?用户输入「React TailwindCSS」,系统只会机械地匹配包含这两个词的标题。如果一篇文章标题是《使用 Next.js 和 Tailwind CSS 构建博客》,尽管内容高度相关,仅仅因为没有出现「React」这个词,就会被系统无情地过滤掉。

这就是传统关键词匹配的局限性------它不懂语义,只认识字面。

而现代的语义搜索,已经能够理解「React」和「Next.js」的关联,知道「TailwindCSS」是一种 CSS 框架。即使标题中没有完全匹配的关键词,只要内容语义相关,就能被找出来。

这背后的魔法,就是 Embedding。


二、Embedding:把文字变成向量的「翻译官」

什么是 Embedding?

简单来说,Embedding 是一种将文本(词、句子、段落)转换成高维向量的技术。这个向量,可以理解为文本在数学空间中的「坐标」。

我用的是 OpenAI 的 text-embedding-ada-002 模型,它会将任何文本转换成 1536 维的向量。是的,1536 个数字代表一段文本!

为什么需要向量化?

因为计算机不理解文字,但理解数字。通过把文本转换成向量,我们就能用数学方法计算文本之间的相似度。

javascript 复制代码
// 把文本变成向量
const res = await client.embeddings.create({
    model: 'text-embedding-ada-002',
    input: 'React,TailwindCSS'
});
const { embedding } = res.data[0]; // 得到1536维的向量

现在,「React」不再只是6个字母,而是1536个数字组成的数学表示。更重要的是,语义相似的文本,它们的向量在空间中的位置也很接近。


三、余弦相似度:衡量「心灵相通」的数学公式

有了向量,如何判断两段文本是否相似呢?这就需要余弦相似度出场了。

余弦相似度通过计算两个向量之间的夹角余弦值,来判断它们的相似程度。值越接近1,说明越相似;越接近0,说明越不相关。

javascript 复制代码
// 计算余弦相似度
export const cosineSimilarity = (v1, v2) => {
    const dotProduct = v1.reduce((acc, curr, i) => acc + curr * v2[i], 0);
    const lengthV1 = Math.sqrt(v1.reduce((acc, curr) => acc + curr * curr, 0));
    const lengthV2 = Math.sqrt(v2.reduce((acc, curr) => acc + curr * curr, 0));
    return dotProduct / (lengthV1 * lengthV2);
};

这个公式的美妙之处在于,它不受向量长度影响,只关注方向。就像比较两个人的思维方式是否相似,而不在乎他们思考的深度。


四、实战:构建智能搜索系统

第一步:建立向量数据库

首先,我需要把所有文章的标题和分类都转换成向量,建立自己的「向量数据库」:

javascript 复制代码
const postsWithEmbedding = [];

for (const { title, category } of posts) {
    const res = await client.embeddings.create({
        model: 'text-embedding-ada-002',
        input: `标题:${title}; 分类:${category}`
    });
    postsWithEmbedding.push({
        title,
        category,
        embedding: res.data[0].embedding
    });
}

// 保存到文件,避免重复计算
await fs.writeFile('./data/posts_with_embedding.json', 
    JSON.stringify(postsWithEmbedding, null, 2));

这个过程虽然耗时,但一劳永逸。建立好的向量数据库可以反复使用。

第二步:实时语义搜索

当用户输入查询时:

javascript 复制代码
// 1. 把查询词转换成向量
const query = 'react,tailwindcss';
const res = await client.embeddings.create({
    model: 'text-embedding-ada-002',
    input: query
});
const { embedding } = res.data[0];

// 2. 与数据库中所有向量计算相似度
const results = posts.map(item => ({
    ...item,
    similarity: cosineSimilarity(embedding, item.embedding)
}))
// 3. 按相似度排序,取前3名
.sort((a, b) => a.similarity - b.similarity)
.reverse()
.slice(0, 3);

console.log(`与「${query}」最相关的内容:`);
results.forEach((item, index) => {
    console.log(`${index + 1}. ${item.title}, ${item.category}`);
});

五、效果对比:传统搜索 vs 语义搜索

让我用实际数据展示两者的差异:

传统关键词搜索「react,tailwindcss」可能返回:

  1. 《如何在 React 中使用 Tailwind CSS 实现 Material Design 风格》
  2. 《使用 Next.js 和 Tailwind CSS 构建一个简单的博客应用》

语义搜索「react,tailwindcss」返回:

  1. 《如何在 React 中使用 Tailwind CSS 实现 Material Design 风格》
  2. 《使用 Next.js 和 Tailwind CSS 构建一个简单的博客应用》
  3. 《使用 Tailwind CSS 和 Alpine.js 构建一个简单的交互式表单》
  4. 《如何使用 Vue.js 和 Tailwind CSS 创建响应式布局》

看到区别了吗?语义搜索找到了更多相关的内容,即使它们没有完全包含查询关键词。


六、为什么这很重要?创作角度的思考

作为内容创作者,我深刻理解读者找不到相关内容的痛苦。很多时候,读者并不知道准确的关键词,他们只能用自己的语言描述需求。

语义搜索的价值在于:

  • 理解用户意图:即使查询不准确,也能找到相关内容
  • 发现隐性关联:识别内容之间的深层联系
  • 提升用户体验:减少「搜索结果为空」的挫败感

这就像有一个懂技术的朋友在帮你找资料,他理解你的需求,而不只是机械地匹配关键词。


七、进阶思考:向量的奇妙特性

Embedding 最神奇的地方不止于相似度计算。由于向量在空间中具有数学关系,我们还可以做很多有趣的事情:

  1. 词义推理国王 - 男人 + 女人 ≈ 女王
  2. 主题聚类:通过向量距离自动给文章分类
  3. 内容推荐:找到语义相似的文章推荐给读者

这些能力,让搜索从「工具」变成了「智能助手」。


八、性能考量:大数据下的优化

你可能会问:每次搜索都要和所有向量计算相似度,性能不会很差吗?

确实,如果数据量很大,需要优化策略:

  • 使用专业的向量数据库(如 Pinecone、Chroma、Supabase)
  • 采用近似最近邻搜索算法
  • 对向量进行索引和分片

但对于大多数应用场景,几千到几万条数据的情况下,直接计算是完全可行的。


结语:技术的温度

技术本身是冰冷的,但当我们用它来解决真实问题时,它就变得有温度了。

通过 Embedding 和语义搜索,我让我的博客、我的项目能够更好地理解读者和用户的需求。这种「被理解」的体验,正是技术最有价值的地方。

下次当你实现搜索功能时,不妨考虑一下:是继续做机械的关键词匹配,还是升级到能够「听懂人话」的语义搜索?

毕竟,在这个信息过载的时代,帮助用户快速找到他们真正需要的内容,就是我们作为开发者能够提供的最好的用户体验。

相关推荐
进阶的鱼4 小时前
React+ts+vite脚手架搭建(四)【mock篇】
前端·javascript·react.js
Jagger_4 小时前
Scrum敏捷开发流程规范
前端·后端
Value_Think_Power4 小时前
变量->约束->目标
前端
开源框架4 小时前
招商银行模拟器app,网银模拟生成器,jar+c++组合模板
前端
日月之行_4 小时前
React 19.2正式发布啦!
前端
奔赴_向往4 小时前
抛弃虚拟DOM:Vue Vapor如何实现性能飞跃?
前端
尘叶心简4 小时前
LangGraph实现自适应RAGAgent
aigc·openai
小高0074 小时前
🤔Proxy 到底比 defineProperty 强在哪?为什么今天还在聊 Proxy?
前端·javascript·vue.js
FogLetter4 小时前
从“满嘴跑火车”到“有据可依”:给大模型配个“外部硬盘”RAG
aigc·openai