Yorumlanabilirlik - Tablosal SHAP açıklayıcısı

Tablosal sınıflandırma modelini açıklamak için Çekirdek SHAP'sini (SHapley Additive exPlanations) kullanın. Çekirdek SHAP, her özelliğin bir modelin tahminine katkısını tahmin eden modelden bağımsız bir yöntemdir. Yetişkin Sayım Geliri veri kümesinde lojistik regresyon modeli eğitip synapseML TabularSHAP transformatörlerini kullanarak özellik düzeyinde açıklamaları hesaplarsınız.

Prerequisites

  • Çalışma alanınızda yeni bir not defteri oluşturun ve bunu bir lakehouse’a bağlayın. Daha fazla bilgi için bkz. Not defteri oluşturma.

SynapseML, PySpark, pandas ve plotly, Fabric not defteri ortamlarına önceden yüklenmiştir. Ek paket yüklemesi gerekmez.

Paketleri içeri aktarma ve yardımcı UDF'leri tanımlama

Fabric not defterinizde aşağıdaki kodu bir hücreye yapıştırın ve çalıştırın. Bu adım gerekli kitaplıkları içeri aktarır ve daha sonra vektör öğelerini ayıklamak için iki kullanıcı tanımlı işlev (UDF) tanımlar.

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

Doğrula: Aşağıdaki kodu yeni bir hücrede çalıştırın. TabularSHAP imported successfully çıktısını görmeniz gerekir.

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

Verileri yükleme ve sınıflandırma modelini eğitma

Azure Blob Depolama'dan Yetişkin Nüfus Sayımı Geliri veri kümesini yükleyin, hedef etiketi dizine alın ve lojistik regresyon işlem hattını eğitin.

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)

Doğrula: Aşağıdaki hücreyi çalıştırın. Eğitim verileri için satır sayılarını ve işlem hattı aşamalarının onayını görmeniz gerekir.

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

Açıklayacak gözlemleri seçin

Puanlanan eğitim verilerinden rastgele beş gözlem seçin. Bu gözlemler, SHAP açıklamaları oluşturduğunuz örneklerdir.

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

Doğrulama: Örnek boyutunu onaylayın.

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

TabularSHAP'ı yapılandırma ve çalıştırma

Bir TabularSHAP açıklayıcı oluşturun ve bunu seçili gözlemlere uygulayın. Anahtar parametreler şunlardır:

Parametre Description
inputCols Modelin tahmin için kullandığı özellik sütunları.
outputCol SHAP çıkış değerlerini içeren sütunun adı.
numSamples Çekirdek SHAP tahmini için pertürbasyon örneklerinin sayısı. Daha yüksek değerler daha doğru ama daha yavaştır.
model Açıklayacak eğitilmiş işlem hattı modeli.
targetCol Açıklayacak model çıkış sütunu. Bu örnekte sütun şeklindedir probability.
targetClasses Açıklayacak sınıf dizinleri. [1] yalnızca sınıf 1 olasılığını açıklar. Her iki sınıfı da açıklamak için kullanın [0, 1] .
backgroundData Özellikleri entegre etmek için referans dağılım olarak kullanılan bir eğitim verisi örneği.
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

Bu adım, numSamples ve küme boyutuna bağlı olarak birkaç dakika sürebilir. numSamples=5000 ve beş gözlemle, varsayılan Fabric Spark kümesinde 3 ila 10 dakika sürmesi beklenir.

Doğrulama: SHAP çıkış sütununun mevcut olup olmadığını denetleyin.

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

SHAP değerlerini ayıklama

Sonuç DataFrame'inden sınıf 1 olasılık ve SHAP değerlerini ayıklayın. Her gözlem için SHAP değerleri vektörü temel değerle (arka plan veri kümesinin ortalama çıkışı) ve ardından özellik başına bir değerle başlar.

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)

Doğrulama: pandas DataFrame yapısını onaylayın.

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

SHAP değerlerini görselleştirme

Her bir gözlem için, her özelliğin tahmin edilen olasılığa nasıl katkıda bulunduğunu gösteren bir çubuk grafik oluşturun.

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

Doğrula: Çizim nesnesinin oluşturulduğunu onaylayın.

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

Sonuçları yorumlama

Her alt grafik bir gözlemi temsil eder. Çubuklar aşağıdakileri gösterir:

  • Temel: Arka plan veri kümesi genelinde ortalama model çıkışı (temel olasılık).
  • Pozitif SHAP değerleri: Tahmini sınıf 1'e (gelir 50 binden büyük) yönlendiren özellikler.
  • Negatif SHAP değerleri: Tahmini sınıf 0'a (50 binden küçük veya buna eşit gelir) yönlendiren özellikler.

Temel değerin ve tüm özellik SHAP değerlerinin toplamı, modelin bu gözlem için tahmin edilen olasılığına eşittir.

Sorun giderme

Sorun Nedeni Çözünürlük
OutOfMemoryError TabularSHAP sırasında numSamples kullanılabilir bellek için çok büyük. örneğin, değerini 1.000'e düşürün numSamplesveya Spark yürütücü belleğini artırın.
SHAP dönüşümü yavaş Birçok özellik ile yüksek numSamples işlem süresini artırır. Daha hızlı keşif sonuçları için 1.000-2.000'e düşürün numSamples . Nihai analiz için artır.
FileNotFoundException parquet için 'a mmlspark.blob.core.windows.net ağ erişimi engellendi. Fabric çalışma alanınızın giden İnternet erişimine sahip olduğunu doğrulayın. Alternatif olarak, veri kümesini lakehouse'unuza yükleyin.
shapValues sütun null içeriyor Özellik değerleri eğitim dağıtımının dışındaysa bazı gözlemler başarısız olabilir. Giriş özelliklerinde null veya beklenmeyen değerler olup olmadığını denetleyin. Sonuçlardan gelen null değerleri filtreleyin.
display() çıktı göstermiyor Kod, Fabric not defteri ortamının dışında çalışıyor. Standart Python ortamlarında shaps_local.head() veya print(shaps_local) kullanın.

Temizleme

Veri kümesini bu öğretici için bir lakehouse'a yüklediyseniz, ücretsiz depolama alanına kaldırın:

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