Прогнозирование результатов футбола с помощью случайного леса

Обзор проекта

Ставки на футбол существуют с момента изобретения футбола в 19 веке. Он присутствует в рекламных роликах, в качестве спонсоров команд или в букмекерских конторах за углом (по крайней мере, в Германии). Тем не менее, я думаю, что хорошо разбираюсь в реальных футбольных тенденциях, отлично разбираюсь в крупнейших лигах Европы и знаю большинство игроков моих любимых команд (Боруссия Дортмунд и Герта Берлин), я никогда не пытался ставить деньги на результат игра или любое другое событие, связанное с игрой. Мне всегда не хватало веских доказательств того, что мое чутье могло быть правильным.

Пользуясь Google, я сталкиваюсь с несколькими специалистами по данным и инженерами-программистами, которые объясняют самые лучшие и распространенные способы начать работу с прогнозами футбольных результатов. Один из них использовал случайный регрессор Форреста, который работает путем построения большого количества деревьев решений во время обучения и использования другой части набора данных в качестве обучающего набора для каждого дерева. Деревья решений имеют то преимущество, что они очень хорошо масштабируются с дополнительными данными, они достаточно устойчивы к несущественным функциям и их можно интерпретировать.

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

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

Если вы хотите поближе познакомиться с кодом, посетите репозиторий Github.

В другом посте мне удалось получить точность ~ 64%, сгруппировав команды с помощью анализа архетипов, а затем используя XGBoost для прогнозирования результатов. Вы можете найти это здесь".

Структура Jupyter-Notebook

Я разделил свою работу на пять частей. В первом блокноте jupyter «load_and_clean.ipynb» я загружаю и исследую необработанные файлы CSV, которые я загрузил с веб-страницы, упомянутой в абзаце выше.

Затем я сохранил результат с помощью pickle локально в той же папке и загрузил этот файл pickle в home_team_prediction.ipynb и home_team_prediction.ipynb, где я добавил дополнительные столбцы и создал деревья / леса решений Random Forrest Regressor по порядку для раздельного прогнозирования голов, забитых хозяевами поля и гостями. Затем я также сохраняю результаты с помощью pickle и загружаю эти файлы pickle в последний блокнот jupyter под названием result.ipynb, чтобы объединить данные и создать файл Excel с окончательными результатами и прогнозами.

Поскольку я выполняю аналогичную работу в «home_team_prediction.ipynb» и «home_team_prediction.ipynb», я создал пакет python, в котором я создаю многоразовые функции, которые затем могу импортировать и использовать в этих файлах.

Вопросы, на которые я буду нацеливаться:

Как правильно прогнозировать количество голов для любой команды.

Можно ли правильно предсказать исход игры (победа, ничья или поражение) по прогнозируемому количеству голов каждой команды хотя бы в 50% случаев?

Исследование данных

С этого веб-сайта мы можем загрузить CSV-файлы с данными о матчах для каждой игры лиги из всех основных лиг, таких как Англия, Испания, Германия и т. Д., И из дополнительных лиг, таких как Аргентина, Китай, Дания и т. Д. содержит всевозможную информацию, включая Удары домашней команды, Желтые карточки команды гостей, данные ставок и т. д. Для нашего анализа мы сосредоточимся на следующих столбцах:

год: 2018 и 2019 для всех точек данных
month: число для месяца в году
day: число для день года
HomeTeam: команда, проводящая матч
AwayTeam: команда в гостях
FTHG: Full Голы тайм-хозяева
FTAG: голы команды на выезде в полное время
HST: броски в створ команды хозяев
AST: Выстрелы команды гостей в створ

Помимо данных столбцов, мы также должны создать два столбца с HTGDIFF (разница мячей домашней команды) и ATGDIFF (разница мячей в гостях), чтобы измерить общую производительность команды в последних играх. Нам просто нужно вычесть FTHG из столбца FTAG и наоборот. В результате остается таблица, которая выглядит следующим образом:

Поскольку источник данных уже содержит чистые и полные данные, особых проблем с данными и аномалий ожидать не приходится. Тем не менее, важно проверить визуализацию, используя, например, функцию pandas scatter_matrix.

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

Также вызов .describe () в нашем начальном фрейме данных полезен, чтобы получить первое впечатление о наших данных, с которыми мы будем работать:

Подготовка данных

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

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

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

AVGHTGDIFF и AVGFTHG за прошлую игру

В этой статье я прочитал, что специалисты по данным в США получили лучшие результаты в спортивных прогнозах, используя среднее значение характеристики за последние 20 игр. Поскольку я прогнозирую счет голов для принимающей и гостевой команд отдельно, а в большинстве европейских футбольных лиг есть чередующиеся домашние и гостевые игры, я рассчитаю среднюю разницу мячей домашней / гостевой команд за последние 10 принимающих или гостевых игр.

Значение строки 0 в столбце «AVGHTGDIFF» рассчитывается как сумма всех значений в «ATGDIFF», деленная на 10. С помощью следующей функции мне удалось вычислить скользящее среднее для каждой строки (или для каждого дня матча):

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

Взглянув на изображение выше, уже ясно видно, что лучшая команда, такая как Бавария Мюнхен, уже идентифицирована по своим высоким показателям AVGHTGDIFF и AVGFTHG, по сравнению с командами, такими как Майнц или Падерборн, которые переживают тяжелые времена в Германии. Футбольный дивизион (Бундеслига).

HTGDIFF, FTHG и HST для последних 10 игр

Чтобы получить достаточно данных для модели, я также включаю значения HTGDIFF, FTHG и HST («Разница целей домашней команды», «Домашние цели на полную ставку» и «Выстрелы домашней команды в цель») из последние десять игр домашней команды за прошедший матч.

С помощью метода pandas .assign () мне удалось разбить массивы с прошлыми значениями для «HTGDIFF», «FTHG» и «HST» в каждую строку:

Что тогда выглядит так:

Форма наших данных теперь 561 x 33.

Определите функции и цели

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

Затем мы должны отбросить все ненужные функции, которые создавали бы шум и снижали нашу точность. Путем многократных итераций и анализа важности функций я выявил особенности, которые незначительно влияют или не влияют на прогноз. Обсудим далее в параграфе «Переменная значимость в%».

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

Рассчитать базовый уровень

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

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

Первые прогнозы на тестовых данных

Затем я тренирую нашу модель с функциями и целями с 1000 оценок. Это означает, что наша модель случайного форреста создает 1000 различных деревьев решений и в результате выводит среднее значение.

Вычитая результат наших тестовых целевых данных из результата прогноза, мы получаем среднюю абсолютную ошибку 1,24 цели. Это означает, что мы ниже нашей базовой ошибки 1,33 цели и что мы на правильном пути.

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

Столбец «FTHG» показывает нам наши прогнозы по голам, которые будут забиты хозяевами поля на 9 матчей уик-энда. Результаты выглядят реалистично по сравнению с прогнозами, которые у меня были, когда я начал подбирать свою модель (в основном, единицы или нули).

Точность

Разделив ошибки на значения test_target, я получил точность модели 21,28% на первой итерации. Это не кажется очень высоким, поэтому нам, возможно, придется улучшить параметры и отказаться от некоторых функций.

Визуализация единого дерева решений

Чтобы поближе взглянуть на то, как делаются прогнозы, мы можем внимательнее взглянуть на одно из 1000 деревьев, созданных моделью. Мы случайным образом выбираем дерево номер 10, которое ограничено глубиной 4.

С помощью этого ограниченного дерева мы можем делать прогнозы для всех игр домашней команды. Давайте рассмотрим пример:

Мы хотим предсказать, сколько голов «Фортуна Дюссельдорф» забьет в матче 22 декабря, учитывая все важные функции (все столбцы, кроме HomeTeam).

Следуя отмеченному пути, это дерево решений предсказывает, что «Фортуна Дюссельдорф» предсказывает один гол (с округлением).

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

На самом деле "Фортуна Дюссельдорф" забила два гола. Блин мы упустили!

Не совсем. Поскольку наша средняя ошибка составляет 1,21 гола, мы все еще в пределах допустимого диапазона и можем считать наш прогноз на «Фортуну Дюссельдорф» на 22 декабря успешным.

Переменные значения в%

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

'Day', 'Month', 'Year', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG',
'HTGDIFF', 'ATGDIFF', 'AVGHTGDIFF', 'AVGFTHG','HS', 'AS', 'HST', 'AST', 'HC', 'AC', 'HF', 'AF', 'HY', 'AY', 'HR', 'AR', 'HTGDIFF_1', 'HTGDIFF_2', 'HTGDIFF_3', 'HTGDIFF_4', 'HTGDIFF_5', 'HTGDIFF_6', 'HTGDIFF_7', 'HTGDIFF_8', 'HTGDIFF_9', 'HTGDIFF_10',  'HST_1',
'HST_2', 'HST_3', 'HST_4', 'HST_5', 'HST_6', 'HST_7', 'HST_8', 'HST_9', 'HST_10', 'FTHG_1', 'FTHG_2', 'FTHG_3', 'FTHG_4', 'FTHG_5', 'FTHG_6', 'FTHG_7', 'FTHG_8', 'FTHG_9', 'FTHG_10'

Подчеркивание за ярлыком означает n игр в прошлом. Например, «HST_5» означает «Удары домашней команды в створ» пять игр назад.

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

Feature Importance: AVGFTHG         14.2%
Feature Importance: Day             7.4%
Feature Importance: HST_1           6.35%
Feature Importance: AVGHTGDIFF      4.85%
Feature Importance: Month           4.72%
Feature Importance: HST_3           4.42%
Feature Importance: HTGDIFF_6       4.36%
Feature Importance: HTGDIFF_3       4.18%
Feature Importance: FTHG_1          4.09%
Feature Importance: HTGDIFF_1       4.07%
Feature Importance: HST_2           3.95%
Feature Importance: HTGDIFF_2       3.47%
Feature Importance: HTGDIFF_8       3.46%
Feature Importance: HST_5           3.31%
Feature Importance: HST_4           3.2%
Feature Importance: HST_8           3.12%
Feature Importance: HTGDIFF_4       2.99%
Feature Importance: HST_9           2.67%
Feature Importance: HST_7           2.55%
Feature Importance: HTGDIFF_5       2.5%
Feature Importance: FTHG_2          2.5%
Feature Importance: HST_10          2.48%
Feature Importance: FTHG_6          2.24%
Feature Importance: FTHG_9          2.09%
Feature Importance: Year            0.82%

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

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

В моем случае я получил функции, показанные на графике выше. Если бы я продолжил сокращать количество функций, я бы получил значительно меньшую точность 21,28%.

Подводя итог, у меня такая же точность (21,28%) с 25 функциями, затем с 53.

Пришло время повысить точность с помощью случайного поиска.

Оптимизация случайного леса посредством случайного поиска

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

Я использовал поиск по сетке гиперпараметров, как описано в этой статье. Поэтому я создал многоразовую функцию, которая принимает в качестве входных данных X_train, y_train, n_estimators, n_iters и cv:

Я в основном экспериментировал с количеством создаваемых деревьев решений (n_estimators), количеством сделанных итераций (n_iter) и увеличением складок перекрестной проверки.

Чтобы повысить точность прогнозов для целей домашней команды, я сохранил диапазон n_estimators от 10 до 1000 и изменил количество кратностей перекрестной проверки с 3 до 5.

Затем я сохранил результат в переменной:

best_params = rs.best_params_
best_params =
{'n_estimators': 434,
 'min_samples_split': 10,
 'max_leaf_nodes': 19,
 'max_features': 0.6,
 'max_depth': 5,
 'bootstrap': False}

Затем я переоборудовал модель случайного леса с новыми параметрами:

Вуаля!

Для прогноза голов нашей домашней команды; средняя абсолютная ошибка с 1,24 гола осталась прежней, но точность улучшилась с 21,28% до 24,82%.

Переделав весь процесс, описанный выше, для прогнозирования голов команды гостей, мне удалось снизить среднюю абсолютную ошибку с 0,98 до 0,89 гола и повысить точность с 31,21% до 35,46%.

В конце записных книжек «home_team_prediction» и «away_team_prediction» я сохраняю результаты локально в файлах Excel, которые будут загружены в пятую записную книжку.

Объединение прогнозов

В result.ipynb я загрузил файлы / результаты Excel из записных книжек «home_team_prediction» и «away_team_prediction» и объединил их в один фрейм данных. Столбцы «HTGDIFF» и «ATGDIFF» - это фактические / тестовые разницы голов по сравнению с прошлыми матчами. Для пояснения я переименовал их в «test_HTGDIFF» и «test_ATGDIFF». Чтобы сравнить их с моими прогнозами, я создал два новых столбца с разницей мячей для домашней и гостевой команд, назвав их «pred_HTGDIFF» и «pred_ATGDIFF». Тогда наш фрейм данных выглядит так:

При использовании «pred_HTGDIFF» или «pred_ATGDIFF» (не имеет значения, поскольку мы будем использовать только абсолютные числа) для вычисления средней абсолютной ошибки и точности мы получаем следующие результаты:

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

Что касается всех игр домашней команды с победителем, я правильно предсказал 51%, на ничью 29% и на проигрыш 63%. Что касается прогнозов на игры выездных команд, ничьи остаются прежними - 29%, но процент выигрышей и проигрышей меняется.

Полученные результаты

Что касается прогнозов на гол, у меня точность 23%.
Полученная точность результата игры (выигрыш, ничья или проигрыш) составляет 51%.

Это значит, что мы (ячмень) преодолели отметку в 50%!

Если бы мы увидели цель успешно и с высокой точностью предсказать точное количество голов, которые будут забиты домашними и гостевыми командами, нам еще предстоит пройти долгий путь.

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

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

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

В другом посте мне удалось получить точность ~ 64%, сгруппировав команды с помощью анализа архетипов, а затем используя XGBoost для прогнозирования результатов. Вы можете найти это здесь".

использованная литература

[1] https://www.football-data.co.uk/germanym.php

[2] https://dashee87.github.io/football/python/predicting-football-results-with-statistical-modelling/

[3] https://www.sciencedirect.com/science/article/pii/S2210832717301485?via%3Dihub

[4] https://towardsdatascience.com/hyperparameter-tuning-the-random-forest-in-python-using-scikit-learn-28d2aa77dd74

[5] https://arxiv.org/pdf/1710.02824.pdf

[6] https://towardsdatascience.com/random-forest-in-python-24d0893d51c0

[7] https://towardsdatascience.com/improving-random-forest-in-python-part-1-893916666cd