Я аналитик данных, а не специалист по данным, но в последнее время я читал о теории алгоритмов машинного обучения и ее реализации в R, и мне захотелось попробовать. Затем… Я попытался построить модель машинного обучения, чтобы предсказать вероятность победы моей сестры и меня в Brawlhalla Ranked 2v2.
Я не получил ожидаемых результатов и потерпел неудачу (но учусь), поэтому, если у вас есть какие-либо рекомендации для новичка по улучшению этой модели, буду признателен! Эта статья является продолжением описательного анализа. :
Преобразование данных
Исходные данные те же, что и в предыдущей статье.
Цель состоит в том, чтобы прогнозировать на основе информации в предматчевых характеристиках (персонажи и цвет, выбранные нами, персонажи сцены и враги); нокауты и удары не применяются, потому что это информация после матча. Целевая переменная довольно сбалансирована: 0 = 318 и 1 = 320.
Наша команда воспринимается как одна переменная вместо отдельного персонажа. Когда мы достигаем рейтинга выше 1389, «лига» становится золотой, а ниже 1389 — серебряной. Каждая категориальная функция имеет слишком много уникальных значений, поэтому я объединяю многие из них, рассматривая возможность использования фиктивных переменных в будущем и не заканчивая набором данных из тысячи столбцов.
Последнее преобразование состоит в объединении Enemy_1
и Enemy_2
, но оно может быть таким же, как team
, потому что здесь не имеет значения порядок, тогда я использовал целевую функцию кодирования для каждого столбца и вычислил среднее гармоническое.
set.seed(123) brawlhalla_clean <- brawlhalla %>% mutate(team = paste0(Ch_D, "-", Ch_S), team = fct_lump_n(team, 7, other_level = "Other"), Rating = ifelse(Rating <= 1389, "Silver", "Gold"), Color = fct_lump_min(Color, 30, other_level = "Other"), Stage = fct_lump_min(Stage, 32, other_level = "Other"), k = sample(1:5, nrow(brawlhalla), replace = TRUE)) %>% select(-starts_with(c("KO", "Hit", "Ch"))) #Creating a dataframe of enemies and lumping below 2% most_freq_enemies2 <- data.frame( enemy = c(brawlhalla_clean$Enemy_1, brawlhalla_clean$Enemy_2), win = rep(brawlhalla_clean$Win, 2), k = rep(brawlhalla_clean$k, 2) ) %>% mutate(enemy = fct_lump_prop(enemy, 0.02, other_level = "Other_ch")) #Target encoding from enemies tar_enc_enemies <- target_enc(data = most_freq_enemies2, enc_col = "enemy", tar_col = "win", k_col = "k", kmin = 1, kmax = 5) brawlhalla_clean <- brawlhalla_clean %>% mutate(Enemy_1 = ifelse(Enemy_1 %in% tar_enc_enemies$enemy, Enemy_1, "Other_ch"), Enemy_2 = ifelse(Enemy_2 %in% tar_enc_enemies$enemy, Enemy_2, "Other_ch")) #Joining the enemies to final dataset for ML brawlhalla_ML <- brawlhalla_clean %>% left_join(tar_enc_enemies, by = c("Enemy_1" = "enemy")) %>% rename(EnemyEnemy_1
tar = tar_enc_enemy) %>% left_join(tar_enc_enemies, by = c("Enemy_2" = "enemy")) %>% rename(EnemyEnemy_2
tar = tar_enc_enemy) %>% mutate(Enemy_mean = (2 * EnemyEnemy_1
tar * EnemyEnemy_2
tar) / (EnemyEnemy_1
tar + EnemyEnemy_2
tar) ) %>% select(-c(EnemyEnemy_1
tar, EnemyEnemy_2
tar, Enemy_1, Enemy_2, k)) %>% mutate(Rating = factor(Rating), Win = factor(Win))
Разделение набора данных
После начального разделения (обучение 75%) я добавляю некоторый шум в набор данных Enemy_mean
on training, чтобы избежать переобучения из-за утечки данных.
set.seed(684) brawlhalla_split <- initial_split(brawlhalla_ML, strata = Win) brawlhalla_train <- training(brawlhalla_split) brawlhalla_test <- testing(brawlhalla_split) Enemy_train_vect <- brawlhalla_train %>% pull(Enemy_mean) noise_limit <- (max(Enemy_train_vect) - min(Enemy_train_vect)) * 0.3 set.seed(1919) brawlhalla_train <- brawlhalla_train %>% mutate(noise = runif(nrow(.), -noise_limit, noise_limit), Enemy_mean = Enemy_mean + noise) %>% select(-noise)
Настройка гиперпараметров
Я использовал случайный лес, который я знаю лучше других, более сложных, таких как «машина опорных векторов» или «нейронные сети». В наборе данных проверки я использовал технику начальной загрузки с 30 сгибами.
Были настроены параметры mtry
(количество объектов, случайно выбранных алгоритмом для использования в деревьях) и min_n
(количество точек данных, необходимых в узле перед разделением). Количество trees
зафиксировано на 500. Первая сетка была общей с использованием 20 комбинаций.
set.seed(1313) brawlhalla_folds <- bootstraps(brawlhalla_train, strata = Win, times = 30) #recipe definition brawlhalla_rec <- recipe(Win ~., data = brawlhalla_train) %>% step_dummy(all_nominal_predictors()) #model definition rand_spec <- rand_forest(mtry = tune(), min_n = tune(), trees = 500) %>% set_mode("classification") %>% set_engine("ranger") #workflow definition brawlhalla_wf <- workflow() %>% add_recipe(brawlhalla_rec) %>% add_model(rand_spec) doParallel::registerDoParallel() set.seed(797) rand_rs <- tune_grid( brawlhalla_wf, resamples = brawlhalla_folds, grid = 20, metrics = metric_set(accuracy, roc_auc) )
Как видите, точность довольно низкая, поэтому в поисках небольшой оптимизации была построена другая сетка, учитывая, что 20 ≤ min_n
≤ 35 и 1 ≤ mtry
≤ 5 имеют большие средние значения.
rf_grid <- grid_regular(mtry(range = c(1, 5)), min_n(range = c(20, 35)), levels = 6) doParallel::registerDoParallel() set.seed(694) regular_rs <- brawlhalla_wf %>% tune_grid(resamples = brawlhalla_folds, grid = rf_grid)
Точность немного выросла. Гиперпараметр mtry
= 2 и min_n
= 20 дает максимальную точность, но низкий ROC-AUC; Я выбрал mtry
= 1 и min_n
= 32, потому что это баланс между двумя показателями.
best_tune <- tibble(mtry = 1, min_n = 32) final_rf <- finalize_model( rand_spec, best_tune )
Тестирование модели
Со всеми настройками мы можем, наконец, протестировать модель с помощью функции last_fit
, которая обучает модель в обучающем наборе данных и одновременно оценивает ее в тестовом наборе данных.
final_wf <- workflow() %>% add_recipe(brawlhalla_rec) %>% add_model(final_rf) set.seed(857) final_res <- final_wf %>% last_fit(brawlhalla_split) final_res %>% collect_metrics()
Ну, могло быть и хуже, я прав? На приведенном ниже графике мы можем видеть вероятность, заданную моделью для контрольных точек по оси x и категорий color
и team
по оси y.
Модель с трудом классифицирует, когда color
является Charged OG или Skyforged, и team
в целом. Это может быть связано со многими точками, которые имеют эти переменные, но разница между выигрышами и проигрышами минимальна, и это может запутать модель. Серый color
, например, имеет довольно большую разницу в выигрыше-проигрыше (-10), и предсказание модели почти правильное.
Кроме того, я думаю, что модель искажена из-за малого количества точек. Например, team
Zariel-Val является лучшим с коэффициентом выигрыша 70%, но у него всего 26 очков во всем наборе данных; затем модель предсказала все вероятности для этой команды > 0,5; Потому что в тренировочных данных было 14 побед и 4 поражения.
Наконец, важность переменной (крутая штука случайного леса). Стадион «Тандергард» и «Энигма» — наши лучшие Stage
, а Кинг-Пасс — худшие, и все они здесь; Зариэль-Вал — наш лучший team
, а Goldenforged and Home team — наш лучший color
. Я думаю, что модель имеет правильное направление и определяет некоторые закономерности, но ей нужно немного подтолкнуть мои данные (то есть больше данных).
set.seed(1912) final_rf %>% set_engine("ranger", importance = "permutation") %>% fit(Win ~., data = juice(prep(brawlhalla_rec))) %>% vip(geom = "point") + theme_light()
В заключение, мне нужно больше очков; в наборе данных слишком много категориальных переменных, комбинация которых не работает как хорошие предикторы. Возможно, модель была не так уж плоха, но я продолжу собирать данные и исследовать более сложные или более простые модели.