Введение
Недавно я отправился в путешествие, чтобы объединить свою страсть к футболу и анализу данных. В качестве первого шага в этом направлении я создал свою собственную модель xG (ожидаемые цели).
Итак, что такое xG?
Я думаю, что одно из лучших объяснений модели xG дает Статсбомба:
Ожидаемое количество голов (xG) — это показатель, предназначенный для измерения вероятности того, что удар приведет к голу.
Модель xG использует историческую информацию о тысячах ударов со схожими характеристиками для оценки вероятности гола по шкале от 0 до 1.
Например, мы ожидаем, что удар со значением xG 0,2 будет реализован дважды за каждые 10 попыток.
Я начал свой проект с поиска соответствующих данных и наткнулся на данные о футбольных событиях, которые были получены Wyscout и размещены в открытом доступе. В частности, я стремился разработать модель, основанную на данных о событиях, собранных в пяти ведущих европейских лигах (Ла Лига, Английская Премьер-лига, Лига 1, Немецкая Бундеслига и Серия А), а также чемпионатах мира и Лиге чемпионов.
В футболе удары имеют решающее значение для определения команды-победителя, поскольку они часто приводят к голам. Поскольку голы случаются редко (в моем наборе данных их всего 2,69 за игру), а разница голов может быть небольшой, важно использовать модель xG для оценки качества каждого выполненного броска. Чтобы лучше понять это, я нанес на карту места ударов из моих данных на футбольное поле и рассчитал процент ударов, в результате которых были забиты голы, на каждом участке поля.
Я начал наносить на футбольное поле места ударов, используя свои данные, чтобы определить долю ударов, приводящих к голу, на каждом участке поля.
а затем отфильтруйте удары, которые завершились голом:
Наконец, я подсчитал долю ударов, в результате которых был забит гол с каждого участка поля:
Очевидно, что процент попаданий в гол имеет некоторые недостатки в моих данных. В частности, на определенных участках поля возле средней линии наблюдается высокий процент голов. Однако базовые знания футбола говорят нам, что удары, сделанные с такого расстояния, вряд ли приведут к голу.
Поэтому я начал улучшать модель, добавляя дополнительные функции. Углы съемки — отличная функция.
При выполнении броска угол (θ) образуется двумя линиями, соединяющими точку выполнения броска с каждой стойкой ворот. Угол становится уже, когда удар производится с большего расстояния, и шире, когда выстрел производится с более близкой точки к цели.
Выстрелы обычно производятся с расстояния от 10 до 20 метров. Реже выстрелы производятся в пределах 6 метров, чем за пределами 10 метров. Заметно уменьшилось количество снимков, сделанных в локальном районе между 18 и 25 метрами. Как и ожидалось, игроки редко наносят удары с углов менее 5 градусов, обычно это удары по центру возле ворот.
мы видим, что градусы угла также коррелируют с расстоянием.
Очевидно, что удары, приводящие к забитию ворот, выполняются с гораздо более близкого расстояния по сравнению с ударами, промахивающимися мимо цели. Средняя дальность успешных бросков – 12,26, неудачных – 19,33. Это указывает на то, что съемка на близких дистанциях является сложной задачей, но результаты благоприятны.
Данные, с которыми я работал, охватывали все события, происходящие в матче, включая пасы, дуэли, кроссы и многое другое. Прежде чем делать снимки, я использовал одно горячее кодирование, чтобы выделить важные предыдущие действия, такие как «отскок», «prev_cross», «prev_touch», «prev_pass» и «prev_smart_pass».
а затем построил простые модели на основе моих данных и сравнил их вместе
from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.metrics import accuracy_score, roc_auc_score from sklearn.preprocessing import StandardScaler from imblearn.over_sampling import SMOTE from sklearn.pipeline import Pipeline # Select features for training features = ['Distance', 'Angle Degrees', 'rebound', 'prev_cross', 'prev_touch', 'prev_pass', 'prev_smart_pass'] # Split data into features (X) and target (y) X = shots[features] y = shots['Goal'] # Split data into training and testing sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Set up preprocessing steps (standardization) and model pipelines scaler = StandardScaler() smote = SMOTE(random_state=42) logreg = LogisticRegression() rf = RandomForestClassifier(random_state=42) gb = GradientBoostingClassifier(random_state=42) pipelines = { 'Logistic Regression': Pipeline([('scaler', scaler), ('logreg', logreg)]), 'Random Forest': Pipeline([('scaler', scaler), ('rf', rf)]), 'Gradient Boosting': Pipeline([('scaler', scaler), ('gb', gb)]) } # Grid of hyperparameters to search over param_grids = { 'Logistic Regression': {'logreg__C': [0.1, 1, 10]}, 'Random Forest': {'rf__n_estimators': [50, 100, 200], 'rf__max_depth': [None, 10, 20]}, 'Gradient Boosting': {'gb__n_estimators': [50, 100, 200], 'gb__learning_rate': [0.01, 0.1, 0.2]} } # Evaluate and compare models for model_name, pipeline in pipelines.items(): print(f"Training {model_name}...") grid_search = GridSearchCV(pipeline, param_grid=param_grids[model_name], scoring='roc_auc', cv=5, n_jobs=-1) grid_search.fit(X_train, y_train) # Make predictions on the testing set y_pred_proba = grid_search.predict_proba(X_test)[:, 1] # Evaluate the model roc_auc = roc_auc_score(y_test, y_pred_proba) print(f"{model_name} ROC AUC:", roc_auc)
В следующем коде используется конвейер для включения предварительной обработки стандартизации и различных алгоритмов машинного обучения, таких как логистическая регрессия, случайный лес и повышение градиента. Кроме того, GridSearchCV используется для настройки гиперпараметров для каждого алгоритма. Чтобы устранить дисбаланс классов, используется SMOTE для избыточной выборки класса меньшинства.
Обучение логистической регрессии…
Логистическая регрессия ROC AUC: 0,772450567394865
Обучение случайного леса…
ROC случайного леса AUC: 0,7740408162036831
Обучение градиентного повышения…
Градиентное повышение ROC AUC: 0,7744384468095702
так что у меня одинаковые результаты для каждой модели (это неплохо)
Теперь у меня есть собственная модель xG, и мы можем проверить ее эффективность на реальных данных, например, гол Эдинсона Кавани в матче Кубка мира против России.
у моей модели xG 0,86 для этого кадра, это означает
или этот удар Ремо Фройлера Фиорентина — Аталанта, 1–1, который не был реализован в гол, имел xG 0,87.
Я разработал модель xG, которая позволяет мне определять значение xG для каждого выстрела. Для этого мне нужны координаты выстрела, а также информация о предшествующем событии, которое привело к выстрелу.