Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Usare Kernel SHAP (SHapley Additive exPlanations) per spiegare un modello di classificazione tabulare. Kernel SHAP è un metodo indipendente dal modello che stima il contributo di ogni funzionalità alla stima di un modello. Si addestra un modello di regressione logistica sul set di dati Adult Census Income e quindi si usa il trasformatore SynapseML TabularSHAP per calcolare spiegazioni per singola caratteristica.
Prerequisiti
Abbonati a Microsoft Fabric. Oppure, registrati per una versione di prova gratuita di Microsoft Fabric.
Accedi a Microsoft Fabric.
Passare a Fabric usando il selettore di esperienza nell'angolo in basso a sinistra della home page.
- Crea un nuovo notebook nell'area di lavoro e associalo a una lakehouse. Per altre informazioni, vedere Creare un notebook.
SynapseML, PySpark, pandas e plotly sono preinstallati in ambienti notebook Fabric. Non è necessaria alcuna installazione aggiuntiva del pacchetto.
Importare i pacchetti e definire UDF di supporto
Nel notebook di Fabric incollare il codice seguente in una cella ed eseguirlo. Questo passaggio importa le librerie necessarie e definisce due funzioni definite dall'utente (UDF) per estrarre gli elementi vettoriali in un secondo momento.
import pyspark
from synapse.ml.explainers import TabularSHAP
from pyspark.ml import Pipeline
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler
from pyspark.sql.types import FloatType, ArrayType
from pyspark.sql.functions import col, lit, rand, broadcast, udf
import pandas as pd
vec_access = udf(lambda v, i: float(v[i]), FloatType())
vec2array = udf(lambda vec: vec.toArray().tolist(), ArrayType(FloatType()))
Verificare: eseguire il codice seguente in una nuova cella. Verrà visualizzato l'output TabularSHAP imported successfully.
print("TabularSHAP imported successfully")
print(f"PySpark version: {pyspark.__version__}")
Caricare i dati ed eseguire il training di un modello di classificazione
Carica il dataset Adult Census Income da Archiviazione BLOB di Azure, indicizza l'etichetta di destinazione e addestra una pipeline di regressione logistica.
df = spark.read.parquet(
"wasbs://publicwasb@mmlspark.blob.core.windows.net/AdultCensusIncome.parquet"
)
labelIndexer = StringIndexer(
inputCol="income", outputCol="label", stringOrderType="alphabetAsc"
).fit(df)
print("Label index assignment: " + str(set(zip(labelIndexer.labels, [0, 1]))))
training = labelIndexer.transform(df).cache()
categorical_features = [
"workclass",
"education",
"marital-status",
"occupation",
"relationship",
"race",
"sex",
"native-country",
]
categorical_features_idx = [feat + "_idx" for feat in categorical_features]
categorical_features_enc = [feat + "_enc" for feat in categorical_features]
numeric_features = [
"age",
"education-num",
"capital-gain",
"capital-loss",
"hours-per-week",
]
strIndexer = StringIndexer(
inputCols=categorical_features, outputCols=categorical_features_idx
)
onehotEnc = OneHotEncoder(
inputCols=categorical_features_idx, outputCols=categorical_features_enc
)
vectAssem = VectorAssembler(
inputCols=categorical_features_enc + numeric_features, outputCol="features"
)
lr = LogisticRegression(featuresCol="features", labelCol="label", weightCol="fnlwgt")
pipeline = Pipeline(stages=[strIndexer, onehotEnc, vectAssem, lr])
model = pipeline.fit(training)
Verificare: eseguire la cella seguente. Dovresti vedere il numero di righe dei dati di addestramento e la conferma delle fasi della pipeline.
print(f"Training rows: {training.count()}")
print(f"Pipeline stages: {[type(s).__name__ for s in model.stages]}")
assert training.count() > 30000, "Dataset should contain over 30,000 rows"
print("Model trained successfully")
# Expected output:
#Training rows: 32561
#Pipeline stages: ['StringIndexerModel', 'OneHotEncoderModel', #'VectorAssembler', 'LogisticRegressionModel']
#Model trained successfully
Selezionare le osservazioni da spiegare
Selezionare casualmente cinque osservazioni dai dati di addestramento con punteggio. Queste osservazioni sono le istanze per cui si generano spiegazioni SHAP.
explain_instances = (
model.transform(training).orderBy(rand()).limit(5).repartition(200).cache()
)
display(explain_instances)
Verificare: confermare le dimensioni del campione.
count = explain_instances.count()
print(f"Explain instances: {count}")
assert count == 5, f"Expected 5 rows, got {count}"
print("Sample selected successfully")
Configurare ed eseguire TabularSHAP
Crea una TabularSHAP spiegazione e applicala alle osservazioni selezionate. I parametri chiave sono:
| Parametro | Descrizione |
|---|---|
inputCols |
Colonne delle caratteristiche utilizzate dal modello per la previsione. |
outputCol |
Nome della colonna contenente i valori di output SHAP. |
numSamples |
Numero di campioni di perturbazione per la stima SHAP del kernel. I valori più elevati sono più accurati ma più lenti. |
model |
Il modello di pipeline addestrato da spiegare. |
targetCol |
Colonna di output del modello da spiegare. In questo esempio la colonna è probability. |
targetClasses |
Indici di classe da spiegare.
[1] spiega solo la probabilità della classe 1. Usare [0, 1] per spiegare entrambe le classi. |
backgroundData |
Esempio di dati di training usati come distribuzione di riferimento per l'integrazione delle funzionalità. |
shap = TabularSHAP(
inputCols=categorical_features + numeric_features,
outputCol="shapValues",
numSamples=5000,
model=model,
targetCol="probability",
targetClasses=[1],
backgroundData=broadcast(training.orderBy(rand()).limit(100).cache()),
)
shap_df = shap.transform(explain_instances)
Note
Questo passaggio può richiedere alcuni minuti a seconda di numSamples e delle dimensioni del cluster. Con numSamples=5000 e cinque osservazioni, prevedere 3-10 minuti in un cluster Spark Fabric predefinito.
Verificare che la colonna di output SHAP esista.
assert "shapValues" in shap_df.columns, "shapValues column missing"
print(f"SHAP output columns: {shap_df.columns}")
print("TabularSHAP transform completed")
Estrarre valori SHAP
Estrarre i valori di probabilità e SHAP della classe 1 dal dataframe del risultato. Per ogni osservazione, il vettore di valori SHAP inizia con il valore di base (output medio del set di dati in background), seguito da un valore per funzionalità.
shaps = (
shap_df.withColumn("probability", vec_access(col("probability"), lit(1)))
.withColumn("shapValues", vec2array(col("shapValues").getItem(0)))
.select(
["shapValues", "probability", "label"] + categorical_features + numeric_features
)
)
shaps_local = shaps.toPandas()
shaps_local.sort_values("probability", ascending=False, inplace=True, ignore_index=True)
pd.set_option("display.max_colwidth", None)
display(shaps_local)
Verifica: confermare la struttura del dataframe pandas.
expected_cols = len(categorical_features) + len(numeric_features) + 3
print(f"DataFrame shape: {shaps_local.shape}")
print(f"Expected columns: {expected_cols}, Actual: {shaps_local.shape[1]}")
assert shaps_local.shape == (5, expected_cols), f"Unexpected shape: {shaps_local.shape}"
print("SHAP values extracted successfully")
Visualizzare i valori SHAP
Creare un grafico a barre per ogni osservazione che mostra come ogni caratteristica contribuisce alla probabilità stimata.
from plotly.subplots import make_subplots
import plotly.graph_objects as go
features = categorical_features + numeric_features
features_with_base = ["Base"] + features
rows = shaps_local.shape[0]
fig = make_subplots(
rows=rows,
cols=1,
subplot_titles="Probability: "
+ shaps_local["probability"].apply("{:.2%}".format)
+ "; Label: "
+ shaps_local["label"].astype(str),
)
for index, row in shaps_local.iterrows():
feature_values = [0] + [row[feature] for feature in features]
shap_values = row["shapValues"]
list_of_tuples = list(zip(features_with_base, feature_values, shap_values))
shap_pdf = pd.DataFrame(list_of_tuples, columns=["name", "value", "shap"])
fig.add_trace(
go.Bar(
x=shap_pdf["name"],
y=shap_pdf["shap"],
hovertext="value: " + shap_pdf["value"].astype(str),
),
row=index + 1,
col=1,
)
fig.update_yaxes(range=[-1, 1], fixedrange=True, zerolinecolor="black")
fig.update_xaxes(type="category", tickangle=45, fixedrange=True)
fig.update_layout(height=400 * rows, title_text="SHAP explanations")
fig.show()
Verifica: verificare che l'oggetto tracciato sia stato creato.
print(f"Figure traces: {len(fig.data)}")
print(f"Figure height: {fig.layout.height}px")
assert len(fig.data) == 5, f"Expected 5 traces, got {len(fig.data)}"
print("Visualization created successfully")
Interpretare i risultati
Ogni sottolot rappresenta un'osservazione. Le barre mostrano:
- Base: output medio del modello nel set di dati in background (probabilità di base).
- Valori SHAP positivi: funzionalità che spingono la stima verso la classe 1 (reddito maggiore di 50.000).
- Valori SHAP negativi: caratteristiche che spingono la previsione verso la classe 0 (reddito inferiore o pari a 50K).
La somma del valore di base e di tutti i valori SHAP della funzionalità è uguale alla probabilità stimata del modello per tale osservazione.
Risoluzione dei problemi
| Problema | Motivo | Risoluzione |
|---|---|---|
OutOfMemoryError durante TabularSHAP |
numSamples è troppo grande per la memoria disponibile. |
Ridurre numSamples, ad esempio a 1.000, oppure aumentare la memoria dell'executor di Spark. |
| La trasformazione SHAP è lenta | Una numSamples elevata con numerose funzionalità aumenta il tempo di calcolo. |
Ridurre numSamples a 1.000-2.000 per risultati esplorativi più veloci. Aumento per l'analisi finale. |
FileNotFoundException per parquet |
L'accesso alla rete a mmlspark.blob.core.windows.net è bloccato. |
Verificare che l'area di lavoro Fabric abbia accesso a Internet in uscita. In alternativa, carica il set di dati nel tuo lakehouse. |
shapValues la colonna contiene valori Null |
Alcune osservazioni potrebbero dare esito negativo se i valori delle variabili escono dalla distribuzione di addestramento. | Verificare la presenza di valori Null o imprevisti nelle funzionalità di input. Filtrare i valori Null dai risultati. |
display() non mostra alcun output |
Il codice viene eseguito all'esterno di un ambiente notebook Fabric. | Usare shaps_local.head() o print(shaps_local) in ambienti di Python standard. |
Pulizia
Se il set di dati è stato caricato in una lakehouse per questa esercitazione, rimuoverlo per liberare spazio di archiviazione:
# Remove cached DataFrames from memory
training.unpersist()
explain_instances.unpersist()
print("Cached DataFrames released")