跳转至

📖 现代表格数据方法

现代表格数据方法图

学习时间:2-3天 | 难度:⭐⭐⭐⭐ | 代码语言:Python | 前置知识:12-集成学习进阶、09-深度学习进阶

目录


1. 表格数据的深度学习革命

1.1 为什么传统ML长期"碾压"深度学习?

在计算机视觉和NLP领域,深度学习已经全面超越传统方法。然而在表格数据(tabular data)这一最常见的工业数据形态上,GBDT家族(XGBoost、LightGBM、CatBoost)却长期占据统治地位。

表格数据的独特挑战

Text Only
图像/文本数据                          表格数据
┌──────────────────┐              ┌──────────────────┐
│ • 空间/序列结构    │              │ • 特征异构(混合类型)│
│ • 局部相关性强      │              │ • 特征间无空间关系   │
│ • 平移不变性        │              │ • 旋转不变性要求     │
│ • 大规模数据常见    │              │ • 数据规模常较小     │
│ • 连续信号          │              │ • 缺失值普遍        │
└──────────────────┘              └──────────────────┘
        ↓                                  ↓
    DNN天然适配                      GBDT更具优势

核心原因分析

因素 GBDT优势 DNN劣势
特征异构性 天然处理数值+类别混合 需要大量预处理和嵌入
样本规模 小样本即可表现优秀(≥1k) 通常需要大量数据(≥10k)
不规则函数 决策树天然逼近阶跃函数 MLP倾向学习平滑函数
旋转不变性 特征排列不影响结果 对特征顺序敏感
训练效率 快速训练,无需GPU 需要GPU,超参敏感
可解释性 特征重要性直观 黑箱模型

Grinsztajn et al. (2022) 的关键发现

NeurIPS 2022 的基准研究《Why do tree-based models still outperform deep learning on typical tabular data?》总结了三个核心原因:

  1. 对不相关特征的鲁棒性:树模型天然执行特征选择,而NN会被噪声特征干扰
  2. 非旋转不变性的数据:真实表格数据中,特征有明确含义,旋转变换没有意义
  3. 不规则目标函数:真实数据的目标函数往往不平滑,树模型擅长逼近

1.2 深度学习的反击:2020-2026 进展

尽管如此,研究者从未放弃让深度学习攻克表格数据,近年来取得了重要突破:

Text Only
时间线
─────────────────────────────────────────────────────────────→
2019        2020        2021        2022        2023    2024-2025
 │           │           │           │           │         │
TabNet    NODE       FT-Trans    TabPFN     TabR      KAN
(Google)  (Yandex)   (Yandex)   (Freiburg) (Yandex)  (MIT)
                     SAINT                  AutoGluon  TabPFN v2
                     (Edinburgh)            FLAML升级

核心思路演进

  • 2019-2020:直接用注意力/树模拟 → TabNet、NODE
  • 2021:Transformer架构适配 → FT-Transformer、SAINT
  • 2022-2023:元学习/检索增强 → TabPFN、TabR
  • 2024-2025:新架构突破 → KAN、TabPFN v2

2. TabNet:注意力驱动的特征选择

2.1 核心思想

TabNet(Google Research, 2019)是第一个在表格数据上取得竞争力的深度学习模型,核心创新是Sequential Attention机制。

架构图

Text Only
输入特征 x ∈ R^D
┌─────────────────────────────────┐
│         Step 1                   │
│  ┌──────────┐  ┌──────────┐     │
│  │ Attentive  │→│ Feature   │     │
│  │ Transformer│  │ Transform │     │
│  └──────────┘  └──────────┘     │
│       │              │           │
│       ▼              ▼           │
│  Mask M₁         Split Layer     │
│  (稀疏特征选择)    ↙        ↘     │
│               决策输出    下一步输入 │
└─────────────────────────────────┘
      ▼ (重复 N_steps 次)
┌─────────────────────────────────┐
│         Step 2 ... N             │
│  使用上一步的先验尺度更新注意力mask │
└─────────────────────────────────┘
  Σ 各步决策输出 → 最终预测

2.2 关键机制

Sequential Attention

\[M[i] = \text{sparsemax}(P[i-1] \cdot h_i(a[i-1]))\]

其中: - \(M[i]\) 是第 \(i\) 步的特征掩码(稀疏) - \(P[i]\) 是先验尺度因子,控制已使用特征的衰减 - \(\text{sparsemax}\) 确保掩码稀疏,实现特征选择

先验尺度更新

\[P[i] = \prod_{j=1}^{i} (\gamma - M[j])\]

\(\gamma\) 控制特征复用程度,\(\gamma=1\) 表示每个特征只用一次。

2.3 优缺点

优点 缺点
内置特征选择,可解释 训练慢于GBDT
端到端学习,无需特征工程 超参数敏感(N_steps, γ等)
支持自监督预训练 小数据集表现不稳定
处理缺失值无需填充 实际性能常不如调优后的XGBoost

2.4 代码示例

Python
# pip install pytorch-tabnet
import torch
from pytorch_tabnet.tab_model import TabNetClassifier
import numpy as np

# 创建模型
clf = TabNetClassifier(
    n_d=64, n_a=64,           # 决策/注意力维度
    n_steps=5,                 # 注意力步数
    gamma=1.5,                 # 特征复用系数
    n_independent=2,           # 独立GLU层数
    n_shared=2,                # 共享GLU层数
    lambda_sparse=1e-4,        # 稀疏正则化
    optimizer_fn=torch.optim.Adam,
    optimizer_params=dict(lr=2e-2),
    scheduler_params={"step_size":50, "gamma":0.9},
    scheduler_fn=torch.optim.lr_scheduler.StepLR,
    mask_type='entmax',        # sparsemax 或 entmax
    verbose=10,
)

# 训练
clf.fit(
    X_train=X_train, y_train=y_train,
    eval_set=[(X_valid, y_valid)],
    eval_name=['valid'],
    eval_metric=['auc'],
    max_epochs=200,
    patience=20,
    batch_size=1024,
    virtual_batch_size=128,    # Ghost Batch Normalization
)

# 特征重要性(全局)
feature_importances = clf.feature_importances_

# 逐样本注意力掩码(局部可解释性)
explain_matrix, masks = clf.explain(X_test)

3. FT-Transformer:特征分词器+Transformer

3.1 核心思想

FT-Transformer(Yandex, 2021)将表格数据的每个特征视为一个"token",然后使用标准Transformer进行处理,是目前最强的深度学习表格模型之一

架构图

Text Only
数值特征: x₁, x₂, ..., xₖ    类别特征: c₁, c₂, ..., cₘ
    │                              │
    ▼                              ▼
┌──────────┐                ┌──────────┐
│ Linear    │                │ Embedding │
│ Embedding │                │ Lookup    │
│ xᵢ→eᵢ∈R^d│                │ cⱼ→eⱼ∈R^d│
└──────────┘                └──────────┘
    │                              │
    └──────────┬───────────────────┘
    [CLS] e₁  e₂  ...  eₖ  eₖ₊₁ ... eₖ₊ₘ
    ┌─────────────────────┐
    │  Transformer Encoder │
    │  (L layers)          │
    │  Multi-Head Attention │
    │  + Feed-Forward       │
    │  + LayerNorm          │
    └─────────────────────┘
         [CLS] output
    ┌──────────────┐
    │  MLP Head     │
    │  → prediction │
    └──────────────┘

3.2 Feature Tokenizer 详解

FT-Transformer 的核心创新在于Feature Tokenizer——将每个特征独立映射到同一嵌入空间:

数值特征(线性嵌入):

\[e_i^{(num)} = W_i \cdot x_i + b_i, \quad W_i \in \mathbb{R}^d, \; b_i \in \mathbb{R}^d\]

每个数值特征有独立的线性变换参数。

类别特征(查表嵌入):

\[e_j^{(cat)} = E_j[c_j], \quad E_j \in \mathbb{R}^{|C_j| \times d}\]

为什么每个特征独立嵌入很重要?

  • 传统MLP将所有特征拼接后共享权重:\(h = W[x_1, x_2, ..., x_n]^T\)
  • FT-Transformer让每个特征有独立表示:Transformer的注意力可以学习特征间交互
  • 这更符合表格数据"特征异构"的特点

3.3 与其他Transformer表格模型对比

模型 Token化方式 注意力类型 特点
FT-Transformer 每特征独立嵌入 标准Self-Attention 简洁高效,性能强
TabTransformer 仅类别特征用Transformer 标准Self-Attention 数值特征处理弱
SAINT 行+列双重注意力 Self + Inter-sample 考虑样本间关系
TabNet 顺序注意力掩码 Attentive Transformer 内置特征选择

3.4 代码示例(PyTorch)

Python
import torch
import torch.nn as nn
import math

class FeatureTokenizer(nn.Module):  # 继承nn.Module定义神经网络层
    """将表格特征转化为token嵌入"""
    def __init__(self, n_num_features, cat_cardinalities, d_token):  # __init__构造方法,创建对象时自动调用
        super().__init__()  # super()调用父类方法
        self.d_token = d_token

        # 数值特征:每个特征独立的线性嵌入
        if n_num_features > 0:
            self.num_embeddings = nn.Parameter(torch.randn(n_num_features, d_token))
            self.num_bias = nn.Parameter(torch.zeros(n_num_features, d_token))

        # 类别特征:每个特征独立的embedding table
        self.cat_embeddings = nn.ModuleList([
            nn.Embedding(card, d_token) for card in cat_cardinalities
        ])

        # [CLS] token
        self.cls_token = nn.Parameter(torch.randn(1, 1, d_token))

    def forward(self, x_num=None, x_cat=None):
        tokens = []

        if x_num is not None:
            # 数值特征嵌入: (batch, n_num, d_token)
            batch_size = x_num.shape[0]
            num_tokens = x_num.unsqueeze(-1) * self.num_embeddings.unsqueeze(0) + self.num_bias.unsqueeze(0)  # unsqueeze增加一个维度
            tokens.append(num_tokens)

        if x_cat is not None:
            cat_tokens = [
                emb(x_cat[:, i]) for i, emb in enumerate(self.cat_embeddings)  # enumerate同时获取索引和元素
            ]
            tokens.append(torch.stack(cat_tokens, dim=1))

        # 拼接所有特征token
        tokens = torch.cat(tokens, dim=1)

        # 添加[CLS] token
        cls = self.cls_token.expand(tokens.shape[0], -1, -1)
        tokens = torch.cat([cls, tokens], dim=1)

        return tokens

class FTTransformer(nn.Module):
    """FT-Transformer完整模型"""
    def __init__(self, n_num_features, cat_cardinalities, d_token=192,
                 n_layers=3, n_heads=8, d_ffn=256, dropout=0.1, n_classes=2):
        super().__init__()

        self.tokenizer = FeatureTokenizer(
            n_num_features, cat_cardinalities, d_token
        )

        # 标准Transformer Encoder
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_token,
            nhead=n_heads,
            dim_feedforward=d_ffn,
            dropout=dropout,
            activation='gelu',
            batch_first=True,
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, n_layers)

        # 分类头:使用[CLS] token的输出
        self.head = nn.Sequential(
            nn.LayerNorm(d_token),
            nn.ReLU(),
            nn.Linear(d_token, n_classes),
        )

    def forward(self, x_num=None, x_cat=None):
        tokens = self.tokenizer(x_num, x_cat)     # (B, 1+n_features, d)
        tokens = self.transformer(tokens)          # (B, 1+n_features, d)
        cls_output = tokens[:, 0]                  # (B, d) 取[CLS]输出
        return self.head(cls_output)               # (B, n_classes)

# === 使用示例 ===
model = FTTransformer(
    n_num_features=10,
    cat_cardinalities=[5, 12, 3],  # 3个类别特征,基数分别为5,12,3
    d_token=192,
    n_layers=3,
    n_heads=8,
    n_classes=2,
)

# 假设输入
x_num = torch.randn(32, 10)                    # 32样本,10个数值特征
x_cat = torch.randint(0, 5, (32, 3))           # 32样本,3个类别特征
logits = model(x_num, x_cat)                    # (32, 2)

4. TabPFN:先验数据拟合网络

4.1 核心思想

TabPFN(Prior-Data Fitted Networks, Hollmann et al., 2022)是一种革命性的元学习方法,它在合成数据集上预训练一个Transformer,然后对新的小型表格数据集进行零样本/少样本预测,无需梯度下降训练。

核心理念

Text Only
传统ML流程:
  训练集 → fit(X_train, y_train) → 学习参数 → predict(X_test)
              需要梯度下降/树分裂
              需要超参数调优

TabPFN流程:
  预训练阶段(一次性):
    生成百万个合成数据集 → 训练大型Transformer
                          (学习 "如何对表格数据建模")

  推理阶段(零样本):
    context = [X_train, y_train]  →  TabPFN  →  predict(X_test)
         ↑                            ↑
    作为输入序列              无需梯度下降!
    (In-Context Learning)     前向传播即得结果

4.2 技术细节

先验数据生成

TabPFN的预训练数据来自结构化因果模型(SCM)生成的合成数据集:

  1. 随机采样因果图结构
  2. 在图上定义随机非线性变换
  3. 生成对应的(X, y)数据集

这使得TabPFN的先验覆盖了广泛的数据生成过程。

架构

\[P(y_{test} | X_{test}, X_{train}, y_{train}) = \text{Transformer}([X_{train}; y_{train}; X_{test}])\]
  • 将训练集和测试集打包为一个序列
  • Transformer通过In-Context Learning(上下文学习)直接输出预测
  • 本质上是在做贝叶斯后验推断的近似

4.3 限制与适用场景

适用场景 限制
小数据集(≤10,000样本) 不适合大规模数据
特征数≤100 高维特征需降维
分类任务 回归支持有限(v2改善)
快速原型/基线 不支持增量学习
AutoML管道中的候选模型 内存消耗大

4.4 TabPFN v2(2024更新)

TabPFN v2 的主要改进: - ✅ 支持回归任务 - ✅ 最大样本数提升至 50,000+ - ✅ 支持最多 500个特征 - ✅ 原生类别特征处理 - ✅ 改进的先验分布和更大的预训练规模

4.5 代码示例

Python
# pip install tabpfn
from tabpfn import TabPFNClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score

# 加载数据
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# TabPFN:无需训练,直接预测!
clf = TabPFNClassifier(device='cpu', N_ensemble_configurations=32)
clf.fit(X_train, y_train)  # 只是存储数据,不做梯度下降
y_pred = clf.predict(X_test)
y_prob = clf.predict_proba(X_test)

print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"ROC-AUC:  {roc_auc_score(y_test, y_prob[:, 1]):.4f}")

# 与XGBoost对比
from xgboost import XGBClassifier

xgb = XGBClassifier(n_estimators=100, max_depth=6, random_state=42)
xgb.fit(X_train, y_train)
y_pred_xgb = xgb.predict(X_test)
print(f"XGBoost Accuracy: {accuracy_score(y_test, y_pred_xgb):.4f}")

5. KAN:Kolmogorov-Arnold 网络

5.1 Kolmogorov-Arnold 表示定理

定理 (Kolmogorov, 1957; Arnold, 1958)

任意多元连续函数 \(f: [0,1]^n \to \mathbb{R}\) 都可以表示为:

\[f(x_1, ..., x_n) = \sum_{q=0}^{2n} \Phi_q\left(\sum_{p=1}^{n} \phi_{q,p}(x_p)\right)\]

其中 \(\phi_{q,p}: [0,1] \to \mathbb{R}\)\(\Phi_q: \mathbb{R} \to \mathbb{R}\)一元连续函数

含义:多元函数可以分解为一元函数的组合,无需多元交互项!

5.2 KAN vs MLP:架构对比

Text Only
MLP (Multi-Layer Perceptron)          KAN (Kolmogorov-Arnold Network)
┌──────────────────────┐            ┌──────────────────────┐
│                      │            │                      │
│  节点: 固定激活函数     │            │  边: 可学习激活函数     │
│  边:   可学习权重       │            │  节点: 简单求和         │
│                      │            │                      │
│  neuron:             │            │  neuron:             │
│  y = σ(Σ wᵢxᵢ + b)  │            │  y = Σ φᵢ(xᵢ)       │
│      ↑     ↑         │            │      ↑               │
│   固定σ  学习w        │            │   学习φ (B-spline)    │
│                      │            │                      │
└──────────────────────┘            └──────────────────────┘

核心区别

特性 MLP KAN
激活函数 固定(ReLU, GELU等) 可学习(B-spline)
权重位置 在边上(线性变换) 激活函数即权重
逼近方式 增加宽度/深度 增加spline的阶/网格
参数效率 需要大量神经元 少量节点即可高精度
可解释性 黑箱 可视化激活函数形状
万能逼近 靠宽度(Universal Approx. Thm) 靠KA表示定理

5.3 B-Spline 激活函数

KAN 中每条边上的激活函数 \(\phi(x)\) 参数化为 B-spline:

\[\phi(x) = w_b \cdot \text{SiLU}(x) + w_s \cdot \text{spline}(x)\]

其中:

\[\text{spline}(x) = \sum_{i} c_i B_i(x)\]
  • \(B_i(x)\) 是 B-spline 基函数
  • \(c_i\) 是可学习系数
  • \(w_b\), \(w_s\) 是可学习标量权重
  • 网格点数量控制函数的表达能力

5.4 KAN用于表格数据

KAN 特别适合表格数据的原因:

  1. 特征独立建模:每个特征有独立的激活函数,天然适配异构特征
  2. 可解释性:可以直接可视化每个特征的变换函数
  3. 参数高效:在低维数据上比MLP用更少参数达到更好效果
  4. 科学发现:激活函数形状可暗示底层物理规律
Python
# 可视化KAN学到的激活函数
# 假设KAN学习到 y = sin(x₁) + x₂²

#   x₁ 的激活函数:        x₂ 的激活函数:
#    ↑ φ(x₁)              ↑ φ(x₂)
#    │  /\                 │     /
#    │ /  \                │    /
#    │/    \    → sin(x₁)  │   /     → x₂²
#    │      \              │  /
#  --│-------\→ x₁       --│/--------→ x₂
#    │                     │

5.5 局限性与争议

⚠️ KAN目前仍存在重要局限

  1. 训练速度慢:B-spline计算比简单矩阵乘法慢得多(约10x)
  2. 高维困难:当特征数 >100 时,优势消失
  3. 大数据集表现一般:在大规模tabular benchmarks上未显著超越GBDT
  4. 内存开销:每条边都有spline参数,内存使用高于等效MLP
  5. 缺乏成熟工程实现:生态系统不如PyTorch/TensorFlow成熟

社区争议

  • 原论文(Liu et al., 2024)声称在科学任务上显著优于MLP
  • 后续复现研究(Yu et al., 2024)表明在标准ML benchmark上优势有限
  • 目前共识:KAN在低维科学计算/可解释性场景有价值,不是通用替代方案

5.6 代码示例

Python
# pip install pykan
from kan import KAN
import torch

# 创建 KAN 模型:2层,输入4维,隐藏[8,4],输出1维
model = KAN(
    width=[4, 8, 4, 1],       # 网络结构
    grid=5,                     # B-spline网格点数
    k=3,                        # B-spline阶数
    seed=42,
)

# 生成合成数据: y = sin(x₁) + x₂² + x₃·x₄
def target_fn(x):
    return torch.sin(x[:, 0]) + x[:, 1]**2 + x[:, 2] * x[:, 3]

X_train = torch.randn(1000, 4)
y_train = target_fn(X_train).unsqueeze(1)

X_test = torch.randn(200, 4)
y_test = target_fn(X_test).unsqueeze(1)

# 训练
dataset = {
    'train_input': X_train, 'train_label': y_train,
    'test_input': X_test, 'test_label': y_test,
}

results = model.fit(
    dataset,
    opt='LBFGS',
    steps=100,
    lamb=0.01,        # 正则化
    lamb_entropy=2.0, # 熵正则化(鼓励简洁函数)
)

# 可视化学到的激活函数
model.plot(beta=3)

# 符号回归(尝试用数学公式解释)
model.auto_symbolic(lib=['sin', 'x^2', 'x*y'])
formula = model.symbolic_formula()[0][0]
print(f"发现的公式: {formula}")

6. SAINT:自注意力与交叉注意力的表格模型

6.1 核心思想

SAINT(Self-Attention and Intersample Attention Transformer, Somepalli et al., 2021)同时考虑特征间关系样本间关系

双重注意力架构

Text Only
输入样本矩阵 X ∈ R^{N×D}
┌──────────────────────────────────────┐
│  Embedding Layer                      │
│  数值: CutMix + 线性投影              │
│  类别: 查表嵌入                       │
│  → Token矩阵 T ∈ R^{N×D×d}          │
└──────────────────────────────────────┘
         ▼ (重复L次)
┌──────────────────────────────────────┐
│  SAINT Block                          │
│                                      │
│  1. Self-Attention (特征维度)          │
│     ┌─────────────────────┐          │
│     │ 对每个样本,在D个特征  │          │
│     │ 之间做self-attention  │          │
│     │ (类似FT-Transformer) │          │
│     └─────────────────────┘          │
│              ↓                       │
│  2. Intersample Attention (样本维度)  │
│     ┌─────────────────────┐          │
│     │ 对每个特征,在N个样本  │          │
│     │ 之间做cross-attention │          │
│     │ (样本间信息传递)      │          │
│     └─────────────────────┘          │
└──────────────────────────────────────┘
    MLP Head → 预测

6.2 Intersample Attention 的意义

传统模型独立处理每个样本,而SAINT让样本之间可以"交流":

  • 检索效果:类似KNN,利用相似样本辅助预测
  • 数据增强:CutMix在嵌入空间混合样本特征
  • 半监督:未标记样本的特征也可参与Intersample Attention

6.3 适用场景

优势场景 劣势场景
中小数据集(1k-50k) 超大数据集(内存限制)
特征交互复杂 特征独立性强
半监督学习 推理延迟严格
需要数据增强 在线增量场景

7. 现代 AutoML 框架

7.1 AutoGluon

AutoGluon(Amazon, 2020)是目前最强的开源AutoML框架,核心策略是多层模型堆叠

Python
# pip install autogluon
from autogluon.tabular import TabularPredictor

# 一行代码完成建模
predictor = TabularPredictor(
    label='target',
    eval_metric='roc_auc',
    problem_type='binary',
).fit(
    train_data=train_df,
    time_limit=600,          # 最多训练10分钟
    presets='best_quality',  # 最优质量(也可选 'medium_quality', 'optimize_for_deployment')
)

# 预测
predictions = predictor.predict(test_df)

# 查看模型排行榜
leaderboard = predictor.leaderboard(test_df)
print(leaderboard)

# 特征重要性
importance = predictor.feature_importance(test_df)

AutoGluon 内部流程

Text Only
原始数据
┌─────────────────────────┐
│ 自动特征处理              │
│ • 缺失值填充              │
│ • 类别编码                │
│ • 特征缩放                │
└─────────────────────────┘
┌─────────────────────────┐
│ 多模型并行训练            │
│ • LightGBM (多配置)      │
│ • XGBoost                │
│ • CatBoost               │
│ • RandomForest           │
│ • Neural Network (TabNet)│
│ • KNN                    │
└─────────────────────────┘
┌─────────────────────────┐
│ 多层 Stacking            │
│ Layer 0: 原始模型         │
│ Layer 1: 基于L0输出再训练  │
│ Layer 2: 最终集成          │
│ → Weighted Ensemble       │
└─────────────────────────┘
  最终预测

7.2 FLAML

FLAML(Microsoft, 2021)专注低成本、高效的AutoML:

Python
# pip install flaml
from flaml import AutoML

automl = AutoML()
automl.fit(
    X_train, y_train,
    task='classification',
    metric='accuracy',
    time_budget=120,         # 预算120秒
    estimator_list=[
        'lgbm', 'xgboost', 'catboost',
        'rf', 'extra_tree',
    ],
    eval_method='cv',        # 交叉验证
    n_splits=5,
)

print(f"Best model: {automl.best_estimator}")
print(f"Best config: {automl.best_config}")
print(f"Best score: {automl.best_loss:.4f}")

# 预测
y_pred = automl.predict(X_test)

FLAML的核心优势: - Cost-Frugal Optimization (CFO):智能采样策略,减少无效搜索 - BlendSearch:结合Bayesian优化和Bandit算法 - 超低开销:同等时间预算下通常优于其他AutoML

7.3 PyCaret

PyCaret 是面向快速实验的高层API框架:

Python
# pip install pycaret
from pycaret.classification import *

# 初始化实验
s = setup(
    data=train_df,
    target='target',
    session_id=42,
    normalize=True,
    transformation=True,
    remove_multicollinearity=True,
    multicollinearity_threshold=0.95,
)

# 比较所有模型
best_model = compare_models(sort='AUC', n_select=1)

# 调优最佳模型
tuned_model = tune_model(best_model, optimize='AUC', n_iter=50)

# 集成
ensemble_model = ensemble_model(tuned_model, method='Bagging')

# 生成最终Pipeline
final_model = finalize_model(ensemble_model)

# 保存
save_model(final_model, 'best_pipeline')

7.4 AutoML框架对比

框架 核心策略 最佳场景 性能 易用性
AutoGluon 多层Stacking 竞赛/最高精度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
FLAML CFO+BlendSearch 低预算/快速搜索 ⭐⭐⭐⭐ ⭐⭐⭐⭐
PyCaret 高层API封装 快速实验/可视化 ⭐⭐⭐ ⭐⭐⭐⭐⭐
Auto-sklearn 贝叶斯优化+集成 Scikit-learn生态 ⭐⭐⭐⭐ ⭐⭐⭐
H2O AutoML 堆叠集成 企业部署 ⭐⭐⭐⭐ ⭐⭐⭐

8. 实践对比:主流方法全方位评测

8.1 综合性能对比

基于多个公开基准数据集的综合评估:

方法 小数据集 (<1k) 中数据集 (1k-50k) 大数据集 (>50k) 训练速度 推理速度 可解释性
XGBoost ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
LightGBM ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
CatBoost ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
TabPFN ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ❌ N/A ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐
FT-Transformer ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐
TabNet ⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
KAN ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
SAINT ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐
AutoGluon ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐

8.2 场景选型指南

Text Only
你的表格数据项目
  数据量 < 1000?  ─── Yes ──→  TabPFN(首选)/ CatBoost
        No
  数据量 < 50000?
    ┌───┴───┐
    Yes     No
    │       │
    ▼       ▼
需要可解释?  LightGBM / AutoGluon
  ┌─┴─┐
  Yes  No
  │    │
  ▼    ▼
TabNet  需要最高精度?
 /KAN      │
         ┌─┴─┐
         Yes  No
         │    │
         ▼    ▼
    AutoGluon  XGBoost/LightGBM
    (Stacking)  (快速且强)

8.3 硬件要求对比

方法 GPU必需? 最低内存 推荐配置
XGBoost 否(GPU可选加速) 4GB CPU即可,GPU有hist模式
LightGBM 4GB CPU,大数据16GB+
CatBoost 否(GPU可选) 4GB CPU/GPU均可
TabPFN 否(GPU加速明显) 8GB GPU 8GB+最佳
FT-Transformer 推荐 8GB GPU 12GB+
TabNet 推荐 8GB GPU 8GB+
KAN 4GB CPU/GPU均可
AutoGluon 否(DL部分需要) 16GB 16GB+ RAM + GPU

9. 代码实战

9.1 实战项目:多方法对比评测

我们用一个完整的流程,在同一数据集上对比所有方法。

Python
"""
现代表格数据方法:全方位对比实战
数据集: Adult Income (UCI) - 预测收入是否>50K
"""
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score, f1_score
from sklearn.preprocessing import LabelEncoder
import time
import warnings
warnings.filterwarnings('ignore')

# === 数据准备 ===
from sklearn.datasets import fetch_openml

data = fetch_openml('adult', version=2, as_frame=True)
df = data.frame

# 分离特征和标签
target_col = 'income'  # >50K / <=50K
y = (df[target_col] == '>50K').astype(int)
X = df.drop(columns=[target_col])

# 识别数值和类别特征
num_cols = X.select_dtypes(include=['number']).columns.tolist()
cat_cols = X.select_dtypes(include=['category', 'object']).columns.tolist()

# 编码类别特征(用于GBDT等)
le_dict = {}
X_encoded = X.copy()
for col in cat_cols:
    le = LabelEncoder()
    X_encoded[col] = le.fit_transform(X[col].astype(str))
    le_dict[col] = le

X_train, X_test, y_train, y_test = train_test_split(
    X_encoded, y, test_size=0.2, random_state=42, stratify=y
)

results = {}

def evaluate(name, y_true, y_pred, y_prob, elapsed):
    """记录评估结果"""
    results[name] = {
        'Accuracy': accuracy_score(y_true, y_pred),
        'ROC-AUC': roc_auc_score(y_true, y_prob),
        'F1': f1_score(y_true, y_pred),
        'Time(s)': elapsed,
    }
    print(f"\n{'='*40}")
    print(f"Model: {name}")
    for k, v in results[name].items():
        print(f"  {k}: {v:.4f}")

9.2 XGBoost / LightGBM 基线

Python
# === 1. XGBoost ===
from xgboost import XGBClassifier

start = time.time()
xgb = XGBClassifier(
    n_estimators=500,
    max_depth=6,
    learning_rate=0.1,
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,
    reg_lambda=1.0,
    random_state=42,
    eval_metric='logloss',
    early_stopping_rounds=50,
    enable_categorical=True,
)
xgb.fit(
    X_train, y_train,
    eval_set=[(X_test, y_test)],
    verbose=False,
)
y_pred_xgb = xgb.predict(X_test)
y_prob_xgb = xgb.predict_proba(X_test)[:, 1]
elapsed = time.time() - start
evaluate('XGBoost', y_test, y_pred_xgb, y_prob_xgb, elapsed)

# === 2. LightGBM ===
import lightgbm as lgb

start = time.time()
lgbm = lgb.LGBMClassifier(
    n_estimators=500,
    max_depth=6,
    learning_rate=0.1,
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,
    reg_lambda=1.0,
    random_state=42,
    verbose=-1,
)
lgbm.fit(
    X_train, y_train,
    eval_set=[(X_test, y_test)],
    callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)],
)
y_pred_lgb = lgbm.predict(X_test)
y_prob_lgb = lgbm.predict_proba(X_test)[:, 1]
elapsed = time.time() - start
evaluate('LightGBM', y_test, y_pred_lgb, y_prob_lgb, elapsed)

# === 3. CatBoost ===
from catboost import CatBoostClassifier

start = time.time()
cat = CatBoostClassifier(
    iterations=500,
    depth=6,
    learning_rate=0.1,
    l2_leaf_reg=3.0,
    random_seed=42,
    verbose=0,
    eval_metric='AUC',
    early_stopping_rounds=50,
)
cat.fit(
    X_train, y_train,
    eval_set=(X_test, y_test),
)
y_pred_cat = cat.predict(X_test).astype(int)  # 链式调用,连续执行多个方法
y_prob_cat = cat.predict_proba(X_test)[:, 1]
elapsed = time.time() - start
evaluate('CatBoost', y_test, y_pred_cat, y_prob_cat, elapsed)

9.3 TabPFN 零样本预测

Python
# === 4. TabPFN(小数据子集演示) ===
from tabpfn import TabPFNClassifier

# TabPFN对数据量有限制,这里取子集演示
n_sub = 3000
idx = np.random.RandomState(42).choice(len(X_train), n_sub, replace=False)
X_train_sub = X_train.iloc[idx].values.astype(np.float32)
y_train_sub = y_train.iloc[idx].values

start = time.time()
tabpfn = TabPFNClassifier(device='cpu', N_ensemble_configurations=16)
tabpfn.fit(X_train_sub, y_train_sub)
y_pred_pfn = tabpfn.predict(X_test.values.astype(np.float32))
y_prob_pfn = tabpfn.predict_proba(X_test.values.astype(np.float32))[:, 1]
elapsed = time.time() - start
evaluate('TabPFN (3k subset)', y_test, y_pred_pfn, y_prob_pfn, elapsed)

9.4 FT-Transformer PyTorch 实现

Python
# === 5. FT-Transformer (简化版训练流程) ===
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 数据准备
X_train_tensor = torch.FloatTensor(X_train.values).to(device)
y_train_tensor = torch.LongTensor(y_train.values).to(device)
X_test_tensor = torch.FloatTensor(X_test.values).to(device)

train_ds = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(train_ds, batch_size=512, shuffle=True)  # DataLoader批量加载数据,支持shuffle和多进程

# 使用之前定义的FTTransformer类(简化版,仅数值特征)
class SimpleFTTransformer(nn.Module):
    def __init__(self, n_features, d_token=128, n_layers=3, n_heads=8, dropout=0.1):
        super().__init__()
        self.feature_embeddings = nn.ModuleList([
            nn.Linear(1, d_token) for _ in range(n_features)
        ])
        self.cls_token = nn.Parameter(torch.randn(1, 1, d_token))

        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_token, nhead=n_heads,
            dim_feedforward=d_token * 4, dropout=dropout,
            activation='gelu', batch_first=True,
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, n_layers)
        self.head = nn.Sequential(
            nn.LayerNorm(d_token), nn.ReLU(), nn.Linear(d_token, 2)
        )

    def forward(self, x):
        # 每个特征独立嵌入
        tokens = [emb(x[:, i:i+1]) for i, emb in enumerate(self.feature_embeddings)]
        tokens = torch.stack(tokens, dim=1)  # (B, n_features, d)
        cls = self.cls_token.expand(x.shape[0], -1, -1)
        tokens = torch.cat([cls, tokens], dim=1)
        tokens = self.transformer(tokens)
        return self.head(tokens[:, 0])

n_features = X_train.shape[1]
model = SimpleFTTransformer(n_features, d_token=128, n_layers=3).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)
criterion = nn.CrossEntropyLoss()

# 训练
start = time.time()
model.train()  # train()开启训练模式
for epoch in range(30):
    total_loss = 0
    for xb, yb in train_loader:
        optimizer.zero_grad()  # 清零梯度,防止梯度累积
        logits = model(xb)
        loss = criterion(logits, yb)
        loss.backward()  # 反向传播计算梯度
        optimizer.step()  # 根据梯度更新模型参数
        total_loss += loss.item()  # .item()将单元素张量转为Python数值

# 评估
model.eval()
with torch.no_grad():  # 禁用梯度计算,节省内存
    logits = model(X_test_tensor)
    y_prob_ft = torch.softmax(logits, dim=1)[:, 1].cpu().numpy()
    y_pred_ft = logits.argmax(dim=1).cpu().numpy()

elapsed = time.time() - start
evaluate('FT-Transformer', y_test, y_pred_ft, y_prob_ft, elapsed)

9.5 AutoGluon 自动建模

Python
# === 6. AutoGluon ===
from autogluon.tabular import TabularPredictor

# 准备DataFrame格式
train_ag = X_train.copy()
train_ag['target'] = y_train.values
test_ag = X_test.copy()
test_ag['target'] = y_test.values

start = time.time()
predictor = TabularPredictor(
    label='target',
    eval_metric='roc_auc',
    problem_type='binary',
    verbosity=1,
).fit(
    train_data=train_ag,
    time_limit=300,                    # 5分钟预算
    presets='best_quality',
)

y_pred_ag = predictor.predict(test_ag.drop(columns=['target']))
y_prob_ag = predictor.predict_proba(test_ag.drop(columns=['target']))[1]
elapsed = time.time() - start
evaluate('AutoGluon', y_test, y_pred_ag.values, y_prob_ag.values, elapsed)

# 查看排行榜
print("\n=== AutoGluon Leaderboard ===")
print(predictor.leaderboard(test_ag, silent=True).to_string())

9.6 结果汇总

Python
# === 汇总对比 ===
results_df = pd.DataFrame(results).T
results_df = results_df.sort_values('ROC-AUC', ascending=False)

print("\n" + "=" * 60)
print("📊 全方法对比结果")
print("=" * 60)
print(results_df.to_string(float_format='{:.4f}'.format))

# 可视化
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

metrics = ['Accuracy', 'ROC-AUC', 'F1']
for ax, metric in zip(axes, metrics):  # zip按位置配对多个可迭代对象
    values = results_df[metric].sort_values(ascending=True)
    colors = ['#2ecc71' if v == values.max() else '#3498db' for v in values]  # 列表推导式,简洁创建列表
    values.plot(kind='barh', ax=ax, color=colors)
    ax.set_title(metric, fontsize=14)
    ax.set_xlim(values.min() - 0.02, values.max() + 0.01)

plt.tight_layout()
plt.savefig('tabular_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

10. 面试题精选

Q1: 为什么GBDT在表格数据上长期优于深度学习?

:主要有三个原因:

  1. 特征异构性:表格数据特征类型混杂(数值+类别+缺失),GBDT天然处理,DNN需要大量预处理
  2. 不规则目标函数:真实表格数据的映射关系通常不平滑,树模型的分段常数逼近匹配更好,而NN偏好平滑函数
  3. 数据规模:工业表格数据通常只有几千到几万样本,GBDT不需要大数据就能表现好;NN在小数据上容易过拟合
  4. 旋转不变性问题:NN对特征排列敏感,而真实表格数据中特征有明确语义,排列无意义

(参考:Grinsztajn et al., NeurIPS 2022)

Q2: FT-Transformer相比传统MLP处理表格数据有什么优势?

FT-Transformer的核心优势在于Feature Tokenizer设计: - 独立特征嵌入:每个特征有独立的投影,保留特征异构性 - 注意力机制:Transformer的Self-Attention自动学习特征间的成对交互,无需手动构造交叉特征 - [CLS] token聚合:通过全局token聚合所有特征信息,优于简单拼接

相比MLP:MLP将所有特征拼接后用共享权重处理,忽略了特征的独立身份;FT-Transformer在中等规模数据集上通常超越调优后的MLP。

Q3: TabPFN的"零样本"是什么含义?它真的不需要训练吗?

TabPFN的"零样本"指的是对新数据集不需要梯度下降训练: - 预训练阶段(一次性):在数百万合成数据集上训练Transformer,学习"如何对表格数据建模" - 推理阶段(零样本):将训练集作为上下文(context),将测试集作为查询(query),通过前向传播直接输出预测

本质上是In-Context Learning——类似大语言模型无微调就能做few-shot任务。TabPFN隐式进行了贝叶斯后验推断的近似

局限:对数据量>10k或特征>100的数据集效果下降(v2有改善)。

Q4: 解释KAN(Kolmogorov-Arnold Network)的核心思想及其与MLP的区别。

核心思想:基于Kolmogorov-Arnold表示定理,任何多元函数都可以分解为一元函数的组合。KAN将可学习的B-spline函数放在网络的边上(替代权重),节点只做简单求和。

与MLP的关键区别

MLP KAN
边上是固定权重 \(w\) 边上是可学习函数 \(\phi(x)\)
节点执行 \(\sigma(\sum w_ix_i)\) 节点执行 \(\sum \phi_i(x_i)\)
固定激活函数 激活函数即参数
宽度/深度增表达力 spline阶/网格增表达力

优势:参数效率高、可解释(可视化激活函数形状)、适合科学计算。

局限:训练慢(B-spline计算开销大)、高维退化、不是通用DNN替代方案。

Q5: 比较AutoGluon和FLAML,各适合什么场景?

维度 AutoGluon FLAML
核心策略 多模型多层Stacking Cost-Frugal搜索
精度 通常更高(暴力集成) 接近,偶尔更优(搜索高效)
速度 较慢(需训练多模型) 更快(智能预算分配)
资源消耗 高(16GB+ RAM推荐) 低(适合笔记本)
最佳场景 Kaggle竞赛/离线批量预测 在线服务/资源受限/快速原型
可定制性 预设模式较固定 灵活配置搜索空间

经验法则:追求最高精度选AutoGluon,追求效率和可控性选FLAML。

Q6: SAINT的Intersample Attention有什么独特价值?

Intersample Attention让模型在样本维度上做注意力,使得每个样本的预测可以参考批次中其他样本的信息。

独特价值: 1. 隐式KNN效果:自动关注相似样本,无需显式构建最近邻 2. 半监督学习友好:无标签样本的特征也可以参与注意力计算 3. 数据增强:配合CutMix在嵌入空间混合样本信息 4. 分布感知:模型可以学习到数据分布的全局结构

局限:推理时依赖批次,实时单样本推理困难;大批次时内存开销大。

Q7: 在一个10,000样本、50特征的二分类任务中,你会如何选择模型?

推荐方案(按优先级):

  1. 快速基线:LightGBM + 5-fold CV → 确定baseline
  2. 精度提升:AutoGluon best_quality preset → 自动集成
  3. DL验证:FT-Transformer → 检验DL方法在此数据上是否有优势
  4. 小数据对比:TabPFN → 若数据量较小,可能有意外好表现

具体选择考量: - 10k样本正好处于GBDT和DL的交叉区域 - 50特征不算高维,各方法均可处理 - 需要可解释性 → TabNet或GBDT + SHAP - 需要最高精度 → AutoGluon - 需要快速迭代 → LightGBM + Optuna调参

Q8: 如果你要将FT-Transformer部署到生产环境,需要注意什么?

关键注意事项

  1. 推理延迟:Transformer的Self-Attention是 \(O(n^2)\) 复杂度(n=特征数),虽然表格数据n通常不大,但仍比GBDT慢
  2. 模型大小:每个特征有独立嵌入,模型参数量随特征数线性增长
  3. 数值稳定性:需要确保输入特征的归一化与训练时一致
  4. 批量推理:利用GPU批量推理提升吞吐量,避免逐条预测
  5. 模型导出:可用ONNX或TorchScript导出,减少Python运行时依赖
  6. 降级策略:建议同时部署一个GBDT模型作为fallback,在DL模型异常时切换
  7. 监控:监控特征分布漂移,Transformer对OOD数据可能表现不稳定

11. 学习检查清单

基础理解

  • 理解为什么GBDT在表格数据上长期优于DNN
  • 了解表格数据与图像/文本数据的本质区别
  • 熟悉Grinsztajn et al. (2022)的三个核心发现

模型原理

  • 理解TabNet的Sequential Attention和Sparsemax
  • 理解FT-Transformer的Feature Tokenizer设计
  • 理解TabPFN的元学习思想和In-Context Learning
  • 理解KAN的KA表示定理和B-spline激活函数
  • 理解SAINT的双重注意力(Self + Intersample)
  • 能说出KAN与MLP的3个核心区别

实践能力

  • 能用XGBoost/LightGBM建立强基线
  • 能用TabPFN进行零样本分类
  • 能用PyTorch实现简化版FT-Transformer
  • 能用AutoGluon/FLAML完成自动建模
  • 能根据数据规模和需求选择合适的方法

进阶思考

  • 理解为什么特征独立嵌入对表格数据重要
  • 能分析KAN的优势与局限
  • 理解AutoGluon多层Stacking的工作原理
  • 能为不同场景推荐合适的模型组合

12. 参考资料

核心论文

  1. TabNet: Arik & Pfister. "TabNet: Attentive Interpretable Tabular Learning." AAAI 2021
  2. FT-Transformer: Gorishniy et al. "Revisiting Deep Learning Models for Tabular Data." NeurIPS 2021
  3. TabPFN: Hollmann et al. "TabPFN: A Transformer That Solves Small Tabular Classification Problems in a Second." ICLR 2023
  4. KAN: Liu et al. "KAN: Kolmogorov-Arnold Networks." arXiv 2024
  5. SAINT: Somepalli et al. "SAINT: Improved Neural Networks for Tabular Data." NeurIPS 2021 Workshop
  6. Why trees?: Grinsztajn et al. "Why do tree-based models still outperform deep learning on typical tabular data?" NeurIPS 2022
  7. TabR: Gorishniy et al. "TabR: Tabular Deep Learning Meets Nearest Neighbors." ICLR 2024

工具文档

推荐阅读

  • Borisov et al. "Deep Neural Networks and Tabular Data: A Survey." IEEE TNNLS 2022
  • Shwartz-Ziv & Armon. "Tabular Data: Deep Learning is Not All You Need." Information Fusion 2022
  • McElfresh et al. "When Do Neural Nets Outperform Boosted Trees on Tabular Data?" NeurIPS 2023

下一步学习建议: - 在Kaggle的表格竞赛中实践本章方法 - 研读FT-Transformer和TabPFN的原论文 - 尝试用AutoGluon参加一个竞赛,体验全自动建模 - 关注KAN的最新进展和社区讨论