Fortolkbarhet - TABULAR SHAP explainer

Bruk Kernel SHAP (SHapley Additive exPlanations) for å forklare en tabellklassifiseringsmodell. Kernel SHAP er en modellagnostisk metode som estimerer bidraget fra hver funksjon til modellens prediksjon. Du trener en logistisk regresjonsmodell på Adult Census Income-datasettet og bruker deretter SynapseML-transformatoren TabularSHAP for å beregne forklaringer på funksjonsnivå.

Forutsetninger

  • Få et Microsoft Fabric-abonnement. Eller registrer deg for en gratis prøveversjon av Microsoft Fabric.

  • Logg på Microsoft Fabric.

  • Bytt til Fabric ved å bruke erfaringsbryteren nederst til venstre på hjemmesiden din.

    Skjermbilde som viser valget av Fabric i menyen for opplevelsesbytter.

  • Lag en ny notatbok i arbeidsområdet ditt og fest den til et hus ved innsjøen. For mer informasjon, se Lag en notatbok.

SynapseML, PySpark, pandas og plotly er forhåndsinstallert i Fabric-notatbokmiljøer. Ingen ekstra pakkeinstallasjon er nødvendig.

Importer pakker og definer hjelpe-UDF-er

I din Fabric-notatbok, lim inn følgende kode i en celle og kjør den. Dette steget importerer de nødvendige bibliotekene og definerer to brukerdefinerte funksjoner (UDF-er) for å hente ut vektorelementer senere.

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()))

Verifiser: Kjør følgende kode i en ny celle. Du skal se utgangen TabularSHAP imported successfully.

print("TabularSHAP imported successfully")
print(f"PySpark version: {pyspark.__version__}")

Last data og tren en klassifiseringsmodell

Last inn datasettet Adult Census Income fra Azure Blob Storage, indekser måletiketten, og tren en logistisk regresjonspipeline.

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)

Verifiser: Kjør følgende celle. Du bør se radteller for treningsdata og bekreftelse av pipeline-stadier.

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

Velg observasjoner for å forklare

Velg tilfeldig fem observasjoner fra de scorede treningsdataene. Disse observasjonene er tilfellene hvor du lager SHAP-forklaringer.

explain_instances = (
    model.transform(training).orderBy(rand()).limit(5).repartition(200).cache()
)
display(explain_instances)

Bekreft: Bekreft utvalgsstørrelsen.

count = explain_instances.count()
print(f"Explain instances: {count}")
assert count == 5, f"Expected 5 rows, got {count}"
print("Sample selected successfully")

Konfigurer og kjør TabularSHAP

Lag en TabularSHAP forklaring og bruk den på de valgte observasjonene. De viktigste parameterne er:

Parameteren Beskrivelse
inputCols Funksjonskolonner som modellen bruker for prediksjon.
outputCol Navnet på kolonnen som inneholder SHAP-utdataverdier.
numSamples Antall forstyrrelsesprøver for Kernel SHAP-estimering. Høyere verdier er mer nøyaktige, men tregere.
model Den trente pipeline-modellen for å forklare.
targetCol Modellutdata-kolonnen for å forklare. I dette eksempelet er kolonnen .probability
targetClasses Klasseindekser for å forklare. [1] forklarer kun klasse 1-sannsynlighet. Bruker det [0, 1] til å forklare begge fagene.
backgroundData Et utvalg av treningsdata brukt som referansefordeling for å integrere ut funksjoner.
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)

Bemerkning

Dette steget kan ta flere minutter avhengig av numSamples klasestørrelsen. Med numSamples=5000 og fem observasjoner, forvent 3-10 minutter på en standard Fabric Spark-klynge.

Verifiser: Sjekk at SHAP-utgangskolonnen finnes.

assert "shapValues" in shap_df.columns, "shapValues column missing"
print(f"SHAP output columns: {shap_df.columns}")
print("TabularSHAP transform completed")

Trekk ut SHAP-verdier

Trekk ut klasse 1-sannsynlighets- og SHAP-verdiene fra resultatdatarammen. For hver observasjon starter SHAP-verdivektoren med basisverdien (gjennomsnittlig utdata fra bakgrunnsdatasettet), etterfulgt av én verdi per funksjon.

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)

Verifiser: Bekreft pandas DataFrame-struktur.

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")

Visualiser SHAP-verdier

Lag et stolpediagram for hver observasjon som viser hvordan hvert trekk bidrar til den predikerte sannsynligheten.

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()

Bekreft: Bekreft at plottobjektet ble opprettet.

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")

Tolk resultatene

Hver underplott representerer én observasjon. Gitteret viser:

  • Base: Gjennomsnittlig modellutdata over bakgrunnsdatasett (baseline sannsynlighet).
  • Positive SHAP-verdier: Egenskaper som peker mot klasse 1 (inntekt over 50 000).
  • Negative SHAP-verdier: Egenskaper som skyver prediksjonen mot klasse 0 (inntekt mindre enn eller lik 50 000).

Summen av basisverdien og alle egenskaps-SHAP-verdier tilsvarer modellens predikerte sannsynlighet for den observasjonen.

Feilsøking

Problem Årsak Løsning
OutOfMemoryError under TabularSHAP numSamples er for stor for tilgjengelig minne. Reduser numSamples, for eksempel til 1 000, eller øk Spark-executorminne.
SHAP-transformasjonen er treg Høyt numSamples med mange funksjoner øker regnetiden. Reduser numSamples til 1 000-2 000 for raskere utforskende resultater. Økning for sluttanalyse.
FileNotFoundException for parkett Nettverkstilgangen er mmlspark.blob.core.windows.net blokkert. Sjekk at Fabric-arbeidsplassen din har utgående internett-tilgang. Alternativt kan du laste datasettet opp til hytta ved innsjøen.
shapValues kolonnen inneholder nullpunkter Noen observasjoner kan feile hvis funksjonsverdiene ligger utenfor treningsfordelingen. Sjekk etter null- eller uventede verdier i input-funksjoner. Filtrer nullpunkter fra resultatene.
display() viser ingen utgang Koden kjører utenfor et Fabric-notatbokmiljø. Bruk shaps_local.head() eller print(shaps_local) i standard Python-miljøer.

Rydd opp

Hvis du lastet opp datasettet til et innsjøhus for denne veiledningen, fjern det til fri lagring:

# Remove cached DataFrames from memory
training.unpersist()
explain_instances.unpersist()
print("Cached DataFrames released")