Ускоренная двойная итерация по одному массиву в Python - PullRequest
7 голосов
/ 25 апреля 2019

Я хочу найти способ ускорить вычисление попарной точности, который будет сравнивать элементы одного и того же массива (в данном случае это столбец panda df), вычислять их разницу и затем сравнивать два полученных результата. Я хотел бы иметь фрейм данных df с 3 столбцами ( id документа, Jugment , который представляет оценку человека, и это объект int, PR_score , которые представляют PageRank этого документа, и это объект с плавающей точкой), и я хочу проверить, согласны ли они классифицировать один документ лучше / хуже другого.

1011 *
*

Например:

id : id1, id2, id3

Jugment : 1, 0, 0

PR_score : 0,18, 0,5, 0,12

в этом случае две оценки сходятся в том, что классификация id1 лучше, чем id3, не совпадает по id1 и id2, и между id2 и id3 существует связь между людьми, поэтому моя попарная точность:

соглашение = 1

несогласие = 1

попарная точность = соглашение / (соглашение + несогласие) = 1/2 = 0,5


Это был код моего первого решения, в котором я использовал столбец df в качестве массива (что помогает сократить время вычислений):

def pairwise(agree, disagree):
    return(agree/(agree+disagree))

def pairwise_computing_array(df):

    humanScores = np.array(df['Judgement'])  
    pagerankScores =  np.array(df['PR_Score']) 

    total = 0 
    agree = 0
    disagree = 0

    for i in range(len(df)-1):  
        for j in range(i+1, len(df)):
            total += 1
            human = humanScores[i] -  humanScores[j] #difference human judg
            if human != 0:
                pr = pagerankScores[i] -  pagerankScores[j]#difference pagerank score
                if pr != 0:
                    if np.sign(human) == np.sign(pr):  
                        agree += 1 #they agree in which of the two is better
                    else:
                        disagree +=1 #they do not agree in which of the two is better
                else:
                    continue;   
            else:
                continue;

    pairwise_accuracy = pairwise(agree, disagree)

    return(agree, disagree, total,  pairwise_accuracy)


Я пытался понять список, чтобы получить более быстрые вычисления, но на самом деле это медленнее, чем первое решение:

def pairwise_computing_list_comprehension(df):

    humanScores = np.array(df['Judgement'])  
    pagerankScores =  np.array(judgmentPR['PR_Score']) 

    sign = [np.sign(pagerankScores[i] - pagerankScores[j]) == np.sign(humanScores[i] - humanScores[j] ) 
            for i in range(len(df)) for j in range(i+1, len(df)) 
                if (np.sign(pagerankScores[i] - pagerankScores[j]) != 0 
                    and np.sign(humanScores[i] - humanScores[j])!=0)]

    agreement = sum(sign)
    disagreement = len(sign) -  agreement                             
    pairwise_accuracy = pairwise(agreement, disagreement)

    return(agreement, disagreement, pairwise_accuracy)

Я не могу запустить весь свой набор данных, потому что это занимает слишком много времени, я хотел бы иметь что-то, что в идеале может быть вычислено менее чем за 1 минуту.

Вычисление на моем компьютере небольшого подмножества из 1000 строк достигло этой производительности:

code1: 1,57 с ± 3,15 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)

code2: 3,51 с ± 10,7 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)

Ответы [ 3 ]

1 голос
/ 26 апреля 2019

Это код, который работает в разумные сроки, полученный благодаря предложению @ juanpa.arrivillaga:

from numba import jit

@jit(nopython = True)
def pairwise_computing(humanScores, pagerankScores):

    total = 0 
    agree = 0
    disagree = 0

    for i in range(len(humanScores)-1):  
        for j in range(i+1, len(humanScores)):
            total += 1
            human = humanScores[i] -  humanScores[j] #difference human judg
            if human != 0:
                pr = pagerankScores[i] -  pagerankScores[j]#difference pagerank score
                if pr != 0:
                    if np.sign(human) == np.sign(pr):  
                        agree += 1 #they agree in which of the two is better
                    else:
                        disagree +=1 #they do not agree in which of the two is better
                else:
                    continue   
            else:
                continue
    pairwise_accuracy = agree/(agree+disagree)
    return(agree, disagree, total,  pairwise_accuracy)

Это время выполнения, достигнутое для всего моего набора данных (строка 58k):

7,98 с ± 2,78 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)

1 голос
/ 26 апреля 2019

Можно избавиться от внутреннего цикла for, используя широковещательную рассылку, поскольку индекс j всегда опережает индекс i на 1 (т.е. мы не оглядываемся назад).Но есть небольшая проблема с вычислительным соглашением / несогласием в следующей строке:

if np.sign(human) == np.sign(pr):

, которую я не знаю, как решить.Итак, я просто предоставил здесь скелетный код для дополнительной настройки и обеспечения его работы, поскольку вы лучше знаете проблему.Вот оно:

def pairwise_computing_array(df):

    humanScores = df['Judgement'].values
    pagerankScores = df['PR_Score'].values 

    total = 0 
    agree = 0
    disagree = 0

    for i in range(len(df)-1):
        j = i+1
        human = humanScores[i] -  humanScores[j:]   #difference human judg
        human_mask = human != 0
        if np.sum(human_mask) > 0:  # check for at least one positive case
            pr = pagerankScores[i] -  pagerankScores[j:][human_mask]  #difference pagerank score
            pr_mask = pr !=0
            if np.sum(pr_mask) > 0:  # check for at least one positive case
                # TODO: issue arises here; how to resolve when (human.shape != pr.shape) ?
                # once this `if ... else` block is fixed, it's done
                if np.sign(human) == np.sign(pr):
                    agree += 1   #they agree in which of the two is better
                else:
                    disagree +=1   #they do not agree in which of the two is better
            else:
                continue
        else:
            continue
    pairwise_accuracy = pairwise(agree, disagree)

    return(agree, disagree, total,  pairwise_accuracy)
1 голос
/ 25 апреля 2019

У вас есть массив numpy, так почему бы просто не использовать его? Вы можете перенести работу из Python в C-скомпилированный код (часто, но не всегда):

Сначала измените размеры векторов на матрицы 1xN:

humanScores = np.array(df['Judgement']).resize((1,-1))
pagerankScores =  np.array(judgmentPR['PR_Score']).resize((1,-1))

Тогда найдите разницу, и нас интересует только знак:

humanDiff = (humanScores - humanScores.T).clip(-1,1)
pagerankDiff = (pagerankScores - pagerankScores.T).clip(-1,1)

Здесь я предположил, что данные являются целыми числами, поэтому функция clip будет выдавать только -1, 0 или 1. Тогда вы можете посчитать это:

agree = ((humanDiff != 0) & (pagerankDiff != 0) & (humanDiff == pagerankDiff)).sum()
disagree = ((humanDiff != 0) & (pagerankDiff != 0) & (humanDiff != pagerankDiff)).sum()

Но приведенный выше счет является двойным счетом, поскольку item (i, j) и item (j, i) будут точным противоположным знаком как в humanDiff, так и в pagerankDiff. Вы можете взять только верхнюю треугольную часть квадратной матрицы в сумме:

agree = ((humanDiff != 0) &
         (pagerankDiff != 0) &
         (np.triu(humanDiff) == np.triu(pagerankDiff))
        ).sum()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...