文本特征工程详解: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()

相关教程

TF-IDF是理解文本特征工程的重要基础,建议先掌握其原理和实现,再学习现代深度学习方法。在实际项目中,TF-IDF仍广泛用于文档检索、关键词提取等任务。

总结

文本特征工程是NLP的基础环节,TF-IDF作为经典的特征提取方法具有重要意义:

  1. 基础知识:词袋模型、TF-IDF算法、相似度度量
  2. 实际应用:文档分类、相似度计算、关键词提取
  3. 局限性认识:无法捕捉语义、忽略词序等问题
  4. 发展方向:从传统方法向深度学习模型演进

💡 核心要点:TF-IDF虽有局限性,但在许多场景下仍非常有效,特别是当计算资源有限或需要可解释性时。现代NLP任务中,TF-IDF常与其他方法结合使用。


🔗 扩展阅读

📂 所属阶段:第一阶段 — 文本预处理(基石篇)
🔗 相关章节:词向量空间WordEmbeddings · 文本清洗与规范化