#!/usr/bin/env python # coding: utf-8 # In[49]: from IPython.core.display import HTML HTML(""" """) # # Dota2 contest # # По первым 5 минутам игры предсказать, какая из команд победит: Radiant или Dire? # # https://inclass.kaggle.com/c/dota-2-win-probability-prediction # # ## Предметная область # # Dota 2 — многопользовательская компьютерная игра жанра MOBA. Игроки играют между собой матчи. В каждом матче участвует две команды, 5 человек в каждой. Одна команда играет за светлую сторону (The Radiant), другая — за тёмную (The Dire). Цель каждой команды — уничтожить главное здание базы противника (трон). # # ![](images/dota.jpeg) # ### Как проходит матч # # #### 1. Выбор героев # # Всего в игре чуть более 100 различных героев (персонажей). Герои различаются между собой своими характеристиками и способностями. От комбинации выбранных героев во многом зависит успех команды. # # #### 2. Основная игра # # Игроки могут получать золото и опыт за убийство чужих героев или прочих юнитов. Накопленный опыт влияет на уровень героя, который в свою очередь позволяет улучшать способности. За накопленное золото игроки покупают предметы, которые улучшают характеристики героев или дают им новые способности. # # После смерти герой отправляется в "таверну" и возрождается только по прошествии некоторого времени, таким образом команда на некоторое время теряет игрока, однако игрок может досрочно выкупить героя из таверны за определенную сумму золота. # # #### 3. Конец игры # # Игра заканчивается, когда одна из команд разрушет определенное число "башен" противника и уничтожает трон. # # ## Признаки # # - `match_id`: идентификатор матча в наборе данных # - `start_time`: время начала матча (unixtime) # - `lobby_type`: тип комнаты, в которой собираются игроки (расшифровка в `dictionaries/lobbies.csv`) # # - Наборы признаков для каждого игрока (игроки команды Radiant — префикс `rN`, Dire — `dN`): # - `rN_hero`: герой игрока (расшифровка в dictionaries/heroes.csv) # - `rN_level`: максимальный достигнутый уровень героя (за первые 5 игровых минут) # - `rN_xp`: максимальный полученный опыт # - `rN_gold`: достигнутая ценность героя # - `rN_lh`: число убитых юнитов # - `rN_kills`: число убитых игроков # - `rN_deaths`: число смертей героя # - `rN_items`: число купленных предметов # # - Признаки события "первая кровь" (first blood). Если событие "первая кровь" не успело произойти за первые 5 минут, то признаки принимают пропущенное значение # - `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`: время установки командой первого "наблюдателя", т.е. предмета, который позволяет видеть часть игрового поля # # - Итог матча (данные поля отсутствуют в тестовой выборке, поскольку содержат информацию, выходящую за пределы первых 5 минут матча) # - `duration`: длительность # - `radiant_win`: 1, если победила команда Radiant, 0 — иначе # - Состояние башен и барраков к концу матча (см. описание полей набора данных) # - `tower_status_radiant` # - `tower_status_dire` # - `barracks_status_radiant` # - `barracks_status_dire` # In[21]: # магическая функция, чтобы графики рисовались в ноутбуке, а не в отдельном окне get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib # графики import numpy as np # библиотека для вычислений, там есть все полезные математические функции import matplotlib.pyplot as plt # графики import pandas as pd # утилиты для работы с данными, умеет csv, sql и так далее... pd.set_option('display.max_columns', 500) import seaborn as sns # красивые графики sns.set_style("dark") plt.rcParams['figure.figsize'] = 16, 12 # увеличиваем размер картинок import datetime from sklearn.dummy import DummyClassifier from sklearn.model_selection import cross_val_score, KFold from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier, GradientBoostingClassifier, VotingClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.linear_model import SGDClassifier, Lasso, RidgeClassifier, LogisticRegression from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline, FeatureUnion from sklearn.base import BaseEstimator, TransformerMixin from xgboost import XGBClassifier from sklearn.preprocessing import PolynomialFeatures from scipy import sparse # In[2]: # df - data frame. # Загружаем тренировочную выборку df = pd.read_csv("data/features.csv.zip", compression='zip', index_col='match_id') df.head() # # Исследуем данные # # Для начала посмотрим информацию о датафреме и базовые характеристики признаков # In[12]: df.info() # In[11]: df.describe() # Если внимательно посмотреть, то мы увидим что в данных есть пропуски - NaN значения. # # ### Что делать с пропусками? # - удалить все строки с пропущенными данными # - заполнить пропуски 0 # - заполнить пропуски совсем нехарактерными значениями (большое отрицательное число) # - заполнить пропуск средним, медианой # - заполнить пропуск средним в подгруппе по какому-то другому признаку # # Для начала посмотрим какие признаки имеют пропуски # In[3]: nan_features = [i for i, v in df.count().iteritems() if v < df.shape[0]] nan_features # In[20]: df[nan_features].describe() # Так как у нас есть только первые 5 минут игры, то некоторые характеристки еще не были заполнены. # Напрмимер, время покупки курьера или first blood. # # Интересные детали в данных: # - *_time бывает отрицательным. Это означает, что отсчет времени начала игры немного смещен. Там есть период времени для покупок товарами, распределения внутри команды и так далее. Вполне возмодно отрицательное время не стоит делать положительным. В нем может заключаться некоторый смысл. # - другие признаки категориальные и они распределены от 0 до N. Заполнять нулями на мой взгляд странно. Это может запутать алгоритм. # # Предлагаю время заполнить `max + std` # # Для категориальных признаков завести специальный номер # In[4]: def fill_na(df, nan_features): times = [f for f in nan_features if f.find('time') != -1] categorical = [f for f in nan_features if f.find('time') == -1] df[times] = df[times].fillna(301.0) df[categorical] = df[categorical].fillna(-1) return df fill_na(df, nan_features) [i for i, v in df.count().iteritems() if v < df.shape[0]] # In[5]: def get_X(df): return df.drop(['start_time', 'duration', 'radiant_win', 'tower_status_radiant', 'tower_status_dire','barracks_status_radiant', 'barracks_status_dire'], axis=1) def get_Y(df): return df['radiant_win'] # In[7]: X = get_X(df) y = get_Y(df) # In[74]: support = np.linspace(0, 1, df.shape[0]) plt.plot(support, df['first_blood_time'].sort_values()) # # Глупое предсказание # # ![](images/ololosh.jpg) # # В качестве базового предсказателя часто берут какой-то константный предсказатель, чтобы понять, что хуже него ни в коем случае быть нельзя. # # В нашем случае выборка сбалансированная, поэтому если мы будем просто предсказывать что победит вторая команда, то мы получим точность в 50%. # In[69]: # почти сбалансированные данные, отлично sns.countplot(data=df, x='radiant_win') # In[20]: clf = DummyClassifier(random_state=42) scores = cross_val_score(clf, X, y, scoring='roc_auc') print("ROC_AUC: mean={}, std={}".format(scores.mean(), 2*scores.std())) # # Решаем в Лоб # # Иногда полезно попробовать парочку моделей на чистых признаках без предподготовки. Это хорошая стартовая линия, по ней мы сможем ориентироваться, улучшаем ли мы модель или нет. # # Можно поробовать: # - Градиентный бустинг на деревьях # - Cлучайный лес # - K ближайших соседей # - Линеный классификатор # - Машину опорных векторов # # Для оценки качества предсказания будем пользоваться кросс-валидацией. Это когда данные разбивают случайным образом на наборы, тренируются на них и предсказывают, а потом получается среднее значение качества и погрешность. # # ![](images/xgboost.jpg) # # Метрика качества # # В задачах бинарной классификации можно использовать метрики accuracy, roc_auc, f1, log_loss, precision, recall. # # ## Ошибки 1, 2 рода # # - true-positive, TP — пациент болен раком, диагноз положительный # - false-positive, FP — пациент здоров, диагноз положительный # - true-negative, TN — пациент здоров, диагноз отрицательный # - false-negative, FN — пациент болен раком, диагноз отрицательный # # ![](images/errors_matrix.png) # # - Precision - точность - способность алгоритма «видеть» больных # - Recall - специфичность - способность алгоритма не принимать здоровых за больных # # $$Precision = \frac{TP}{TP+FP}$$ # # $$Recall = \frac{TP}{TP+FN}$$ # # ## F1 # # 1- хорошо, 0 - плохо # $$F1 = 2 * \frac{precision * recall}{precision + recall} = \frac{2TP}{2TP + FP + FN}$$ # # ![](images/f1.png) # # # ## Площадь под ROC кривой # # $$FPR = \frac{FP}{FP+TN}$$ # # $$TPR=\frac{TP}{TP+FN}$$ # # ![](images/roc_auc.png) # In[8]: def examine(clf, X, y, scoring='roc_auc'): cv = KFold(n_splits=5, shuffle=True, random_state=42) start_time = datetime.datetime.now() scores = cross_val_score(clf, X, y, scoring=scoring, cv=cv, n_jobs=4) end_time = datetime.datetime.now() result = (clf.__class__.__name__, scoring, scores.mean(), scores.std()*2, end_time-start_time) return result def print_report(result): print('{0}. {1} {2:.5f} +-{3:.5f}. Time to fit: {4}'.format(result[0], result[1], result[2], result[3], result[4])) # In[87]: classifiers = [ DummyClassifier(random_state=42), Lasso(random_state=42), RidgeClassifier(random_state=42), SGDClassifier(random_state=42), AdaBoostClassifier(random_state=42), GradientBoostingClassifier(n_estimators=30, random_state=42), RandomForestClassifier(n_estimators=30, random_state=42), ] results = [examine(clf, X, y) for clf in classifiers] results = sorted(results, key=lambda el: el[2]) for r in results: print_report(r) # Достаточно ли хорошее качество получилось у нас? Может быть и невозможно предсказать лучше? Но стоит учесть, что мы совсем никак не подготавливали признаки. Мы не показали что там есть какие-то закономерности. Категориальные признаки мы просто рассматривали как числа, что не совсем верно(откуда между категориями может появиться отношение порядка?) и так далее. # # Поэтому далее нужно анализировать признаки. Зафиксируем какой-нибудь алгоритм классификации и будем проверять, как он реагирует на новые признаки. # # Тюним признаки # # ![shaman](http://boldyrev.net/blog/sev2/1877narros_shaman_i_shamanka.jpg) # # К счастью, некоторые модели внутри себя содержат информацию о важности признаков для них. В первую очередь стоит посмотреть на эти данные. # In[9]: def describeImportance(clf, X): indices = np.argsort(clf.feature_importances_)[::-1] for f in range(X.shape[1]): print('%d. feature %d %s (%f)' % (f + 1, indices[f], X.columns[indices[f]], clf.feature_importances_[indices[f]])) def describeCoef(clf, X): coefs = clf.coef_[0] indices = np.argsort(np.abs(coefs))[::-1] for f in range(X.shape[1]): print('%d. feature %d %s (%f)' % (f, indices[f], X.columns[indices[f]], coefs[indices[f]])) # In[136]: clf = RidgeClassifier(normalize=True) clf.fit(X, y) describeCoef(clf, X) # In[137]: clf = RandomForestClassifier(n_estimators=50,random_state=42, n_jobs=4) clf.fit(X, y) describeImportance(clf, X) # Один классификатор говорит что деньги и опыт важен и время покупки и использования каких-то предметов, а другой говорит важны убийства персонажей и их уровни. Такое разное "восприятие" из-за неподготовленности признаков. Лучше доаверять в этом плане деревьям, так как для них без разницы как представленны признаки. # # Детальный анализ предметной области подсказывает нам, что должны быть важны следующие признаки: # - деньги # - ботинки # - курьеры # - first blood # - свитки телепортации! # - опыт # # И нам не важны характеристики каждого героя по отдельности, можно посмотреть в целом на команду. Среднее, сумма золота, предметов, опыта и так далее. То есть то, что алгоритм машинного обучения сам не сможет выявить. Это наше эмперическая креативная идея. # ## Проверяем распределения # # Обычно, алгоритмы машинного обучения работаю лучше если распределения признаков близки к нормальным. Если есть смещения то из лучше выровнять. Если есть прерывистые изменения, то их лучше сгладить. Обычно помогают логарифмы, возведения в степени. # In[164]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_first_ward_time'], ax=ax1) sns.distplot(df['radiant_first_ward_time'], ax=ax2) sns.distplot(df['dire_first_ward_time'] - df['radiant_first_ward_time'], ax=ax3) # In[165]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_bottle_time'], ax=ax1) sns.distplot(df['radiant_bottle_time'], ax=ax2) sns.distplot(df['dire_bottle_time'] - df['radiant_bottle_time'], ax=ax3) # In[214]: # Весьма логично, что чем больше разница времени покупки важного артефакта, тем больше шансов победить df['delta_bottle_time'] = df['dire_bottle_time'] - df['radiant_bottle_time'] h = df.groupby(['delta_bottle_time'])['radiant_win'].sum() / df.groupby(['delta_bottle_time'])['radiant_win'].count() sns.jointplot(h.index, h.values, kind="reg") # In[166]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_tpscroll_count'], ax=ax1) sns.distplot(df['radiant_tpscroll_count'], ax=ax2) sns.distplot(df['dire_tpscroll_count'] - df['radiant_tpscroll_count'], ax=ax3) # In[167]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_courier_time'], ax=ax1) sns.distplot(df['radiant_courier_time'], ax=ax2) sns.distplot(df['dire_courier_time'] - df['radiant_courier_time'], ax=ax3) # In[168]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_flying_courier_time'], ax=ax1) sns.distplot(df['radiant_flying_courier_time'], ax=ax2) sns.distplot(df['dire_flying_courier_time'] - df['radiant_flying_courier_time'], ax=ax3) # In[218]: # Очень сильный признак - наличие ботинок df['delta_flying_courier_time'] = df['dire_flying_courier_time'] - df['radiant_flying_courier_time'] h = df.groupby(['delta_flying_courier_time'])['radiant_win'].sum() / df.groupby(['delta_flying_courier_time'])['radiant_win'].count() sns.jointplot(h.index, h.values, kind="reg") # In[169]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_boots_count'], ax=ax1) sns.distplot(df['radiant_boots_count'], ax=ax2) sns.distplot(df['dire_boots_count'] - df['radiant_boots_count'], ax=ax3) # In[217]: # Очень сильный признак - наличие ботинок df['delta_boots_count'] = df['radiant_boots_count'] - df['dire_boots_count'] h = df.groupby(['delta_boots_count'])['radiant_win'].sum() / df.groupby(['delta_boots_count'])['radiant_win'].count() sns.jointplot(h.index, h.values, kind="reg") # In[171]: fig = plt.figure() ax1 = plt.subplot2grid((3, 1), (0, 0), colspan=2) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=2) ax3 = plt.subplot2grid((3, 1), (2, 0), colspan=2) sns.distplot(df['dire_ward_observer_count'], ax=ax1) sns.distplot(df['radiant_ward_observer_count'], ax=ax2) sns.distplot(df['dire_ward_observer_count'] - df['radiant_ward_observer_count'], ax=ax3) # In[157]: sns.distplot(df['first_blood_time']) # In[223]: # какая команда сделал first blood тоже влияет df.groupby(['first_blood_team'])['radiant_win'].sum() / df.groupby(['first_blood_team'])['radiant_win'].count() # In[10]: """Метод для подготовки данных. Убирает все ненужные признаки, оставляет только полезные и проводит аггрегацию некоторых. Заметим, что у нас теперь нет информации о героях совсем! """ def prepareData(X): X_ = pd.DataFrame(index=X.index) #X_['first_blood_team'] = X['first_blood_team'] # Для этих признаков лучше больше, поэтому разность в прямом направлении dire_gold = X['d5_gold'] + X['d4_gold'] + X['d3_gold'] + X['d2_gold'] + X['d1_gold'] radiant_gold = X['r5_gold'] + X['r4_gold'] + X['r3_gold'] + X['r2_gold'] + X['r1_gold'] X_['gold_delta'] = radiant_gold - dire_gold # дельта золота на команду dire_lh = X['d5_lh'] + X['d4_lh'] + X['d3_lh']+ X['d2_lh'] +X['d1_lh'] radiant_lh = X['r5_lh'] + X['r4_lh'] + X['r3_lh']+ X['r2_lh'] +X['r1_lh'] X_['lh_delta'] = radiant_lh - dire_lh # дельта числа убитых юнитов dire_items = X['d5_items'] + X['d4_items'] + X['d3_items'] + X['d2_items'] + X['d1_items'] radiant_items = X['r5_items'] + X['r4_items'] + X['r3_items'] + X['r2_items'] + X['r1_items'] X_['items_delta'] = radiant_items - dire_items # дельта числа купленных предметов dire_boots_count = X['dire_boots_count'] radiant_boots_count = X['radiant_boots_count'] X_['boots_count_delta'] = radiant_boots_count - dire_boots_count # дельта числа ботинок на команду dire_xp = X['d5_xp'] + X['d4_xp'] + X['d3_xp'] + X['d2_xp'] + X['d1_xp'] radiant_xp = X['r5_xp'] + X['r4_xp'] + X['r3_xp'] + X['r2_xp'] + X['r1_xp'] X_['xp_delta'] = radiant_xp - dire_xp # дельта опыта на команду. Нам не нужны поэтому уровни, так как они зависимы друг от друга, но не совсем линейно dire_kills = X['d5_kills'] + X['d4_kills'] + X['d3_kills'] + X['d2_kills'] + X['d1_kills'] radiant_kills = X['r5_kills'] + X['r4_kills'] + X['r3_kills'] + X['r2_kills'] + X['r1_kills'] X_['kills_delta'] = radiant_kills - dire_kills # дельта килов героев на команду X_['tpscroll_count_delta'] = X['radiant_tpscroll_count'] - X['dire_tpscroll_count'] # дельта числа свитков телепортации X_['ward_observer_count_delta'] = X['radiant_ward_observer_count'] - X['dire_ward_observer_count'] X_['ward_sentry_count_delta'] = X['radiant_ward_sentry_count'] - X['dire_ward_sentry_count'] # Для времени лучше меньше чем больше поэтому разность в другую сторону X_['bottle_time_delta'] = X['dire_bottle_time'] - X['radiant_bottle_time'] X_['flying_courier_time_delta'] = X['dire_flying_courier_time'] - X['radiant_flying_courier_time'] X_['courier_time_delta'] = X['dire_courier_time'] - X['radiant_courier_time'] X_['first_ward_time_delta'] = X['dire_first_ward_time'] - X['radiant_first_ward_time'] return X_ # In[11]: def drawJointPlots(df): X = prepareData(df) y = df['radiant_win'] d = pd.concat([X, y], axis=1) for column in X.columns: h = d.groupby([column])['radiant_win'].sum() / d.groupby([column])['radiant_win'].count() sns.jointplot(h.index, h.values, kind="reg") # In[241]: drawJointPlots(df) # Теперь запустим наш набор классификаторов по этим сгенерированным признакам # In[25]: classifiers = [ DummyClassifier(random_state=42), Lasso(random_state=42), RidgeClassifier(random_state=42), SGDClassifier(random_state=42), AdaBoostClassifier(random_state=42), GradientBoostingClassifier(n_estimators=50, random_state=42), RandomForestClassifier(n_estimators=50, random_state=42), LogisticRegression(), XGBClassifier() ] X = prepareData(df) y = df['radiant_win'] results = [examine(clf, X, y) for clf in classifiers] results = sorted(results, key=lambda el: el[2]) for r in results: print_report(r) # Заметим, что линейные модели до сих пор решают проблему лучше всего. Потому что мы выше создали линейные комбинации прошлых признаков. НО зато ансамблевые модели стали вычисляться **намного быстрее** и качество стало таким же как у линейных моделей. # # Синергия героев # # ![](images/heroes.jpg) # # До сих пор мы с вами рассматривали только числовые характеристики - время, количество чего-то. Но у нас есть очень много информации о героях. Учитывая специфику игры, можно предположить, что в долгосрочной перспективе решающим фактором может стать особая способность героев, их комбинация и так далее. Поэтому стоит каким-то образом внести в признаковое пространство героев. # # Для этого можно составить мешок слов - мешок героев. Т.е. мы добавим матрицу, где в столбцах - герои, а в ячейках - 1 или -1 - т.е. за кого играет герой в данном матче. Или в ячейке может быть то сколько золота, опыта, предметов, киллов у данного персонажа. Это всё стоит проверить на выборке. # In[12]: def wordsBag(X, N=113): # N — количество различных героев в выборке X_pick = np.zeros((X.shape[0], N)) for i, match_id in enumerate(X.index): for p in range(5): X_pick[i, X.ix[match_id, 'r%d_hero' % (p+1)]-1] = 1 X_pick[i, X.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1 return X_pick # In[52]: bag = wordsBag(df, 113) print_report(examine(LogisticRegression(random_state=42), pd.DataFrame(bag, index=df.index), y)) # Только на одном мешке слов, мы получили качество предсказания 0.6!! Это лучше, чем тупой предсказатель и на уровне с первыми решениями в лоб. Кажется это действительно улучшит качество! # In[14]: def wordsBag_Exp(X, N=113): # N — количество различных героев в выборке X_pick = np.zeros((X.shape[0], N)) for i, match_id in enumerate(X.index): for p in range(5): X_pick[i, X.ix[match_id, 'r%d_hero' % (p+1)]-1] = X.ix[match_id, 'r%d_gold' % (p+1)] * X.ix[match_id, 'r%d_xp' % (p+1)] X_pick[i, X.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1 * X.ix[match_id, 'd%d_gold' % (p+1)] * X.ix[match_id, 'd%d_xp' % (p+1)] return X_pick # In[15]: bag_exp = wordsBag_Exp(df, 113) # In[50]: print_report(examine(LogisticRegression(), pd.DataFrame(bag_exp, index=df.index), y)) # In[45]: print_report(examine(GradientBoostingClassifier(n_estimators=50, random_state=42), pd.DataFrame(bag_exp, index=df.index), y)) # In[50]: print_report(examine(XGBClassifier(seed=42), pd.DataFrame(bag_exp, index=df.index), y)) # ROC_AUC 0.74 в линейной модели по мешку взвешенных героев??? Oh RLY? # # ![](images/omg_omg_cat.jpg) # In[16]: def wordsBag_KillsDeath(X, N=113): # N — количество различных героев в выборке X_pick = np.zeros((X.shape[0], N)) for i, match_id in enumerate(X.index): for p in range(5): X_pick[i, X.ix[match_id, 'r%d_hero' % (p+1)]-1] = (X.ix[match_id, 'r%d_kills' % (p+1)] + 1) / (X.ix[match_id, 'r%d_deaths' % (p+1)] + 1) X_pick[i, X.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1 * (X.ix[match_id, 'd%d_kills' % (p+1)] + 1) / (X.ix[match_id, 'd%d_deaths' % (p+1)] + 1) return X_pick # In[291]: bag_kd = wordsBag_KillsDeath(df, 113) print_report(examine(LogisticRegression(), pd.DataFrame(bag_kd, index=df.index), y)) # In[17]: def wordsBag_Lh(X, N=113): # N — количество различных героев в выборке X_pick = np.zeros((X.shape[0], N)) for i, match_id in enumerate(X.index): for p in range(5): X_pick[i, X.ix[match_id, 'r%d_hero' % (p+1)]-1] = (X.ix[match_id, 'r%d_lh' % (p+1)] + 1) X_pick[i, X.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1 * (X.ix[match_id, 'd%d_lh' % (p+1)] + 1) return X_pick # In[39]: bag_lh = wordsBag_Lh(df, 113) print_report(examine(LogisticRegression(), pd.DataFrame(bag_lh, index=df.index), y)) # In[18]: def wordsBag_Items(X, N=113): # N — количество различных героев в выборке X_pick = np.zeros((X.shape[0], N)) for i, match_id in enumerate(X.index): for p in range(5): X_pick[i, X.ix[match_id, 'r%d_hero' % (p+1)]-1] = (X.ix[match_id, 'r%d_items' % (p+1)] + 1) X_pick[i, X.ix[match_id, 'd%d_hero' % (p+1)]-1] = -1 * (X.ix[match_id, 'd%d_items' % (p+1)] + 1) return X_pick # In[293]: bag_items = wordsBag_Items(df, 113) print_report(examine(LogisticRegression(), pd.DataFrame(bag_items, index=df.index), y)) # In[295]: bag_all = np.concatenate([bag_exp, bag_items, bag_kd, bag_lh], axis = 1) print_report(examine(LogisticRegression(), pd.DataFrame(bag_all, index=df.index), y)) # ну это было тупо, так как логистическая регрессия просто выкинула другие коэффициенты # # Настало время ансамблей!! # # Генерируем признаки, предсказываем победителя на разных моделях, потом берем предсказания и на основе них опять делаем предсказания!! # Для этого создадим свой первый Трансформер признаков и Пайплайн! # ![](images/deepers.png) # In[26]: # Просто так сделать сложную модель нельзя. Придётся писать свой класс. class DataTransformer(BaseEstimator, TransformerMixin): def __init__(self, func): self.func = func def fit(self, x, y=None): return self def transform(self, X): return self.func(X) # In[33]: # items_clf = Pipeline([('items_bag', BagTransformer(wordsBag_Items)), ('clf', LogisticRegression(random_state=42))]) exp_clf = Pipeline([('exp_bag', DataTransformer(wordsBag_Exp)), ('clf', LogisticRegression(random_state=42))]) kills_clf = Pipeline([('kills_bag', DataTransformer(wordsBag_KillsDeath)), ('clf', LogisticRegression(random_state=42))]) lh_clf = Pipeline([('lh_bag', DataTransformer(wordsBag_Lh)), ('clf', LogisticRegression(random_state=42))]) clf_heroes = VotingClassifier([ # ('items_clf', items_clf), ('exp_clf', exp_clf), ('kills_clf', kills_clf), ('lh_clf', lh_clf) ], voting='soft') # In[46]: get_ipython().run_line_magic('time', 'print_report(examine(clf_heroes, get_X(df), get_Y(df)))') # Самая лучшая модель отдельно давала 0.74126, ансамбль же дал 0.74452. Прирост 0.0033 можно считать значимым! # In[24]: def poly_experiment(bag): pf = PolynomialFeatures() bag_poly = sparse.csr_matrix(pf.fit_transform(bag)) print_report(examine(LogisticRegression(), bag_poly, y)) # # Слияние моделей # # Теперь объединим модели основанные на характеристиках героев и характеристиках команды в целом. # # Напомню, Лучшее предсказание для команды было 0.71718, для героев 0.74452. Есть надежда что комбинация даст 0.75 # In[47]: # Строим вундервафлю для предсказания по командам clf_lr = Pipeline([ ('prepare_data', DataTransformer(prepareData)), ('clf', LogisticRegression(random_state=42)) ]) clf_xgb = Pipeline([ ('prepare_data', DataTransformer(prepareData)), ('clf', XGBClassifier(n_estimators=100, max_depth=5,seed=42)) ]) clf_team = VotingClassifier([('xgb', clf_xgb), ('lr', clf_lr)], voting='soft') print_report(examine(clf_team, get_X(df), get_Y(df))) # In[37]: clf = VotingClassifier([('heroes', clf_heroes), ('team', clf_team)], voting='soft') print_report(examine(clf, get_X(df), get_Y(df))) # Так, стоп, погодите, у нас ничего не улучшилось. Это как так вообще??? # # ![](images/wtf.png) # In[38]: exp_clf = Pipeline([('exp_bag', DataTransformer(wordsBag_Exp)), ('clf', LogisticRegression(random_state=42))]) clf_lr = Pipeline([('prepare_data', DataTransformer(prepareData)), ('clf', LogisticRegression(random_state=42))]) clf = VotingClassifier([('heroes', exp_clf), ('team', clf_lr)], voting='soft') print_report(examine(clf, get_X(df), get_Y(df))) # In[40]: X = np.concatenate([DataTransformer(wordsBag_Exp).transform(get_X(df)), DataTransformer(prepareData).transform(get_X(df))], axis=1) y = get_Y(df) # In[46]: clf = LogisticRegression(random_state=42, C=0.112) print_report(examine(clf, X, y)) # В общем машинное обучение это еще так странная штука. # # ![](images/daria.jpg) # In[55]: from sklearn.decomposition import TruncatedSVD svd = TruncatedSVD(n_components=2) bag_xy = svd.fit_transform(bag) # In[ ]: # from sklearn.manifold import TSNE # tsne = TSNE() # bag_xy = tsne.fit_transform(bag) # In[64]: df_xy = pd.DataFrame(bag_xy, index=df.index, columns=['x', 'y']) df_xy['target'] = df['radiant_win'] plt.scatter(df_xy[df_xy['target'] == 0]['x'], df_xy[df_xy['target'] == 0]['y'],c = 'r', alpha=0.5) plt.scatter(df_xy[df_xy['target'] == 1]['x'], df_xy[df_xy['target'] == 1]['y'],c = 'b', alpha=0.5)