深度实战玩转算法 - 选择排序算法可视化

5 阅读6分钟

深度实战玩转算法

前言

在海量信息爆炸的今天,一个优秀的搜索系统不仅是“能搜到”,更是“猜得准”。从你在 Python 爬虫课程中抓取到的海量数据,到用户输入关键词获取精准结果,中间的桥梁就是搜索排序算法

对于算法工程师而言,构建搜索系统的核心难点在于处理“文本理解”与“用户意图匹配”的矛盾。本文将从基础的文本相似度计算,过渡到工业级的向量检索,带你深度实战搜索排序的核心算法。

一、 基础篇:基于 TF-IDF 的文本排序

TF-IDF(Term Frequency-Inverse Document Frequency)是搜索引擎的基石。它通过衡量一个词在文档中的重要程度,来计算文档与查询语句的相关性。

算法逻辑

  • TF (词频) :词在文章中出现得越多越重要。
  • IDF (逆文档频率) :词在所有文章中出现得越少越独特(比如“Python”比“是”更重要)。

代码实战:手写 TF-IDF 相似度排序

python

复制

import math
import jieba

class SimpleSearchEngine:
    def __init__(self):
        self.documents = []
        self.idf_cache = {}

    def add_document(self, doc_id, text):
        """添加文档到索引"""
        words = list(jieba.cut(text))
        self.documents.append({"id": doc_id, "text": text, "words": words})

    def compute_idf(self, query_word):
        """计算 IDF 值"""
        if query_word in self.idf_cache:
            return self.idf_cache[query_word]
        
        N = len(self.documents)
        # 包含该词的文档数
        df = sum(1 for doc in self.documents if query_word in doc["words"])
        
        if df == 0:
            idf = 0
        else:
            idf = math.log(N / df) + 1
            
        self.idf_cache[query_word] = idf
        return idf

    def search(self, query):
        """执行搜索并返回排序后的结果"""
        query_words = list(jieba.cut(query))
        scores = []

        for doc in self.documents:
            score = 0
            for word in query_words:
                # 计算 TF: 词在当前文档出现的频率
                tf = doc["words"].count(word) / len(doc["words"])
                # 计算 IDF
                idf = self.compute_idf(word)
                score += tf * idf
            
            scores.append({"id": doc["id"], "score": score, "text": doc["text"]})

        # 按照得分降序排序 (核心排序逻辑)
        ranked_results = sorted(scores, key=lambda x: x["score"], reverse=True)
        return ranked_results

# 模拟数据
engine = SimpleSearchEngine()
engine.add_document(1, "Python 是一种广泛使用的编程语言,适合数据分析和爬虫开发")
engine.add_document(2, "TensorFlow 是 Google 开发的深度学习框架")
engine.add_document(3, "使用 Python 开发爬虫可以高效获取互联网数据")

# 搜索测试
query = "Python 爬虫"
results = engine.search(query)

print(f"搜索关键词: '{query}' 的结果:")
for res in results:
    if res['score'] > 0: # 只输出相关的
        print(f"DocID: {res['id']} | 相似度得分: {res['score']:.4f} | 内容: {res['text']}")

代码解析

  • 分词:使用了 jieba 库处理中文分词。
  • 向量空间:虽然没有显式生成向量,但 TF-IDF 本质上是将文档映射到了向量空间进行计算。
  • 排序:最后一行 sorted(..., reverse=True) 实现了基础的排序逻辑,得分越高,排在越前面。

二、 进阶篇:基于向量相似度的语义搜索

传统的 TF-IDF 有一个致命缺陷:它是“关键词匹配”。如果用户搜“小狗”,文档里写的是“犬”,TF-IDF 可能认为它们不相关。

结合大模型时代的背景,现在的搜索系统更倾向于使用向量检索。我们将文本转化为高维向量,计算向量之间的余弦相似度。这也是你在大模型 Agent 课程中学习到的重要知识(RAG 系统的核心)。

代码实战:使用 Sentence-Transformers 进行语义排序

python

复制

from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

# 1. 加载预训练模型 (支持中文)
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

# 2. 构建文档库
documents = [
    "机器学习是人工智能的一个子集",
    "深度学习使用神经网络模拟人脑",
    "今天天气真好,适合出去散步",
    "番茄炒蛋是一道美味的家常菜"
]

# 3. 将文档编码为向量 (Embedding)
# 这一步将文本变成了 [384维] 的浮点数数组
doc_vectors = model.encode(documents)

# 4. 用户搜索
query = "AI 神经网络技术"
query_vector = model.encode([query])

# 5. 计算余弦相似度
# 相似度范围 [-1, 1],越接近 1 越相似
similarities = cosine_similarity(query_vector, doc_vectors)

# 6. 排序并输出结果
print(f"搜索查询: '{query}'\n")
# argsort 返回的是从小到大的索引,所以取[::-1]进行反转
ranked_indices = np.argsort(similarities[0])[::-1]

for i, idx in enumerate(ranked_indices):
    score = similarities[0][idx]
    # 过滤掉相似度过低的结果
    if score > 0.3: 
        print(f"Rank {i+1}: 相似度 {score:.4f} -> {documents[idx]}")

算法优势体现

  • 语义理解:即使用户搜索词“AI”和文档词“机器学习”字面不完全重叠,模型也能捕捉到它们在语义上的接近。
  • 高效计算:虽然是高维数学运算,但在底层通过矩阵运算加速,非常适合 Python 科学计算栈。

三、 工业级排序:多因子综合打分

在实际的搜索引擎(如抖音、淘宝、Google)中,排序不是单一维度的。通常是多路召回 + 精排

精排阶段通常会结合以下因子:

  1. 文本相关性(TF-IDF / 向量相似度)
  2. 时效性(新鲜的内容加权)
  3. 用户行为(点击率 CTR、停留时长)
  4. 质量分(内容是否原创、清晰度)

代码实战:多因子线性加权排序

python

复制

import datetime

def advanced_sort(query, docs):
    results = []
    
    # 模拟当前时间
    now = datetime.datetime.now()
    
    for doc in docs:
        # 因子 1: 文本相关性 (模拟值 0-1)
        text_score = doc.get('text_score', 0)
        
        # 因子 2: 时效性 (假设发布时间越新,得分越高)
        # 计算距离发布的天数,衰减系数
        days_ago = (now - doc['publish_time']).days
        time_score = max(0, 1 - days_ago / 30.0) # 30天内线性衰减
        
        # 因子 3: 质量分 (基于点赞数)
        # 简单的归一化逻辑
        like_score = min(doc['likes'] / 1000.0, 1.0)
        
        # --- 核心算法:加权融合 ---
        # 算法工程师需要调整这里的权重 (alpha, beta, gamma)
        alpha, beta, gamma = 0.5, 0.2, 0.3 
        
        final_score = (alpha * text_score) + \
                      (beta * time_score) + \
                      (gamma * like_score)
                      
        results.append({
            "title": doc['title'],
            "final_score": final_score,
            "details": f"文本:{text_score:.2f}, 时效:{time_score:.2f}, 质量:{like_score:.2f}"
        })
    
    # 按照最终得分排序
    return sorted(results, key=lambda x: x['final_score'], reverse=True)

# 模拟数据
docs_data = [
    {'title': 'Python 爬虫教程', 'publish_time': now - datetime.timedelta(days=2), 'likes': 50, 'text_score': 0.9},
    {'title': '深度学习基础', 'publish_time': now - datetime.timedelta(days=100), 'likes': 2000, 'text_score': 0.5}, # 老但优质
    {'title': '今日新闻', 'publish_time': now - datetime.timedelta(days=0), 'likes': 10, 'text_score': 0.1}, # 新但不相关
]

ranked = advanced_sort("Python", docs_data)

print("=== 多因子综合排序结果 ===")
for item in ranked:
    print(f"Title: {item['title']}")
    print(f"Total Score: {item['final_score']:.3f} | Details: [{item['details']}]")
    print("-" * 30)

总结

提升搜索体验是一个系统工程:

  1. 初学者从 TF-IDF 入手,理解关键词匹配。
  2. 进阶者拥抱 向量检索,利用 Embedding 解决语义鸿沟。
  3. 专家通过多因子加权模型,在相关性、时效性和用户体验之间寻找最优解。