9.2. 计算性能#
对于某些应用程序来说,估计器的性能(主要是预测时的延迟和吞吐量)至关重要。考虑训练吞吐量也可能很有趣,但这在生产设置中通常不太重要(通常离线进行)。
我们将在这里回顾不同环境下您可以从许多scikit-learn估计器中预期的数量级,并提供一些克服性能瓶颈的技巧和技巧。
预测延迟被测量为进行预测所需的经过时间(例如以微秒为单位)。延迟通常被视为一种分布,运营工程师通常关注该分布给定百分位数(例如第90百分位数)的延迟。
预测吞吐量定义为软件在给定时间内可以交付的预测数量(例如每秒的预测)。
性能优化的一个重要方面还在于它可能会损害预测准确性。事实上,更简单的模型(例如线性而不是非线性,或参数更少)通常运行得更快,但并不总是能够像更复杂的模型一样考虑相同的数据精确属性。
9.2.1. 预测延迟#
在使用/选择机器学习工具包时,人们可能遇到的最直接的问题之一是在生产环境中进行预测的延迟。
影响预测潜伏期的主要因素有
数量的特征
输入数据表示和稀疏性
模型复杂性
特征提取
最后一个主要参数也是以批量或一次一个模式进行预测的可能性。
9.2.1.1. 批量模式与原子模式#
一般来说,由于多种原因(分支可预测性、中央处理器缓存、线性代数库优化等),批量进行预测(同时进行许多实例)更有效。在这里,我们看到,在一个几乎没有功能的设置中,批量模式总是更快,并且对于其中一些功能来说快1到2个数量级:
要对您的案例进行不同的估计器进行基准测试,您只需更改 n_features
本例中的参数: 预测延迟 .这应该可以让您估计预测延迟的数量级。
9.2.1.2. 配置Scikit-learn以减少验证费用#
Scikit-learn对数据进行了一些验证,从而增加了每次呼叫的费用 predict
类似的功能。特别是,检查特征是有限的(不是NaN或无限的)涉及到对数据的完全传递。如果确保数据是可接受的,则可以通过设置环境变量来禁止检查有限性 SKLEARN_ASSUME_FINITE
在导入scikit-learn之前将其配置为非空字符串,或者在Python中使用 set_config
.要获得比这些全局设置更多的控制, config_context
允许您在指定的上下文中设置此配置:
>>> import sklearn
>>> with sklearn.config_context(assume_finite=True):
... pass # do learning/prediction here with reduced validation
请注意,这将影响所有使用 assert_all_finite
在上下文中。
9.2.1.3. 特征数量的影响#
显然,当功能数量增加时,每个示例的内存消耗也会增加。事实上,对于 \(M\) 实例与 \(N\) 特征,空间复杂性在于 \(O(NM)\) .从计算的角度来看,这还意味着基本操作的数量(例如,线性模型中的矩阵积的相乘)也增加了。以下是预测延迟随特征数量变化的图表:
总体而言,您可以预计预测时间至少会随着特征数量线性增加(非线性情况可能会发生,具体取决于全局内存占用和估计器)。
9.2.1.4. 输入数据表示的影响#
Scipy提供了针对存储稀疏数据而优化的稀疏矩阵数据结构。稀疏格式的主要特征是不存储零,因此如果您的数据是稀疏的,那么您使用的内存要少得多。稀疏中的非零值 (CSR or CSC )表示将平均只采用一个32位的整点位置+64位的浮点值+矩阵中的每一行或每列额外的32位。在密集(或稀疏)线性模型上使用稀疏输入可以大大加快预测速度,因为只有非零值特征会影响点积,从而影响模型预测。因此,如果1 e6维空间中有100个非零,则只需100次乘加操作,而不是1 e6。
然而,在密集表示上进行计算可以利用BLAS中高度优化的载体操作和多线程,并且往往会导致更少的中央缓存未命中。因此,稀疏性通常应该相当高(最大为10%非零,根据硬件进行检查),以便在具有许多处理器和优化BLAS实现的机器上,稀疏输入表示比密集输入表示更快。
以下是测试输入稀疏性的示例代码::
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))
根据经验,您可以考虑,如果稀疏率大于90%,您可能可以从稀疏格式中受益。检查Scipy的稀疏矩阵格式 documentation 了解有关如何构建(或将数据转换为)稀疏矩阵格式的更多信息。大多数时候 CSR
和 CSC
格式工作最好。
9.2.1.5. 模型复杂性的影响#
一般来说,当模型复杂性增加时,预测能力和延迟就会增加。提高预测能力通常很有趣,但对于许多应用程序,我们最好不要过多地增加预测延迟。我们现在将针对不同的监督模型家族回顾这个想法。
为 sklearn.linear_model
(e.g. Lasso、ElasticNet、SGDClassifier/Regressor、Ridge & RidgeClassifier、PassiveAggressiveClassifier/Regressor、LinearsVC、LogisticRegistry.)预测时应用的决策函数相同(点积),因此延迟应该相等。
这是一个使用的例子 SGDClassifier
与 elasticnet
点球正规化实力由全球控制 alpha
参数.具有足够高 alpha
,然后可以增加 l1_ratio
参数 elasticnet
以在模型系数中实施各种水平的稀疏性。这里更高的稀疏性被解释为更低的模型复杂性,因为我们需要更少的系数来完全描述它。当然,稀疏性反过来影响预测时间,因为稀疏点积所花费的时间大致与非零系数的数量成比例。
为 sklearn.svm
具有非线性核心的算法家族,延迟与支持载体的数量有关(越少越快)。延迟和吞吐量应该(渐进地)随着SRC或SVR模型中支持载体的数量线性增长。内核还将影响延迟,因为它用于计算每个支持量一次输入量的投影。在下图中 nu
参数 NuSVR
用于影响支持载体的数量。
为 sklearn.ensemble
树(例如RandomForest、GBT、ExtraTrees等)树木的数量和深度起着最重要的作用。延迟和吞吐量应该随着树的数量线性扩展。在这种情况下,我们直接使用 n_estimators
参数 GradientBoostingRegressor
.
无论如何,请注意,降低模型复杂性可能会损害如上所述的准确性。例如,可以用快速线性模型来处理非线性可分问题,但预测能力很可能会在此过程中受到影响。
9.2.1.6. 特征提取延迟#
大多数scikit-learn模型通常速度相当快,因为它们是通过编译的Cython扩展或优化的计算库实现的。另一方面,在许多现实世界的应用程序中,特征提取过程(即将数据库行或网络数据包等原始数据转化为numpy数组)决定总体预测时间。例如,在路透社的文本分类任务中,整个准备(读取和解析SGML文件、将文本标记化并将其哈希到公共载体空间中)花费的时间比实际预测代码多100到500倍,具体取决于所选的模型。
因此,在许多情况下,建议仔细计时和分析您的特征提取代码,因为当总体延迟对于应用程序来说太慢时,这可能是开始优化的好地方。
9.2.2. 预测产出#
调整生产系统规模时需要关注的另一个重要指标是吞吐量,即在给定时间内可以做出的预测数量。这是来自 预测延迟 测量合成数据上许多估计量的此量的示例:
这些吞吐量是通过单个流程实现的。提高应用程序吞吐量的一个明显方法是产生额外的实例(通常是Python中的进程,因为 GIL )共享相同模型。人们还可以添加机器来分散负载。但关于如何实现这一点的详细解释超出了本文档的范围。
9.2.3. 提示和技巧#
9.2.3.1. 线性代数库#
由于scikit-learn总体上严重依赖Numpy/Scipy和线性代数,因此明确照顾这些库的版本是有意义的。基本上,您应该确保Numpy是使用优化的 BLAS / LAPACK 图书馆
并非所有型号都受益于优化的BLAS和Lapack实施。例如,基于(随机)决策树的模型通常不依赖于其内循环中的BLAS调用,内核支持器也不依赖于 (SVC
, SVR
, NuSVC
, NuSVR
). 另一方面,通过BLAS DGEMM调用实现线性模型(通过 numpy.dot
)通常将从优化的BLAS实施中受益匪浅,并比未优化的BLAS带来数量级的加速。
您可以使用以下命令显示NumPy / SciPy / scikit-learn安装使用的BLAS / LAPACK实现:
python -c "import sklearn; sklearn.show_versions()"
优化的BLAS / LAPACK实施包括:
Atlas(需要通过在目标计算机上重建来进行特定硬件的调整)
OpenBLAS
MKL
Apple Accelerate和vecLib框架(仅限NSX)
更多信息请访问 NumPy install page 并在这 blog post 来自Daniel Nouri,其中有一些很好的Debian / Ubuntu分步安装说明。
9.2.3.2. 限制工作记忆#
使用标准numpy向量化操作实现时,某些计算涉及使用大量临时内存。 这可能会耗尽系统内存。 如果计算可以在固定内存块中执行,我们尝试这样做,并允许用户使用提示此工作内存的最大大小(默认为1GB) set_config
或 config_context
. 以下建议将临时工作记忆限制在128 MiB:
>>> import sklearn
>>> with sklearn.config_context(working_memory=128):
... pass # do chunked work here
遵守此设置的分块操作的一个示例是 pairwise_distances_chunked
,这有助于计算成对距离矩阵的行约简。
9.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 当模型和输入都是稀疏的(非零系数比分别为0.000024和0.027400)时,合成数据的延迟降低>30%。您的里程可能会因数据和模型的稀疏性和大小而有所不同。此外,稀疏化对于减少部署在生产服务器上的预测模型的内存使用非常有用。
9.2.3.4. 模型重塑#
模型重塑包括仅选择一部分可用特征来适应模型。换句话说,如果模型在学习阶段放弃了特征,我们就可以从输入中删除这些特征。这有几个好处。首先,它减少了模型本身的内存(从而减少了时间)负担。一旦我们知道要从之前的运行中保留哪些功能,它还允许丢弃管道中的显式功能选择组件。最后,它可以通过不收集和构建被模型丢弃的特征来帮助减少数据访问和特征提取层上游的处理时间和I/O使用。例如,如果原始数据来自数据库,则可以编写更简单、更快的查询,或者通过使查询返回更轻的记录来减少I/O使用。目前,需要在scikit-learn中手动执行重塑。在稀疏输入的情况下(特别是在 CSR
格式),通常不生成相关功能,而使其列为空就足够了。
9.2.3.5. 链接#
Scipy sparse matrix formats documentation <https://docs.scipy.org/doc/scipy/reference/sparse.html>
_