Interpreteerbaarheid - Shap-uitleg in tabelvorm
In dit voorbeeld gebruiken we Kernel SHAP om een tabellair classificatiemodel uit te leggen dat is gebouwd op basis van de gegevensset Voor volwassenen volkstelling.
Eerst importeren we de pakketten en definiƫren we enkele UDF's die we later nodig hebben.
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()))
Laten we nu de gegevens lezen en een binair classificatiemodel trainen.
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 assigment: " + 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)
Nadat het model is getraind, selecteren we willekeurig enkele waarnemingen die moeten worden uitgelegd.
explain_instances = (
model.transform(training).orderBy(rand()).limit(5).repartition(200).cache()
)
display(explain_instances)
We maken een TabularSHAP-uitleg, stellen de invoerkolommen in op alle functies die het model gebruikt, specificeren het model en de doeluitvoerkolom die we willen uitleggen. In dit geval proberen we de 'waarschijnlijkheidsuitvoer' uit te leggen. Dit is een vector van lengte 2 en we kijken alleen naar de kans van klasse 1. Geef targetClasses [0, 1]
op als u tegelijkertijd klasse 0 en 1 wilt uitleggen. Ten slotte nemen we 100 rijen uit de trainingsgegevens voor achtergrondgegevens, die worden gebruikt voor het integreren van functies in 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)
Zodra we het resulterende dataframe hebben, halen we de kans van klasse 1 op van de modeluitvoer, de SHAP-waarden voor de doelklasse, de oorspronkelijke functies en het ware label. Vervolgens converteren we deze naar een pandas-dataframe voor visualisatie. Voor elke observatie is het eerste element in de SHAP-waardenvector de basiswaarde (de gemiddelde uitvoer van de achtergrondgegevensset) en elk van de volgende elementen zijn de SHAP-waarden voor elke functie.
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
We gebruiken plotly subplot om de SHAP-waarden te visualiseren.
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()