Как выполнить matthews_corrcoef в sklearn одновременно для каждого столбца, используя матрицу?

Я хочу использовать коэффициент корреляции Мэтьюза (MCC) в sklearn, чтобы найти корреляцию между различными функциями (логическими векторами) в двумерном массиве numpyarray. До сих пор я просматривал каждый столбец и находил значение корреляции между функциями один за другим.

Вот мой код:

from sklearn.metrics import matthews_corrcoef
import numpy as np

X = np.array([[1, 0, 0, 0, 0],
              [1, 0, 0, 1, 0],
              [1, 0, 0, 0, 1],
              [1, 1, 0, 0, 0],
              [1, 1, 0, 1, 0],
              [1, 1, 0, 0, 1],
              [1, 0, 1, 0, 0],
              [1, 0, 1, 1, 0],
              [1, 0, 1, 0, 1],
              [1, 0, 0, 0, 0]])
n_sample, n_feature = X.shape
rff_all = []
for i in range(n_feature):
    for j in range(i + 1, n_feature):
        coeff_f_f = abs(matthews_corrcoef(X[:, i], X[:, j]))
        rff_all.append(coeff_f_f)
rff = np.mean(rff_all)

Поскольку у меня огромный размер 2D-массива, это кажется очень медленным и непрактичным. Каков наиболее эффективный способ одновременного выполнения такого рода операций без использования циклов?

Обновлено: Затем мне пришла в голову эта идея, но она все еще довольно медленная.

from more_itertools import distinct_combinations
all_c = []
for item in distinct_combinations(np.arange(X.shape[1]), r=2):
    c = matthews_corrcoef(X[:, item][:, 0], X[:, item][:, 1])
    all_c.append(abs(c))

Если не считать многопроцессорности, вы не получите никакого ускорения от того, что вы уже делаете.

James 30.04.2024 17:01

@James Можем ли мы использовать одну матрицу, умноженную на одну и ту же матрицу, но транспонировать матрицу, а затем выполнить matthews_corrcoef, чтобы получить одну матрицу и взять половину значений от диагонали к верхней части или от диагонали к нижней части, а затем взять среднее значение из них ценности?

Erwin 30.04.2024 17:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать numba для ускорения вычислений, например:

import numba
import numpy as np

@numba.njit
def _fill_cm(m, c1, c2):
    m[:] = 0

    for a, b in zip(c1, c2):
        m[a, b] += 1


@numba.njit
def mcc(confusion_matrix):
    # https://stackoverflow.com/a/56875660/992687

    tp = confusion_matrix[0, 0]
    tn = confusion_matrix[1, 1]
    fp = confusion_matrix[1, 0]
    fn = confusion_matrix[0, 1]

    x = (tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)
    return ((tp * tn) - (fp * fn)) / sqrt(x + 1e-6)


@numba.njit
def get_all_mcc_numba(X):
    rows, columns = X.shape

    confusion_matrix = np.zeros((2, 2), dtype = "float32")

    out = []

    for i in range(columns):
        c1 = X[:, i]
        for j in range(i + 1, columns):
            # make confusion matrix
            c2 = X[:, j]
            _fill_cm(confusion_matrix, c1, c2)
            out.append(abs(mcc(confusion_matrix)))

    return out

Тест:

from timeit import timeit
from math import sqrt

import numba
import numpy as np
from sklearn.metrics import matthews_corrcoef


def get_all_mcc_normal(X):
    n_sample, n_feature = X.shape
    rff_all = []
    for i in range(n_feature):
        for j in range(i + 1, n_feature):
            coeff_f_f = abs(matthews_corrcoef(X[:, i], X[:, j]))
            rff_all.append(coeff_f_f)
    return rff_all

@numba.njit
def _fill_cm(m, c1, c2):
    m[:] = 0

    for a, b in zip(c1, c2):
        m[a, b] += 1


@numba.njit
def mcc(confusion_matrix):
    # https://stackoverflow.com/a/56875660/992687

    tp = confusion_matrix[0, 0]
    tn = confusion_matrix[1, 1]
    fp = confusion_matrix[1, 0]
    fn = confusion_matrix[0, 1]

    x = (tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)
    return ((tp * tn) - (fp * fn)) / sqrt(x + 1e-6)


@numba.njit
def get_all_mcc_numba(X):
    rows, columns = X.shape

    confusion_matrix = np.zeros((2, 2), dtype = "float32")

    out = []

    for i in range(columns):
        c1 = X[:, i]
        for j in range(i + 1, columns):
            # make confusion matrix
            c2 = X[:, j]
            _fill_cm(confusion_matrix, c1, c2)
            out.append(abs(mcc(confusion_matrix)))

    return out

# make 2000 x 100 0-1 matrix:
np.random.seed(42)
X = np.random.randint(low=0, high=2, size=(2000, 100), dtype = "uint8")

assert np.allclose(get_all_mcc_normal(X), get_all_mcc_numba(X))


t_normal = timeit("get_all_mcc_normal(X)", number=1, globals=globals())
t_numba = timeit("get_all_mcc_numba(X)", number=1, globals=globals())

print(f"{t_normal=}")
print(f"{t_numba=}")

Распечатывает на своем компьютере (AMD 5700x):

t_normal=4.352220230008243
t_numba=0.008588693017372862

Вы можете еще больше ускорить вычисления, используя многопроцессорную обработку numba:

@numba.njit(parallel=True)
def get_all_mcc_numba_parallel(X):
    rows, columns = X.shape

    num_cpus = numba.get_num_threads()

    # for each thread, allocate array for confusion matrix
    cms = []
    for i in range(num_cpus):
        cms.append(np.empty((2, 2), dtype = "float32"))

    out = np.empty(shape=(columns, columns), dtype = "float32")

    # make indexes for each thread
    thread_column_idxs = np.array_split(np.arange(columns), num_cpus)

    for i in range(columns):
        c1 = X[:, i]

        for thread_idx in numba.prange(num_cpus):
            for j in thread_column_idxs[thread_idx]:
                if j < i + 1:
                    continue

                c2 = X[:, j]
                _fill_cm(cms[thread_idx], c1, c2)
                out[i, j] = abs(mcc(cms[thread_idx]))

    out2 = []
    for i in range(columns):
        for j in range(i + 1, columns):
            out2.append(out[i, j])

    return out2

Тестирование (с использованием только версий numba/numba+parallel):

import perfplot

np.random.seed(42)
X = np.random.randint(low=0, high=2, size=(2000, 100), dtype = "uint8")

assert np.allclose(get_all_mcc_normal(X), get_all_mcc_numba(X))
assert np.allclose(get_all_mcc_numba(X), get_all_mcc_numba_parallel(X))

perfplot.show(
    setup=lambda n: np.random.randint(low=0, high=2, size=(2000, n), dtype = "uint8"),
    kernels=[
        get_all_mcc_numba,
        get_all_mcc_numba_parallel,
    ],
    labels=["single", "parallel"],
    n_range=[100, 250, 500, 750, 1000],
    xlabel = "2000 rows x N columns",
    logx=True,
    logy=True,
    equality_check=np.allclose,
)

Создает этот график:


Для матрицы с 10_000 строк и 10_000 столбцов:

t_numba=782.5051075649972            (~13 minutes)
t_numba_parallel=215.43611432801117  (~3.5 minutes)

Огромное спасибо за содержательный подход. У меня есть связанный с этим вопрос. Предположим, у меня есть матрица X и выходной класс, скажем, y. Как я могу вычислить MCC между каждым признаком X с y и одновременно и быстрее получить среднее значение?

Erwin 01.05.2024 09:16

@Эрвин, я предлагаю задать новый вопрос здесь, на stackoverflow. Я попробую посмотреть.

Andrej Kesely 01.05.2024 09:18

Вот мой новый вопрос stackoverflow.com/questions/78412549/…

Erwin 01.05.2024 09:32

Знаете ли вы, как ускорить этот процесс, в моем недавнем вопросе здесь: stackoverflow.com/questions/78419689/…

Erwin 02.05.2024 16:22

Я думаю, вы также можете использовать pdist:

from scipy.spatial.distance import pdist
np.abs(pdist(X.T, matthews_corrcoef)).mean()
0.10476190476190476

Хотя это будет значительно медленнее, чем numba, поскольку у него будет еще один цикл для абсолютности и еще один для суммирования.

Другие вопросы по теме