给定两个矩阵和一个采用两个向量的函数,如何对矩阵中每对向量的函数均值进行向量化?
Given two matrices and a function that takes two vectors, how to vectorize mean of function for every pair of vectors from the matrices?
我正在评估推荐算法(关于它们的排名表现)。这里,true_scores
(二进制)矩阵中的一行是用户所有项目的基础值,而 predicted_scores
(连续)矩阵中的一行是来自某种算法的所有项目的预测分数。 sklearn
有方法 average_precision_score
,它接受两个数组(真实和预测)返回 score。需要的是所有用户的这些分数 的平均值。 (顺便说一句 true_scores
& predicted_scores
显然形状相同)
目前,我正在使用 for
循环对用户 进行平均
import numpy as np
from sklearn.metrics import average_precision_score as aps
def mean_aps(true_scores, predicted_scores):
'''Mean Average Precision Score'''
return np.mean([aps(t, p) for t, p in zip(true_scores, predicted_scores) if t.sum() > 0])
能不能去掉上面代码中的for
循环,完全用numpy写?我基本上想加快这段代码的速度(可能使用矢量化)。
我知道我们可能需要自定义实现方法 average_precision_score
。所以我将重新定义问题:我需要一个 numpy 感知的 any ranking score like NDCG.
分数平均值的实现
我能够为 NDCG 得分(线性相关)执行此操作。代码扩展为 2D 版本类似 to the code here.
import numpy as np
def dcg_score_2d(y_true, y_score, k=100):
order = np.argsort(y_score)[:, ::-1]
y_true = y_true[np.arange(y_true.shape[0])[:,np.newaxis], order[:, :k]]
discounts = np.log2(np.arange(y_true.shape[-1]) + 2)
return np.sum(y_true / discounts, axis=1)
def ndcg_score_2d(y_true, y_score, k=100):
best = dcg_score_2d(y_true, y_true, k=k)
actual = dcg_score_2d(y_true, y_score, k=k)
return actual / best
虽然它并没有像我预期的那样在 for
循环中给我带来太多性能提升。
编辑: 我列出了该问题的三种实现方式。
首先,完全消除循环是可能的,但是生成的函数 avg_prec_noloop()
非常耗费内存,因为它试图一次完成 每个 操作。只要项目的数量在 100 以内,它就会运行得非常快。不幸的是,当项目数趋向于 1000 或更多时,它会消耗太多内存,并会导致崩溃。我包含这个只是为了表明它可以在没有循环的情况下完成,但我不建议使用它。
遵循与原始逻辑类似的逻辑,但通过在项目上添加一个循环,我们得到函数 avg_prec_colwise
。我们可以通过一次获取整个阈值列来为所有用户计算精度和召回@K。它的时间与之前的无循环实现相似,但它并不像内存消耗那么大,而且仍然有 属性 当项目 <=100 时速度要快得多,无论用户数量如何。 100,000个用户,10个item,比原来快了近300倍;但如果 items>=1000,它会比原来慢一百倍。每当你有大量用户和少量项目的场景时,我建议你使用这个。
最后,我有一个可能最接近 sklearn 的实现 avg_prec_rowwise
。当项目较低时,它没有 colwise 或 noloop 函数的惊人增益,但无论项目或用户数量如何,它始终比使用原始函数快 10-20%。对于一般用途,我建议您使用这个。
import numpy as np
from sklearn.metrics import average_precision_score as aps
from sklearn.metrics import precision_recall_curve as prc
import warnings
warnings.filterwarnings('ignore')
def mean_aps(true_scores, predicted_scores):
'''Mean Average Precision Score'''
return np.mean([aps(t, p) for t, p in zip(true_scores, predicted_scores) if t.sum() > 0])
def avg_prec_noloop(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
THRESH = np.sort(yp).T
yp = yp.reshape(1, yp.shape[0], yp.shape[1]) >= THRESH.reshape(THRESH.shape[0], THRESH.shape[1], 1)
a = (yt*(yt==yp)).sum(axis=2)
b = yp.sum(axis=2)
c = yt.sum(axis=1)
p = (np.where(b==0,0,a/b))
r = a/c
rdif = np.vstack((r[:-1]-r[1:],r[-1]))
return (rdif*p).sum()/yt.shape[0]
def avg_prec_colwise(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
N_USER, N_ITEM = yt.shape
THRESH = np.sort(yp)
p, r = np.zeros((N_USER, N_ITEM)), np.zeros((N_USER, N_ITEM))
c = yt.sum(axis=1)
for i in range(N_ITEM):
ypt = yp >= THRESH[:,i].reshape(-1,1)
a = (yt*(yt==ypt)).sum(axis=1)
b = ypt.sum(axis=1)
p[:,i] = np.where(b==0,0,a/b).reshape(-1)
r[:,i] = a/c
rdif = np.hstack((r[:,:-1]-r[:,1:],r[:,-1].reshape(-1,1)))
return (rdif*p).sum()/N_USER
def avg_prec_rowwise(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
N_USER, N_ITEM = yt.shape
p, r = np.zeros((N_USER, N_ITEM)), np.zeros((N_USER, N_ITEM))
for i in range(N_USER):
a, b, _ = prc(yt[i,:], yp[i,:])
p[i,:len(a)-1] = a[:-1]
r[i,:len(b)-1] = b[:-1]
rdif = np.hstack((r[:,:-1]-r[:,1:],r[:,-1].reshape(-1,1)))
return (rdif*p).sum()/N_USER
一些时间场景:1)真正少的物品
N_USERS = 10000
N_ITEMS = 10
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 47.91176509857178
Colwise: 0.16370844841003418
Rowwise: 37.96852993965149
2) 更多项目:
N_USERS = 3000
N_ITEMS = 100
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 14.943019151687622
Colwise: 2.0997579097747803
Rowwise: 11.798128604888916
3) 项目很多:
N_USERS = 3000
N_ITEMS = 1000
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 20.760642051696777
Colwise: 248.5634708404541
Rowwise: 17.940539121627808
4) 原始和按行的最后比较,没有任何循环:
N_USERS = 10000
N_ITEMS = 1000
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 6.912739515304565
Rowwise: 5.9845476150512695
我对这个问题没有太多的见解,直到你提供了你自己的 。
我假设那里的代码正在执行正确的操作。
我还假设 k
通常比分数的数量小得多。
如果是这种情况,您可以相当多地优化您的代码,因为您不需要像您那样进行排序,您只对执行 [=16] 的计算时排序的最大 k
值感兴趣=].
为此,您可以使用 np.argpartition()
。
此外,您在 np.argsort()
调用后执行的索引非常麻烦。
更简洁的方法是使用 np.take_along_axis()
。
下面的代码实现了这一切。
import numpy as np
def dcg_score_opt_np(values, scores, k):
idx = np.argsort(scores, axis=1)[:, :-k - 1:-1]
values = np.take_along_axis(values, idx, 1)
result = np.sum(values / np.log2(2 + np.arange(values.shape[1])), axis=1)
return result
def ndcg_score_opt_np(y_true, y_score, k):
best = dcg_score_opt_np(y_true, y_true, k)
n = y_score.shape[1]
if n > k:
idx = np.argpartition(y_score, n - k, axis=1)[:, -k:]
y_true = np.take_along_axis(y_true, idx, 1)
y_score = np.take_along_axis(y_score, idx, 1)
actual = dcg_score_opt_np(y_true, y_score, k)
return actual / best
此外,best
的计算独立于 y_score
,如果注意到唯一相关的数量是 [=23] 中的 1
的数量,则可以更有效地计算=].
不幸的是,后续计算无法在 NumPy 中高效完成。
但是如果你愿意使用 Numba,你可以这样写:
import numpy as np
import numba as nb
@nb.jit
def dcg(scores):
result = 0
for i, score in enumerate(scores, 2):
if score > 0.0:
result += score / np.log2(i)
return result
@nb.jit
def idcg(n):
result = 0
for i in range(2, n + 2):
result += 1 / np.log2(i)
return result
@nb.jit
def dcg_score(result, scores, k):
for i in range(result.shape[0]):
result[i] = dcg(scores[i, :k])
@nb.jit
def idcg_score(result, counts, k):
for i in range(result.shape[0]):
n = counts[i] if counts[i] < k else k
result[i] = idcg(n)
def idcg_score_opt_nb(values, k):
counts = np.count_nonzero(values, axis=1)
result = np.empty(values.shape[0])
idcg_score(result, counts, k)
return result
def dcg_score_opt_nb(values, scores, k):
idx = np.argsort(scores, axis=1)[:, :-k - 1:-1]
values = np.take_along_axis(values, idx, 1)
result = np.empty(scores.shape[0])
dcg_score(result, values, k)
return result
def ndcg_score_opt_nb(y_true, y_score, k):
best = idcg_score_opt_nb(y_true, k)
n = y_score.shape[1]
if n > k:
idx = np.argpartition(y_score, n - k, axis=1)[:, -k:]
y_true = np.take_along_axis(y_true, idx, 1)
y_score = np.take_along_axis(y_score, idx, 1)
actual = dcg_score_opt_nb(y_true, y_score, k)
return actual / best
请注意 dcg_score_opt_nb()
并不是真正必要的,它似乎与 dcg_score_opt_np()
基本相当。
Numba 增强实现的另一个优点是它们的内存效率更高,因为临时数组的使用保持在最低限度。
一个简单的测试表明我们一直得到相同的结果:
N_USERS = 6
N_ITEMS = 4
k = 3
np.random.seed(0)
y_true = np.random.choice(2, (N_USERS, N_ITEMS))
y_score = np.random.random(size=(N_USERS, N_ITEMS))
print(ndcg_score_2d(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
print(ndcg_score_opt_np(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
print(ndcg_score_opt_nb(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
虽然对可观数据集的基准测试给出:
N_USERS = 3000
N_ITEMS = 2000
k = 100
y_true = np.random.choice(2, (N_USERS, N_ITEMS))
y_score = np.random.random(size=(N_USERS, N_ITEMS))
%timeit ndcg_score_2d(y_true, y_score, k)
# 1 loop, best of 3: 540 ms per loop
%timeit ndcg_score_opt_np(y_true, y_score, k)
# 1 loop, best of 3: 226 ms per loop
%timeit ndcg_score_opt_nb(y_true, y_score, k)
# 10 loops, best of 3: 138 ms per loop
我正在评估推荐算法(关于它们的排名表现)。这里,true_scores
(二进制)矩阵中的一行是用户所有项目的基础值,而 predicted_scores
(连续)矩阵中的一行是来自某种算法的所有项目的预测分数。 sklearn
有方法 average_precision_score
,它接受两个数组(真实和预测)返回 score。需要的是所有用户的这些分数 的平均值。 (顺便说一句 true_scores
& predicted_scores
显然形状相同)
目前,我正在使用 for
循环对用户 进行平均
import numpy as np
from sklearn.metrics import average_precision_score as aps
def mean_aps(true_scores, predicted_scores):
'''Mean Average Precision Score'''
return np.mean([aps(t, p) for t, p in zip(true_scores, predicted_scores) if t.sum() > 0])
能不能去掉上面代码中的for
循环,完全用numpy写?我基本上想加快这段代码的速度(可能使用矢量化)。
我知道我们可能需要自定义实现方法 average_precision_score
。所以我将重新定义问题:我需要一个 numpy 感知的 any ranking score like NDCG.
我能够为 NDCG 得分(线性相关)执行此操作。代码扩展为 2D 版本类似 to the code here.
import numpy as np
def dcg_score_2d(y_true, y_score, k=100):
order = np.argsort(y_score)[:, ::-1]
y_true = y_true[np.arange(y_true.shape[0])[:,np.newaxis], order[:, :k]]
discounts = np.log2(np.arange(y_true.shape[-1]) + 2)
return np.sum(y_true / discounts, axis=1)
def ndcg_score_2d(y_true, y_score, k=100):
best = dcg_score_2d(y_true, y_true, k=k)
actual = dcg_score_2d(y_true, y_score, k=k)
return actual / best
虽然它并没有像我预期的那样在 for
循环中给我带来太多性能提升。
编辑: 我列出了该问题的三种实现方式。
首先,完全消除循环是可能的,但是生成的函数 avg_prec_noloop()
非常耗费内存,因为它试图一次完成 每个 操作。只要项目的数量在 100 以内,它就会运行得非常快。不幸的是,当项目数趋向于 1000 或更多时,它会消耗太多内存,并会导致崩溃。我包含这个只是为了表明它可以在没有循环的情况下完成,但我不建议使用它。
遵循与原始逻辑类似的逻辑,但通过在项目上添加一个循环,我们得到函数 avg_prec_colwise
。我们可以通过一次获取整个阈值列来为所有用户计算精度和召回@K。它的时间与之前的无循环实现相似,但它并不像内存消耗那么大,而且仍然有 属性 当项目 <=100 时速度要快得多,无论用户数量如何。 100,000个用户,10个item,比原来快了近300倍;但如果 items>=1000,它会比原来慢一百倍。每当你有大量用户和少量项目的场景时,我建议你使用这个。
最后,我有一个可能最接近 sklearn 的实现 avg_prec_rowwise
。当项目较低时,它没有 colwise 或 noloop 函数的惊人增益,但无论项目或用户数量如何,它始终比使用原始函数快 10-20%。对于一般用途,我建议您使用这个。
import numpy as np
from sklearn.metrics import average_precision_score as aps
from sklearn.metrics import precision_recall_curve as prc
import warnings
warnings.filterwarnings('ignore')
def mean_aps(true_scores, predicted_scores):
'''Mean Average Precision Score'''
return np.mean([aps(t, p) for t, p in zip(true_scores, predicted_scores) if t.sum() > 0])
def avg_prec_noloop(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
THRESH = np.sort(yp).T
yp = yp.reshape(1, yp.shape[0], yp.shape[1]) >= THRESH.reshape(THRESH.shape[0], THRESH.shape[1], 1)
a = (yt*(yt==yp)).sum(axis=2)
b = yp.sum(axis=2)
c = yt.sum(axis=1)
p = (np.where(b==0,0,a/b))
r = a/c
rdif = np.vstack((r[:-1]-r[1:],r[-1]))
return (rdif*p).sum()/yt.shape[0]
def avg_prec_colwise(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
N_USER, N_ITEM = yt.shape
THRESH = np.sort(yp)
p, r = np.zeros((N_USER, N_ITEM)), np.zeros((N_USER, N_ITEM))
c = yt.sum(axis=1)
for i in range(N_ITEM):
ypt = yp >= THRESH[:,i].reshape(-1,1)
a = (yt*(yt==ypt)).sum(axis=1)
b = ypt.sum(axis=1)
p[:,i] = np.where(b==0,0,a/b).reshape(-1)
r[:,i] = a/c
rdif = np.hstack((r[:,:-1]-r[:,1:],r[:,-1].reshape(-1,1)))
return (rdif*p).sum()/N_USER
def avg_prec_rowwise(yt, yp):
valid = yt.sum(axis=1) != 0
yt, yp = yt[valid], yp[valid]
N_USER, N_ITEM = yt.shape
p, r = np.zeros((N_USER, N_ITEM)), np.zeros((N_USER, N_ITEM))
for i in range(N_USER):
a, b, _ = prc(yt[i,:], yp[i,:])
p[i,:len(a)-1] = a[:-1]
r[i,:len(b)-1] = b[:-1]
rdif = np.hstack((r[:,:-1]-r[:,1:],r[:,-1].reshape(-1,1)))
return (rdif*p).sum()/N_USER
一些时间场景:1)真正少的物品
N_USERS = 10000
N_ITEMS = 10
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 47.91176509857178
Colwise: 0.16370844841003418
Rowwise: 37.96852993965149
2) 更多项目:
N_USERS = 3000
N_ITEMS = 100
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 14.943019151687622
Colwise: 2.0997579097747803
Rowwise: 11.798128604888916
3) 项目很多:
N_USERS = 3000
N_ITEMS = 1000
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
for i in range(10):
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
for i in range(10):
avg_prec_colwise(a,b)
end = time.time()
print('Colwise:',end-start)
start = time.time()
for i in range(10):
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 20.760642051696777
Colwise: 248.5634708404541
Rowwise: 17.940539121627808
4) 原始和按行的最后比较,没有任何循环:
N_USERS = 10000
N_ITEMS = 1000
a = np.random.choice(2,(N_USERS, N_ITEMS))
b = np.random.random(size=(N_USERS, N_ITEMS))
start = time.time()
mean_aps(a,b)
end = time.time()
print('Original:',end-start)
start = time.time()
avg_prec_rowwise(a,b)
end = time.time()
print('Rowwise:',end-start)
输出:
Original: 6.912739515304565
Rowwise: 5.9845476150512695
我对这个问题没有太多的见解,直到你提供了你自己的
我假设那里的代码正在执行正确的操作。
我还假设 k
通常比分数的数量小得多。
如果是这种情况,您可以相当多地优化您的代码,因为您不需要像您那样进行排序,您只对执行 [=16] 的计算时排序的最大 k
值感兴趣=].
为此,您可以使用 np.argpartition()
。
此外,您在 np.argsort()
调用后执行的索引非常麻烦。
更简洁的方法是使用 np.take_along_axis()
。
下面的代码实现了这一切。
import numpy as np
def dcg_score_opt_np(values, scores, k):
idx = np.argsort(scores, axis=1)[:, :-k - 1:-1]
values = np.take_along_axis(values, idx, 1)
result = np.sum(values / np.log2(2 + np.arange(values.shape[1])), axis=1)
return result
def ndcg_score_opt_np(y_true, y_score, k):
best = dcg_score_opt_np(y_true, y_true, k)
n = y_score.shape[1]
if n > k:
idx = np.argpartition(y_score, n - k, axis=1)[:, -k:]
y_true = np.take_along_axis(y_true, idx, 1)
y_score = np.take_along_axis(y_score, idx, 1)
actual = dcg_score_opt_np(y_true, y_score, k)
return actual / best
此外,best
的计算独立于 y_score
,如果注意到唯一相关的数量是 [=23] 中的 1
的数量,则可以更有效地计算=].
不幸的是,后续计算无法在 NumPy 中高效完成。
但是如果你愿意使用 Numba,你可以这样写:
import numpy as np
import numba as nb
@nb.jit
def dcg(scores):
result = 0
for i, score in enumerate(scores, 2):
if score > 0.0:
result += score / np.log2(i)
return result
@nb.jit
def idcg(n):
result = 0
for i in range(2, n + 2):
result += 1 / np.log2(i)
return result
@nb.jit
def dcg_score(result, scores, k):
for i in range(result.shape[0]):
result[i] = dcg(scores[i, :k])
@nb.jit
def idcg_score(result, counts, k):
for i in range(result.shape[0]):
n = counts[i] if counts[i] < k else k
result[i] = idcg(n)
def idcg_score_opt_nb(values, k):
counts = np.count_nonzero(values, axis=1)
result = np.empty(values.shape[0])
idcg_score(result, counts, k)
return result
def dcg_score_opt_nb(values, scores, k):
idx = np.argsort(scores, axis=1)[:, :-k - 1:-1]
values = np.take_along_axis(values, idx, 1)
result = np.empty(scores.shape[0])
dcg_score(result, values, k)
return result
def ndcg_score_opt_nb(y_true, y_score, k):
best = idcg_score_opt_nb(y_true, k)
n = y_score.shape[1]
if n > k:
idx = np.argpartition(y_score, n - k, axis=1)[:, -k:]
y_true = np.take_along_axis(y_true, idx, 1)
y_score = np.take_along_axis(y_score, idx, 1)
actual = dcg_score_opt_nb(y_true, y_score, k)
return actual / best
请注意 dcg_score_opt_nb()
并不是真正必要的,它似乎与 dcg_score_opt_np()
基本相当。
Numba 增强实现的另一个优点是它们的内存效率更高,因为临时数组的使用保持在最低限度。
一个简单的测试表明我们一直得到相同的结果:
N_USERS = 6
N_ITEMS = 4
k = 3
np.random.seed(0)
y_true = np.random.choice(2, (N_USERS, N_ITEMS))
y_score = np.random.random(size=(N_USERS, N_ITEMS))
print(ndcg_score_2d(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
print(ndcg_score_opt_np(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
print(ndcg_score_opt_nb(y_true, y_score, k))
# [0.61314719 1. 0.76536064 0.63092975 1. 0.38685281]
虽然对可观数据集的基准测试给出:
N_USERS = 3000
N_ITEMS = 2000
k = 100
y_true = np.random.choice(2, (N_USERS, N_ITEMS))
y_score = np.random.random(size=(N_USERS, N_ITEMS))
%timeit ndcg_score_2d(y_true, y_score, k)
# 1 loop, best of 3: 540 ms per loop
%timeit ndcg_score_opt_np(y_true, y_score, k)
# 1 loop, best of 3: 226 ms per loop
%timeit ndcg_score_opt_nb(y_true, y_score, k)
# 10 loops, best of 3: 138 ms per loop