📖 现代表格数据方法¶
学习时间:2-3天 | 难度:⭐⭐⭐⭐ | 代码语言:Python | 前置知识:12-集成学习进阶、09-深度学习进阶
目录¶
- 1. 表格数据的深度学习革命
- 2. TabNet:注意力驱动的特征选择
- 3. FT-Transformer:特征分词器+Transformer
- 4. TabPFN:先验数据拟合网络
- 5. KAN:Kolmogorov-Arnold 网络
- 6. SAINT:自注意力与交叉注意力的表格模型
- 7. 现代 AutoML 框架
- 8. 实践对比:主流方法全方位评测
- 9. 代码实战
- 10. 面试题精选
- 11. 学习检查清单
- 12. 参考资料
1. 表格数据的深度学习革命¶
1.1 为什么传统ML长期"碾压"深度学习?¶
在计算机视觉和NLP领域,深度学习已经全面超越传统方法。然而在表格数据(tabular data)这一最常见的工业数据形态上,GBDT家族(XGBoost、LightGBM、CatBoost)却长期占据统治地位。
表格数据的独特挑战¶
图像/文本数据 表格数据
┌──────────────────┐ ┌──────────────────┐
│ • 空间/序列结构 │ │ • 特征异构(混合类型)│
│ • 局部相关性强 │ │ • 特征间无空间关系 │
│ • 平移不变性 │ │ • 旋转不变性要求 │
│ • 大规模数据常见 │ │ • 数据规模常较小 │
│ • 连续信号 │ │ • 缺失值普遍 │
└──────────────────┘ └──────────────────┘
↓ ↓
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?》总结了三个核心原因:
- 对不相关特征的鲁棒性:树模型天然执行特征选择,而NN会被噪声特征干扰
- 非旋转不变性的数据:真实表格数据中,特征有明确含义,旋转变换没有意义
- 不规则目标函数:真实数据的目标函数往往不平滑,树模型擅长逼近
1.2 深度学习的反击:2020-2026 进展¶
尽管如此,研究者从未放弃让深度学习攻克表格数据,近年来取得了重要突破:
时间线
─────────────────────────────────────────────────────────────→
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机制。
架构图¶
输入特征 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]\) 是第 \(i\) 步的特征掩码(稀疏) - \(P[i]\) 是先验尺度因子,控制已使用特征的衰减 - \(\text{sparsemax}\) 确保掩码稀疏,实现特征选择
先验尺度更新:
\(\gamma\) 控制特征复用程度,\(\gamma=1\) 表示每个特征只用一次。
2.3 优缺点¶
| 优点 | 缺点 |
|---|---|
| 内置特征选择,可解释 | 训练慢于GBDT |
| 端到端学习,无需特征工程 | 超参数敏感(N_steps, γ等) |
| 支持自监督预训练 | 小数据集表现不稳定 |
| 处理缺失值无需填充 | 实际性能常不如调优后的XGBoost |
2.4 代码示例¶
# 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进行处理,是目前最强的深度学习表格模型之一。
架构图¶
数值特征: 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——将每个特征独立映射到同一嵌入空间:
数值特征(线性嵌入):
每个数值特征有独立的线性变换参数。
类别特征(查表嵌入):
为什么每个特征独立嵌入很重要?
- 传统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)¶
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,然后对新的小型表格数据集进行零样本/少样本预测,无需梯度下降训练。
核心理念¶
传统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)生成的合成数据集:
- 随机采样因果图结构
- 在图上定义随机非线性变换
- 生成对应的(X, y)数据集
这使得TabPFN的先验覆盖了广泛的数据生成过程。
架构:
- 将训练集和测试集打包为一个序列
- Transformer通过In-Context Learning(上下文学习)直接输出预测
- 本质上是在做贝叶斯后验推断的近似
4.3 限制与适用场景¶
| 适用场景 | 限制 |
|---|---|
| 小数据集(≤10,000样本) | 不适合大规模数据 |
| 特征数≤100 | 高维特征需降维 |
| 分类任务 | 回归支持有限(v2改善) |
| 快速原型/基线 | 不支持增量学习 |
| AutoML管道中的候选模型 | 内存消耗大 |
4.4 TabPFN v2(2024更新)¶
TabPFN v2 的主要改进: - ✅ 支持回归任务 - ✅ 最大样本数提升至 50,000+ - ✅ 支持最多 500个特征 - ✅ 原生类别特征处理 - ✅ 改进的先验分布和更大的预训练规模
4.5 代码示例¶
# 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}\) 都可以表示为:
其中 \(\phi_{q,p}: [0,1] \to \mathbb{R}\) 和 \(\Phi_q: \mathbb{R} \to \mathbb{R}\) 是一元连续函数。
含义:多元函数可以分解为一元函数的组合,无需多元交互项!
5.2 KAN vs MLP:架构对比¶
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:
其中:
- \(B_i(x)\) 是 B-spline 基函数
- \(c_i\) 是可学习系数
- \(w_b\), \(w_s\) 是可学习标量权重
- 网格点数量控制函数的表达能力
5.4 KAN用于表格数据¶
KAN 特别适合表格数据的原因:
- 特征独立建模:每个特征有独立的激活函数,天然适配异构特征
- 可解释性:可以直接可视化每个特征的变换函数
- 参数高效:在低维数据上比MLP用更少参数达到更好效果
- 科学发现:激活函数形状可暗示底层物理规律
# 可视化KAN学到的激活函数
# 假设KAN学习到 y = sin(x₁) + x₂²
# x₁ 的激活函数: x₂ 的激活函数:
# ↑ φ(x₁) ↑ φ(x₂)
# │ /\ │ /
# │ / \ │ /
# │/ \ → sin(x₁) │ / → x₂²
# │ \ │ /
# --│-------\→ x₁ --│/--------→ x₂
# │ │
5.5 局限性与争议¶
⚠️ KAN目前仍存在重要局限:
- 训练速度慢:B-spline计算比简单矩阵乘法慢得多(约10x)
- 高维困难:当特征数 >100 时,优势消失
- 大数据集表现一般:在大规模tabular benchmarks上未显著超越GBDT
- 内存开销:每条边都有spline参数,内存使用高于等效MLP
- 缺乏成熟工程实现:生态系统不如PyTorch/TensorFlow成熟
社区争议:
- 原论文(Liu et al., 2024)声称在科学任务上显著优于MLP
- 后续复现研究(Yu et al., 2024)表明在标准ML benchmark上优势有限
- 目前共识:KAN在低维科学计算/可解释性场景有价值,不是通用替代方案
5.6 代码示例¶
# 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)同时考虑特征间关系和样本间关系。
双重注意力架构¶
输入样本矩阵 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框架,核心策略是多层模型堆叠。
# 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 内部流程:
原始数据
│
▼
┌─────────────────────────┐
│ 自动特征处理 │
│ • 缺失值填充 │
│ • 类别编码 │
│ • 特征缩放 │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ 多模型并行训练 │
│ • 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:
# 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框架:
# 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 场景选型指南¶
你的表格数据项目
│
▼
数据量 < 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 实战项目:多方法对比评测¶
我们用一个完整的流程,在同一数据集上对比所有方法。
"""
现代表格数据方法:全方位对比实战
数据集: 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 基线¶
# === 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 零样本预测¶
# === 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 实现¶
# === 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 自动建模¶
# === 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 结果汇总¶
# === 汇总对比 ===
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在表格数据上长期优于深度学习?¶
答:主要有三个原因:
- 特征异构性:表格数据特征类型混杂(数值+类别+缺失),GBDT天然处理,DNN需要大量预处理
- 不规则目标函数:真实表格数据的映射关系通常不平滑,树模型的分段常数逼近匹配更好,而NN偏好平滑函数
- 数据规模:工业表格数据通常只有几千到几万样本,GBDT不需要大数据就能表现好;NN在小数据上容易过拟合
- 旋转不变性问题: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特征的二分类任务中,你会如何选择模型?¶
答:
推荐方案(按优先级):
- 快速基线:LightGBM + 5-fold CV → 确定baseline
- 精度提升:AutoGluon
best_qualitypreset → 自动集成 - DL验证:FT-Transformer → 检验DL方法在此数据上是否有优势
- 小数据对比:TabPFN → 若数据量较小,可能有意外好表现
具体选择考量: - 10k样本正好处于GBDT和DL的交叉区域 - 50特征不算高维,各方法均可处理 - 需要可解释性 → TabNet或GBDT + SHAP - 需要最高精度 → AutoGluon - 需要快速迭代 → LightGBM + Optuna调参
Q8: 如果你要将FT-Transformer部署到生产环境,需要注意什么?¶
答:
关键注意事项:
- 推理延迟:Transformer的Self-Attention是 \(O(n^2)\) 复杂度(n=特征数),虽然表格数据n通常不大,但仍比GBDT慢
- 模型大小:每个特征有独立嵌入,模型参数量随特征数线性增长
- 数值稳定性:需要确保输入特征的归一化与训练时一致
- 批量推理:利用GPU批量推理提升吞吐量,避免逐条预测
- 模型导出:可用ONNX或TorchScript导出,减少Python运行时依赖
- 降级策略:建议同时部署一个GBDT模型作为fallback,在DL模型异常时切换
- 监控:监控特征分布漂移,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. 参考资料¶
核心论文¶
- TabNet: Arik & Pfister. "TabNet: Attentive Interpretable Tabular Learning." AAAI 2021
- FT-Transformer: Gorishniy et al. "Revisiting Deep Learning Models for Tabular Data." NeurIPS 2021
- TabPFN: Hollmann et al. "TabPFN: A Transformer That Solves Small Tabular Classification Problems in a Second." ICLR 2023
- KAN: Liu et al. "KAN: Kolmogorov-Arnold Networks." arXiv 2024
- SAINT: Somepalli et al. "SAINT: Improved Neural Networks for Tabular Data." NeurIPS 2021 Workshop
- Why trees?: Grinsztajn et al. "Why do tree-based models still outperform deep learning on typical tabular data?" NeurIPS 2022
- TabR: Gorishniy et al. "TabR: Tabular Deep Learning Meets Nearest Neighbors." ICLR 2024
工具文档¶
- AutoGluon: https://auto.gluon.ai/
- FLAML: https://microsoft.github.io/FLAML/
- PyCaret: https://pycaret.org/
- pytorch-tabnet: https://github.com/dreamquark-ai/tabnet
- TabPFN: https://github.com/automl/TabPFN
- pykan: https://github.com/KindXiaoming/pykan
推荐阅读¶
- 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的最新进展和社区讨论