Dota 2 — многопользовательская компьютерная игра жанра MOBA. Игроки играют между собой матчи. В каждом матче участвует две команды, 5 человек в каждой. Одна команда играет за светлую сторону (The Radiant), другая — за тёмную (The Dire). Цель каждой команды — уничтожить главное здание базы противника (трон).
Существуют разные режимы игры, мы будем рассматривать режим Captain's Mode, в формате которого происходит большая часть киберспортивных мероприятий по Dota 2.
Всего в игре чуть более 100 различных героев (персонажей). В начале игры, команды в определенном порядке выбирают героев себе и запрещают выбирать определенных героев противнику (баны). Каждый игрок будет управлять одним героем, в рамках одного матча не может быть несколько одинаковых героев. Герои различаются между собой своими характеристиками и способностями. От комбинации выбранных героев во многом зависит успех команды.
Игроки могут получать золото и опыт за убийство чужих героев или прочих юнитов. Накопленный опыт влияет на уровень героя, который в свою очередь позволяет улучшать способности. За накопленное золото игроки покупают предметы, которые улучшают характеристики героев или дают им новые способности.
После смерти герой отправляется в "таверну" и возрождается только по прошествии некоторого времени, таким образом команда на некоторое время теряет игрока, однако игрок может досрочно выкупить героя из таверны за определенную сумму золота.
В течение игры команды развивают своих героев, обороняют свою часть поля и нападают на вражескую.
Игра заканчивается, когда одна из команд разрушет определенное число "башен" противника и уничтожает трон.
По первым 5 минутам игры предсказать, какая из команд победит: Radiant или Dire?
Набор данных с матчами записан в файле matches.jsonlines.bz2
.
В каталоге dictionaries
приведены расшифровки идентификаторов, которые присутствуют в записях матчей.
import json
import bz2
with bz2.BZ2File('./matches.jsonlines.bz2') as matches_file:
for line in matches_file:
match = json.loads(line)
# Обработка матча
break
{
"match_id": 247, # идентификатор матча
"start_time": 1430514316, # дата/время начала матча, unixtime
"lobby_type": 0, # тип комнаты, в которой собираются игроки
# (расшифровка в dictionaries/lobbies.csv)
# стадия выбора героев
"picks_bans": [
{
"order": 0, # порядковый номер действия
"is_pick": false, # true если команда выбирает героя, false — если банит
"team": 1, # команда, совершающая действие (0 — Radiant, 1 — Dire)
"hero_id": 95 # герой, связанный с действием
# (расшифровка в dictionaries/heroes.csv)
},
...
],
# информация про каждого игрока, список ровно из 10 элементов
# игроки с индексами от 0 до 4 — из команды Radiant, от 5 до 9 — Dire
"players": [
{
# герой игрока (расшифровка в dictionaries/heroes.csv)
"hero_id": 67,
# временные ряды (отсчеты указаны в поле "times")
"xp_t": [0, 13, 115, 177, 335, ...], # опыт
"gold_t": [0, 99, 243, 343, 499, ...], # золото + стоимость всех купленных вещей (net worth)
"lh_t": [0, 0, 2, 2, 2, ...], # количество убитых юнитов (не героев) противника
# список событий: улучшение способностей героя
"ability_upgrades": [
{
"time": 51, # игровое время
"level": 1, # уровень игрока, на котором произошло улучшение
"ability": 5334 # способность, которая была улучшена
# (расшифровка в dictionaries/abilities.csv)
},
...
],
# список событий: убийства
"kills_log": [
{
"time": 831, # игровое время
"player": 7, # индекс игрока, чей герой был убит
# (не заполнено, если был убит не герой)
"unit": "npc_dota_hero_viper" # тип убитого юнита
},
...
],
# список событий: покупка предметов
"purchase_log": [
{
"time": -73, # игровое время
# точка отсчета игрового времени (ноль) начинается через
# несколько минут после фактического начала матча, поэтому
# время некоторых событий может быть отрицательным
"item_id": 44 # купленный предмер (расшифровка в dictionaries/items.csv)
},
...
]
# список событий: выкуп героя из таверны
"buyback_log": [
{"time": 2507},
...
],
# список событий: установка героем "наблюдателей", позволяющих команде
# следить за чатью игрового поля на некотором расстоянии от точки установки
"obs_log": [
{
"time": 1711, # игровое время установки
"xy": [111, 130] # координаты игрового поля
},
...
],
"sen_log": [], # аналогично полю obs_log, другой тип "наблюдателя"
},
...
],
# отсчеты игрового времени, в которые вычисляются значения временных рядов
"times": [0, 60, 120, 180, ...],
# ключевые события игры
"objectives": [
{
"time": 198, # время события
"type": "firstblood", # тип события
"player1": 6, # параметры события, могут содержать
"player2": 1 # индексы игроков (player),
# номер команды (team, 0 — Radiant, 1 — Dire)
},
{
"time": 765,
"type": "tower_kill",
"player": 7,
"team": 1
},
...
]
# итог матча (отсутствует в тестовых матчах)
"finish": {
"duration": 2980, # длительность в секундах
"radiant_win": false, # true, если победила команда Radiant
"tower_status_radiant": 0, # состояние башен у команд к концу игры
"tower_status_dire": 1972, # (см. описание битовой маски)
"barracks_status_dire": 63, # состояние бараков у команд к концу игры
"barracks_status_radiant": 0 # (см. описание битовой маски)
}
}
Состояние башен к концу игры задается целым числом, закодировано в битах:
┌─┬─┬─┬─┬─────────────────────── Not used.
│ │ │ │ │ ┌───────────────────── Ancient Bottom
│ │ │ │ │ │ ┌─────────────────── Ancient Top
│ │ │ │ │ │ │ ┌───────────────── Bottom Tier 3
│ │ │ │ │ │ │ │ ┌─────────────── Bottom Tier 2
│ │ │ │ │ │ │ │ │ ┌───────────── Bottom Tier 1
│ │ │ │ │ │ │ │ │ │ ┌─────────── Middle Tier 3
│ │ │ │ │ │ │ │ │ │ │ ┌───────── Middle Tier 2
│ │ │ │ │ │ │ │ │ │ │ │ ┌─────── Middle Tier 1
│ │ │ │ │ │ │ │ │ │ │ │ │ ┌───── Top Tier 3
│ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─── Top Tier 2
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─ Top Tier 1
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Состояние бараков к концу игры закодировано в битах целого числа:
┌─┬───────────── Not used.
│ │ ┌─────────── Bottom Ranged
│ │ │ ┌───────── Bottom Melee
│ │ │ │ ┌─────── Middle Ranged
│ │ │ │ │ ┌───── Middle Melee
│ │ │ │ │ │ ┌─── Top Ranged
│ │ │ │ │ │ │ ┌─ Top Melee
│ │ │ │ │ │ │ │
0 0 0 0 0 0 0 0
Скрипт extract_features.py производит извлечение признаков из известной информации о матче за первые 5 игровых минут, составляет из них таблицу. Таблица поможет вам быстрее сформировать матрицу объект-признак, вектор ответов и начать применять методы машинного обучения для решения поставленной задачи.
Признаки, представленные в таблице features.csv
, по мнению экспертов в предметной области являются наиболее важными для решения задачи предсказания победы команды. Тем не менее, не обязательно использовать эти признаки в исходном виде для применения методов машинного обучения — вы можете сделать новые признаки из имеющихся. Более того, признаки в файле features.csv
содержат не всю информацию, известную про матч за первые 5 игровых минут. Вы можете использовать скрипт extract_features.py
как пример и добавлять свои признаки для улучшения качества предсказания.
import pandas
features = pandas.read_csv('./features.csv', index_col='match_id')
features.head()
start_time | lobby_type | r1_hero | r1_level | r1_xp | r1_gold | r1_lh | r1_kills | r1_deaths | r1_items | ... | dire_boots_count | dire_ward_observer_count | dire_ward_sentry_count | dire_first_ward_time | duration | radiant_win | tower_status_radiant | tower_status_dire | barracks_status_radiant | barracks_status_dire | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
match_id | |||||||||||||||||||||
0 | 1430198770 | 7 | 11 | 5 | 2098 | 1489 | 20 | 0 | 0 | 7 | ... | 4 | 2 | 2 | -52 | 2874 | 1 | 1796 | 0 | 51 | 0 |
1 | 1430220345 | 0 | 42 | 4 | 1188 | 1033 | 9 | 0 | 1 | 12 | ... | 4 | 3 | 1 | -5 | 2463 | 1 | 1974 | 0 | 63 | 1 |
2 | 1430227081 | 7 | 33 | 4 | 1319 | 1270 | 22 | 0 | 0 | 12 | ... | 4 | 3 | 1 | 13 | 2130 | 0 | 0 | 1830 | 0 | 63 |
3 | 1430263531 | 1 | 29 | 4 | 1779 | 1056 | 14 | 0 | 0 | 5 | ... | 4 | 2 | 0 | 27 | 1459 | 0 | 1920 | 2047 | 50 | 63 |
4 | 1430282290 | 7 | 13 | 4 | 1431 | 1090 | 8 | 1 | 0 | 8 | ... | 3 | 3 | 0 | -16 | 2449 | 0 | 4 | 1974 | 3 | 63 |
5 rows × 108 columns
match_id
: идентификатор матча в наборе данныхstart_time
: время начала матча (unixtime)lobby_type
: тип комнаты, в которой собираются игроки (расшифровка в dictionaries/lobbies.csv
)rN
, Dire — dN
):r1_hero
: герой игрока (расшифровка в dictionaries/heroes.csv)r1_level
: максимальный достигнутый уровень героя (за первые 5 игровых минут)r1_xp
: максимальный полученный опытr1_gold
: достигнутая ценность герояr1_lh
: число убитых юнитовr1_kills
: число убитых игроковr1_deaths
: число смертей герояr1_items
: число купленных предметовfirst_blood_time
: игровое время первой кровиfirst_blood_team
: команда, совершившая первую кровь (0 — Radiant, 1 — Dire)first_blood_player1
: игрок, причастный к событиюfirst_blood_player2
: второй игрок, причастный к событиюradiant_
и dire_
)radiant_bottle_time
: время первого приобретения командой предмета "bottle"radiant_courier_time
: время приобретения предмета "courier"radiant_flying_courier_time
: время приобретения предмета "flying_courier"radiant_tpscroll_count
: число предметов "tpscroll" за первые 5 минутradiant_boots_count
: число предметов "boots"radiant_ward_observer_count
: число предметов "ward_observer"radiant_ward_sentry_count
: число предметов "ward_sentry"radiant_first_ward_time
: время установки командой первого "наблюдателя", т.е. предмета, который позволяет видеть часть игрового поляduration
: длительностьradiant_win
: 1, если победила команда Radiant, 0 — иначеtower_status_radiant
tower_status_dire
barracks_status_radiant
barracks_status_dire
В качестве метрики качества мы будем использовать площадь под ROC-кривой (AUC-ROC). Обратите внимание, что AUC-ROC — это метрика качества для алгоритма, выдающего оценки принадлежности первому классу. Оба алгоритма, которые будут использоваться в проекте — градиентный бустинг, и логистическая регрессия — умеют выдавать такие оценки. Для этого нужно получать предсказания с помощью функции predict_proba. Она возвращает два столбца: первый содержит оценки принадлежности нулевому классу, второй — первому классу. Вам нужны значения из второго столбца:
pred = clf.predict_proba(X_test)[:, 1]
Вам необходимо провести описанные ниже четыре этапа исследования, написать по результатам каждого этапа небольшой отчет (ниже указаны вопросы, ответы на которые должны содержаться в отчете), и предоставить для ревью данный отчет и код, с помощью которого вы выполнили задание.
Обратите внимание: высокое качество работы на кросс-валидации (близкое к 100%) — это в первую очередь повод задуматься о том, правильно ли вы обучаете модель. Возможно, вы заглядываете в будущее или настраиваетесь на неправильном наборе признаков.
Важно: не забывайте, что линейные алгоритмы чувствительны к масштабу признаков! Может пригодиться sklearn.preprocessing.StandartScaler.
# N — количество различных героев в выборке
X_pick = np.zeros((data.shape[0], N))
for i, match_id in enumerate(data.index):
for p in xrange(5):
X_pick[i, data.ix[match_id, 'r%d_hero' % (p+1)]-1] = 1
X_pick[i, data.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1
for i, match_id in enumerate(data.index):
for p in xrange(5):
X_pick[i, data.ix[match_id, 'r%d_hero' % (p+1)]-1] = 1
X_pick[i, data.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1
Для всех матчей из тестового набора предскажите вероятность победы Radiant, запишите предсказания в CSV файл с колонками match_id
(идентификатор матча) и radiant_win
— предсказанная вероятность. Файл с предсказаниями должен выглядеть примерно следующим образом:
match_id,radiant_win
1,0.51997370502
4,0.51997370502
15,0.51997370502
...
Отправьте решение на Kaggle.
Разумеется, можно попробовать еще очень много разных идей, которые помогут вам получить еще более высокий результат на kaggle. Вот лишь несколько возможных вариантов:
Набор данных был сделан на основе выгрузки YASP 3.5 Million Data Dump реплеев матчей Dota 2 с сайта yasp.co. За выгрузку огромное спасибо Albert Cui and Howard Chung and Nicholas Hanson-Holtry. Лицензия на выгрузку: CC BY-SA 4.0.
Оригинальная выгрузка матчей была очищена, в предложенном наборе присутствуют матчи:
Из всего датасета 15% случайных записей были выделены в тестовое множество.
Для того чтобы размотивировать участников соревнования на Kaggle занимать высокие места читерскими методами (например, скачав оригинальный набор данных и подсмотрев ответы на тестовом множестве матчей), мы произвели минимальную обфускацию данных, т.е. немного запутали датасет: