文本特征工程:TF-IDF、嵌入与统计特征

FreeGuideOnline 最新 2026-06-14

文本特征工程:TF-IDF、嵌入与统计特征

在自然语言处理与文本分析任务中,原始文本无法直接输入机器学习模型。特征工程的作用就是将非结构化的文本转化为结构化的数值向量,同时尽可能保留语义信息。本文将从最基础的统计特征开始,逐步深入到TF-IDF与词嵌入,帮助你建立起完整的文本特征提取知识体系。

什么是文本特征工程?

文本特征工程是指从原始文本中提取能够描述其内容、结构、语法或语义属性的数值化表示的过程。一个好的特征表示能够显著提升下游任务的性能,例如文本分类、情感分析、信息检索等。特征可以分为几个层次:

  • 统计特征:基于文本表面的数量统计
  • 词汇级特征:如词频、TF-IDF,关注单词的出现模式
  • 语义特征:如词嵌入、句子嵌入,试图捕捉上下文含义

接下来我们逐一学习这些方法,并给出Python实现示例。

统计特征:从文本中挖掘数字信息

在处理任何文本之前,先提取一些基本的统计量是一个好习惯。这些特征虽然简单,但能够提供关于文本复杂性、编写风格的重要线索,常用作基线或辅助特征。

常用统计特征

特征名称 描述
字符数 文本中的总字符数(含空格或不含空格)
单词数 按空格或标点分割得到的单词数量
句子数 以句号、问号、感叹号等为分隔符统计的句子数量
平均词长 总字符数(不含空格) / 单词数
平均句长 单词数 / 句子数
大写词比例 大写字母开头的单词数 / 总单词数
标点符号数量 各类标点的计数
停用词比例 停用词数量 / 总单词数(可反映内容密度)

Python实现示例

使用Python内置库和nltk即可快速计算这些特征:

import nltk
from nltk.corpus import stopwords
import string

nltk.download('punkt')
nltk.download('stopwords')

def get_statistical_features(text):
    # 句子分词
    sentences = nltk.sent_tokenize(text)
    # 单词分词
    words = nltk.word_tokenize(text)
    
    # 字符数(含空格和不含空格)
    char_count_with_space = len(text)
    char_count_without_space = sum(1 for c in text if not c.isspace())
    
    # 单词数
    word_count = len(words)
    # 句子数
    sentence_count = len(sentences)
    # 平均词长
    avg_word_len = char_count_without_space / word_count if word_count else 0
    # 平均句长
    avg_sentence_len = word_count / sentence_count if sentence_count else 0
    
    # 大写词比例
    upper_words = sum(1 for w in words if w[0].isupper())
    upper_ratio = upper_words / word_count if word_count else 0
    
    # 标点符号数量
    punctuation_count = sum(1 for c in text if c in string.punctuation)
    
    # 停用词比例 (英文)
    stop_words = set(stopwords.words('english'))
    stopword_count = sum(1 for w in words if w.lower() in stop_words)
    stopword_ratio = stopword_count / word_count if word_count else 0
    
    return {
        'char_count_with_space': char_count_with_space,
        'char_count_without_space': char_count_without_space,
        'word_count': word_count,
        'sentence_count': sentence_count,
        'avg_word_length': avg_word_len,
        'avg_sentence_length': avg_sentence_len,
        'upper_word_ratio': upper_ratio,
        'punctuation_count': punctuation_count,
        'stopword_ratio': stopword_ratio
    }

这些统计特征通常作为特征向量的一部分,与更高级的语义特征结合使用。

词袋模型与N元语法

在进入TF-IDF之前,有必要先了解词袋模型(Bag of Words, BoW),因为它构成了TF-IDF的基础。

词袋模型原理

BoW将整个语料库视为一个无序的单词集合,忽略语法和词序,仅统计每个单词在文档中出现的次数。这样,每篇文档被表示为一个长度等于词汇表大小的向量,每个元素是对应单词的频次。

例如,两个文档:

  • 文档1:“我 爱 自然 语言 处理”
  • 文档2:“自然 语言 处理 很 有趣”

词汇表:[我, 爱, 自然, 语言, 处理, 很, 有趣] 文档1向量:[1,1,1,1,1,0,0] 文档2向量:[0,0,1,1,1,1,1]

N元语法(N-gram)

N-gram是连续N个词语组成的序列。它能够捕获局部词序信息。例如,2-gram(bigram)对于文档1会产生:“我 爱”、“爱 自然”、“自然 语言”、“语言 处理”。

使用sklearn.feature_extraction.text.CountVectorizer可以轻松生成词袋和N-gram特征:

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    "我 爱 自然 语言 处理",
    "自然 语言 处理 很 有趣"
]
vectorizer = CountVectorizer(ngram_range=(1,2))  # 一元及二元语法
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out())
print(X.toarray())

TF-IDF:衡量单词的重要性

词袋模型的缺点是:一些高频但无信息量的词(如“的”、“是”)会获得过高的权重。TF-IDF(词频-逆文档频率)正是用来修正这个问题的方法。

TF-IDF计算原理

TF-IDF由两部分组成:

  • TF(词频):单词在当前文档中出现的频率,通常为 (单词在该文档中出现的次数) / (文档总词数),以抑制长文档的偏向。
  • IDF(逆文档频率):衡量单词在整个语料库中的普遍性。公式为 log((总文档数) / (包含该单词的文档数 + 1)),分母加平滑避免除零。IDF值越大,说明该词越稀有。

最终权重:TF-IDF = TF * IDF

通过这个乘积,在一篇文档中频繁出现,但在整个语料库中很少出现的词会得到高权重,从而突出关键的区分性词语。

Scikit-learn实现

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    "我喜欢机器学习",
    "机器学习是人工智能的核心",
    "深度学习是机器学习的一个分支"
]
vectorizer = TfidfVectorizer()
X_tfidf = vectorizer.fit_transform(corpus)

# 查看特征词
print(vectorizer.get_feature_names_out())
# 打印第一文档的TF-IDF向量
print(X_tfidf[0].toarray())

TF-IDF的局限与改进

  • 无法捕捉语义:视为单词独立,同义词不会被关联。
  • 没有词序信息:与BoW一样丢失顺序。
  • 对于短文本或领域特定词汇,IDF的作用可能减弱。

因此,更现代的方案是使用词嵌入。

词嵌入:将语义编码为向量

词嵌入(Word Embedding)是一种稠密的向量表示,能够捕捉单词之间的语义和语法关系。著名的例子包括Word2Vec、GloVe和FastText。

Word2Vec的核心思想

Word2Vec通过训练一个浅层神经网络,学习单词的分布式表示。它提出了两种模型架构:

  • CBOW(连续词袋):根据上下文单词预测中心词。
  • Skip-gram:根据中心词预测周围上下文单词。

训练结束后,隐藏层的权重就可以作为单词的向量。特点是:语义相近的词在向量空间中距离较近,且满足向量运算,例如 king - man + woman ≈ queen

使用预训练词嵌入

实际应用中,我们通常使用在大规模语料上预训练好的词向量(例如Google News的Word2Vec,维基百科+Gigaword的GloVe)。可以使用gensim库加载:

import gensim.downloader as api

# 下载并加载一个轻量级模型,例如glove-twitter-25 (25维)
model = api.load("glove-twitter-25")

# 获取单词向量
vector = model['computer']
print(vector.shape)  # (25,)

# 查找相似词
print(model.most_similar('king', topn=5))

如果需要在特定领域任务上获得更好效果,也可以从头训练Word2Vec:

from gensim.models import Word2Vec

sentences = [['我', '爱', '机器学习'], ['机器学习', '是', '人工智能', '核心']]
model_w2v = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)

从词向量到文档向量

大多数文本分类任务需要文档级别的特征。如何聚合词向量以表示整篇文档?常见方法:

  1. 平均词向量:计算文档中所有词向量的均值。简单有效。
  2. 加权平均:用TF-IDF等作为权重进行加权平均。
  3. 使用Doc2Vec:直接学习文档级嵌入。
  4. 现代句子嵌入:使用预训练的Sentence-BERT、Universal Sentence Encoder等。

简单平均实现:

import numpy as np

def document_vector(doc, model):
    # 将文档分词后,取所有已知词的向量平均
    words = nltk.word_tokenize(doc)
    vectors = [model[word] for word in words if word in model]
    if len(vectors) == 0:
        return np.zeros(model.vector_size)
    return np.mean(vectors, axis=0)

特征选择与降维

无论使用TF-IDF还是词嵌入,最终经常会得到高维稀疏或高维稠密特征。为了降低维度、减少噪声并提高泛化能力,常常进行特征选择或降维。

  • 方差分析(ANOVA)或卡方检验:选出与目标变量最相关的特征。
  • L1正则化:在逻辑回归等模型中自动进行特征选择。
  • PCA或TruncatedSVD:适用于稀疏矩阵(如TF-IDF矩阵)降维。
  • 词嵌入本身就是一种降维表示:相比one-hot,维度大幅降低。

实践对比:特征如何影响分类效果

我们以简单的文本分类为例,对比统计特征 + TF-IDF 与平均词向量的性能。以下示例使用电影评论的情感二分类:

from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# TF-IDF + 逻辑回归
pipeline_tfidf = Pipeline([
    ('tfidf', TfidfVectorizer()),
    ('clf', LogisticRegression(max_iter=200))
])

# 假设 X_train, y_train 已准备
scores = cross_val_score(pipeline_tfidf, X_train, y_train, cv=5)
print("TF-IDF 平均准确率:", scores.mean())

使用平均词嵌入时,可先转换文档为向量再训练:

from sklearn.base import BaseEstimator, TransformerMixin

class Word2VecVectorizer(BaseEstimator, TransformerMixin):
    def __init__(self, model):
        self.model = model
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return np.array([document_vector(doc, self.model) for doc in X])

pipeline_w2v = Pipeline([
    ('w2v', Word2VecVectorizer(model)),
    ('clf', LogisticRegression(max_iter=200))
])
scores = cross_val_score(pipeline_w2v, X_train, y_train, cv=5)
print("Word2Vec 平均准确率:", scores.mean())

通常情况下,对于小数据集,TF-IDF + 简单分类器效果稳健;对于大数据集且语义复杂时,词嵌入方法更优。

如何选择合适的文本特征?

  • 数据量较小(几千样本):优先尝试TF-IDF或统计特征 + 线性模型(如逻辑回归、SVM)。
  • 数据量较大且任务需要语义理解:使用预训练词嵌入(如Word2Vec、GloVe)或句子嵌入。
  • 追求最先进性能:考虑基于Transformer的预训练模型(BERT、RoBERTa),它们提供了更强大的上下文相关表示。
  • 基线与可解释性:统计特征与TF-IDF更容易解释特征的贡献,适合需要分析重要词汇的场景。

总结

文本特征工程是连接原始语言与机器学习的桥梁。从简单却有效的统计特征,到经典的TF-IDF,再到捕捉语义的词嵌入,每一步都在提升文本的表达能力。掌握这些方法后,推荐你在实际项目中组合使用,并根据模型表现迭代优化特征。

后续学习建议:尝试在真实数据集上对比不同特征的效果,并逐步探索预训练语言模型的使用,它们将为你揭开现代NLP特征提取的强大能力。