Байесовская статистика

Учимся ранжировать по байесовскому методу

Реализуйте модель Брэдли-Терри в PyMC.

Представьте, что группа игроков соревнуется в какой-то игре один на один. Тогда возникает естественный вопрос:

Как ранжировать игроков?

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

  • вы не можете сказать, означает ли коэффициент выигрыша близкий к 100%, что игрок исключителен, или же игрок топтал только слабых противников, и
  • если игрок сыграл всего несколько игр, оценка силы этого игрока должна сопровождаться высокой неопределенностью, чего не дает исходный коэффициент выигрышей. .

Во многих местах вы можете столкнуться с проблемами, которые можно сформулировать как игры «один на один»:

  • при рейтинге игроков в настоящих соревнованиях: теннисе, гонках, карточных играх, боях покемонов и т. д.
  • при ранжировании результатов поиска:один результат поискалучше другого, если он более релевантен запросу пользователя.
  • когда рекомендации ранжирования:одна рекомендация лучше другой, если она больше соответствует тому, что пользователь может захотеть купить.

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



Модель Брэдли-Терри

Модель, которую мы будем использовать, называется моделью Брэдли-Терри, и мы придаем ей байесовский вид. Звучит страшно, но на самом деле это довольно просто. Просто следуй за мной.

Модель Интуиция

Вся модель сводится к всего двум предположениям:

  1. Мы предполагаем, что каждый игрок (человек, покемон, результат поиска, рекомендация и т. д.) обладает некоторой силой (навыком, релевантностью и т. д.).
  2. Если соревнуются Игрок 1 с силой s₁ и Игрок 2 с силой s₂, Игрок 1 побеждает с вероятностью

где σ — это ваш старый приятель, сигмовидная функция. Вот и все.

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

Однако, если у нас есть функции игроков, мы также можем включить их в модель и получить что-то вроде Microsoft’s RankNet [1]. Авторы используют архитектуру нейронной сети для явного построения сильных сторон игроков s = f(x) из характеристик x, в то время как в нашем байесовском подходе мы напрямую рассматриваем сильные стороны s как параметры.

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

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

Если теперь предположить, что сила Игрока 1 намного выше, чем у Игрока 2, то есть s₁ — s₂ — это большое число, из которого следует, что σ(s₁ — s₂ )близок к 1. Таким образом, Игрок 1 выигрывает с подавляющей вероятностью, а это именно то, что нам нужно в данном случае. Тот же аргумент работает, если сила Игрока 1 намного ниже, чем у Игрока 2. Если оба игрока имеют одинаковую силу, вероятность того, что каждый из них выиграет, равна σ(0) =50%. Идеальный!

Создание набора данных

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

Преимущество состоит в том, что мы знаем, какие свойства должна найти модель.

Чтобы создать этот набор данных, вы можете использовать следующий код:

import pandas as pd
import numpy as np

np.random.seed(0)

def determine_winner(player_1, player_2):
    if player_1 == 0 and player_2 == 1:
        return np.random.binomial(n=1, p=0.05)
    if player_1 == 0 and player_2 == 2:
        return np.random.binomial(n=1, p=0.05)
    if player_1 == 1 and player_2 == 0:
        return np.random.binomial(n=1, p=0.9)
    if player_1 == 1 and player_2 == 2:
        return np.random.binomial(n=1, p=0.1)
    if player_1 == 2 and player_2 == 0:
        return np.random.binomial(n=1, p=0.9)
    if player_1 == 2 and player_2 == 1:
        return np.random.binomial(n=1, p=0.85)

games = pd.DataFrame({
    "Player 1": np.random.randint(0, 3, size=1000),
    "Player 2": np.random.randint(0, 3, size=1000)
}).query("`Player 1` != `Player 2`")

games["Player 1 wins"] = games.apply(
    lambda row: determine_winner(row["Player 1"], row["Player 2"]),
    axis=1
)

Здесь мы создали набор данных, состоящий из трех игроков, случайно соревнующихся друг с другом. Функция determine_winner делает именно это: она принимает два индекса игрока (0, 1, 2) и выводит, если player_1 выигрывает. Например, в игре (0, 1), отмеченной жирным шрифтом в коде, игрок с номером 0 выигрывает с вероятностью p=0.05 у игрока с номером 1.

Если вы внимательно проверите вероятность, то увидите, что игрок с номером 2 должен быть лучшим, с номером 1 в середине, а с номером 0 — самым слабым.

Чтобы оживить ситуацию, давайте представим четвертого игрока, который сыграл всего две игры.

new_games = pd.DataFrame({
    "Player 1": [3, 3],
    "Player 2": [2, 2],
    "Player 1 wins": [1, 1]
})

games = pd.concat(
    [games, new_games],
    ignore_index=True
)

Игрок под номером 3 два раза играл против номера 2 и даже дважды выиграл. Таким образом, число 3 также должно иметь довольно высокую силу, но мы не можем сказать, действительно ли число 3 лучше, чем число 2, или это простое везение.

Создание модели в PyMC

Теперь мы можем построить модель в PyMC. Обратите внимание, что мы будем использовать априорные значения Гаусса для сильных сторон игроков. Кроме того, я позволю модели вывести апостериорные значения для пяти игроков, хотя данных о последнем игроке с номером 4 нет. Посмотрим, как модель справится с этим.

Еще один важный момент заключается в том, что я не буду использовать сигмовидную функцию явно. Если мы передаем разницу в силе игроков через параметр logit_p вместо p , об этом позаботится объект pm.Bernoulli.

import pymc as pm

with pm.Model() as model:
    strength = pm.Normal("strength", 0, 1, shape=5)
    diff = strength[games["Player 1"]] - strength[games["Player 2"]]
    
    obs = pm.Bernoulli(
        "wins",
        logit_p=diff,
        observed=games["Player 1 wins"]
    )
    
    trace = pm.sample()

После того, как произошло волшебство, мы можем проверить, как распределяются апостериоры. Слева вы можете увидеть апостериорные распределения в виде графиков плотности, справа вы можете увидеть график леса, который позволяет легко сравнивать апостериорные значения силы.

Здесь вы можете видеть, что номер 0 действительно является самым слабым игроком, за ним следует номер 1. Как и ожидалось, числа 2 и 3 являются лучшими. Апостериорное распределение числа 3 имеет немного более низкое среднее значение, чем у числа 2, но HDI (интервал высокой плотности) намного больше, что свидетельствует о большей неопределенности в отношении силы числа 3 по сравнению с числом 2. Апостериорная сила числа 4 такая же, как и априорная: нормально распределенная со средним значением 0 и стандартным отклонением 1. Здесь модель не смогла узнать ничего нового.

Если вам больше нравятся цифры, вот они:

Отсюда мы также можем видеть, что MCMC, похоже, хорошо сошелся, поскольку все значения r_hat равны 1.

Мы также можем видеть, что у некоторых игроков есть отрицательная сила, но это совершенно нормально, так как мы в любом случае используем только разницу в силе между двумя игроками. Если вам это не нравится по какой-либо причине, вы можете либо заменить априорные значения силы распределением HalfNormal, либо просто добавить некоторую константу, например 5, к апостериорным значениям, чтобы все средние значения и ИРЧП находились в положительном диапазоне.

Заключение

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

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

Рекомендации

[1] Берджес, К., Шакед, Т., Реншоу, Э., Лазьер, А., Дидс, М., Гамильтон, Н. и Халлендер, Г., Обучение ранжированию с использованием градиентного спуска (2005 г.). ), Материалы 22-й международной конференции по машинному обучению (стр. 89–96).

Надеюсь, что сегодня вы узнали что-то новое, интересное и полезное. Спасибо за прочтение!

И последнее, если вы

  1. поддержите меня в написании дополнительных статей о машинном обучении и
  2. в любом случае планируйте получить подписку на Medium,

почему бы не сделать это по этой ссылке? Это бы мне очень помогло! 😊



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

Большое спасибо, если вы поддержите меня!

Если у вас есть вопросы, пишите мне в LinkedIn!