Вторая часть моего анализа сравнивает простые объяснимые модели машинного обучения со сложными необъяснимыми моделями.

Цель

Первый анализ, который я сделал, рассматривал сравнение различных алгоритмов машинного обучения (ML) для задачи регрессии, и его можно найти здесь: https://medium.datadriveninvestor.com/comparing-simple-explainable-machine-learning-models-to- сложные модели черного ящика для регрессии d8634c275f37

В этом анализе я сравню модели ML по проблеме классификации. Я буду использовать набор данных Titanic — Machine Learning from Disaster (https://www.kaggle.com/competitions/titanic/data) и попытаюсь точно предсказать, какие пассажиры Титаника выживут.

Исследование данных

# Load Libraries #
library(dplyr)
library(tidyr)
library(glmnet)
library(caret)
library(rpart)
library(randomForest)
library(gbm)
library(rsample)
library(keras)
library(plotly)
library(readr)
library(reticulate)
library(naivebayes)
# Read in Data #
train_data <- read.csv(".../train.csv",
                       na.strings = c("", "NA"))
train_data$Description <- "Train"
test_data <- read.csv(".../test.csv",
                      na.strings = c("", "NA"))
test_data$Description <- "Test"
data <- dplyr::bind_rows(train_data, test_data)
head(data)
dim(data)

# Check for Nulls #
colSums(is.na(data))

Мы можем игнорировать 418 нулей в столбце «Выживший», потому что это то, что мы пытаемся предсказать на основе тестовых данных. 1014 пустых значений в столбце «Каюта» представляют 77% от общего числа 1309 строк, что является таким большим числом, что мы можем полностью удалить этот столбец. Это оставляет пустые значения в столбцах «Возраст», «Тариф» и «Посадка», которые необходимо будет обработать. Как я упоминал в части 1, я обычно удаляю строки в обучающих данных, которые содержат пустые значения, но, поскольку я объединил свои обучающие и тестовые данные, я просто вменю пустые значения с помощью медианы для «Возраст» и «Проезд» и режим для «Отправлено». ».

# Impute missing Values #
data$Age[is.na(data$Age)] <- median(na.omit(data$Age))
data$Fare[is.na(data$Fare)] <- median(na.omit(data$Fare))
data$Embarked[is.na(data$Embarked)] <- "S"
colSums(is.na(data))

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

# Check column class type #
str(data)

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

# Set String Columns to Factor #
data$Sex <- as.factor(data$Sex)
data$Embarked <- as.factor(data$Embarked)

Теперь мы можем удалить столбцы «Имя», «Билет» и «Каюта».

# Remove unnecessary Columns #
df <- data[,-c(4, 9, 11)]
head(df)

Разработка функций

Горячее кодирование

Теперь мы можем выполнить однократное кодирование наших факторных переменных.

# One hot encode Factor Columns #
xfactors <- model.matrix(df$PassengerId ~ df$Sex + df$Embarked)[,-1]
# Declare x Variables #
x <- data.frame(df$Description, df$Survived, df$Pclass, df$Age, df$SibSp, df$Parch, df$Fare, xfactors)
head(x)

Настройка данных обучения и тестирования

# Set up Training and Testing Data #
train <- dplyr::filter(x, grepl("Train", x$df.Description))
model_train <- train[,-1] # Remove the "Description" column
test <- dplyr::filter(x, grepl("Test", x$df.Description))
model_test <-test[, c(-1, -2)] # Remove the "Description" and "Survived" columns

Простые, объяснимые модели машинного обучения

Логистическая регрессия

Мы начнем сравнение моделей с простой и понятной модели логистической регрессии.

# Logistic Regression #
log_model <- glm(df.Survived ~ ., data = model_train, 
family = 'binomial')
# Understand Summary and Coefficients #
summary(log_model)
exp(coefficients(log_model))

Мы можем использовать коэффициенты, чтобы понять, что делает модель логистической регрессии:

  • pclass: 1 (1-й класс), 2 (2-й класс) и 3 (3-й класс). Модель логарифмической регрессии говорит, что при повышении на 1 класс ваши шансы на выживание уменьшаются на 67% (1–0,333). Ака, чем ты богаче, тем больше у тебя шансов на выживание.
  • Возраст. Согласно модели логарифмической регрессии, с возрастом на 1 год ваши шансы на выживание снижаются на 4% (1–0,962). Ака, чем ты моложе, тем больше шансов на твое выживание.
  • sibsp: числобратьев, сестер и супругов на борту Титаника. Модель логарифмической регрессии говорит, что по мере увеличения количества братьев, сестер и супругов на борту «Титаника» на 1 человека ваши шансы на выживание уменьшаются на 28% (1–0,722). Ака, чем меньше у вас братьев и сестер + супруга на Титанике, тем выше ваши шансы на выживание.
  • parch:количество родителей и детей на борту Титаника. Модель логарифмической регрессии говорит, что по мере увеличения количества родителей и детей на борту «Титаника» на 1 человека ваши шансы на выживание уменьшаются на 9% (1–0,913). Ака, чем меньше родителей и детей на борту Титаника, тем выше ваши шансы на выживание.
  • Тариф: пассажирский тариф. Модель логарифмической регрессии говорит, что по мере увеличения вашего тарифа на 1 доллар ваши шансы на выживание увеличиваются на 0,2% (1,002–1). Ака, чем больше вы потратили на проезд (чем вы богаче), тем больше ваши шансы на выживание.
  • Пол. Модель логарифмической регрессии говорит о том, что у мужчин шансы на выживание ниже на 93% (1–0,065) по сравнению с женщинами. Ака, если ты мужчина, у тебя проблемы.
  • отправлен: порт отправления. Модель логарифмической регрессии говорит, что если вы отправились в Queenstown, ваши шансы на выживание снизятся на 6 % (1 – 0,941), а если вы отправились в Саутгемптоне, у вас снизится на 33 % (1–0,941). –0,666) в шансах на выживание по сравнению с тем, если бы вы отправились в Cherbourg. Ака, если вы не отправились в Шербуре, я надеялся, что вы отправились в Куинстаун, иначе у вас, вероятно, будут проблемы.

Модели логистической регрессии великолепны, потому что с помощью 2/3 строк кода мы можем определить, что молодые, богатые женщины из высшего общества, с небольшой семьей на борту, которые отправились на борт в Шербуре, с наибольшей вероятностью выживут на Титанике. Это дает отличное понимание, но насколько хорошо эта простая модель предсказывает на тестовом наборе?

# Predict on Test Data #
logistic_regression_predict <- predict(log_model, as.data.frame(model_test), type="response")

Модель логистической регрессии смогла предсказать выживание Титаника с точностью 76,6%. Простота создания этой модели, генерируемые ею идеи и точность, которую она дает на невидимых данных, действительно делают ее отличной моделью, особенно в бизнес-среде, где ключевыми факторами являются объяснимость и точность модели.

Древо решений

Далее давайте рассмотрим еще одну простую объяснимую модель — дерево решений:

# Decision Tree #
# Model Tuning #
hyper_grid <- expand.grid(
  minsplit = seq(5, 20, 1),
  maxdepth = seq(8, 15, 1)
)
models <- list()
for (i in 1:nrow(hyper_grid)) {
  
  # get minsplit, maxdepth values at row i
  minsplit <- hyper_grid$minsplit[i]
  maxdepth <- hyper_grid$maxdepth[i]
  
  # train a model and store in the list
  models[[i]] <- rpart(
    formula = df.Survived ~ .,
    data    = model_train,
    method  = "class",
    control = list(minsplit = minsplit, maxdepth = maxdepth)
  )
}
# function to get optimal cp
get_cp <- function(x) {
  min    <- which.min(x$cptable[, "xerror"])
  cp <- x$cptable[min, "CP"] 
}
# function to get minimum error
get_min_error <- function(x) {
  min    <- which.min(x$cptable[, "xerror"])
  xerror <- x$cptable[min, "xerror"] 
}
hyper_grid %>%
  mutate(
    cp    = purrr::map_dbl(models, get_cp),
    error = purrr::map_dbl(models, get_min_error)
  ) %>%
  arrange(error) %>%
  top_n(-5, wt = error)

# Full Model #
tree_model <- rpart(
  formula = df.Survived ~ .,
  data    = model_train,
  method  = "class",
  control = list(minsplit = 10, maxdepth = 10, cp = 0.01)
)
# Plot #
plot(tree_model, uniform = TRUE,
     main = "Titanic Survival Decision Tree")
text(tree_model, use.n = TRUE, cex = .7)

Деревья решений относительно легко создать, и они создают великолепно выглядящие визуальные эффекты, которые позволяют конечным пользователям начинать с вершины и проходить каждое разделение, чтобы определить окончательную классификацию, в данном случае выживет ли пассажир. Например, первый раздел спрашивает нас, является ли пассажир мужчиной. Если да, то идем налево, если нет — направо. Я не буду проходить все дерево, но мы видим, что оно предсказывает, что все мужчины с возрастом ≥ 6,5 умрут, а все женщины 1-го и 2-го класса выживут. Давайте определим, насколько точно дерево решений предсказывает невидимые данные:

# Predict on Test Set #
decision_tree_predict <- predict(tree_model, as.data.frame(model_test), method = "class")

Простая, легко объяснимая модель дерева решений была чрезвычайно точной и позволяла предсказать, кто выживет на «Титанике» с точностью 77,5%, превосходя модель логистической регрессии.

Сложные модели черного ящика

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

Предварительная обработка данных

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

# Standardize Data #
numeric_x <- data.frame(df$Age, df$Fare)
scaled_x <- scale(numeric_x)
# check that we get mean of 0 and sd of 1
colMeans(scaled_x)
apply(scaled_x, 2, sd)

standardized_x <- data.frame(x$df.Description, x$df.Survived, x$df.Pclass, x$df.SibSp, x$df.Parch, 
                             scaled_x, xfactors)
head(standardized_x)

# Set up Standardized Training and Testing Data #
standardized_train <- dplyr::filter(standardized_x, grepl("Train", standardized_x$x.df.Description))

standardized_model_train <- standardized_train[,-1]

standardized_test <- dplyr::filter(standardized_x, grepl("Test", standardized_x$x.df.Description))

standardized_model_test <- standardized_test[, c(-1, -2)]

# Declare x and y variables #
standardized_y <- as.factor(standardized_model_train$x.df.Survived)
standardized_x <- standardized_model_train[,-1]
standardized_data <- cbind(standardized_y, standardized_x)

Наивный Байес

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

# Naive Bayes Model #
naive_model <- naive_bayes(standardized_y ~ ., data = standardized_data, usekernel = T)
# Predict on Test Data #
naive_predict <- predict(naive_model, standardized_model_test)

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

Случайный лес

Мне очень нравятся модели Random Forest. Их немного легче настроить, чем модели с градиентным усилением, и их сложнее перенастроить. С моделями Random Forest я настраиваю только 2 параметра; количество деревьев и min.node.size (алгоритм прекращает разбиение, когда размер узла меньше или равен min.node.size).

# Random Forest #
# Find Optimal Number of Trees #
optimal_tree <- randomForest(
  formula = standardized_y ~ .,
  data    = standardized_data,
  ntree   = 5000,
  proximity=TRUE
)
plot(optimal_tree)

Поскольку размер нашей выборки невелик, поэтому нам не нужно беспокоиться о нехватке вычислительной мощности, я решил использовать 5000 деревьев. Путем настройки гиперпараметров я обнаружил, что min.node.size, равный 7, является оптимальным.

# Full Model #
tune_model <- randomForest(
  formula         = standardized_y ~ ., 
  data            = standardized_data, 
  ntree       = 5000,
  min.node.size   = 7
)
# Predict on Test Data #
Pred <- predict(tune_model, 
                newdata         = standardized_model_test, 
                ntree       = 5000,
                min.node.size   = 7
)

Модель случайного леса была наиболее точной, успешно предсказывая выживших на Титанике с точностью 78,5%, но недостаточной, чтобы оправдать ее использование по сравнению с простой и объяснимой моделью.

Машина с градиентным усилением (GBM)

Сначала мы должны настроить наши данные для прогнозов классификации с использованием GBM в R:

# GBM #
# set up y variable for GBM #
standardized_data$standardized_y <- as.numeric(standardized_data$standardized_y)
gbm_data = transform(standardized_data, y=standardized_y-1)[,-1]

Модели с градиентным усилением работают хорошо, но их сложно настроить. Я всегда использую значения по умолчанию n.minobsinnode = 10 и bag.fraction = 0,5. Я использую значение сжатия 0,001 для небольших наборов данных (до ~ 100 тыс. строк), 0,01 для средних наборов данных (от ~ 100 тыс. до 1 млн строк) и 0,1 для больших наборов данных (~ > 1 млн строк). В этом случае, поскольку это небольшой набор данных, я буду использовать значение сжатия 0,001. Для глубины взаимодействия я нахожу квадратный корень из числа моих предикторов, округленный до ближайшего целого числа и не превышающего 5, чтобы предотвратить переоснащение:

# Find Max Interaction Depth #
floor(sqrt(NCOL(standardized_model_test)))

Наконец, я использую оценку OOB для определения оптимального количества деревьев:

# Find Optimal n.trees #
tree_mod <- gbm(
  formula = y ~ .,
  distribution = "bernoulli",
  data = gbm_data,
  shrinkage = 0.001,
  interaction.depth = 2,
  n.minobsinnode = 10,
  bag.fraction = 0.5, #Default
  n.trees = 10000, #10,000 is preferable
  n.cores = NULL, # will use all cores by default
  verbose = FALSE
)
best.iter <- gbm.perf(tree_mod, method="OOB", plot.it=TRUE, oobag.curve=TRUE, overlay=TRUE)
print(best.iter)

GBM <- gbm(y ~ .,
           distribution = "bernoulli",
           data = gbm_data,
           n.trees = 4122,  
           interaction.depth = 2, 
           shrinkage = 0.001,
           n.minobsinnode = 10, 
           bag.fraction = 0.5, 
           train.fraction = 1, 
           n.cores = NULL, 
           verbose = FALSE
)
# Predict on Test Set #
GBM_Prediction <- predict(GBM, standardized_model_test, 
                          n.trees = 4122,
                          distribution = "bernoulli",
                          shrinkage = 0.001,
                          interaction.depth = 2,
                          n.minobsinnode = 10,
                          bag.fraction = 0.5, #Default
                          train.fraction = 1,
                          n.cores = NULL, 
                          verbose = FALSE,
                          type="response"
)

Несмотря на свою сложность, модель GBM едва превзошла простую и объяснимую модель дерева решений, точно предсказывая выживших на Титанике с точностью 78%.

Полученные результаты

Более сложные, необъяснимые модели Random Forest и GBM едва превзошли простые и объяснимые модели логистической регрессии и дерева решений. Не было бы причин использовать сложные модели черного ящика вместо более простых для создания и производства моделей, которые обеспечивают понимание бизнеса.

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

В стороне: кто жульничает в бесплатном конкурсе Kaggle?

Моей лучшей моделью была модель Random Forest, которая предсказывала выживших на Титанике с точностью 78,5%. Этого было достаточно для 1889 мест из 14 248 участников на момент написания статьи. Эта оценка находится в верхнем 13-м процентиле, что означает, что моя модель превзошла 87% тех, которые были введены. Это заставило меня взглянуть на высшие баллы, и мне стало очень грустно. 100 лучших участников предсказывали со 100% точностью… Очевидно, что в ML это невозможно, и это действительно заставило меня задаться вопросом, кто будет жульничать в бесплатном соревновании Kaggle без призов за лучший результат? Вы не могли хвастаться перед специалистами по обработке данных или менеджерами по найму, что создали идеальную модель на конкурсе Kaggle, потому что они рассмеялись бы вам в лицо. я не понимаю…

Подпишитесь на DDIntel Здесь.

Присоединяйтесь к нашей сети здесь: https://datadriveninvestor.com/collaborate