【机器学习】用决策树理解文本
【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在本文中,你将学习到:
- 构建用于垃圾邮件检测的决策树分类器,分析文本数据。
- 在训练决策树时,结合文本建模技术,如 TF-IDF 和嵌入(embeddings)。
- 使用 Scikit-learn 对分类结果进行评估,并与其他文本分类器(如朴素贝叶斯)进行比较。
引言
基于决策树的模型在处理各类分类和回归任务时表现出色,尤其是针对结构化的表格数据。然而,当结合适当的工具时,决策树也能成为处理非结构化数据(如文本、图像,甚至时间序列数据)的强大预测工具。
本文将展示如何针对文本数据构建决策树。具体而言,我们会在用于垃圾邮件分类的决策树中引入 TF-IDF 和嵌入等文本表示技术,评估其性能,并将结果与另一种文本分类模型进行比较——所有操作均借助 Python 的 Scikit-learn 库完成。
为文本分类构建决策树
下面的实践教程将使用公开的 UCI 垃圾邮件分类数据集:这是一个文本-标签对集合,每条记录描述一封电子邮件及其标注为垃圾邮件(spam)或非垃圾邮件(ham,口语中表示非垃圾邮件)。
下面的代码从公开仓库 URL 下载、解压并加载数据集到名为 df 的 Pandas DataFrame 对象中:
import pandas as pd
import requests
import zipfile
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip"
r = requests.get(url)
open("smsspamcollection.zip", "wb").write(r.content)
with zipfile.ZipFile("smsspamcollection.zip", "r") as z:
with z.open("SMSSpamCollection") as f:
df = pd.read_csv(f, sep='\t', names=["label", "text"])
df.head()
作为快速检查,让我们查看垃圾邮件与非垃圾邮件的数量:
df["label"].value_counts()
结果显示有 4,825 封非垃圾邮件(占 86%)和 747 封垃圾邮件(占 14%)。这表明数据集存在类别不平衡问题,需要注意,因为仅用准确率作为评估指标并不理想。
接下来,我们将数据集(输入文本和标签)划分为训练集和测试集。由于类别不平衡,我们将使用分层抽样(stratified sampling)保持训练集和测试集中的类别比例一致,这有助于训练更具泛化能力的模型。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
df["text"], df["label"], test_size=0.2, random_state=42, stratify=df["label"]
)
现在,我们准备训练第一个决策树模型。关键在于将文本数据编码为决策树可以处理的结构化格式。一种常用方法是 TF-IDF 向量化。TF-IDF 将每条文本映射为稀疏数值向量,每个维度(特征)表示词汇表中的一个词,并按其 TF-IDF 分数加权。
Scikit-learn 的 Pipeline 类提供了一种优雅的方式来串联这些步骤。我们将创建一个管道,首先使用 TfidfVectorizer 进行 TF-IDF 向量化,然后训练一个 DecisionTreeClassifier。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
tfidf_tree = Pipeline([
("tfidf", TfidfVectorizer()),
("clf", DecisionTreeClassifier(random_state=42))
])
tfidf_tree.fit(X_train, y_train)
y_pred = tfidf_tree.predict(X_test)
print("MODEL 1. Decision Tree + TF-IDF:")
print(classification_report(y_test, y_pred))
结果:
MODEL 1. Decision Tree + TF-IDF:
precision recall f1-score support
ham 0.97 0.99 0.98 966
spam 0.91 0.83 0.87 149
accuracy 0.97 1115
macro avg 0.94 0.91 0.92 1115
weighted avg 0.97 0.97 0.97 1115
结果还算不错,但因为非垃圾邮件(ham)数量占主导,准确率稍显偏高。如果抓取所有垃圾邮件至关重要,我们需要特别关注垃圾邮件的召回率(recall),在本例中仅为 0.83。
垃圾邮件的精确率(precision)较高,说明很少有非垃圾邮件被误标为垃圾邮件。如果希望避免重要邮件被误送到垃圾箱,这一点非常重要。
我们的第二个决策树将采用另一种文本表示方法:嵌入(embeddings)。嵌入是词或句子的向量表示,相似的文本在向量空间中会靠得很近,能够捕捉语义含义和上下文关系,而不仅仅是词频。
一种简单生成文本嵌入的方法是使用预训练模型,如 GloVe。我们可以将邮件中的每个单词映射到对应的稠密 GloVe 向量,然后通过对这些单词向量取平均来表示整封邮件,从而得到每条邮件的紧凑、稠密数值表示。
下面的代码实现了这一过程:定义了 text_to_embedding() 函数,将其应用到训练集和测试集,然后训练并评估新的决策树模型。
import numpy as np
# 下载 GloVe 嵌入
!wget -q http://nlp.stanford.edu/data/glove.6B.zip
!unzip -q glove.6B.zip -d glove.6B
# 将嵌入加载到字典中
embeddings_index = {}
with open("glove.6B/glove.6B.50d.txt", encoding="utf8") as f:
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
def text_to_embedding(texts):
vectors = []
for text in texts:
words = text.lower().split()
word_vecs = [embeddings_index[w] for w in words if w in embeddings_index]
if word_vecs:
vectors.append(np.mean(word_vecs, axis=0))
else:
vectors.append(np.zeros(50))
return np.array(vectors)
X_train_emb = text_to_embedding(X_train)
X_test_emb = text_to_embedding(X_test)
tree_emb = DecisionTreeClassifier(random_state=42)
tree_emb.fit(X_train_emb, y_train)
y_pred_emb = tree_emb.predict(X_test_emb)
print("MODEL 2. Decision Tree + Embeddings")
print(classification_report(y_test, y_pred_emb))
结果:
MODEL 2. Decision Tree + Embeddings
precision recall f1-score support
ham 0.95 0.95 0.95 966
spam 0.66 0.69 0.68 149
accuracy 0.91 1115
macro avg 0.81 0.82 0.81 1115
weighted avg 0.91 0.91 0.91 1115
不幸的是,这种简单的平均方法会造成显著的信息丢失,也称为 表示损失(representation loss)。这解释了模型性能相较于 TF-IDF 模型的下降。决策树通常更适合处理稀疏、高信号特征,例如 TF-IDF 特征。这些词级特征可以成为强判别因子(例如,通过邮件中是否出现 “free” 或 “million” 来判断是否为垃圾邮件),这也在很大程度上解释了两种模型的性能差异。
与朴素贝叶斯(Naive Bayes)文本分类器的比较
最后,让我们将结果与另一种流行的文本分类模型——朴素贝叶斯(Naive Bayes)进行比较。虽然它不是基于树的,但在处理 TF-IDF 特征时表现良好。过程与第一个模型非常相似:
from sklearn.naive_bayes import MultinomialNB
nb_model = Pipeline([
("tfidf", TfidfVectorizer()),
("clf", MultinomialNB())
])
nb_model.fit(X_train, y_train)
y_pred_nb = nb_model.predict(X_test)
print("BASELINE. Naive Bayes + TF-IDF")
print(classification_report(y_test, y_pred_nb))
结果:
BASELINE. Naive Bayes + TF-IDF
precision recall f1-score support
ham 0.96 1.00 0.98 966
spam 1.00 0.70 0.83 149
accuracy 0.96 1115
macro avg 0.98 0.85 0.90 1115
weighted avg 0.96 0.96 0.96 1115
对比第一个决策树模型(MODEL 1)与 Naive Bayes 模型,我们发现它们在分类非垃圾邮件(ham)上差异不大。
但在垃圾邮件(spam)分类上,Naive Bayes 模型精确率达到完美(1.00),即所有被识别为垃圾邮件的邮件确实都是垃圾邮件。
然而,其召回率仅为 0.70,在测试集中约 30% 的真实垃圾邮件未被识别。如果召回率是我们最关键的性能指标,我们会倾向于使用 TF-IDF + 决策树的第一个模型,并可进一步通过超参数调优或增加训练数据来优化模型。
总结
本文展示了如何针对文本数据训练决策树模型,以垃圾邮件分类为例,使用常用的文本表示方法(TF-IDF 和向量嵌入)进行训练与评估,并与朴素贝叶斯分类器进行了比较。
更多推荐


所有评论(0)