📢 转载信息
原文作者:Jason Brownlee
在机器学习中,文本特征提取是构建文本分类模型的第一步。当处理文本数据时,最常见的方法是使用词袋模型(Bag-of-Words, BoW),它使用词频(Term Frequency, TF)来表示文档。更复杂的方法包括TF-IDF(Term Frequency-Inverse Document Frequency),它通过考虑词在语料库中的稀有程度来加权词频。
近年来,大型语言模型(LLMs)的兴起为文本表示带来了范式转变。LLM的嵌入向量(Embeddings)不仅捕捉了词汇的频率,还捕获了它们的语义和上下文信息。
Scikit-learn 是一个流行的Python机器学习库,它提供了BoW和TF-IDF的实现。虽然LLM嵌入通常需要外部库(如Hugging Face Transformers或Sentence Transformers),但我们可以使用Scikit-learn的API来封装这些嵌入,从而实现对传统方法和现代方法的统一比较。
本文的目标是比较Scikit-learn中BoW、TF-IDF和基于LLM的嵌入在二元(Binary)文本分类任务上的性能。我们将使用一个二元分类数据集来评估这三种表示方法的相对表现。
实验设置
我们将构建一个端到端的实验来比较这三种方法。对于这个比较,我们需要一个用于训练和测试的文本数据集。我们将使用 Scikit-learn 内置的 20 Newsgroups 数据集,因为它是一个经典的文本分类基准数据集。
1. 准备数据
首先,我们需要导入必要的库并加载数据集。我们将只使用两个类别(rec.sport.baseball 和 sci.electronics)以确保这是一个二元分类问题,从而简化评估。
from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
# 仅选择两个类别
CATEGORIES = ['rec.sport.baseball', 'sci.electronics']
# 加载数据
groups = fetch_20newsgroups(subset='all', categories=CATEGORIES, remove=('headers', 'footers', 'quotes'))
X_train, X_test, y_train, y_test = train_test_split(groups.data, groups.target, test_size=0.3, random_state=42)
print(f"训练样本数: {len(X_train)}")
print(f"测试样本数: {len(X_test)}")
接下来,我们需要一个模型进行分类。我们将使用一个简单的逻辑回归 (Logistic Regression) 模型作为基准分类器,因为它对特征缩放不敏感,并且适用于多种特征表示。
2. 传统方法:词袋模型和 TF-IDF
Scikit-learn 提供了 CountVectorizer(用于 BoW)和 TfidfVectorizer(用于 TF-IDF)来处理文本特征提取。
词袋模型 (BoW)
词袋模型只是计算每个文档中每个词出现的次数。它忽略了词的顺序和语义。
from sklearn.feature_extraction.text import CountVectorizer
# 初始化并训练 CountVectorizer
vectorizer_bow = CountVectorizer()
# 注意:fit_transform 仅在训练集上进行
X_train_bow = vectorizer_bow.fit_transform(X_train)
X_test_bow = vectorizer_bow.transform(X_test)
print(f"BoW 特征形状: {X_train_bow.shape}")
输出显示了特征维度(词汇表大小),这通常是一个非常大的稀疏矩阵。
TF-IDF
TF-IDF 通过惩罚常见词(如“the”、“a”)的权重来改进 BoW 模型。
from sklearn.feature_extraction.text import TfidfVectorizer
# 初始化并训练 TfidfVectorizer
vectorizer_tfidf = TfidfVectorizer()
X_train_tfidf = vectorizer_tfidf.fit_transform(X_train)
X_test_tfidf = vectorizer_tfidf.transform(X_test)
print(f"TF-IDF 特征形状: {X_train_tfidf.shape}")
3. 现代方法:LLM 嵌入
要使用LLM嵌入,我们不能直接使用 Scikit-learn 的向量化器,因为它们依赖于训练数据中的词汇表。相反,我们需要一个外部库来生成固定维度的向量,然后才能将它们与 Scikit-learn 分类器集成。
我们将使用 Sentence Transformers 库中的预训练模型,因为它专为句子和文档的语义相似性设计,并且可以直接集成到 Scikit-learn 的工作流程中。请注意,要运行此代码,需要安装 sentence-transformers 库。
# 假设已安装 sentence-transformers
from sentence_transformers import SentenceTransformer
# 选择一个轻量级且性能良好的模型
MODEL_NAME = 'all-MiniLM-L6-v2'
# 加载模型
embedding_model = SentenceTransformer(MODEL_NAME)
# 生成嵌入
X_train_emb = embedding_model.encode(X_train, convert_to_numpy=True)
X_test_emb = embedding_model.encode(X_test, convert_to_numpy=True)
print(f"LLM 嵌入特征形状: {X_train_emb.shape}")
这里的关键区别在于,LLM嵌入的维度是固定且相对较小的(例如,all-MiniLM-L6-v2 的维度为384),这与 BoW 或 TF-IDF 中可能高达数万维的稀疏特征形成鲜明对比。
4. 评估分类性能
现在我们使用相同的逻辑回归模型来评估每种特征表示的性能(准确率)。
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 用于存储结果
results = {}
# ----------------------
# 评估 BoW
# ----------------------
print("\n评估 BoW...")
classifier_bow = LogisticRegression(max_iter=1000)
classifier_bow.fit(X_train_bow, y_train)
y_pred_bow = classifier_bow.predict(X_test_bow)
accuracy_bow = accuracy_score(y_test, y_pred_bow)
results['BoW'] = accuracy_bow
print(f"BoW 准确率: {accuracy_bow:.4f}")
# ----------------------
# 评估 TF-IDF
# ----------------------
print("\n评估 TF-IDF...")
classifier_tfidf = LogisticRegression(max_iter=1000)
classifier_tfidf.fit(X_train_tfidf, y_train)
y_pred_tfidf = classifier_tfidf.predict(X_test_tfidf)
accuracy_tfidf = accuracy_score(y_test, y_pred_tfidf)
results['TF-IDF'] = accuracy_tfidf
print(f"TF-IDF 准确率: {accuracy_tfidf:.4f}")
# ----------------------
# 评估 LLM 嵌入
# ----------------------
print("\n评估 LLM 嵌入...")
# 嵌入特征通常需要C-SVM或线性模型,但我们使用相同的逻辑回归进行直接比较
# 对于嵌入,我们可能需要稍微调整正则化或增加迭代次数
classifier_emb = LogisticRegression(max_iter=2000)
classifier_emb.fit(X_train_emb, y_train)
y_pred_emb = classifier_emb.predict(X_test_emb)
accuracy_emb = accuracy_score(y_test, y_pred_emb)
results['LLM Embeddings'] = accuracy_emb
print(f"LLM 嵌入 准确率: {accuracy_emb:.4f}")
# ----------------------
# 最终对比
# ----------------------
print("\n--- 最终结果对比 ---")
import pandas as pd
results_df = pd.DataFrame(list(results.items()), columns=['Method', 'Accuracy'])
results_df = results_df.sort_values(by='Accuracy', ascending=False)
print(results_df.to_string(index=False))
结果分析与讨论
实验结果清晰地表明了不同特征表示方法的性能差异:
- 词袋模型 (BoW):作为最基本的方法,其性能通常最低,因为它仅依赖于原始词频,无法理解语义关系。
- TF-IDF:通过引入逆文档频率,TF-IDF显著优于BoW。它能够有效降低常见词汇的权重,突出文档特有的关键词。在许多传统NLP任务中,TF-IDF仍然是一个强大的基线。
- LLM 嵌入:基于LLM(如MiniLM)生成的嵌入向量在准确率上表现最佳。这证实了现代预训练模型在捕捉文本深层语义和上下文方面的巨大优势。即使使用相对较小的嵌入维度(如384维),它们也比高维稀疏的BoW/TF-IDF向量包含更丰富的信息。
效率与资源考量
虽然LLM嵌入在性能上占优,但在效率和资源消耗上需要权衡:
- 维度:BoW和TF-IDF的特征维度可能达到数万(取决于词汇表大小),导致训练稀疏矩阵上的模型时需要更多内存和计算资源(尽管计算速度可能较快)。
- 嵌入生成:生成LLM嵌入(
embedding_model.encode())需要GPU或足够的CPU内存来加载和运行模型,并且耗时可能更长,尤其是在大型数据集上。 - 模型大小:LLM嵌入模型本身(如MiniLM)是数百MB的稠密文件,需要加载到内存中。
对于高性能要求的任务,LLM嵌入是首选,因为它们提供了更高的准确率,尽管需要更多的初始设置和计算资源。
总结
在Scikit-learn的框架下进行文本分类比较时,LLM嵌入明显优于传统的BoW和TF-IDF方法。它们通过捕获语义信息,使得下游的线性模型(如逻辑回归)也能取得更高的准确率。虽然TF-IDF在性能上仍是一个可靠的基准,但对于寻求最先进性能的开发者来说,集成预训练的LLM嵌入是迈向更智能文本处理的关键一步。
您可以使用 Scikit-learn 的 Pipeline 来更好地集成这些步骤,特别是对于传统向量化器,但对于外部嵌入,手动编码和转换通常是最灵活的方式。
🚀 想要体验更好更全面的AI调用?
欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。
评论区