智能同义词处理与命中优化:提升知识库查询精度

效果展示(环境依赖请参看上一篇文章):

qa.json(示例知识):

测试结果:

引言

在构建智能问答系统时,常常遇到用户提问方式多样化的问题。即使问题本质相同,表达方式可能千差万别。为了提高搜索和匹配的准确性,我们需要对原始问题进行扩展,即生成多个同义表达。这有助于增强向量检索的召回能力,从而提升问答系统的性能。

Demo的目标是基于 Hugging Face 预训练模型和 FAISS 向量数据库,实现高效的知识检索,支持对用户问题的同义扩展,并通过向量搜索提供最佳答案。

问题分析

在传统的基于关键字匹配的问答系统中,用户的查询必须与数据库中的问题文本完全匹配,否则无法找到答案。这样的方法存在以下缺陷:

  1. 用户表达多样性:相同的问题可以有多种不同的提问方式,例如“天气怎么样?”与“今天的天气如何?”
  2. 查询灵活性不足:固定的问答库难以覆盖所有可能的提问方式,导致问答系统容易失配。
  3. 检索准确度低:单纯的关键字匹配容易受到语序、停用词等因素影响。

为了解决这些问题,我们需要构建一个基于语义的查询扩展和高效的向量检索系统。

解决方案

本项目采用以下方法来优化问答系统的性能:

  1. 生成同义表达
    • 使用 Hugging Face 预训练的大模型(Qwen1.5-1.8B)生成不同的问法。
    • 通过 prompt 设定规则,确保生成的问法保持原意但表达不同。
  2. 构建向量数据库
    • 采用 BAAI/bge-large-zh 作为嵌入模型,将问题转换为向量。
    • 使用 FAISS 存储和索引这些向量,提高检索效率。
  3. 语义匹配优化
    • 通过向量相似度搜索,而非简单的关键字匹配,来提高查询命中率。
    • 设定合理的相似度阈值,减少无关答案。

为什么选择这些技术?

  1. Qwen1.5-1.8B 生成同义问法
    • 该模型在中文生成任务上表现优异,能够生成多样化但语义一致的问法。
    • 通过温度调整提高生成质量,保证问题扩展的合理性。
  2. BAAI/bge-large-zh 作为嵌入模型
    • 专为中文优化,相较于通用的嵌入模型效果更好。
    • 支持归一化嵌入,提高向量检索的精确度。
  3. FAISS 作为向量存储与检索工具
    • 高效支持海量数据的相似性搜索。
    • 通过索引优化,提高查询速度。

替换方案

对于不同的应用场景,可以考虑如下替换方案:

  • 数据存储替换
    • 如果 JSON 存储方式不适用,可替换为数据库(如 MySQL, PostgreSQL, MongoDB)。
  • 模型替换
    • 可替换为更轻量级的 Qwen 模型(如 Qwen1.5-0.5B)以适应资源受限环境。
    • 也可以用 ChatGLM、GPT-4 等大模型,根据任务需求选择合适的生成模型。
  • 嵌入方法替换
    • 可使用 OpenAI 的 text-embedding-ada-002 进行文本向量化。
    • 可选用 Sentence-BERT 等其他嵌入模型。

代码实现(包含详细注释)

import os
import json
import torch
import logging
import re
from datetime import datetime
from transformers import pipeline
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.schema import Document

# 设置日志
logging.basicConfig(filename='retrieval.log', level=logging.INFO)

# 认证和环境配置
os.environ["HF_TOKEN"] = "you_huggingface_token"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 读取 JSON 数据,避免损坏
def load_json_safe(file_path):
    """安全加载 JSON 文件,避免文件损坏导致崩溃"""
    if not os.path.exists(file_path):
        return {}

    try:
        with open(file_path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        print(f"⚠️ 文件 {file_path} 解析失败,已重置为空!")
        return {}

qa_data = load_json_safe("qa.json")

# 备份原始 JSON,防止数据丢失
synonyms_cache_file = "synonyms_cache.json"
synonyms_cache = load_json_safe(synonyms_cache_file)

# 使用 Hugging Face 生成同义扩展
generator = pipeline("text-generation", model="Qwen/Qwen1.5-1.8B", device=0)

def generate_synonyms(question, num_variations=10):

    # 优化后的生成提示词
    prompt = (
        f"请为以下问题生成 {num_variations} 个不同的问法,但要保持意思不变:\n"
        f"- 只调整句式,不改变核心含义\n"
        f"- 必须保留原问题的核心主题\n"
        f"- 直接返回问法,每行一个\n\n"
        f"原问题:{question}\n"
    )

    # 调整生成参数
    response = generator(
        prompt,
        max_new_tokens=200,
        do_sample=True,  # 启用采样模式
        temperature=0.5,  # 提高创造性
        num_return_sequences=3  # 生成多组结果增加多样性
    )

    # 合并多组结果并去重
    raw_output = " ".join([res["generated_text"] for res in response])
    raw_output = raw_output.replace(prompt, "").strip()

    # 强化过滤规则
    synonyms = list(set(
        q[1].strip().rstrip("??") + "?"  # 只取匹配到的第二个元素(实际的问题)
        for q in re.findall(r"(\d+\.\s*)?(.+?)", raw_output)
        if len(q[1]) > 3 and not any(w in q[1] for w in ["注意事项", "常用语", "技巧"])
    ))

    print(f"\n🔹【原问题】{question}\n🔹【同义扩展】{synonyms}\n")  # ✅ 打印同义扩展

    return synonyms[:num_variations]  # 确保返回数量准确

# 生成所有问题的扩展
question_expansions = {q: generate_synonyms(q) for q in qa_data.keys()}

# **保存到 JSON 文件**
with open("synonyms_cache.json", "w", encoding="utf-8") as f:
    json.dump(question_expansions, f, ensure_ascii=False, indent=4)

print("✅ 同义扩展数据已保存到 synonyms_cache.json")

# 构建向量数据库
def create_documents(qa_data, question_expansions):
    documents = []
    for question, answer in qa_data.items():
        # 确保答案格式统一
        answer = answer[0] if isinstance(answer, list) else answer

        # 添加原始问题(重要!)
        documents.append(Document(
            page_content=question.rstrip("??") + "?",  # 统一问号
            metadata={"answer": answer}
        ))

        # 添加扩展问题
        for syn in question_expansions.get(question, []):
            syn = syn.rstrip("??") + "?"
            documents.append(Document(
                page_content=syn,
                metadata={"answer": answer}
            ))

    return documents

documents = create_documents(qa_data, question_expansions)

# 选择适合中文的嵌入模型
model_name = "BAAI/bge-large-zh"
embeddings = HuggingFaceEmbeddings(
    model_name=model_name,
    model_kwargs={"device": device},
    encode_kwargs={'normalize_embeddings': True}
)

vectorstore = FAISS.from_documents(documents, embeddings)

# 查询函数
def query_ai_knowledge_base(query):
    # 预处理查询语句
    processed_query = query.strip().rstrip("??") + "?"

    # 放宽阈值并增加返回数量
    similar_docs = vectorstore.similarity_search_with_relevance_scores(
        processed_query,
        k=10,  # 扩大搜索范围
        score_threshold=0.3  # 降低阈值
    )

    # 加权得分排序
    answers = {}
    for doc, score in similar_docs:
        ans = doc.metadata["answer"]
        answers[ans] = answers.get(ans, 0) + score

    # 选取最高分答案
    if not answers:
        return "未找到相关信息"

    best_answer = max(answers.items(), key=lambda x: x[1])[0]
    return best_answer

# 带日志记录的查询函数
def query_with_logging(query):
    result = query_ai_knowledge_base(query)
    logging.info(f"Query: {query} | Result: {result} | Timestamp: {datetime.now()}")
    return result

# 交互式测试查询
while True:
    user_query = input("\n请输入查询问题 (输入 'exit' 结束): ").strip()
    if user_query.lower() in ["退出", "q", "exit"]:
        print("🔚 退出查询")
        break

    print("回答:", query_with_logging(user_query))

结论

通过这个简单的示例,展示了通过智能生成同义问法、向量化查询和快速检索,提供相关的答案,极大地提升了查询效率和准确性。

如果你觉得这篇博客对你有帮助,别忘了点赞、收藏并转发给你的朋友!更多精彩内容,敬请关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值