Tulkittavuus – taulukkomuotoinen SHAP-selitys

Käytä Kernel SHAP (SHapley Additive ExPlanations) selittämään taulukkomaisen luokittelumallin. Kernel SHAP on malliriippumaton menetelmä, joka arvioi kunkin ominaisuuden panosta mallin ennusteeseen. Koulutat logistisen regressiomallin Adult Census Income -aineistolla ja käytät sitten SynapseML-muuntajaa TabularSHAP ominaisuustason selityksien laskemiseen.

Edellytykset

SynapseML, PySpark, pandas ja plotly on valmiiksi asennettu Fabric-muistikirjaympäristöihin. Lisäpakettien asennusta ei tarvita.

Tuo paketteja ja määrittele apu-UDF:t

Liitä seuraava koodi Fabric-muistikirjaasi soluun ja suorita se. Tämä vaihe tuo tarvittavat kirjastot ja määrittelee kaksi käyttäjän määrittelemää funktiota (UDF) vektorielementtien poimimista varten myöhemmin.

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

Vahvista: Suorita seuraava koodi uudessa solussa. Sinun pitäisi nähdä ulostulo TabularSHAP imported successfully.

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

Lataa data ja kouluta luokitusmalli

Lataa Adult Census Income -tietoaineisto Azure Blob Storage:sta, indeksoi kohdetunniste ja kouluta logistinen regressioputki.

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)

Vahvista: Käynnistä seuraava solu. Sinun pitäisi nähdä rivimäärät koulutusdatalle ja putkivaiheiden vahvistuksille.

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

Valikoituja havaintoja selitettäväksi

Valitse satunnaisesti viisi havaintoa pisteytetystä harjoitusdatasta. Nämä havainnot ovat tilanteita, joissa luot SHAP-selityksiä.

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

Vahvista: Vahvista otoskoko.

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

Määritä ja aja TabularSHAP

Luo selitys TabularSHAP ja sovella sitä valittuihin havaintoihin. Keskeiset parametrit ovat:

Parametri Description
inputCols Mallin käyttämät ominaisuussarakkeet ennustamiseen.
outputCol Nimi sarakkeelle, joka sisältää SHAP-tulosarvot.
numSamples Perturbaationäytteiden määrä ytimen SHAP-estimaatiota varten. Korkeammat arvot ovat tarkempia, mutta hitaampia.
model Koulutettu putkimalli selittää.
targetCol Mallin ulostulosarake selittämään. Tässä esimerkissä sarake on probability.
targetClasses Luokkaindeksit selitettäväksi. [1] Selittää vain luokan 1 todennäköisyyden. Käytin [0, 1] selittämään molempia kursseja.
backgroundData Koulutusdatan otos, jota käytetään viitejakaumana ominaisuuksien integrointiin.
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)

Muistio

Tämä vaihe voi kestää useita minuutteja riippuen numSamples klusterin koosta. numSamples=5000 ja viiden havainnon myötä odotettavissa on 3–10 minuuttia oletus Fabric Spark-klusterilla.

Varmista: Tarkista, että SHAP-ulostulosarake on olemassa.

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

Poimi SHAP-arvot

Poima luokan 1 todennäköisyys- ja SHAP-arvot tuloksesta DataFrame. Jokaisessa havainnossa SHAP-arvovektori alkaa perusarvosta (taustaaineiston keskimääräisestä tulosta), jota seuraa yksi arvo per ominaisuus.

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)

Vahvista: Vahvista pandas DataFramen rakenne.

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

Visualisoi SHAP-arvot

Luo jokaiselle havaintolle pylväskaavio, joka näyttää, miten kukin piirre vaikuttaa ennustettuun todennäköisyyteen.

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

Vahvista: Vahvista, että juoniobjekti on luotu.

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

Tulosten tulkitseminen

Jokainen sivukuva edustaa yhtä havaintoa. Baarit näyttävät:

  • Perusta: Keskimääräinen mallin tuotos taustaaineistossa (perustodennäköisyys).
  • Positiiviset SHAP-arvot: Ominaisuudet, jotka ohjaavat ennustetta kohti luokkaa 1 (tulot yli 50K).
  • Negatiiviset SHAP-arvot: Ominaisuudet, jotka ohjaavat ennustetta kohti luokkaa 0 (tulot alle 50K tai yhtä suuret).

Perusarvon ja kaikkien ominaisuuksien SHAP-arvojen summa vastaa mallin ennustettua todennäköisyyttä kyseiselle havainnolle.

Vianmääritys

Ongelma Syy Ratkaisu
OutOfMemoryError TabularSHAPin aikana numSamples on liian suuri käytettävissä olevalle muistille. Esimerkiksi vähennä numSamples, 1 000:een tai lisää Sparkin suoritusohjelman muistia.
SHAP-muunnos on hidas Korkea numSamples ja moni ominaisuus lisää laskenta-aikaa. Vähennä numSamples 1 000–2 000:een nopeampien tutkimustulosten saamiseksi. Korotus lopullista analyysiä varten.
FileNotFoundException parkettia varten Verkkoyhteys mmlspark.blob.core.windows.net on estetty. Varmista, että Fabric-työtilassasi on ulospäin suuntautuva internet-yhteys. Vaihtoehtoisesti lataa aineisto järvenmajasi.
shapValues sarake sisältää nollapisteitä Jotkut havainnot voivat epäonnistua, jos ominaisuusarvot ovat harjoitusjakauman ulkopuolella. Tarkista nolla- tai odottamattomia arvoja syöteominaisuuksista. Suodata nullit tuloksista.
display() Ei ulostuloa Koodi toimii Fabric-muistikirjaympäristön ulkopuolella. Käytä shaps_local.head() tai print(shaps_local) tavallisissa Python-ympäristöissä.

Pestä

Jos latasit aineiston järventaloon tätä opetusta varten, poista se vapaaseen tallennustilaan:

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