#文本特征工程详解:TF-IDF算法、相似度计算与词袋模型演进及PyTorch实现
#目录
#什么是文本特征工程?
文本特征工程是将原始文本转换为数值向量的过程,这是机器学习模型处理文本数据的基础。由于机器学习算法只能处理数值数据,我们需要将文本转换为有意义的数值表示。
#文本特征工程的重要性
文本特征工程在NLP任务中扮演着至关重要的角色:
- 数值化转换:将离散文本转换为连续数值向量
- 特征提取:从文本中提取有用的特征信息
- 降维处理:减少特征维度,提高计算效率
- 语义保留:在数值转换中保持文本的语义信息
#词袋模型(Bag of Words)
词袋模型是最基础的文本表示方法,它忽略词序但保留词频信息。
#词袋模型原理
from sklearn.feature_extraction.text import CountVectorizer
import jieba
def bag_of_words_example():
"""
词袋模型示例
"""
# 示例语料
documents = [
"自然语言处理是人工智能的重要分支",
"机器学习和深度学习是核心技术",
"自然语言处理和机器学习密切相关"
]
# 中文分词
def chinese_tokenizer(text):
return jieba.lcut(text)
# 创建词袋模型向量化器
vectorizer = CountVectorizer(
tokenizer=chinese_tokenizer,
lowercase=False, # 中文不需要转小写
token_pattern=None # 使用自定义分词器
)
# 拟合并转换
bow_matrix = vectorizer.fit_transform(documents)
print("词汇表:", vectorizer.get_feature_names_out())
print("词袋矩阵形状:", bow_matrix.shape)
print("词袋矩阵:\n", bow_matrix.toarray())
# 每个文档的词频统计
for i, doc in enumerate(documents):
print(f"文档{i+1}: {doc}")
print(f"词频向量: {bow_matrix[i].toarray().flatten()}")
bag_of_words_example()#词袋模型的优缺点
优点:
- 简单易懂,实现容易
- 计算效率高
- 适合基础文本分类任务
缺点:
- 忽略词序信息
- 无法捕捉语义关系
- 高维稀疏向量
- 无法区分重要词和停用词
#TF-IDF算法详解
TF-IDF(Term Frequency-Inverse Document Frequency)是词袋模型的改进版本,通过引入权重来突出重要词汇。
#TF-IDF的核心思想
TF-IDF = 词频(TF) × 逆文档频率(IDF)
TF(词频):一个词在当前文档中出现的频率
IDF(逆文档频率):衡量一个词的普遍重要性
核心洞察:
- 在当前文档中频繁出现的词 → 重要性高
- 在所有文档中都很罕见的词 → 信息量大
- 在所有文档中都频繁出现的词(如"的"、"是")→ 重要性低#TF-IDF数学公式
import math
import numpy as np
def calculate_tf_idf_manual():
"""
手动计算TF-IDF的示例
"""
# 示例文档集合
documents = [
"自然语言处理是人工智能的重要分支",
"机器学习和深度学习是核心技术",
"自然语言处理和机器学习密切相关"
]
# 简化的手动计算过程
print("TF-IDF计算步骤:")
print("1. TF(t,d) = 词t在文档d中的出现次数 / 文档d的总词数")
print("2. IDF(t) = log(总文档数 / 包含词t的文档数)")
print("3. TF-IDF(t,d) = TF(t,d) × IDF(t)")
# 实际计算示例
word = "学习"
doc_idx = 1 # 第二个文档:"机器学习和深度学习是核心技术"
# 计算TF
doc_words = documents[doc_idx].split()
tf = doc_words.count("学习") / len(doc_words)
# 计算IDF
docs_containing_word = sum(1 for doc in documents if "学习" in doc)
total_docs = len(documents)
idf = math.log(total_docs / docs_containing_word)
# 计算TF-IDF
tfidf = tf * idf
print(f"\n'学习'在文档{doc_idx+1}中的TF-IDF值: {tfidf:.4f}")
calculate_tf_idf_manual()#使用Scikit-learn实现TF-IDF
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import jieba
import numpy as np
def advanced_tfidf_implementation():
"""
高级TF-IDF实现
"""
# 示例语料
documents = [
"自然语言处理是人工智能的重要分支",
"机器学习和深度学习是人工智能的核心技术",
"自然语言处理和机器学习有着密切的关系",
"深度学习在计算机视觉领域应用广泛",
"机器学习算法包括监督学习和无监督学习"
]
# 中文分词函数
def chinese_tokenizer(text):
return jieba.lcut(text)
# 创建高级TF-IDF向量化器
vectorizer = TfidfVectorizer(
tokenizer=chinese_tokenizer,
max_features=1000, # 最大特征数
min_df=1, # 最小文档频率(至少在1个文档中出现)
max_df=0.8, # 最大文档频率(过滤出现在80%以上文档中的词)
ngram_range=(1, 2), # 考虑1-gram和2-gram
norm='l2', # L2归一化
use_idf=True, # 使用IDF
smooth_idf=True, # 平滑IDF
sublinear_tf=True # 使用对数TF缩放
)
# 拟合并转换
tfidf_matrix = vectorizer.fit_transform(documents)
print("词汇表大小:", len(vectorizer.get_feature_names_out()))
print("TF-IDF矩阵形状:", tfidf_matrix.shape)
print("稀疏度:", 1 - tfidf_matrix.nnz / (tfidf_matrix.shape[0] * tfidf_matrix.shape[1]))
# 显示前几个文档的TF-IDF向量
feature_names = vectorizer.get_feature_names_out()
for i in range(min(2, len(documents))):
print(f"\n文档{i+1}: {documents[i]}")
# 获取非零TF-IDF值
doc_vector = tfidf_matrix[i].toarray().flatten()
nonzero_indices = np.nonzero(doc_vector)[0]
top_indices = nonzero_indices[np.argsort(doc_vector[nonzero_indices])[::-1][:5]]
print("Top 5关键词:")
for idx in top_indices:
print(f" {feature_names[idx]}: {doc_vector[idx]:.4f}")
advanced_tfidf_implementation()#关键词提取
def extract_keywords_with_tfidf(documents, top_k=5):
"""
使用TF-IDF提取关键词
"""
def chinese_tokenizer(text):
return jieba.lcut(text)
vectorizer = TfidfVectorizer(
tokenizer=chinese_tokenizer,
stop_words=None,
max_features=500,
ngram_range=(1, 2)
)
# 只对单个文档计算TF-IDF
tfidf_matrix = vectorizer.fit_transform(documents)
feature_names = vectorizer.get_feature_names_out()
# 提取每个文档的关键词
keywords_per_doc = []
for i in range(len(documents)):
doc_tfidf = tfidf_matrix[i].toarray().flatten()
top_indices = np.argsort(doc_tfidf)[::-1][:top_k]
keywords = [(feature_names[idx], doc_tfidf[idx]) for idx in top_indices if doc_tfidf[idx] > 0]
keywords_per_doc.append(keywords)
return keywords_per_doc
# 示例使用
sample_docs = [
"自然语言处理是人工智能领域的重要分支,涉及文本分析和语言理解",
"机器学习算法通过数据训练模型,实现预测和分类功能"
]
keywords = extract_keywords_with_tfidf(sample_docs)
for i, doc_keywords in enumerate(keywords):
print(f"\n文档{i+1}的关键词:")
for word, score in doc_keywords:
print(f" {word}: {score:.4f}")#相似度度量方法
#余弦相似度(最常用)
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances, manhattan_distances
import numpy as np
def cosine_similarity_detailed():
"""
余弦相似度详细实现
"""
# 示例:使用上面的TF-IDF矩阵
documents = [
"自然语言处理是人工智能的重要分支",
"机器学习是人工智能的核心技术",
"计算机科学包括多个子领域"
]
def chinese_tokenizer(text):
return jieba.lcut(text)
vectorizer = TfidfVectorizer(tokenizer=chinese_tokenizer)
tfidf_matrix = vectorizer.fit_transform(documents)
# 计算文档间的余弦相似度
similarity_matrix = cosine_similarity(tfidf_matrix)
print("文档相似度矩阵:")
print(similarity_matrix.round(4))
# 详细比较两个文档
doc1_idx, doc2_idx = 0, 1
similarity = cosine_similarity(tfidf_matrix[doc1_idx], tfidf_matrix[doc2_idx])[0][0]
print(f"\n文档1: {documents[doc1_idx]}")
print(f"文档2: {documents[doc2_idx]}")
print(f"余弦相似度: {similarity:.4f}")
# 余弦相似度的手动计算验证
v1 = tfidf_matrix[doc1_idx].toarray().flatten()
v2 = tfidf_matrix[doc2_idx].toarray().flatten()
manual_cosine = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
print(f"手动计算余弦相似度: {manual_cosine:.4f}")
cosine_similarity_detailed()#其他相似度度量
def various_similarity_measures():
"""
多种相似度度量方法对比
"""
documents = [
"自然语言处理技术",
"机器学习算法",
"自然语言处理算法"
]
def chinese_tokenizer(text):
return jieba.lcut(text)
vectorizer = TfidfVectorizer(tokenizer=chinese_tokenizer)
tfidf_matrix = vectorizer.fit_transform(documents)
# 余弦相似度
cosine_sim = cosine_similarity(tfidf_matrix)
# 欧氏距离
euclidean_dist = euclidean_distances(tfidf_matrix)
# 曼哈顿距离
manhattan_dist = manhattan_distances(tfidf_matrix)
print("相似度/距离矩阵对比:")
print("余弦相似度 (越接近1越相似):")
print(cosine_sim.round(4))
print("\n欧氏距离 (越小越相似):")
print(euclidean_dist.round(4))
print("\n曼哈顿距离 (越小越相似):")
print(manhattan_dist.round(4))
various_similarity_measures()#杰卡德相似度
def jaccard_similarity_custom(doc1, doc2):
"""
自定义杰卡德相似度计算
"""
def chinese_tokenizer(text):
return set(jieba.lcut(text))
set1 = chinese_tokenizer(doc1)
set2 = chinese_tokenizer(doc2)
intersection = len(set1.intersection(set2))
union = len(set1.union(set2))
jaccard_coeff = intersection / union if union > 0 else 0
return jaccard_coeff
# 示例
doc1 = "自然语言处理是人工智能的重要分支"
doc2 = "人工智能的重要分支是自然语言处理"
jaccard_sim = jaccard_similarity_custom(doc1, doc2)
print(f"文档1: {doc1}")
print(f"文档2: {doc2}")
print(f"杰卡德相似度: {jaccard_sim:.4f}")#实际应用与案例
#文档相似度搜索
class DocumentSimilaritySearch:
"""
文档相似度搜索系统
"""
def __init__(self, documents):
self.documents = documents
self.vectorizer = TfidfVectorizer(
tokenizer=lambda x: jieba.lcut(x),
ngram_range=(1, 2),
max_features=1000,
min_df=1,
max_df=0.8
)
# 训练向量化器
self.tfidf_matrix = self.vectorizer.fit_transform(documents)
def find_similar_documents(self, query, top_k=3):
"""
查找与查询最相似的文档
"""
query_vector = self.vectorizer.transform([query])
similarities = cosine_similarity(query_vector, self.tfidf_matrix).flatten()
# 获取最相似的文档索引
top_indices = np.argsort(similarities)[::-1][:top_k]
results = []
for idx in top_indices:
results.append({
'document': self.documents[idx],
'similarity': similarities[idx],
'index': idx
})
return results
# 示例使用
sample_documents = [
"自然语言处理是人工智能的重要分支",
"机器学习算法包括监督学习和无监督学习",
"深度学习在计算机视觉领域应用广泛",
"自然语言处理技术用于文本分析和理解",
"数据科学结合统计学和计算机科学",
"机器学习模型需要大量训练数据"
]
search_system = DocumentSimilaritySearch(sample_documents)
results = search_system.find_similar_documents("文本分析技术", top_k=3)
print("查询: 文本分析技术")
print("最相似的文档:")
for i, result in enumerate(results, 1):
print(f"{i}. 相似度: {result['similarity']:.4f}")
print(f" 文档: {result['document']}")#文本分类应用
def text_classification_with_tfidf():
"""
使用TF-IDF进行文本分类的完整示例
"""
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
import jieba
# 模拟数据集
texts = [
# 科技类
("人工智能技术发展迅速", "technology"),
("机器学习算法不断进步", "technology"),
("深度学习在各领域应用", "technology"),
("自然语言处理技术成熟", "technology"),
("计算机视觉算法优化", "technology"),
# 体育类
("足球比赛激烈进行", "sports"),
("篮球运动员表现优异", "sports"),
("网球锦标赛圆满结束", "sports"),
("游泳选手打破纪录", "sports"),
("田径比赛精彩纷呈", "sports"),
# 娱乐类
("电影票房创历史新高", "entertainment"),
("音乐节吸引众多观众", "entertainment"),
("电视剧收视率很高", "entertainment"),
("演唱会门票一票难求", "entertainment"),
("综艺节目收视火爆", "entertainment")
]
X = [item[0] for item in texts]
y = [item[1] for item in texts]
# TF-IDF向量化
vectorizer = TfidfVectorizer(
tokenizer=lambda x: jieba.lcut(x),
ngram_range=(1, 2),
max_features=100,
min_df=1
)
X_tfidf = vectorizer.fit_transform(X)
# 分割训练测试集(由于数据少,这里只做演示)
X_train, X_test, y_train, y_test = train_test_split(
X_tfidf, y, test_size=0.2, random_state=42
)
# 训练分类器
classifier = MultinomialNB()
classifier.fit(X_train, y_train)
# 预测
y_pred = classifier.predict(X_test)
print("TF-IDF + 朴素贝叶斯分类结果:")
print(f"准确率: {accuracy_score(y_test, y_pred):.4f}")
# 测试新文本
new_texts = ["新的AI技术发布", "精彩的篮球比赛"]
new_tfidf = vectorizer.transform(new_texts)
predictions = classifier.predict(new_tfidf)
for text, pred in zip(new_texts, predictions):
print(f"文本: '{text}' -> 预测类别: {pred}")
text_classification_with_tfidf()#局限性与现代替代方案
#TF-IDF的局限性
def tfidf_limitations():
"""
展示TF-IDF的局限性
"""
print("TF-IDF的主要局限性:")
print("1. 无法捕捉语义相似性")
print(" 例如:'汽车' 和 '车辆' 在语义上相似,但TF-IDF向量完全不同")
print("2. 忽略词序信息")
print(" 例如:'狗咬人' 和 '人咬狗' 的TF-IDF向量相同")
print("3. 高维稀疏向量")
print(" 词汇表很大时,向量维度高且稀疏")
print("4. 无法处理同义词和反义词")
print(" 同义词没有相似的向量表示")
tfidf_limitations()#现代替代方案对比
| 方法 | 维度 | 语义理解 | 上下文感知 | 计算复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 词袋模型 | 高(词汇表大小) | ❌ | ❌ | 低 | 快速原型、简单分类 |
| TF-IDF | 高(词汇表大小) | ❌ | ❌ | 低 | 文档检索、关键词提取 |
| Word2Vec平均 | 低(100-300) | ✅ | ❌ | 中 | 简单分类、聚类 |
| Doc2Vec | 低(100-300) | ✅ | 部分 | 中 | 文档相似度 |
| Sentence-BERT | 低(768-1024) | ✅✅ | ✅ | 高 | 语义相似度、问答 |
| Transformer | 低(768-4096) | ✅✅✅ | ✅✅ | 很高 | 复杂NLP任务 |
#现代方法示例
def modern_approach_comparison():
"""
现代方法与TF-IDF的对比
"""
print("现代NLP特征工程发展趋势:")
print("\n1. 预训练语言模型:")
print(" - BERT、RoBERTa、GPT等提供上下文相关的词向量")
print(" - 能够理解语义和上下文关系")
print(" - 通常比TF-IDF有更高的准确率")
print("\n2. 句子嵌入:")
print(" - Sentence-BERT、Universal Sentence Encoder")
print(" - 直接生成句子级别的向量表示")
print(" - 语义相似度计算更准确")
print("\n3. 混合方法:")
print(" - TF-IDF用于初步筛选,深度模型精排")
print(" - 结合传统方法和现代技术的优势")
modern_approach_comparison()
# 演示现代方法的优越性
def semantic_similarity_example():
"""
语义相似度示例 - 展示现代方法的优势
"""
print("\n语义相似度对比示例:")
print("传统TF-IDF无法识别的语义关系:")
semantic_pairs = [
("汽车", "车辆"),
("快乐", "高兴"),
("美丽", "漂亮"),
("快速", "迅速")
]
print("这些词对在语义上相似,但TF-IDF向量正交(相似度为0)")
print("而现代方法(如BERT)能够识别这些语义关系")
semantic_similarity_example()#相关教程
#总结
文本特征工程是NLP的基础环节,TF-IDF作为经典的特征提取方法具有重要意义:
- 基础知识:词袋模型、TF-IDF算法、相似度度量
- 实际应用:文档分类、相似度计算、关键词提取
- 局限性认识:无法捕捉语义、忽略词序等问题
- 发展方向:从传统方法向深度学习模型演进
💡 核心要点:TF-IDF虽有局限性,但在许多场景下仍非常有效,特别是当计算资源有限或需要可解释性时。现代NLP任务中,TF-IDF常与其他方法结合使用。
🔗 扩展阅读
📂 所属阶段:第一阶段 — 文本预处理(基石篇)
🔗 相关章节:词向量空间WordEmbeddings · 文本清洗与规范化

