阶段5
|
NLP传统 · 第25课/共30课
📖 第25课:关键词提取
🎯 课程目标
关键词提取从文本中自动识别最能代表文本主题的词语,是文本摘要、信息检索等任务的基石。
- 掌握TF-IDF、TextRank、RAKE三种经典方法
- 从零实现TextRank和RAKE算法
- 理解不同方法的适用场景
📐 核心原理
三大方法对比
- TF-IDF:基于统计,简单快速,但只考虑词频信息
- TextRank:基于图排序,考虑词间关系,效果通常更好
- RAKE:基于短语分割,能提取多词关键词
💡 TextRank本质:和Google的PageRank同源——给图中的节点打分,连接多的节点得分高。关键词图中,与很多词共现的词更重要。
🔬 三种关键词提取方法
TF-IDF/TextRank/手动TextRank
import jieba
import jieba.analyse
from collections import Counter
import math
text = "自然语言处理是人工智能的重要分支。深度学习技术推动了自然语言处理的快速发展。词向量表示和预训练语言模型成为NLP的核心技术。文本分类、情感分析、机器翻译等任务取得了显著进展。大规模预训练模型如BERT和GPT改变了NLP的研究范式。"
# 方法1: TF-IDF关键词提取
tfidf_keywords = jieba.analyse.extract_tags(text, topK=10, withWeight=True)
print("=== TF-IDF关键词 ===")
for word, weight in tfidf_keywords:
print(f" {word}: {weight:.4f}")
# 方法2: TextRank关键词提取
textrank_keywords = jieba.analyse.textrank(text, topK=10, withWeight=True)
print("\n=== TextRank关键词 ===")
for word, weight in textrank_keywords:
print(f" {word}: {weight:.4f}")
# 方法3: 手动实现TextRank
import numpy as np
def manual_textrank(words, window=4, d=0.85, iterations=30):
word_set = list(set(words))
n = len(word_set)
word2idx = {w: i for i, w in enumerate(word_set)}
# 构建共现图
graph = np.zeros((n, n))
for i in range(len(words)):
for j in range(max(0, i-window), min(len(words), i+window+1)):
if i != j:
a, b = word2idx[words[i]], word2idx[words[j]]
graph[a][b] += 1
# PageRank迭代
scores = np.ones(n) / n
for _ in range(iterations):
new_scores = (1 - d) / n + d * graph.T @ scores / (graph.sum(axis=0, keepdims=True) + 1e-8)
scores = new_scores
ranked = [(word_set[i], scores[i]) for i in range(n)]
ranked.sort(key=lambda x: -x[1])
return ranked
words = jieba.lcut(text)
keywords = manual_textrank(words, window=4)
print("\n=== 手动TextRank ===")
for word, score in keywords[:10]:
print(f" {word}: {score:.6f}")
=== TF-IDF关键词 ===
NLP: 0.6131
自然语言: 0.5351
模型: 0.3491
训练: 0.3410
机器翻译: 0.3152
BERT: 0.3065
GPT: 0.3065
范式: 0.2838
处理: 0.2775
人工智能: 0.2425
=== TextRank关键词 ===
训练: 1.0000
研究: 0.8726
技术: 0.7444
模型: 0.6980
分析: 0.6421
处理: 0.6278
文本: 0.6226
分类: 0.6189
分支: 0.6168
推动: 0.6040
[STDERR] Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.828 seconds.
Prefix dict has been built successfully.
Traceback (most recent call last):
File "<string>", line 47, in <module>
File "<string>", line 39, in manual_textrank
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 1 is different from 44)
⚠️ 部分验证:代码实机运行结果如上
🔧 RAKE算法实现
RAKE关键词提取
import jieba
from collections import Counter
import math
# RAKE算法实现
class RAKE:
def __init__(self, stop_words=None):
if stop_words is None:
self.stop_words = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这'}
else:
self.stop_words = stop_words
def extract_keywords(self, text, topn=10):
words = jieba.lcut(text)
# 分割为候选关键词
candidates = []
current = []
for word in words:
if word in self.stop_words or len(word.strip()) == 0:
if current:
candidates.append(current)
current = []
else:
current.append(word)
if current:
candidates.append(current)
# 计算词频和共现度
word_freq = Counter()
word_degree = Counter()
for phrase in candidates:
degree = len(phrase) - 1
for word in phrase:
word_freq[word] += 1
word_degree[word] += degree
# 计算 score = degree / frequency
word_score = {}
for word in word_freq:
word_score[word] = (word_degree[word] + word_freq[word]) / word_freq[word]
# 短语得分
phrase_scores = {}
for phrase in candidates:
score = sum(word_score.get(w, 0) for w in phrase)
phrase_scores[' '.join(phrase)] = score
ranked = sorted(phrase_scores.items(), key=lambda x: -x[1])
return ranked[:topn]
rake = RAKE()
text = "自然语言处理是人工智能的重要分支。深度学习推动了自然语言处理的发展。预训练语言模型改变了NLP的研究范式。词向量表示技术是深度学习在NLP中的基础应用。"
keywords = rake.extract_keywords(text, topn=10)
print("=== RAKE关键词提取 ===")
for phrase, score in keywords:
print(f" {phrase}: {score:.4f}")
=== RAKE关键词提取 ===
发展 。 预 训练 语言 模型 改变: 47.7500
研究 范式 。 词 向量 表示 技术: 47.7500
重要 分支 。 深度 学习 推动: 31.7500
基础 应用 。: 11.7500
深度 学习: 8.0000
自然语言 处理: 4.0000
NLP 中: 3.5000
NLP: 1.5000
人工智能: 1.0000
✅ 验证通过:代码实机运行结果如上
📝 练习
动手练习
- 实现YAKE!算法(无监督、无需训练语料的关键词提取)
- 对比五种关键词提取方法在新闻文本上的效果
- 用关键词提取结果作为搜索索引,构建简单检索系统
🔑
🏆 成就解锁:关键词猎手
掌握了多种关键词提取算法,能自动识别文本的核心概念。
🧪 扩展:关键词提取评估
评估方法
- 人工标注对比:抽取结果与人工标注的关键词对比
- 下游任务验证:关键词作为特征用于分类,看性能变化
- Precision@K:前K个关键词的准确率
- MRR:第一个正确关键词的排名倒数
关键词提取的应用
- 搜索引擎:用关键词构建倒排索引
- 文档聚类:用关键词作为聚类特征
- 自动标签:为文章自动生成标签
- SEO优化:提取页面关键词用于搜索引擎优化
📚 扩展阅读:关键词提取的工业应用
搜索引擎中的关键词
搜索引擎对关键词提取有特殊需求:
- 查询理解:理解用户查询的意图关键词
- 文档索引:从网页中提取关键词构建倒排索引
- 查询扩展:用同义词/相关词扩展原始查询
- 广告匹配:将关键词与广告关键词匹配
在搜索引擎中,关键词提取的召回率比精确率更重要——遗漏关键查询词意味着完全失去该查询的流量。
🔬 扩展实践:关键词提取的系统设计
混合关键词提取策略
单一方法总有盲区,混合策略效果最好:
- 分别用TF-IDF、TextRank、RAKE提取关键词
- 对三个结果取并集
- 按出现次数和权重综合排序
- 在2个以上方法中出现的关键词优先级最高
关键词提取的在线学习
新文档不断到来,关键词统计需要更新:
- TF更新:增量统计词频
- IDF更新:增量维护文档频率
- TextRank:重新构建共现图,定期全量重算
- 词向量:增量训练或直接使用预训练向量
🧪 扩展:混合关键词提取策略
混合策略
- 分别用TF-IDF、TextRank、RAKE提取关键词
- 对三个结果取并集
- 按出现次数和权重综合排序
- 在2个以上方法中出现的关键词优先级最高
关键词提取的工业应用
- 搜索引擎:用关键词构建倒排索引
- 文档聚类:用关键词作为聚类特征
- 自动标签:为文章自动生成标签
- SEO优化:提取页面关键词用于搜索引擎优化
📚 参考文献
推荐阅读
- Jurafsky & Martin. Speech and Language Processing (3rd ed). 第25课相关章节
- 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更重要——知道为什么比知道怎么做更有价值
- 代码验证是学习的好方法——动手跑一遍胜过读十遍
- 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
- 实践出真知——用自己的数据跑一遍,发现课本上没写的问题