随着人工智能技术的飞速发展,简历解析已不再局限于简单的关键词匹配,而是需要理解上下文、技能以及细微的经验差异。检索增强生成(RAG)与大型语言模型(LLM)的结合,为简历处理带来了变革性的方法。而这项技术的基石,便是向量搜索,选择正确的索引方式,直接决定了应用的效果。在众多向量索引算法中,分层导航小世界(HNSW)索引和倒排文件索引(IVF)是两种常用的方法。本文将深入探讨这两种方法在基于RAG简历提取任务中的优劣,通过实际代码案例,展示为什么HNSW能提供更准确、更快速的结果,从而提升整体的简历智能水平。

简历智能的核心:RAG与向量搜索

在传统的简历解析流程中,依赖于复杂的规则引擎和大量的正则表达式来提取信息,这种方法不仅耗时,而且难以应对简历格式的多样性。RAG的出现,使得我们可以将简历内容进行分块和嵌入,然后存储在向量索引中,在查询时检索最佳的块。这种方法的核心优势在于它能够理解语义,而不仅仅是匹配关键词。例如,我们可以提出这样的问题:“Aditya在使用RAG进行处方扫描方面的经验是什么?”。

为了实现高效的简历智能,一个理想的RAG系统需要具备以下特性:

  • 高召回率:确保不会遗漏任何关键技能。
  • 低延迟:满足招聘人员实时仪表板的需求。
  • 动态更新:能够每日处理新增的简历

代码实战:基于HNSW的简历提取

为了更好地理解HNSW简历提取中的应用,我们可以通过一个实际的代码示例进行说明。以下步骤展示了如何使用HNSW进行简历信息的提取:

  1. 安装依赖:首先,安装必要的Python库,包括pdfplumber(用于解析PDF简历)、sentence-transformers(用于生成嵌入向量)、hnswlib(用于构建HNSW索引)和faiss-cpu(用于对比IVF索引)。

    pip install pdfplumber sentence-transformers hnswlib faiss-cpu
    
  2. 递归分块和嵌入:将PDF简历的内容分割成较小的文本块,并使用sentence-transformers模型将这些文本块转换为向量表示。递归分块的目的是为了处理长文本,并确保每个块都包含足够的上下文信息。

    from sentence_transformers import SentenceTransformer
    import numpy as np
    import pdfplumber
    import re
    import os
    import hnswlib
    import json
    
    # 递归分块
    def recursive_chunk(text, max_length=300, overlap=50):
        words = text.split()
        chunks = []
        start = 0
        while start < len(words):
            end = min(start + max_length, len(words))
            chunk = " ".join(words[start:end])
            chunks.append(chunk)
            start += max_length - overlap
        return chunks
    
    def extract_chunks_from_pdf(pdf_path):
        all_chunks = []
        with pdfplumber.open(pdf_path) as pdf:
            for page_num, page in enumerate(pdf.pages):
                text = page.extract_text()
                if text:
                    paragraphs = re.split(r'\n{2,}|\.\s+', text)
                    for para in paragraphs:
                        clean_para = para.strip()
                        if len(clean_para) > 50:
                            chunks = recursive_chunk(clean_para)
                            for chunk in chunks:
                                all_chunks.append({"text": chunk, "page": page_num + 1})
        return all_chunks
    
  3. 构建或加载HNSW向量索引:创建一个HNSW索引,并将简历文本块的嵌入向量添加到索引中。为了提高效率,我们可以选择将索引保存到磁盘,并在下次使用时直接加载。

    def build_or_load_hnsw(chunks, embedding_model, hnsw_path='vector_db'):
        dim = 768
        if os.path.exists(f'{hnsw_path}.bin') and os.path.exists(f'{hnsw_path}_meta.json'):
            print("✅ Loading existing vector DB...")
            index = hnswlib.Index(space='l2', dim=dim)
            index.load_index(f'{hnsw_path}.bin')
            with open(f'{hnsw_path}_meta.json', 'r') as f:
                metadata = json.load(f)
            return index, metadata
        else:
            print("🛠️ Building new vector DB...")
            texts = [c["text"] for c in chunks]
            embeddings = embedding_model.encode(texts, normalize_embeddings=True)
            index = hnswlib.Index(space='l2', dim=dim)
            index.init_index(max_elements=len(texts), ef_construction=100, M=16)
            index.add_items(embeddings)
            index.save_index(f'{hnsw_path}.bin')
            with open(f'{hnsw_path}_meta.json', 'w') as f:
                json.dump(chunks, f)
            return index, chunks
    
  4. 使用HNSW进行查询:根据用户提出的问题,将问题转换为向量表示,并在HNSW索引中查找最相关的文本块。

    def hnsw_search(query, model, index, metadata, top_k=3):
        q_vec = model.encode([query], normalize_embeddings=True)
        labels, distances = index.knn_query(q_vec, k=top_k)
        results = []
        for rank, i in enumerate(labels[0]):
            similarity = 1 - distances[0][rank]
            results.append((metadata[i], similarity))
        return results
    
  5. 查询简历:将上述步骤整合在一起,完成简历的查询。

    pdf_path = "Aditya_Mangal_Resume_2.pdf"
    query = "what is project of prescription scanning with LLM and RAG?"
    model = SentenceTransformer('all-mpnet-base-v2')
    chunks = extract_chunks_from_pdf(pdf_path)
    index, metadata = build_or_load_hnsw(chunks, model)
    for result, score in hnsw_search(query, model, index, metadata):
        print(f"\n[Similarity: {score:.4f}] Page {result['page']}\n{result['text'][:300]}...")
    

IVF的替代方案及其局限性

倒排文件索引(IVF)是另一种常用的向量索引方法。与HNSW不同,IVF通过聚类将向量空间划分为多个区域,并在查询时只搜索相关的区域。虽然IVF在某些情况下也能取得不错的效果,但它在处理动态数据和高召回率方面存在一定的局限性。

import faiss

texts = [c["text"] for c in chunks]
embeddings = model.encode(texts, normalize_embeddings=True)
embeddings = np.array(embeddings).astype('float32')
dim = embeddings.shape[1]
quantizer = faiss.IndexFlatL2(dim)
ivf = faiss.IndexIVFFlat(quantizer, dim, 5)
ivf.train(embeddings)
ivf.add(embeddings)
ivf.nprobe = 2
q_vec = model.encode([query], normalize_embeddings=True)
q_vec = np.array(q_vec).astype('float32')
D, I = ivf.search(q_vec, k=3)
print("\n🔍 IVF Results with Similarity Scores:")
scores = 1 / (1 + D[0])  # Inverse L2 to get similarity-like scores
total_score = np.sum(scores)
for rank, idx in enumerate(I[0]):
    probability = scores[rank] / total_score  # Normalized to sum to 1
    print(f"\n[Prob: {probability:.4f}] Page {chunks[idx]['page']}")
    print(chunks[idx]['text'][:300] + "...")

结果分析与对比:HNSW vs IVF

从代码示例的结果可以看出,HNSWIVF都能成功检索到与“使用RAG进行处方扫描”相关的文本块。然而,它们的方法和评分方式有所不同:

  • HNSW返回最相关的文本块,并提供绝对相似度分数,通常具有较高的召回率,尤其是在大型数据集中。
  • IVF提供相对概率分数,这些分数分布良好,但仅限于其搜索的聚类。当nprobe设置得当(即搜索的聚类数量)时,IVF表现得很有竞争力。

在小型数据集中,IVF可能看起来与HNSW相当。但是,在实际的大规模简历处理流程中,HNSW在召回率和适应性方面始终优于IVF

此外,HNSW支持动态的简历摄取,而无需重新训练索引。相比之下,IVF需要重新聚类,这在处理不断变化的数据时是一个显著的缺点。

深入理解HNSW的优势

HNSW(Hierarchical Navigable Small World)是一种基于图的近似最近邻搜索算法。它通过构建一个多层图结构来加速搜索过程。最上层图的节点稀疏,用于快速定位到目标区域;下层图的节点稠密,用于在目标区域内进行精确搜索。HNSW的优势在于:

  • 高效率:在保证搜索精度的前提下,能够显著降低搜索时间。
  • 可扩展性:可以处理大规模的数据集。
  • 动态性:支持动态添加和删除节点,无需重新构建索引。
  • 参数可调:可以通过调整参数来平衡搜索精度和搜索时间。

HNSW算法的核心思想是构建一个多层图结构。每一层都是一个图,图中的节点表示向量,节点之间的边表示向量之间的连接关系。最上层图的节点数量最少,节点之间的连接关系也最稀疏。随着层数的增加,节点数量逐渐增加,节点之间的连接关系也逐渐稠密。

在进行搜索时,首先从最上层图开始,找到与查询向量最接近的节点。然后,沿着该节点所在的边,向下层图进行搜索,找到更接近的节点。重复这个过程,直到到达最底层图。在最底层图中,找到与查询向量最接近的若干个节点,作为搜索结果。

实际应用案例:提升招聘效率

假设一家大型企业每天收到数千份简历,招聘人员需要快速筛选出符合职位要求的候选人。如果使用传统的简历解析方法,不仅效率低下,而且容易遗漏潜在的优秀人才。通过采用基于HNSWRAG系统,招聘人员可以:

  • 快速检索:在几毫秒内找到与职位描述相关的简历
  • 语义理解:理解简历中的技能和经验,而不仅仅是匹配关键词。
  • 个性化推荐:根据招聘人员的偏好,推荐合适的候选人。

例如,招聘人员可以输入以下问题:“寻找具有三年以上Python开发经验,熟悉机器学习算法的候选人。” RAG系统会利用HNSW索引,快速找到满足这些条件的简历,并按照相关性进行排序。招聘人员可以直接查看简历摘要,并选择合适的候选人进行面试。

向量索引的选择:HNSW的长期价值

尽管IVF在特定场景下也能提供一定的性能,但HNSW在实际的简历智能应用中表现出更强的适应性和长期价值。尤其是在数据规模持续增长,需要频繁更新索引的情况下,HNSW的优势更加明显。

选择HNSW作为向量索引,意味着:

  • 更快的响应速度:即使在处理数百万份简历时,也能保持低延迟。
  • 更高的准确率:能够找到与查询意图最相关的简历,提高招聘效率。
  • 更强的可维护性:支持动态更新,无需重新构建索引,降低维护成本。

结论:HNSW胜出,赋能简历智能

综上所述,对于基于RAG简历提取任务,HNSW在性能、可扩展性和动态性方面均优于IVF。虽然IVF在某些情况下也能取得不错的效果,但HNSW能够提供更准确、更快速的结果,从而提升整体的简历智能水平。选择HNSW,能够帮助企业更好地利用简历数据,提高招聘效率,找到最合适的候选人。最终,HNSW的胜出不仅体现在技术指标上,更体现在对企业人才战略的赋能上。