Ответ 1
Позвольте разделить косинус-сходство на части и посмотреть, как и почему оно работает.
Косинус между двумя векторами - a
и b
- определяется как:
cos(a, b) = sum(a .* b) / (length(a) * length(b))
где .*
- умножение по элементам. Знаменатель здесь только для нормализации, поэтому просто назовите его L
. С его помощью наши функции превращаются в:
cos(a, b) = sum(a .* b) / L
который, в свою очередь, может быть переписан как:
cos(a, b) = (a[1]*b[1] + a[2]*b[2] + ... + a[k]*b[k]) / L =
= a[1]*b[1]/L + a[2]*b[2]/L + ... + a[k]*b[k]/L
Позвольте получить более абстрактную информацию и заменить x * y / L
на функцию g(x, y)
(L
здесь постоянна, поэтому мы не ставим ее как аргумент функции). Таким образом, наша косинусная функция становится:
cos(a, b) = g(a[1], b[1]) + g(a[2], b[2]) + ... + g(a[n], b[n])
То есть каждая пара элементов (a[i], b[i])
обрабатывается отдельно, а результат - просто сумма всех обработок. И это хорошо для вашего случая, потому что вы не хотите, чтобы разные пары (разные вершины) были беспорядочны друг с другом: если пользователь1 посещал только vertex2 и user2 - только vertex1, то у них нет ничего общего, и сходство между ними должно быть нуль. То, что вам на самом деле не нравится, - это то, как рассчитывается сходство между отдельными парами, т.е. Функция g()
.
С косинусной функцией сходство между отдельными парами выглядит следующим образом:
g(x, y) = x * y / L
где x
и y
представляют время, затраченное пользователями на вершину. И вот главный вопрос: умножение означает сходство между отдельными парами хорошо? Я так не думаю. Пользователь, который потратил 90 секунд на какую-то вершину, должен быть близок к пользователю, который провел там, скажем, 70 или 110 секунд, но гораздо более далеким от пользователей, которые тратят там 1000 или 0 секунд. Умножение (даже нормализованное на L
) совершенно вводит в заблуждение. Что это означает даже умножить 2 периода времени?
Хорошая новость заключается в том, что именно вы создаете функцию сходства. Мы уже решили, что нас удовлетворяет независимое обращение с парами (вершинами), и мы хотим только, чтобы функция индивидуального подобия g(x, y)
делала что-то разумное с ее аргументами. И что разумная функция для сравнения периодов времени? Я бы сказал, что вычитание является хорошим кандидатом:
g(x, y) = abs(x - y)
Это не функция подобия, но вместо этого функция расстояния - чем ближе значения друг к другу, тем меньше результат g()
, но в конечном итоге идея одинакована, поэтому мы можем их менять, когда нам нужно.
Мы также можем увеличить влияние больших несоответствий, возведя квадрат разницы:
g(x, y) = (x - y)^2
Эй! Мы только что заново изобрели (средняя) квадратная ошибка! Теперь мы можем придерживаться MSE для вычисления расстояния, или мы можем продолжить поиск хорошей функции g()
.
Иногда мы можем не увеличивать, а вместо этого разглаживать разницу. В этом случае мы можем использовать log
:
g(x, y) = log(abs(x - y))
Мы можем использовать специальную обработку для нулей, таких как:
g(x, y) = sign(x)*sign(y)*abs(x - y) # sign(0) will turn whole expression to 0
Или мы можем вернуться от расстояния к подобию, инвертируя разницу:
g(x, y) = 1 / abs(x - y)
Обратите внимание, что в последних параметрах мы не использовали коэффициент нормировки. Фактически, вы можете придумать хорошую нормализацию для каждого случая или просто опустить это - нормализация не всегда необходима или хороша. Например, в формуле подобия косинуса, если вы измените константу нормализации L=length(a) * length(b)
на L=1
, вы получите разные, но все же разумные результаты. Например.
cos([90, 90, 90]) == cos(1000, 1000, 1000) # measuring angle only
cos_no_norm([90, 90, 90]) < cos_no_norm([1000, 1000, 1000]) # measuring both - angle and magnitude
Подводя итог этой длинной и в основном скучной истории, я бы предложил переписывать косинус-сходство/расстояние, чтобы использовать некую разницу между переменными в двух векторах.