Como eu gostaria que alguém me explicasse SHAP values

Conteúdos deste artigo:

Já passou por dificuldades na hora de interpretar as decisões de uma IA? O SHAP foi criado para te ajudar a vencer essas questões. A sigla significa SHapley Additive exPlanations, um método relativamente recente (menos de 10 anos) que busca explicar as decisões dos modelos de inteligência artificial de uma forma mais direta e intuitiva, fugindo de soluções “caixa preta”.

Seu conceito é baseado na teoria dos jogos com uma matemática bem robusta. Porém, para utilizar esta metodologia no nosso dia a dia, não é necessário o seu completo entendimento matemático. Para quem deseja aprender de forma mais profunda como é construída a teoria recomendo a leitura desta publicação que está em inglês.

Neste texto, irei demonstrar interpretações mais práticas sobre o SHAP, assim como compreender seus resultados, tudo isso em português! Sem mais delongas, vamos colocar a mão na massa! Para isso, vamos precisar de um modelo para interpretar, certo?

Usarei como base o modelo construído no meu notebook (indicado pelo link anterior). Trata-se de um modelo baseado em árvores para predição binária de Diabetes. Ou seja, o modelo prevê pessoas que possuem esta patologia. Para construção de toda esta análise, foi utilizada a biblioteca shap, mantida inicialmente pelo autor do artigo que originou o método e agora por uma vasta comunidade.

Primeiramente, vamos calcular os valores SHAP seguindo os tutoriais do pacote:

# Biblioteca
import shap

# Cálculo do SHAP - Definindo explainer com características desejadas
explainer = shap.TreeExplainer(model=model)

# Cálculo do SHAP
shap_values_train = explainer.shap_values(x_train, y_train)

Note que eu defini um TreeExplainer. Isso ocorreu porque o meu modelo é  baseado em árvore, logo, a biblioteca possui um explainer específico para esta família de modelos. Além disso, até o momento, o que fizemos foi:

  • Definir um explainer com os parâmetros desejados (existem uma diversidade de parâmetros para TreeExplainer, recomendo checar as opções na biblioteca).
  • Calcular os valores SHAP para o conjuntos de trein.

O que são os valores SHAP?

Com o conjunto de valores SHAP já definidos para o nosso conjunto de treinamento, podemos avaliar como cada valor de cada variável influenciou no resultado alcançado pelo modelo preditivo. Em nosso caso, estaremos avaliando os resultados dos modelos em termos de probabilidade, ou seja, a porcentagem X que o modelo apresentou para dizer se a classe correta é 0 (não tem diabetes) ou 1 (tem diabetes). Vale ressaltar que isso pode variar de modelo para modelo: Caso você use um modelo XGBoost, provavelmente seu resultado padrão não será em termos de probabilidade como é o da random forest do pacote sklearn.

Para tornar o valor em termos de probabilidade você pode defini-lo através do TreeExplainer, por meio dos parâmetros.

Mas, a pergunta que não quer calar é: Como posso interpretar os valores de SHAP? Para isso, vamos calcular o resultado de probabilidade de predição do conjunto de treino para uma amostra qualquer que previu valor positivo:

# Probabilidade de predição do conjunto de treino
y_pred_train_proba = model.predict_proba(x_train)

# Vamos agora selecionar um resultado que previu como positivo
print('Probabilidade do modelo prever negativo -',100*y_pred_train_proba[3][0].round(2),'%.')
print('Probabilidade do modelo prever positivo -',100*y_pred_train_proba[3][1].round(2),'%.')
  • Probabilidade do modelo prever negativo – 17.0 %.
  • Probabilidade do modelo prever positivo – 83.0 %.

O código acima gerou a probabilidade dada pelo modelo para as duas classes. Vamos visualizar agora os valores de SHAP para aquela amostra de acordo as classes possíveis:

# Valores de SHAP para essa amostra na classe positiva
shap_values_train[1][3]
array([-0.01811709,  0.0807582 ,  0.01562981,  0.10591462, 0.11167778, 0.09126282,  0.05179034, -0.10822825])
# Valores de SHAP para essa amostra na classe negativa
shap_values_train[0][3]
array([ 0.01811709, -0.0807582 , -0.01562981, -0.10591462, -0.11167778,-0.09126282, -0.05179034,  0.10822825])
Fórmula simplificada para o SHAP, onde i é referente a categoria que aqueles valores representam (em nosso caso, categoria 0 ou 1).

Vamos verificar isso em código:

# Somatório dos valores SHAP para classe positiva
print('Somatório SHAP para classe negativa nesta amostra:',100*y_pred_train_proba[3][0].round(2)-100*expected_value[0].round(2))
print('Somatório SHAP para classe positiva nesta amostra:',100*y_pred_train_proba[3][1].round(2)-100*expected_value[1].round(2))
Somatório SHAP para classe negativa nesta amostra: -33.0
Somatório SHAP para classe positiva nesta amostra: 33.0

E como lição de home office, fica a seguinte indagação: O valor do somatório de valores SHAP de uma classe adicionado ao valor de base daquela classe dará exatamente o valor de probabilidade do modelo encontrado no início dessa seção!

Note que os valores SHAP bateram com o resultado apresentado anteriormente. Mas, e os valores SHAP individualmente, o que eles representam? Para isso vamos usar mais código, usando como referência a classe positiva:

for col, vShap in zip(x_train.columns, shap_values_train[1][3]):
    print('###################', col)
    print('Valor SHAP associado:',100*vShap.round(2))
################### Pregnancies
Valor SHAP associado: -2.0
################### Glucose
Valor SHAP associado: 8.0
################### BloodPressure
Valor SHAP associado: 2.0
################### SkinThickness
Valor SHAP associado: 11.0
################### Insulin
Valor SHAP associado: 11.0
################### BMI
Valor SHAP associado: 9.0
################### DiabetesPedigreeFunction
Valor SHAP associado: 5.0
################### Age
Valor SHAP associado: -11.0

Aqui avaliamos para a amostra 3 os valores SHAP referentes à classe positiva. Valores SHAP positivos como da Glucose, BloodPressure, SkinThickness, BMI e DiabetesPedigreeFunction influenciaram o modelo na previsão da classe positiva como correta. Ou seja, valores positivos implicam uma tendência para a categoria de referência.

Já os valores negativos como Age e Pregnancies, buscam indicar que a classe verdadeira é a negativa (a oposta). Neste exemplo, se ambas fossem também positivas o nosso modelo resultaria em uma predição de 100% para a classe positiva, porém, como isso não aconteceu, eles representam os 17% que são contra a escolha da classe positiva.

Em resumo, você pode pensar no SHAP em contribuições para o modelo definir entre uma das classes. Assim:

  • Neste caso, o somatório dos valores SHAP não podem ultrapassar de 50%.
  • Valores positivos considerando uma classe de referência indicam ser favoráveis aquela classe na predição.
  • Valores negativos indicam que a classe correta não é aquela de referência, mas sim outra classe.

    Além disso, podemos quantificar em termos de porcentagem a contribuição de cada variável a resposta final daquele modelo dividindo pelo máximo de contribuição possível, neste caso, 50%:

for col, vShap in zip(x_train.columns, shap_values_train[1][3]):
    print('###################', col)
    print('Valor SHAP associado:',100*(100*vShap.round(2)/50).round(2),'%')
################### Pregnancies
Valor SHAP associado: -4.0 %
################### Glucose
Valor SHAP associado: 16.0 %
################### BloodPressure
Valor SHAP associado: 4.0 %
################### SkinThickness
Valor SHAP associado: 22.0 %
################### Insulin
Valor SHAP associado: 22.0 %
################### BMI
Valor SHAP associado: 18.0 %
################### DiabetesPedigreeFunction
Valor SHAP associado: 10.0 %
################### Age
Valor SHAP associado: -22.0 %

Aqui, conseguimos verificar que Insulin e SkinThickness e BMI tiveram juntas uma influência de 62%. Podemos perceber também que a variável Age consegue anular o impacto de SkinThickness ou Insulin neste amostra.

Visualização geral

Agora que vimos muitos números, vamos para as visualizações. Em minha percepção, um dos motivos do SHAP ter sido tão difundido é a qualidade de suas visualizações, que em minha opinião, superam as do LIME.

Vamos fazer uma avaliação geral do conjunto de treino em relação a predição do nosso modelo para compreender o que está acontecendo no meio de tantas árvores:

# Gráfico 1 - Contribução das variáveis
shap.summary_plot(shap_values_train[1], x_train, plot_type="dot", plot_size=(20,15));
Gráfico 1: Gráfico Resumo para os valores de SHAP 

Avaliação do Gráfico 1

Antes de avaliar o que este gráfico quer nos dizer sobre o nosso problema, precisamos entender cada característica nele presente:

  • O eixo Y são as variáveis do nosso modelo em ordem de importância (o SHAP ordena isso de forma padrão, você pode escolher outra ordem através dos parâmetros).
  • O eixo X são os valores SHAP. Como a nossa referência é a categoria positiva, valores positivos indicam um suporte para a categoria de referência (contribui para o modelo responder categoria positiva no final) e valores negativos indicam um suporte à categoria oposta (neste caso de classificação binária, seria a classe negativa).
  • Cada ponto no gráfico representa uma amostra. Cada variável possui 800 pontos distribuídos horizontalmente (visto que temos 800 amostras, logo cada amostra tem um valor para aquela variável). Note que essas nuvens de pontos em algum momento se expande verticalmente. Isso ocorre dado a densidade de valores daquela variável em relação aos valores SHAP.
  • Finalmente, as cores representam o aumento/diminuição do valor da variável. Tons mais vermelhos são valores altos e tons azulados são valores mais baixos.

De forma geral, iremos buscar variáveis que:

  • Tenham uma divisão bem clara de cores, ou seja, vermelho e azul em lugares opostos. Essa informação mostra que elas são bons previsores, afinal apenas ao mudar seu valor o modelo consegue verificar de forma mais simples sua contribuição para uma classe.
  • Associado a isso, quanto maior o intervalo de alcance de valores SHAP, melhor será aquela variável para o modelo. Vamos considerar Glucose, que apresenta em algumas situações valores de SHAP em torno de 0,3, ou seja, 30% de contribuição para o resultado do modelo (isso porque o máximo que qualquer variável pode atingir é 50%).

As variáveis Glucose e Insulin apresentam essas duas características mencionadas. Agora, note a variável BloodPressure: No geral, ela é uma variável confusa visto que seus valores SHAP ficam em torno de 0 (contribuições fracas) e com uma clara mistura de cores. Além disso, você não consegue ver uma tendência do aumento/diminuição dessa variável na resposta final. Vale destacar também a variável Pregnancies que não possui um intervalo tão grande como Glucose, porém demonstra uma divisão clara de cores.

Por meio desse gráfico, você consegue ter um panorama geral de como seu modelo chega em suas conclusões a partir do conjunto de treinamento e das variáveis. O gráfico seguinte mostra uma contribuição média do gráfico que apresentamos anteriormente:

# Gráfico 2 - Contribução de Importância das variáveis
shap.summary_plot(shap_values_train[1], x_train, plot_type="bar", plot_size=(20,15));
Gráfico 2: Gráfico de Importâncias das variáveis a partir dos valores SHAP

Avaliação do Gráfico 2

Basicamente como o próprio título do eixo X demonstra, cada barra representa a média dos valores SHAP em módulo, assim, avaliamos a contribuição média das variáveis nas respostas do modelo. Considerando a Glucose, vemos que sua contribuição média gira em torno de 12% para a categoria positiva.

Este gráfico pode ser feito em relação a qualquer uma das categorias (optei pela positiva) ou, ainda, todas elas. Ela configura como um ótimo gráfico para substituir o primeiro em explicações para gestores ou pessoas mais ligadas na área de negócio dado sua simplicidade.

Interpretação de predição da amostra

Além de visualizações gerais, o SHAP proporciona análises mais individuais por amostras. Gráficos como esses são interessantes para apresentar algum resultado em especial. Por exemplo, digamos que esteja trabalhando em um problema de churn de clientes e você quer mostrar como seu modelo compreendeu a saída do maior cliente de sua companhia.

Através dos gráficos que serão apresentados aqui, você consegue mostrar em uma apresentação de forma bem didática o que aconteceu através do Machine Learning e discutir sobre aquele case. O primeiro gráfico é o Waterfall construído em relação a categoria positiva para a amostra 3 que estudamos anteriormente:

# Gráfico 3 - Impacto das variáveis em uma predição específica do modelo versão Waterfall Plot
shap.plots._waterfall.waterfall_legacy(expected_value=expected_value[1], shap_values=shap_values_train[1][3].reshape(-1), feature_names=x_train.columns, show=True)
Gráfico 3: Contribuição das variáveis para a predição de uma amostra.

Avaliação do Gráfico 3

Neste gráfico, você consegue ver que sua predição começa na parte inferior e sobe até o resultado em probabilidade.

Cada variável contribui de forma positiva (modelo prever categoria positiva) e de forma negativa (modelo prever outra classe). Neste exemplo, vemos por exemplo que a contribuição de SkinThickness é anulada pela contribuição de Age.

Ainda neste gráfico, o eixo X representa os valores de SHAP e os valores das setas indicam as contribuições dessas variáveis.

No gráfico seguinte, temos uma nova roupagem para esta visualização:

# Gráfico 4 - Impacto das variáveis em uma predição específica do modelo versão Line Plot
shap.decision_plot(base_value=expected_value[1], shap_values=shap_values_train[1][3], features=x_train.iloc[3,:],highlight=0)
Gráfico 4: Contribuição das variáveis para a predição de uma amostra através de “percurso”.

Avaliação do Gráfico 4

Este gráfico é equivalente ao anterior. Como nossa categoria de referência é a positiva, o resultado do modelo segue para tons mais avermelhados (na direita) indica uma predição para classe positiva e para a esquerda, uma predição para a classe negativa. Neste gráfico, os valores próximos da seta indicam os valores das variáveis (referente à amostra) e não aos valores SHAP.

Conclusão

O SHAP se apresenta como uma ferramenta capaz de explicar, de forma gráfica e intuitiva, como modelos de inteligência artificial conseguem chegar em seus resultados. Através da interpretação dos gráficos, é possível entender as tomadas de decisão em Machine Learning de forma simplificada, permitindo apresentar explicações e levar esse conhecimento à pessoas que não necessariamente trabalham nesta área. 

Por meio desse texto, conseguimos avaliar as principais noções sobre os valores SHAP, assim como suas visualizações. A partir dos SHAP values, compreendemos como os valores de cada variável influenciaram no resultado do modelo. Nesse caso, avaliamos os resultados em termos de probabilidade. Ao analisar as visualização, foi possível perceber que o SHAP nos permite interpretar resultados específicos e individuais, além de compreender o que o esquema expressa sobre o problema.

Apesar da matemática robusta, compreender essa metodologia é mais simples do que parece. A tecnologia SHAP não para por aqui! Existem muitas coisas que podem ser feitas com essa técnica e por isso recomendo fortemente:

Quer conversar sobre outras aplicações de SHAP? Quer implementar a ciências de dados e tornar a tomada de decisão mais assertiva em seu negócio? Entre em contato conosco! Vamos marcar um bate-papo para falar sobre como a tecnologia pode ajudar a sua empresa!

Escrito por Kaike Reis