intial commit (forked from private repo)

This commit is contained in:
2025-04-11 11:08:28 +02:00
commit 3bdd37f46c
154 changed files with 45901 additions and 0 deletions

BIN
Aufgabenstellung.pdf Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

260
README.md Normal file
View File

@ -0,0 +1,260 @@
# Explainable AI Projektaufgabe - Adult Census Income Datensatz
Dieses Repository enthält eine Projektaufgabe zum Thema Explainable AI (XAI), die anhand des Adult Census Income Datensatzes demonstriert wird. Das Projekt verwendet einen Random Forest Classifier, um Einkommensvorhersagen zu treffen, und verschiedene Methoden zur Erklärung der Modellentscheidungen, darunter Feature Importance, LIME (Local Interpretable Model-agnostic Explanations) und regelbasierte Erklärungen mit dem RIPPER-Algorithmus.
## Inhaltsverzeichnis
1. [Projektübersicht](#projektübersicht)
2. [Datensatz](#datensatz)
3. [Notebook-Struktur](#notebook-struktur)
4. [Datenvorverarbeitung](#datenvorverarbeitung)
5. [Modelltraining](#modelltraining)
6. [Hyperparameter-Tuning](#hyperparameter-tuning)
7. [Modellbewertung](#modellbewertung)
8. [Explainable AI Methoden](#explainable-ai-methoden)
- [Feature Importance](#feature-importance)
- [LIME](#lime)
- [Regelbasierte Erklärungen (RIPPER)](#regelbasierte-erklärungen-ripper)
- [Vergleich der Methoden](#vergleich-der-methoden)
9. [Anwendung auf einzelne Beispiele](#anwendung-auf-einzelne-beispiele)
10. [Voraussetzungen und Installation](#voraussetzungen-und-installation)
11. [Fazit](#fazit)
## Projektübersicht
Dieses Projekt demonstriert die Anwendung von Explainable AI-Techniken auf ein klassisches Machine Learning-Problem: die Vorhersage, ob eine Person ein Einkommen von mehr als 50.000 Dollar pro Jahr erzielt. Während die Vorhersagegenauigkeit wichtig ist, liegt der Schwerpunkt dieses Projekts auf der Erklärbarkeit der Modellentscheidungen. Wir verwenden verschiedene Methoden (Feature Importance, LIME und regelbasierte Erklärungen), um zu verstehen, welche Faktoren das Einkommensniveau am stärksten beeinflussen.
## Datensatz
Der Adult Census Income Datensatz (auch bekannt als "Census Income" oder "Adult" Datensatz) enthält demografische Informationen aus der US-Volkszählung und wird verwendet, um vorherzusagen, ob eine Person ein Einkommen von mehr als 50.000 Dollar pro Jahr erzielt. Der Datensatz enthält folgende Attribute:
- **age**: Alter der Person
- **c**: Arbeitsklasse (Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked)
- **fnlwgt**: Gewichtungsfaktor (repräsentiert die Anzahl der Personen mit ähnlichen demografischen Merkmalen)
- **education**: Höchster Bildungsabschluss (Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool)
- **education.num**: Numerische Darstellung des Bildungsabschlusses
- **marital.status**: Familienstand (Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse)
- **occupation**: Beruf
- **relationship**: Beziehungsstatus (Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried)
- **race**: Ethnische Zugehörigkeit (White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other, Black)
- **sex**: Geschlecht (Female, Male)
- **c**: Kapitalgewinn
- **capital.loss**: Kapitalverlust
- **hours.per.week**: Arbeitsstunden pro Woche
- **native.country**: Herkunftsland
- **income**: Zielvariable, gibt an, ob das Einkommen über 50.000 Dollar liegt (>50K) oder nicht (<=50K)
## Notebook-Struktur
Das Jupyter Notebook `Explainable_AI_Adult_Census_Income.ipynb` ist in folgende Abschnitte unterteilt:
1. **Abhängigkeiten installieren**: Installation der benötigten Python-Bibliotheken (scikit-learn, matplotlib, seaborn, pandas, numpy, lime, wittgenstein)
2. **Daten einlesen und erkunden**: Laden und erste Analyse der Daten
3. **Datenvorverarbeitung**: Behandlung fehlender Werte und Umwandlung kategorischer Variablen
4. **Daten aufteilen und Modell trainieren**: Aufteilung der Daten in Trainings- und Testsets und Training des Random Forest Modells
5. **Feature Importance**: Analyse der wichtigsten Features für die Vorhersage
6. **Hyperparameter-Tuning**: Optimierung der Modellparameter mit GridSearchCV
7. **LIME für Erklärbarkeit**: Verwendung von LIME für lokale Erklärungen
8. **Regelbasierte Erklärungen**: Verwendung des RIPPER-Algorithmus für interpretierbare Regeln
9. **Anwendung der Regeln auf einzelne Beispiele**: Demonstration, wie die Regeln auf einzelne Datenpunkte angewendet werden
10. **Vergleich zwischen LIME und regelbasierten Erklärungen**: Vergleich der verschiedenen Erklärungsmethoden
11. **Diskussion und Fazit**: Zusammenfassung der Ergebnisse und Schlussfolgerungen
## Datenvorverarbeitung
Die Datenvorverarbeitung umfasst mehrere Schritte:
1. **Behandlung fehlender Werte**:
- Identifizierung von Einträgen mit '?' als fehlende Werte
- Ersetzung fehlender Werte durch den häufigsten Wert in der jeweiligen Spalte
2. **Umwandlung kategorischer Variablen**:
- Label Encoding für die Zielvariable 'income'
- One-Hot-Encoding für alle anderen kategorischen Variablen
Diese Schritte sind wichtig, um die Daten in ein Format zu bringen, das von Machine Learning-Algorithmen verarbeitet werden kann.
## Modelltraining
Für das Modelltraining verwenden wir einen Random Forest Classifier, der folgende Vorteile bietet:
- Gute Leistung bei verschiedenen Arten von Daten
- Robustheit gegenüber Overfitting
- Fähigkeit, mit fehlenden Werten und kategorischen Variablen umzugehen
- Eingebaute Feature Importance
Der Trainingsprozess umfasst:
1. Initialisierung des Random Forest Classifiers mit 100 Entscheidungsbäumen
2. Training des Modells mit den Trainingsdaten
3. Vorhersage für die Testdaten
4. Berechnung von Metriken wie Accuracy, Precision, Recall und F1-Score
## Hyperparameter-Tuning
Um die Leistung des Modells zu optimieren, wird Hyperparameter-Tuning mit GridSearchCV durchgeführt. Folgende Parameter werden optimiert:
- **n_estimators**: Anzahl der Bäume im Random Forest (50, 100)
- **max_depth**: Maximale Tiefe der Bäume (None, 10, 20)
- **min_samples_split**: Minimale Anzahl von Samples, um einen Knoten zu teilen (2, 5)
- **min_samples_leaf**: Minimale Anzahl von Samples in einem Blatt (1, 2)
GridSearchCV testet alle möglichen Kombinationen dieser Parameter mit 3-facher Kreuzvalidierung und wählt die beste Kombination aus. Das optimierte Modell wird dann für die endgültigen Vorhersagen verwendet.
## Modellbewertung
Die Modellbewertung erfolgt anhand mehrerer Metriken:
- **Accuracy**: Anteil der korrekt klassifizierten Instanzen
- **Precision**: Anteil der als positiv klassifizierten Instanzen, die tatsächlich positiv sind
- **Recall**: Anteil der tatsächlich positiven Instanzen, die als positiv klassifiziert wurden
- **F1-Score**: Harmonisches Mittel aus Precision und Recall
Zusätzlich werden visuelle Darstellungen verwendet:
- **Confusion Matrix**: Zeigt die Anzahl der True Positives, False Positives, True Negatives und False Negatives
- **Klassifikationsbericht**: Detaillierte Metriken für jede Klasse
Diese Metriken helfen zu verstehen, wie gut das Modell funktioniert und ob es bestimmte Klassen besser vorhersagt als andere.
## Explainable AI Methoden
### Feature Importance
Die einfachste Methode zur Erklärung des Modells ist die eingebaute Feature Importance des Random Forest Classifiers. Diese zeigt, wie wichtig jedes Feature für die Vorhersage ist, basierend auf der durchschnittlichen Verringerung der Unreinheit (Impurity) über alle Bäume.
Vorteile:
- Einfach zu berechnen und zu verstehen
- Gibt einen globalen Überblick über die Wichtigkeit der Features
Nachteile:
- Zeigt nur die globale Wichtigkeit, nicht die Auswirkung auf einzelne Vorhersagen
- Berücksichtigt keine Feature-Interaktionen
### LIME
LIME (Local Interpretable Model-agnostic Explanations) ist eine Methode zur Erklärung einzelner Vorhersagen, indem ein lokales lineares Modell um die Vorhersage herum erstellt wird.
In unserem Projekt verwenden wir LIME für:
1. **Lokale Erklärungen**: Erklärung einzelner Vorhersagen durch Approximation mit einem linearen Modell
2. **Feature-Wichtigkeit**: Identifikation der wichtigsten Features für eine einzelne Vorhersage
Vorteile:
- Modellunabhängig (kann mit jedem Modell verwendet werden)
- Einfach zu verstehen (basiert auf linearen Modellen)
- Fokussiert auf lokale Erklärungen
Nachteile:
- Ergebnisse können instabil sein (abhängig von der Stichprobe)
- Keine globalen Erklärungen
### Regelbasierte Erklärungen (RIPPER)
RIPPER (Repeated Incremental Pruning to Produce Error Reduction) ist ein Algorithmus zur Extraktion von Regeln aus Daten. Er erstellt eine Reihe von Wenn-Dann-Regeln, die leicht zu interpretieren sind.
In unserem Projekt verwenden wir den RIPPER-Algorithmus (implementiert in der Wittgenstein-Bibliothek) für:
1. **Globale Erklärungen**: Extraktion von interpretierbaren Regeln aus den Daten
2. **Regelbasierte Vorhersagen**: Verwendung der Regeln für Vorhersagen
Vorteile:
- Sehr interpretierbar (Regeln sind in natürlicher Sprache)
- Bietet globale Erklärungen für das gesamte Modell
- Kann direkt für Vorhersagen verwendet werden
Nachteile:
- Möglicherweise geringere Genauigkeit als komplexere Modelle
- Kann bei großen Datensätzen rechenintensiv sein
### Vergleich der Methoden
In unserem Projekt vergleichen wir die Erklärungen von LIME und RIPPER, um ein umfassenderes Verständnis der Modellentscheidungen zu gewinnen. Dieser Vergleich zeigt:
- Welche Features von beiden Methoden als wichtig identifiziert werden
- Wo die Methoden unterschiedliche Erklärungen liefern
- Die Vor- und Nachteile jeder Methode in Bezug auf Interpretierbarkeit und Genauigkeit
## Anwendung auf einzelne Beispiele
Ein wichtiger Aspekt von Explainable AI ist die Fähigkeit, einzelne Vorhersagen zu erklären. Im Notebook wird ein zufälliges Beispiel aus den Testdaten ausgewählt und detailliert erklärt:
1. Anzeige des tatsächlichen und vorhergesagten Labels
2. Erklärung mit LIME, die ein lokales lineares Modell um die Vorhersage herum erstellt
3. Anwendung der RIPPER-Regeln auf das Beispiel
4. Vergleich der Vorhersagen des Random Forest Modells und der RIPPER-Regeln
Diese Erklärung hilft zu verstehen, warum das Modell eine bestimmte Vorhersage für eine bestimmte Person getroffen hat, was in realen Anwendungen sehr wertvoll sein kann.
## Voraussetzungen und Installation
Um dieses Projekt auszuführen, benötigen Sie:
1. Python 3.6 oder höher
2. Jupyter Notebook oder JupyterLab
3. Folgende Python-Bibliotheken:
- scikit-learn
- pandas
- numpy
- matplotlib
- seaborn
- lime
- wittgenstein
Sie können die benötigten Bibliotheken mit folgendem Befehl installieren:
```bash
pip install scikit-learn pandas numpy matplotlib seaborn lime wittgenstein
```
## Fazit
Dieses Projekt demonstriert, wie Machine Learning-Modelle nicht nur für Vorhersagen, sondern auch für das Verständnis der zugrunde liegenden Daten und Zusammenhänge verwendet werden können. Durch die Verwendung verschiedener Explainable AI-Techniken wie Feature Importance, LIME und regelbasierte Erklärungen können wir:
1. Die wichtigsten Faktoren identifizieren, die das Einkommensniveau beeinflussen
2. Verstehen, wie diese Faktoren zusammenwirken
3. Einzelne Vorhersagen detailliert erklären
4. Verschiedene Erklärungsmethoden vergleichen und ihre Stärken und Schwächen verstehen
Der Vergleich zwischen LIME und regelbasierten Erklärungen zeigt, dass beide Methoden wertvolle Einblicke bieten können:
- LIME bietet detaillierte lokale Erklärungen für einzelne Vorhersagen
- Regelbasierte Erklärungen bietet globale, leicht verständliche Regeln, die das Gesamtverhalten des Modells beschreiben
Die Kombination verschiedener XAI-Methoden ermöglicht ein umfassenderes Verständnis der Modellentscheidungen und kann dazu beitragen, das Vertrauen in Machine Learning-Modelle zu erhöhen.
# Modell und XAI-Methoden im Überblick
## Random Forest
Random Forest ist ein Ensemble-Lernalgorithmus, der mehrere Entscheidungsbäume kombiniert, um präzisere Vorhersagen zu treffen. Er funktioniert, indem er:
Viele verschiedene Entscheidungsbäume auf zufällig ausgewählten Teilmengen der Trainingsdaten aufbaut
Bei jedem Split nur eine zufällige Teilmenge der Features berücksichtigt
Die Ergebnisse aller Bäume mittelt (bei Regression) oder durch Mehrheitsentscheidung kombiniert (bei Klassifikation)
Random Forests sind beliebt wegen ihrer hohen Genauigkeit, Robustheit gegen Overfitting und geringen Parameteroptimierungsbedarf.
## LIME (Local Interpretable Model-agnostic Explanations)
LIME ist eine Methode zur Erklärung der Vorhersagen beliebiger Machine Learning Modelle. LIME funktioniert durch:
Erzeugung von Stichproben in der Nähe der zu erklärenden Instanz
Training eines lokalen, interpretierbaren Modells (z.B. linearer Regression) auf diesen Stichproben
Nutzung der Gewichte des lokalen Modells, um zu zeigen, welche Features die Vorhersage am stärksten beeinflussen
LIME ermöglicht es, komplexe Modelle wie neuronale Netze oder Random Forests lokal zu interpretieren, ohne deren innere Struktur zu kennen.
## Rule-Based Explanations
Rule-Based Explanations verwenden Wenn-Dann-Regeln, um Modellvorhersagen zu erklären. Sie:
Extrahieren verständliche Entscheidungsregeln aus komplexen Modellen
Präsentieren Bedingungen, die zu bestimmten Vorhersagen führen
Können aus regelbasierten Modellen direkt abgeleitet oder aus komplexeren Modellen approximiert werden
Methoden wie RIPPER, CART oder die Extraktion von Regeln aus Random Forests fallen in diese Kategorie. Diese Erklärungen sind besonders wertvoll für Domänenexperten ohne ML-Hintergrund, da sie in natürlicher Sprache formuliert werden können.

22
extract_cells.py Normal file
View File

@ -0,0 +1,22 @@
import json
import os
from pathlib import Path
def extractToFiles(filePath: str, folder: str = "extracted_cells"):
with open(filePath, "r") as file:
fileContent = file.read()
content = json.loads(fileContent)
i=0
for cell in content["cells"]:
if cell["cell_type"] == "code":
codeStr = ""
for line in cell["source"]:
codeStr += line
Path(folder).mkdir(parents=True, exist_ok=True)
location = os.path.join(folder, f"cell{i}.py")
with open(location, "w+") as file:
file.write(codeStr)
i += 1
extractToFiles("Explainable_AI_Adult_Census_Income.ipynb")

1
extracted_cells/cell0.py Normal file
View File

@ -0,0 +1 @@
%pip install scikit-learn matplotlib seaborn pandas numpy lime

15
extracted_cells/cell1.py Normal file
View File

@ -0,0 +1,15 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report
from sklearn.preprocessing import LabelEncoder
from sklearn.tree import plot_tree
# Daten einlesen
df = pd.read_csv("./sample_data/adult_census_income/adult.csv")
# Anzeigen der ersten Zeilen des Datensatzes
df.head(10)

11
extracted_cells/cell10.py Normal file
View File

@ -0,0 +1,11 @@
# Features und Zielwerte definieren
X = df_encoded.drop(['income', 'income_encoded'], axis=1)
y = df_encoded['income_encoded']
# Daten in Trainings- und Testdaten aufteilen
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
print(f"Trainingsdaten: {X_train.shape[0]} Beispiele")
print(f"Testdaten: {X_test.shape[0]} Beispiele")
print(f"Features: {X_train.shape[1]} Merkmale")

17
extracted_cells/cell11.py Normal file
View File

@ -0,0 +1,17 @@
# Random Forest Modell trainieren
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
# Vorhersagen für die Testdaten
y_pred = rf_model.predict(X_test)
# Modellleistung evaluieren
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.4f}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")

16
extracted_cells/cell12.py Normal file
View File

@ -0,0 +1,16 @@
# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False,
xticklabels=['<=50K', '>50K'],
yticklabels=['<=50K', '>50K'])
plt.xlabel('Vorhergesagt')
plt.ylabel('Tatsächlich')
plt.title('Confusion Matrix')
plt.savefig('output/Confusions_Matrix.png', dpi=300)
plt.show()
# Klassifikationsbericht
print("\nKlassifikationsbericht:")
print(classification_report(y_test, y_pred, target_names=['<=50K', '>50K']))

12
extracted_cells/cell13.py Normal file
View File

@ -0,0 +1,12 @@
# Visualisierung eines einzelnen Entscheidungsbaums aus dem Random Forest
plt.figure(figsize=(25, 12))
tree_to_plot = rf_model.estimators_[0] # Ersten Baum aus dem Forest auswählen
plot_tree(tree_to_plot,
feature_names=X_train.columns,
class_names=['<=50K', '>50K'],
filled=True,
rounded=True,
fontsize=10,
max_depth=3)
plt.savefig('output/Random_Forest_Tree_Example.png', dpi=300)
plt.show()

12
extracted_cells/cell14.py Normal file
View File

@ -0,0 +1,12 @@
# Feature Importance
feature_importance = pd.DataFrame({
'Feature': X_train.columns,
'Importance': rf_model.feature_importances_
}).sort_values('Importance', ascending=False)
plt.figure(figsize=(12, 8))
sns.barplot(x='Importance', y='Feature', data=feature_importance.head(15))
plt.title('Feature Importance')
plt.tight_layout()
plt.savefig('output/Feature_Importance.png', dpi=300)
plt.show()

33
extracted_cells/cell15.py Normal file
View File

@ -0,0 +1,33 @@
# Hyperparameter-Grid definieren
param_grid = {
'n_estimators': [50, 100],
'max_depth': [None, 10, 20],
'min_samples_split': [2, 5],
'min_samples_leaf': [1, 2]
}
# GridSearchCV
grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=3, scoring='accuracy')
grid_search.fit(X_train, y_train)
# Beste Parameter
print("Beste Parameter:")
print(grid_search.best_params_)
# Bestes Modell
best_rf_model = grid_search.best_estimator_
# Vorhersagen mit dem besten Modell
y_pred_best = best_rf_model.predict(X_test)
# Modellleistung evaluieren
accuracy_best = accuracy_score(y_test, y_pred_best)
precision_best = precision_score(y_test, y_pred_best)
recall_best = recall_score(y_test, y_pred_best)
f1_best = f1_score(y_test, y_pred_best)
print(f"\nBestes Modell:")
print(f"Accuracy: {accuracy_best:.4f}")
print(f"Precision: {precision_best:.4f}")
print(f"Recall: {recall_best:.4f}")
print(f"F1 Score: {f1_best:.4f}")

17
extracted_cells/cell16.py Normal file
View File

@ -0,0 +1,17 @@
# LIME für Erklärbarkeit
from lime import lime_tabular
import random
# Erstelle einen LIME-Erklärer
lime_explainer = lime_tabular.LimeTabularExplainer(
X_train.values,
feature_names=X_train.columns,
class_names=['<=50K', '>50K'],
mode='classification',
random_state=42
)
# Wähle ein zufälliges Beispiel aus den Testdaten
random_idx = random.randint(0, len(X_test) - 1)
instance_df = X_test.iloc[random_idx:random_idx+1]
instance = instance_df.values[0] # Für LIME benötigen wir das Array

65
extracted_cells/cell17.py Normal file
View File

@ -0,0 +1,65 @@
# Erkläre die Vorhersage mit LIME
def analyze_lime_feature_importance(instance, rf_model, lime_explainer, num_features=5):
"""
Analysiert die Feature-Wichtigkeiten der LIME-Erklärung.
"""
# LIME-Erklärung generieren
exp = lime_explainer.explain_instance(
instance,
rf_model.predict_proba,
num_features=num_features
)
# Random Forest Vorhersage (nur zur Information)
feature_names = rf_model.feature_names_in_
instance_df = pd.DataFrame([instance], columns=feature_names)
rf_prediction = rf_model.predict_proba(instance_df)[0, 1]
# Feature-Wichtigkeiten aus LIME extrahieren
feature_importance = exp.as_list()
# Visualisierung der Feature-Wichtigkeiten
plt.figure(figsize=(10, 6))
# Features und ihre Wichtigkeiten trennen
features, importances = zip(*feature_importance)
# Balkendiagramm erstellen
colors = ['red' if imp < 0 else 'green' for imp in importances]
y_pos = np.arange(len(features))
plt.barh(y_pos, importances, color=colors)
plt.yticks(y_pos, features)
plt.xlabel('Feature-Einfluss')
plt.title('LIME Feature-Wichtigkeiten')
# Vertikale Linie bei 0 für bessere Lesbarkeit
plt.axvline(x=0, color='black', linestyle='-', alpha=0.3)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('output/lime_feature_importance.png', dpi=300)
plt.show()
# Ausgabe der Ergebnisse
print("\nLIME Feature-Wichtigkeiten Analyse:")
print("-" * 50)
print(f"Random Forest Vorhersage für diese Instanz: {rf_prediction:.4f}")
print("\nFeature-Einflüsse:")
for feature, importance in feature_importance:
print(f"{feature}: {importance:+.4f}")
return {
'rf_prediction': rf_prediction,
'feature_importance': dict(feature_importance)
}
# Analysiere wie gut LIME die RF-Vorhersage erklärt
importance_analysis = analyze_lime_feature_importance(
instance=instance,
rf_model=best_rf_model,
lime_explainer=lime_explainer,
num_features=20
)

49
extracted_cells/cell18.py Normal file
View File

@ -0,0 +1,49 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Wählen wir zwei Features zur Variation:
less_important_feature_education = "education.num"
less_important_feature_fnlwgt = "fnlwgt"
# Festlegen derRange für die Variation der zwei Features
education_range = np.linspace(instance_df[less_important_feature_education].values[0] - 10, instance_df[less_important_feature_education].values[0] + 10, 50)
fnlwgt_range = np.linspace(instance_df[less_important_feature_fnlwgt].values[0] - 100000, instance_df[less_important_feature_fnlwgt].values[0] + 100000, 50)
# Erstellen von Instanzen für LIME
instances_education = pd.DataFrame([instance] * len(education_range), columns=X_train.columns)
instances_fnlwgt = pd.DataFrame([instance] * len(fnlwgt_range), columns=X_train.columns)
# Ändern der Feature-Werte in den Instanzen
instances_education[less_important_feature_education] = education_range
instances_fnlwgt[less_important_feature_fnlwgt] = fnlwgt_range
# Vorhersagen mit dem Modell (Wahrscheinlichkeiten)
instances_education["prediction"] = best_rf_model.predict_proba(instances_education)[:, 1]
instances_fnlwgt["prediction"] = best_rf_model.predict_proba(instances_fnlwgt)[:, 1]
# Bestimmen der y-Achsen-Grenzen (min/max für alle Vorhersagen)
y_min = min(instances_education["prediction"].min(), instances_fnlwgt["prediction"].min())
y_max = max(instances_education["prediction"].max(), instances_fnlwgt["prediction"].max())
# Visualisierung der Variation von 'education-num' (moderater Einfluss)
plt.figure(figsize=(8,5))
plt.plot(education_range, instances_education["prediction"], label="Moderater Einfluss auf die Vorhersage", color='green')
plt.axvline(instance_df[less_important_feature_education].values[0], color="red", linestyle="dashed", label="Originalwert")
plt.xlabel("Bildungsniveau (education-num)")
plt.ylabel("Vorhersage (0 = <=50K, 1 = >50K)")
plt.title(f"Einfluss von {less_important_feature_education} auf die Vorhersage")
plt.ylim([y_min, y_max]) # Einheitliche y-Achse
plt.legend()
plt.show()
# Visualisierung der Variation von 'fnlwgt' (wenig Einfluss)
plt.figure(figsize=(8,5))
plt.plot(fnlwgt_range, instances_fnlwgt["prediction"], label="Wenig Einfluss auf die Vorhersage", color='orange')
plt.axvline(instance_df[less_important_feature_fnlwgt].values[0], color="red", linestyle="dashed", label="Originalwert")
plt.xlabel("Finales Gewicht (fnlwgt)")
plt.ylabel("Vorhersage (0 = <=50K, 1 = >50K)")
plt.title(f"Einfluss von {less_important_feature_fnlwgt} auf die Vorhersage")
plt.ylim([y_min, y_max]) # Einheitliche y-Achse
plt.legend()
plt.show()

40
extracted_cells/cell19.py Normal file
View File

@ -0,0 +1,40 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics.pairwise import euclidean_distances
# Anzahl der zu erzeugenden Perturbationen
num_samples = 500
# Die Originalinstanz, die wir erklären wollen
original_instance = instance_df.iloc[0].copy()
# Erstelle perturbierte Instanzen durch zufällige Variation aller Features
perturbed_instances = pd.DataFrame(
np.random.normal(loc=original_instance, scale=1.0, size=(num_samples, len(original_instance))),
columns=original_instance.index
)
# Vorhersagen für die perturbierten Instanzen mit dem Random-Forest-Modell
perturbed_instances["prediction"] = best_rf_model.predict_proba(perturbed_instances)[:, 1]
# Berechnung der Gewichte nach Distanz zur Originalinstanz
distances = euclidean_distances(perturbed_instances.drop(columns=["prediction"]), [original_instance])
kernel_width = np.sqrt(len(original_instance)) # Kernel-Bandbreite
weights = np.exp(- (distances ** 2) / (2 * (kernel_width ** 2)))
# Gewichtete lokale lineare Regression zum Erklären der Vorhersage
lin_reg = LinearRegression()
lin_reg.fit(perturbed_instances.drop(columns=["prediction"]), perturbed_instances["prediction"], sample_weight=weights.flatten())
# Anzeige der Feature-Wichtigkeiten
feature_importances = pd.Series(lin_reg.coef_, index=original_instance.index).sort_values(key=abs, ascending=False)
# Visualisierung der wichtigsten Features
plt.figure(figsize=(8, 5))
feature_importances[:10].plot(kind="barh", color="skyblue")
plt.xlabel("Einfluss auf die Vorhersage")
plt.title("Erklärungsmodell (Nachbildung von LIME)")
plt.gca().invert_yaxis()
plt.show()

2
extracted_cells/cell2.py Normal file
View File

@ -0,0 +1,2 @@
# Informationen über den Datensatz
print("Datensatzgröße:", df.shape)

25
extracted_cells/cell20.py Normal file
View File

@ -0,0 +1,25 @@
def predict_fn(x):
# Konvertiere das NumPy-Array zurück in ein DataFrame mit den richtigen Feature-Namen
df = pd.DataFrame(x, columns=X_train.columns)
return best_rf_model.predict_proba(df)
# Speichere mehrere Erklärungen
explanations = []
for i in range(100):
exp = lime_explainer.explain_instance(
instance,
predict_fn,
num_features=20
)
explanations.append(exp.as_list())
# Berechne die Varianz der Feature-Wichtigkeiten
feature_variances = {}
for i in range(len(explanations[0])):
feature = explanations[0][i][0]
values = [exp[i][1] for exp in explanations if len(exp) > i and exp[i][0] == feature]
feature_variances[feature] = np.var(values)
print("Varianz der Feature-Wichtigkeiten:")
for feature, variance in feature_variances.items():
print(f"{feature}: {variance:.6f}")

53
extracted_cells/cell21.py Normal file
View File

@ -0,0 +1,53 @@
# Erstelle nur das farbcodierte Stabilitätsdiagramm für die Top-5 Features
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
# Anzahl der anzuzeigenden Features (Top-N mit höchster Varianz)
num_features_to_show = 5 # Top-5 Features
# Sortiere die Features nach Varianz (absteigend)
sorted_features = sorted(feature_variances.items(), key=lambda x: x[1], reverse=True)
# Beschränke auf die Top-N Features
sorted_features = sorted_features[:num_features_to_show]
features = [item[0] for item in sorted_features]
variances = [item[1] for item in sorted_features]
# Definiere Schwellenwerte für die Farbkodierung
low_threshold = 0.0001
medium_threshold = 0.001
# Farbkodierung basierend auf Varianzwerten
colors = []
for v in variances:
if v < low_threshold:
colors.append('green') # Niedrige Varianz - sehr stabil
elif v < medium_threshold:
colors.append('orange') # Mittlere Varianz - mäßig stabil
else:
colors.append('red') # Hohe Varianz - instabil
# Erstelle das Balkendiagramm mit Farbkodierung
plt.figure(figsize=(10, 6))
bars = plt.barh(features, variances, color=colors)
# Füge Werte am Ende der Balken hinzu
for i, v in enumerate(variances):
plt.text(v + 0.00001, i, f"{v:.6f}", va='center')
# Füge eine Legende hinzu
legend_elements = [
Patch(facecolor='green', label='Sehr stabil (< 0.0001)'),
Patch(facecolor='orange', label='Mäßig stabil (< 0.001)'),
Patch(facecolor='red', label='Instabil (≥ 0.001)')
]
plt.legend(handles=legend_elements, loc='lower right')
plt.xlabel('Varianz')
plt.title('Stabilität der Top-5 LIME-Features')
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.tight_layout()
# Speichere die Abbildung
plt.savefig('output/Lime_Varianz_Features.png', dpi=300)
plt.show()

14
extracted_cells/cell22.py Normal file
View File

@ -0,0 +1,14 @@
from sklearn.tree import DecisionTreeClassifier, export_text
# Random Forest-Vorhersagen verwenden als Ziel
rf_predictions = rf_model.predict(X_train)
# Einfachen Entscheidungsbaum auf die Vorhersagen des Random Forests trainieren
surrogate_tree = DecisionTreeClassifier(max_depth=5)
surrogate_tree.fit(X_train, rf_predictions)
# Evaluieren, wie gut der Baum den Random Forest approximiert
surrogate_predictions = surrogate_tree.predict(X_test)
rf_test_predictions = rf_model.predict(X_test)
surrogate_accuracy = np.mean(surrogate_predictions == rf_test_predictions)
print(f"Genauigkeit des Surrogate-Modells: {surrogate_accuracy:.4f}")

13
extracted_cells/cell23.py Normal file
View File

@ -0,0 +1,13 @@
# Formatierte Regeln anzeigen
def format_rules(tree_rules):
"""Formatiert die Baumregeln mit menschenlesbaren Klassennamen"""
# Ersetze 'class: 0' durch 'Einkommen ≤ 50K'
formatted_rules = tree_rules.replace('class: 0', 'Einkommen ≤ 50K')
# Ersetze 'class: 1' durch 'Einkommen > 50K'
formatted_rules = formatted_rules.replace('class: 1', 'Einkommen > 50K')
return formatted_rules
# Regeln aus dem Surrogate-Baum extrahieren
tree_rules = export_text(surrogate_tree, feature_names=X_train.columns.tolist())
#print(tree_rules)
print(format_rules(tree_rules))

69
extracted_cells/cell24.py Normal file
View File

@ -0,0 +1,69 @@
from sklearn.tree import _tree
def extract_single_rule(tree, feature_names, class_to_extract=1):
"""
Extrahiert eine einzelne Regel aus einem Decision Tree für eine bestimmte Klasse.
Parameters:
-----------
tree : DecisionTreeClassifier
Der trainierte Entscheidungsbaum
feature_names : list
Liste der Feature-Namen
class_to_extract : int, default=1
Die Klasse, für die eine Regel extrahiert werden soll (0=≤50K, 1=>50K)
Returns:
--------
rule : str
Eine lesbare Regel als String
"""
tree_ = tree.tree_
# Funktion zum rekursiven Extrahieren einer Regel
def tree_to_rule(node, depth, conditions):
# Wenn wir einen Blattknoten erreicht haben
if tree_.children_left[node] == _tree.TREE_LEAF:
# Prüfe, ob dieser Blattknoten die gewünschte Klasse vorhersagt
if np.argmax(tree_.value[node][0]) == class_to_extract:
# Formatiere die Bedingungen als Regel
if conditions:
rule = " UND ".join(conditions)
return rule
else:
return "Keine Bedingungen (Wurzelklasse)"
return None
# Feature und Schwellenwert am aktuellen Knoten
feature = feature_names[tree_.feature[node]]
threshold = tree_.threshold[node]
# Linkspfad (≤)
left_conditions = conditions + [f"{feature}{threshold:.2f}"]
left_rule = tree_to_rule(tree_.children_left[node], depth + 1, left_conditions)
if left_rule is not None:
return left_rule
# Rechtspfad (>)
right_conditions = conditions + [f"{feature} > {threshold:.2f}"]
right_rule = tree_to_rule(tree_.children_right[node], depth + 1, right_conditions)
if right_rule is not None:
return right_rule
# Keine passende Regel gefunden
return None
# Starte die Suche vom Wurzelknoten
rule = tree_to_rule(0, 1, [])
# Formatiere die Ausgabe
class_name = "Einkommen > 50K" if class_to_extract == 1 else "Einkommen ≤ 50K"
if rule:
return f"WENN {rule} DANN {class_name}"
else:
return f"Keine Regel für {class_name} gefunden."
# Anwendung für die Extraktion einer Regel für hohes Einkommen (Klasse 1)
single_rule = extract_single_rule(surrogate_tree, X_train.columns.tolist(), class_to_extract=1)
print("Einzelne Regel aus dem Surrogate-Modell:")
print(single_rule)

67
extracted_cells/cell25.py Normal file
View File

@ -0,0 +1,67 @@
import matplotlib.pyplot as plt
import numpy as np
def plot_surrogate_accuracy_vs_depth_test_only(rf_model, X_train, X_test, max_depths=range(1, 16)):
"""
Visualisiert die Genauigkeit des Surrogate-Modells für verschiedene Baumtiefen,
fokussiert nur auf die Testdaten.
"""
# Random Forest-Vorhersagen (nur einmal berechnen)
rf_train_predictions = rf_model.predict(X_train)
rf_test_predictions = rf_model.predict(X_test)
# Ergebnisse für verschiedene Baumtiefen
test_accuracies = []
for depth in max_depths:
# Surrogate-Baum mit aktueller Tiefe trainieren
surrogate_tree = DecisionTreeClassifier(max_depth=depth, random_state=42)
surrogate_tree.fit(X_train, rf_train_predictions)
# Vorhersagen
surrogate_test_pred = surrogate_tree.predict(X_test)
# Genauigkeit berechnen
test_acc = np.mean(surrogate_test_pred == rf_test_predictions)
test_accuracies.append(test_acc)
# Visualisierung
plt.figure(figsize=(10, 6))
plt.plot(max_depths, test_accuracies, 'o-', color='#ED7D31', linewidth=2)
# Finde die beste Tiefe
best_depth = max_depths[np.argmax(test_accuracies)]
best_acc = max(test_accuracies)
# Markiere den besten Punkt
plt.scatter([best_depth], [best_acc], s=100, c='red', zorder=5)
plt.annotate(f'Optimale Tiefe: {best_depth}\nGenauigkeit: {best_acc:.4f}',
xy=(best_depth, best_acc), xytext=(best_depth+1, best_acc-0.05),
arrowprops=dict(facecolor='black', shrink=0.05, width=1.5))
# Beschriftungen und Layout
plt.grid(alpha=0.3)
plt.title('Surrogate-Modell-Genauigkeit bei verschiedenen Baumtiefen', fontsize=14)
plt.xlabel('Maximale Baumtiefe', fontsize=12)
plt.ylabel('Genauigkeit auf Testdaten', fontsize=12)
# Füge Werte über den Punkten hinzu
for i, acc in enumerate(test_accuracies):
plt.text(max_depths[i], acc + 0.01, f'{acc:.3f}', ha='center')
# Y-Achse anpassen (je nach Daten)
y_min = max(0, min(test_accuracies) - 0.05)
plt.ylim(y_min, 1.05)
# Verbesserte visuelle Elemente
plt.fill_between(max_depths, test_accuracies, y_min, alpha=0.1, color='#ED7D31')
plt.tight_layout()
plt.savefig('output/surrogate_accuracy.png', dpi=300)
plt.show()
return best_depth, best_acc
# Aufruf der Funktion
best_depth, best_accuracy = plot_surrogate_accuracy_vs_depth_test_only(rf_model, X_train, X_test)
print(f"Optimale Baumtiefe: {best_depth} mit einer Genauigkeit von {best_accuracy:.4f}")

View File

@ -0,0 +1,3 @@
# Für Quarto Präsentationen
import extract_cells
extract_cells.extractToFiles("Explainable_AI_Adult_Census_Income.ipynb")

3
extracted_cells/cell3.py Normal file
View File

@ -0,0 +1,3 @@
print("\nDatentypen:")
#print(df.dtypes)
df.info()

2
extracted_cells/cell4.py Normal file
View File

@ -0,0 +1,2 @@
print("\nFehlende Werte:")
df.isnull().sum()

2
extracted_cells/cell5.py Normal file
View File

@ -0,0 +1,2 @@
print("\nStatistische Zusammenfassung:")
df.describe()

13
extracted_cells/cell6.py Normal file
View File

@ -0,0 +1,13 @@
# Überprüfen der Verteilung der Zielklasse
plt.figure(figsize=(8, 6))
sns.countplot(x='income', data=df)
plt.title('Verteilung der Einkommensklassen')
plt.xlabel('Einkommen')
plt.ylabel('Anzahl')
plt.savefig('output/Verteilung_Einkommensklassen.png', dpi=300)
plt.show()
# Prozentuale Verteilung berechnen
income_counts = df['income'].value_counts(normalize=True) * 100
print("Prozentuale Verteilung der Einkommensklassen:")
print(income_counts)

5
extracted_cells/cell7.py Normal file
View File

@ -0,0 +1,5 @@
# Überprüfen auf fehlende Werte oder '?'
for col in df.columns:
missing_count = df[df[col] == '?'].shape[0]
if missing_count > 0:
print(f"Spalte '{col}' hat {missing_count} Einträge mit '?'")

13
extracted_cells/cell8.py Normal file
View File

@ -0,0 +1,13 @@
# Ersetzen von '?' durch NaN und dann durch den häufigsten Wert
df_clean = df.copy()
for col in df_clean.columns:
if df_clean[col].dtype == 'object':
# Ersetze '?' durch NaN
df_clean[col] = df_clean[col].replace('?', np.nan)
# Ersetze NaN durch den häufigsten Wert
most_frequent = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(most_frequent)
df_clean.head(10)

20
extracted_cells/cell9.py Normal file
View File

@ -0,0 +1,20 @@
# Kategorische Variablen in numerische umwandeln
categorical_cols = df_clean.select_dtypes(include=['object']).columns
print("Kategorische Spalten:", categorical_cols.tolist())
# Label Encoding für die Zielvariable
label_encoder = LabelEncoder()
df_clean['income_encoded'] = label_encoder.fit_transform(df_clean['income'])
print("\nLabel Encoding für 'income':")
for i, label in enumerate(label_encoder.classes_):
print(f"{label} -> {i}")
# One-Hot Encoding für kategorische Variablen (außer der Zielvariable)
categorical_cols = categorical_cols.drop('income')
df_encoded = pd.get_dummies(df_clean, columns=categorical_cols, drop_first=False)
print("\nNeue Spalten durch One-Hot Encoding:")
print(df_encoded.columns[:10].tolist())
print("\nDatensatz nach Vorverarbeitung:", df_encoded.shape)

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

BIN
output/LIME_Erklärung.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
output/daten_uebersicht.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
output/lime_accuracy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
output/lime_consistency.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
output/lime_fidelity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
output/lime_stability.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
title: clean
author: Grant McDermott
version: 1.3.0
quarto-required: ">=1.3.0"
contributes:
formats:
revealjs:
theme: [default, clean.scss]
menu:
side: left
slide-number: true
date-format: long

View File

@ -0,0 +1,351 @@
/*-- scss:defaults --*/
// Custom colours and variables
$jet: #131516;
$accent: #107895;
$accent2: #9a2515;
// $accent2: #e64173;
$right-arrow: "\2192"; // Unicode character for right arrow
// fonts
/*
Note: This theme uses the Roboto font family, which it imports from Google
Fonts to ensure consistent weighting in addition to availability. While
you can use a local installation of Roboto, this is generally not
recommended since the weighting will likely be wrong (probably too
light). OTOH, importing from Google Fonts can cause some issues in
certain secure environments due the external CDN (see:
https://github.com/grantmcdermott/quarto-revealjs-clean/issues/7). If
that's the case for you, simply comment out the `@import url(...)` line
below and it will default for the default Sans Serif font on your system
(e.g., Helvetica on a Mac). Circling back to the earlier point about
preserving consistent font weights, you may also wish to remove "Roboto"
from the choice set if the family is installed locally.
*/
@import url('https://fonts.googleapis.com/css?family=Roboto:200,200i,300,300i,350,350i,400,400i&display=swap');
$font-family-sans-serif: "Roboto", sans-serif !default;
$presentation-heading-font: "Roboto", sans-serif !default;
$presentation-heading-color: $jet !default;
$presentation-heading-font-weight: lighter;
//$presentation-heading-line-height: 2;
//$presentation-block-margin: 28px;
$presentation-font-size-root: 32px;
// colors
//$body-bg: #f0f1eb !default;
$body-color: $jet !default;
$link-color: $accent !default;
$selection-bg: #26351c !default;
/*-- scss:rules --*/
.reveal a {
line-height: 1.5em;
}
.reveal p {
// font-weight: 300;
font-weight: lighter;
margin-top: 1.25em;
}
// title and headings
#title-slide {
text-align: left;
.title {
color: $body-color;
font-size: 1.4em;
// font-weight: 350;
font-weight: lighter;
}
.subtitle {
color: $accent;
font-style: italic;
margin-top: 0em;
font-weight: lighter;
}
.institute,
.quarto-title-affiliation,
.quarto-title-author-email {
font-style: italic;
// font-size: 80%;
// color: #7F7F7F;
}
.author,
.quarto-title-author-name {
color: $body-color;
}
.quarto-title-authors {
display: flex;
justify-content: left;
.quarto-title-author {
padding-left: 0em;
padding-right: 0em;
width: 100%;
}
}
}
.reveal h2 {
// font-weight: 350;
font-weight: lighter;
font-size: 1.4em;
}
.reveal h3 {
color: $accent;
font-style: italic;
// font-weight: 350;
font-weight: lighter;
font-size: 0.95em;
}
.reveal h4 {
color: $accent2;
// font-weight: 350;
font-weight: normal;
margin-top: 1.25em;
}
// alerts etc.
.alert {
color: $accent2;
}
.fg {
color: var(--col, $jet);
}
.bg {
background-color: var(--col, #fff);
padding: 0.1em;
border-radius: 5px;
display: inline-block;
}
// lists
// Unordered lists
.reveal ul {
// font-weight: 300;
font-weight: lighter;
padding-left: 16px;
li::marker {
color: mix($accent, white, 70%);
}
}
.reveal ul ul {
list-style: none;
li:before {
content: $right-arrow;
color: mix($accent, white, 60%);
display: inline-block;
width: 1em;
margin-left: -1em;
margin-right: 0.5em;
}
}
// Ordered lists
.reveal ol {
// font-weight: 300;
font-weight: lighter;
padding-left: 16px;
li::marker {
color: $accent;
}
}
// Move "hamburger" menu button to top right
.reveal .slide-menu-button {
position: fixed;
top: 6px;
right: 0;
display: flex;
justify-content: flex-end;
align-items: flex-start;
pointer-events: none;
}
.reveal .slide-menu-button > * {
pointer-events: auto;
}
// Same for chalkboard buttons (with an offset)
.reveal .slide-chalkboard-buttons {
position: fixed;
top: 12px;
right: 24px;
display: flex;
justify-content: flex-end;
align-items: flex-start;
pointer-events: none;
}
.reveal .slide-chalkboard-buttons > * {
pointer-events: auto;
}
// Logo to the bottom-left
.slide-logo {
display: block !important;
position: fixed !important;
bottom: 0 !important;
left: 10px !important;
max-width: 150px; // Adjust if necessary
max-height: 50px;
width: auto !important;
color: $body-color !important;
}
// Also need to enforce slide numbers at bottom-right (if logo is present)
.slide-number, .reveal.has-logo .slide-number {
bottom: 6px !important;
right: 10px !important;
top: unset !important;
color: #777777 !important;
}
// Beamer-style button link environment
.button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
background-color: $accent;
border: 1px solid $accent;
color: #fff !important;
text-decoration: none;
border-radius: 4px;
transition: all 0.2s ease-in-out;
}
.button:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.button::before {
content: "";
margin-right: 5px;
}
// tables
.reveal table {
// height: auto; /* Adjust table width to fit content up to the available slide space */
margin: auto;
border-collapse: collapse;
border-spacing: 0;
font-size: 0.8em;
}
.reveal table th,
.reveal table td {
border: none; /* Remove internal row lines */
padding: .23em; /* Adjust padding as needed */
text-align: left; /* Adjust text alignment as needed */
font-weight: lighter; /* Lighter font weight for main table text */
}
/* Adds a bottom border to the table header row for distinction */
.reveal table thead th,
.reveal .slides table tr:last-child td,
.reveal .slides table {
border-bottom: 2px solid #D3D3D3; /* Dark grey color for the bottom border */
}
/* Make column headers bold */
.reveal table thead th {
font-weight: bold;
}
/* Styling table captions */
.reveal table caption {
color: #666666; /* Dark grey color for the caption */
font-variant: small-caps; /* Use small caps for the caption text */
}
// Special catch for etable environment to ensure these table images
// don't overflow the slide.
// See: https://lrberge.github.io/fixest/articles/etable_new_features.html
.etable {
width: 100%;
height: calc(100% - 3em); /* Adjust 3em based on the height of your header, if necessary */
display: flex;
align-items: center;
justify-content: center;
}
.etable img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
}
// Change the relative widths of `output-location: column`.
// See: https://github.com/grantmcdermott/quarto-revealjs-clean/pull/16
// Example usage:
// ```{python}
// #| echo: true
// #| output-location: column
// #| classes: columns3070
// <code>
// ```
.reveal .columns3070 > div.column:first-child {
width: 30%;
}
.reveal .columns3070 div.column:not(:first-child) {
width: 70%;
}
.reveal .columns7030 > div.column:first-child {
width: 70%;
}
.reveal .columns7030 div.column:not(:first-child) {
width: 30%;
}
.reveal .columns4060 > div.column:first-child {
width: 40%;
}
.reveal .columns4060 div.column:not(:first-child) {
width: 60%;
}
.reveal .columns6040 > div.column:first-child {
width: 60%;
}
.reveal .columns6040 div.column:not(:first-child) {
width: 40%;
}

View File

@ -0,0 +1,7 @@
title: Quarto Metropolis Theme
author: Patrick Schratz
version: 1.0.0
contributes:
formats:
revealjs:
theme: metropolis.scss

View File

@ -0,0 +1,218 @@
/*-- scss:defaults --*/
// fonts
@import url(https://fonts.googleapis.com/css?family=Fira+Sans:300,300i,400,400i,500,500i,700,700i);
@import url(https://cdn.rawgit.com/tonsky/FiraCode/1.204/distr/fira_code.css);
@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|JetBrains+Mono&display=swap");
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap");
$font-family-sans-serif: "Roboto", "Fira Sans", "Droid Serif", serif !default;
$font-family-monospace: "JetBrains Mono", "Fira Code", monospace;
$presentation-font-size-root: 32px;
$presentation-line-height: 1.5em;
$presentation-heading-font-weight: 400;
// colors
$body-bg: #fafafa !default;
$body-color: #000 !default;
// $link-color: #EB811B !default;
$selection-bg: #26351c;
// headings
// $presentation-heading-font: "Palatino Linotype", "Book Antiqua", Palatino,
// FreeSerif, serif !default;
// $presentation-heading-color: #383d3d !default;
/*-- scss:rules --*/
.reveal a {
line-height: 1.5em;
color: #eb811b;
font-weight: 300;
}
.reveal .footer a {
color: #eb811b !important;
}
.reveal p {
font-weight: 300;
}
.reveal .slide ul li,
.reveal .slide ol li {
font-weight: 300;
}
// maximum height of code blocks before scrolling is used
.reveal pre.sourceCode code {
max-height: 700px; // default 500
}
// title slide
.title-slide {
background-color: #fafafa;
border-top: 80px solid #fafafa;
}
h1.title {
color: #1a292c;
font-size: 45px;
text-shadow: none;
font-weight: 400;
text-align: left;
margin-left: 15px;
padding-top: 80px;
}
p.subtitle {
// margin-top: -10px;
// padding-bottom: -20px;
color: #1a292c;
text-shadow: none;
font-weight: 300;
font-size: 40px;
text-align: left;
margin-left: 15px;
}
p.author {
color: #1a292c;
text-shadow: none;
font-weight: 300;
font-size: 30px;
text-align: left;
margin-left: 15px;
margin-bottom: -10px;
margin-top: 0px;
}
p.date {
color: #1a292c;
text-shadow: none;
font-weight: 300;
font-size: 30px;
text-align: left;
margin-left: 15px;
// margin-bottom: -30px;
}
p.subtitle:after {
content: "";
display: block;
border: none;
background-color: #eb811b;
color: #eb811b;
height: 1px;
margin: 25px 0 25px;
}
// Section break slide
hr,
h1::after {
content: "";
display: block;
border: none;
background-color: #eb811b;
color: #eb811b;
height: 1px;
margin: 1em 10px 0 10px;
}
// Override h1 style for title slide (remove section break slide style)
hr,
h1.title::after {
content: "";
display: block;
border: none;
background-color: transparent !important;
color: transparent !important;
height: 0px;
margin: 0px !important;
}
h2::after.title {
margin: 10px 15px 35px 0;
}
.reveal .slide-number a {
font-size: 120%;
background-color: #fafafa;
border-radius: 12px;
padding: 5px;
}
// inline
.reveal code {
font-size: 70%;
background-color: #afb8c133;
color: #000;
padding: 4px;
border-radius: 6px;
}
// code blocks
.reveal div.sourceCode pre code {
font-size: 100%;
}
// code output
.reveal pre code {
font-size: 100%;
padding-top: 15px;
}
.colored-column {
border: 2px solid red;
border-radius: 6px !important;
padding: 10px;
margin: 5px;
}
.column {
// #column;
// border: 2px solid red;
border-radius: 10px !important;
padding: 10px;
margin: 5px;
// background-color: #ededed;
background-color: #eeeeee;
}
.reveal h2 {
background-color: #23373b;
padding: 5px 0px 5px 10px;
color: #fafafa;
border-radius: 12px;
}
.small-font {
font-size: 70%;
}
iframe {
display: block;
margin-right: auto;
margin-left: auto;
}
.center {
text-align: center;
}
//
.reveal .slide-menu-button .fa-bars::before {
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(35, 55, 59)" class="bi bi-list" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/></svg>');
}
.reveal .slide-chalkboard-buttons .fa-easel2::before {
padding-bottom: 6px;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(35, 55, 59)" class="bi bi-easel2" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0a.5.5 0 0 1 .447.276L8.81 1h4.69A1.5 1.5 0 0 1 15 2.5V11h.5a.5.5 0 0 1 0 1h-2.86l.845 3.379a.5.5 0 0 1-.97.242L12.11 14H3.89l-.405 1.621a.5.5 0 0 1-.97-.242L3.36 12H.5a.5.5 0 0 1 0-1H1V2.5A1.5 1.5 0 0 1 2.5 1h4.691l.362-.724A.5.5 0 0 1 8 0ZM2 11h12V2.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5V11Zm9.61 1H4.39l-.25 1h7.72l-.25-1Z"/></svg>');
}
.reveal .slide-chalkboard-buttons .fa-brush::before {
padding-bottom: 6px;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="rgb(35, 55, 59)" class="bi bi-brush" viewBox="0 0 16 16"><path d="M15.825.12a.5.5 0 0 1 .132.584c-1.53 3.43-4.743 8.17-7.095 10.64a6.067 6.067 0 0 1-2.373 1.534c-.018.227-.06.538-.16.868-.201.659-.667 1.479-1.708 1.74a8.118 8.118 0 0 1-3.078.132 3.659 3.659 0 0 1-.562-.135 1.382 1.382 0 0 1-.466-.247.714.714 0 0 1-.204-.288.622.622 0 0 1 .004-.443c.095-.245.316-.38.461-.452.394-.197.625-.453.867-.826.095-.144.184-.297.287-.472l.117-.198c.151-.255.326-.54.546-.848.528-.739 1.201-.925 1.746-.896.126.007.243.025.348.048.062-.172.142-.38.238-.608.261-.619.658-1.419 1.187-2.069 2.176-2.67 6.18-6.206 9.117-8.104a.5.5 0 0 1 .596.04zM4.705 11.912a1.23 1.23 0 0 0-.419-.1c-.246-.013-.573.05-.879.479-.197.275-.355.532-.5.777l-.105.177c-.106.181-.213.362-.32.528a3.39 3.39 0 0 1-.76.861c.69.112 1.736.111 2.657-.12.559-.139.843-.569.993-1.06a3.122 3.122 0 0 0 .126-.75l-.793-.792zm1.44.026c.12-.04.277-.1.458-.183a5.068 5.068 0 0 0 1.535-1.1c1.9-1.996 4.412-5.57 6.052-8.631-2.59 1.927-5.566 4.66-7.302 6.792-.442.543-.795 1.243-1.042 1.826-.121.288-.214.54-.275.72v.001l.575.575zm-4.973 3.04.007-.005a.031.031 0 0 1-.007.004zm3.582-3.043.002.001h-.002z"/></svg>');
}
.reveal .progress {
color: #23373b;
}

View File

@ -0,0 +1,9 @@
title: Include Code Files
author: Bruno Beaufils
version: 1.0.0
quarto-required: ">=1.2"
contributes:
filters:
- include-code-files.lua

View File

@ -0,0 +1,130 @@
--- include-code-files.lua filter to include code from source files
---
--- Copyright: © 2020 Bruno BEAUFILS
--- License: MIT see LICENSE file for details
--- Dedent a line
local function dedent(line, n)
return line:sub(1, n):gsub(" ", "") .. line:sub(n + 1)
end
--- Find snippet start and end.
--
-- Use this to populate startline and endline.
-- This should work like pandocs snippet functionality: https://github.com/owickstrom/pandoc-include-code/tree/master
local function snippet(cb, fh)
if not cb.attributes.snippet then
return
end
-- Cannot capture enum: http://lua-users.org/wiki/PatternsTutorial
local comment
local comment_stop = ""
if
string.match(cb.attributes.include, ".py$")
or string.match(cb.attributes.include, ".jl$")
or string.match(cb.attributes.include, ".r$")
then
comment = "#"
elseif string.match(cb.attributes.include, ".o?js$") or string.match(cb.attributes.include, ".css$") then
comment = "//"
elseif string.match(cb.attributes.include, ".lua$") then
comment = "--"
elseif string.match(cb.attributes.include, ".html$") then
comment = "<!%-%-"
comment_stop = " *%-%->"
else
-- If not known assume that it is something one or two long and not alphanumeric.
comment = "%W%W?"
end
local p_start = string.format("^ *%s start snippet %s%s", comment, cb.attributes.snippet, comment_stop)
local p_stop = string.format("^ *%s end snippet %s%s", comment, cb.attributes.snippet, comment_stop)
local start, stop = nil, nil
-- Cannot use pairs.
local line_no = 1
for line in fh:lines() do
if start == nil then
if string.match(line, p_start) then
start = line_no + 1
end
elseif stop == nil then
if string.match(line, p_stop) then
stop = line_no - 1
end
else
break
end
line_no = line_no + 1
end
-- Reset so nothing is broken later on.
fh:seek("set")
-- If start and stop not found, just continue
if start == nil or stop == nil then
return nil
end
cb.attributes.startLine = tostring(start)
cb.attributes.endLine = tostring(stop)
end
--- Filter function for code blocks
local function transclude(cb)
if cb.attributes.include then
local content = ""
local fh = io.open(cb.attributes.include)
if not fh then
io.stderr:write("Cannot open file " .. cb.attributes.include .. " | Skipping includes\n")
else
local number = 1
local start = 1
-- change hyphenated attributes to PascalCase
for i, pascal in pairs({ "startLine", "endLine" }) do
local hyphen = pascal:gsub("%u", "-%0"):lower()
if cb.attributes[hyphen] then
cb.attributes[pascal] = cb.attributes[hyphen]
cb.attributes[hyphen] = nil
end
end
-- Overwrite startLine and stopLine with the snippet if any.
snippet(cb, fh)
if cb.attributes.startLine then
cb.attributes.startFrom = cb.attributes.startLine
start = tonumber(cb.attributes.startLine)
end
for line in fh:lines("L") do
if cb.attributes.dedent then
line = dedent(line, cb.attributes.dedent)
end
if number >= start then
if not cb.attributes.endLine or number <= tonumber(cb.attributes.endLine) then
content = content .. line
end
end
number = number + 1
end
fh:close()
end
-- remove key-value pair for used keys
cb.attributes.include = nil
cb.attributes.startLine = nil
cb.attributes.endLine = nil
cb.attributes.dedent = nil
-- return final code block
return pandoc.CodeBlock(content, cb.attr)
end
end
return {
{ CodeBlock = transclude },
}

68
slides/custom.css Normal file
View File

@ -0,0 +1,68 @@
/* Benutzerdefinierte Schriftart für die gesamte Präsentation */
.reveal {
font-family: 'Roboto', sans-serif;
}
/* Überschriften */
.reveal h1,
.reveal h2
{
padding-bottom: 20px;
font-size: 58px;
}
.reveal h3
{
font-size: 38px;
text-align: left;
}
.reveal h4
{
text-align: center;
}
.reveal h5
{
font-size: 30px;
}
.reveal h6 {
font-family: 'Montserrat', sans-serif;
font-weight: 600;
}
/* Text */
.reveal p {
font-size: 24px;
}
/* Tabellen */
.reveal li {
font-size: 26px;
}
/* Code-Blöcke */
.reveal .sourceCode {
font-size: 17px;
color: #888;
}
/* Tabellen */
table {
border-collapse: collapse;
border: 1px solid black;
}
th {
background-color: #63bdc2;
border: 1px solid black;
font-size: 22px;
}
td {
border: 1px solid black;
font-size: 15px;
}
/* Importiere die Schriftarten von Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&family=Montserrat:wght@500;600&family=Fira+Code&display=swap');

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
slides/img/Komplett.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
slides/img/LIME_fnlwgt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
slides/img/Neue_Regel.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
slides/img/Nur Input.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
slides/img/Vorteile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

2150
slides/slides.html Normal file

File diff suppressed because it is too large Load Diff

914
slides/slides.qmd Normal file
View File

@ -0,0 +1,914 @@
---
title: "Explainable AI"
subtitle: "LIME & RBE auf Random Forest <br> mit Adult Census Datensatz"
filters:
- include-code-files
date: today
date-format: "D.M.YYYY"
format:
revealjs:
incremental: true
slide-number: c
css: custom.css
footer: "Mario, Jan, Nick, Andi, Sebastian, Paul | TINF22B3 | Explainable AI "
---
# Datensatz
Analyse der Daten
## Adult Census Datensatz
- Enthält demografische Informationen aus der US-Volkszählung
- Soll Einkommen einer Person vorhersagen (<|>50000$ pro Jahr)
## Datensatzauswahl
- Gut strukturiert und saubere Daten
- Weit verbreiteter Benchmark in der Forschung und im maschinellen Lernen --> wird in vielen Studien verwendet
- Vielfalt der Variablen Typen --> enthält sowohl kategorische als auch numerische Variablen
- Realistisches Datenset --> gut geeignet für praxisnahe Anwendungen
:::{.notes}
1. Gut strukturierte und saubere Daten
Der Datensatz enthält strukturierte, tabellarische Daten mit klar definierten Spalten.
Fehlende Werte sind begrenzt, sodass weniger Datenbereinigung erforderlich ist.
2. Klassifikationsproblem für Machine Learning
Zielvariable: Einkommen (über oder unter 50.000 USD jährlich).
Ideal für binäre Klassifikationsmodelle (z. B. Logistic Regression, Decision Trees, Random Forests, Neural Networks).
3. Vielfältige Feature-Typen
Beinhaltet sowohl kategorische (z. B. Beruf, Geschlecht, Bildungsniveau) als auch numerische (z. B. Alter, Arbeitsstunden pro Woche) Variablen.
Ermöglicht das Testen verschiedener Feature-Engineering-Methoden.
4. Ein realistisches Datenset
Reale demografische und sozioökonomische Daten aus der US-Volkszählung (1994).
Praktisch für praxisnahe Anwendungen wie Sozialforschung, Wirtschaftsanalyse oder politische Entscheidungsfindung.
5. Gut dokumentiert & weit verbreitet
Häufig verwendet in Tutorials, Forschungsarbeiten und Kaggle-Wettbewerben.
Viele Ressourcen und Beispielcodes verfügbar.
6. Ideal für Fairness- und Bias-Analysen
Der Datensatz enthält sensible Merkmale wie Geschlecht und Ethnie.
Eignet sich für Studien zur Diskriminierung und Fairness von Algorithmen.
:::
## Enthaltene Daten im Datensatz {.smaller}
| Column Name | Data Type |
|------------|-----------|
| age | int64 |
| workclass | object |
| fnlwgt | int64 |
| education | object |
| education.num | int64 |
| marital.status | object |
| occupation | object |
| relationship | object |
| race | object |
| sex | object |
| capital.gain | int64 |
| capital.loss | int64 |
| hours.per.week | int64 |
| native.country | object |
| income | object |
:::{.notes}
1. age (numerisch)
Alter der Person (in Jahren).
2. workclass (kategorisch)
Art der Anstellung der Person.
Private (privatwirtschaftlich)
Self-emp-not-inc (selbstständig, aber nicht als Unternehmen)
Self-emp-inc (selbstständig mit Unternehmen)
Government (staatliche Anstellung: Local, State, Federal)
Without-pay (ohne Bezahlung)
Never-worked (nie gearbeitet)
3. fnlwgt (numerisch)
Final weight: Gewichtungsfaktor für Hochrechnungen in der Volkszählung -> gibt an wie viele Menschen mit ähnlichen Merkmalen in der Gesamtbevölkerung durch diese einzelne Person repräsentiert werden
Wird meist für ML-Modelle nicht genutzt. --> für unseren Datensatz normalerweise nicht wichtig aber da wir eine Gewichtung machen
4. education (kategorisch)
Bachelors (Bachelor-Abschluss)
Masters (Master-Abschluss)
Doctorate (Promotion)
5. education.num (numerisch)
9 (High School)
16 (Promotion)
6. marital.status (kategorisch)
Familienstand der Person.
Married (verheiratet)
Divorced (geschieden)
Never-married (nie verheiratet)
Separated (getrennt lebend)
Widowed (verwitwet)
7. occupation (kategorisch)
Beruf der Person.
8. relationship (kategorisch)
Beziehung zur Familie im Haushalt.
Husband (Ehemann)
Wife (Ehefrau)
Own-child (eigenes Kind)
Unmarried (unverheiratet)
9. race (kategorisch)
Ethnische Zugehörigkeit der Person.
White (weiß)
Black (schwarz)
Asian-Pac-Islander (asiatisch-pazifische Herkunft)
10. sex (kategorisch)
Geschlecht der Person.
Werte: Male (männlich), Female (weiblich)
11. capital.gain (numerisch)
Kapitalgewinne (z. B. durch Investitionen).
12. capital.loss (numerisch)
Kapitalverluste (z. B. aus Investitionen).
13. hours.per.week (numerisch)
Durchschnittliche Arbeitsstunden pro Woche.
14. native.country (kategorisch)
Herkunftsland der Person.
15. income (Zielvariable, kategorisch)
Einkommensklasse der Person.
<=50K (Jahreseinkommen ≤50.000 USD)
>50K (Jahreseinkommen >50.000 USD)
:::
## Analyse des Datensatzes {.smaller}
- Datensatzgröße
- Anzahl und Art der Datentypen
- Fehlende Werte in den einzelnen Spalten
- Statistische Zusammenfasung
. . .
```{python}
import pandas as pd
# Daten einlesen
df = pd.read_csv("../sample_data/adult_census_income/adult.csv")
df.describe()
```
## Prozentuale Verteilung der Personen im Datensatz
![](img/Verteilung_Einkommensklassen.png){height=500 fig-align="center"}
# Datenvorverarbeitung
Auffüllen von fehlenden Werten, Umwandlung von Variablen
## Überprüfung auf fehlende oder ungültige Werte
```{python}
#| echo: true
# Überprüfen auf fehlende Werte oder '?'
for col in df.columns:
missing_count = df[df[col] == '?'].shape[0]
if missing_count > 0:
print(f"Spalte '{col}' hat {missing_count} Einträge mit '?'")
```
## Ersetzung von fehlenden/ ungültigen Werten (vorher) {.smaller}
```{python}
df.iloc[:5, :7]
```
## Ersetzung von fehlenden/ ungültigen Werten (nachher){.smaller}
```{python}
import numpy as np
# Ersetzen von '?' durch NaN und dann durch den häufigsten Wert
df_clean = df.copy()
for col in df_clean.columns:
if df_clean[col].dtype == 'object':
# Ersetze '?' durch NaN
df_clean[col] = df_clean[col].replace('?', np.nan)
# Ersetze NaN durch den häufigsten Wert
most_frequent = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(most_frequent)
df_clean.iloc[:5, :7]
```
## Umwandlung der Variablen
- Von Katergorischen Variablen zu Numerischen
- Beispiel Geschlecht:
- Mann -> 1
- Frau -> 0
- Wird benötigt um mit dem Datensatz weiter zu arbeiten zu können -> ML-Modelle benötigen numerische Werte um sinnvolle Berechnungen
durchführen zu können
# ML-Modell
Auswahl, Beschreibung, Hyperparameter, Güte
## Auswahl eines geeigneten Modells
<br>
| Kriterium | Unsere Situation |
|- |-- |
| Art der Daten | Strukturiert |
| Datenmenge | ~32.500 Einträge |
| Komplexität | 13-dimensionale Daten --> eher komplex |
| Ziel und Art der Klassifizierung | Binäre Klassifizierung (>/< 50.000$ Einkommen)|
<br>
--> Gute Voraussetzungen für **Random Forest**
:::{.notes}
Außerdem:
- Rechenressourcen
- Zeit für Training
:::
## Random Forest
- **Ensemble-Learning-Verfahren**, bestehend aus vielen Entscheidungsbaummodellen (bei uns 100 Bäume)
- **Training:** Jeder Baum wird auf zufälliger Teilmenge der Daten und zufälliger Teilmenge der Features trainiert
- **Vorhersagen:** Ergebnisse aller Bäume werden aggregiert, um eine finale Vorhersage zu erhalten
- Keine Aktivierungsfunktionen oder Layer
:::{.notes}
- **Ensemble Learning**: Mehrere verschiedene Lernalgorithmen, um bessere Ergebnisse zu erhalten als mit einem einzelnen Lernalgorithmus
- Gute Leistung bei verschiedenen Arten von Daten
- Robustheit gegenüber Overfitting
- Fähigkeit, mit fehlenden Werten und kategorischen Variablen umzugehen
- **Eingebaute Feature Importance**
- **Aktivierungsfunktionen:** Entscheidungsbäume, basieren auf Regeln und Splits, ohne mathematische Aktivierungsfunktionen.
- **Layers:** Es gibt keine Schichten (Layers) im klassischen Sinne, wie bei neuronalen Netzwerken. Jeder Baum ist ein eigenständiger Klassifikator.
:::
## Random Forest {background-image="img/Random_Forest_Tree_Example.png"}
## Hyperparameter
> *Parameter, die vor dem Training festgelegt werden müssen*
| Hyperparameter | Bedeutung |
|- |-- |
| n_estimators | Anzahl an Bäumen |
| max_depth | Maximale Tiefe eines Baumes |
| min_samples_split | Mindestanzahl an Samples, um ein Split durchzuführen |
| min_samples_leaf | Mindestanzahl an Samples in einem Blatt |
## Hyperparameter-Tuning {.smaller}
> *Optimierung der Hyperparameter, um die Modellgüte zu verbessern*
```{.python include="../extracted_cells/cell15.py"
code-line-numbers="|2-7|10-11|15"
end-line=15}
```
. . .
```json
{
'max_depth': None,
'min_samples_leaf': 2,
'min_samples_split': 2,
'n_estimators': 100
}
```
## Hyperparameter-Tuning {.smaller}
```{.python include="../extracted_cells/cell15.py"
code-line-numbers="2-7,10-11,15|18,21|24-27"
end-line=27}
```
## Modellgüte {.smaller auto-animate=true}
:::{.notes}
- **Accuracy**: Anteil der korrekt klassifizierten Instanzen
- **Precision**: Anteil der als positiv klassifizierten Instanzen, die tatsächlich positiv sind
- **Recall**: Anteil der tatsächlich positiven Instanzen, die als positiv klassifiziert wurden
- **F1-Score**: Harmonisches Mittel aus Precision und Recall
:::
```{.json code-line-numbers="|2|3|4|5"}
{
'Accuracy': 0.8607,
'Precision': 0.7613,
'Recall': 0.6142,
'F1 Score': 0.6798
}
```
. . .
**Konfusionsmatrix**
. . .
![](img/Confusions_Matrix.png){width=50%}
## Konfusionsmatrix {auto-animate=true}
![](img/Confusions_Matrix.png)
:::{.notes}
- (oben links) Tatsächlich sind die meisten weniger als 50K und auch richtig vorhergesagt
- (unten rechts) Zweitgrößte Gruppe: Mehr als 50k und auch so vorhergesagt
- (unten links) Drittgrößte Gruppe: Eigentlich mehr als 50k aber als weniger vorhergesagt
- (oben rechts) Kleinste Gruppe: Tatsächlich weniger als 50k aber als mehr vorhergesagt
:::
## Feature Importances
> *Metrik, wie viel ein Feature zur Vorhersage des Modells beiträgt*
![](img/Feature_Importance.png){width=80%}
# LIME {visibility="uncounted"}
Local Interpretable Model-agnostic Explanations
## 1. Grundprinzip:
- LIME erklärt einzelne Vorhersagen eines beliebigen ML-Modells
- Es arbeitet modellunabhängig (model-agnostic)
- Erzeugt lokale, interpretierbare Erklärungen für einzelne Vorhersagen
## 2. Funktionsweise im Detail:
### Ausgangssituation:
- Ein trainiertes ML-Modell liegt vor
- Eine spezifische Vorhersage soll erklärt werden
- Das Original-Modell wird als "Black Box" behandelt
## 2. Funktionsweise im Detail:
### Prozessschritte:
- Sampling um den Datenpunkt:
- Erzeugt synthetische Samples in der Nachbarschaft des zu erklärenden Datenpunkts
- Verwendet Perturbationen (kleine Änderungen) der Original-Features
- Gewichtung der Samples:
- Näher liegende Samples erhalten höhere Gewichte
## 2. Funktionsweise im Detail:
### Prozessschritte:
- Feature-Transformation:
- Konvertiert die Daten in ein interpretierbares Format
- Bei Texten z.B. Umwandlung in binäre Features (Wort vorhanden/nicht vorhanden)
- Training eines einfachen Modells:
- typischer weise durch lineare Regression
- Verwendet die gewichteten Samples
- Optimiert auf lokale Genauigkeit
- damit man ein leicht Interpretierbares Modell bekommt
## 2. Funktionsweise im Detail:
Extraktion der Erklärung:
![](.\img\LIME_Erklärung.png){width=80% fig-align="center"}
:::{.notes}
Hier sind zwei Beispiele wie die Erklärung von LIME extrahiert werden kann.
Einmal als Grafik durch ein Balkendiagramm und einmal als Tabelle.
Roter Bereich: Je größer der Rote Bereich, je geringer wird die Chance mehr als 50k zu bekommen
Grüner Bereich: Je größer der Grüne Bereich, je größer wird die Chance mehr als 50k zu bekommen
:::
## 3. Wichtige Eigenschaften: {.smaller}
- Lokalität:
- Fokussiert sich auf lokale Umgebung der zu erklärenden Instanz
- Erzeugt keine globalen Erklärungen für das gesamte Modell
- Interpretierbarkeit:
- Nutzt einfache, verständliche Modelle für Erklärungen
- Meist lineare Modelle oder Entscheidungsbäume
- Modell-Agnostik:
- Funktioniert mit jedem ML-Modell
- Benötigt nur Zugriff auf Vorhersagefunktion
## 4. Vorteile und Grenzen: {.smaller}
:::{.notes}
LIME ist instabil, bedeuted das wenn man LIME zweimal ausführen würde, kann es sein das es nicht auf das gleiche Ergebnis kommt, sondern die Gewichtung anders verteilt.
:::
- Vorteile:
- Flexibel einsetzbar
- Intuitiv verständliche Erklärungen
- Unterstützt verschiedene Datentypen
- Grenzen:
- Nur lokale Erklärungen
- Sampling kann rechenintensiv sein
- LIME ist instabil
- LIME ist besonders nützlich, wenn man:
- Einzelne Vorhersagen verstehen möchten
- Mit komplexen Modellen arbeitet
- Modelle debuggen oder verbessern will
## LIME Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Krebsdiagnose: Erklärung, welche Merkmale in medizinischen Bildern zu einer Krebsdiagnose beitragen
:::
## LIME Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Krebsdiagnose: Erklärung, welche Merkmale in medizinischen Bildern zu einer Krebsdiagnose beitragen
**Finanzwesen und Kreditvergabe**
- Kreditwürdigkeitsprüfung: Transparente Begründung für Kreditablehnungen oder -genehmigungen
- Betrugserkennung: Erklärung, warum bestimmte Transaktionen als verdächtig eingestuft werden
:::
## LIME Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Krebsdiagnose: Erklärung, welche Merkmale in medizinischen Bildern zu einer Krebsdiagnose beitragen
**Finanzwesen und Kreditvergabe**
- Kreditwürdigkeitsprüfung: Transparente Begründung für Kreditablehnungen oder -genehmigungen
- Betrugserkennung: Erklärung, warum bestimmte Transaktionen als verdächtig eingestuft werden
**Personalabteilung und Recruiting**
- Bewerberselektion: Erklärung, welche Qualifikationen oder Fähigkeiten bei der Kandidatenauswahl entscheidend waren
:::
## LIME Berechnungskosten
LIME skaliert:
::: {.nonincremental}
- Linear mit der Anzahl der Samples (N)
- Quadratisch bis kubisch mit der Anzahl der Features (D)
- Linear mit der Komplexität des zu erklärenden Modells M(D)
:::
Komplexität in BigO Notation:
::: {.nonincremental}
- **O(N × M(D))**, wenn das zu erklärende Modell komplex ist
- **O(N × D²)**, wenn das lineare Modell der rechenintensivste Teil ist
:::
:::{.notes}
**Große Datensätze generell Machbar**
- Dürfen nur nicht zu viele Features haben, das erhöhe stark den Rechenaufwand
:::
## Rule Based Explenation
![](img/Komplett.bmp){height=400 fig-align="center"}
:::{.notes}
- Verfahren wird gefüttert mit
- Daten und Labels
- Erstellt draus Regeln
:::
## Detaillierte Erklärung und Funktionsweise
![](img/Neue_Regel.bmp){height=400 fig-align="center"}
::: {.notes}
- Menschen lernen schneller als Tiere
- Menschen kommunizieren schneller / besser
- Vor allem auf Grund von Regeln
- Satzbau, Erklärungen, Logik
- Hund Sitz beibringen
- braucht viele iterationen (mit Belohnungen)
- Mensch kann deutlicher erklären was das Ziel ist
- Regel Basiertes Lernen kommt zum Einsatz
- Anstatt (unverständlicher) neuronaler Netze
- oder komplexen mathematischen Modellen
- Verwendung von logischen Rgeln
- extrahiert aus Daten oder durch Experten definiert
:::
## Beispiel Regel
<br>
> WENN
> Fieber über 38°C
> UND Husten
> DANN Grippe
## Beispiel Regel {.text-align-right}
<br>
> WENN
> capital.gain ≤ 7073.50 UND
> education.num ≤ 12.50 UND
> capital.loss > 2218.50 UND
> DANN Einkommen > 50K
## Vorteile
- Transparenz: Menschen können Entscheidungen leicht <br> nachvollziehen
- Überprüfbarkeit: Regeln können durch Experten überprüft <br> und Ausgewertet werden
- Anpassbarkeit: Vordefinierte Regeln von Experten können <br> mit maschinell erstellten Regeln ergänzt werden
![](img/Vorteile.jpg){.absolute top=10 right=10 height="900"}
::: {.notes}
Transparenz: Menschen können Entscheidungen leicht nachvollziehen
Überprüfbarkeit: Regeln können durch Experten überprüft werden und Anpassungen vorgenommen werden
Anpassbarkeit: Vordefinierte Regeln von Experten können mit maschinell erstellten Regeln kombiniert werden
- (Modellunabhängig)
:::
## Nachteile/Herausforderungen
- Skalierbarkeit: komplexen Anwendungsgebiete können eine hohe <br> Anzahl an Regeln schnell erreichen
- Gereralität: Regeln sind oft domänenspezifisch und benötigen <br> für unterschiedliche Kontexte entsprechend anpassungen
- Widersprüche: Regeln können zu Widersprüchen untereinander <br> führen und müssen daher angepasst werden
![](img/Nachteile_gespiegelt.jpg){.absolute top=10 right=10 height="900"}
::: {.notes}
- Skalierbarkeit: In komplexen Anwendungsgebieten können die Anzahl an Regeln schnell sehr hoch werden.
- Gereralität: Regeln sind oft domänenspezifisch und müssen für unterschiedliche Kontexte entsprechend angepasst werden
- Widersprüche: Regeln können zu Widersprüchen untereinander führen und müssen daher angepasst werden
:::
## RBE Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Diagnostische Systeme: Ärzte benötigen klare Regeln, um Diagnosevorschläge zu verstehen und zu validieren
:::
:::{.notes}
Medizin / Gesundheitswesen
- **Regeln, warum welche Diagnose vorgeschlagen wurde**
Finanzwesen
- **Regeln, warum Transaktion verdächtig ist**
- **Regeln, warum Kredit genehmigt / abgelehnt wurde**
Versicherungswesen
- **Regeln, warum Prämien zu stande kommen**
- **Regeln, warum Schadensansprüche geltend gemacht werden können**
:::
## RBE Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Diagnostische Systeme: Ärzte benötigen klare Regeln, um Diagnosevorschläge zu verstehen und zu validieren
**Versicherungswesen**
- Risikobewertung: Transparente Regeln zur Prämienberechnung
- Schadenregulierung: Nachvollziehbare Entscheidungskriterien für Schadensansprüche
:::
:::{.notes}
Medizin / Gesundheitswesen
- **Regeln, warum welche Diagnose vorgeschlagen wurde**
Finanzwesen
- **Regeln, warum Transaktion verdächtig ist**
- **Regeln, warum Kredit genehmigt / abgelehnt wurde**
Versicherungswesen
- **Regeln, warum Prämien zu stande kommen**
- **Regeln, warum Schadensansprüche geltend gemacht werden können**
:::
## RBE Anwendungsszenarien
::: {.nonincremental}
**Medizinische Diagnostik und Gesundheitswesen**
- Diagnostische Systeme: Ärzte benötigen klare Regeln, um Diagnosevorschläge zu verstehen und zu validieren
**Versicherungswesen**
- Risikobewertung: Transparente Regeln zur Prämienberechnung
- Schadenregulierung: Nachvollziehbare Entscheidungskriterien für Schadensansprüche
**Finanzwesen und Kreditvergabe**
- Betrugserkennung: Einfach verständliche Regeln zur Identifikation verdächtiger Transaktionen
- Kreditentscheidungen: Banken müssen Ablehnungen oder Genehmigungen rechtlich begründen können
:::
:::{.notes}
Medizin / Gesundheitswesen
- **Regeln, warum welche Diagnose vorgeschlagen wurde**
Finanzwesen
- **Regeln, warum Transaktion verdächtig ist**
- **Regeln, warum Kredit genehmigt / abgelehnt wurde**
Versicherungswesen
- **Regeln, warum Prämien zu stande kommen**
- **Regeln, warum Schadensansprüche geltend gemacht werden können**
:::
## RBE Output
```
|--- marital.status_Married-civ-spouse <= 0.50
| |--- capital.gain <= 7073.50
| | |--- education.num <= 12.50
| | | |--- capital.loss <= 2218.50
| | | | |--- hours.per.week <= 40.50
| | | | | |--- Einkommen ≤ 50K
| | | | |--- hours.per.week > 40.50
| | | | | |--- Einkommen ≤ 50K
| | | |--- capital.loss > 2218.50
| | | | |--- fnlwgt <= 125450.50
| | | | | |--- Einkommen > 50K
| | | | |--- fnlwgt > 125450.50
| | | | | |--- Einkommen ≤ 50K
```
:::{.notes}
- Ich zeige euch keinen code aber zumindes den Output solltet ihr mal gesehen haben
- Als erstes den Decission Tree
- Und dann gescheit formatiert als Satz
- Richtigen Code wird später im Notebook vorgestellt
:::
## RBE Berechnungskosten
RBE skaliert:
::: {.nonincremental}
- N = Anzahl der Trainingsbeispiele
- D = Anzahl der Merkmale (Features)
:::
Komplexität in BigO Notation: **O(N × D × log N)**
:::{.notes}
Komplexität zusammengesetzt aus:
- Training des Surrogate-Modells (DecisionTreeClassifier)
- Vorhersagen mit dem Random Forest (Referenzmodell)
- Extraktion der Regeln aus dem Entscheidungsbaum
- Evaluierung des Surrogate-Modells
**Große Datensätze generell Machbar**
- Dürfen nur nicht zu viele Features haben, das erhöhe stark den Rechenaufwand
:::
::: {.center}
## Jetzt wird es Praktisch
:::
:::{.notes}
Jetzt das Notebook zeigen und konkret durchgehen
:::
## Vergleich der XAI-Verfahren
- Interpretierbarkeit
- Modellunabhängigkeit
- Genauigkeit / Konsistenz
- Anwendungsszenarien
## LIME Interpretierbarkeit
::: {.nonincremental}
- Leicht verständlich, welche Features relevant waren
- Auch verständlich Laien
:::
![](img/LIME_Erklärung.png){height=400 fig-align="center"}
:::{.notes}
**Rote Balken**
- Features die die Wahrscheinlichkeit von >=50k verringern
**Grüne Balken**
- Features die die Wahrscheinlichkeit von >=50k erhöhen
:::
## LIME Modellabhängigkeit
::: {.nonincremental}
- Ist Model-Agnostic
- Behandelt jedes Modell als Blackbox
:::
:::{.notes}
Wie in LIME schon beschrieben
L: local
I: Interpretable
M: Model Agnostic
:::
## LIME Konsistenz
::: {.nonincremental}
- Die Ergebnisse sind instabil
- Können aber stark variieren da es auf zufälligen Abänderungen der Daten basiert
:::
![](img/Lime_Varianz_Features.png){height=400 fig-align="center"}
:::{.notes}
**Wir haben 10 Erklärungen laufen lassen**
- Dann immer die Feature Wichtigkeit verglichen
- Geringe Varianz: Ähnliche Ergebnisse
- Hohe Varianz: Unterschiedliche Ergebnisse
=> Wir haben hier die Top 5 mit der größten Varianz => Immernoch gering
:::
## LIME Anwendungsszenarien
::: {.nonincremental}
- Medizinische Diagnostik und Gesundheitswesen
- Finanzwesen und Kreditvergabe
- Personalabteilung und Recruiting
:::
:::{.notes}
**Verständnis einer einzelnen Vorhersage**
:::
## RBE Interpretierbarkeit
::: {.nonincremental}
- Regeln Leicht Nachvollziehbar: Wenn-Dann-Struktur von Regeln
- Potenziell komplex bei vielen Regeln
- Auch für Laien Verständlich
:::
:::{.notes}
1. **Wenn-Dann-Struktur ist leicht zu verstehen, wie man auf Ergebnis kommt**
2. **Komplex / unübersichtlich wenn man viele Regeln hat**
3. **Regeln verstehen auch Lian**
:::
## RBE Interpretierbarkeit
Beispiel einer Regel: Vorhersagt > 50K
::: {.nonincremental}
- *WENN*
- *capital.gain ≤ 7073.50 UND*
- *education.num ≤ 12.50 UND*
- *capital.loss > 2218.50 UND*
- *DANN Einkommen > 50K*
:::
:::{.notes}
**Beispielregel für > 50K Einkommen**
:::
## RBE Modellabhängigkeit
::: {.nonincremental}
- **Inhärent regelbasierte Modelle** (modellabhängig)
- **Post-hoc regelbasierte Erklärungen** (modellunabhängig)
:::
Wir verwenden einen: **Surrogate-Entscheidungsbaum**
::: {.nonincremental}
- Dieser ist modellunabhängig
:::
:::{.notes}
**Inhärent regelbasierte Modelle**
- Diese Modelle sind ihre eigenen Erklärungen - die Regeln sind das Modell.
**Post-hoc regelbasierte Erklärungen**
- Diese Ansätze können prinzipiell auf jedes Modell angewendet werden und sind daher modellunabhängig.
**Surrogate-Entscheidungsbaum**
- **Post-hoc (Modellunabhängig)**
- **Bekommt Trainingsdaten und Random Forest Vorhersagen**
Die extrahierten Regeln bieten eine **globale Erklärung des Random Forest-Verhaltens**.
:::
## RBE Genauigkeit & Konsistenz
::: {.nonincremental}
- Genauigkeit: Wie nahe an Ergebnis von RF
- Konsistenz: Schwankungen der Genauigkeit
:::
![](img/surrogate_accuracy.png){height=400 fig-align="center"}
:::{.notes}
Vorgehen:
- **Surrogate-Modell trainiert, sodass es RF-Vorhersagen nachahmt**
- **Vorhersage von Surrogate-Modell mit RF vergleichen**
- Berechnen die oft Vorhersagen gleich ist in %
Das ganze mit mehreren Baumtiefen
- So finden wir die **optimale Baumtiefe**
=> Man sieht **sehr hohe Genauigkeit**
:::
## RBE Anwendungsszenarien
::: {.nonincremental}
- Medizinische Diagnostik und Gesundheitswesen
- Finanzwesen und Kreditvergabe
- Versicherungswesen
:::
:::{.notes}
**Globlaler Überblick von Entscheidungslogik**
**Wieso Entscheidet das Modell Allgemein**
Medizin / Gesundheitswesen
- **Beispiel Schlaganfall**
- => Wir wollen wissen, warum er allgemein zur Diagnose kommt
:::
::: {.center}
## Vielen Dank für Eure Aufmerksamkeit!
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,821 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="513.290625pt" height="392.514375pt" viewBox="0 0 513.290625 392.514375" xmlns="http://www.w3.org/2000/svg" version="1.1">
<metadata>
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-03-31T15:54:30.691742</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
<dc:title>Matplotlib v3.10.1, https://matplotlib.org/</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 392.514375
L 513.290625 392.514375
L 513.290625 0
L 0 0
z
" style="fill: #ffffff"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 59.690625 354.958125
L 506.090625 354.958125
L 506.090625 22.318125
L 59.690625 22.318125
z
" style="fill: #ffffff"/>
</g>
<g id="patch_3">
<path d="M 82.010625 354.958125
L 260.570625 354.958125
L 260.570625 38.158125
L 82.010625 38.158125
z
" clip-path="url(#pd48399ff98)" style="fill: #3274a1"/>
</g>
<g id="patch_4">
<path d="M 305.210625 354.958125
L 483.770625 354.958125
L 483.770625 254.471523
L 305.210625 254.471523
z
" clip-path="url(#pd48399ff98)" style="fill: #3274a1"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m7a2f3ae2f9" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m7a2f3ae2f9" x="171.290625" y="354.958125" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
<!-- &lt;=50K -->
<g transform="translate(153.269531 369.556562) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-3c" d="M 4684 3150
L 1459 2003
L 4684 863
L 4684 294
L 678 1747
L 678 2266
L 4684 3719
L 4684 3150
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-3d" d="M 678 2906
L 4684 2906
L 4684 2381
L 678 2381
L 678 2906
z
M 678 1631
L 4684 1631
L 4684 1100
L 678 1100
L 678 1631
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-35" d="M 691 4666
L 3169 4666
L 3169 4134
L 1269 4134
L 1269 2991
Q 1406 3038 1543 3061
Q 1681 3084 1819 3084
Q 2600 3084 3056 2656
Q 3513 2228 3513 1497
Q 3513 744 3044 326
Q 2575 -91 1722 -91
Q 1428 -91 1123 -41
Q 819 9 494 109
L 494 744
Q 775 591 1075 516
Q 1375 441 1709 441
Q 2250 441 2565 725
Q 2881 1009 2881 1497
Q 2881 1984 2565 2268
Q 2250 2553 1709 2553
Q 1456 2553 1204 2497
Q 953 2441 691 2322
L 691 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-30" d="M 2034 4250
Q 1547 4250 1301 3770
Q 1056 3291 1056 2328
Q 1056 1369 1301 889
Q 1547 409 2034 409
Q 2525 409 2770 889
Q 3016 1369 3016 2328
Q 3016 3291 2770 3770
Q 2525 4250 2034 4250
z
M 2034 4750
Q 2819 4750 3233 4129
Q 3647 3509 3647 2328
Q 3647 1150 3233 529
Q 2819 -91 2034 -91
Q 1250 -91 836 529
Q 422 1150 422 2328
Q 422 3509 836 4129
Q 1250 4750 2034 4750
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-4b" d="M 628 4666
L 1259 4666
L 1259 2694
L 3353 4666
L 4166 4666
L 1850 2491
L 4331 0
L 3500 0
L 1259 2247
L 1259 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-3c"/>
<use xlink:href="#DejaVuSans-3d" transform="translate(83.789062 0)"/>
<use xlink:href="#DejaVuSans-35" transform="translate(167.578125 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(231.201172 0)"/>
<use xlink:href="#DejaVuSans-4b" transform="translate(294.824219 0)"/>
</g>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m7a2f3ae2f9" x="394.490625" y="354.958125" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
<!-- &gt;50K -->
<g transform="translate(380.659375 369.556562) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-3e" d="M 678 3150
L 678 3719
L 4684 2266
L 4684 1747
L 678 294
L 678 863
L 3897 2003
L 678 3150
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-3e"/>
<use xlink:href="#DejaVuSans-35" transform="translate(83.789062 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(147.412109 0)"/>
<use xlink:href="#DejaVuSans-4b" transform="translate(211.035156 0)"/>
</g>
</g>
</g>
<g id="text_3">
<!-- Einkommen -->
<g transform="translate(253.414062 383.234687) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-45" d="M 628 4666
L 3578 4666
L 3578 4134
L 1259 4134
L 1259 2753
L 3481 2753
L 3481 2222
L 1259 2222
L 1259 531
L 3634 531
L 3634 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-69" d="M 603 3500
L 1178 3500
L 1178 0
L 603 0
L 603 3500
z
M 603 4863
L 1178 4863
L 1178 4134
L 603 4134
L 603 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6e" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6b" d="M 581 4863
L 1159 4863
L 1159 1991
L 2875 3500
L 3609 3500
L 1753 1863
L 3688 0
L 2938 0
L 1159 1709
L 1159 0
L 581 0
L 581 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6f" d="M 1959 3097
Q 1497 3097 1228 2736
Q 959 2375 959 1747
Q 959 1119 1226 758
Q 1494 397 1959 397
Q 2419 397 2687 759
Q 2956 1122 2956 1747
Q 2956 2369 2687 2733
Q 2419 3097 1959 3097
z
M 1959 3584
Q 2709 3584 3137 3096
Q 3566 2609 3566 1747
Q 3566 888 3137 398
Q 2709 -91 1959 -91
Q 1206 -91 779 398
Q 353 888 353 1747
Q 353 2609 779 3096
Q 1206 3584 1959 3584
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6d" d="M 3328 2828
Q 3544 3216 3844 3400
Q 4144 3584 4550 3584
Q 5097 3584 5394 3201
Q 5691 2819 5691 2113
L 5691 0
L 5113 0
L 5113 2094
Q 5113 2597 4934 2840
Q 4756 3084 4391 3084
Q 3944 3084 3684 2787
Q 3425 2491 3425 1978
L 3425 0
L 2847 0
L 2847 2094
Q 2847 2600 2669 2842
Q 2491 3084 2119 3084
Q 1678 3084 1418 2786
Q 1159 2488 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1356 3278 1631 3431
Q 1906 3584 2284 3584
Q 2666 3584 2933 3390
Q 3200 3197 3328 2828
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-65" d="M 3597 1894
L 3597 1613
L 953 1613
Q 991 1019 1311 708
Q 1631 397 2203 397
Q 2534 397 2845 478
Q 3156 559 3463 722
L 3463 178
Q 3153 47 2828 -22
Q 2503 -91 2169 -91
Q 1331 -91 842 396
Q 353 884 353 1716
Q 353 2575 817 3079
Q 1281 3584 2069 3584
Q 2775 3584 3186 3129
Q 3597 2675 3597 1894
z
M 3022 2063
Q 3016 2534 2758 2815
Q 2500 3097 2075 3097
Q 1594 3097 1305 2825
Q 1016 2553 972 2059
L 3022 2063
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-45"/>
<use xlink:href="#DejaVuSans-69" transform="translate(63.183594 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(90.966797 0)"/>
<use xlink:href="#DejaVuSans-6b" transform="translate(154.345703 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(208.630859 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(269.8125 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(367.224609 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(464.636719 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(526.160156 0)"/>
</g>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_3">
<defs>
<path id="ma37fdb22a8" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="354.958125" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
<!-- 0 -->
<g transform="translate(46.328125 358.757344) scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
<g id="ytick_2">
<g id="line2d_4">
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="290.880455" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
<!-- 5000 -->
<g transform="translate(27.240625 294.679674) scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-35"/>
<use xlink:href="#DejaVuSans-30" transform="translate(63.623047 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(127.246094 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(190.869141 0)"/>
</g>
</g>
</g>
<g id="ytick_3">
<g id="line2d_5">
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="226.802785" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
<!-- 10000 -->
<g transform="translate(20.878125 230.602004) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-31" d="M 794 531
L 1825 531
L 1825 4091
L 703 3866
L 703 4441
L 1819 4666
L 2450 4666
L 2450 531
L 3481 531
L 3481 0
L 794 0
L 794 531
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-30" transform="translate(63.623047 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(127.246094 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(190.869141 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(254.492188 0)"/>
</g>
</g>
</g>
<g id="ytick_4">
<g id="line2d_6">
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="162.725115" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
<!-- 15000 -->
<g transform="translate(20.878125 166.524334) scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-35" transform="translate(63.623047 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(127.246094 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(190.869141 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(254.492188 0)"/>
</g>
</g>
</g>
<g id="ytick_5">
<g id="line2d_7">
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="98.647445" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
<!-- 20000 -->
<g transform="translate(20.878125 102.446664) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-32" d="M 1228 531
L 3431 531
L 3431 0
L 469 0
L 469 531
Q 828 903 1448 1529
Q 2069 2156 2228 2338
Q 2531 2678 2651 2914
Q 2772 3150 2772 3378
Q 2772 3750 2511 3984
Q 2250 4219 1831 4219
Q 1534 4219 1204 4116
Q 875 4013 500 3803
L 500 4441
Q 881 4594 1212 4672
Q 1544 4750 1819 4750
Q 2544 4750 2975 4387
Q 3406 4025 3406 3419
Q 3406 3131 3298 2873
Q 3191 2616 2906 2266
Q 2828 2175 2409 1742
Q 1991 1309 1228 531
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-32"/>
<use xlink:href="#DejaVuSans-30" transform="translate(63.623047 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(127.246094 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(190.869141 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(254.492188 0)"/>
</g>
</g>
</g>
<g id="ytick_6">
<g id="line2d_8">
<g>
<use xlink:href="#ma37fdb22a8" x="59.690625" y="34.569775" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
<!-- 25000 -->
<g transform="translate(20.878125 38.368994) scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-32"/>
<use xlink:href="#DejaVuSans-35" transform="translate(63.623047 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(127.246094 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(190.869141 0)"/>
<use xlink:href="#DejaVuSans-30" transform="translate(254.492188 0)"/>
</g>
</g>
</g>
<g id="text_10">
<!-- Anzahl -->
<g transform="translate(14.798438 205.473281) rotate(-90) scale(0.1 -0.1)">
<defs>
<path id="DejaVuSans-41" d="M 2188 4044
L 1331 1722
L 3047 1722
L 2188 4044
z
M 1831 4666
L 2547 4666
L 4325 0
L 3669 0
L 3244 1197
L 1141 1197
L 716 0
L 50 0
L 1831 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-7a" d="M 353 3500
L 3084 3500
L 3084 2975
L 922 459
L 3084 459
L 3084 0
L 275 0
L 275 525
L 2438 3041
L 353 3041
L 353 3500
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-61" d="M 2194 1759
Q 1497 1759 1228 1600
Q 959 1441 959 1056
Q 959 750 1161 570
Q 1363 391 1709 391
Q 2188 391 2477 730
Q 2766 1069 2766 1631
L 2766 1759
L 2194 1759
z
M 3341 1997
L 3341 0
L 2766 0
L 2766 531
Q 2569 213 2275 61
Q 1981 -91 1556 -91
Q 1019 -91 701 211
Q 384 513 384 1019
Q 384 1609 779 1909
Q 1175 2209 1959 2209
L 2766 2209
L 2766 2266
Q 2766 2663 2505 2880
Q 2244 3097 1772 3097
Q 1472 3097 1187 3025
Q 903 2953 641 2809
L 641 3341
Q 956 3463 1253 3523
Q 1550 3584 1831 3584
Q 2591 3584 2966 3190
Q 3341 2797 3341 1997
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-68" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 4863
L 1159 4863
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6c" d="M 603 4863
L 1178 4863
L 1178 0
L 603 0
L 603 4863
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-41"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(68.408203 0)"/>
<use xlink:href="#DejaVuSans-7a" transform="translate(131.787109 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(184.277344 0)"/>
<use xlink:href="#DejaVuSans-68" transform="translate(245.556641 0)"/>
<use xlink:href="#DejaVuSans-6c" transform="translate(308.935547 0)"/>
</g>
</g>
</g>
<g id="patch_5">
<path d="M 59.690625 354.958125
L 59.690625 22.318125
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_6">
<path d="M 506.090625 354.958125
L 506.090625 22.318125
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_7">
<path d="M 59.690625 354.958125
L 506.090625 354.958125
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_8">
<path d="M 59.690625 22.318125
L 506.090625 22.318125
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="text_11">
<!-- Verteilung der Einkommensklassen -->
<g transform="translate(177.458438 16.318125) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-56" d="M 1831 0
L 50 4666
L 709 4666
L 2188 738
L 3669 4666
L 4325 4666
L 2547 0
L 1831 0
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-72" d="M 2631 2963
Q 2534 3019 2420 3045
Q 2306 3072 2169 3072
Q 1681 3072 1420 2755
Q 1159 2438 1159 1844
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1341 3275 1631 3429
Q 1922 3584 2338 3584
Q 2397 3584 2469 3576
Q 2541 3569 2628 3553
L 2631 2963
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-74" d="M 1172 4494
L 1172 3500
L 2356 3500
L 2356 3053
L 1172 3053
L 1172 1153
Q 1172 725 1289 603
Q 1406 481 1766 481
L 2356 481
L 2356 0
L 1766 0
Q 1100 0 847 248
Q 594 497 594 1153
L 594 3053
L 172 3053
L 172 3500
L 594 3500
L 594 4494
L 1172 4494
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-75" d="M 544 1381
L 544 3500
L 1119 3500
L 1119 1403
Q 1119 906 1312 657
Q 1506 409 1894 409
Q 2359 409 2629 706
Q 2900 1003 2900 1516
L 2900 3500
L 3475 3500
L 3475 0
L 2900 0
L 2900 538
Q 2691 219 2414 64
Q 2138 -91 1772 -91
Q 1169 -91 856 284
Q 544 659 544 1381
z
M 1991 3584
L 1991 3584
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-67" d="M 2906 1791
Q 2906 2416 2648 2759
Q 2391 3103 1925 3103
Q 1463 3103 1205 2759
Q 947 2416 947 1791
Q 947 1169 1205 825
Q 1463 481 1925 481
Q 2391 481 2648 825
Q 2906 1169 2906 1791
z
M 3481 434
Q 3481 -459 3084 -895
Q 2688 -1331 1869 -1331
Q 1566 -1331 1297 -1286
Q 1028 -1241 775 -1147
L 775 -588
Q 1028 -725 1275 -790
Q 1522 -856 1778 -856
Q 2344 -856 2625 -561
Q 2906 -266 2906 331
L 2906 616
Q 2728 306 2450 153
Q 2172 0 1784 0
Q 1141 0 747 490
Q 353 981 353 1791
Q 353 2603 747 3093
Q 1141 3584 1784 3584
Q 2172 3584 2450 3431
Q 2728 3278 2906 2969
L 2906 3500
L 3481 3500
L 3481 434
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-20" transform="scale(0.015625)"/>
<path id="DejaVuSans-64" d="M 2906 2969
L 2906 4863
L 3481 4863
L 3481 0
L 2906 0
L 2906 525
Q 2725 213 2448 61
Q 2172 -91 1784 -91
Q 1150 -91 751 415
Q 353 922 353 1747
Q 353 2572 751 3078
Q 1150 3584 1784 3584
Q 2172 3584 2448 3432
Q 2725 3281 2906 2969
z
M 947 1747
Q 947 1113 1208 752
Q 1469 391 1925 391
Q 2381 391 2643 752
Q 2906 1113 2906 1747
Q 2906 2381 2643 2742
Q 2381 3103 1925 3103
Q 1469 3103 1208 2742
Q 947 2381 947 1747
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-73" d="M 2834 3397
L 2834 2853
Q 2591 2978 2328 3040
Q 2066 3103 1784 3103
Q 1356 3103 1142 2972
Q 928 2841 928 2578
Q 928 2378 1081 2264
Q 1234 2150 1697 2047
L 1894 2003
Q 2506 1872 2764 1633
Q 3022 1394 3022 966
Q 3022 478 2636 193
Q 2250 -91 1575 -91
Q 1294 -91 989 -36
Q 684 19 347 128
L 347 722
Q 666 556 975 473
Q 1284 391 1588 391
Q 1994 391 2212 530
Q 2431 669 2431 922
Q 2431 1156 2273 1281
Q 2116 1406 1581 1522
L 1381 1569
Q 847 1681 609 1914
Q 372 2147 372 2553
Q 372 3047 722 3315
Q 1072 3584 1716 3584
Q 2034 3584 2315 3537
Q 2597 3491 2834 3397
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-56"/>
<use xlink:href="#DejaVuSans-65" transform="translate(60.658203 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(122.181641 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(163.294922 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(202.503906 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(264.027344 0)"/>
<use xlink:href="#DejaVuSans-6c" transform="translate(291.810547 0)"/>
<use xlink:href="#DejaVuSans-75" transform="translate(319.59375 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(382.972656 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(446.351562 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(509.828125 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(541.615234 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(605.091797 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(666.615234 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(707.728516 0)"/>
<use xlink:href="#DejaVuSans-45" transform="translate(739.515625 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(802.699219 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(830.482422 0)"/>
<use xlink:href="#DejaVuSans-6b" transform="translate(893.861328 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(948.146484 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(1009.328125 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(1106.740234 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(1204.152344 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(1265.675781 0)"/>
<use xlink:href="#DejaVuSans-73" transform="translate(1329.054688 0)"/>
<use xlink:href="#DejaVuSans-6b" transform="translate(1381.154297 0)"/>
<use xlink:href="#DejaVuSans-6c" transform="translate(1439.064453 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(1466.847656 0)"/>
<use xlink:href="#DejaVuSans-73" transform="translate(1528.126953 0)"/>
<use xlink:href="#DejaVuSans-73" transform="translate(1580.226562 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(1632.326172 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(1693.849609 0)"/>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pd48399ff98">
<rect x="59.690625" y="22.318125" width="446.4" height="332.64"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.tippy-box[data-theme~=light-border]{background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,8,16,.15);color:#333;box-shadow:0 4px 14px -2px rgba(0,8,16,.08)}.tippy-box[data-theme~=light-border]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light-border]>.tippy-arrow:after,.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=light-border]>.tippy-arrow:after{border-color:transparent;border-style:solid}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:after{border-top-color:rgba(0,8,16,.2);border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:rgba(0,8,16,.2);border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:after{border-left-color:rgba(0,8,16,.2);border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,.2)}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow{fill:#fff}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,205 @@
/* quarto syntax highlight colors */
:root {
--quarto-hl-ot-color: #003B4F;
--quarto-hl-at-color: #657422;
--quarto-hl-ss-color: #20794D;
--quarto-hl-an-color: #5E5E5E;
--quarto-hl-fu-color: #4758AB;
--quarto-hl-st-color: #20794D;
--quarto-hl-cf-color: #003B4F;
--quarto-hl-op-color: #5E5E5E;
--quarto-hl-er-color: #AD0000;
--quarto-hl-bn-color: #AD0000;
--quarto-hl-al-color: #AD0000;
--quarto-hl-va-color: #111111;
--quarto-hl-bu-color: inherit;
--quarto-hl-ex-color: inherit;
--quarto-hl-pp-color: #AD0000;
--quarto-hl-in-color: #5E5E5E;
--quarto-hl-vs-color: #20794D;
--quarto-hl-wa-color: #5E5E5E;
--quarto-hl-do-color: #5E5E5E;
--quarto-hl-im-color: #00769E;
--quarto-hl-ch-color: #20794D;
--quarto-hl-dt-color: #AD0000;
--quarto-hl-fl-color: #AD0000;
--quarto-hl-co-color: #5E5E5E;
--quarto-hl-cv-color: #5E5E5E;
--quarto-hl-cn-color: #8f5902;
--quarto-hl-sc-color: #5E5E5E;
--quarto-hl-dv-color: #AD0000;
--quarto-hl-kw-color: #003B4F;
}
/* other quarto variables */
:root {
--quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
pre > code.sourceCode > span {
color: #003B4F;
}
code span {
color: #003B4F;
}
code.sourceCode > span {
color: #003B4F;
}
div.sourceCode,
div.sourceCode pre.sourceCode {
color: #003B4F;
}
code span.ot {
color: #003B4F;
font-style: inherit;
}
code span.at {
color: #657422;
font-style: inherit;
}
code span.ss {
color: #20794D;
font-style: inherit;
}
code span.an {
color: #5E5E5E;
font-style: inherit;
}
code span.fu {
color: #4758AB;
font-style: inherit;
}
code span.st {
color: #20794D;
font-style: inherit;
}
code span.cf {
color: #003B4F;
font-weight: bold;
font-style: inherit;
}
code span.op {
color: #5E5E5E;
font-style: inherit;
}
code span.er {
color: #AD0000;
font-style: inherit;
}
code span.bn {
color: #AD0000;
font-style: inherit;
}
code span.al {
color: #AD0000;
font-style: inherit;
}
code span.va {
color: #111111;
font-style: inherit;
}
code span.bu {
font-style: inherit;
}
code span.ex {
font-style: inherit;
}
code span.pp {
color: #AD0000;
font-style: inherit;
}
code span.in {
color: #5E5E5E;
font-style: inherit;
}
code span.vs {
color: #20794D;
font-style: inherit;
}
code span.wa {
color: #5E5E5E;
font-style: italic;
}
code span.do {
color: #5E5E5E;
font-style: italic;
}
code span.im {
color: #00769E;
font-style: inherit;
}
code span.ch {
color: #20794D;
font-style: inherit;
}
code span.dt {
color: #AD0000;
font-style: inherit;
}
code span.fl {
color: #AD0000;
font-style: inherit;
}
code span.co {
color: #5E5E5E;
font-style: inherit;
}
code span.cv {
color: #5E5E5E;
font-style: italic;
}
code span.cn {
color: #8f5902;
font-style: inherit;
}
code span.sc {
color: #5E5E5E;
font-style: inherit;
}
code span.dv {
color: #AD0000;
font-style: inherit;
}
code span.kw {
color: #003B4F;
font-weight: bold;
font-style: inherit;
}
.prevent-inlining {
content: "</";
}
/*# sourceMappingURL=ae99138f4fbc2fb4c9f5e5cd69c22459.css.map */

View File

@ -0,0 +1,418 @@
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], function () {
return factory(root);
});
} else if (typeof exports === "object") {
module.exports = factory(root);
} else {
root.Tabby = factory(root);
}
})(
typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: this,
function (window) {
"use strict";
//
// Variables
//
var defaults = {
idPrefix: "tabby-toggle_",
default: "[data-tabby-default]",
};
//
// Methods
//
/**
* Merge two or more objects together.
* @param {Object} objects The objects to merge together
* @returns {Object} Merged values of defaults and options
*/
var extend = function () {
var merged = {};
Array.prototype.forEach.call(arguments, function (obj) {
for (var key in obj) {
if (!obj.hasOwnProperty(key)) return;
merged[key] = obj[key];
}
});
return merged;
};
/**
* Emit a custom event
* @param {String} type The event type
* @param {Node} tab The tab to attach the event to
* @param {Node} details Details about the event
*/
var emitEvent = function (tab, details) {
// Create a new event
var event;
if (typeof window.CustomEvent === "function") {
event = new CustomEvent("tabby", {
bubbles: true,
cancelable: true,
detail: details,
});
} else {
event = document.createEvent("CustomEvent");
event.initCustomEvent("tabby", true, true, details);
}
// Dispatch the event
tab.dispatchEvent(event);
};
var focusHandler = function (event) {
toggle(event.target);
};
var getKeyboardFocusableElements = function (element) {
return [
...element.querySelectorAll(
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])'
),
].filter(
(el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden")
);
};
/**
* Remove roles and attributes from a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
* @param {Object} settings User settings and options
*/
var destroyTab = function (tab, content, settings) {
// Remove the generated ID
if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) {
tab.id = "";
}
// remove event listener
tab.removeEventListener("focus", focusHandler, true);
// Remove roles
tab.removeAttribute("role");
tab.removeAttribute("aria-controls");
tab.removeAttribute("aria-selected");
tab.removeAttribute("tabindex");
tab.closest("li").removeAttribute("role");
content.removeAttribute("role");
content.removeAttribute("aria-labelledby");
content.removeAttribute("hidden");
};
/**
* Add the required roles and attributes to a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
* @param {Object} settings User settings and options
*/
var setupTab = function (tab, content, settings) {
// Give tab an ID if it doesn't already have one
if (!tab.id) {
tab.id = settings.idPrefix + content.id;
}
// Add roles
tab.setAttribute("role", "tab");
tab.setAttribute("aria-controls", content.id);
tab.closest("li").setAttribute("role", "presentation");
content.setAttribute("role", "tabpanel");
content.setAttribute("aria-labelledby", tab.id);
// Add selected state
if (tab.matches(settings.default)) {
tab.setAttribute("aria-selected", "true");
} else {
tab.setAttribute("aria-selected", "false");
content.setAttribute("hidden", "hidden");
}
// add focus event listender
tab.addEventListener("focus", focusHandler);
};
/**
* Hide a tab and its content
* @param {Node} newTab The new tab that's replacing it
*/
var hide = function (newTab) {
// Variables
var tabGroup = newTab.closest('[role="tablist"]');
if (!tabGroup) return {};
var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]');
if (!tab) return {};
var content = document.querySelector(tab.hash);
// Hide the tab
tab.setAttribute("aria-selected", "false");
// Hide the content
if (!content) return { previousTab: tab };
content.setAttribute("hidden", "hidden");
// Return the hidden tab and content
return {
previousTab: tab,
previousContent: content,
};
};
/**
* Show a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
*/
var show = function (tab, content) {
tab.setAttribute("aria-selected", "true");
content.removeAttribute("hidden");
tab.focus();
};
/**
* Toggle a new tab
* @param {Node} tab The tab to show
*/
var toggle = function (tab) {
// Make sure there's a tab to toggle and it's not already active
if (!tab || tab.getAttribute("aria-selected") == "true") return;
// Variables
var content = document.querySelector(tab.hash);
if (!content) return;
// Hide active tab and content
var details = hide(tab);
// Show new tab and content
show(tab, content);
// Add event details
details.tab = tab;
details.content = content;
// Emit a custom event
emitEvent(tab, details);
};
/**
* Get all of the tabs in a tablist
* @param {Node} tab A tab from the list
* @return {Object} The tabs and the index of the currently active one
*/
var getTabsMap = function (tab) {
var tabGroup = tab.closest('[role="tablist"]');
var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null;
if (!tabs) return;
return {
tabs: tabs,
index: Array.prototype.indexOf.call(tabs, tab),
};
};
/**
* Switch the active tab based on keyboard activity
* @param {Node} tab The currently active tab
* @param {Key} key The key that was pressed
*/
var switchTabs = function (tab, key) {
// Get a map of tabs
var map = getTabsMap(tab);
if (!map) return;
var length = map.tabs.length - 1;
var index;
// Go to previous tab
if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) {
index = map.index < 1 ? length : map.index - 1;
}
// Go to next tab
else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) {
index = map.index === length ? 0 : map.index + 1;
}
// Go to home
else if (key === "Home") {
index = 0;
}
// Go to end
else if (key === "End") {
index = length;
}
// Toggle the tab
toggle(map.tabs[index]);
};
/**
* Create the Constructor object
*/
var Constructor = function (selector, options) {
//
// Variables
//
var publicAPIs = {};
var settings, tabWrapper;
//
// Methods
//
publicAPIs.destroy = function () {
// Get all tabs
var tabs = tabWrapper.querySelectorAll("a");
// Add roles to tabs
Array.prototype.forEach.call(tabs, function (tab) {
// Get the tab content
var content = document.querySelector(tab.hash);
if (!content) return;
// Setup the tab
destroyTab(tab, content, settings);
});
// Remove role from wrapper
tabWrapper.removeAttribute("role");
// Remove event listeners
document.documentElement.removeEventListener(
"click",
clickHandler,
true
);
tabWrapper.removeEventListener("keydown", keyHandler, true);
// Reset variables
settings = null;
tabWrapper = null;
};
/**
* Setup the DOM with the proper attributes
*/
publicAPIs.setup = function () {
// Variables
tabWrapper = document.querySelector(selector);
if (!tabWrapper) return;
var tabs = tabWrapper.querySelectorAll("a");
// Add role to wrapper
tabWrapper.setAttribute("role", "tablist");
// Add roles to tabs. provide dynanmic tab indexes if we are within reveal
var contentTabindexes =
window.document.body.classList.contains("reveal-viewport");
var nextTabindex = 1;
Array.prototype.forEach.call(tabs, function (tab) {
if (contentTabindexes) {
tab.setAttribute("tabindex", "" + nextTabindex++);
} else {
tab.setAttribute("tabindex", "0");
}
// Get the tab content
var content = document.querySelector(tab.hash);
if (!content) return;
// set tab indexes for content
if (contentTabindexes) {
getKeyboardFocusableElements(content).forEach(function (el) {
el.setAttribute("tabindex", "" + nextTabindex++);
});
}
// Setup the tab
setupTab(tab, content, settings);
});
};
/**
* Toggle a tab based on an ID
* @param {String|Node} id The tab to toggle
*/
publicAPIs.toggle = function (id) {
// Get the tab
var tab = id;
if (typeof id === "string") {
tab = document.querySelector(
selector + ' [role="tab"][href*="' + id + '"]'
);
}
// Toggle the tab
toggle(tab);
};
/**
* Handle click events
*/
var clickHandler = function (event) {
// Only run on toggles
var tab = event.target.closest(selector + ' [role="tab"]');
if (!tab) return;
// Prevent link behavior
event.preventDefault();
// Toggle the tab
toggle(tab);
};
/**
* Handle keydown events
*/
var keyHandler = function (event) {
// Only run if a tab is in focus
var tab = document.activeElement;
if (!tab.matches(selector + ' [role="tab"]')) return;
// Only run for specific keys
if (["Home", "End"].indexOf(event.key) < 0) return;
// Switch tabs
switchTabs(tab, event.key);
};
/**
* Initialize the instance
*/
var init = function () {
// Merge user options with defaults
settings = extend(defaults, options || {});
// Setup the DOM
publicAPIs.setup();
// Add event listeners
document.documentElement.addEventListener("click", clickHandler, true);
tabWrapper.addEventListener("keydown", keyHandler, true);
};
//
// Initialize and return the Public APIs
//
init();
return publicAPIs;
};
//
// Return the Constructor
//
return Constructor;
}
);

View File

@ -0,0 +1 @@
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,30 @@
/* http://meyerweb.com/eric/tools/css/reset/
v4.0 | 20180602
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
main, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, main, menu, nav, section {
display: block;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
SIL Open Font License (OFL)
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL

View File

@ -0,0 +1,10 @@
@font-face {
font-family: 'League Gothic';
src: url('./league-gothic.eot');
src: url('./league-gothic.eot?#iefix') format('embedded-opentype'),
url('./league-gothic.woff') format('woff'),
url('./league-gothic.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}

View File

@ -0,0 +1,45 @@
SIL Open Font License
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name Source. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More