Escrevi esse post como parte de um processo para entender as possibilidades de modelagem com a abordagem utilizada no tidymodels. As principais referências para esse post são dadas pelo vídeo da Julia Silge e uma apresentação do Max Kuhn.
Vamos comparar os desempenhos preditivos dos modelos KNN e regressão logística utilizando os dados Wine Quality Data Set. Esses dados apresentam diversas medidas obtidas para os vinhos além de escores de qualidade.
Primeiro, vamos carregar os pacotes, fazer a leitura e recodificação das variáveis.
library(tidymodels)library(tidyverse)library(tune)url <-"https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv"dados <-read.csv2(url, dec =".")dados$quality <-ifelse(dados$quality >=6 ,"Alto", "Baixo")head(dados)
O primeiro passo é separar os dados em conjunto de treinamento e de teste. Para isso, utilizaremos a função initial_split do pacote rsample. O primeiro argumento dessa funçao será dado pelo banco dados que particionaremos e o segundo argumento (prop) indicará a proporção dos dados que será utilizada para o treinamento.
Note que, ao imprimir o objeto dados_split, recebemos uma indicação de como foi feita a partição. Assim, temos 3.919 observações para treinamento, 979 para teste e o total de observações é dado por 4.898 vinhos. Portanto, podemos definir explicitamente os conjuntos de treinamento e teste da seguinte forma:
O fluxo utilizado com o pacote recipes pode ser definido pela seguinte figura (retirada da apresentação do Max Kuhn).
Primeiro definimos a receita/recipe, ou seja, como será dado o processamento dos dados, depois fazemos a preparação/prepare que especifica as estimavas necessárias para cada passo (por exemplo, para padronização ou alguma transformação como Box-Cox) e, por fim, aplicamos a receita com as estimativas utilizando as funções bake ou juice.
Agora podemos preparar a receita com o conjunto de treinamento definido. Uma receita define uma série de transformações que aplicaremos aos dados. Nesse caso criaremos uma receita de modelo com a variável quality como resposta em função das demais variáveis. Após a definição da fórmula do modelo, faremos a padronização de todas as variáveis numéricas com a função step_normalize. Além da normalização, podemos utilizar diversas tranformações. A seguir listamos alguns exemplos:
step_BoxCox: aplica transformação de Box-Cox
step_discretize: converte os dados númericos em fatores com níveis de tamanhos amostrais aproximadamente iguais
step_dummy: converte variáveis nominais ou de fatores em variáveis indicadoras
step_log: aplica logaritmo
step_meaninput, step_medianinput, step_modeinput: imputa os dados faltantes por uma das medidas (média, mediana e moda) do conjunto de treinamento
step_normalize: padroniza as variáveis para ter média zero e desvio padrão igual a um
step_YeoJohnson: transforma as variáveis de acordo com a transformação de Yeo-Johnson
Após definir a fórmula utilizada e a padronização dos dados, utilizaremos a função prep para aplicar as transformações a um determinado conjunto de dados. Nesse caso, aplicaremos no conjunto de treinamento (dados_tr).
Recipe
Inputs:
role #variables
outcome 1
predictor 11
Training data contained 3918 data points and no missing data.
Operations:
Centering and scaling for fixed.acidity, volatile.acidity, citric.acid, r... [trained]
Para obter os dados após a aplicação da receita, podemos utilizar os seguintes comandos:
A função set_engine especifica qual pacote ou sistema será utilizado para ajustar o modelo. Aqui utilizaremos o kknn e glm. Além disso, o modo do modelo é classificação. Poderíamos utilizar o modo regression, caso a variável resposta fosse numérica.
Uma vez estabelecida a especificação do modelo, podemos utilizar a função mc_cv (Monte Carlo Cross-Validation) para fazer várias amostras de treinamento e teste com base no conjunto de dados de treinamento processado. O argumento prop indica a proporção utilizada para modelagem. O padrão da função é repetir esse processo 25 vezes. O resultado é um tibble com as indicações do número de observações para treinamento, teste e total na coluna splits e uma coluna id atribuindo um código para cada reamostragem.
# Monte Carlo cross-validation (0.8/0.2) with 25 resamples
# A tibble: 25 × 2
splits id
<list> <chr>
1 <split [3134/784]> Resample01
2 <split [3134/784]> Resample02
3 <split [3134/784]> Resample03
4 <split [3134/784]> Resample04
5 <split [3134/784]> Resample05
6 <split [3134/784]> Resample06
7 <split [3134/784]> Resample07
8 <split [3134/784]> Resample08
9 <split [3134/784]> Resample09
10 <split [3134/784]> Resample10
# … with 15 more rows
Após a criação de várias amostras de treinamento e teste no objeto validation_splits, podemos utilizar a função fit_resamples do pacote tune. Com isso, é possível ajustar os modelos knn e logístico com as especificações guardadas nos objetos knn_spec e logistic_spec de acordo com a seguinte função:
knn_res <-fit_resamples(knn_spec, quality ~ ., validation_splits, control =control_resamples(save_pred =TRUE))knn_res
Note que agora, além das colunas splits e id, temos as colunas .metrics, .notes e .predictions. A coluna .metrics apresenta as medidas de desempenho calculadas para cada conjunto gerado e a coluna .predictions guarda a probabilidade predita para cada classe e a classificação observada (as colunas com as probabilidades de cada classe serão nomeadas de acordo com o padrão .pred_classe)
Além da medida resumo, podemos utilizar as previsões obtidas para os 25 conjuntos de teste gerados para fazer uma curva ROC. Para isso, usamos o comando unnest, definimos a curva roc com o comando roc_curve considerando o valor observado como primeiro argumento e a probabilidade do vinho apresentar alta qualidade no segundo argumento. Após esses passos, utilizamos a função autoplot.
Após o estudo dos modelos com os conjuntos de treinamentos obtidos por reamostragem, podemos ajustá-los, agora considerando o conjunto de treinamento completo, com a especificação dada por knn_spec/logistic_spec. Assim, teremos
knn_fit <- knn_spec %>%fit(quality ~ ., data =juice(dados_recipe))knn_fit
parsnip model object
Call:
kknn::train.kknn(formula = quality ~ ., data = data, ks = min_rows(5, data, 5))
Type of response variable: nominal
Minimal misclassification: 0.1993364
Best kernel: optimal
Best k: 5
logistic_fit <- logistic_spec %>%fit(quality ~ ., data =juice(dados_recipe))logistic_fit
parsnip model object
Call: stats::glm(formula = quality ~ ., family = stats::binomial, data = data)
Coefficients:
(Intercept) fixed.acidity volatile.acidity
-0.915911 0.013944 0.647401
citric.acid residual.sugar chlorides
-0.009177 -0.730209 -0.014389
free.sulfur.dioxide total.sulfur.dioxide density
-0.098973 0.013988 0.607163
pH sulphates alcohol
-0.113490 -0.180647 -1.030140
Degrees of Freedom: 3917 Total (i.e. Null); 3906 Residual
Null Deviance: 5000
Residual Deviance: 3964 AIC: 3988
Assim, para avaliar a AUC obtida com o conjunto de teste, utilizamos:
knn_fit %>%predict(new_data = test_proc, type ="prob") %>%mutate(truth = test_proc$quality, model ="knn") %>%bind_rows(logistic_fit %>%predict(new_data = test_proc, type ="prob") %>%mutate(truth = test_proc$quality, model ="logistic")) %>%group_by(model) %>%roc_auc(truth, .pred_Alto)
Para fazer a curva ROC, poderíamos utilizar diretamente:
knn_fit %>%predict(new_data = test_proc, type ="prob") %>%mutate(truth = test_proc$quality, model ="knn") %>%bind_rows(logistic_fit %>%predict(new_data = test_proc, type ="prob") %>%mutate(truth = test_proc$quality, model ="logistic")) %>%group_by(model) %>%roc_curve(truth, .pred_Alto) %>%autoplot()
O fluxo apresentado acima pode não ser exatamente o fluxo utilizado num processo de modelagem, mas apresenta as principais formas de aplicação dessas abordagens no contexto do tidymodels.
Caso tenha alguma crítica, sugestão ou comentário, me envie uma mensagem!