阶段5
|
NLP传统 · 第29课/共30课
📖 第29课:信息检索
🎯 课程目标
信息检索是搜索引擎的核心技术,从海量文档中找到最相关的结果。本课学习TF-IDF检索和BM25算法。
- 实现基于TF-IDF的检索引擎
- 从零实现BM25算法
- 理解检索评估指标(MRR、NDCG)
📐 核心原理
BM25:TF-IDF的进化版
BM25对TF-IDF做了两个关键改进:
- TF饱和:TF不是线性增长,而是趋于饱和(k1参数控制)
- 文档长度归一化:长文档不一定更相关(b参数控制)
💡 BM25至今仍是大多数搜索引擎的基础算法。Elasticsearch默认使用BM25作为相似度函数。
🔬 TF-IDF检索引擎
TF-IDF搜索引擎
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
class SearchEngine:
def __init__(self):
self.documents = []
self.vectorizer = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b', max_features=1000)
self.doc_matrix = None
def index(self, documents):
self.documents = documents
doc_texts = [' '.join(jieba.cut(doc)) for doc in documents]
self.doc_matrix = self.vectorizer.fit_transform(doc_texts)
def search(self, query, top_k=5):
query_vec = self.vectorizer.transform([' '.join(jieba.cut(query))])
scores = cosine_similarity(query_vec, self.doc_matrix)[0]
top_indices = np.argsort(scores)[-top_k:][::-1]
results = []
for idx in top_indices:
if scores[idx] > 0:
results.append({
'doc_id': idx,
'content': self.documents[idx],
'score': scores[idx]
})
return results
# 构建文档库
documents = [
"自然语言处理是人工智能的重要分支,研究计算机理解和生成人类语言的技术",
"深度学习使用多层神经网络从数据中学习特征表示,在图像识别和语音识别领域取得突破",
"词向量是将词映射到低维连续空间的技术,使语义相似的词在空间中接近",
"BERT是Google提出的预训练语言模型,通过双向Transformer学习上下文化表示",
"知识图谱以实体和关系为核心,构建结构化的语义网络",
"机器翻译使用序列到序列模型将一种语言翻译为另一种语言",
"情感分析判断文本的情感倾向,在电商评论和社交媒体分析中应用广泛",
"信息检索从大量文档中找到与查询最相关的文档",
"命名实体识别从文本中识别人名、地名、机构名等实体",
"文本摘要自动生成文本的简洁版本,分为抽取式和生成式两种方法",
]
engine = SearchEngine()
engine.index(documents)
queries = ["什么是深度学习", "词的表示方法", "文本情感分析", "知识图谱是什么"]
for query in queries:
results = engine.search(query, top_k=3)
print(f"查询: {query}")
for r in results:
print(f" [{r['score']:.4f}] {r['content'][:40]}...")
print()
查询: 什么是深度学习
[0.3946] 深度学习使用多层神经网络从数据中学习特征表示,在图像识别和语音识别领域取得突破...
[0.2301] BERT是Google提出的预训练语言模型,通过双向Transformer学习上...
[0.1023] 自然语言处理是人工智能的重要分支,研究计算机理解和生成人类语言的技术...
查询: 词的表示方法
[0.4016] 词向量是将词映射到低维连续空间的技术,使语义相似的词在空间中接近...
[0.1956] 文本摘要自动生成文本的简洁版本,分为抽取式和生成式两种方法...
[0.1518] BERT是Google提出的预训练语言模型,通过双向Transformer学习上...
查询: 文本情感分析
[0.5732] 情感分析判断文本的情感倾向,在电商评论和社交媒体分析中应用广泛...
[0.1883] 文本摘要自动生成文本的简洁版本,分为抽取式和生成式两种方法...
[0.0859] 命名实体识别从文本中识别人名、地名、机构名等实体...
查询: 知识图谱是什么
[0.3816] 知识图谱以实体和关系为核心,构建结构化的语义网络...
[0.0966] 自然语言处理是人工智能的重要分支,研究计算机理解和生成人类语言的技术...
[0.0942] BERT是Google提出的预训练语言模型,通过双向Transformer学习上...
✅ 验证通过:代码实机运行结果如上
🔧 BM25算法实现
BM25检索
class BM25:
def __init__(self, k1=1.5, b=0.75):
self.k1 = k1
self.b = b
self.doc_count = 0
self.avg_dl = 0
self.doc_lens = []
self.df = {}
self.doc_terms = []
def fit(self, documents):
import jieba
self.doc_count = len(documents)
self.doc_terms = [jieba.lcut(doc) for doc in documents]
self.doc_lens = [len(terms) for terms in self.doc_terms]
self.avg_dl = sum(self.doc_lens) / len(self.doc_lens)
for terms in self.doc_terms:
for term in set(terms):
self.df[term] = self.df.get(term, 0) + 1
def score(self, query, doc_idx):
import math
query_terms = jieba.lcut(query)
doc = self.doc_terms[doc_idx]
dl = self.doc_lens[doc_idx]
tf_dict = {}
for t in doc:
tf_dict[t] = tf_dict.get(t, 0) + 1
score = 0
for term in query_terms:
if term not in tf_dict: continue
tf = tf_dict[term]
df = self.df.get(term, 0)
idf = math.log((self.doc_count - df + 0.5) / (df + 0.5) + 1)
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * dl / self.avg_dl)
score += idf * numerator / denominator
return score
def search(self, query, top_k=5):
scores = [(i, self.score(query, i)) for i in range(self.doc_count)]
scores.sort(key=lambda x: -x[1])
return scores[:top_k]
bm25 = BM25()
bm25.fit(documents)
print("=== BM25检索 ===")
for query in ["什么是深度学习", "词的表示方法", "情感分析技术"]:
results = bm25.search(query, top_k=3)
print(f"查询: {query}")
for idx, score in results:
print(f" [{score:.4f}] {documents[idx][:40]}...")
print()
[STDERR] Traceback (most recent call last):
File "<string>", line 49, in <module>
NameError: name 'documents' is not defined
⚠️ 部分验证:代码实机运行结果如上
📝 练习
动手练习
- 实现MRR(Mean Reciprocal Rank)和NDCG评估指标
- 对比TF-IDF和BM25在不同查询上的效果差异
- 添加查询扩展(同义词扩展),提升召回率
🔎
🏆 成就解锁:检索大师
掌握了信息检索的核心算法,能构建高效的文档检索系统。
🧪 扩展:检索评估指标
MRR (Mean Reciprocal Rank)
第一个相关结果的排名倒数的平均值。MRR = (1/|Q|) × Σ 1/rank_i
NDCG (Normalized Discounted Cumulative Gain)
考虑了位置的折扣因子:DCG = Σ rel_i / log2(i+1)
NDCG = DCG / IDCG(理想情况下的DCG)
Precision@K 和 Recall@K
P@K = 前K个结果中相关的比例
R@K = 前K个结果覆盖了多少相关文档
BM25参数调优
- k1(默认1.5):控制TF饱和度。越大,TF影响越大
- b(默认0.75):控制文档长度归一化。1=完全归一化,0=忽略长度
- 调优方法:在标注数据上搜索最佳(k1, b)组合
📚 参考文献
推荐阅读
- Jurafsky & Martin. Speech and Language Processing (3rd ed). 第29课相关章节
- Manning, Raghavan & Schutze. Introduction to Information Retrieval
- Goodfellow, Bengio & Courville. Deep Learning. 自然语言处理章节
在线资源
- NLTK文档: https://www.nltk.org/
- spaCy文档: https://spacy.io/
- gensim文档: https://radimrehurek.com/gensim/
- sklearn文本处理: https://scikit-learn.org/stable/modules/feature_extraction.html
信息检索 - 关键概念速查
| 概念 | 定义 | 应用 |
| Pipeline | 处理流水线,各步骤串行执行 | 文本处理系统 |
| Tokenization | 将文本切分为词/子词单元 | 分词、子词分割 |
| Embedding | 将离散符号映射到连续向量空间 | 词向量、句向量 |
| Feature Engineering | 从原始数据提取有效数值特征 | 传统ML模型输入 |
| Fine-tuning | 在预训练模型基础上微调 | BERT等模型适配 |
本课要点回顾
- 信息检索是NLP技术栈的重要组件
- 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
- 代码验证是学习的好方法——动手跑一遍胜过读十遍
- 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
- 实践出真知——用自己的数据跑一遍,发现课本上没写的问题
❓ 常见问题FAQ
- Q: 这个方法的数据需求量是多少?
- A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
- Q: 如何处理中文的特殊情况?
- A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
- Q: 传统方法和深度学习如何选择?
- A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
- Q: 如何评估和调优?
- A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
- Q: 生产环境部署要注意什么?
- A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。
🛠️ 实用工具箱
| 工具 | 用途 | 安装 |
| jieba | 中文分词 | pip install jieba |
| gensim | 词向量训练 | pip install gensim |
| scikit-learn | 传统ML | pip install scikit-learn |
| nltk | 英文NLP工具 | pip install nltk |
| spaCy | 工业NLP管线 | pip install spacy |
| hanlp | 中文NLP工具包 | pip install hanlp |
| fasttext | Facebook文本分类 | pip install fasttext |
| textblob | 简单NLP接口 | pip install textblob |