Предсказываем результаты группового этапа и победителя Евро 2024 при помощи машинного обучения и чата GPT 4.0

от автора

Дисклеймер

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

Для проведения исследования использовались: датасет: https://www.kaggle.com/datasets/martj42/international-football-results-from-1872-to-2017?resource=download , язык программирования R, чат GPT 4.0. 

Цели исследования: 1) проверить точность прогноза в результате машинного обучения на основании базы данных за 20 лет 2) узнать размер выигрыша/проигрыша в букмекерской конторе при использовании приведенного подхода.

Проблематика исследования: здесь не учтены важные факторы такие как уровень и стоимость игроков, текущая форма команд, фактор домашнего турнира для сборной Германии и многое другое.

Это все учтено самими букмекерами при выставлении коэффициентов на матчи, минус 10-15 процентов их маржи, поэтому просто выбирая фаворитов выиграть невозможно.

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

 

Методология

В первую очередь был обработан датасет, так как он включает результаты более 47 000 матчей за 152 года, в том числе — различных африканских квалификаций, которые нам не интересны и замедлили бы обработку данных, датасет был сокращен до результатов евро, квалификации к нему и лиге наций.

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

За точку отсчёта взят евро 1996 года и как следствие квалификация к нему, начиная с 1994 года. Такое решение связано с изменением формата турнира, а также с развалом стран социалистического блока (увеличилось количество стран участниц).

Таким образом мы получаем примерно один состав участников и результаты за последние 20 лет. Финальный датасет составил 2 758 матчей.

 

Далее с помощью чата GPT я перебрал несколько вариантов машинного обучения в Python (использовал: pandas, numpy, train_test_split, GridSearchCV, RandomForestClassifier, accuracy_score).

Лучшим результатом стала точность прогноза — 53.51%.

Точность прогноза получилось улучшить, используя язык R.

Лучшим результатом на R стала точность прогноза — 57.65%

Весьма неплохой процент, учитывая, что игра идет на 3 результата. Так как процент точности на R у нас выше, будем использовать его для прогнозирования.

> library(randomForest) > library(dplyr) >  > # Загрузка данных > data <- read.csv("filtered_results.csv") >  > # Преобразование столбца date в формат даты > data$date <- as.Date(data$date, format="%Y-%m-%d") >  > # Создание целевой переменной > data$result <- ifelse(data$home_score > data$away_score, 1,  +                       ifelse(data$home_score < data$away_score, -1, 0)) >  > # Преобразование данных в единый формат > home_games <- data %>% +     select(team = home_team, opponent = away_team, score = home_score, opponent_score = away_score, result) >  > away_games <- data %>% +     select(team = away_team, opponent = home_team, score = away_score, opponent_score = home_score, result) %>% +     mutate(result = ifelse(result == 1, -1, ifelse(result == -1, 1, 0))) >  > all_games <- bind_rows(home_games, away_games) >  > # Создание новых признаков > team_stats <- all_games %>% +     group_by(team) %>% +     summarise(total_games = n(), +               total_win_rate = mean(result == 1), +               total_avg_score = mean(score)) >  > # Подготовка данных для модели > data <- data %>% +     left_join(team_stats, by = c("home_team" = "team")) %>% +     rename(home_team_total_games = total_games, +            home_team_total_win_rate = total_win_rate, +            home_team_total_avg_score = total_avg_score) %>% +     left_join(team_stats, by = c("away_team" = "team")) %>% +     rename(away_team_total_games = total_games, +            away_team_total_win_rate = total_win_rate, +            away_team_total_avg_score = total_avg_score) >  > # Проверка и замена NA значений > data[is.na(data)] <- 0 >  > # Подготовка данных для модели > features <- c("home_team_total_win_rate", "away_team_total_win_rate",  +               "home_team_total_games", "away_team_total_games",  +               "home_team_total_avg_score", "away_team_total_avg_score") > X <- data[features] > y <- factor(data$result) >  > # Разделение данных на обучающую и тестовую выборки > set.seed(42) > train_indices <- sample(seq_len(nrow(data)), size = 0.8 * nrow(data)) > X_train <- X[train_indices, ] > y_train <- y[train_indices] > X_test <- X[-train_indices, ] > y_test <- y[-train_indices] >  > # Обучение модели Random Forest > rf_model <- randomForest(X_train, y_train, ntree=200, mtry=3, importance=TRUE) >  > # Предсказание на тестовой выборке > y_pred <- predict(rf_model, X_test) > accuracy <- sum(y_pred == y_test) / length(y_test) > print(paste("Accuracy:", accuracy)) [1] "Accuracy: 0.576576576576577" >  > # Пример новых матчей > new_matches <- data.frame( +     home_team = c("Germany", "Hungary", "Spain", "Italy", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Austria",  +                   "Turkey", "Portugal", "Croatia", "Germany", "Scotland", "Slovenia", "Denmark", "Spain", "Slovakia",  +                   "Poland", "Netherlands", "Georgia", "Turkey", "Belgium", "Switzerland", "Scotland", "Albania", "Croatia",  +                   "Netherlands", "France", "England", "Denmark", "Slovakia", "Ukraine", "Georgia", "Czech Republic"), +     away_team = c("Scotland", "Switzerland", "Croatia", "Albania", "Netherlands", "Denmark", "England", "Ukraine", "Slovakia",  +                   "France", "Georgia", "Czech Republic", "Albania", "Hungary", "Switzerland", "Serbia", "England", "Italy",  +                   "Ukraine", "Austria", "France", "Czech Republic", "Portugal", "Romania", "Germany", "Hungary", "Spain",  +                   "Italy", "Austria", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Portugal", "Turkey") + ) >  > # Расчет признаков для новых матчей > new_matches <- new_matches %>% +     left_join(team_stats, by = c("home_team" = "team")) %>% +     rename(home_team_total_win_rate = total_win_rate, +            home_team_total_games = total_games, +            home_team_total_avg_score = total_avg_score) %>% +     left_join(team_stats, by = c("away_team" = "team")) %>% +     rename(away_team_total_win_rate = total_win_rate, +            away_team_total_games = total_games, +            away_team_total_avg_score = total_avg_score) >  > # Проверка и замена NA значений > new_matches[is.na(new_matches)] <- 0 >  > # Предсказание результатов новых матчей > predictions <- predict(rf_model, new_matches[features]) > results <- ifelse(predictions == 1, "Home Win", ifelse(predictions == 0, "Draw", "Away Win")) >  > # Вывод результатов > for (i in 1:nrow(new_matches)) { +     print(paste(new_matches$home_team[i], "vs", new_matches$away_team[i], "-> Prediction:", results[i]))

Результаты группового этапа:

1. Germany vs Scotland -> Prediction: Home Win

2. Hungary vs Switzerland -> Prediction: Home Win

3. Spain vs Croatia -> Prediction: Home Win

4. Italy vs Albania -> Prediction: Home Win

5. Poland vs Netherlands -> Prediction: Away Win

6. Slovenia vs Denmark -> Prediction: Draw

7. Serbia vs England -> Prediction: Draw

8. Romania vs Ukraine -> Prediction: Home Win

9. Belgium vs Slovakia -> Prediction: Home Win

10. Austria vs France -> Prediction: Away Win

11. Turkey vs Georgia -> Prediction: Home Win

12. Portugal vs Czech Republic -> Prediction: Home Win

13. Croatia vs Albania -> Prediction: Home Win

14. Germany vs Hungary -> Prediction: Home Win

15. Scotland vs Switzerland -> Prediction: Home Win

16. Slovenia vs Serbia -> Prediction: Home Win

17. Denmark vs England -> Prediction: Draw

18. Spain vs Italy -> Prediction: Home Win

19. Slovakia vs Ukraine -> Prediction: Home Win

20. Poland vs Austria -> Prediction: Home Win

21. Netherlands vs France -> Prediction: Away Win

22. Georgia vs Czech Republic -> Prediction: Away Win

23. Turkey vs Portugal -> Prediction: Away Win

24. Belgium vs Romania -> Prediction: Draw

25. Switzerland vs Germany -> Prediction: Away Win

26. Scotland vs Hungary -> Prediction: Home Win

27. Albania vs Spain -> Prediction: Away Win

28. Croatia vs Italy -> Prediction: Draw

29. Netherlands vs Austria -> Prediction: Home Win

30. France vs Poland -> Prediction: Home Win

31. England vs Slovenia -> Prediction: Home Win

32. Denmark vs Serbia -> Prediction: Home Win

33. Slovakia vs Romania -> Prediction: Away Win

34. Ukraine vs Belgium -> Prediction: Home Win

35. Georgia vs Portugal -> Prediction: Away Win

36. Czech Republic vs Turkey -> Prediction: Home Win

Посмотрим, каким образом сформировалась сетка 1/8 плей-офф с учетом полученных результатов матчей.

library(randomForest) > library(dplyr) >  > # Загрузка данных > data <- read.csv("filtered_results.csv") >  > # Преобразование столбца date в формат даты > data$date <- as.Date(data$date, format="%Y-%m-%d") >  > # Создание целевой переменной > data$result <- ifelse(data$home_score > data$away_score, 1,  +                       ifelse(data$home_score < data$away_score, -1, 0)) >  > # Преобразование данных в единый формат > home_games <- data %>% +     select(team = home_team, opponent = away_team, score = home_score, opponent_score = away_score, result) >  > away_games <- data %>% +     select(team = away_team, opponent = home_team, score = away_score, opponent_score = home_score, result) %>% +     mutate(result = ifelse(result == 1, -1, ifelse(result == -1, 1, 0))) >  > all_games <- bind_rows(home_games, away_games) >  > # Создание новых признаков > team_stats <- all_games %>% +     group_by(team) %>% +     summarise(total_games = n(), +               total_win_rate = mean(result == 1), +               total_avg_score = mean(score)) >  > # Подготовка данных для модели > data <- data %>% +     left_join(team_stats, by = c("home_team" = "team")) %>% +     rename(home_team_total_games = total_games, +            home_team_total_win_rate = total_win_rate, +            home_team_total_avg_score = total_avg_score) %>% +     left_join(team_stats, by = c("away_team" = "team")) %>% +     rename(away_team_total_games = total_games, +            away_team_total_win_rate = total_win_rate, +            away_team_total_avg_score = total_avg_score) >  > # Проверка и замена NA значений > data[is.na(data)] <- 0 >  > # Подготовка данных для модели > features <- c("home_team_total_win_rate", "away_team_total_win_rate",  +               "home_team_total_games", "away_team_total_games",  +               "home_team_total_avg_score", "away_team_total_avg_score") > X <- data[features] > y <- factor(data$result) >  > # Разделение данных на обучающую и тестовую выборки > set.seed(42) > train_indices <- sample(seq_len(nrow(data)), size = 0.8 * nrow(data)) > X_train <- X[train_indices, ] > y_train <- y[train_indices] > X_test <- X[-train_indices, ] > y_test <- y[-train_indices] >  > # Обучение модели Random Forest > rf_model <- randomForest(X_train, y_train, ntree=200, mtry=3, importance=TRUE) >  > # Предсказание на тестовой выборке > y_pred <- predict(rf_model, X_test) > accuracy <- sum(y_pred == y_test) / length(y_test) > print(paste("Accuracy:", accuracy)) [1] "Accuracy: 0.576576576576577" >  > # Групповой этап > group_stage_matches <- data.frame( +     home_team = c("Germany", "Hungary", "Spain", "Italy", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Austria",  +                   "Turkey", "Portugal", "Croatia", "Germany", "Scotland", "Slovenia", "Denmark", "Spain", "Slovakia",  +                   "Poland", "Netherlands", "Georgia", "Turkey", "Belgium", "Switzerland", "Scotland", "Albania", "Croatia",  +                   "Netherlands", "France", "England", "Denmark", "Slovakia", "Ukraine", "Georgia", "Czech Republic"), +     away_team = c("Scotland", "Switzerland", "Croatia", "Albania", "Netherlands", "Denmark", "England", "Ukraine", "Slovakia",  +                   "France", "Georgia", "Czech Republic", "Albania", "Hungary", "Switzerland", "Serbia", "England", "Italy",  +                   "Ukraine", "Austria", "France", "Czech Republic", "Portugal", "Romania", "Germany", "Hungary", "Spain",  +                   "Italy", "Austria", "Poland", "Slovenia", "Serbia", "Romania", "Belgium", "Portugal", "Turkey") + ) >  > # Расчет признаков для группового этапа > group_stage_matches <- group_stage_matches %>% +     left_join(team_stats, by = c("home_team" = "team")) %>% +     rename(home_team_total_win_rate = total_win_rate, +            home_team_total_games = total_games, +            home_team_total_avg_score = total_avg_score) %>% +     left_join(team_stats, by = c("away_team" = "team")) %>% +     rename(away_team_total_win_rate = total_win_rate, +            away_team_total_games = total_games, +            away_team_total_avg_score = total_avg_score) >  > # Проверка и замена NA значений > group_stage_matches[is.na(group_stage_matches)] <- 0 >  > # Предсказание результатов группового этапа > predictions <- predict(rf_model, group_stage_matches[features]) > results <- ifelse(predictions == 1, "Home Win", ifelse(predictions == 0, "Draw", "Away Win")) >  > # Вывод результатов и подсчет очков > group_stage_matches <- group_stage_matches %>% +     mutate(result = results, +            home_points = ifelse(result == "Home Win", 3, ifelse(result == "Draw", 1, 0)), +            away_points = ifelse(result == "Away Win", 3, ifelse(result == "Draw", 1, 0))) >  > # Создание таблицы очков > group_points <- group_stage_matches %>% +     select(home_team, home_points) %>% +     rename(team = home_team, points = home_points) %>% +     bind_rows(group_stage_matches %>% +                   select(away_team, away_points) %>% +                   rename(team = away_team, points = away_points)) %>% +     group_by(team) %>% +     summarise(total_points = sum(points)) %>% +     arrange(desc(total_points)) >  > # Вывод очков команд > print(group_points) # A tibble: 24 × 2    team           total_points    <chr>                 <dbl>  1 France                    9  2 Germany                   9  3 Portugal                  9  4 Spain                     9  5 Romania                   7  6 Czech Republic            6  7 Netherlands               6  8 Scotland                  6  9 Denmark                   5 10 England                   5 # ℹ 14 more rows # ℹ Use `print(n = ...)` to see more rows >  > # Определение команд, вышедших в плей-офф > groups <- list( +     A = c("Germany", "Scotland", "Hungary", "Switzerland"), +     B = c("Spain", "Croatia", "Italy", "Albania"), +     C = c("Slovenia", "Denmark", "Serbia", "England"), +     D = c("Poland", "Netherlands", "Austria", "France"), +     E = c("Belgium", "Slovakia", "Romania", "Ukraine"), +     F = c("Turkey", "Georgia", "Portugal", "Czech Republic") + ) >  > playoff_teams <- list() > third_place_teams <- list() >  > for (group in names(groups)) { +     group_teams <- groups[[group]] +     group_points_filtered <- group_points %>% filter(team %in% group_teams) +     playoff_teams[[group]] <- group_points_filtered$team[1:2] +     third_place_teams[[group]] <- group_points_filtered$team[3] + } >  > # Определение лучших третьих мест > third_place_teams_points <- group_points %>% filter(team %in% unlist(third_place_teams)) > best_third_place_teams <- third_place_teams_points %>% arrange(desc(total_points)) %>% head(4) %>% pull(team) >  > # Заполнение расписания матчей плей-офф > playoff_schedule <- data.frame( +     match = c("Match № 38", "Match № 37", "Match № 40", "Match № 39", "Match № 42", "Match № 41", "Match № 43", "Match № 44"), +     home_team = c(playoff_teams$A[2], playoff_teams$A[1], playoff_teams$C[1], playoff_teams$B[1], playoff_teams$D[2], playoff_teams$F[1], playoff_teams$E[1], playoff_teams$D[1]), +     away_team = c(playoff_teams$B[2], playoff_teams$C[2], best_third_place_teams[1], best_third_place_teams[2], playoff_teams$E[2], best_third_place_teams[3], best_third_place_teams[4], playoff_teams$F[2]) + ) >  > print(playoff_schedule)

1/8 плей-офф:

Match № 38

Scotland

Croatia

Match № 37

Germany

England

Match № 40

Denmark

Italy

Match № 39

Spain

Slovenia

Match № 42

Netherlands

Belgium

Match № 41

Portugal

Hungary

Match № 43

Romania

Poland

Match № 44

France

Czech Republic

На этом завершается первый этап исследования.

На втором этапе я подведу промежуточные итоги и дам прогноз на плей-офф с учетом реально образовавшихся пар в 1/8.

На третьем этапе подведу общие итоги.

 

Оценка результатов исследования:

1) Посмотрим, сколько результатов было предсказано верно и сравним процент с 57.65. Так проверим, насколько верно компьютер оценил точность своего прогноза.

2) Посмотрим виртуальный банк после турнира и проверим, удалось ли машине обыграть букмекера.

 

Виртуальный банк

Для того чтобы узнать, принесет нам прибыль или убыток в букмекерской конторе такая стратегия, мы создадим виртуальный банк в размере 5 300 долларов. 51 матч будет сыгран на этом турнире, на каждый будет совершена условная ставка в размере 100 долларов на основании прогноза машины + 2 раза по 100 долларов мы поставим на чемпиона — до начала турнира и после окончания групповой стадии.

Я буду брать средний коэффициент на сайте https://www.flashscore.com.ua/, чтобы не рекламировать какого-то конкретного букмекера.

 

А чемпионом Евро 2024 по версии машины будет Испания.


ссылка на оригинал статьи https://habr.com/ru/articles/821133/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *