文本特征工程:TF-IDF、嵌入与统计特征
文本特征工程: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)
从词向量到文档向量
大多数文本分类任务需要文档级别的特征。如何聚合词向量以表示整篇文档?常见方法:
- 平均词向量:计算文档中所有词向量的均值。简单有效。
- 加权平均:用TF-IDF等作为权重进行加权平均。
- 使用Doc2Vec:直接学习文档级嵌入。
- 现代句子嵌入:使用预训练的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特征提取的强大能力。