Scikit-learn 1.8 完全指南:GPU 加速、免GIL多线程与七大新特性实战

实战拆解 scikit-learn 1.8 七大核心新特性:通过 Array API 实现 GPU 10倍加速、免GIL自由线程真正多核并行、温度缩放校准、Gap Safe Screening 高维加速、经典多维缩放等,附完整代码示例。

2025年12月,scikit-learn 终于发布了 1.8 版本——说"终于"是因为这次更新的幅度之大,社区已经期待很久了。作为 Python 机器学习生态里最核心的库(没有之一),scikit-learn 1.8 可以说是近几年来最重要的一次迭代。GPU 加速、免 GIL 多线程、温度缩放校准、经典多维缩放……每一项都值得单独拿出来聊。

所以,这篇文章会用实战代码带你逐一拆解这七大核心新特性。不管你是刚接触 sklearn 的新手,还是用了好几年的老用户,相信都能从中找到让你眼前一亮的东西。

一、Array API 支持与 GPU 加速

1.1 为什么需要 Array API

说实话,scikit-learn "只能跑在 CPU 上"这件事,一直是它被人诟病的痛点。数据集一旦到了百万级别,训练速度就让人有点抓狂。

1.8 版本通过拥抱 Python Array API 标准,终于迈出了关键一步。现在 PyTorch 张量和 CuPy 数组都可以直接丢给估计器,让 GPU 来干活。实测下来,典型工作负载能获得大约10倍的加速——这个提升不是挤牙膏,是实打实的。

更赞的是,scikit-learn 本身不会因此变重。GPU 计算是通过你自己选的数组库(CuPy 或 PyTorch)来完成的,主包依旧保持轻量。

1.2 支持 Array API 的估计器

1.8 版本中已经支持 Array API 的估计器和工具函数还挺多的,这里列一下主要的:

  • 降维PCA(需指定 svd_solver
  • 线性模型RidgeRidgeCVRidgeClassifierRidgeClassifierCV(需设置 solver="svd"
  • 判别分析LinearDiscriminantAnalysis(需设置 solver="svd"
  • 朴素贝叶斯GaussianNB
  • 聚类GaussianMixture(需设置 init_params="random""random_from_data"
  • 预处理StandardScalerMinMaxScalerMaxAbsScalerNormalizerPolynomialFeaturesBinarizerLabelEncoder
  • 校准CalibratedClassifierCV(使用 method="temperature"
  • 模型选择GridSearchCVRandomizedSearchCVcross_val_predicttrain_test_split
  • 评估指标accuracy_scoref1_scoreconfusion_matrixroc_curvebrier_score_losslog_loss 等几十个函数

1.3 使用 CuPy 实现 GPU 加速

CuPy 大概是上手 GPU 加速最直接的方式了。先设好环境变量 SCIPY_ARRAY_API=1,然后在代码里开启 array_api_dispatch

import os
os.environ["SCIPY_ARRAY_API"] = "1"

import cupy
from sklearn.datasets import make_classification
from sklearn import config_context
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

# 生成示例数据
X_np, y_np = make_classification(
    n_samples=50000, n_features=200, random_state=42
)

# 将数据转移到 GPU
X_cu = cupy.asarray(X_np)
y_cu = cupy.asarray(y_np)

# 启用 Array API 调度并训练模型
with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis(solver="svd")
    X_trans = lda.fit_transform(X_cu, y_cu)
    print(type(X_trans))  # <class 'cupy.ndarray'>

# 如需将结果转回 NumPy
X_trans_np = X_trans.get()

就这么简单。数据放到 GPU 上,模型训练也自动在 GPU 上跑,返回的结果还是 CuPy 数组。需要的话再 .get() 转回 NumPy 就行。

1.4 使用 PyTorch 张量在 CUDA 上计算

如果你的项目本来就在用 PyTorch(大概率是吧),那就更方便了——直接把 CUDA 张量扔给 sklearn:

import os
os.environ["SCIPY_ARRAY_API"] = "1"

import torch
import numpy as np
from sklearn import config_context
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 准备 GPU 上的 PyTorch 张量
X_np = np.random.randn(10000, 50).astype(np.float32)
X_torch = torch.asarray(X_np, device="cuda", dtype=torch.float32)

with config_context(array_api_dispatch=True):
    # 标准化 + PCA 流水线,全部在 GPU 上执行
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_torch)

    pca = PCA(n_components=10, svd_solver="full")
    X_pca = pca.fit_transform(X_scaled)

    print(X_pca.device)  # cuda:0
    print(X_pca.shape)   # torch.Size([10000, 10])

1.5 GPU 加速流水线的完整示例

来看一个更完整的例子,把数据转换、模型训练、概率校准、交叉验证全都串在 GPU 上跑:

import os
os.environ["SCIPY_ARRAY_API"] = "1"

import numpy as np
import torch
import sklearn
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.calibration import CalibratedClassifierCV
from sklearn.linear_model import RidgeClassifierCV
from sklearn.model_selection import cross_validate
from sklearn.datasets import make_classification

X_np, y_np = make_classification(
    n_samples=20000, n_features=100, n_classes=3,
    n_informative=30, random_state=42
)

alphas = np.logspace(-3, 3, 20)

# 构建 GPU 加速流水线
gpu_pipeline = make_pipeline(
    StandardScaler(),
    FunctionTransformer(
        lambda x: torch.tensor(
            x if isinstance(x, np.ndarray) else x.numpy(),
            device="cuda", dtype=torch.float32
        )
    ),
    CalibratedClassifierCV(
        RidgeClassifierCV(alphas=alphas),
        method="temperature"
    ),
)

with sklearn.config_context(array_api_dispatch=True):
    results = cross_validate(gpu_pipeline, X_np, y_np, cv=5)
    print(f"平均准确率: {results['test_score'].mean():.4f}")

二、免 GIL 的自由线程 CPython 3.14 支持

2.1 什么是自由线程 CPython

如果你写过 Python 多线程代码,大概都被 GIL 折磨过。全局解释器锁(GIL)确保同一时刻只有一个线程在执行 Python 字节码——也就是说,哪怕你的机器有 64 个核,纯 Python 多线程也只能干看着。

CPython 3.14 带来了自由线程(free-threaded)构建版本,直接把 GIL 给摘掉了。多个线程终于可以真正同时执行 Python 代码。

好消息是,scikit-learn 1.8 已经为所有平台提供了自由线程的预编译 wheel 包。直接 pip install 就能用,不用自己从源码编译(这一点太重要了)。

2.2 使用线程后端加速 GridSearchCV

在自由线程 CPython 下,你可以把 joblib 的后端从进程(loky)切换到线程(threading)。好处很明显:省去了进程间数据序列化和传输的开销。

import joblib
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV

X, y = make_classification(n_samples=5000, n_features=20, random_state=42)

param_grid = {
    "n_estimators": [50, 100, 200],
    "max_depth": [5, 10, 20, None],
    "min_samples_split": [2, 5, 10],
}

clf = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(
    clf, param_grid=param_grid, cv=5, n_jobs=4, scoring="accuracy"
)

# 使用线程后端,在自由线程 CPython 下可获得真正的并行加速
with joblib.parallel_config(backend="threading"):
    grid_search.fit(X, y)

print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳准确率: {grid_search.best_score_:.4f}")

2.3 性能对比与注意事项

传统 CPython 里用线程后端跑并行?别想了,GIL 会让你白忙一场。但在自由线程 CPython 3.14 下,线程后端真的能让多核 CPU 火力全开,而且比进程后端少了数据复制的内存开销。

有个小建议:体验自由线程最好直接上 Python 3.14,别用 3.13。3.13 的自由线程支持有不少已知问题,3.14 里都修了。安装时通常需要编译选项 --disable-gil,或者找一个支持自由线程的预编译发行版。scikit-learn 官方也鼓励大家积极测试并反馈问题。

三、温度缩放校准(Temperature Scaling)

3.1 概率校准的重要性

你有没有注意过,很多分类器的 predict_proba 输出其实不太靠谱?比如 GaussianNB 给出的概率预测往往过度自信——说是 95% 的概率,实际可能只有 70%。概率校准就是为了解决这个问题:让模型输出的概率更接近真实情况。

之前 scikit-learn 支持 sigmoid(Platt 缩放)和 isotonic(保序回归)两种校准方法。1.8 版本加了第三种:温度缩放

3.2 温度缩放的原理与优势

温度缩放的思路其实很简洁:把分类器的 logits 除以一个温度参数 T,再过 softmax 得到校准后的概率。

p_calibrated = softmax(logits / T)

T 通过最小化 log loss 来优化。跟 sigmoid 校准比起来,温度缩放只有一个自由参数,天然适合多分类问题。类别再多,参数也不会膨胀。这意味着在多分类场景下,它通常比 sigmoid 校准效果更好,也更不容易过拟合。

3.3 实战代码

from sklearn.calibration import CalibratedClassifierCV, CalibrationDisplay
from sklearn.datasets import make_classification
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# 生成三分类数据集
X, y = make_classification(
    n_samples=5000, n_classes=3, n_features=20,
    n_informative=8, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# 训练基础分类器
base_clf = GaussianNB().fit(X_train, y_train)

# Sigmoid 校准
sigmoid_clf = CalibratedClassifierCV(
    base_clf, method="sigmoid", ensemble=False
).fit(X_train, y_train)

# 温度缩放校准(1.8 新特性)
temp_clf = CalibratedClassifierCV(
    base_clf, method="temperature", ensemble=False
).fit(X_train, y_train)

# 绘制可靠性图
fig, axes = plt.subplots(figsize=(12, 4), ncols=3, sharey=True)
for i, c in enumerate(temp_clf.classes_):
    CalibrationDisplay.from_predictions(
        y_test == c, base_clf.predict_proba(X_test)[:, i],
        name="未校准", ax=axes[i], marker="s"
    )
    CalibrationDisplay.from_predictions(
        y_test == c, temp_clf.predict_proba(X_test)[:, i],
        name="温度缩放", ax=axes[i], marker="o"
    )
    CalibrationDisplay.from_predictions(
        y_test == c, sigmoid_clf.predict_proba(X_test)[:, i],
        name="Sigmoid", ax=axes[i], marker="v"
    )
    axes[i].set_title(f"类别 {c}")

fig.suptitle("各类别可靠性图对比")
plt.tight_layout()
plt.show()

从可靠性图里能看得很清楚:温度缩放在三个类别上都把预测概率拉向了对角线(也就是完美校准线)。而且因为只优化一个参数,它确实比 sigmoid 更不容易过拟合。个人觉得,对于大多数多分类任务,温度缩放应该是首选方案。

四、Gap Safe Screening 加速 L1 正则化模型

4.1 技术原理

L1 正则化(Lasso、ElasticNet 这些)的一个关键特性就是产生稀疏解——很多系数直接被拍成零。那问题来了:既然最终有那么多系数是零,为什么还要在每次迭代中都去更新它们呢?

Gap Safe Screening 正是基于这个思路。它是一种数学上可以证明安全的筛选规则,能在求解器的早期就把那些"注定为零"的特征踢出去,后续迭代就不用管它们了。

scikit-learn 1.8 在坐标下降求解器里引入了这个规则,受益的模型包括:

  • Lasso / LassoCV
  • ElasticNet / ElasticNetCV
  • MultiTaskLasso / MultiTaskLassoCV
  • MultiTaskElasticNet / MultiTaskElasticNetCV
  • GraphicalLasso / GraphicalLassoCV
  • SparsePCA / MiniBatchSparsePCA
  • DictionaryLearning / MiniBatchDictionaryLearning

4.2 性能对比

L1 惩罚越强,被筛掉的特征就越多,加速效果也越明显。在高维数据上,加速比能到数倍甚至几十倍。

import time
from sklearn.datasets import make_regression
from sklearn.linear_model import ElasticNetCV, LassoCV

# 生成高维回归数据
X, y = make_regression(
    n_samples=2000, n_features=10000, n_informative=50, random_state=0
)

# ElasticNetCV 性能测试
model_en = ElasticNetCV(cv=5)
tic = time.time()
model_en.fit(X, y)
elapsed_en = time.time() - tic
print(f"ElasticNetCV 拟合耗时: {elapsed_en:.1f} 秒")
print(f"非零系数数量: {(model_en.coef_ != 0).sum()}")

# LassoCV 性能测试
model_lasso = LassoCV(cv=5)
tic = time.time()
model_lasso.fit(X, y)
elapsed_lasso = time.time() - tic
print(f"LassoCV 拟合耗时: {elapsed_lasso:.1f} 秒")
print(f"非零系数数量: {(model_lasso.coef_ != 0).sum()}")

在这个高维场景里,Gap Safe Screening 把大部分特征在较大的 alpha 值处就提前排除了,坐标下降每次迭代只需要更新少量活跃特征。和 scikit-learn 1.7 比,同等配置下 ElasticNetCV 的拟合时间可能缩短到原来的十分之一甚至更少。

4.3 对用户的影响

最棒的是,这个优化对你来说完全透明。不用改一行代码,升级到 1.8 就自动加速。特别是做基因组学、文本分类这类特征维度远超样本数的场景,提升会非常明显。

五、经典多维缩放(ClassicalMDS)

5.1 算法介绍

经典多维缩放(Classical MDS),也叫主坐标分析(PCoA)或 Torgerson 缩放,是一种把高维数据映射到低维空间的方法。和非度量 MDS 需要迭代优化不同,经典 MDS 通过对双中心化距离矩阵做特征值分解,直接给出解析解——算得快、结果确定。

scikit-learn 1.8 在 sklearn.manifold 模块里新增了 ClassicalMDS。一个有趣的数学事实是:用欧氏距离的时候,ClassicalMDS 的结果和 PCA 完全等价。但它的真正优势在于,它能接受任意距离度量,甚至可以直接输入你预先算好的距离矩阵。

5.2 API 与参数

ClassicalMDS 的接口很简洁:

from sklearn.manifold import ClassicalMDS

cmds = ClassicalMDS(
    n_components=2,       # 目标维度
    metric="euclidean",   # 距离度量,也可设为 "precomputed"
    metric_params=None    # 传递给距离度量的额外参数
)

5.3 实战示例:S 曲线降维

import matplotlib.pyplot as plt
from matplotlib import ticker
from sklearn import datasets, manifold

# 生成三维 S 曲线数据
n_samples = 1500
S_points, S_color = datasets.make_s_curve(n_samples, random_state=0)

# 使用 ClassicalMDS 降到二维
cmds = manifold.ClassicalMDS(n_components=2)
S_2d = cmds.fit_transform(S_points)

# 可视化
fig = plt.figure(figsize=(12, 5))

# 原始三维 S 曲线
ax1 = fig.add_subplot(1, 2, 1, projection="3d")
ax1.scatter(*S_points.T, c=S_color, s=30, alpha=0.8, cmap="Spectral")
ax1.set_title("原始三维 S 曲线", fontsize=14)
ax1.view_init(azim=-60, elev=9)

# ClassicalMDS 二维映射
ax2 = fig.add_subplot(1, 2, 2)
ax2.scatter(*S_2d.T, c=S_color, s=30, alpha=0.8, cmap="Spectral")
ax2.set_title("ClassicalMDS 二维投影", fontsize=14)

plt.tight_layout()
plt.show()

5.4 使用预计算距离矩阵

ClassicalMDS 还支持直接输入预计算的距离矩阵。当你需要用一些不太常规的距离度量时,这个功能就特别好用了:

from sklearn.manifold import ClassicalMDS
from sklearn.metrics import pairwise_distances
from sklearn.datasets import load_digits

X, y = load_digits(return_X_y=True)
X = X[:300]  # 取前 300 个样本

# 计算余弦距离矩阵
dist_matrix = pairwise_distances(X, metric="cosine")

# 使用预计算距离矩阵进行降维
cmds = ClassicalMDS(n_components=2, metric="precomputed")
X_2d = cmds.fit_transform(dist_matrix)
print(f"降维结果形状: {X_2d.shape}")  # (300, 2)

六、DecisionTreeRegressor absolute_error 复杂度优化

6.1 问题背景

决策树回归器的 criterion="absolute_error"(用中位数绝对误差作为分裂准则)之前的性能简直一言难尽。每次找最佳分裂点都要重算中位数,复杂度是 O(n²)。数据量一过万,训练时间就完全不可接受了。

6.2 优化效果

1.8 版本直接把复杂度优化到了 O(n log n)。效果有多夸张呢?10 万个样本上单次分裂,从大约 20 秒降到 0.1 秒左右,快了超过 100 倍

import time
from sklearn.datasets import make_regression
from sklearn.tree import DecisionTreeRegressor

# 生成大规模数据
X, y = make_regression(n_samples=500_000, n_features=10, random_state=42)

# 使用优化后的 absolute_error 准则
tree = DecisionTreeRegressor(criterion="absolute_error", max_depth=10)

tic = time.time()
tree.fit(X, y)
elapsed = time.time() - tic

print(f"样本数量: 500,000")
print(f"训练耗时: {elapsed:.2f} 秒")
print(f"树的叶节点数: {tree.get_n_leaves()}")

6.3 适用场景

absolute_error 准则用中位数而非均值来衡量节点的中心趋势,所以对异常值的鲁棒性天然更强。做房价预测、金融时间序列这类包含噪声和离群点的回归任务时,它通常比默认的 "squared_error" 更稳健。但之前的性能问题让它基本只能停留在小数据集上。1.8 的优化终于让这个好用的准则可以在大规模数据上落地了。

七、HTML 估计器表示增强

7.1 功能概述

这个特性可能不像 GPU 加速那么耀眼,但对日常开发体验的提升真的是润物细无声。scikit-learn 1.8 大幅增强了估计器在 Jupyter Notebook 里的 HTML 展示效果:

  • 参数链接:每个超参数名称都变成了指向在线文档的超链接,点一下就能查到完整说明
  • 文档字符串工具提示:鼠标悬停在参数上,就能看到简要描述
  • 交互式下拉:复杂流水线和嵌套估计器可以展开/折叠

7.2 使用示例

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.model_selection import GridSearchCV

# 构建一个嵌套流水线
pipe = make_pipeline(
    StandardScaler(),
    PolynomialFeatures(degree=2, interaction_only=True),
    LogisticRegression(C=10, max_iter=1000, random_state=0)
)

# 包装在 GridSearchCV 中
param_grid = {"logisticregression__C": [0.1, 1, 10]}
grid = GridSearchCV(pipe, param_grid, cv=5)

# 在 Jupyter Notebook 中直接显示
# 将看到带有参数链接和工具提示的交互式 HTML 表示
grid

说个实际的使用场景:当团队成员在审阅你的 Notebook 时,他们可以直接在估计器的 HTML 展示里了解每个参数是什么意思,不用跳出去翻文档。这对团队协作和代码可读性的提升是很大的。

八、其他值得关注的更新

除了上面这七个大的新特性,1.8 还有一些值得留意的改动:

  • QuadraticDiscriminantAnalysis 增强:新增了 solvercovariance_estimatorshrinkage 参数,API 终于跟 LinearDiscriminantAnalysis 对齐了,协方差矩阵估计的灵活性大幅提升。
  • LogisticRegression API 重构penalty 参数已弃用,改为用 l1_ratio 来统一控制正则化类型(l1_ratio=0.0 等价于 L2,l1_ratio=1.0 等价于 L1)。这个改动其实挺大的,迁移时注意一下。
  • PassiveAggressive 系列弃用PassiveAggressiveClassifierPassiveAggressiveRegressor 已弃用,推荐用 SGDClassifier(learning_rate="pa1") 替代。
  • 新评估指标:新增 d2_brier_score(Brier 损失的 D² 得分)和 confusion_matrix_at_thresholds(在多个阈值处计算混淆矩阵)。
  • SplineTransformer 缺失值处理:新增对缺失值的原生支持。终于不用手动处理了。
  • MaxAbsScaler 裁剪:新增 clip=True 参数,可以把变换后超出范围的值裁剪回 [-1, 1]。
  • MDS 初始化改进MDS 现在可以用 ClassicalMDS 的结果作为初始化,通过 init 参数指定,同时支持自定义距离度量。

九、安装与升级

升级很简单,几条命令的事:

# 使用 pip 安装或升级
pip install --upgrade scikit-learn

# 使用 conda 安装或升级
conda install -c conda-forge scikit-learn

# 验证版本
python -c "import sklearn; print(sklearn.__version__)"
# 输出: 1.8.0

想体验自由线程 CPython 3.14 的话,需要装对应版本的 Python:

# 在自由线程 CPython 3.14 环境中安装
pip install scikit-learn
# scikit-learn 会自动提供免 GIL 的 wheel 包

GPU 加速还需要额外装 CuPy 或 PyTorch:

# 安装 CuPy(根据 CUDA 版本选择)
pip install cupy-cuda12x

# 或安装 PyTorch(根据 CUDA 版本选择)
pip install torch --index-url https://download.pytorch.org/whl/cu121

十、总结

说句掏心窝子的话,scikit-learn 1.8 确实是一次让人兴奋的大版本更新。它在好几个维度上都有实质性的突破:

  1. 硬件利用率:Array API 让 GPU 加速成为现实,自由线程 CPython 支持了真正的多核并行。scikit-learn 终于摆脱了"只能跑在单核 CPU"的标签。
  2. 算法丰富度:温度缩放校准和经典多维缩放的加入,补上了概率校准和流形学习方面的重要短板。
  3. 性能优化:Gap Safe Screening 和决策树 absolute_error 的优化,在高维和大规模数据上带来了一到两个数量级的提速,而且你不需要改任何代码。
  4. 开发体验:HTML 估计器展示的增强虽然不起眼,但用起来真的让调试和协作顺畅不少。

我的建议是尽快升级。特别是如果你在做高维特征选择(Gap Safe Screening 加速太明显了)、多分类概率校准(温度缩放就是比 sigmoid 好用)、或者处理大规模数据(GPU 加速 + 优化的决策树),这些改进不是锦上添花,是能实实在在帮你省时间、出更好模型的。

关于作者 Editorial Team

Our team of expert writers and editors.