我需要测量我的模型对二元分类(0 和 1 输出)的预测准确度。我正在用许多不同的阈值测试我的模型,我的测试数据集非常大(5000 万到 1 亿个示例),所以我需要一种快速的方法来计算模型的准确度。我正在优化我的代码,并注意到计算准确度的标准函数比直接计算慢约 50 倍。最小示例:
from sklearn.metrics import accuracy_score
import numpy as np
import timeit
a=np.random.randint(0,2,1000000)
b=np.random.randint(0,2,1000000)
%timeit accuracy_score(a,b)
# 46.7 ms ± 390 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit (a==b).sum()/a.size
# 713 µs ± 7.22 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
我是不是漏掉了什么?看起来 accuracy_score 是衡量准确度的标准方法。为什么它这么慢?底层没有 C 优化?
accuracy_score
从源代码中可以看出,它比比较代码片段执行的工作多得多。它也不是直接用 C 编写的,只是通过 NumPy 间接利用了基于 C 的优化。您未考虑的操作包括:
您的比较代码(上文)应该总是更快,因为它不太安全,也不太灵活。有时我们会做出这样的权衡。
出现这种情况的原因有两个。部分原因是
accuracy_score()
需要进行更多验证以确保其计算出的答案有意义。然而,另一部分原因是部分验证似乎没有得到尽可能有效的实施。为了解释我为什么这么想,我将展示一些分析数据。我使用line profiler和以下 Jupyter 命令获取了这些数据:
关于此的一些说明:
-f
选项控制要跟踪哪些函数。我之所以跟踪,accuracy_score.__wrapped__
是因为accuracy_score()
有一个装饰器。其他函数是accuracy_score()
调用的函数;我们将深入讨论这一点。[accuracy_score(a,b) for i in range(10)]
是我们正在运行的代码。这是的结果
accuracy_score
:请注意,其 97% 的时间都花在了 内部
_check_targets()
。让我们跟踪一下该函数:此函数中时间有两个主要用途:
type_of_target()
针对 y_true 和 y_pred 都调用。这占了所用时间的 24.3% + 21.8% = 46.1%。_union1d()
被调用来获取 y_true 和 y_pred 中不同类别的数量。这占了所用时间的 53.6%。这两个功能的作用是什么?
在 中
type_of_target()
,大部分时间都花在这一行上:_union1d
取两个 NumPy 数组的并集。它调用np.union1d
,其实现如下:总而言之,对于二元分类,
accuracy_score
96%的计算时间用于计算以下三件事之一:np.unique(y_true)
np.unique(y_pred)
np.unique(np.concatenate([y_true, y_pred]))
然而,第三件事可以通过前两件事推算出来。
具体来说,这三个操作可以重写为:
在不同类别的数量远小于训练示例的数量的情况下(我认为这适用于大多数机器学习问题),第二种形式将快约 50%,因为
np.concatenate([y_true_unique, y_pred_unique])
会比小得多np.concatenate([y_true, y_pred])
。实际上进行这种优化并提交 scikit-learn PR 留给读者作为练习。:)