文本特征工程详解:TF-IDF算法、相似度计算与词袋模型演进及PyTorch实现

本文已精简冗余内容,去掉数学公式,新增PyTorch极简实现,控制在2500字内,重点突出实用代码和工程场景。

目录


什么是文本特征工程?

机器无法直接理解“自然语言处理”这样的字符串,它只接受数值数据。文本特征工程的核心任务,就是把原始文本转换成带有语义线索的数值向量,让算法能够处理。

它的四大核心目标:

  1. 数值化文本:将文字映射为数字。
  2. 提取关键特征:比如让关键词拥有更高的权重。
  3. 合理降维:避免词汇表爆炸式增长。
  4. 保留语义:尽可能让相似的文本在向量空间中也靠近。

词袋模型(Bag of Words)

词袋模型是文本向量化的“初代机”,思想非常简单:完全忽略词序和语法,只统计每个词在文档里出现的次数。就好像把所有词语倒进一个袋子里,只看每个词的数量。

极简代码实现

借助 sklearn 和中文分词库 jieba,几行代码就能实现:

from sklearn.feature_extraction.text import CountVectorizer
import jieba

# 自定义中文分词器
def zh_tokenizer(text):
    return jieba.lcut(text)

# 示例语料
docs = [
    "自然语言处理是人工智能的重要分支",
    "机器学习和深度学习是核心技术",
    "自然语言处理和机器学习密切相关"
]

# 初始化词袋向量化器
bow_vec = CountVectorizer(
    tokenizer=zh_tokenizer,
    lowercase=False,       # 中文无需转小写
    token_pattern=None     # 使用自定义分词器
)

# 拟合并生成词袋矩阵
bow_matrix = bow_vec.fit_transform(docs)

print("词汇表:", bow_vec.get_feature_names_out())
print("词袋矩阵:\n", bow_matrix.toarray())

优缺点一览

优点缺点
简单易懂,实现极快完全丢失词序(“狗咬人”和“人咬狗”向量一样)
计算效率高无法捕捉语义关联(“汽车”和“车辆”被视为完全无关)
适合快速建立文本检索原型无法自动处理“的”“是”这类高频停用词

TF-IDF算法详解

TF-IDF 可以看作词袋模型的升级版,它为每个词赋予一个权重。核心思想是:如果一个词在当前文档中出现频繁,但在整个语料库中很少见,那么它就能很好地代表这篇文档

比如,“机器学习”在技术文章里很关键,而“的”在所有文章中都很常见,TF-IDF 会给前者高权重,后者低权重。

核心权重逻辑

  1. 词频 (TF):衡量某个词在当前文档里的“存在感”。
  2. 逆文档频率 (IDF):衡量某个词在整个语料中的“稀缺度”——越稀缺的词信息量越大,权重越高。
  3. 最终权重:TF 与 IDF 的乘积。

实用 Sklearn 实现

TfidfVectorizer 不仅封装了上述逻辑,还支持 n-gram、归一化等高级功能,是工程中的首选:

from sklearn.feature_extraction.text import TfidfVectorizer
import jieba
import numpy as np

def zh_tokenizer(text):
    return jieba.lcut(text)

# 更丰富的语料
docs = [
    "自然语言处理是人工智能的重要分支",
    "机器学习和深度学习是人工智能的核心技术",
    "深度学习在计算机视觉领域应用广泛",
    "数据科学结合统计学和计算机科学"
]

# 高级向量化器配置
tfidf_vec = TfidfVectorizer(
    tokenizer=zh_tokenizer,
    max_features=1000,    # 限制词汇表大小,实现降维
    min_df=1,             # 至少在1个文档中出现
    max_df=0.8,           # 过滤掉80%以上文档都出现的词(近似停用词)
    ngram_range=(1,2),    # 同时考虑单个词与双词(如“自然语言”)
    norm='l2',            # L2归一化,便于后续计算余弦相似度
    sublinear_tf=True     # 对数缩放TF,避免高频词权重过大
)

# 生成TF-IDF矩阵
tfidf_matrix = tfidf_vec.fit_transform(docs)

# 查看第一篇文档的Top3关键词
feature_names = tfidf_vec.get_feature_names_out()
doc1_tfidf = tfidf_matrix[0].toarray().flatten()
top3_idx = np.argsort(doc1_tfidf)[::-1][:3]
print("文档1的Top3关键词:")
for idx in top3_idx:
    print(f"  {feature_names[idx]}: {doc1_tfidf[idx]:.4f}")

相似度度量方法

得到文本向量后,最常见的操作就是计算文本相似度,应用在文档检索、问答匹配等场景。

1. 余弦相似度(最常用)

余弦相似度通过计算两个向量的夹角来衡量相似程度:夹角越小,相似度越高。它的最大优势是不受向量长度影响,特别适合经过 L2 归一化后的 TF-IDF 向量。

from sklearn.metrics.pairwise import cosine_similarity

# 计算整个语料的相似度矩阵
sim_matrix = cosine_similarity(tfidf_matrix)
print("语料相似度矩阵:\n", sim_matrix.round(4))

2. 其他方法对比

方法逻辑适用场景
欧氏距离向量在欧几里得空间中的直线距离向量长度有实际意义的场景(如未归一化的词频)
曼哈顿距离各维度绝对差之和稀疏向量,对异常值不敏感
杰卡德相似度词集交集大小与并集大小的比值短文本、关键词重合度分析

极简PyTorch TF-IDF实现

既然标题提到 PyTorch,这里提供一个极简、可直接运行的版本,方便你自定义底层逻辑,或将其嵌入到深度学习管线中:

import jieba
import torch
from collections import defaultdict

def zh_tokenizer(text):
    return jieba.lcut(text)

class PyTorchTFIDF:
    def __init__(self, max_features=1000, min_df=1, max_df=0.8):
        self.max_features = max_features
        self.min_df = min_df
        self.max_df = max_df
        self.vocab = None
        self.idf = None
    
    def fit(self, texts):
        # 分词
        tokenized = [zh_tokenizer(t) for t in texts]
        total_docs = len(tokenized)
        
        # 统计词频和文档频率
        word_doc_count = defaultdict(int)
        word_total_count = defaultdict(int)
        for tokens in tokenized:
            unique_tokens = set(tokens)
            for token in unique_tokens:
                word_doc_count[token] += 1
            for token in tokens:
                word_total_count[token] += 1
        
        # 过滤词汇(按文档频率与总词频)
        filtered_words = [
            w for w in word_total_count 
            if self.min_df <= word_doc_count[w] <= self.max_df * total_docs
        ]
        # 取总词频最高的max_features个词
        filtered_words.sort(key=lambda x: word_total_count[x], reverse=True)
        self.vocab = {w: i for i, w in enumerate(filtered_words[:self.max_features])}
        
        # 计算IDF(加入平滑项)
        self.idf = torch.zeros(len(self.vocab))
        for w, idx in self.vocab.items():
            self.idf[idx] = torch.log(
                torch.tensor(total_docs / (word_doc_count[w] + 1))
            ) + 1
    
    def transform(self, texts):
        tokenized = [zh_tokenizer(t) for t in texts]
        tfidf_matrix = torch.zeros(len(texts), len(self.vocab))
        
        for i, tokens in enumerate(tokenized):
            token_count = defaultdict(int)
            for t in tokens:
                if t in self.vocab:
                    token_count[t] += 1
            # 归一化TF(词频 / 文档总词数)
            doc_len = len(tokens) if len(tokens) > 0 else 1
            for t, cnt in token_count.items():
                tf = cnt / doc_len
                tfidf_matrix[i, self.vocab[t]] = tf * self.idf[self.vocab[t]]
        
        # L2归一化
        norm = torch.norm(tfidf_matrix, p=2, dim=1, keepdim=True)
        norm[norm == 0] = 1.0   # 避免空文档导致除零
        tfidf_matrix /= norm
        return tfidf_matrix

# 测试
pytorch_tfidf = PyTorchTFIDF()
pytorch_tfidf.fit(docs)
pytorch_matrix = pytorch_tfidf.transform(docs)
print("PyTorch TF-IDF矩阵形状:", pytorch_matrix.shape)

实际应用与案例

文档相似度搜索

下面展示一个最贴近工程的应用:输入查询,返回最相似的 N 个文档

class SimpleDocSearch:
    def __init__(self, docs):
        self.docs = docs
        # 使用更稳定的sklearn封装
        self.vec = TfidfVectorizer(
            tokenizer=zh_tokenizer, 
            ngram_range=(1,2), 
            norm='l2'
        )
        self.matrix = self.vec.fit_transform(docs)
    
    def search(self, query, top_k=2):
        query_vec = self.vec.transform([query])
        sims = cosine_similarity(query_vec, self.matrix).flatten()
        top_idx = sims.argsort()[::-1][:top_k]
        return [(self.docs[i], sims[i].round(4)) for i in top_idx]

# 测试
searcher = SimpleDocSearch(docs)
print("搜索结果(查询:计算机):", searcher.search("计算机"))

局限性与现代替代方案

TF-IDF 的三大局限

  1. 完全忽略词序:无法区分“深度学习”和“学习深度”。
  2. 无法捕捉语义:“汽车”和“轿车”在向量空间中毫无关联。
  3. 高维稀疏:词汇表极大时,计算效率与内存会成为瓶颈。

现代替代方案对比

方法向量维度语义理解计算复杂度典型场景
TF-IDF高(词汇表大小)❌ 无快速检索、关键词提取
Sentence-BERT低(通常768维)✅✅ 强语义相似度、问答匹配
Doc2Vec低(100-300维)✅ 中文档聚类、主题分析

总结

TF-IDF 是 NLP 文本特征工程中必学的入门算法,也是工业界长期依赖的实用工具

  1. 核心逻辑清晰,便于理解和调试。
  2. 计算效率极高,适合大规模语料处理。
  3. 至今仍在文档检索、关键词提取等场景中广泛使用。
  4. 建议先掌握 TF-IDF,再逐步过渡到更复杂的预训练模型。
词袋模型 → TF-IDF → 相似度计算 → 预训练词向量 → Sentence-BERT