Даниэль Ко

Общий прогноз золота в League of Legends с использованием статистики чемпионов

Изучение League of Legends с помощью машинного обучения.

Обзор

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

Сбор данных

Мы собирали данные с помощью API riot games. Получив последние 100 рейтинговых матчей от игроков с высоким рейтингом на сервере Северной Америки, мы смогли получить список из 857 уникальных матчей. Оттуда мы обработали наши данные, чтобы каждые 10 строк представляли кадр. API предоставляет один кадр каждую минуту. Таким образом, для 27-минутного матча у нас будет 270 строк в наших данных, так как нам нужна одна строка для каждого из 10 игроков в каждом кадре.

Помимо статистики, нам также предоставили координаты x и y. Используя это, мы добавили функцию расстояния от каждого игрока до других. Хотя мы не собираемся использовать это сегодня, это может пригодиться позже.

Подготовить данные

Мы решили исключить каждый столбец, который напрямую не связан со статистикой чемпионов, чтобы получить наш X, и мы установили наш y как наше общее золото. Мы масштабировали наш X с помощью MinMaxScaler sklearn, а затем разделили на обучающие и тестовые наборы, используя train_test_split от sklearn. Масштабирование предназначено для глубокого обучения, которое мы рассмотрим позже.

Забавный факт: если учесть все, то хр — лучший показатель общего количества золота (чрезвычайно сильная положительная корреляция). имеет смысл для меня!

import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split

frame_data = pd.read_csv('./data.csv')
frame_data.columns

exclude_cols = [f'distance_to_{i+1}' for i in range(10)] + ['x', 'y', 'totalGold', 'xp', 
                                                            'totalDamageDoneToChampions', 'goldPerSecond', 
                                                            'jungleMinionsKilled', 'currentGold', 'level', 
                                                            'minionsKilled', 'participantId', 
                                                            'timeEnemySpentControlled', 'magicDamageDone',
                                                            'magicDamageDoneToChampions', 'magicDamageTaken', 'physicalDamageDone',
                                                            'physicalDamageDoneToChampions', 'physicalDamageTaken',
                                                            'totalDamageDone', 'totalDamageTaken', 'trueDamageDone',
                                                            'trueDamageDoneToChampions', 'trueDamageTaken']
X = frame_data.drop(exclude_cols, axis=1)

scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
y = frame_data.totalGold

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state = 42)

Вот как выглядит X.columns.

Index(['abilityHaste', 'abilityPower', 'armor', 'armorPen', 'armorPenPercent',
       'attackDamage', 'attackSpeed', 'bonusArmorPenPercent',
       'bonusMagicPenPercent', 'ccReduction', 'cooldownReduction', 'health',
       'healthMax', 'healthRegen', 'lifesteal', 'magicPen', 'magicPenPercent',
       'magicResist', 'movementSpeed', 'omnivamp', 'physicalVamp', 'power',
       'powerMax', 'powerRegen', 'spellVamp'],
      dtype='object')

Обучить модель XGBoost

Сначала мы рассмотрим XGBoostRegressor с параметрами по умолчанию. Мы будем использовать раунды ранней остановки, чтобы предотвратить переоснащение.

from xgboost import XGBRegressor
from sklearn.metrics import mean_absolute_error

# default n_estimators=100, learning_rate=0.1
model = XGBRegressor(n_jobs=-1, random_state=42)

model.fit(X_train, y_train,
          early_stopping_rounds=10, 
          eval_set=[(X_test, y_test)],
          verbose=False)

xgb_preds = model.predict(X_test)

score = mean_absolute_error(y_test, xgb_preds)
print('MAE:', score)

МАЭ: 426.09102505061475

Мы получаем среднюю абсолютную ошибку ~426 золотых. Неплохо, учитывая, что один предмет может стоить около 3000 золотых. Но можем ли мы сделать его лучше?

Настройка гиперпараметров с поиском по сетке

from sklearn.model_selection import GridSearchCV

# param_grid = {
#     'n_estimators': [100, 300, 500],
#     'learning_rate': [0.01, 0.1, 0.3],
#     'max_depth': [3, 6, 9]
# }
param_grid = {
    'n_estimators': [100, 500],
    'learning_rate': [0.1, 0.3]
}

xgb = XGBRegressor(random_state=42)

grid = GridSearchCV(estimator=xgb, param_grid=param_grid, scoring='neg_mean_absolute_error', n_jobs=-1, cv=3, verbose=3)
grid.fit(X_train, y_train)

grid.best_params_

Используя GridSearchCV sklearn и несколько параметров, мы определили, что {‘learning_rate’: 0,3, ‘n_estimators’: 500} были лучшими параметрами. Довольно простой способ повысить производительность.

К сожалению, Google Colab не позволил мне использовать большую сетку параметров :(

Прогноз с лучшими параметрами

best_estimator = grid.best_estimator_
best_xgb_preds = best_estimator.predict(X_test)

score = mean_absolute_error(y_test, best_xgb_preds)
print('MAE:', score)

МАЭ: 373,98448720432464

Улучшение примерно на ~52 золота. Не огромное улучшение, но все же улучшение.

Важность функции модели XGBoost

import matplotlib.pyplot as plt
import numpy as np

importances = best_estimator.feature_importances_
feature_names = X.columns

indices = np.argsort(importances)[::-1]

plt.figure(figsize=(10, 6))
plt.bar(range(X_train.shape[1]), importances[indices])
plt.xticks(range(X_train.shape[1]), feature_names[indices], rotation=90)
plt.xlabel('Features')
plt.ylabel('Importance')
plt.title('Feature Importances')
plt.show()

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

Важность HealthMax может быть связана с его корреляцией со временем. Чемпионы получают здоровье по мере повышения уровня на протяжении всей игры. В отличие от Урона атаки или Силы способностей, где игрок обычно выбирает одно или другое, все чемпионы получают здоровье или могут получать пользу от здоровья. Несмотря на то, что со временем масштабируется приличное количество других характеристик, он по-прежнему зависел от HealthMax, даже когда мы масштабировали наши данные. Это может говорить нам о том, что чемпионы всегда выигрывают от здоровья, а не от других характеристик.

Однако самое интересное для меня наблюдение — это magicPenPercent. Причина его важности может заключаться в том, что этот показатель является ситуативным. Игроки могут создать статистику для противодействия магическому сопротивлению, и это может дать хорошие результаты, что приведет к большему количеству золота. Хотя нам не нужна модель, чтобы понять, что magicPenPercent лучше, чем просто magicPen, приятно наблюдать за этим самим. Эта важность статистики также отражена в вики League of Legends, которая дает значение золота 54,33 за очко magicPenPercent и значение золота 31,11 за очко magicPen.

Вывод:healthMax, AttackDamage, AbilityPower и magicPenPercent играют важную роль в определении общего золота, отражая важность статистики урона, масштабирования здоровья чемпиона и ситуационной статистики в League of Legends.

Обучите модель глубокого обучения

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping

early_stopping = EarlyStopping(
    min_delta=0.01,
    patience=5,
    restore_best_weights=True,
)

model = keras.Sequential([
    layers.Dense(512, activation='relu', input_shape=[len(list(X.columns))]),
    layers.Dropout(0.3),
    layers.BatchNormalization(),
    layers.Dense(512, activation='relu'),
    layers.Dropout(0.3),
    layers.BatchNormalization(),
    layers.Dense(512, activation='relu'),
    layers.Dropout(0.3),
    layers.BatchNormalization(),
    layers.Dense(1),
])

model.compile(
    optimizer='adam',
    loss='mae',
)

history = model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),
    batch_size=256,
    epochs=100,
    callbacks=[early_stopping],
)

Мы используем 3 плотных слоя по 512 единиц, используя ReLU, и применяем к каждому из них отсев и пакетную нормализацию. Выходной слой представляет собой одну единицу, поскольку это проблема регрессии. Мы используем как выпадающие слои, так и раннюю остановку, чтобы предотвратить переобучение.

Прогнозирование с помощью модели глубокого обучения

history_df = pd.DataFrame(history.history)
history_df.loc[0:, ['loss', 'val_loss']].plot()
print(("Minimum Validation Loss: {:0.4f}").format(history_df['val_loss'].min()))

Минимальная потеря проверки: 462,2293

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

Сравнение XGBoost и глубокого обучения

Хотя я не могу судить о производительности из-за ограниченности данных и тестирования, становится совершенно ясно, в чем заключается главный недостаток глубокого обучения в подобных сценариях. Глубокое обучение не позволяет нам анализировать важность функции, поэтому мы не можем объяснить, как она пришла к ее предсказанию. Когда мы хотим понять, какая именно статистика определяет общее количество золота, XGBoost предпочтительнее.

Обновление: больше данных, проверка результатов

Хотя раньше мы могли получать результаты, у меня были опасения, что игр 857, которые мы использовали, было недостаточно, и что мы переобучили наши данные. Чтобы проверить наши результаты, я пошел и собрал данные из примерно 8792 матчей между 160 игроками с высоким рейтингом на сервере Северной Америки. Я использовал XGBRegressor с learning_rate=0,3 и n_estimators=500 в новом наборе данных, предварительно обработанном теми же методами, что и раньше.

МАЭ: 383.0562020865684

Очень минимальные изменения!

Заключительные примечания

Что касается результатов

У меня есть опасения, что игр 857, которые я использовал, было недостаточно, и что я переобучил свои данные. Если я вернусь к этому, я могу вместо этого рассмотреть возможность использования ~ 10 000 игр. Изменить: добавлены результаты использования большего набора данных.

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

Позже я также осознал опасность утечки данных из-за их случайного перемешивания. Я повторил попытку с shuffle=False в тестовом разделении поездов и получил немного худшие результаты ~ 20–30, но все остальное было почти таким же.

Возможные применения

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

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

Для будущего

Идентификаторы участников указаны в наборе данных. идентификаторы участников 1 и 6, 2 и 7, 3 и 8, 4 и 9, а также 5 и 10 соответствуют верхней полосе, джунглям, средней полосе, нижней полосе и поддержке соответственно. Запуск модели для каждой роли может дать нам более глубокое понимание статистики для каждой роли.

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

Читателям

Хотя у меня есть текущий опыт работы в UIUC по специальности «Математика и информатика», мне еще предстоит пройти какие-либо курсы по машинному обучению (я посещал только соответствующие математические курсы). Большая часть того, что я до сих пор узнал о машинном обучении, была изучена мной самостоятельно из различных источников, поэтому, если в моей работе есть какие-либо серьезные недостатки или неточности, сообщите мне, чтобы я мог вернуться к ней. Все что угодно помогает!

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

Спасибо, что прочитали!

kaggle dataset, github repo, личный сайт