World of Tanks: від чого ж залежить вінрейт танків?

Сьогодні ми поговоримо про використання Wargaming API, побудуємо багато графіків і проаналізуємо, від чого ж залежить вінрейт танків. Відразу хочу відзначити, що я не гуру World of Tanks, і якщо я десь помилився, то напишіть будь-ласка в коментарях.
image
На гістограмі винрейта по всіх танків видно, що загальний розподіл нормальний, але є хвіст праворуч. Спробуємо розібратися.
В грі World of Tanks багато гравці приділяють велику увагу статистикою свого облікового запису, а саме винрейту (відсоток перемог), особистим рейтингом, WN8 і т. д. Для цих параметрів є формули, які враховують безліч характеристик. В основному на вінрейт гравця впливає середній шкоди за бій, виживання, середній рівень боїв і ще кількох параметрів. Але від чого ж залежить вінрейт окремого танка? Найочевидніший варіант – від гравців, які на ньому більше грають. Але сьогодні я хочу провести аналіз параметрів танків, не включаючи середній шкоди на танку по серверу і подібні характеристики, які ми не можемо побачити з ангара.
І так, якщо взяти окремий бій, то в кожній команді по 15 людей, отже, кожен гравець у середньому впливає на результат бою на 6.66%. Якби в команді було менше гравців, то їх би стало складніше балансить, а також вплив кожного згладжується. Команди формуються матчмейкером на основі ваги кожного танка так, щоб сумарна різниця ваг команд була мінімальна. Вага танка залежить від його рівня боїв і його класу – важкий, середній, легкий, пт або пт-сау. Загальноприйнята думка в грі, що всі результати боїв зводяться до усереднених 49% перемог, стільки ж поразок і 2% нічиїх.
Зрозуміло, що чим більше шкоди гравець буде наносити і чим менше отримувати, тим більше шансів виграти, а значить підвищити свій вінрейт. Це більшою мірою залежить від самого гравця і його досвіду, так як навіть самий крутий танк «не тих руках» не принесе користі команді.
Отримання даних
Щоб отримати дані можна скористатися публічним Wargaming API, який надає досить багато різних відомостей про гравців і техніці. З допомогою GET запиту з полем account_id за адресою https://api.worldoftanks.ru/wot/account/tanks/ можна отримати інформацію про техніку гравця, а саме загальна кількість боїв і перемог на кожному танку в форматі json. Я робив в лоб: в циклі від 0 до 40кк намагався отримати дані по всім account_id. Уривок коду на python:
url_users = 'https://api.worldoftanks.ru/wot/account/tanks/'
# створюється з'єднання Keep-Alive, що зменшує час на запит
session = requests.Session()
def get_users_json(ids):
# можна передавати масив з 100 id
# application_id можна отримати в кабінеті розробника WG
params = {'account_id': ids, 'application_id': 'demo'}
while True:
try:
r = session.get(url_users, params)
r_json = r.json()
except:
# хоч таким способом я не разу і не вперся
# в ліміт за кількістю запитів, але все ж
time.sleep(1)
continue
if r.status_code == 200 and r_json['status'] == 'ok':
return r_json['data']

Звичайно можна було скористатися модулем для багатопоточності або для асинхронності, що безсумнівно б прискорило завантаження. На моєму комп'ютері скрипт працював 2 дні і скачав дані про 26млн користувачів. Так як я їхав на вихідні, то 2 дні завантаження були не критичні.
Далі ми можемо порахувати вінрейт для кожного танка (всього 450), а також отримати докладні характеристики по всій техніці. Характеристики можна отримати запитом на https://api.worldoftanks.ru/wot/encyclopedia/vehicles/, але API не говорить нам, які модулі є типовими для даного танка. У відповіді цього методу є поле «modules_tree», в якому міститься дерево дослідження модулів танка, тому пройшовши по ньому можна вибрати топові модулі. За визначенням — це модуль найбільшого рівня, а якщо таких кілька, то найбільш дорогий для дослідження. Тепер можна зробити запит на https://api.worldoftanks.ru/wot/encyclopedia/vehicleprofile/ передавши id потрібних модулів. У підсумку отримуємо дані 450 танків.
Робота з ознаками
Для аналізу даних я використовував питоновскую бібліотеку pandas. Завантажимо всі дані в pandas.DataFrame, отримали 450 рядків і колонок 40. Список усіх ознак:
image
Всі фічі повинні бути інтуїтивно зрозумілі, крім ap_damage, apcr_damage, he_damge, hc_damage і такі ж з _penetration. Це шкода і бронепробитие різними типами снарядів. API повертає інформацію про знаряддя у вигляді масиву об'єктів, які містять дані про утраті і бронепробитие для конкретного типу снарядів. Їх є 4 типи:
  • ARMOR_PIERCING — бронебійні снаряди
  • ARMOR_PIERCING_CR — підкаліберні снаряди
  • HIGH_EXPLOSIVE — осколково-фугасні снаряди
  • HOLLOW_CHARGE — кумулятивні снаряди
API не говорить який зі снарядів основним, а який купується за золото, що ускладнює аналіз.
Створення та відбір ознак
На основі вихідних даних можна отримати більш інформативні ознаки:
df['power'] = df.engine_power / (df.weight / 1000) # в конях на тонну 
df['max_damage'] = df[['ap_damage', 'apcr_damage', 'he_damage', 'hc_damage']].max(axis=1)
df['max_penetration'] = df[['ap_penetration', 'apcr_penetration', 'he_penetration', 'hc_penetraion']].max(axis=1)
df['dpm'] = df['max_damage'] * df['gun_fire_rate'] # шкоди в хвилину
def get_armor(y):
# якщо є вежа, то беремо середнє значення лобової броні башти і корпусу
# якщо ні, то просто беремо лобову броню корпусу
if y[1]:
return np.mean(y[:2])
else:
return y[0]
df['armor'] = df[['armor_hull_front', 'armor_turrer_sides']].apply(get_armor, axis=1) 

Методом проб і помилок (random forest) я відібрав найбільш значущі ознаки (але далі ми також розглянемо ще два цікавих ознаки):
image
Для тих, хто не грав в WOT, тут відображені: рівень танка (від 1 до 10), преміумний танк чи ні, кількість очок міцності, потужність (коней/тонну), скорострільність(пострілів/хвилину), швидкість відомості знаряддя©, розкид знаряддя(метри), швидкість(км/год), максимальний збиток(хп), максимальне бронепробитие(мм), втрата у хвилину(хп/хв), броня(мм).
Нормалізація ознак
Перед тим як приступити до аналізу потрібно нормалізувати деякі фічі. Ми хочемо отримати значення, що не залежать від рівня танка, тому для кожного рівня будемо нормалізувалася окремо. Іншими словами, зробимо так, щоб середнє значення ознак за рівнем було одно 0. Таку нормалізацію я провів для максимального шкоди, максимального бронепробития, утрати в хвилину, броні, міцності та потужності.
Аналіз даних
Залежить вінрейт від нації техніки? Логічно припустити, що ні, так як розробники намагаються максимально збалансувати це. Давайте побудуємо графік. Для побудови графіків я використовував бібліотеку seaborn:
sns.factorplot('nation','winrate', data=df_normalized,size=4,aspect=3)
sns.plt.title('Winrate from nation')

image
В очі відразу кидаються чеські танки – середнє значення винрейта 51%, але і найбільший розкид. Це пояснюється тим, що гілка відносно нова і багато гравців, які вже викачали все, що тільки можна, кинулися викачувати і цю гілку. Зрозуміло, що такі гравці досить скиловые, тому і відсоток перемог вище середнього. Також ще не всі, хто грають проти чехів, знають їх слабкі місця і зони пробиття. Але з часом значення винрейта швидше за все вирівняється (а якщо ні, то WG понерфит багато танки в гілці).
А як йдуть справи з класом техніки, який клас «нагинає»? Побудуємо схожий графік:
ax = sns.factorplot('type','winrate', data=df_normalized,size=5,aspect=3)
sns.plt.title('Winrate from type')

image
Видно, що найбільший шанс перемогти на середньому танку, а найменший на легких танках і артилерії. Те, що на легких танках таке значення — зрозуміло. Багато гравців на цьому класі, несуться стрімголов вперед, відразу після початку бою, і природно зливаються, не завдаючи особливої користі команді. Арта це взагалі окрема тема, яку не будемо тут обговорювати.
Далі ми не будемо говорити про цих двох ознаках, так як вони не вносять особливої користі в модель на основі random forest.
Подивимося на кореляцію вибраних раннє ознак і відсотка перемог:
image
Виділяється сильна кореляція is_premium з winrate. Невже преміумні танки набагато краще звичайних? Не зовсім так. Така сильна залежність швидше за все пояснюється тим, що на преміумного техніці грають досвідчені гравці, щоб фарм срібло, так як у багатьох танків, купуються за золото, пільговий рівень боїв, більше срібла за бій, можливість швидкої прокачування екіпажу. Можна побудувати графік і подивитися, як розподілений вінрейт на преміумного і звичайній техніці:
facet = sns.FacetGrid(df_normalized, hue="is_premium",aspect=4)
facet.map(sns.kdeplot,'winrate',shade= True)
facet.set(xlim=(0.40, df_normalized['winrate'].max()))
facet.add_legend()
sns.plt.title('Winrate from premium')

image
Видно, що щільність розподілу перемог на звичайній техніці — це Гаусовское нормальний розподіл із середнім значенням 49%. Щільність розподілу перемог на преміумного техніці витягнута в бік більшого винрейта, середнє значення 52%, а дисперсія набагато більше ніж у звичайної техніки.
У грі всього 114 преміумних танка, а це 25% від загальної кількості. На гістограмі всіх танків за відсотком перемог ми бачили хвіст праворуч. Давайте подивимося, які танки потрапили в нього:
image
Виходить 93% танків з хвоста — преміумні. Що цікаво решта 7% (2 з 31) це чеські танки.
Також з таблиці кореляції видно, що вінрейт прямо пропорційний рівню танка. Розглянемо детальніше на графіку:
image
Легко пояснити таку картину. На перших двох рівнях техніки такий маленький вінрейт з-за того, що всі початківці гравці псують статистику танків через відсутність досвіду. Також на перших рівнях більше шансів опинитися внизу списку. На 10 рівні навпаки, ти завжди в топі. Також на рівні 9-10 грати без преміум аккаунта збитково, тому більшість людей там, грають з преміумом.
З решти ознак прямо пропорційні винрейту: міцність, швидкість вперед, шкоди в хвилину і броня. Обернено пропорційні: швидкість відомості, розкид знаряддя. Поки все очевидно, але далі видно, що максимальний збиток і бронепробитие обернено пропорційні відсотком перемог.
Це дивно, адже чим більше танк завдає шкоди, тим краще. Так і є. Якщо ще раз поглянути на те, як я отримував значення для максимального шкоди, можна здогадатися, у чому підступ. Я просто брав максимальні значення утрати і бронепробития з усіх можливих снарядів для топового знаряддя. Але ж найчастіше найбільший шкоди у фугасів (при найменшому бронепробитие), а фугаси далеко не самі часто використовувані снаряди у звичайних танків, отримуємо неточність. Також разовий шкоди може бути великою, а втрата у хвилину маленький із-за довгої перезарядки. Більш того, від'ємне значення кореляції можливо пов'язано з тим, що в артилерії зазвичай найбільший шкоди і найменший вінрейт – звідси і зворотна пропорційність.
Важливість ознак
Тепер можна побудувати random forest на цих даних і подивитися на результат. Random forest це один з найпоширеніших алгоритмів машинного навчання, заснований на усередненні результатів безлічі різних дерев рішень. Цей алгоритм добре підходить для того щоб дізнатися важливість окремих ознак:
image
Я пробував різні параметри і ознаки, але сильно зменшити помилку мені не вдалося. Видно, що алгоритм в середньому помиляється в прогнозах на 1.3% відсотка. А тепер подивимося на важливість ознак для цього лісу:
importances = rf.feature_importances_
std = np.std([tree.feature_importances_ for tree in rf.estimators_], axis=0)
indices = np.argsort(importances)[::-1]
legends = []
for i in range(X. shape[1]):
legends.append('%d.%s (%f)' % (i + 1, X. columns[indices[i]], importances[indices[i]]))
plt.title('Feature importances')
bars = plt.bar(range(X. shape[1]), importances[indices], color='c', yerr=std[indices], align='center')
plt.xticks(range(X. shape[1]), range(1, X. shape[1] + 1))
plt.xlim([-1, X. shape[1]])
plt.legend(bars, legends, fontsize=12)

image
Вийшло, що для цієї моделі найбільш важливим параметром виявилося – преміумний танк чи ні, важливість цієї ознаки в два рази більше ніж наступного за спаданням за ним. Наступні чотири по важливості ознаки – це характеристики знаряддя, що теж передбачувано. Можна помітити, що я також додав фічу приналежності до чеської нації, так як це трохи зменшило помилку. А ось додавання всіх інших фичей з націями та класами техніки не поліпшувало роботу алгоритму.
Що буде, якщо ми приберемо з вибірки преміумні танки і навчимо random forest з такими ж параметрами? Результати зручно подати на boxplot:
fig, ax1 = plt.subplots(figsize=(10, 6))
data = [score_with_premium, score_without_premim]
bp = plt.boxplot(data, notch=0, sym='+', vert=1, whis=1.5)
ax1.set_title('Comparison of score with and without premium')
ax1.set_ylabel('mean_absolute_error')
xtickNames = plt.setp(ax1, xticklabels=['With premium', 'Without premium'])
plt.setp(xtickNames, rotation=0, fontsize=12)

image
Алгоритмом відразу стало набагато легше вгадувати відсоток перемог і в середньому помилка на крос валідації зменшилася до 0.9%, розкид помилки також став значно менше.
Висновок
Ми подивилися, як працювати з WG API. Дізналися, як вінрейт залежить від нації — на даний момент на чехах він самий нестабільний, від класу техніки — на середніх танках самий великий, а на арте найменший. Також побачили прямолінійну залежність від рівня. Проаналізували, які ознаки у танка найсильніше впливають на перемогу в бою — преміумний танк чи ні, а також параметри знаряддя. Ще ми побудували просту модель, яка відносно точно за характеристиками танка може передбачити його відсоток перемог.
P. S.: Якщо ви теж хочете попрацювати з цим датасетом, але не хочете завантажувати дані через API то пишіть мені.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.