Редактировать: Я перечислил три реализации для этой проблемы.
Во-первых, можно полностью исключить циклы, но результирующая функция avg_prec_noloop()
довольно требовательна к памяти, поскольку она пытается каждую операцию за одну go. Пока количество предметов находится в пределах 100, оно всегда будет работать довольно быстро. К сожалению, он потребляет слишком много памяти, когда количество предметов стремится к 1000 или более, и вызовет крэ sh. Я включил это только для того, чтобы показать, что это можно сделать без циклов, но я не рекомендую использовать его.
Следуя аналогичной логике c к оригиналу, но добавляя один l oop над элементами у нас есть функция avg_prec_colwise
. Мы можем рассчитать точность и вызвать @K для всех пользователей, используя целые пороговые столбцы за раз. Он имеет время, аналогичное предыдущей реализации no l oop, но он не так требователен к памяти и все еще обладает тем свойством, что он немного быстрее, когда элементы <= 100, независимо от количества пользователей. Для 100 000 пользователей и 10 товаров это почти в 300 раз быстрее, чем оригинал; но если items> = 1000, он становится в сто раз медленнее, чем оригинал. Всякий раз, когда у вас есть сценарий с большим количеством пользователей и небольшим количеством элементов, я рекомендую вам использовать это.
Наконец, у меня есть реализация avg_prec_rowwise
, которая, возможно, наиболее близка к реализации sklearn. Он не имеет удивительного усиления функций colwise или nol oop, когда элементы ниже, но он последовательно на 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
Некоторый временной сценарий ios: 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)
Out:
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)
Out:
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)
Out:
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)
Out:
Original: 6.912739515304565
Rowwise: 5.9845476150512695