distances值表示矢量数据库查询计算出的两个矢量的距离,**距离越小,表示语义最接近**。我们可以将distances值返回,用于人工审核的阈值筛选条件。比如distances` 值大于某个值的,需要人工审核一下。

查询测试:

search_words = ["葡萄", "葡萄干", "葡萄糖", "葡萄石"]
for search_word in search_words:
    print(f"{search_word} 属于分类:", search_similar_texts(search_word))

结果:

葡萄 属于分类:(['水果'], ['445652651623587475'], ['156.342529296875'])
葡萄干 属于分类:(['干果'], ['445652651623587474'], ['91.40153503417969'])
葡萄糖 属于分类:(['零食'], ['445652651623587478'], ['176.91494750976562'])
葡萄石 属于分类:(['宝石'], ['445652651623587476'], ['123.83802032470703'])

可以看到,即使是包含相同关键词的商品名,系统也能准确地将其分类到不同的品类中。

工程化完善

环境安装好,分步调试好,就可以组装了。接下来可能要完善的包括:

  • 商品分类元数据的更新
  • 查询邻近分类的 API 接口
  • 人工审核步骤(可选)
  • 集成到已有系统

总结与思考

对于特定场景问题,不一定非得上最优的大模型,只用 Embedding 模型就能解决。AI 应用落地的关键点在落地。只要能解决问题,方案应该尽可能简洁、便宜。

本文记录了使用语言 Embedding 模型和矢量数据库做商品分类查询的实现方式。相比于知识库问答,少了大语言模型(LLM)的使用,但在商品分类这个特定场景下,效果反而更好,成本更低。

核心要点

  1. 选择合适的工具:不是所有问题都需要 LLM,Embedding 模型在语义匹配场景下更高效
  2. 分步策略:面对大规模数据处理,采用分步迭代的方式更可靠
  3. 工程化思维:理论验证后,要考虑实际的工程落地和系统集成
  4. 成本控制:在没有 GPU 的内网环境下,依然可以实现高效的 AI 应用

如果你觉得这个方案有用,欢迎点赞转发。也欢迎大家找我索取 Embedding 分类离线安装包。


参考资源:

前言

在 AI 技术快速发展的今天,如何将 AI 能力真正落地到实际业务场景中,是每个技术从业者都在思考的问题。本文将分享一个真实的电商商品分类案例,看看如何用一个人在没有强大服务器支持的情况下,借助 Embedding 模型完成 30 万商品的智能分类。

背景与挑战

近期遇到一个做电商的朋友的需求:他们的电商平台上有几十万商品上千种商品品类。这些商品品类的划分数据来自多个电商平台,标准描述不统一,分类也常有错误。需要对所有商品品类做一个统一的梳理。

如果由人工完成这项工作,会非常耗时费力。期望借助 AI 的能力帮忙梳理已有商品品类的划分,并且对于新加入的商品,能自动为其分类。

限制条件

这个需求有几个关键限制:

  • 商品名主要是中文
  • 只能在内网使用
  • 没有性能强大的服务器(更别提 GPU)

传统的关键词匹配方案显然不合适。比如"葡萄"、"葡萄干"、"葡萄糖"、"葡萄石"就是四种完全不同的品类,关键词匹配无法区分语义差异。传统的 NLP 处理也有局限性,且需要重新训练模型。

解决思路

从 LLM 到 Embedding 的转变

最初想到的是微调一个 4bit 量化的中文 LLM(如 ChatGLM3),实现输入商品名返回商品二级和一级分类。但实测下来,4bit LLM 能力有限,输出的准确度和格式一致性不能保证,需要多次"炼丹",结果还不能保证达到预期效果。

换个思路,我们要解决传统关键字匹配的问题,本质上是语义的匹配。在知识库问答中用到的 Embeddings 不就是实现了语义匹配吗?商品分类需求中,并不涉及复杂的语言理解和逻辑推理,那其实可以不用 LLM,只需要 Embedding 模型就能实现

什么是 Embedding 模型?

Embedding 模型是一种广泛应用于自然语言处理(NLP)和计算机视觉(CV)等领域的机器学习模型,它可以将高维度的数据转化为低维度的嵌入空间(embedding space),并保留原始数据的特征和语义信息。

说人话:Embedding 模型就是把词或句用多维向量来表示,向量之间的距离表示语义的相近程度。向量之间的距离越短,表示语义越接近。

例如:

  • "土豆" → [0,1,2]
  • "马铃薯" → [0,1,1]
  • "土狗" → [1,0,0]

比较 [0,1,2] 与 [0,1,1] 的距离要小于 [0,1,2] 与 [1,0,0] 的距离,得出结论:"土豆"表达的意思与"马铃薯"更接近。

上面的例子只是一个 3 维向量,而实际可用的 Embedding 模型中,维度要大得多。比如 OpenAI 提供的 Embeddings 接口支持 1536 维度,开源的中文 Embeddings 中 m3e-base 支持 768 维度

技术方案选型

基于以上分析,我们需要以下服务:

  • Embedding 模型:选择 m3e-base
  • 本地向量数据库:选择 Milvus
  • 本地关系数据库:选

m3e-base 模型介绍

m3e-base 是 Moka Massive Mixed Embedding 的缩写,具有以下特点:

核心特性

  • Moka:此模型由 MokaAI 训练,开源和评测,训练脚本使用 uniem,评测 BenchMark 使用 MTEB-zh
  • Massive:此模型通过千万级(2200w+)的中文句对数据集进行训练
  • Mixed:此模型支持中英双语的同质文本相似度计算,异质文本检索等功能,未来还会支持代码检索
  • Embedding:此模型是文本嵌入模型,可以将自然语言转换成稠密的向量

模型对比

模型 参数数量 维度 中文 英文 s2s Acc s2p ndcg@10
m3e-small 24M 512 0.5834 0.7262
m3e-base 110M 768 0.6157 0.8004
text2vec 110M 768 0.5755 0.6346
openai-ada-002 未知 1536 0.5956 0.7786

说明:

  • s2s (sentence to sentence):代表同质文本之间的嵌入能力,适用任务包括文本相似度、重复问题检测、文本分类等
  • s2p (sentence to passage):代表异质文本之间的嵌入能力,适用任务包括文本检索、GPT 记忆模块等

从评测结果来看,m3e-base 在中文文本分类和检索排序任务上都超过了 openai-ada-002,非常适合中文场景的应用。

实现流程

下面是整个系统的技术流程图:

文本向量化流程示意图

如图所示,整个流程分为以下几个步骤:

  1. 左侧储存内容为'土豆、土狗',查询内容为'马铃薯'
  2. 中间部分通过'm3e-base'模块进行'text to vector'(文本转向量)操作,生成如 [0,1,2]、[0,1,0]、[0,1,1] 等向量
  3. 这些向量流向'Milvus'数据库,该数据库存储有 a,[0,1,2] 和 b,[0,1,0]
  4. 通过查询获取最邻近矢量 a,[0,1,2]
  5. 最终指向'MySQL'数据库,其中包含 a,土豆和 b,土狗的内容

具体实现步骤

1. 准备标准的商品分类

商品分类的元数据可以从已有的商品分类中提炼,也可借助于 AI 生成新的。

由于 AI 推理有一定局限性和输出 token 限制,不要一次性让它生成所有的商品分类。采取分步策略,可以获得更好的结果:

  1. 先生成一级目录
  2. 再一个个生成二级目录
  3. 再生成对应的三级目录

这个过程循环操作并记录,理论上就能得到一个全新的、覆盖面较全的商品分类元数据了。

# 第一步:生成二级目录
def generate_second_level_categories(first_level_category):
    prompt = f"一级目录:{first_level_category};"
    response = call_openai_api(prompt)
    second_level_categories = response.split(",")
    return second_level_categories

# 第二步:生成三级目录
def generate_third_level_categories(first_level_category, second_level_category):
    prompt = f"一级目录:{first_level_category}; 二级目录:{second_level_category};"
    response = call_openai_api(prompt)
    third_level_categories = response.split(",")
    return third_level_categories

2. 商品目录存入矢量数据库

使用 m3e-base 模型来做中文 Embedding,将三级商品名转换成向量。选用 Milvus 来做矢量数据库,储存转换好的向量。另外,还需要在 MySQL 中储存向量 Index 与对应的三级商品名。

2.1 m3e-base 安装使用

pip install -U sentence-transformers

初次运行时会联网下载 Model,耗时较长。如果需要,可准备离线包。

准备 text_to_vector 方法:

from sentence_transformers import SentenceTransformer

# Initialize the model
model = SentenceTransformer('moka-ai/m3e-base')

def text_to_vector(text):
    embedding = model.encode([text])
    return embedding[0]

2.2 Milvus 安装使用

最方便的方式是用 Docker 来启动 Milvus 服务:

wget https://github.com/milvus-io/milvus/releases/download/v2.3.3/milvus-standalone-docker-compose.yml -O docker-compose.yml
sudo docker-compose up -d

安装必要的 Python 库:

pip install pyarrow pymysql pymilvus milvus

矢量数据库 Milvus 准备好后,就可以将 text_to_vector 转换后的矢量存进去,然后创建 index,把 index 对应的 text 也记得存入 MySQL。

3. 查询数据库

查询的过程:文本先通过 text_to_vector 转换为矢量,然后在 Milvus 里查询最邻近的值,拿出 Index,再根据这个 Index 到 MySQL 里查询对应的文本。

以下是查询方法:

def search_similar_texts(query_text):
    connections, db = establish_connections()
    try:
        query_vector = text_to_vector(query_text)
        collection = Collection("text_search_collection")
        collection.load()
        search_params = {"metric_type": "L2", "params": {"nprobe": 16}}
        results = collection.search([query_vector], "embedding", search_params, limit=1)
        ids = [str(result.id) for result in results[0]]
        distances = [str(result.distance) for result in results[0]]
        
        cursor = db.cursor()
        query = f"SELECT text FROM text_table WHERE text_id IN ({','.join(['%s'] * len(ids))})"
        cursor.execute(query, tuple(ids))
        similar_texts = [text for text, in cursor.fetchall()]
        
        return similar_texts, ids, distances
    finally:
        close_connections(connections, db)

代码中的 distances 值表示矢量数据库查询计算出的两个矢量的距离,距离越小,表示语义最接近。我们可以将 distances 值返回,用于人工审核的阈值筛选条件。比如 distances 值大于某个值的,需要人工审核一下。

查询测试:

search_words = ["葡萄", "葡萄干", "葡萄糖", "葡萄石"]
for search_word in search_words:
    print(f"{search_word} 属于分类:", search_similar_texts(search_word))

结果:

葡萄 属于分类:(['水果'], ['445652651623587475'], ['156.342529296875'])
葡萄干 属于分类:(['干果'], ['445652651623587474'], ['91.40153503417969'])
葡萄糖 属于分类:(['零食'], ['445652651623587478'], ['176.91494750976562'])
葡萄石 属于分类:(['宝石'], ['445652651623587476'], ['123.83802032470703'])

可以看到,即使是包含相同关键词的商品名,系统也能准确地将其分类到不同的品类中。


参考资源:

择 MySQL