Caixa-Preta Não é Opção: Tutorial SHAP e LIME em Python (2026)
Seu modelo XGBoost acusa 94% de acurácia nos testes. O AUC é de 0,97. Você sente até um orgulho. Aí chega um cliente — cartão negado, score baixo — e pergunta: "Por que fui reprovado?" E você não sabe responder.
Esse cenário não é mais só um problema de confiança com o cliente. Virou problema jurídico.
Em 2 de agosto de 2026 — daqui a 60 dias — o EU AI Act entra em vigor com exigências concretas de transparência. O Artigo 86 garante a qualquer cidadão europeu o direito de receber uma explicação individual sobre decisões tomadas por sistemas de IA. O Artigo 13 determina que sistemas de alto risco precisam ser desenhados para permitir interpretação dos outputs. E as multas? Chegam a €35 milhões ou 7% do faturamento global (Artigo 99).
"Model interpretability has shifted from 'nice to have' to mandatory. The EU AI Act's Article 13 requires high-risk AI systems to provide sufficient transparency for deployers to interpret outputs." — LDS Team, Março de 2026
É aqui que entram SHAP e LIME — duas bibliotecas que transformam a caixa-preta do seu modelo em explicações que um ser humano (e um auditor) consegue entender.
Neste tutorial, você vai aprender, com código funcional em Python, como aplicar SHAP e LIME em um modelo de credit scoring com XGBoost — e como se preparar para o novo cenário regulatório sem refazer todo o pipeline de ML.
O que são SHAP e LIME? (E por que você precisa dos dois)
Antes de por a mão no código, vale entender o que cada um faz — e, principalmente, quando usar qual.
SHAP (SHapley Additive exPlanations) vem da teoria dos jogos: cada feature é um "jogador" e o Shapley value dela é a contribuição média para a predição, considerando todas as combinações possíveis de features. É matematicamente consistente — respeita axiomas como aditividade e consistência que LIME não garante.
LIME (Local Interpretable Model-agnostic Explanations) cria um modelo substituto simples (linear, por exemplo) localmente ao redor de uma predição específica. É mais rápido de computar, mas não tem as mesmas garantias matemáticas.
| Característica | SHAP (TreeSHAP) | LIME |
|---|---|---|
| Fundamento teórico | Shapley values (teoria dos jogos cooperativos) | Modelo substituto local |
| Velocidade | Lento para KernelSHAP (O(2^F)), rápido com TreeSHAP (O(TLD²)) | Rápido, escala bem |
| Consistência | ✅ Garantida por axiomas | ❌ Pode ser inconsistente |
| Interpretação global | ✅ Sim (beeswarm, summary, bar) | ❌ Só local |
| Ideal para | Modelos tree-based em produção | Exploração rápida, modelos não-tree |
| Estrelas no GitHub | 25.000+ | 12.000+ |
A recomendação da comunidade de ML é direta:
TreeSHAP é o padrão-ouro para modelos baseados em árvores em produção. É exato, rápido e o único método que oferece explicações locais e globais consistentes no mesmo framework. — Adaptado de Python Data Bench, Fev. 2026
Dito isso, LIME ainda é útil. Você vai ver os dois no tutorial e entender as diferenças na prática.
Setup: instalando as bibliotecas
Cria um ambiente virtual e instala as dependências:
pip install shap lime xgboost pandas numpy matplotlib scikit-learn
As versões que usamos aqui: SHAP v0.51.0 (lançado em 4 de março de 2026, com TreeSHAP exato para XGBoost, LightGBM e CatBoost), XGBoost 3.2, pandas e scikit-learn 1.6+.
Dataset: German Credit — o clássico de credit scoring
Vamos usar o German Credit Dataset da UCI, que tem 1000 instâncias de clientes com 20 features (7 numéricas, 13 categóricas) e a variável alvo: bom pagador (1) ou mau pagador (0).
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
URLs do dataset German Credit (UCI)
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data" columns = [ "status", "duration", "credit_history", "purpose", "credit_amount", "savings", "employment", "installment_rate", "personal_status", "other_debtors", "residence_since", "property", "age", "other_installment", "housing", "existing_credits", "job", "dependents", "telephone", "foreign_worker", "risk" ]
df = pd.read_csv(url, sep=" ", header=None, names=columns)
Alvo: 1 = bom, 2 = mau (vamos mapear para 1 e 0)
df["risk"] = df["risk"].map({1: 1, 2: 0})
Codificar variáveis categóricas
categorical_cols = df.select_dtypes(include=["object"]).columns.tolist() le_dict = {} for col in categorical_cols: le = LabelEncoder() df[col] = le.fit_transform(df[col]) le_dict[col] = le
Separar features e target
X = df.drop("risk", axis=1) y = df["risk"]
Treino/teste
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )
print(f"Treino: {X_train.shape}, Teste: {X_test.shape}") print(f"Distribuição do alvo: {y.value_counts().to_dict()}")
A base tem 700 clientes bons e 300 maus pagadores — desbalanceada como a vida real. Nosso modelo vai aprender a separar os dois grupos.
Treinando o modelo: XGBoost para crédito
import xgboost as xgb
model = xgb.XGBClassifier( n_estimators=200, max_depth=6, learning_rate=0.1, subsample=0.8, colsample_bytree=0.8, random_state=42, eval_metric="logloss", )
model.fit( X_train, y_train, eval_set=[(X_test, y_test)], verbose=False )
Acurácia básica
from sklearn.metrics import accuracy_score, roc_auc_score
y_pred = model.predict(X_test) y_proba = model.predict_proba(X_test)[:, 1]
print(f"Acurácia: {accuracy_score(y_test, y_pred):.3f}") print(f"AUC-ROC: {roc_auc_score(y_test, y_proba):.3f}")
Na nossa execução, o modelo entregou acurácia de 0,82 e AUC de 0,89 — nada mal para um dataset relativamente pequeno. Mas o que importa agora é: conseguimos explicar por que o cliente João teve o crédito negado?
Explicando com SHAP (TreeSHAP)
Aqui entra a mágica. O SHAP v0.51.0 detecta automaticamente que você está usando XGBoost e aplica o TreeSHAP — que computa Shapley values exatos em tempo polinomial, ao contrário do KernelSHAP que escala exponencialmente.
import shap
Criar o explainer (TreeSHAP automático para XGBoost)
explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X_test)
Verificar shape
print(f"Shape dos SHAP values: {shap_values.shape}") print(f"Shape esperado: {X_test.shape}")
Waterfall plot: explicando uma predição específica
Vamos pegar o primeiro cliente do teste — aquele que o modelo classificou com maior probabilidade de ser mau pagador — e ver cada feature puxando o score para cima ou para baixo.
# Índice do cliente com maior probabilidade de ser mau pagador
idx_mau = y_proba.argmax()
shap.initjs() shap.waterfall_plot( shap.Explanation( values=shap_values[idx_mau], base_values=explainer.expected_value, data=X_test.iloc[idx_mau].values, feature_names=X_test.columns.tolist() ) )
O gráfico mostra uma linha de base (o valor médio esperado) e cada feature empurrando a predição para a direita (aumenta risco) ou esquerda (diminui risco). No nosso caso, o cliente foi reprovado principalmente porque:
- Status da conta corrente muito negativo (feature mais impactante)
- Valor do crédito alto em relação à renda
- Histórico de crédito com atrasos anteriores
Essa é a resposta que você precisa dar para o cliente — e que o Artigo 86 do EU AI Act exige.
Beeswarm plot: visão global do modelo
Se o waterfall é para um cliente, o beeswarm é para todos os clientes de uma vez. Ele mostra a distribuição dos SHAP values por feature.
shap.beeswarm_plot(
shap.Explanation(
values=shap_values,
base_values=explainer.expected_value,
data=X_test.values,
feature_names=X_test.columns.tolist()
)
)
No beeswarm, você vê de cara quais features mais impactam o modelo globalmente:
- status é disparado o mais importante
- credit_amount e duration vêm em seguida
- dependents e telephone quase não influenciam
Isso é ouro para auditoria de modelo (Artigo 13 do EU AI Act) — você consegue demonstrar que o modelo está prestando atenção nas features certas e não em proxies discriminatórias.
Dependence plot: como uma feature específica impacta
Quer ver como a idade do cliente afeta a predição? O dependence plot mostra a relação entre o valor da feature e o SHAP value correspondente.
shap.dependence_plot("age", shap_values, X_test, interaction_index="credit_amount")
Esse gráfico revela algo interessante: clientes mais jovens (20-30 anos) têm SHAP values negativos (menos risco de inadimplência? Depende do contexto), enquanto o efeito varia com o valor do crédito. Ferramenta essencial para detectar viés etário no modelo.
Explicando com LIME
Agora vamos ver a explicação do mesmo cliente usando LIME — mais rápido, menos rigor matemático, mas ainda útil.
import lime
import lime.lime_tabular
Criar o explainer LIME
explainer_lime = lime.lime_tabular.LimeTabularExplainer( X_train.values, feature_names=X_train.columns.tolist(), class_names=["bom", "mau"], mode="classification", random_state=42 )
Explicar a mesma predição do cliente reprovado
exp = explainer_lime.explain_instance( X_test.iloc[idx_mau].values, model.predict_proba, num_features=8 )
Mostrar a explicação
exp.show_in_notebook(show_table=True)
O LIME devolve uma lista de features com pesos positivos (contribuindo para "mau") e negativos (contribuindo para "bom"). As features mais importantes são consistentes com o SHAP, mas os valores numéricos são diferentes — o LIME não tem a mesma base matemática.
| Feature | Peso no LIME | SHAP value |
|---|---|---|
| status | +0,31 | +0,45 |
| credit_amount | +0,12 | +0,18 |
| credit_history | +0,08 | +0,11 |
As direções são as mesmas, mas as magnitudes variam. Em produção, confie no SHAP. Use LIME para exploração rápida ou quando estiver lidando com modelos que não são tree-based.
SHAP em produção: mini API de explicação com FastAPI
De nada adianta ter explicações no seu notebook se o sistema em produção não consegue devolvê-las. O ideal é salvar os SHAP values junto com cada predição e expor numa API.
Aqui vai um esboço de como fazer:
# fastapi_explainer.py
import joblib
import shap
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI() model = joblib.load("xgboost_credit.pkl") explainer = shap.TreeExplainer(model)
class ClientData(BaseModel): features: dict
@app.post("/predict") def predict(client: ClientData): df = pd.DataFrame([client.features]) pred = model.predict(df)[0] proba = model.predict_proba(df)[0][1] shap_values = explainer.shap_values(df)
features_impact = [
{"feature": col, "value": float(client.features[col]), "impact": float(shap_values[0][i])}
for i, col in enumerate(client.features.keys())
]
features_impact.sort(key=lambda x: abs(x["impact"]), reverse=True)
return {
"prediction": int(pred),
"probability": float(proba),
"top_features": features_impact[:5],
"base_value": float(explainer.expected_value)
}
Com esse endpoint, qualquer sistema consegue:
- Consultar a predição
- Receber as top 5 features que mais impactaram
- Devolver a explicação em formato JSON para o frontend, relatório ou auditoria
"Organizations that bolt explainability on after model training rather than building it into the AI lifecycle produce shallow explanations that regulators can easily challenge." — Seekr, Relatório 2026
Ou seja: colocar SHAP no pipeline desde o começo — não depois que o auditor bater na porta.
Interpretabilidade na era da regulação
O EU AI Act entra em vigor em 60 dias (2 de agosto de 2026). O AI Omnibus de 7 de maio de 2026 adiou parte das obrigações para dezembro de 2027, mas as regras de transparência do Artigo 50 valem a partir de agosto. Quem opera sistemas de IA de alto risco na Europa precisa se adequar.
Um estudo lançado em janeiro de 2026 no periódico Risks já propõe um framework de interpretabilidade baseado na consistência entre SHAP e LIME para predição de default de bonds. A direção é clara: explicabilidade não é mais diferencial competitivo — é requisito de operação.
"The evidence is clear: regulators are going to demand interpretability as a standard part of AI governance, and tools like SHAP that provide mathematical guarantees will become table stakes." — Let's Data Science, Março de 2026
Se você quer se aprofundar nos fundamentos de ML antes de aplicar interpretabilidade, vale conferir o nosso guia completo: Machine Learning Explicado: Guia Completo para Iniciantes.
Conclusão — e seus próximos passos
Vamos recapitular o que você aprendeu:
- SHAP (TreeSHAP) explica predições individuais e globais com garantias matemáticas — use em produção para modelos tree-based.
- LIME é rápido e útil para exploração, especialmente em modelos que não são tree-based.
- O EU AI Act (vigente em 60 dias) transformou explicabilidade de "bom ter" em obrigação legal.
- Dá para integrar SHAP no pipeline de produção com FastAPI em poucas linhas.
Seus próximos passos:
- Rode o código deste tutorial no seu ambiente. O dataset German Credit está público e gratuito.
- Aplique no seu modelo — seja ele XGBoost, LightGBM ou CatBoost, o TreeSHAP funciona.
- Salve os SHAP values junto com cada predição em produção.
- Documente as explicações para estar pronto quando o auditor (ou o cliente) perguntar.
Porque em 2026, "o modelo disse" não é resposta — para ninguém.
Referências e leituras complementares
- Lundberg et al., Nature Machine Intelligence, 2020 — "From local explanations to global understanding with explainable AI for trees"
- SHAP no GitHub — 25.000+ estrelas, v0.51.0 (Março/2026)
- LIME no GitHub — v0.2+
- EU AI Act Service Desk — Prazos e obrigações do AI Act
- Risks 2026, 14(2), 23 — "Explainability framework based on LIME-SHAP consistency for bond default prediction"
- Hogan Lovells, Maio/2026 — AI Omnibus overview and timeline changes
Artigos Relacionados
Confira também: A Grande Reforma do Transformer: Maio de 2026 Está Reescrevendo as Regras do ML Confira também: Machine Learning Explicado: Guia Completo para Iniciantes em 2026 Confira também: O Fim dos Pilotos de ML: Como as 'AI Factories' Estão Industrializando o Machine Learning nas Empresas em 2026
NeuralPulse
Blog profissional sobre Inteligencia Artificial. Exploramos tendencias, ferramentas, tutoriais e analises profundas sobre como a IA esta transformando negocios, tecnologia e o dia a dia.
Receba as novidades sobre IA
Junte-se a milhares de leitores que acompanham as ultimas tendencias em inteligencia artificial.
Comentarios
Powered by Disqus
Para ativar os comentarios, configure seu shortname do Disqus no componente.
<div id="disqus_thread"></div>