В этой статье я буду говорить о данных нашего соревнования Кубка мира по крикету. Чтобы принять участие в соревновании и загрузить набор данных, перейдите на страницу quant-quest.aquan.com/competitions/cricket-qq1.

Обзор

Введение
Наука о данных и спорт становятся все более и более интегрированными, и многое изменилось после знаменитого сезона Билли Бина в 2002 году в бейсбольной команде "Окленд А" (о чем свидетельствует фильм "Moneyball"). Люди используют науку о данных для прогнозирования результатов игр, разработки индивидуальных планов тренировок, оценки потенциала игроков и многого другого. Сегодня мы рассмотрим только один аспект этого: предсказание того, будет ли мяч калиткой в ​​матче по крикету.

Как отмечалось выше, мы собираемся использовать набор данных, щедро предоставленный Mustard Systems, который содержит информацию о 4000 недавних матчах ODI и крикетных матчах высокого уровня 50/50. Было зарегистрировано почти 30 факторов, что дает в общей сложности почти 1 миллион точек данных.

Не стесняйтесь загружать набор данных и следовать!

План атаки

  • Нулевой шаг: нам нужно загрузить, открыть и просмотреть некоторую описательную информацию о данных. Я скопировал заголовки и типы столбцов ниже:
## Code for getting Data descriptions
print(df.shape)
print(df.columns)
print(df.dtypes)

  • Шаг первый: Как видите, у нас есть несколько переменных, которые содержат категориальные данные в виде строк. Нам нужно преобразовать эти данные в более удобный формат.
  • Шаг второй: Далее мы займемся разработкой некоторых функций, чтобы создать переменные, представляющие текущее состояние игры. Это может включать в себя такие вещи, как импульс, форма, пробеги в последнем овере, калитки в последнем овере и т. д. Они должны быть более предсказуемыми, чем исходные факторы.
  • Шаг третий: Используйте эти новые функции и постройте модель, чтобы предсказать, будет ли мяч калиткой или нет. (Сделаем пару и посмотрим, какая лучше).
  • Шаг четвертый: повторите порог вероятности, чтобы определить прогнозируемый класс. (Мы вернемся к этому, но в основном, поскольку около 95% мячей не имеют калитки, мы можем создать модель с точностью 95%, всегда угадывая отсутствие калитки).
  • Шаг пятый: насладитесь славой нашего творения.

Выполнение

Шаг первый: подготовка данных
Чтобы облегчить себе жизнь при построении модели, мы хотим, чтобы все наши данные были числовыми. Это создает для нас проблему, так как многие наши данные представлены в виде строк.

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

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

Listing non-numerical variabels:
listf = []
for c in data.columns:
    if data[c].dtype==object:
        print(c, data[c].dtype)
        listf.append(c)
Converting unique values to integer:
from sklearn import preprocessing
feature_dict ={}
for feature in data.columns:
 if data[feature].dtype==object: 
 le = preprocessing.LabelEncoder()
 fs = data[feature].unique()
 f_dict = {}
 le.fit(fs)
 data[feature] = le.transform(data[feature])
 feature_dict[feature] = le

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

Clean data by removing NaN values:
data[data.isnull().any(axis=1)]
data = data.dropna()
del data['date']
Create target variable (y):
pd.get_dummies(s1)
y = data['Out']
del data['Out']

Шаг второй: создание новых переменных
Представьте, что вы вместе со мной смотрите матч по крикету, и мы решаем сделать ставку на то, сколько ранов будет забито в следующем раунде. Какую информацию мы будем использовать, чтобы принять это решение? Будем ли мы взвешивать все это одинаково?

Мы хотели бы учитывать такие факторы, как: кто играет, кто отбивает/боулинг, где они играют, какое поле и т. д. Это та информация, которую мы имеем в нашей таблице.

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

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

Create variables that measure the number of runs and wickets in the last 6/12 balls:
data['run_last_6_balls'] = data['innings_runs_before_ball'].rolling(6).sum()
data['run_last_12_balls'] = data['innings_runs_before_ball'].rolling(12).sum()
data['wkt_last_6_balls'] = data['PreviousBallOut'].rolling(6).sum()
data['wkt_last_12_balls'] = data['PreviousBallOut'].rolling(12).sum()
Remember to set these to 0 at the start of the game:
data['run_last_6_balls'][data['innings_ball_number']<6] = 0
data['run_last_12_balls'][data['innings_ball_number']<12] = data['run_last_6_balls']
data['wkt_last_6_balls'][data['innings_ball_number']<6] = 0
data['wkt_last_12_balls'][data['innings_ball_number']<12] = data['wkt_last_6_balls']
data.fillna(0, inplace=True)

Шаг 3. Создание моделей
Пришло время поколдовать! Почти. На самом деле сначала нам нужно разделить данные на обучающие и тестовые данные, чтобы мы могли оценить наши модели. Давайте быстро сделаем это сейчас.

Creating traning and test data sets
Note: We're going to use stratified sampling here to make sure we maintain representative amounts of each class in each set
from sklearn.metrics import confusion_matrix, log_loss, make_scorer
from sklearn.metrics import roc_curve, precision_recall_curve, auc, recall_score, accuracy_score, precision_score
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
X_train, X_test, y_train, y_test = train_test_split(data, y, stratify=y, random_state = 10)
Show the distributions for each group
print('y_train class distribution')
print(y_train.value_counts(normalize=True))
print('y_test class distribution')
print(y_test.value_counts(normalize=True))

Теперь мы собираемся создать несколько моделей. Прежде чем заняться этой проблемой, я провел небольшое исследование и обнаружил, что одни классификаторы работают лучше, чем другие. Я выбрал четыре примера, но вы можете попробовать свои собственные. Те, которые мы собираемся рассмотреть: классификатор дерева решений, классификатор KNN, наивный байесовский классификатор и классификатор случайного леса.

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

Мы будем использовать эти значения позже для расчета показателей производительности для нашей модели. Это может включать: процент истинных/ложноположительных результатов, показатель истинных/ложноотрицательных результатов, чувствительность, специфичность, Cohens Kappa и т. д. Нас особенно интересуют чувствительность и специфичность, которые мы будем использовать для создания ROC-кривой и выполнения анализа AUC. (увидим позже).

Классификатор дерева решений
Классификаторы дерева решений — это модели, которые пытаются прогнозировать и получать результаты путем построения дерева решений. Напоминаем, что наблюдения за данными создают ветви, а результаты формируют листья дерева. Затем модель будет использовать это, чтобы попытаться предсказать калитки.

First Train a DescisionTreeClassifier:
from sklearn.tree import DecisionTreeClassifier
dtree_model = DecisionTreeClassifier(max_depth = 10).fit(X_train, y_train) 
dtree_predictions = dtree_model.predict(X_test)
Calculate accuracy on X_test
accuracy = dtree_model.score(X_test, y_test) 
print(accuracy)
lg = log_loss(y_test, dtree_predictions)
print(lg)
Creating a confusion matrix
cm = confusion_matrix(y_test, dtree_predictions)
cm

Классификатор KNN
KNN обозначает K ближайших соседей и представляет собой модель, которая берет мяч, ищет и ищет наиболее похожие на него другие мячи и предсказывает результат на основе результатов этих подобные мячи. Вы можете поиграть со значением K, чтобы изменить степень подгонки.

Training a KNN classifier
from sklearn.neighbors import KNeighborsClassifier
# neigh = RadiusNeighborsClassifier(radius=1.0).fit(X_train, y_train)
knn = KNeighborsClassifier(n_neighbors = 10, weights='distance').fit(X_train, y_train) 
knn_predictions = knn.predict(X_test)
Accuracy on X_test
accuracy = knn.score(X_test, y_test) 
# accuracy = neigh.score(X_test, y_test) 
print(accuracy)  
lg = log_loss(y_test, knn_predictions)
print(lg)
Creating a confusion matrix
cm = confusion_matrix(y_test, knn_predictions)
# neigh_predictions = neigh.predict(X_test)  
# cm = confusion_matrix(y_test, neigh_predictions)
cm

Наивные байесовские модели
Наивные байесовские модели используют байесовскую статистику для создания модели. Наивный относится к предположениям, которые он делает о взаимосвязи между переменными (т. Е. Независимости).

Training a Naive Bayes classifier
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB().fit(X_train, y_train) 
gnb_predictions = gnb.predict(X_test)
Accuracy on X_test
accuracy = gnb.score(X_test, y_test) 
print(accuracy)
lg = log_loss(y_test, gnb_predictions)
print(lg)
Creating a confusion matrix
cm = confusion_matrix(y_test, gnb_predictions) 
cm

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

Training a Random Forest classifier
from sklearn.ensemble import RandomForestClassifier
rforest_model = RandomForestClassifier(n_estimators=300, max_depth=8,random_state=0).fit(X_train, y_train) 
rforest_predictions = rforest_model.predict(X_test)
Accuracy on X_test
accuracy = rforest_model.score(X_test, y_test) 
print(accuracy)
lg = log_loss(y_test, rforest_predictions)
print(lg)
Creating a confusion matrix
cm = confusion_matrix(y_test, rforest_predictions)cm

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

evaluate_clf = rforest_model
for i in range(len(X_test.columns)):
    print(X_test.columns[i], evaluate_clf.feature_importances_[i])
z = evaluate_clf.predict_proba(X_test)
for i in range(len(y_test)):
    if y_test.iloc[i]>0:
        print(y_test.iloc[i])
        print(z[i])

Мы можем повторять этот процесс, чтобы опробовать новые функции, пока не будем довольны тем, что созданные нами эффективны. Помните, важно убедиться, что ваши функции имеют смысл с практической точки зрения! Не просто случайным образом комбинируйте и манипулируйте функциями.

Шаг четвертый. Определение порога вероятности

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

y_scores = evaluate_clf.predict_proba(X_test)[:, 1]
p, r, thresholds = precision_recall_curve(y_test, y_scores)

Для этого мы вычислим пары точность-отзыв для разных порогов вероятности.

  • Точность = доля истинных положительных результатов: истинные положительные результаты / все положительные прогнозы (истинные положительные результаты + ложные положительные результаты)
  • Отзыв = истинные положительные результаты / все положительные результаты (истинные положительные + ложноотрицательные)

Их может быть интуитивно трудно разделить, но вы можете думать об этом так:

  • Точность — это мера того, сколько раз предсказание модели будет правильным. т. е. какой процент предсказанных калиток мы на самом деле калитки
  • Отзыв — это мера того, сколько раз модель успешно предсказала фактический результат. т. е. какой процент фактических калиток мы предсказывали так
import matplotlib.pyplot as plt
plt.style.use("ggplot")
Adjust class predictions based on threshold:
def adjusted_classes(y_scores, t):
    return [1 if y >= t else 0 for y in y_scores]
Create precision recall function:
def precision_recall_threshold(p, r, thresholds, t=0.5):
Generate new class predictions based on the adjusted_classes function above and view the resulting confusion matrix:
 y_pred_adj = adjusted_classes(y_scores, t)
    print(pd.DataFrame(confusion_matrix(y_test, y_pred_adj),
                       columns=['pred_neg', 'pred_pos'], 
                       index=['neg', 'pos']))
    
Plot Curve of precision and recall scores for different thresholds:
    plt.figure(figsize=(8,8))
    plt.title("Precision and Recall curve ^ = current threshold")
    plt.step(r, p)#, color='b', alpha=0.2,
#              where='post')
    plt.fill_between(r, p)#, step='post', alpha=0.2,
#                      color='b')
    plt.ylim([0.5, 1.01]);
    plt.xlim([0.5, 1.01]);
    plt.xlabel('Recall');
    plt.ylabel('Precision');
Plot current threshold on line:
    close_default_clf = np.argmin(np.abs(thresholds - t))
    plt.plot(r[close_default_clf], p[close_default_clf], '^', c='k',
            markersize=15)
    return y_pred_adj

Вот функции графика, которые мы собираемся использовать для визуализации нашего

Precision Recall Curve
Modified from Hands-On Machine learning with Scikit-Learn and TensorFlow; p.89
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
  
    plt.figure(figsize=(8, 8))
    plt.title("Precision and Recall Scores as a function of the decision threshold")
    plt.plot(thresholds, precisions[:-1], "b--", label="Precision")
    plt.plot(thresholds, recalls[:-1], "g-", label="Recall")
    plt.ylabel("Score")
    plt.xlabel("Decision Threshold")
    plt.legend(loc='best')
ROC curve:
Modified from Hands-On Machine learning with Scikit-Learn and TensorFlow; p.91
def plot_roc_curve(fpr, tpr, label=None):
   
    plt.figure(figsize=(8,8))
    plt.title('ROC Curve')
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1], [0, 1], 'k--')
    plt.axis([-0.005, 1, 0, 1.005])
    plt.xticks(np.arange(0,1, 0.05), rotation=90)
    plt.xlabel("False Positive Rate")
    plt.ylabel("True Positive Rate (Recall)")
    plt.legend(loc='best')

Теперь вы можете настроить порог модели, чтобы оптимизировать компромисс точности отзыва. Один из способов сделать это — максимизировать AUC (площадь под кривой) ROC-графика.

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

На странице конкурса также есть ссылки на некоторые более продвинутые методы, но вы можете провести собственное исследование и удивить нас!

Ссылка на конкурс здесь: https://quant-quest.aquan.com/competitions/cricket-qq1