Алгоритм расчета шансов команды на победу в спортивном матче с учетом полной истории
предположения:
- команды никогда не меняются
- команды не улучшаются в мастерстве
- вся история производительности каждой команды против некоторого подмножества других команд известна
- количество игр, сыгранных между командами, большое, но потенциально разреженное (каждая команда не играла друг с другом)
например:
У меня есть длинный список результатов матча, которые выглядят как это:
Team A beats Team B
Team B beats Team A
Team A beats Team B
Team C beats Team A
Team A beats Team C
проблема:
предсказать правильные ставки шансы любой команды, победив любую другую команду.
в приведенном выше примере, возможно, мы придем к выводу, что A должен бить B 66% времени. Это основано на прямом наблюдении и довольно просто. Однако найти вероятность того, что C бьет B, кажется сложнее. Они никогда не играли вместе, но кажется, что, скорее всего, C > B, с некоторой низкой уверенностью.
Я Сделано:
Я читал довольно много о различных системах ранжирования для игр мастерства, таких как рейтинговые системы Elo и Glicko для шахмат. Это потому что они делают предположения о распределениях вероятностей. Например, центральное предположение Эло состояло в том, что шахматная производительность каждого игрока в каждой игре является нормально распределенной случайной величиной. Однако, согласно Википедии, есть и другие дистрибутивы, которые лучше соответствуют существующим данным.
I не хочу предполагать распространение. Мне кажется, что с результатами 10,000+ match я должен быть в состоянии либо вывести распределение из доказательств (я не знаю, как это сделать), либо использовать какую-то схему обучения подкреплению, которая не заботится о том, что такое распределение.
7 ответов
вы хотите сделать лучшую оценку вероятности (или нескольких вероятностей) и постоянно обновлять свою оценку по мере поступления дополнительных данных. Это требует Байесовский вывод! Байесовские рассуждения, основанные на наблюдении, что вероятность (распределение) две вещи, а и Б, будучи в одно и то же время равна вероятности (распределения) в таком случае, учитывая, что б в случае раз вероятность того, что б в случае. В формуле форма:
P (A, B) = P(A|B) P(B)
и
P (A, B) = P(B|A) P(A)
и поэтому
P (A|B)P(B) = P(B / A)P(A)
возьмите P (B) на другую сторону, и мы получим байесовское правило обновления:
P (A|B)' = P(B/A)P(A) / P(B)
обычно A означает любую переменную, которую вы пытаетесь оценить (например, " команда x beats team y"), А B-ваши наблюдения (например, полная история выигранных и проигранных матчей между командами). Я написал prime (т. е. цитату в P (A/B)'), чтобы показать, что левая рука уравнения представляет собой обновление ваших убеждений. Чтобы сделать его конкретным, ваш новая оценка вероятности того, что команда x победит команду y,учитывая все наблюдения до сих пор, вероятность выполнения этих наблюдений учитывая твои предыдущие оценка, раз ваш оценка, деленная на общую вероятность увидеть наблюдения, которые вы видели (т. е. не учитывая никаких предположений об относительной силе между командами; одна команда выигрывает большую часть времени с меньшей вероятностью, чем обе команды выигрывают примерно одинаково часто).
P(A|B)' с левой стороны текущего обновления становится новым P (A) с правой стороны следующего обновления. Вы просто продолжаете повторять это по мере поступления новых данных. Как правило, в чтобы быть максимально беспристрастным, вы начинаете с полностью плоского распределения для P (A). Со временем P (A) будет становиться все более и более определенным, хотя алгоритм довольно хорошо справляется с внезапными изменениями базовой вероятности, которую вы пытаетесь оценить (например, если команда x внезапно становится намного сильнее, потому что новый игрок присоединяется к команде).
хорошей новостью является то, что Байесовский вывод хорошо работает с бета-распределения который ElKamina также упомянутый. На самом деле они часто объединяются в системах искусственного интеллекта, которые предназначены для изучения распределения вероятности. Хотя бета-дистрибутив сам по себе все еще является предположением, он имеет то преимущество, что он может принимать множество форм (включая полностью плоский и чрезвычайно колючий), поэтому относительно мало причин беспокоиться о том, что ваш выбор дистрибутива может повлиять на ваш результат.
одна плохая новость заключается в том, что вам все равно придется делать предположения, помимо бета-распределения. Например, предположим, что у вас есть следующие переменные:
A: команда x бьет команду y
B: команда y бьет команду z
C: команда x бьет команду z
и у вас есть наблюдения от прямых совпадений между x и y и от совпадений между y и z, но не от совпадений между x и Z. Простым(хотя и наивным) способом оценки P (C) может быть предположение о транзитивности:
P(C) = P (A) P(B)
независимо от того, насколько сложен ваш подход, вам придется определить какую-то структуру вероятностей, чтобы справиться с пробелами, а также взаимозависимостями в ваших данных. Какую бы структуру вы ни выбрали, она всегда будет предположением.
еще одна плохая новость заключается в том, что этот подход прост сложная и я не могу дать вам полный отчет о том, как применить его к вашей проблеме. Учитывая, что вам нужна структура взаимозависимых вероятностей (вероятность того, что команда x победит команду y, учитывая другие распределения с участием команд x, y и z), вы можете использовать сеть Байеса или связанный анализ (например, a Марковское случайное поле или анализ пути).
надеюсь, это поможет. В любом случае, не стесняйтесь просить разъяснений.
нам нужно сделать некоторые предположения, как видно из следующего примера:
Team Rock beats Team Scissors
Team Paper beats Team Rock
Team Rock beats Team Scissors
Теперь у нас есть борьба между ножницами команды и командой бумаги. Поскольку Team Paper победила Team Rock, которая снова дважды победила team Scissors, мы можем предположить, что лучшие шансы-на бумаге.
однако в приведенном выше мы предположили транзитивную модель, которая явно не применима. Это может быть лучше подходит для некоторых видов спорта, таких как футбол, но все же не именно так.
что делает Эло, так это предположить, что все команды имеют некоторую "врожденную силу" по шкале от 0
to infinity
. Должно быть очевидно, что никакие навыки не работают так, как это, но это оказывается полезной моделью для прогнозирования игр. Также обратите внимание, как эта модель не работает хорошо для игры Rock, Paper, Scissors.
следующее предположение, сделанное в шахматах, заключается в том, что абсолютная разница в "собственной силе" создает распределение вероятности по вероятности один игрок бьет другого. Опять же, это явно не так, так как такие вещи, как белые/черные фигуры, также играют. Однако это можно сделать более точным (показывает доказательства), посмотрев на шансы на победу в нескольких играх.
с вышеуказанными двумя предположениями мы можем рассчитать шансы на победу, и если модель окажется хорошо подходящей для игры, они могут быть достаточно точными. К сожалению, такого рода моделирование не является точной наукой, и независимо от того, какие предположения вы всегда можете найти игру/ситуацию, где они не применяются.
Я надеюсь, что это дало вам некоторое вдохновение для того, чтобы придумать больше предположений для вашей модели :) как только вы их получите, вы можете проверить, насколько хорошо он работает, увидев, может ли он предсказать некоторые из результатов, которые у вас уже есть в вашем наборе тренировок. Существует целый мир машинного обучения и статистики литературы там для вас, чтобы наслаждаться:)
Почему бы вам просто не использовать Рейтинг Процентный Индекс из Википедии. Вы можете найти его лучше объяснить там, но в качестве быстрого введения вы используете следующую формулу:
RPI = (WP * 0.25) + (OWP * 0.50) + (OOWP * 0.25)
WP: процент выигрыша выигрыши / games_played
компании OWP: рассчитывается, принимая среднее значение WP для каждого из противников команды с требованием, чтобы все игры против команды, о которой идет речь, удаляются из расчета
OOWP: среднее значение OWP каждого противника.
эта проблема также использовалась в Google Code Jam.
надеюсь, что algortihm может помочь вам.
все благодаря Википедии.
рейтинг процентный индекс-реализован в C# ниже:
// <copyright file="RPICalculator.cs" company="Steve Stokes Consulting, LLC">
// Copyright © Steve Stokes Consulting, LLC. All Rights Reserved.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RPI.Calculator
{
public class RPICalculator
{
public void Test()
{
//Home Score Away Score
//UConn 64 Kansas 57
//UConn 82 Duke 68
//Minnesota 71 UConn 72
//Kansas 69 UConn 62
//Duke 81 Minnesota 70
//Minnesota 52 Kansas 62
//resulting in the following ratings:
//UConn: 0.6660
//Kansas: 0.6378
//Duke: 0.3840
//Minnesota: 0.3403
List<Team> Teams = new List<Team>();
List<Match> Matches = new List<Match>();
Teams.Add(new Team()
{
Name = "UConn",
});
Teams.Add(new Team()
{
Name = "Kansas",
});
Teams.Add(new Team()
{
Name = "Duke",
});
Teams.Add(new Team()
{
Name = "Minnesota",
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "UConn").First(),
AwayTeam = Teams.Where(t => t.Name == "Kansas").First(),
Winner = Teams.Where(t => t.Name == "UConn").First(),
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "UConn").First(),
AwayTeam = Teams.Where(t => t.Name == "Duke").First(),
Winner = Teams.Where(t => t.Name == "UConn").First(),
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "Minnesota").First(),
AwayTeam = Teams.Where(t => t.Name == "UConn").First(),
Winner = Teams.Where(t => t.Name == "UConn").First(),
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "Kansas").First(),
AwayTeam = Teams.Where(t => t.Name == "UConn").First(),
Winner = Teams.Where(t => t.Name == "Kansas").First(),
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "Duke").First(),
AwayTeam = Teams.Where(t => t.Name == "Minnesota").First(),
Winner = Teams.Where(t => t.Name == "Duke").First(),
});
Matches.Add(new Match()
{
HomeTeam = Teams.Where(t => t.Name == "Minnesota").First(),
AwayTeam = Teams.Where(t => t.Name == "Kansas").First(),
Winner = Teams.Where(t => t.Name == "Kansas").First(),
});
var results = Calculate(Teams, Matches, Sport.NCAA_Basketball);
foreach (var team in results)
{
Debug.WriteLine(string.Format("{0} - {1}", team.Name.PadRight(Teams.Select(t => t.Name).Max(s => s.Length)), team.RPI));
}
}
private decimal defaultHomeWinValue = 1.0m;
private decimal defaultAwayWinValue = 1.0m;
private decimal homeWinValue = 1.0m;
private decimal awayWinValue = 1.0m;
public IEnumerable<TTeam> Calculate<TTeam, TMatch>(IEnumerable<TTeam> Teams, IEnumerable<TMatch> Matches, Sport Sport)
{
// TODO: transform a user team to our internal team type
}
/// <summary>
/// Calculate the RPI of each team
/// </summary>
/// <param name="Teams">The list of teams to calculate RPI for</param>
/// <param name="Matches">The list of matches and results of the matches the teams played in a period</param>
/// <param name="Sport">The sport the teams played - this modifies home/away weighting based on NCAA rules</param>
/// <returns>The list of teams with calculated RPI's</returns>
public IEnumerable<Team> Calculate(IEnumerable<Team> Teams, IEnumerable<Match> Matches, Sport Sport)
{
SetWeightingBasedOnSport(Sport);
foreach (var team in Teams)
{
// calculate the WP of each team
team.WP = CalculateWinPercent(team, Matches, homeWinValue, awayWinValue);
// calculate the OWP of each team
team.OWP = CalculateOpponentsWinPercent(team, Matches);
// calculate the OOWP of each team
team.OOWP = CalculateOpponentsOpponentsWinPercent(team, Teams, Matches);
// calculate the RPI for each team
team.RPI = CalculateRPI(team);
}
return Teams.OrderByDescending(t => t.RPI);
}
private decimal CalculateRPI(Team team)
{
//RPI = (WP * 0.25) + (OWP * 0.50) + (OOWP * 0.25)
//UConn: 0.6660
//Kansas: 0.6378
//Duke: 0.3840
//Minnesota: 0.3403
var RPI = (team.WP * 0.25m) + (team.OWP * 0.50m) + (team.OOWP * 0.25m);
return Math.Round(RPI, 4);
}
private decimal CalculateOpponentsOpponentsWinPercent(Team teamInQuestion, IEnumerable<Team> Teams, IEnumerable<Match> Matches)
{
//UConn: ((Kansas 0.6667) + (Kansas 0.6667) + (Duke 0.3333) + (Minnesota 0.3889)) / (4 games) = 0.5139
//Kansas: ((UConn 0.7500) + (UConn 0.7500) + (Minnesota 0.3889)) / (3 games) = 0.6296
//Duke: ((UConn 0.7500) + (Minnesota 0.3889)) / (2 games) = 0.5694
//Minnesota: ((UConn 0.7500) + (Duke 0.3333) + (Kansas 0.6667)) / (3 games) = 0.5833
// get each team i've played this season (not unique)
var teamsIvePlayed = Matches.Where(m => m.AwayTeam == teamInQuestion || m.HomeTeam == teamInQuestion).Select(s => s.HomeTeam == teamInQuestion ? s.AwayTeam : s.HomeTeam);
// get the opponent win percent (OWP) of each team I played
var teamsIvePlayedOpponentWinPercent = teamsIvePlayed.Select(t => new { Team = t, OWP = CalculateOpponentsWinPercent(t, Matches) });
// calculate the OOWP
return (decimal)(teamsIvePlayedOpponentWinPercent.Sum(t => t.OWP) / teamsIvePlayed.Count());
}
private decimal CalculateOpponentsWinPercent(Team teamInQuestion, IEnumerable<Match> Matches)
{
// get each teams WP without the team in question
//Home Score Away Score
//UConn 64 Kansas 57
//UConn 82 Duke 68
//Minnesota 71 UConn 72
//Kansas 69 UConn 62
//Duke 81 Minnesota 70
//Minnesota 52 Kansas 62
//UConn: ((Kansas 1.0) + (Kansas 1.0) + (Duke 1.0) + (Minnesota 0)) / (4 games) = 0.7500
//Kansas: ((UConn 1.0) + (UConn 1.0) + (Minnesota 0.0)) / (3 games) = 0.6667
//Duke: ((UConn 0.6667) + (Minnesota 0.0)) / (2 games) = 0.3333
//Minnesota: ((UConn 0.6667) + (Duke 0.0) + (Kansas 0.5)) / (3 games) = 0.3889
// get each team i've played this season (not unique)
var teamsIvePlayed = Matches.Where(m => m.AwayTeam == teamInQuestion || m.HomeTeam == teamInQuestion).Select(s => s.HomeTeam == teamInQuestion ? s.AwayTeam : s.HomeTeam);
// get the win percent of each team I played excluding matches with me
var teamsIvePlayedWinPercent = teamsIvePlayed.Select(t => new
{
Team = t,
WP = CalculateWinPercent(t, Matches.Where(m => m.AwayTeam != teamInQuestion && m.HomeTeam != teamInQuestion), defaultHomeWinValue, defaultAwayWinValue)
});
// calculate the OWP
return (decimal)(teamsIvePlayedWinPercent.Sum(t => t.WP) / teamsIvePlayed.Count());
}
private decimal CalculateWinPercent(Team teamInQuestion, IEnumerable<Match> Matches, decimal HomeWinValue, decimal AwayWinValue)
{
// get the teams win percent - sometimes weighted based on NCAA rules
//UConn: (0.6 + 0.6 + 1.4 + 0) / (0.6 + 0.6 + 1.4 + 1.4) = 0.6500
//Kansas: (0 + 0.6 + 1.4) / (1.4 + 0.6 + 1.4) = 0.5882
//Duke: (0 + 0.6) / (1.4 + 0.6) = 0.3000
//Minnesota: (0 + 0 + 0) / (0.6 + 1.4 + 0.6) = 0.0000
// get my wins and sum with weighting
var wins = Matches.Where(m => m.Winner == teamInQuestion).Sum(s => s.HomeTeam == teamInQuestion ? HomeWinValue : AwayWinValue);
// get my games played count weighted
var gamesPlayed = Matches.Where(m => m.HomeTeam == teamInQuestion || m.AwayTeam == teamInQuestion).Sum(s => s.HomeTeam == teamInQuestion ? HomeWinValue : AwayWinValue);
// get the WP
return wins / gamesPlayed;
}
private void SetWeightingBasedOnSport(Sport Sport)
{
switch (Sport)
{
case Sport.NCAA_Basketball:
homeWinValue = 0.6m;
awayWinValue = 1.4m;
break;
case Sport.NCAA_Baseball:
homeWinValue = 0.7m;
awayWinValue = 1.3m;
break;
default:
homeWinValue = defaultHomeWinValue;
awayWinValue = defaultAwayWinValue;
break;
}
}
}
public enum Sport
{
NoHomeOrAwayWeighting = 1,
NCAA_Basketball = 2,
NCAA_Baseball = 3,
}
public class Team
{
public string Name { get; set; }
public decimal RPI { get; internal set; }
public decimal WP { get; internal set; }
public decimal OWP { get; internal set; }
public decimal OOWP { get; internal set; }
}
public class Match
{
public Team HomeTeam { get; set; }
public Team AwayTeam { get; set; }
public Team Winner { get; set; }
}
}
вы можете попробовать применить http://en.wikipedia.org/wiki/Elo_rating_system, главным образом на том основании, что другие люди использовали это для разработки сильных сторон. Однако любая такая система действительно основана на какой-то вероятностной модели того, что происходит на самом деле, и вам лучше попытаться придумать ее для вашей конкретной игры. Например, для футбола один из подходов заключается в моделировании количества голов, забитых командой, как процесса Пуассона, который зависит от сила их нападения и оборона другой стороны. Как только у вас есть модель, вы можете поместить ее в данные, например, по максимальной вероятности.
в качестве примера модели, делающей разницу, посмотрите на http://en.wikipedia.org/wiki/Nontransitive_dice. Это простой пример ситуации, в которой а обычно побеждает в, В обычно побеждает с, А С обычно побеждает а, а это не то, что вы ожидаете, учитывая простую одномерную систему силы.
===Шаг 1===
предположим, две команды A и B сыграли n матчей друг с другом и выиграли m раз. Применяя плоское бета-распределение, вероятность выигрыша в следующий раз равна: (m+1)/(n+2).
Как вы можете видеть, если m и n-большие числа, это примерно равно m/n.
===Шаг 2===
в вашем случае я предлагаю следующую стратегию.
пусть m = mp + md + mr и n = np + nd + nr
суффиксы П означает до, d означает прямой и R означает косвенный.
вы можете установить mp и np на 1 и 2 соответственно (предполагая, что плоский до) или продвинутым способом (подробно в конце)
md и nd-это победы и игры.
mr и nr рассчитываются с некоторой стратегией.
в итоге вероятность выигрыша равна (mp+md+mr) / (np+nd+nr).
===Шаг 3===
Как рассчитать mr и nr:
вы можете использовать какое-то глушащее. Например. Если a def C и C def B, посчитайте, что p выигрывает для A против B. Для более длинных цепочек используйте экспоненциальный спад.
оптимальное значение p может быть рассчитано с помощью перекрестной проверки, где вы оставляете определенную часть данных и используете p, который максимизирует вероятность этих оставшихся данных. Для вашей конкретной проблемы я предлагаю оставить игры между парой, оцененную вероятность и сравнение с фактическими значениями.
вы можете использовать: K*log^2(s/t) в качестве штрафа, где k-количество игр между левой парой A и B, s-прогнозируемая и T-фактическая вероятность выигрыша. Вы также можете использовать такую вещь, как дивергенция KL.
===Шаг 4===
пересмотреть настройки mp и np.
вам нужно иметь много нескольких матчей между одними и теми же командами, чтобы это сработало.
для каждой пары команд вычислить вероятность победы и построить его. Если он выглядит плоским, использование 1 и 2 в качестве mp и np в порядке. Иначе пройти http://en.wikipedia.org/wiki/Beta_distribution и выберите распределение, которое лучше всего соответствует.
эта проблема может быть решена с помощью направленных графов.
пусть все команды будут вершинами, а направленное ребро между team1 и team2 означает, что team1 победил team2.
после этого вы можете разделить график на сильно связанные компоненты,и работать с каждым подключенным компонентом независимо, так как они статистически независимы. или они? Моргун
вопрос, который мы должны задать,каковы шансы из team1 бить team2?
это легко ответить, если команды, которые вы сравниваете, имеют прямые матчи между ними. В этом случае вы заботитесь только о прямых матчах; например сколько раз team1 победил team2? То, как вы отвечаете на вопрос;
(team1WinsAgainstTeam2)/(matchesPlayedBetweenThem)
это правильно? Должны ли шансы уменьшиться для teamA, когда мы знаем, что teamB играл с teamX и выиграл 100 раз, но teamX всегда побеждал teamA? Если да, отбросить все, что я сказал в этом посте :-)
окончательный алгоритм должен выглядеть примерно так:
double getOddsTeam1Winning(int team1, int team2){
if(!isInSameConnectedComponent(team1, team2)){
// if two teams are not in the same
// connected component,
// we can use heuristic,
// we'll compare how many matches has team1 won,
// compared to team2.
var team1Wins = (team1Wins - team1Loses);
var team2Wins = (team2Wins - team2Loses);
return team1Wins / (team1Wins + team2Wins);
}
if(isDirectMatchBetween(team1, team2)){
return team1WinsOverTeam2/
totalMatchesPlayedBetweenTeam1AndTeam2;
}
List<double> odds= new List<double>();
foreach(var opponentTeam in teamsThatTeam1HasPlayedWith){
var oddsThatOponentTeamBeatsTeam2 = getOddsTeam1Winning(opponentTeam, team2);
var oddsThatTeam1BeatsOpponentTeam = getOddsTeam1Winning(team1, opponentTeam);
// combine them & push them to odds list
}
return odds.Average(); // taking average of odds?!
}
ps, я собрал это за несколько минут, не совсем уверен, что это правильно математически, но я думаю, что это решит проблему в вашей исходной проблеме, которую вы перечислили, по крайней мере, один ее экземпляр :p.