MonikaDvorackova's picture
Train governance risk classifier baseline
25dfeb9 unverified
Raw
History Blame Contribute Delete
4.71 kB
import json
from pathlib import Path
import joblib
import pandas as pd
import sklearn
from datasets import load_dataset
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
classification_report,
confusion_matrix,
)
from sklearn.model_selection import StratifiedKFold, cross_val_predict
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
DATASET_ID = "aigovdev/ai-governance-scenarios"
MODEL_PATH = Path("artifacts/model.joblib")
METRICS_PATH = Path("artifacts/metrics.json")
FEATURES = [
"sector",
"impact",
"decision_autonomy",
"human_oversight",
"monitoring",
"traceability",
"technical_documentation",
]
def map_risk(label: str) -> str:
if label in {"low", "limited"}:
return "lower"
if label == "high":
return "high"
if label == "unacceptable":
return "unacceptable"
raise ValueError(f"Unexpected governance_risk label: {label}")
def build_pipeline() -> Pipeline:
preprocessor = ColumnTransformer(
transformers=[
(
"categorical",
OneHotEncoder(
handle_unknown="ignore",
),
FEATURES,
)
],
remainder="drop",
)
classifier = LogisticRegression(
max_iter=2000,
class_weight="balanced",
random_state=42,
)
return Pipeline(
[
("preprocessor", preprocessor),
("classifier", classifier),
]
)
def main():
MODEL_PATH.parent.mkdir(parents=True, exist_ok=True)
dataset = load_dataset(
DATASET_ID,
split="train",
)
df = dataset.to_pandas()
df["risk_tier"] = df["governance_risk"].map(map_risk)
X = df[FEATURES].copy()
y = df["risk_tier"].copy()
print("Dataset:", DATASET_ID)
print("Examples:", len(df))
print()
print("Target distribution:")
print(y.value_counts().sort_index())
print()
cv = StratifiedKFold(
n_splits=3,
shuffle=True,
random_state=42,
)
pipeline = build_pipeline()
predictions = cross_val_predict(
pipeline,
X,
y,
cv=cv,
)
labels = [
"lower",
"high",
"unacceptable",
]
accuracy = accuracy_score(
y,
predictions,
)
report = classification_report(
y,
predictions,
labels=labels,
output_dict=True,
zero_division=0,
)
matrix = confusion_matrix(
y,
predictions,
labels=labels,
)
print("Stratified 3-Fold Cross-Validation")
print("=" * 42)
print(
classification_report(
y,
predictions,
labels=labels,
digits=3,
zero_division=0,
)
)
print("Confusion matrix")
print("Labels:", labels)
print(matrix)
print()
pipeline.fit(
X,
y,
)
joblib.dump(
pipeline,
MODEL_PATH,
)
metrics = {
"dataset": DATASET_ID,
"examples": int(len(df)),
"target_distribution": {
key: int(value)
for key, value in y.value_counts().to_dict().items()
},
"evaluation": {
"method": "stratified_3_fold_cross_validation",
"accuracy": float(accuracy),
"macro_precision": float(
report["macro avg"]["precision"]
),
"macro_recall": float(
report["macro avg"]["recall"]
),
"macro_f1": float(
report["macro avg"]["f1-score"]
),
"confusion_matrix_labels": labels,
"confusion_matrix": matrix.tolist(),
},
"features": FEATURES,
"risk_mapping": {
"low": "lower",
"limited": "lower",
"high": "high",
"unacceptable": "unacceptable",
},
"runtime": {
"scikit_learn": sklearn.__version__,
},
"limitations": [
"Dataset contains only 12 synthetic scenarios.",
"Evaluation is illustrative and not a production benchmark.",
"Risk tiers are engineering labels, not legal classifications.",
],
}
METRICS_PATH.write_text(
json.dumps(
metrics,
indent=2,
),
encoding="utf-8",
)
print("Saved model:", MODEL_PATH.resolve())
print("Saved metrics:", METRICS_PATH.resolve())
if __name__ == "__main__":
main()