В этой статье я покажу вам альтернативный слепой подход к моделированию матчей WC, который может достичь уровней точности традиционных подходов к моделированию с некоторыми преимуществами, такими как чувствительность к «сюрпризам» (что было обычным явлением в Катаре). Также я буду использовать эту статью для критики традиционных подходов к моделированию, которые я видел до сих пор, в отношении их выбора данных и предположений.
Когда я закончил свою модель чемпионата мира, я заметил, что некоторые команды кажутся слишком сильными (например, Япония, Сербия, Иран и другие «сюрпризы»), по крайней мере, по сравнению с общепринятыми мнениями и прогнозами. Сразу после этого я начал сомневаться в качестве своей методики, как Япония собирается победить Германию? Однако по мере того, как матчи были сыграны, я мог видеть согласованность, модель просто отражала то, что она видела на данных. Затем я понял, что смог разработать хороший подход к моделированию турниров.
В моем подходе модель не знает, что Бразилия является победителем ЧМ номер 1 за все время или что Бельгия занимает первое место в ФИФА, она просто знает, что игроки страны А имеют эту статистику в этой лиге, а игроки страны Б - это другая статистика в других лигах. И, что удивительно, модель достигла того же уровня точности, что и традиционные модели «всегда ставь на фаворита».
В этой статье я объясню свой альтернативный «слепой» подход к тому, как смоделировать туалет и как построить его самостоятельно.
Предсказания
Прогнозы можно найти здесь:
https://storage.googleapis.com/public_bucket2/index.html
Прогнозы позиций:
|Group| Place | Country | |-|-|-| |A |1 |Netherlands| |A|2 |Senegal| |B|1 |England| |B|2 |IR Iran| |C|1 |Argentina| |C|2 |Poland| |D|1 |France| |D|2 |Denmark| |E|1 |Japan| |E|2 |Germany| |F|1 |Belgium| |F|2 |Croatia| |G|1 |Japan| |G|2 |Spain| |H|1 |Portugal| |H|2| Uruguay|
Проблема моделирования чемпионата мира по футболу: данные
Чемпионат мира по футболу является самым важным футбольным событием в мире (футбол является самым популярным видом спорта в мире) и спортивным событием с самой большой аудиторией. Кубок разыгрывается каждые 4 года 32 странами, которым удалось выйти из сложного, долгого и конкурентного процесса. Первый вопрос, который возникает у специалиста по обработке и анализу данных, заключается в том, можем ли мы смоделировать чемпионат мира как любую другую футбольную лигу или он настолько исключительный, что нам нужно разработать особый подход.
Я видел много попыток смоделировать чемпионат мира с моими коллегами, на моем факультете, в твиттере и по ссылкам экономистов, статистиков и специалистов по данным, которые следовали интуитивному, но все же наивному подходу: собирайте данные о предыдущих международных матчах и обучайте какую-то модель, от простых регрессий к более сложным алгоритмам, от эмпирических моделей к теоретическим, полным предположений (таких как традиционное предположение о распределении Пуассона целей).
Это наивный подход по двум причинам: во-первых, конкуренция в каждой игре разная. Большинство предыдущих матчей — это товарищеские матчи, в которых команды обычно экспериментируют с выбором игроков, стратегиями и схемами. Кроме того, команды не играют с одинаковой интенсивностью, потому что на карту ничего не поставлено, поэтому не стоит рисковать своими лучшими игроками травмами и заставлять их тратить несколько месяцев на восстановление, проиграв в будущих матчах страны и в своих клубах. . В результате вы не найдете закономерности или не той, которую нам нужно правильно смоделировать.
Вторая причина связана с первой: состав каждой команды сильно различается между турнирами и даже между последовательными товарищескими матчами, в основном, чтобы избежать риска травм, чтобы поэкспериментировать с другими игроками, а также из-за того, что некоторые другие игроки недоступны (из-за клубных ограничений, фактические травмы или уровень игры). Как следствие, преемственность состава команды не гарантируется. Так что, если мы пойдем по наивному пути, мы положим в один мешок (например, «Бразилия») десятки разных бразильских команд, которые играли исторически.
Я видел модели, использующие данные матчей 1934 года! В свое время состав команды был другим, но также и мировое распределение талантов; способ игры и даже страны (подумайте о Югославии и Федеративной Германии). Очевидно, что эти модели не будут достаточно чувствительными к современным тенденциям в футболе и будут сильно предвзяты к показателям страны за последнее столетие: например, в этих моделях Уругвай непобедим.
Я потратил много времени, размышляя над этими двумя проблемами, пытаясь решить их наилучшим образом.Мои выводы были такими: если я хочу получить хорошие прогнозы для этого единственного WC, мне нужно:
- данные матчей, в которых я уверен, что уровень состязательности аналогичен уровню ЧМ (может быть, предыдущие ЧМ или, может быть, квалификации ФИФА, турниры местных конфедераций и т. д.)
- данные о прошлых матчах, но не так давно (вероятно, более 20 лет будут бесполезны, потому что игра сильно изменилась).
- способ хоть как-то решить проблему преемственности состава команды
Решение этой проблемы значительно сократит количество качественных данных, к которым мы можем иметь доступ.
Проблема моделирования чемпионата мира по футболу: особенности
Теперь, когда мы отфильтровали данные, которые нам понадобятся, пришло время поговорить о функциях. При моделировании матча из лиги эта проблема довольно проста: используйте положение команды, ее местоположение, возможно, некоторые переменные окна веселья (такие как голы, передачи, владение мячом в течение последних n матчей). С правильными функциями вы можете легко создавать модели с точностью более 70% (некоторые из них приближаются к 90%). Мы можем достичь этих уровней, потому что регулярные сезоны дают регулярные данные!
Регулярность является нормой во многих лигах, где вы играете 38 матчей за сезон, и где у вас есть много различий в конкурентоспособности каждой команды (где у вас есть действительно четкие группы команд, которые играют за лигу, некоторые играют за средние места за столом и некоторые игры, чтобы держаться подальше от зоны вылета). Подумайте о Ла Лиге последнего десятилетия, когда победители были почти дуополиями: либо «Барселона», либо «Реал Мадрид».
Однако в WC у нас нет такого уровня регулярности, и на самом деле попытка найти регулярность — это палка о двух концах. Вы можете найти ее, используя такие переменные, как очки в рейтинге ФИФА, позиция или среднее значение. голов на команду, и вы получите регулярность, но ваша модель потеряет потенциал в обнаружении «сюрпризов».
Я видел другое семейство моделей, в которых они правильно решают проблемы качества данных, однако они много внимания уделяют регулярности и, как следствие, строят модели, нечувствительные к неожиданностям: в моделях этого типа нет места для неудачников, и они предсказывают одни и те же результаты снова и снова. Например, в этих моделях непобедимы Германия и Бельгия (одна из-за истории матчей, а другая из-за рейтинга ФИФА).
Альтернатива: слепая эмпирическая модель
В этот момент вы можете задаться вопросом: «Ну, тогда скажи мне, каков правильный подход?». Я скажу, что нет рецепта или безопасного решения при построении модели, вы должны рисковать и балансировать в зависимости от проблемы и характеристик, которые вы хотите улучшить.
Сначала я начал предлагать следующие предположения:
- Предположение 1: футбол сильно изменился за последние 2 десятилетия с точки зрения стиля игры, способностей, формаций, стратегий, конкурентоспособности лиг и интернационализации высших мировых лиг.
- Предположение 2. конкурентоспособность команды ЧМ тесно связана с качеством ее игроков, и лучший способ проверить качество отдельного игрока — это его статистика.
- Предположение 3: релевантная статистика для ЧМ — это статистика в обычных лигах, где отдельные игроки играют только в течение предыдущего сезона или во время чемпионата мира.
Для этой конкретной задачи я предложил построить модель, функции которой для каждой страны представляют собой сводку статистики каждого из 23 игроков, включенных в шорт-лист.Таким образом, моя модель не учитывает историческую значимость или популярность каждой страны. а скорее он ориентирован на необработанные данные каждого отдельного игрока, который составляет каждую команду.
Таким образом я решаю проблему наивной регулярности, не используя ранжирование или средние цели; и я решаю проблему непрерывности состава, потому что в модели каждый WC имеет уникальную комбинацию характеристик выбранных 23 игроков в течение определенного времени, а не использует команду как единое целое.
Я получил данные с помощью частного футбольного API (у которого есть множество альтернатив и разные цены, в зависимости от необходимых вам функций).
Итак, для сборки модели мне понадобилось:
- данные всех команд и матчей всех чемпионатов мира с 2006 года
- данные состава игроков каждой команды всех чемпионатов мира с 2006 года
- данные статистики игроков за последний сезон непосредственно перед чемпионатом мира.
Затем я создал набор данных игр WC с 2006 года, со статистикой игроков каждой страны в качестве X и исходом матча в качестве Y. Затем случайным образом разделил набор данных на тестовый и обучающий наборы. Наконец, я построил несколько моделей на нескольких разбиениях только для того, чтобы понять, что этот слепой подход дает регулярные прогнозы (без необходимости наивных регулярных данных) и что он может иметь среднюю точность около 70%. Вы можете проверить прогнозы Out of Bag (Чемпионат Катара) здесь: https://storage.googleapis.com/public_bucket2/index.html
Важно то, что моя модель не знает, что Бразилия является победителем ЧМ номер 1 или что Бельгия занимает первое место в ФИФА, она просто знает, что игроки страны А имеют эту статистику в этой лиге, а игроки страны Б имеют эта другая статистика в этой лиге. И, что удивительно, модель достигла того же уровня точности, что и традиционные модели «всегда делать ставки на фаворитов», но она смогла обнаружить «сюрпризы», такие как победа Японии над Германией, среди многих других.
Размышляя об этом, неудивительно, что многие страны-аутсайдеры играют хорошо, если посмотреть на конкретную статистику их игроков и лиги, в которых они играют: например, почти все японские игроки регулярно играют в высших лигах Европы. Еще одна вещь, которую я узнал, заключается в том, что этот подход на самом деле моделирует очень хорошие совпадения аутсайдеров и аутсайдеров и фаворитов и фаворитов именно потому, что он опирается на необработанные и высококачественные данные.
Цель этой статьи состояла в том, чтобы показать, как альтернативный слепой подход может достичь уровней точности традиционных подходов к моделированию с некоторыми преимуществами, однако в следующем разделе я покажу вам шаг за шагом, как я построил модель эмпирическим путем.
Руки на данных
В этом разделе я объясню, как я разработал модель. Цель состоит в том, чтобы показать общую идею, а не сосредотачиваться на конкретном технологическом стеке, чтобы каждый мог воспроизвести общую методологию со своим собственным API и выбором технологий. Если вы хотите увидеть фактическое кодирование, я загружу файлы в общедоступный репозиторий github в конце WC.
Требования к данным
Я подготовил свои источники таблиц в базе данных, хранящейся на сервере MYSQL у поставщика услуг облачных вычислений. Также для моих требований к данным я использовал футбольный API. На рынке существует множество API с разной ценой и разной глубиной данных. На данный момент мне нужны были только следующие таблицы:
- каталог стран: список всех стран, сыгравших ЧМ с 2006 г.
- каталог матчей: список всех матчей, которые произошли и будут происходить в ЧМ с 2006 года.
- каталог игроков: список всех игроков, входивших в каждую команду ЧМ с 2006 г.
- каталог лиг: список всех лиг, в которых эти игроки играли или играют в настоящее время
- каталог сезонов: из каждой лиги список соответствующих сезонов (до или во время ЧМ)
- статистика игроков: из каждого соответствующего сезона и лиги список статистики каждого игрока WC.
Я покажу вам пример, чтобы вы имели наглядное представление о требованиях к данным.
Каталог стран:
Сначала нам нужно сохранить данные для всех стран, которые играли в ЧМ с 2006 года.
Каталог матчей:
Затем нам нужны данные всех матчей ЧМ. Я решил смоделировать только WC, потому что я считаю, что это исключительный турнир, и поскольку это был экспериментальный подход, цена моего решения заключается в том, что это действительно очень маленький набор данных, который может вызвать проблемы в момент прогнозов из сумки (также это уменьшает наши степени свободы и может вызвать проблемы, если мы планируем добавить много функций). Однако, если у нас есть чистый и масштабируемый конвейер, было бы очень легко добавить данные из основных турниров УЕФА, КОНКАКАФ, КОНМЕБОЛ, КАФ, АФК, ОФК и ОФК (Копа Америка, Еврокопа, Кубок Африки и т. д.), чтобы спасти больше наблюдения и эмпирически посмотреть, как это работает (возможно, в будущем я мог бы написать статью под названием «Сложнее ли моделировать ЧМ, чем локальные турниры?»).
Каталог игроков:
Затем нам нужно построить каталог всех игроков во время каждого турнира. Например, для одной команды во время ЧМ у нас есть список их игроков, который выглядит так:
Таким образом, общая таблица для всех команд во время всех соответствующих ЧМ выглядит так:
Каталог лиги:
Затем мы создаем простой каталог лиг: в этом смысле мы можем иметь столько лиг, сколько захотим, однако, поскольку наш набор данных очень мал (и я не хочу создавать много шума в модели), я решил оставить только несколько лиг, которые имеют лучших игроков в мире и чьи игроки кормят подавляющее большинство игроков WC: Premier League, La Liga, Seria A, Bundesliga, Eredivise, Liga NOS, Ligue 1, Champions League и Europa. Лига.
Сезонный каталог:
Теперь, после обнаружения всех сезонов, в которых играли все игроки ЧМ с 2006 года, мы фильтруем соответствующие сезоны:
- 2005/2006 для ЧМ 2006
- 2009/2010 на ЧМ-2010
- 2013/2014 на ЧМ-2014
- 2017/2018 для ЧМ-2018
- 2022/2023 на ЧМ-2022
Статистика игрока
Наконец, для каждого игрока в каталоге игроков мы запрашиваем его сезонную статистику в соответствующие годы, а также в лиге и команде, в которой он играл во время ЧМ. Данные, которые мы получаем, выглядят так для каждого игрока.
[{'player_id': 31000, 'team_id': 68, 'league_id': 82, 'season_id': 213, 'captain': 0, 'minutes': 1585, 'appearences': 33, 'lineups': 15, 'substitute_in': 18, 'substitute_out': 9, 'substitutes_on_bench': 19, 'goals': 8, 'owngoals': 0, 'assists': 1, 'saves': 0, 'inside_box_saves': 0, 'dispossesed': 0, 'interceptions': 0, 'yellowcards': 4, 'yellowred': 0, 'redcards': 0, 'type': 'domestic', 'tackles': None, 'blocks': None, 'hit_post': None, 'cleansheets': 13, 'rating': None, 'fouls': {'committed': None, 'drawn': None}, 'crosses': {'total': None, 'accurate': None}, 'dribbles': {'attempts': None, 'success': None, 'dribbled_past': None}, 'shots': {'shots_total': None, 'shots_on_target': None}, 'duels': {'total': None, 'won': None}, 'passes': {'total': None, 'accuracy': None, 'key_passes': None}, 'penalties': {'won': None, 'scores': None, 'missed': None, 'committed': None, 'saves': None}}
Теперь у нас есть все данные для продолжения нашего эксперимента.
Разработка функций
Первое, что нам нужно сделать, это построить нашу статистику X игроков по командам и мою Y результатов.
Прежде всего, я подключаюсь к своей базе данных, где я хранил все предыдущие таблицы.
from sqlalchemy import create_engine import pandas as pd import numpy as np # Credentials to database connection hostname="XX.XX.XX.XX" dbname="xxxxx" uname="xxxx" pwd="xxxx" # Create SQLAlchemy engine to connect to MySQL Database engine = create_engine("mysql+pymysql://{user}:{pw}@{host}/{db}" .format(host=hostname, db=dbname, user=uname, pw=pwd))
Я делаю запрос к таблице совпадений и создаю свою переменную для прогнозирования, которая будет 3 альтернативами для 3 разных моделей (выигрыш, проигрыш или ничья).
local and visitor tags just make reference to the order # of how the teams are announced in the match # local being the firt team. df = pd.read_sql('SELECT * FROM wc_games', con=engine) df['y_local'] = np.where(df['winner_team_id'] == df['localteam_id'], 1, 0) df['y_visitor'] = np.where(df['winner_team_id'] == df['visitorteam_id'], 1, 0) df['y_tie'] = np.where((df['y_visitor'] + df['y_local'] == 0), 1, 0) df=df[['id','season_id','stage_id','round_id','localteam_id','visitorteam_id','y_local','y_visitor','y_tie']] df
Соответствующая статистика
Теперь, когда у нас есть наши матчи и готовые Y, нам нужен наш X. Чтобы построить наш X, нам нужна наша статистика, таблицы сезонов и лиг. Во-первых, нам нужно выяснить, какие сезоны и лиги относятся к делу (помните? года во время ЧМ), чтобы отфильтровать нашу таблицу статистики игроков.
# Request the stats, seasons and league tables stats = pd.read_sql('SELECT * FROM wc_player_allstats', con=engine) seasons = pd.read_sql('SELECT * FROM seasons', con=engine) leagues = pd.read_sql('SELECT * FROM leagues', con=engine) # filter to relevant leagues relevant_leagues=leagues[0:25] relevant_leagues=list(relevant_leagues.id.values) relevant_seasons = seasons[seasons['league_id'].isin(relevant_leagues)] # filter to relevant seasons leagues_names=["2005/2006","2009/2010","2013/2014","2017/2018","2022/2023"] # Then we just take the stats for each player # for each relevant league and each relevant season relevant_seasons = relevant_seasons[relevant_seasons['name'].isin(leagues_names)] relevant_seasons=relevant_seasons[["id","name","league_id"]] relevant_seasons_list=list(relevant_seasons.id.values) relevant_stats = stats[stats['season_id'].isin(relevant_seasons_list)] # this are our variables to play with! relevant_stats
Обобщить по командам
После того, как у нас есть отфильтрованная таблица статистики игроков, нам нужно обобщить эти данные по странам и сезонам. В этой модели я предложил простую сумму (которая служит для основной цели — имитировать монотонную переменную, которая служит показателем конкурентоспособности команды), однако здесь вы можете быть настолько умными, творческими или сложными, насколько захотите. Например, позже в будущем я попытаюсь поэкспериментировать в этой части, суммируя все статистические данные с индексом склонности, с собственным вектором и декомпозицией значений или, может быть, с PCA.
preproces_data=relevant_stats.merge(players, left_on='player_id', right_on='player_id') # We are filling NAs with 0s because we want a monothonic variable preproces_data = preproces_data.fillna(0) # Here we make the aggregate sum of stats for a country in a single WC preproces_data = preproces_data.groupby(['season_id', 'country_id'],as_index=False)['minutes','appearences','lineups','substitute_out', 'substitutes_on_bench','goals','owngoals','assists', 'saves','inside_box_saves','dispossesed','interceptions', 'yellowcards','yellowred','redcards','tackles', 'blocks','hit_post','cleansheets','rating','committed','drawn', 'total','accurate','attempts','success','dribbled_past', 'shots_total','shots_on_target','won','accuracy','key_passes', 'scores','missed'].apply(lambda x : x.astype(float).sum()) preproces_data=preproces_data.merge(relevant_seasons, left_on='season_id', right_on='id') preproces_data = preproces_data.drop('id', axis=1) preproces_data = preproces_data.drop('season_id', axis=1) preproces_data
Конкуренция в лиге
Теперь, когда у нас есть все сводные статистические данные для одной команды с учетом одного соответствующего сезона, я создам переменную для конкурентоспособности лиги в 3 категориях:
- вверху: Лига чемпионов, Премьер-лига и Ла Лига
- середина: Бундеслига, Серия А
- низкие: Лига Европы, Eredivise, Liga Nos и Ligue 1
В этом смысле мы будем экономить степени свободы: вместо того, чтобы иметь функции для каждой отдельной лиги, мы сгруппируем их по уровням и резко уменьшим размер нашего X.
experiment["wc_id"]=experiment["country_id"]+"-"+experiment["name"] experiment = experiment.drop('country_id', axis=1) experiment = experiment.drop('name', axis=1) league_rating= { 2:'top', 8:'top', 564:'top', 384:'mid', 82:'mid', 5:'low', 301:'low', 72:'low', 462:'low'} experiment["league_id"]=experiment["league_id"].replace(league_rating) experiment = experiment.groupby(['wc_id', 'league_id'],as_index=False)["minutes","goals","assists","interceptions","appearences","yellowcards","cleansheets","key_passes"].apply(lambda x : x.astype(float).sum()) experiment
Последний спор
Теперь у нас есть все данные и функции, которые нам нужны, последнее, что нам нужно сделать, это преобразовать данные статистики в правильный формат фрейма данных, который позволит нам обучить нашу модель. Для окончательного спора нам нужно сравнить сводную статистику одной команды со статистикой другой команды, чтобы мы могли получить богатый и густонаселенный X, который сильно различается и коррелирует с результатом. Для этого нам нужно выполнить некоторые окончательные обработки данных:
Сначала мы создаем небольшую таблицу с уникальным идентификатором для каждой команды на каждом отдельном ЧМ (поскольку Бразилия 2006 сильно отличается от Бразилии 2018, поэтому мы концептуализируем их как разные команды и не даем машине понять, что обе команды находятся в одной стране).
# First we create a table with an unique id for each team at each single WC # because Brazil 2006 is far different for Brazil 2018 preproces_data["wc_id"]=preproces_data["country_id"]+"-"+preproces_data["name"] wc_ids_catalogue=preproces_data[["country_id","name","wc_id"]] wc_ids_catalogue=wc_ids_catalogue.drop_duplicates() wc_ids_catalogue
Затем мы используем этот идентификатор для идентификации каждой команды в нашем фрейме данных матчей и для определения сводной статистики команды, которую мы создали ранее.
di = {5794: "2005/2006", 5795: "2009/2010",5796:"2013/2014",892:"2017/2018",18017:"2022/2023"} df=df.replace({"season_id": di}) df["localteam_wc_id"]=df["localteam_id"].astype(str)+"-"+df["season_id"] df["visitorteam_wc_id"]=df["visitorteam_id"].astype(str)+"-"+df["season_id"] df
# We prepare our summary stats table in a wide format # for the merge with the previous table X=experiment.pivot(index='wc_id', columns='league_id').fillna(0) X.columns = X.columns.map(lambda index: f'{index[0]}_{index[1]}') X.reset_index(inplace=True) X
Теперь у нас есть соответствующая сводная статистика каждой команды по ЧМ, но помните, что в каждом матче нам нужно играть с двумя командами, поэтому нам нужно сравнить две сводные статистики. Для этой модели я предлагаю простой отдых. Опять же, здесь можно быть таким же умным или изобретательным, но простой отдых делает работу.
local_stats=df[["localteam_wc_id"]].merge(X,how='left', left_on='localteam_wc_id', right_on='wc_id') local_stats=local_stats.drop('localteam_wc_id', axis=1) local_stats=local_stats.drop('wc_id', axis=1) visitor_stats=df[["visitorteam_wc_id"]].merge(X,how='left', left_on='visitorteam_wc_id', right_on='wc_id') visitor_stats=visitor_stats.drop('visitorteam_wc_id', axis=1) visitor_stats=visitor_stats.drop('wc_id', axis=1)
Итак, каждый из этих фреймов данных выглядит так:
После простого отдыха мы получаем матрицу разностей! это как раз то, что нужно нашим моделям: нейтральный, объективный, эмпирический, основанный на данных рейтинг популярности для конкурентоспособности команды.
core=local_stats.fillna(0)-visitor_stats.fillna(0)
Наконец, мы делаем наш окончательный кадр данных и наш готовый кадр данных для этих матчей ЧМ в Катаре:
data=pd.concat([df, core], axis = 1) data = data.drop([ 'stage_id','round_id','localteam_id', 'visitorteam_id','localteam_wc_id','visitorteam_wc_id'], axis=1) oob = data[data['season_id'] == '2022/2023'] oob = oob.drop('season_id', axis=1) data = data[data['season_id'] != '2022/2023'] data = data.drop('season_id', axis=1) oob
И мы готовы моделировать! На данный момент эти данные очень богаты (мы действительно усердно добывали золото), и мы можем использовать любую модель, которую захотим.
Моделирование
Для простоты я решил использовать 2 модели RF: одну для регрессии и одну для классификации (обе измеряют вероятность «локальной» победы в каждом матче). Я создал следующие функции, чтобы разделить наборы данных для обучения и тестирования и сравнить уровни точности для каждой модели.
import numpy as np from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from random import randint from sklearn.ensemble import RandomForestClassifier def compare_models_regressor(y,size): # Use numpy to convert to arrays # Labels are the values we want to predict labels = np.array(data[y]) index=np.array(data['id']) # Remove the labels from the features # axis 1 refers to the columns features= data.drop(['y_local','y_visitor','y_tie', 'id'], axis = 1) # Saving feature names for later use feature_list = list(features.columns) # Convert to numpy array features = np.array(features) # Using Skicit-learn to split data into training and testing sets # Split the data into training and testing sets train_features, test_features, train_labels, test_labels,index_features, index_labels = train_test_split(features, labels,index, test_size = size, random_state = randint(0,200)) # Import the model we are using # Instantiate model with 1000 decision trees rf = RandomForestRegressor(n_estimators = 100, random_state = 0) # Train the model on training data rf.fit(train_features, train_labels); # Use the forest's predict method on the test data predictions = rf.predict(test_features) # Calculate the absolute errors errors = abs(predictions - test_labels) # Print out the mean absolute error (mae) netric= sum(np.round(predictions)==test_labels)/len(test_labels) #print('netric:', netric) return(round(netric*100,2)) def compare_models_class(y,size,n): # Use numpy to convert to arrays # Labels are the values we want to predict labels = np.array(data[y]) index=np.array(data['id']) # Remove the labels from the features # axis 1 refers to the columns features= data.drop(['y_local','y_visitor','y_tie', 'id'], axis = 1) # Saving feature names for later use feature_list = list(features.columns) # Convert to numpy array features = np.array(features) # Using Skicit-learn to split data into training and testing sets # Split the data into training and testing sets train_features, test_features, train_labels, test_labels,index_features, index_labels = train_test_split(features, labels,index, test_size = size, random_state = randint(0,200)) clf = RandomForestClassifier(max_depth=n, random_state=randint(0,200)) clf.fit(train_features, train_labels) partidos_train=clf.score(train_features,train_labels) partidos_test=clf.score(test_features,test_labels) return(round(partidos_test*100,2)) ## Fine tuning
Эмпирическая эффективность
Теперь мы оцениваем точность обеих моделей для 50 различных случайных разделений, используя 20% наблюдений для тестового набора. Я использовал только 20%, потому что у нас очень мало наблюдений, поэтому нам нужно расставить приоритеты в наборе поездов:
x=0 for z in range(50): y= compare_models_regressor('y_visitor',.2) x=x+y print(y) x/50 75.0 65.38 57.69 67.31 67.31 71.15 78.85 69.23 73.08 73.08 69.23 75.0 75.0 71.15 78.85 63.46 65.38 63.46 71.15 65.38 71.15 69.23 67.31 63.46 67.31 63.46 59.62 57.69 61.54 71.15 65.38 75.0 75.0 73.08 65.38 75.0 75.0 67.31 71.15 71.15 67.31 63.46 63.46 73.08 71.15 71.15 67.31 65.38 63.46 59.62
средний = 68,5
x=0 for z in range(50): y= compare_models_class('y_visitor',.1,6) x=x+y print(y) x/50 73.08 73.08 69.23 80.77 69.23 65.38 69.23 80.77 76.92 73.08 69.23 57.69 76.92 76.92 73.08 69.23 80.77 57.69 65.38 57.69 69.23 84.62 73.08 69.23 65.38 84.62 65.38 61.54 73.08 57.69 61.54 65.38 65.38 69.23 76.92 80.77 73.08 84.62 61.54 76.92 65.38 69.23 57.69 84.62 69.23 69.23 61.54 73.08 73.08 76.92 70.692
среднее значение = 70,62
Итак, мы видим, что точность в обеих моделях на самом деле довольно стабильна, независимо от выбранного нами разделения тестов. Мы обнаружили хорошую закономерность в модели, которая не ищет ее традиционным способом: переменные ранжирования популярности, средние голы или множество нерелевантных совпадений, и это хорошие новости!
Итак, теперь мы будем использовать вторую модель, чтобы делать прогнозы для нашего готового набора данных, или, как его еще называют: Qatar WC.
## Прогнозы
Наш набор данных oob выглядит так
# this are the first 48 group stages matches oob=oob[0:48] oob= oob.drop(['y_local','y_visitor','y_tie'], axis = 1) # the image is just a sample oob
Ансамбль
Как вы можете видеть в разных моделях с разным разделением, точность сосредоточена в 70%, однако есть модели с более низкой и высокой точностью. Мы хотели бы снизить риск делать прогнозы с помощью плохой модели, поэтому для этого мы можем делать прогнозы с помощью ансамбля различных моделей. Опять же, в этой части можно быть максимально умным и креативным, думаю, можно было бы экспериментировать с подходами бэггинга и бустинга, однако из-за нехватки времени я их не реализовал.
Поскольку переобучить модель очень быстро и дешево, и я хочу гарантировать, что совпадения oob прогнозируются на основе модели с точностью 70%, я создам 200 моделей и сделаю среднее значение прогнозов, таким образом я предотвратю использование одна модель, которая может быть ниже или выше эталонного показателя 70%. Поэтому я уменьшаю дисперсию в пользу уменьшения смещения. Опять же, это тот выбор, который нужно сделать.
Кроме того, я воссоздал тот же процесс для создания дополнительных моделей: одну для ничьей и одну для победителя посетителя (хотя мы знаем, что это не влияет на то, является ли он местным или приезжим). Делая это, я мог предсказать вероятности каждого из 3 исходов: выигрыш, ничья или проигрыш.
После выполнения этого процесса я создаю следующую HTML-таблицу с прогнозами для группового этапа.
https://storage.googleapis.com/public_bucket2/index.html
Финальный ремак
И последнее замечание: математика, CS, DS предоставляют нам мощные инструменты для моделирования реальности, но если мы используем их также со знанием предметной области и интуицией, мы можем строить модели, которые предсказывают хорошие, простые для понимания, а также помогают нам понять более тонкие и сложные вещи (не на глаз) по каждой теме, в данном случае наш любимый вид спорта: футбол. Я надеюсь, что мое сообщение было доставлено, и надеюсь, что вы сможете развлечься (или заработать немного денег), используя мои прогнозы.