[ad_1]
Problema!
Os algoritmos de ML, embora sua eficácia proclamada na precisão preditiva possa ser suspeita, muitas vezes são a primeira ferramenta que um cientista de dados recorre ao lidar com um problema de previsão. Essa atitude não é a melhor na minha opinião, e geralmente prefiro um modelo estatístico / probabilístico em vez de uma caixa preta. No entanto, há situações em que se pede (leia-se: disse) para usar o último.
Solução?
Então, normalmente xgboost
é um modelo de ML a que se recorre com bastante frequência, devido ao seu bom desempenho logo que sai da caixa. Uma vez que tal modelo tenha sido treinado e testado, o analista se depara com a tarefa desafiadora de explicar o que o modelo está fazendo nos bastidores.
Problema com nossa solução!
Para ser capaz de explicar nosso modelo – que muitas vezes é mais complexo do que facilmente explicado – tendemos a usar técnicas como importância de recurso via permutação, dependência parcial e outras. Apesar de as 2 técnicas mencionadas fazerem uma suposição de independência de recursos que pode ser irreal (nesse ponto, elas não são diferentes de um modelo de regressão vanilla), tais visualizações são úteis como uma primeira tentativa de interpretação do modelo.
Enquanto trabalhava em um projeto, descobri que alguns ajustes foram necessários para poder usar o pdp
pacote para parcelas de dependência parcial com um xgboost
modelo construído a partir de tidymodels
. Vamos tentar isso com o código que Julia Silge usou em sua modelagem, apenas para mostrar rapidamente o procedimento.
# Read in the data from #tidytuesday repo
vb_matches %
transmute(
circuit,
gender,
year,
w_attacks = w_p1_tot_attacks + w_p2_tot_attacks,
w_kills = w_p1_tot_kills + w_p2_tot_kills,
w_errors = w_p1_tot_errors + w_p2_tot_errors,
w_aces = w_p1_tot_aces + w_p2_tot_aces,
w_serve_errors = w_p1_tot_serve_errors + w_p2_tot_serve_errors,
w_blocks = w_p1_tot_blocks + w_p2_tot_blocks,
w_digs = w_p1_tot_digs + w_p2_tot_digs,
l_attacks = l_p1_tot_attacks + l_p2_tot_attacks,
l_kills = l_p1_tot_kills + l_p2_tot_kills,
l_errors = l_p1_tot_errors + l_p2_tot_errors,
l_aces = l_p1_tot_aces + l_p2_tot_aces,
l_serve_errors = l_p1_tot_serve_errors + l_p2_tot_serve_errors,
l_blocks = l_p1_tot_blocks + l_p2_tot_blocks,
l_digs = l_p1_tot_digs + l_p2_tot_digs
) %>%
na.omit()
winners %
select(circuit, gender, year,
w_attacks:w_digs) %>%
rename_with(~ str_remove_all(., "w_"), w_attacks:w_digs) %>%
mutate(win = "win")
losers %
select(circuit, gender, year,
l_attacks:l_digs) %>%
rename_with(~ str_remove_all(., "l_"), l_attacks:l_digs) %>%
mutate(win = "lose")
vb_df %
mutate_if(is.character, factor)
# Split data for training, testing (even though we only really need the training for our purpose)
library(tidymodels)
set.seed(123)
vb_split %
set_engine("xgboost") %>%
set_mode("classification") %>%
fit(win ~ ., data = vb_train)
Por enquanto, tudo bem. Agora, queremos obter gráficos de dependência parcial. o partial
função de pdp
espera um xgb.Booster
objeto, junto com os dados de treinamento usados na modelagem.
pdp::partial(
xgb_spec$fit,
pred.var = "kills",
ice = T,
center = T,
plot = T,
alpha = .1,
plot.engine = "ggplot2",
train = vb_train %>% select(-win)
)
#> Error in predict.xgb.Booster(object, newdata = newdata, reshape = TRUE, : Feature names stored in `object` and `newdata` are different!
Hmm … Como os nomes são diferentes?
xgb_spec$fit$feature_names
#> [1] "circuitAVP" "circuitFIVB" "genderM" "genderW" "year" "attacks"
#> [7] "kills" "errors" "aces" "serve_errors" "blocks" "digs"
names(vb_train %>% select(-win))
#> [1] "circuit" "gender" "year" "attacks" "kills" "errors"
#> [7] "aces" "serve_errors" "blocks" "digs"
Bem, isso é compreensível, porque:
- xgboost espera que todas as variáveis categóricas sejam codificadas em um único momento
- tidymodels, de forma bastante útil, faz isso automaticamente para nós durante o ajuste
- uma vez que vb_train é deixado como está / incompatível com como o xgboost o vê, o
partial
a função sai com um erro
Podemos tentar adicionar uma etapa de codificação fictícia em nossa receita …
vb_recipe %
step_dummy(all_nominal(),-all_outcomes(), one_hot = T) %>%
prep()
vb_train_2
Os nomes são idênticos agora?
vb_train_2 %>% select(-win) %>% names()
#> [1] "year" "attacks" "kills" "errors" "aces" "serve_errors"
#> [7] "blocks" "digs" "circuit_AVP" "circuit_FIVB" "gender_M" "gender_W"
Opa. Parece que a única diferença agora está no sublinhado que separa uma variável categórica de seu valor em nosso conjunto de dados de trem, enquanto o modelo não tem nenhuma separação de sublinhado.
Felizmente, sem muitas mudanças, podemos resolver isso: o step_dummy
função tem um argumento naming
, que usa uma função dummy_names
. Esta função pode ser usada para indicar se um sublinhado ou qualquer outro separador deve ser adicionado para variáveis codificadas por um-hot.
Abaixo, vou criar outra versão desta função, sem nenhum separador, e passá-la para o step_dummy
função.
# Creating another version of the dummy names function
dummy_names_2 %
step_dummy(all_nominal(),-all_outcomes(), one_hot = T, naming = dummy_names_2) %>%
prep()
vb_train_2 % select(-win) %>% names() %in% xgb_spec$fit$feature_names)
#>[1] TRUE
Perfeito! Podemos gerar um gráfico de dependência parcial agora?
pdp::partial(
xgb_spec$fit,
pred.var = "kills",
ice = T,
center = T,
plot = T,
alpha = .1,
plot.engine = "ggplot2",
train = vb_train_2 %>% select(-win)
)
#> Error in predict.xgb.Booster(object, newdata = newdata, reshape = TRUE,: Feature names stored in `object` and `newdata` are different!
Isso foi estranho, então eu tive que fazer um pouco de depuração com traceback()
para descobrir por que isso estava acontecendo. O problema parecia estar ocorrendo na seguinte função dentro partial
.
Parece que identical()
espera que os vetores de caracteres também sigam a mesma ordem, além de serem apenas os mesmos vetores:
a1 [1] "a" "b" "c" "d" "e"
a2 [1] "c" "a" "d" "b" "e"
identical(a1, a2)
#> FALSE
a3 [1] "a" "b" "c" "d" "e"
identical(a1, a3)
#> TRUE
Portanto, se apenas reorganizarmos nossas colunas nos dados de treinamento de acordo com os nomes dos recursos do modelo xgboost, isso deve funcionar …
data.table::setcolorder
pdp::partial(
xgb_spec$fit,
pred.var = "kills",
ice = T,
center = T,
plot = T,
alpha = .1,
plot.engine = "ggplot2",
train = vb_train_2 %>%
select(-win) %>%
select(match(xgb_spec$fit$feature_names, vb_train_2 %>% select(-win) %>% names()))
)
Finalmente!
Relacionados
[ad_2]