【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

在本文中,你将学习到:

  • 构建用于垃圾邮件检测的决策树分类器,分析文本数据。
  • 在训练决策树时,结合文本建模技术,如 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 和向量嵌入)进行训练与评估,并与朴素贝叶斯分类器进行了比较。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐