阅读(3955) (0)

scikit-learn 计算性能

2023-02-17 13:57:44 更新

对于某些应用,估计器的性能(主要是预测时间的延迟和吞吐量)至关重要。考虑训练吞吐量也可能是有意义的,但是在生产设置(通常在脱机中运行)通常是不太重要的。

我们将在这里审查您可以从不同上下文中的一些 scikit-learn估计器预期的数量级,并提供一些 解决性能瓶颈的技巧和诀窍。

将预测的延迟作为进行预测所需的耗费时间(例如,以微秒为单位)进行测量。延迟通常被认为一种分布,运营工程师通常将注意力集中在该分布的给定百分位数(例如 90 百分位数)上的延迟。

预测吞吐量被定义为软件可以在给定的时间量内可预测的预测数(例如每秒的预测)。

性能优化需要注意的一个重要方面是它可能会损害预测精度。 实际上,更简单的模型(例如 线性的,而不是 non-非线性的,或者具有较少参数的模型)通常运行得更快,但是可能会丢失数据中包含的信息。

8.2.1. 预测延迟

在使用/选择机器学习工具包时可能遇到的最直接的问题之一是生产环境中发生的预测延迟。

影响 预测延迟的主要因素是

  1. 特征的数量
  2. 输入数据的表示和稀疏性
  3. 模型复杂性
  4. 特征提取

最后一个主要参数是在批量预测 或 一次执行一个预测模式下进行预测的可能性。

8.2.1.1. 批量与原子模式

通常进行批量预测 (可能有更多的方法) 非常有效的原因有很多(分支预测, CPU缓存, 线性代数库的优化 等.)。

在这里,我们看到一些具有很少特征的设置,独立于估计器选择,批量模式总是更快,而对于其中的一些,它们的数量大约是 1 到 2 个数量级:

atomic_prediction_latency

bulk_prediction_latency

为了对您的案例的不同的估计器进行基准测试,您可以在此示例中简单地更改 n_features 参数: 预测延迟. 这应该给你估计预测延迟的数量级。

8.2.1.2. 配置 Scikit-learn 来减少验证开销

Scikit-learn 对数据进行了一些验证,从而增加了对 predict 和类似函数的调用开销。特别地,检查这些特征是有限制的(非空 或无限)涉及对数据的完全传递。如果你确定自己的数据是没问题的,你可以通过在导入 scikit-learn 之前将环境变量配置 SKLEARN_ASSUME_FINITE 设置为非空字符串来抑制检查有限性,或者使用以下方式在 Python 中配置 sklearn.set_config 。为了比这些全局设置更多的控制 config_context 允许您在指定的上下文中设置此配置:

>>> import sklearn
>>> with sklearn.config_context(assume_finite=True):
...     pass  # do learning/prediction here with reduced validation

注意这个配置会影响上下文中的 sklearn.utils.assert_all_finite 所有功能.

8.2.1.3. 特征数量的影响

显然,当特征数量增加时,每个示例的内存消耗量也会增加。实际上,对于具有个特征的 个实例的矩阵,空间复杂度在 。从 computing (计算)角度来看,这也意味着 the number of basic operations (基本操作的数量)(例如,线性模型中向量矩阵乘积的乘法)也增加。以下是 prediction latency (预测延迟)与 number of features(特征数) 的变化图:

influence_of_n_features_on_latency

总的来说,您可以预期预测时间至少随特征数量线性增加非线性情况可能会发生,取决于全局内存占用和 估计。

8.2.1.4. 输入数据的影响

Scipy 提供 用来优化稀疏矩阵的稀疏矩阵数据结构. 稀疏矩阵最主要的特征是不存储0值,因此当你的数据是稀疏的这样可以消耗更少的内存。稀疏(CSR or CSC) 矩阵中的一个非零值仅需占用32bit的位置+64位浮点型的值+额外的32bit表示矩阵中的行或者列。在密集(或者稀疏)线性模型上使用稀疏输入可以加速预测的速度,只有非零值特征才会影响点乘以至于影响预测结果。因此如果你在一个1e6维空间中只有100个非零值,那么你只需要100次乘法和加法运算而不是1e6。

然而,密度表示的计算可以利用 BLAS 中高度优化的向量操作和多线程,并且往往导致更少的 CPU 高速缓存 misses 。因此,sparse input (稀疏输入)表示的 sparsity (稀疏度)通常应相当高(10% 非零最大值,要根据硬件进行检查)比在具有多个 CPU 和优化 BLAS 实现的机器上的 dense input (密集输入)表示更快。

以下是测试输入 sparsity (稀疏度)的示例代码:

def sparsity_ratio(X):
    return 1.0 - np.count_nonzero(X) / float(X.shape[0] * X.shape[1])
print("input sparsity ratio:", sparsity_ratio(X))

根据经验,您可以考虑如果 sparsity ratio (稀疏比)大于 90% , 您可能会从 sparse formats (稀疏格式)中受益。有关如何构建(或将数据转换为) sparse matrix formats (稀疏矩阵格式)的更多信息,请参阅 Scipy 的稀疏矩阵格式文档 documentation 。大多数的时候, CSRCSC 格式是最有效的。

8.2.1.5. 模型复杂度的影响

一般来说,当模型的复杂成都增加了,预测性能和延迟也会一起增加。增长的预测性能通常是好的,但是对于许多应用我们最好不要增加太多的预测延迟。我们将通过不同类别的监督学习模型来重新认识这个问题。

对于 sklearn.linear_model (例如 Lasso, ElasticNet, SGDClassifier/Regressor, Ridge & RidgeClassifier, PassiveAggressiveClassifier/Regressor, LinearSVC, LogisticRegression…) 使用的决策函数是一致的 (点积) , 所以延迟应该也是一致的。这里给一个例子使用 sklearn.linear_model.SGDClassifierelasticnet penalty(弹性网惩罚)。正则化的强度由alpha参数全局决定。一个足够高的alpha,可以增加 elasticnetl1_ratio 参数,以在模型参数中执行各种稀疏程度。这里的 Higher sparsity (较高稀疏度)被解释为 less model complexity (较少的模型复杂度),因为我们需要较少的系数充分描述它。当然, sparsity (稀疏性)会随着稀疏点积 产生时间大致与非零系数的数目成比例地影响 prediction time (预测时间)。

en_model_complexity

对于具有非线性内核的 sklearn.svm 算法系列,延迟与支持向量的数量有关(越少越快)。 在 SVC 或 SVR 模型中 延迟 和 吞吐量随着支持向量的数量线性增长。内核也将影响延迟,因为它用于计算每个支持向量一次输入向量的投影。在下面的图中, sklearn.svm.NuSVRnu 参数用于影响支持向量的数量。

nusvr_model_complexity

对于sklearn.ensemble的 trees (例如 RandomForest, GBT, ExtraTrees 等)树的数量及其深度发挥着最重要的作用。延迟和吞吐量应与树的数量呈线性关系。在这种情况下,我们直接使用 sklearn.ensemble.gradient_boosting.GradientBoostingRegressorn_estimators 参数。

gbt_model_complexity

在任何情况下都应该警告,降低的 model complexity (模型复杂性)可能会损害如上所述的准确性。例如,可以用快速线性模型来处理 non-linearly separable problem (非线性可分离问题),但是在该过程中预测能力将很可能受到影响。

8.2.1.6. 特征提取的延迟

大多数 scikit-learn 模型通常非常快,因为它们可以通过编译的 Cython 扩展或优化的计算库来实现。 另一方面,在许多现实世界的应用中,特征提取过程(即,将 数据库行或网络分组的原始数据转换为 numpy arrays )来控制总体预测时间。例如在路透社文本分类任务中,根据所选择的模型,整个准备(读取和解析 SGML 文件,将文本进行标记并将其散列为公共向量空间)的时间比实际预测代码的时间长 100 到 500 倍。

prediction_time

因此,在很多情况下,建议您仔细地对 carefully time and profile your feature extraction code ( 特征提取代码进行时间预估和简档),因为当您的 overall latency (整体延迟)对您的应用程序来说太慢时,可能是开始优化的好地方。

8.2.2. 预测吞吐量

另一个度量生产系统大小的重要的指标是预测吞吐量,即在给定时间内能做出预测的数量。

以下是 Prediction Latency 示例的基准测试,该示例针对合成数据的多个 estimators (估计器)测量此数量:

throughput_benchmark

这些 throughputs(吞吐量)早单个进程上实现。提高应用程序吞吐量的一个明显的方法是产生其他实例(通常由于 GIL而在 Python 中进行处理)共享相同模型。还可能添加机器来分布式负载。关于如何实现这一点的详细解释超出了本文档的范围。

8.2.3. 技巧与窍门

8.2.3.1. 线性代数库

由于 scikit-learn 在很大程度上依赖于 Numpy/Scipy 和 线性代数,所以需要理解这些库的版本。 基本上,你应该确保使用优化的 BLAS / LAPACK 构建numpy库.

并非所有的模型都受益于优化的 BLAS 和 Lapack 实现。例如,基于(随机化)决策树的模型通常不依赖于内部循环中的 BLAS 调用,kernel SVMs (SVC, SVR, NuSVC, NuSVR) 。另一方面,使用 BLAS DGEMM 调用(通过 numpy.dot)实现的线性模型通常将受益于调整的 BLAS 实现,并且导致非优化 BLAS 的数量级加速。

你可以使用以下命令显示您的 NumPy / SciPy / scikit-learn 安装使用的 BLAS / LAPACK 实现:

from numpy.distutils.system_info import get_info
print(get_info('blas_opt'))
print(get_info('lapack_opt'))

优化的 BLAS / LAPACK 实现如下:

  • Atlas (需要通过在目标机器上 rebuilding 进行硬件特定调整)

  • OpenBLAS

  • MKL

  • Apple Accelerate and vecLib frameworks (仅适用于OSX系统 )

有关更多信息,请参见 Scipy install page 和 Daniel Nouri 的 blog post 在这篇博客中介绍了如何一步一步在 Debian / Ubuntu中安装配置的信息.

8.2.3.2. 限制工作内存

当直接使用numpy的向量化操作执行计算的时候,需要使用大量的临时内存空间。这个很可能会耗尽系统的内存空间。在可以以固定内存块执行计算的地方,我们尝试这样做,并允许用户使用 sklearn.set_configconfig_context提示该工作内存的最大大小(默认为1GB)。以下建议将临时工作记忆限制在128mib:

>>> import sklearn
>>> with sklearn.config_context(working_memory=128):
...     pass  # do chunked work here

遵循此设置的块操作的一个示例是metric.pairwise_distances_chunked,用于计算成对距离矩阵的行压缩。

8.2.3.3. 模型压缩

在scikit-learn中模型压缩一般只在线性模型中考虑。在这种情况下,这意味着我们要控制模型稀疏度(即 模型向量中的非零坐标数)。将模型稀疏度与稀疏输入数据表示相结合是一个好主意。

以下是示例代码,说明了如何使用 sparsify() 方法:

clf = SGDRegressor(penalty='elasticnet', l1_ratio=0.25)
clf.fit(X_train, y_train).sparsify()
clf.predict(X_test)

在这个示例中,我们更喜欢 elasticnet 惩罚,因为它通常是模型紧凑性和预测能力之间的一个很好的妥协。还可以进一步调整 l1_ratio 参数(结合正则化强度 alpha )来控制这个权衡。

对于合成数据,典型的 benchmark在模型和输入时都会降低 30% 的延迟。稀疏(分别为 0.000024 和 0.027400 非零系数比)。您的里程可能会因您的数据和模型的稀疏性和大小而有所不同。 因此,为了减少部署在生产服务器上的预测模型的内存使用,扩展可能非常有用。

8.2.3.4. 模型重塑

模型重塑在于仅选择一部分可用功能以适应模型。换句话说,如果模型在学习阶段丢弃特征,我们可以从输入中删除这些特征。这有几个好处。首先,它减少了模型本身的内存(因此是减少了时间)的开销。一旦知道要从上一次运行中保留哪些功能,它也允许在 pipeline 中丢弃显式的特征选择组件。最后,它可以通过不收集和构建模型丢弃的特征来帮助减少数据访问和 特征提取层上游的处理时间和 I/O 的使用。例如,如果原始数据来自数据库,则可以通过使查询返回较轻的记录,从而可以编写更简单和更快速的查询或减少 I/O 的使用。 目前,重塑需要在 scikit-learn 中手动执行。 在 稀疏输入(特别是 CSR 格式)的情况下,通常不能生成相关的特征,使其列为空。

8.2.3.5. 参考链接