#python #arrays #numpy
Вопрос:
У меня есть двумерный массив, где столбцы-турниры, а строки-результаты игроков. Что я хотел бы сделать, так это создать матрицу результатов, где каждая строка представляет частоту выигрышей конкретного игрока против всех остальных.
Вот мое наивное решение этой проблемы.
import numpy as np
input_example = np.array([
[55.90, 81.50, 76.60, 69.50],
[52.50, 74.60, 74.00, 64.80],
[52.40, 74.90, 78.20, 60.90],
[52.60, 78.90, 77.60, 60.80],
])
output_example = np.array([
[0.50, 1.00, 0.75, 0.75],
[0.00, 0.50, 0.50, 0.25],
[0.25, 0.50, 0.50, 0.50],
[0.25, 0.75, 0.50, 0.50],
])
def results_matrix(tournament_results):
players, tournaments = tournament_results.shape
res_matrix = np.zeros(shape=(players, tournaments))
for n in range(players):
for m in range(players):
if n == m:
res_matrix[n][m] = 0.50
continue
res_matrix[n][m] = (tournament_results[n] > tournament_results[m]).sum() / tournaments
return res_matrix
if __name__ == '__main__':
res_matrix = results_matrix(input_example)
assert np.array_equal(res_matrix, output_example)
Хотя это дает правильные результаты, я хотел бы превратить это в идиоматическое решение numpy, чтобы оно быстро работало с большими входными данными.
Вы можете предположить, что результаты в конкретном турнире различны, так что ни одна пара игроков не может иметь идентичного результата.
Ответ №1:
Как насчет этого решения?
players, tournaments = input_example.shape
arr0 = input_example.reshape(players, 1, tournaments)
arr1 = input_example.reshape(1, players, tournaments)
res_matrix = (arr0 > arr1).sum(2)/tournaments
res_matrix[np.arange(players), np.arange(players)] = 0.5
Редактировать:
Если у вас недостаточно памяти, чтобы сделать это таким образом, то не будет никаких обходных циклов. Но вы можете просто использовать numba, чтобы ваш код работал быстрее, как сейчас.
import numpy as np
from numba import jit
input_example = np.array([
[55.90, 81.50, 76.60, 69.50],
[52.50, 74.60, 74.00, 64.80],
[52.40, 74.90, 78.20, 60.90],
[52.60, 78.90, 77.60, 60.80],
])
output_example = np.array([
[0.50, 1.00, 0.75, 0.75],
[0.00, 0.50, 0.50, 0.25],
[0.25, 0.50, 0.50, 0.50],
[0.25, 0.75, 0.50, 0.50],
])
def results_matrix(tournament_results):
players, tournaments = tournament_results.shape
res_matrix = np.zeros(shape=(players, players))
for n in range(players):
for m in range(players):
if n == m:
res_matrix[n][m] = 0.50
continue
res_matrix[n][m] = (tournament_results[n] > tournament_results[m]).sum() / tournaments
return res_matrix
@jit(nopython=True)
def results_matrix_ba(tournament_results):
players, tournaments = tournament_results.shape
res_matrix = np.zeros(shape=(players, players))
for n in range(players):
for m in range(players):
if n == m:
res_matrix[n][m] = 0.50
continue
res_matrix[n][m] = (tournament_results[n] > tournament_results[m]).sum() / tournaments
return res_matrix
print('running w/o numba')
%timeit assert np.array_equal(output_example, results_matrix(input_example))
print('running with numba')
%timeit assert np.array_equal(output_example, results_matrix_ba(input_example))
Выход:
running w/o numba
The slowest run took 4.10 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 5: 71.9 µs per loop
running with numba
The slowest run took 5.06 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 5: 7.74 µs per loop
Еще один пример:
np.random.seed(0)
input_example = np.random.rand(900, 500)
print('running w/o numba')
%timeit results_matrix(input_example)
print('running with numba')
%timeit results_matrix_ba(input_example)
Выход:
running w/o numba
1 loop, best of 5: 4.72 s per loop
running with numba
1 loop, best of 5: 407 ms per loop
Комментарии:
1. При попытке использовать его на большем входном размере 9000×5000 я получил
MemoryError: Unable to allocate 377. GiB for an array with shape (9000, 9000, 5000) and data type bool
2. @KonstantinKostanzhoglo Я добавил еще один вариант, который, я думаю, может помочь в вашем случае
3. отличная работа, с numba он работает в 4 раза быстрее для массива размером 9000×5000. Мне интересно, можно ли добавить многопроцессорную обработку для достижения дальнейшего ускорения?
4. обновление: Я использовал
@njit(parallel=True)
декоратор сprange
вместоrange
, и это дало 7-кратное ускорение по@jit(nopython=True)
сравнению с декоратором и 28-кратное ускорение по сравнению с моим первоначальным решением для массива размером 9000×5000.5. @KonstantinKostanzhoglo хорошая работа, спасибо за обновление.