Узнайте, как кодировать (почти) однострочную функцию Python для вычисления (вручную) косинусных матриц сходства или корреляции, используемых во многих алгоритмах науки о данных, с использованием функции широковещательной передачи библиотеки numpy в Python.

Как вы думаете, можно ли сказать, что профессиональный гонщик MotoGP и ребенок на картинке имеют одинаковую страсть к автоспорту, даже если они никогда не встретятся, и отличаются во всех других аспектах своей жизни? Если вы думаете, что да, значит, вы уловили идею косинусного подобия и корреляции.

Теперь предположим, что вы работаете на платном телеканале и у вас есть результаты опроса двух групп подписчиков. Один из анализов может касаться сходства вкусов между двумя группами. Для этого типа анализа нас интересует отбор людей, разделяющих схожее поведение, независимо от того, «сколько времени» они смотрят телевизор. Это хорошо представлено концепцией косинусного сходства, которая позволяет рассматривать как «близкие» те «наблюдения», привязанные к некоторым интересным для нас направлениям, независимо от того, насколько различаются величины показателей друг от друга.

Например, если «человек А» смотрит 10 часов спортивных состязаний и 4 часа фильмов, а «человек Б» смотрит 5 часов спортивных состязаний, 2 часа фильмов, мы можем увидеть, что двое (в данном случае идеально) выровнены, учитывая тот факт, что независимо от того, сколько в общей сложности часов они смотрят телевизор, пропорционально они придерживаются одного и того же поведения.

Напротив, если цель состоит в том, чтобы проанализировать тех, кто смотрит подобное количество часов в интервале, евклидово расстояние было бы более подходящим, поскольку оно оценивает расстояние, как мы привыкли думать.

Из приведенной ниже таблицы довольно интуитивно понятно, как это сравнивать две точки A и B с длиной сегмента f = 10 (евклидово расстояние) с косинусом угла альфа = 0,9487, который колеблется между 1 и -1, где 1 означает то же направление и ту же ориентацию. , -1 то же направление, но противоположная ориентация.

Если ориентация не важна в нашем анализе, модуль косинуса обнулит этот эффект и считает +1 таким же, как -1.

В терминах формул косинусное сходство связано с коэффициентом корреляции Пирсона почти по той же формуле, что и косинусное сходство - корреляция Пирсона, когда векторы центрируются по их среднему значению:

Матрица косинусного сходства:

Обобщение концепции косинусного подобия, когда у нас есть много точек в матрице данных A для сравнения между собой (матрица косинусного сходства с использованием A по сравнению с A) или для сравнения с точками во второй матрице данных B (матрица косинусного сходства для A vs. B с таким же количеством измерений) - та же проблема.

Итак, чтобы сделать вещи отличными от обычных, мы хотим вычислить матрицу косинусного сходства группы точек A по сравнению со второй группой точек B, обе с одинаковым количеством переменных (столбцов), например:

Предполагая, что сравниваемые векторы находятся в строках A и B, матрица косинусного сходства будет выглядеть следующим образом, где каждая ячейка является косинусом угла между всеми векторами A (строки) со всеми векторами B (столбцы):

Если вы посмотрите на цветовой узор, вы увидите, что первые векторы «a» реплицируются по строкам, а векторы «b» реплицируются по столбцам.

Чтобы вычислить эту матрицу в (почти) одной строке кода, нам нужно найти способ использовать то, что мы знаем об алгебре для числителя и знаменателя, а затем сложить все это вместе.

Числитель ячеек:

Если мы сохраняем матрицу A фиксированной (3,3), мы должны оперировать «скалярным» произведением с транспонированием B [= ›(5,3)], и мы получим результат (3,3). В python это легко сделать с помощью:

num=np.dot(A,B.T)

Знаменатель ячеек:

Это простое умножение двух чисел, но сначала мы должны вычислить длину двух векторов. Давайте найдем способ сделать это в нескольких строках Python, используя операцию numpy broadcasting, которая является разумным способом решения этой проблемы.

Чтобы вычислить длины векторов в A (и B), мы должны сделать это:

  1. возвести в квадрат элементы матрицы A
  2. суммировать значения по строкам
  3. корень квадрат из значений из точки 2

В приведенном выше случае, когда A = (3,3) и B = (5,3), две строки ниже (помните, что ось = 1 означает «по строке») возвращают два массива (не матрицы!):

p1=np.sqrt(np.sum(A**2,axis=1)) # array with 3 elements (it’s not a matrix)
p2=np.sqrt(np.sum(B**2,axis=1)) # array with 5 elements (it’s not a matrix)

Если мы просто умножим их вместе, это не сработает, потому что "*" работает поэлементно, а формы, как вы видите, разные.

Поскольку операция «*» выполняется поэлементно, нам нужны две матрицы, в которых первая имеет вектор p1 по вертикали и копируется по ширине p2 раза, а p2 по горизонтали и копируется по высоте p1 раз.

Чтобы сделать это с «трансляцией», мы должны изменить p1 так, чтобы он стал фиксированным по вертикали (a1, a2, a3), но «эластичным» во втором измерении. То же самое с p2, так что он становится фиксированным по горизонтали и «эластичным» во втором измерении.

Для этого мы используем функцию np.newaxis со следующим:

p1=np.sqrt(np.sum(A**2,axis=1))[:,np.newaxis]
p2=np.sqrt(np.sum(B**2,axis=1))[np.newaxis,:]

p1 можно читать так: сделать вектор вертикальным (:) и добавить размер столбца, а p2 можно читать как: добавить размер строки, сделать вектор горизонтальным. Теоретически в этой операции для p2 нет необходимости, потому что p2 уже был горизонтальным, и даже если это был массив, умножение матрицы (p1) на массив (p2) приводит к матрице (если они, конечно, совместимы), но мне нравится выше, потому что он более чистый и гибкий.

Теперь, если вы посмотрите p1 и p2 до и после, вы заметите, что p1 теперь является матрицей, и поэтому p2, но все еще одномерным.

Если вы теперь умножите их на p1 * p2, то волшебство произойдет, и в результате получится матрица 3x5, такая как p1 * p2 серым цветом на изображении выше.

Итак, теперь мы можем завершить (почти) один лайнер для нашей матрицы сходства косинусов с помощью этого примера, дополненного некоторыми данными для A и B:

import numpy as np
A=np.array([[2,2,3],[1,0,4],[6,9,7]])
B=np.array([[1,5,2],[6,6,4],[1,10,7],[5,8,2],[3,0,6]])
def csm(A,B):
    num=np.dot(A,B.T)
    p1=np.sqrt(np.sum(A**2,axis=1))[:,np.newaxis]
    p2=np.sqrt(np.sum(B**2,axis=1))[np.newaxis,:]
return num/(p1*p2)
print(csm(A,B))

Матрица корреляции между A и B

Если вы хотите изменить функцию, чтобы использовать ее для вычисления корреляционной матрицы, единственное отличие состоит в том, что вы должны вычесть из исходных матриц A и B их среднее значение по строке, а также в этом случае вы можете использовать функцию np.newaxis.

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

B-B.mean(axis=1)
A-A.mean(axis=1)

Мы должны сделать вектор средних значений A совместимым с матрицей A, вертикализуя и копируя вектор столбца now шириной A раз и то же самое для B. Для этого мы можем снова использовать функцию широковещания в Python, «вертикализуя» вектор (используя ':') и создание нового (эластичного) измерения для столбцов.

B=B-B.mean(axis=1)[:,np.newaxis]
A=A-A.mean(axis=1)[:,np.newaxis]

Теперь мы можем изменить нашу функцию, включая логическое значение, где, если оно истинно, вычисляется корреляционная матрица между A и B, а если оно ложно, вычисляется матрица подобия косинуса:

import numpy as np
A=np.array([[1,2,3],[5,0,4],[6,9,7]])
B=np.array([[4,0,9],[1,5,4],[2,8,6],[3,2,7],[5,9,4]])
def csm(A,B,corr):
    if corr:
        B=B-B.mean(axis=1)[:,np.newaxis]
        A=A-A.mean(axis=1)[:,np.newaxis]
    num=np.dot(A,B.T)
    p1=np.sqrt(np.sum(A**2,axis=1))[:,np.newaxis]
    p2=np.sqrt(np.sum(B**2,axis=1))[np.newaxis,:]
return num/(p1*p2)
print(csm(A,B,True))

Обратите внимание, что если вы используете эту функцию для вычисления корреляционной матрицы, результат будет аналогичен функции numpy np.corrcoef (A, B) с той разницей, что функция numpy вычисляет также корреляцию A с A и B с B, что может быть избыточным и заставит вас вырезать части, которые вам не нужны. Например, корреляция A и B находится в подматрице справа вверху, которую можно вырезать, зная формы A и B и работая с индексами.

Конечно, есть много способов сделать то же самое, что описано здесь, включая другие библиотеки и функции, но np.newaxis довольно умен, и в этом примере я надеюсь, что помог вам в этом… направлении