Скрининг серии типов моделей и этапов разработки функций для задачи классификации с помощью Tidymodels

Представьте, что вы специалист по обработке данных в большом банке, и ваш CDO поручил вам разработать средства автоматизации решений по банковским кредитам. Вы решаете, что это должен быть двоичный классификатор, и приступаете к сбору нескольких сотен точек данных. Но с какой модели следует начать и какие функции следует завершить? Почему бы не просмотреть несколько комбинаций?

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

Данные для прогнозирования ссуд были взяты из https://datahack.analyticsvidhya.com/contest/practice-problem-loan-prediction-iii/.

Загрузить пакеты и данные

library(tidymodels) #ML meta packages
library(themis) #Recipe functions to deal with class imbalances
library(discrim) #Naive Bayes Models
library(tidyposterior) #Bayesian Performance Comparison
library(corrr) #Correlation Viz
library(readr) #Read Tabular Data
library(magrittr) #Pipe Operators
library(stringr) #Work with Strings
library(forcats) #Work with Factors
library(skimr) #Data Summary
library(patchwork) #ggplot grids
library(GGally) #Scatterplot Matrices
train <- read_csv("train_ctrUa4K.csv")
train %<>% rename(Applicant_Income = ApplicantIncome,
                  CoApplicant_Income = CoapplicantIncome,
                  Loan_Amount = LoanAmount) 

Исследовательский анализ данных

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

Мы используем пакет skimr для получения следующего вывода

skim(train)

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

#Automated Exploratory Data Analysis
viz_by_dtype <- function (x,y) {
  title <- str_replace_all(y,"_"," ") %>% 
           str_to_title()
  if ("factor" %in% class(x)) {
    ggplot(train, aes(x, fill = x)) +
      geom_bar() +
      theme(legend.position = "none",
            axis.text.x = element_text(angle = 45, hjust = 1),
            axis.text = element_text(size = 8)) +
      theme_minimal() +
      scale_fill_viridis_c()+
      labs(title = title, y = "", x = "")
  }
  else if ("numeric" %in% class(x)) {
    ggplot(train, aes(x)) +
      geom_histogram()  +
      theme_minimal() +
      scale_fill_viridis_c()+
      labs(title = title, y = "", x = "")
  } 
  else if ("integer" %in% class(x)) {
    ggplot(train, aes(x)) +
      geom_histogram() +
      theme_minimal() +
      scale_fill_viridis_c()+
      labs(title = title, y = "", x = "")
  }
  else if ("character" %in% class(x)) {
    ggplot(train, aes(x, fill = x)) +
      geom_bar() +
      theme_minimal() +
      scale_fill_viridis_d()+
      theme(legend.position = "none",
            axis.text.x = element_text(angle = 45, hjust = 1),
            axis.text = element_text(size = 8)) +
      labs(title = title, y  ="", x= "")
  }
}
variable_plot <- map2(train, colnames(train), viz_by_dtype) %>%
  wrap_plots(ncol = 3,
             nrow = 5)

  • Гендерный дисбаланс, высокая доля кандидатов-мужчин, присутствуют НП
  • Семейное положение, почти 3: 2 заявителя, состоящих в браке, к не состоящим в браке, с присутствующими НП
  • Большинство поступающих не имеют детей
  • Большинство поступающих - выпускники вузов.
  • Большинство из них не работают на себя
  • Доход заявителя искажен вправо
  • Доход со-заявителя искажен вправо
  • Сумма ссуды искажена вправо
  • Срок кредита обычно составляет 360 дней.
  • Большинство соискателей имеют кредитную историю, присутствуют НП.
  • Сочетание типов площадей
  • По большинству заявок заявки были одобрены, целевая переменная Статус ссуды несбалансирована 3: 2

Двумерный анализ данных

Количественные переменные

Используя GGally :: ggpairs (), мы генерируем график матрицы переменных для всех числовых переменных и раскрашиваем Loan_Status.

#Correlation Matrix Plot
ggpairs(train %>% select(7:10,13), ggplot2::aes(color = Loan_Status, alpha = 0.3)) + 
  theme_minimal() + 
  scale_fill_viridis_d(aesthetics = c("color", "fill"), begin = 0.15, end = 0.85) +
  labs(title = "Numeric Bivariate Analysis of Loan Data")

Качественные переменные

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

#Generate Summary Variables for Qualitative Variables
summary_train <- 
  train %>% 
  select(where(is.character),
         -Loan_ID) %>% 
  drop_na() %>% 
  mutate(Loan_Status = if_else(Loan_Status == "Y",1,0)) %>% 
  pivot_longer(1:6, names_to = "Variables", values_to = "Values") %>% 
  group_by(Variables, Values) %>% 
    summarise(mean = mean(Loan_Status),
              conf_int = 1.96*sd(Loan_Status)/sqrt(n())) %>% 
  pivot_wider(names_from = Variables, values_from = Values)
summary_train %>% select(Married, mean, conf_int) %>% 
  drop_na() %>% 
  ggplot(aes(x=Married, y = mean, color = Married)) +
  geom_point() +
  geom_errorbar(aes(ymin = mean - conf_int, ymax = mean + conf_int), width = 0.1) +
  theme_minimal() +
  theme(legend.position = "none",
        axis.title.x = element_blank(),
        axis.title.y = element_blank()) +
  scale_colour_viridis_d(aesthetics = c("color", "fill"), begin = 0.15, end = 0.85) +
  labs(title="Married")

Преимущество выполнения визуализации, как указано выше, заключается в том, что она дает понимание величины разницы в успешности заявки на получение кредита, а также неопределенности, визуализированной с помощью 95% доверительных интервалов для каждой категориальной переменной. Отсюда отметим следующее:

  • Соискатели, состоящие в браке, как правило, имеют больше шансов на то, чтобы их заявление было одобрено.
  • Кандидаты в аспирантуру имеют больше шансов на успех, возможно, из-за более стабильных / более высоких доходов.
  • Точно так же существует гораздо больший разброс в успехах кандидатов, которые работают на себя, вероятно, из-за разницы в стабильности доходов.
  • Количество детей не оказывает большого влияния на статус кредита
  • Клиенты-женщины имеют гораздо больший разброс в успехе приложения, клиенты-мужчины более дискретны. Мы должны быть осторожны с этим, чтобы не создать укоренившуюся предвзятость в алгоритме.
  • Semiurban или Suburban имеет самый высокий процент успеха.
  • Наконец, существует значительная разница в шансах успеха клиентов с кредитной историей и без нее.

Если бы мы захотели, мы могли бы изучить эти отношения дальше с помощью теста ANOVA / Chi Squared.

Разделение данных - образцы

Мы разделили данные 80:20, стратифицированные по Loan_Status. Доступ к каждому разделенному тибблу можно получить, вызвав функцию training () или testing () для объекта mc_split.

#Split Data for Testing and Training
set.seed(101)
loan_split <- initial_split(train, prop = 0.8, strata = Loan_Status)

Разработка модели - пастернак.

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

#Initialise Seven Models for Screening
nb_loan <- 
  naive_Bayes(smoothness = tune(), Laplace = tune()) %>% 
  set_engine("klaR") %>% 
  set_mode("classification")
logistic_loan <- 
  logistic_reg(penalty = tune(), mixture = tune()) %>% 
  set_engine("glmnet") %>% 
  set_mode("classification")
dt_loan <- decision_tree(cost_complexity = tune(), tree_depth = tune(), min_n = tune()) %>% 
  set_engine("rpart") %>% 
  set_mode("classification")
rf_loan <- 
  rand_forest(mtry = tune(), trees = tune(), min_n = tune()) %>% 
  set_engine("ranger") %>% 
  set_mode("classification")
knn_loan <- nearest_neighbor(neighbors = tune(), weight_func = tune(), dist_power = tune()) %>% 
  set_engine("kknn") %>% 
  set_mode("classification")
svm_loan <- 
  svm_rbf(cost = tune(), rbf_sigma = tune(), margin = tune()) %>% 
  set_engine("kernlab") %>% 
  set_mode("classification")
xgboost_loan <- boost_tree(mtry = tune(), trees = tune(), min_n = tune(), tree_depth = tune(), learn_rate = tune(), loss_reduction = tune(), sample_size = tune())  %>% 
  set_engine("xgboost") %>% 
  set_mode("classification")

Разработка функций - рецепты

Ниже представлена ​​серия конфигураций рецептов. Как два разных метода вменения будут сравниваться с одним и другим, в примечании ниже мы использовали impute_mean (для числовых), impute_mode (для символов) или использовали impute_bag (вменение с использованием деревьев в мешках), которые могут вменять как числовые, так и символьные переменные.

Обратите внимание, что с Credit_History мы решили не вменять это значение, вместо этого присвоили три результата целочисленному значению, установив для неизвестных экземпляров значение 0.

Поскольку Loan_Status несбалансирован, мы применили SMOTE (метод синтетической избыточной выборки меньшинства) к рецептам 2 и 4. Мы проведем сравнение рабочего процесса, чтобы понять любую разницу в точности между стратегиями вменения.

#Initialise Four Recipes
recipe_1 <- 
  recipe(Loan_Status ~., data = training(loan_split)) %>% 
  step_rm(Loan_ID) %>%
  step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>% 
  step_scale(all_numeric_predictors(), -Credit_History) %>% 
  step_impute_bag(Gender, 
                  Married, 
                  Dependents, 
                  Self_Employed, 
                  Loan_Amount, 
                  Loan_Amount_Term) %>% 
  step_dummy(all_nominal_predictors())
recipe_2 <- 
  recipe(Loan_Status ~., data = training(loan_split)) %>% 
  step_rm(Loan_ID) %>%
  step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>% 
  step_scale(all_numeric_predictors(), -Credit_History) %>% 
  step_impute_bag(Gender, 
                  Married, 
                  Dependents, 
                  Self_Employed, 
                  Loan_Amount, 
                  Loan_Amount_Term) %>% 
  step_dummy(all_nominal_predictors()) %>% 
  step_smote(Loan_Status)
  
recipe_3 <- 
  recipe(Loan_Status ~., data = training(loan_split)) %>% 
  step_rm(Loan_ID) %>%
  step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>%  
  step_scale(all_numeric_predictors(), -Credit_History) %>%  
  step_impute_mean(all_numeric_predictors()) %>%
  step_impute_mode(all_nominal_predictors()) %>% 
  step_dummy(all_nominal_predictors()) %>% 
  step_zv(all_predictors())
recipe_4 <- 
  recipe(Loan_Status ~., data = training(loan_split)) %>% 
  step_rm(Loan_ID) %>%
  step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>% 
  step_scale(all_numeric_predictors(), -Credit_History) %>%  
  step_impute_mean(all_numeric_predictors()) %>%
  step_impute_mode(all_nominal_predictors()) %>% 
  step_dummy(all_nominal_predictors()) %>% 
  step_zv(all_predictors()) %>% 
  step_smote(Loan_Status)

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

#Prep and Bake Training and Test Datasets
loan_train <- recipe_1 %>% prep() %>% bake(new_data = NULL)
loan_test <- recipe_1 %>% prep() %>% bake(testing(loan_split))

Затем возьмите эти преобразованные наборы данных и визуализируйте корреляцию переменных.

#Generate Correlation Visualisation
loan_train %>% bind_rows(loan_test) %>% 
  mutate(Loan_Status = if_else(Loan_Status == "Y",1,0)) %>% 
              correlate() %>%
              rearrange() %>% 
              shave() %>% 
              rplot(print_cor = T,.order = "alphabet") +
                theme_minimal() +
                theme(axis.text.x = element_text(angle = 90)) +
                scale_color_viridis_c() +
                labs(title = "Correlation Plot for Trained Loan Dataset")

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

Итерация комбинаций Parnsip и рецептов - наборы рабочих процессов

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

#Generate List of Recipes
recipe_list <- 
list(Recipe1 = recipe_1, Recipe2 = recipe_2, Recipe3 = recipe_3, Recipe4 = recipe_4)
#Generate List of Model Types
model_list <- 
list(Random_Forest = rf_loan, SVM = svm_loan, Naive_Bayes = nb_loan, Decision_Tree = dt_loan, Boosted_Trees = xgboost_loan, KNN = knn_loan, Logistic_Regression = logistic_loan)

А вот и самое интересное - создание серии рабочих процессов с помощью workflow_sets ()

model_set <- workflow_set(preproc = recipe_list, models = model_list, cross = T)

Установка cross = T указывает workflow_set на создание всех возможных комбинаций модели пастернака и спецификации рецепта.

set.seed(2)
train_resamples <- bootstraps(training(loan_split), strata = Loan_Status)
doParallel::registerDoParallel(cores = 12)
all_workflows <- 
  model_set %>% workflow_map(resamples = train_resamples, 
                             verbose = TRUE)

Мы инициализировали процедуру повторной выборки Boostraps, она требует больше вычислений, но в целом приводит к меньшему количеству ошибок. Передача объекта model_set в workflow_map с процедурой повторной выборки инициирует экран всех комбинаций пастернака и рецепта, показывающий такие выходные данные. Это сложная процедура, и поэтому мы инициировали параллельные вычисления, чтобы упростить ее.

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

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

N.B. есть функция autoplot (), доступная для объектов workflow_set, однако мы хотим иметь возможность сравнивать производительность рецепта и модели, поэтому нам пришлось перепроектировать функции для визуализации ниже.

#Visualise Performance Comparison of Workflows
collect_metrics(all_workflows) %>% 
  separate(wflow_id, into = c("Recipe", "Model_Type"), sep = "_", remove = F, extra = "merge") %>% 
  filter(.metric == "accuracy") %>% 
  group_by(wflow_id) %>% 
  filter(mean == max(mean)) %>% 
  group_by(model) %>% 
  select(-.config) %>% 
  distinct() %>%
  ungroup() %>% 
  mutate(Workflow_Rank =  row_number(-mean),
         .metric = str_to_upper(.metric)) %>%
  ggplot(aes(x=Workflow_Rank, y = mean, shape = Recipe, color = Model_Type)) +
    geom_point() +
    geom_errorbar(aes(ymin = mean-std_err, ymax = mean+std_err)) +
    theme_minimal()+
    scale_colour_viridis_d() +
    labs(title = "Performance Comparison of Workflow Sets", x = "Workflow Rank", y = "Accuracy", color = "Model Types", shape = "Recipes")

Из вышесказанного мы можем наблюдать:

  • Наивные модели Байеса и KNN показали худшие результаты
  • Древовидные методы показали себя очень хорошо
  • SMOTE не предлагал повышения производительности для более сложных типов моделей, никаких изменений между рецептами для модели дерева решений.

Несмотря на то, что мы сделали очень общие наблюдения с первого взгляда, учитывая небольшую разницу в средней точности, скажем, 8 основных рабочих процессов, как нам различить, какие из них отличаются, а в остальном практически одинаковы?

Постфокусный анализ передискретизации - tidyposterior

Один из способов сравнения моделей - изучить результаты повторной выборки и спросить, действительно ли модели разные?
Функция tidyposterior :: perf_mod позволяет сравнивать все наши комбинации рабочего процесса, генерируя набор апостериорных распределений для заданной метрики. , которые затем можно сравнить друг с другом на предмет практической эквивалентности. По сути, мы можем проводить сравнения между рабочими процессами.

doParallel::registerDoParallel(cores = 12)
set.seed(246)
acc_model_eval <- perf_mod(all_workflows, metric = "accuracy", iter = 5000)

Затем результаты perf_mod передаются в tidy () для извлечения основных результатов апостериорного анализа. Их распределение визуализировано ниже.

#Extract Results from Posterior Analysis and Visualise Distributions
acc_model_eval %>% 
  tidy() %>% 
  mutate(model = fct_inorder(model)) %>% 
  ggplot(aes(x=posterior)) +
   geom_histogram(bins = 50) +
   theme_minimal() +
   facet_wrap(~model, nrow = 7, ncol = 6) +
   labs(title = "Comparison of Posterior Distributions of Model Recipe Combinations", x = expression(paste("Posterior for Mean Accuracy")), y = "")

Оглядываясь назад на наше сравнение производительности, мы видим, что две модели наивысшего ранга - это деревья с усилением и деревья решений с рецептом 1, модели на крайних сторонах шкалы интерпретируемости для моделей на основе деревьев. Мы можем попытаться понять дальше, взяв разницу в средних их соответствующих апостериорных распределений.

Используя Contrast_models (), мы можем провести этот анализ.

#Compare Two Models - Difference in Means
mod_compare <- contrast_models(acc_model_eval,
                            list_1 = "Recipe1_Decision_Tree",
                            list_2 = "Recipe1_Boosted_Trees")
a1 <- mod_compare %>% 
  as_tibble() %>% 
  ggplot(aes(x=difference)) +
  geom_histogram(bins = 50, col = "white", fill = "#73D055FF")+
  geom_vline(xintercept = 0, lty = 2) +
  theme_minimal()+
  scale_fill_viridis_b()+
  labs(x= "Posterior for Mean Difference in Accuracy", y="", title = "Posterior Mean Difference Recipe1_Decision_Tree & Recipe3_Boosted_Trees")
a2 <- acc_model_eval %>% 
  tidy() %>% mutate(model = fct_inorder(model)) %>% 
  filter(model %in% c("Recipe1_Boosted_Trees", "Recipe1_Decision_Tree")) %>% 
  ggplot(aes(x=posterior)) +
  geom_histogram(bins = 50, col = "white", fill = "#73D055FF") +
  theme_minimal()+
  scale_colour_viridis_b() +
  facet_wrap(~model, nrow = 2, ncol = 1) +
  labs(title = "Comparison of Posterior Distributions of Model Recipe Combinations", x = expression(paste("Posterior for Mean Accuracy")), y = "")
a2/a1

При передаче объекта mod_compare в сводку генерируется следующий вывод

mod_compare %>% summary()
mean
0.001371989 #Difference in means between posterior distributions
probability
0.5842 #Proportion of Posterior of Mean Difference > 0

Как также подтверждается апостериорной разницей среднего, средняя разница между соответствующими апостериорными распределениями рабочего процесса мала для средней точности. 58,4% апостериорной разницы в распределении средних больше 0, поэтому мы можем сделать вывод, что положительная разница является реальной (хотя и небольшой).

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

summary(mod_compare, size = 0.02)
pract_equiv
0.9975

Размер эффекта - это пороговые значения с обеих сторон [-0,02, 0,02] разницы в апостериорном распределении средних значений, а Pract_equiv измеряет, сколько разницы. в средствах сообщения. распределение находится в пределах этих пороговых значений. Для нашего сравнения Boosted_Trees составляет 99,75% в пределах апостериорного распределения для Decision_Tree.

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

#Pluck and modify underlying tibble from autoplot()
autoplot(acc_model_eval, type = "ROPE", size = 0.02) %>% 
  pluck("data") %>% 
  mutate(rank = row_number(-pract_equiv)) %>% 
  arrange(rank) %>% 
  separate(model, into = c("Recipe", "Model_Type"), sep = "_", remove = F, extra = "merge") %>% 
  ggplot(aes(x=rank, y= pract_equiv, color = Model_Type, shape = Recipe)) +
   geom_point(size = 5) +
   theme_minimal() +
   scale_colour_viridis_d() +
   labs(y= "Practical Equivalance", x = "Workflow Rank", size = "Probability of Practical Equivalence", color = "Model Type", title = "Practical Equivalence of Workflow Sets", subtitle = "Calculated Using an Effect Size of 0.02")

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

Два лучших кандидата, Boosted_Trees и Decision_Tree, находятся на противоположных концах спектра интерпретируемости. Boosted Trees - это метод черного ящика, и наоборот, деревья решений легче понять, и в этом случае обеспечивает наилучший результат, поэтому мы завершим наш рабочий процесс на основе использования Recipe1_Decision_Tree.

#Pull Best Performing Hyperparameter Set From workflow_map Object
best_result <- all_workflows %>% 
  pull_workflow_set_result("Recipe1_Decision_Tree") %>% 
  select_best(metric = "accuracy")
#Finalise Workflow Object With Best Parameters
dt_wf <- all_workflows %>% 
  pull_workflow("Recipe1_Decision_Tree") %>% 
  finalize_workflow(best_result)
#Fit Workflow Object to Training Data and Predict Using Test Dataset
dt_res <- 
  dt_wf %>%
  fit(training(loan_split)) %>% 
  predict(new_data = testing(loan_split)) %>% 
  bind_cols(loan_test) %>% 
  mutate(.pred_class = fct_infreq(.pred_class),
         Loan_Status = fct_infreq(Loan_Status))
#Calculate Accuracy of Prediction
accuracy(dt_res, truth = Loan_Status, estimate = .pred_class)

Наша окончательная модель смогла дать очень хороший результат с точностью 84,68% на тестовом наборе данных. Модель в целом хорошо работает при прогнозировании разрешений на выданные ссуды. Однако модель одобряет ссуды, против которых отказал банк. Это любопытный случай, очевидно, что больше кредитов, которые предлагает банк, означает более высокий потенциал дохода при более высокой подверженности риску. Это указывает на то, что набор данных содержит некоторые несоответствия, которые модель не может полностью различить и которые были «запутаны» на основании наблюдений в обучающем наборе.

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

#Fit and Extract Fit from Workflow Object
dt_wf_fit <- 
  dt_wf %>% 
  fit(training(loan_split))
dt_fit <- 
  dt_wf_fit %>% 
  pull_workflow_fit()
#Generate Decision Tree Plot Using rpart.plot package
rpart.plot::rpart.plot(dt_fit$fit)

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

Сразу заметим, что решение было принято исключительно на основании кредитной истории. Модель также допускает ошибку, предоставляя кандидатам неизвестную Credit_History (= 0) и отказывая кандидатам без нее (Credit_History = -1). Отмеченные выше несоответствия очевидны, не всем с кредитной историей выдают ссуду, но это, безусловно, помогает.

Заключение

Вы возвращаетесь к своему CDO и объясняете свои выводы. Их впечатлил результат точности, но теперь бизнес должен взвесить риск невыполнения обязательств по заявкам на выдачу разрешений с неизвестной кредитной историей и потенциальный доход, который в противном случае мог бы быть получен.

В этой статье была предпринята попытка объяснить возможности workflow_sets в экосистеме Tidymodels. Кроме того, мы изучили теорему «Нет бесплатного обеда», согласно которой ни одну модель нельзя назвать лучшей или лучшей, и поэтому лучше всего проверять множество моделей. Типы моделей, которые хорошо реагируют, также зависят от выполняемой разработки функций.

Спасибо, что прочитали мою вторую публикацию. Если вас интересуют модели Tidymodels, я опубликовал вводный проект, объясняющий каждый из основных пакетов при построении регрессионной модели.

Хотел бы поблагодарить Джули Слайдж и Макса Куна за их постоянные усилия по развитию экосистемы Tidymodels. Их текущую книгу можно просмотреть https://www.tmwr.org/, которая сыграла важную роль в моем понимании.