Байесовская статистика
Учимся ранжировать по байесовскому методу
Реализуйте модель Брэдли-Терри в PyMC.
Представьте, что группа игроков соревнуется в какой-то игре один на один. Тогда возникает естественный вопрос:
Как ранжировать игроков?
Теоретически эта задача не должна быть слишком сложной — пусть они сыграют несколько игр и проверят коэффициент выигрыша игроков. К сожалению, у этого естественного подхода есть некоторые проблемы:
- вы не можете сказать, означает ли коэффициент выигрыша близкий к 100%, что игрок исключителен, или же игрок топтал только слабых противников, и
- если игрок сыграл всего несколько игр, оценка силы этого игрока должна сопровождаться высокой неопределенностью, чего не дает исходный коэффициент выигрышей. .
Во многих местах вы можете столкнуться с проблемами, которые можно сформулировать как игры «один на один»:
- при рейтинге игроков в настоящих соревнованиях: теннисе, гонках, карточных играх, боях покемонов и т. д.
- при ранжировании результатов поиска:один результат поискалучше другого, если он более релевантен запросу пользователя.
- когда рекомендации ранжирования:одна рекомендация лучше другой, если она больше соответствует тому, что пользователь может захотеть купить.
- …
В этой статье я покажу вам, как построить простую байесовскую модель в PyMC, которая решает эту проблему. Если вы не понимаете, о чем я говорю, взгляните на мое введение в байесовский мир с использованием PyMC3, предшественника PyMC с почти идентичным синтаксисом.
Модель Брэдли-Терри
Модель, которую мы будем использовать, называется моделью Брэдли-Терри, и мы придаем ей байесовский вид. Звучит страшно, но на самом деле это довольно просто. Просто следуй за мной.
Модель Интуиция
Вся модель сводится к всего двум предположениям:
- Мы предполагаем, что каждый игрок (человек, покемон, результат поиска, рекомендация и т. д.) обладает некоторой силой (навыком, релевантностью и т. д.).
- Если соревнуются Игрок 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).
Надеюсь, что сегодня вы узнали что-то новое, интересное и полезное. Спасибо за прочтение!
И последнее, если вы
- поддержите меня в написании дополнительных статей о машинном обучении и
- в любом случае планируйте получить подписку на Medium,
почему бы не сделать это по этой ссылке? Это бы мне очень помогло! 😊
Чтобы быть прозрачным, цена для вас не меняется, но около половины абонентской платы идет непосредственно мне.
Большое спасибо, если вы поддержите меня!
Если у вас есть вопросы, пишите мне в LinkedIn!