Κοινή χρήση μέσω


Ερμηνευτικότητα - Επεξήγηση SHAP σε μορφή πίνακα

Αυτό το παράδειγμα χρησιμοποιεί το Kernel SHAP για να εξηγήσει ένα μοντέλο ταξινόμησης σε μορφή πίνακα που έχει δημιουργηθεί από το σύνολο δεδομένων της απογραφής ενηλίκων.

Εισαγάγετε τα απαιτούμενα πακέτα και καθορίστε τα UDF που χρειαζόμαστε αργότερα:

import pyspark
from synapse.ml.explainers import *
from pyspark.ml import Pipeline
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler
from pyspark.sql.types import *
from pyspark.sql.functions import *
import pandas as pd
from pyspark.sql import SparkSession

# Bootstrap Spark Session
spark = SparkSession.builder.getOrCreate()

from synapse.ml.core.platform import *

vec_access = udf(lambda v, i: float(v[i]), FloatType())
vec2array = udf(lambda vec: vec.toArray().tolist(), ArrayType(FloatType()))

Διαβάστε τα δεδομένα και εκπαιδεύστε ένα μοντέλο δυαδικής ταξινόμησης:

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()
display(training)
categorical_features = [
    "workclass",
    "education",
    "marital-status",
    "occupation",
    "relationship",
    "race",
    "sex",
    "native-country",
]
categorical_features_idx = [col + "_idx" for col in categorical_features]
categorical_features_enc = [col + "_enc" for col 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)

Μετά την εκπαίδευση του μοντέλου, επιλέξτε τυχαία ορισμένες παρατηρήσεις για να εξηγήσετε τα εξής:

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

Δημιουργήστε μια επεξήγηση tabularSHAP και ορίστε τις στήλες εισόδου σε όλες τις δυνατότητες που λαμβάνει το μοντέλο. Στη συνέχεια, καθορίστε το μοντέλο και τη στήλη εξόδου προορισμού που θέλουμε να εξηγήσουμε. Εδώ, θέλουμε να εξηγήσουμε την έξοδο "πιθανότητας", η οποία είναι ένα διάνυσμα μήκους 2 και εξετάζουμε μόνο την πιθανότητα κλάσης 1. Καθορίστε τη targetClasses του [0, 1] για να εξηγήσετε την κλάση 0 και 1 πιθανότητα ταυτόχρονα. Τέλος, δείγμα 100 γραμμών από τα δεδομένα εκπαίδευσης για δεδομένα παρασκηνίου, που χρησιμοποιείται για την ενοποίηση δυνατοτήτων στο Kernel SHAP:

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)

Με το πλαίσιο δεδομένων που προκύπτει, εξαγάγετε

  • η πιθανότητα κλάσης 1 της εξόδου μοντέλου
  • οι τιμές SHAP για την κλάση προορισμού
  • τις αρχικές δυνατότητες
  • η ετικέτα true

Στη συνέχεια, μετατρέψτε το πλαίσιο δεδομένων σε ένα πλαίσιο δεδομένων pandas για απεικόνιση.

Για κάθε παρατήρηση, το πρώτο στοιχείο στο διάνυσμα τιμών SHAP είναι η βασική τιμή (η μέση έξοδος του συνόλου δεδομένων παρασκηνίου). Κάθε ένα από τα παρακάτω στοιχεία είναι οι τιμές SHAP για κάθε δυνατότητα:

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

Χρησιμοποιήστε το υποπλόιο σχεδίασης για να απεικονίσετε τις τιμές SHAP:

from plotly.subplots import make_subplots
import plotly.graph_objects as go
import pandas as pd

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