1 min read
Responsible AI in Practice: Building Ethical AI Systems
Microsoft’s Responsible AI Principles
Microsoft’s framework includes six principles:
- Fairness: AI systems should treat all people fairly
- Reliability & Safety: AI should perform reliably and safely
- Privacy & Security: AI should be secure and respect privacy
- Inclusiveness: AI should empower everyone
- Transparency: AI systems should be understandable
- Accountability: People should be accountable for AI systems
Implementing Fairness
Detecting Bias in Models
from fairlearn.metrics import MetricFrame
from fairlearn.reductions import ExponentiatedGradient, DemographicParity
from sklearn.linear_model import LogisticRegression
import pandas as pd
import numpy as np
def assess_model_fairness(model, X_test, y_test, sensitive_features):
"""
Assess fairness metrics across different demographic groups.
"""
y_pred = model.predict(X_test)
# Create metric frame
metrics = MetricFrame(
metrics={
'accuracy': lambda y, pred: (y == pred).mean(),
'selection_rate': lambda y, pred: pred.mean(),
'false_positive_rate': lambda y, pred: ((pred == 1) & (y == 0)).sum() / (y == 0).sum(),
'false_negative_rate': lambda y, pred: ((pred == 0) & (y == 1)).sum() / (y == 1).sum()
},
y_true=y_test,
y_pred=y_pred,
sensitive_features=sensitive_features
)
print("Overall Metrics:")
print(metrics.overall)
print("\nMetrics by Group:")
print(metrics.by_group)
print("\nDisparity (max difference between groups):")
print(metrics.difference(method='between_groups'))
return metrics
def mitigate_bias(X_train, y_train, sensitive_features, base_estimator=None):
"""
Train a fair classifier using exponentiated gradient reduction.
"""
if base_estimator is None:
base_estimator = LogisticRegression(solver='lbfgs', max_iter=1000)
constraint = DemographicParity()
mitigator = ExponentiatedGradient(
estimator=base_estimator,
constraints=constraint,
eps=0.01
)
mitigator.fit(X_train, y_train, sensitive_features=sensitive_features)
return mitigator
# Example usage
# X_train, X_test, y_train, y_test = load_data()
# gender = X_test['gender']
# Original model
# original_model = LogisticRegression().fit(X_train, y_train)
# assess_model_fairness(original_model, X_test, y_test, gender)
# Fair model
# fair_model = mitigate_bias(X_train, y_train, X_train['gender'])
# assess_model_fairness(fair_model, X_test, y_test, gender)
Fairness Dashboard in Azure ML
from azureml.core import Workspace, Experiment
from azureml.contrib.fairness import upload_dashboard_dictionary
def upload_fairness_metrics(workspace, experiment_name, metrics_dict):
"""
Upload fairness metrics to Azure ML for visualization.
"""
experiment = Experiment(workspace, experiment_name)
with experiment.start_logging() as run:
upload_dashboard_dictionary(
run,
metrics_dict,
dashboard_title="Model Fairness Analysis",
dataset_name="test_data"
)
print(f"Fairness dashboard uploaded. View at: {run.get_portal_url()}")
Model Interpretability
SHAP Values for Explanation
import shap
import matplotlib.pyplot as plt
def explain_model_predictions(model, X_train, X_test):
"""
Generate SHAP explanations for model predictions.
"""
# Create explainer
explainer = shap.TreeExplainer(model)
# Calculate SHAP values for test set
shap_values = explainer.shap_values(X_test)
# Summary plot - feature importance
plt.figure(figsize=(10, 8))
shap.summary_plot(shap_values, X_test, show=False)
plt.title("Feature Importance (SHAP Values)")
plt.tight_layout()
plt.savefig("shap_summary.png")
return shap_values
def explain_single_prediction(model, explainer, instance, feature_names):
"""
Explain a single prediction.
"""
shap_values = explainer.shap_values(instance)
# Waterfall plot for single prediction
shap.waterfall_plot(
shap.Explanation(
values=shap_values[0],
base_values=explainer.expected_value,
data=instance.values[0],
feature_names=feature_names
)
)
return {
"prediction": model.predict(instance)[0],
"base_value": explainer.expected_value,
"shap_values": dict(zip(feature_names, shap_values[0]))
}
InterpretML for Glass-Box Models
from interpret.glassbox import ExplainableBoostingClassifier
from interpret import show
def train_interpretable_model(X_train, y_train, feature_names):
"""
Train an inherently interpretable model.
"""
ebm = ExplainableBoostingClassifier(
feature_names=feature_names,
interactions=10,
learning_rate=0.01,
max_rounds=5000
)
ebm.fit(X_train, y_train)
# Global explanation
global_explanation = ebm.explain_global()
# Local explanation for specific instance
local_explanation = ebm.explain_local(X_train[:1], y_train[:1])
return ebm, global_explanation, local_explanation
# Visualize in notebook
# show(global_explanation)
# show(local_explanation)
Reliability and Safety
Model Validation Pipeline
from sklearn.model_selection import cross_val_score
import numpy as np
class ModelValidator:
def __init__(self, model, X, y):
self.model = model
self.X = X
self.y = y
self.validation_results = {}
def check_performance_consistency(self, cv=5):
"""Check if model performance is consistent across folds."""
scores = cross_val_score(self.model, self.X, self.y, cv=cv, scoring='accuracy')
self.validation_results['cv_mean'] = scores.mean()
self.validation_results['cv_std'] = scores.std()
self.validation_results['cv_scores'] = scores.tolist()
# Flag high variance
if scores.std() > 0.05:
self.validation_results['variance_warning'] = True
print(f"Warning: High variance in CV scores (std={scores.std():.3f})")
return self
def check_edge_cases(self, edge_cases):
"""Test model on known edge cases."""
results = []
for case in edge_cases:
pred = self.model.predict([case['input']])[0]
expected = case['expected']
passed = pred == expected
results.append({
'name': case['name'],
'passed': passed,
'predicted': pred,
'expected': expected
})
self.validation_results['edge_cases'] = results
failed = [r for r in results if not r['passed']]
if failed:
print(f"Warning: {len(failed)} edge case(s) failed")
return self
def check_confidence_calibration(self):
"""Check if model confidence scores are calibrated."""
if hasattr(self.model, 'predict_proba'):
probs = self.model.predict_proba(self.X)
max_probs = probs.max(axis=1)
# Check for overconfidence
very_confident = (max_probs > 0.95).mean()
if very_confident > 0.8:
self.validation_results['overconfidence_warning'] = True
print(f"Warning: Model may be overconfident ({very_confident:.1%} predictions > 95% confidence)")
return self
def generate_report(self):
"""Generate validation report."""
return self.validation_results
# Usage
# validator = ModelValidator(model, X_test, y_test)
# report = validator \
# .check_performance_consistency() \
# .check_edge_cases(edge_cases) \
# .check_confidence_calibration() \
# .generate_report()
Privacy and Security
Differential Privacy in Training
from opacus import PrivacyEngine
import torch
import torch.nn as nn
def train_private_model(model, train_loader, epochs=10, max_grad_norm=1.0, epsilon=1.0):
"""
Train a model with differential privacy guarantees.
"""
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
privacy_engine = PrivacyEngine()
model, optimizer, train_loader = privacy_engine.make_private_with_epsilon(
module=model,
optimizer=optimizer,
data_loader=train_loader,
epochs=epochs,
target_epsilon=epsilon,
target_delta=1e-5,
max_grad_norm=max_grad_norm
)
criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
for batch in train_loader:
optimizer.zero_grad()
outputs = model(batch['input'])
loss = criterion(outputs, batch['label'])
loss.backward()
optimizer.step()
epsilon_spent = privacy_engine.get_epsilon(delta=1e-5)
print(f"Epoch {epoch+1}: epsilon = {epsilon_spent:.2f}")
return model, privacy_engine
# Example model
# model = nn.Sequential(
# nn.Linear(784, 128),
# nn.ReLU(),
# nn.Linear(128, 10)
# )
# private_model, engine = train_private_model(model, train_loader)
Data Anonymization
import hashlib
from typing import Dict, List
class DataAnonymizer:
def __init__(self):
self.pii_columns = ['name', 'email', 'phone', 'ssn', 'address']
def hash_pii(self, value: str, salt: str = "") -> str:
"""Hash PII values consistently."""
return hashlib.sha256((str(value) + salt).encode()).hexdigest()[:12]
def anonymize_dataframe(self, df, columns_to_hash: List[str] = None):
"""Anonymize PII columns in a dataframe."""
df_anon = df.copy()
columns = columns_to_hash or [c for c in df.columns if c.lower() in self.pii_columns]
for col in columns:
if col in df_anon.columns:
df_anon[col] = df_anon[col].apply(lambda x: self.hash_pii(x) if pd.notna(x) else x)
return df_anon
def k_anonymize(self, df, quasi_identifiers: List[str], k: int = 5):
"""
Ensure each combination of quasi-identifiers appears at least k times.
"""
group_sizes = df.groupby(quasi_identifiers).size()
small_groups = group_sizes[group_sizes < k].index.tolist()
# Remove small groups or generalize
df_anon = df.copy()
mask = ~df_anon.set_index(quasi_identifiers).index.isin(small_groups)
df_anon = df_anon[mask.values]
return df_anon
# Usage
# anonymizer = DataAnonymizer()
# df_safe = anonymizer.anonymize_dataframe(df, ['name', 'email'])
# df_k_anon = anonymizer.k_anonymize(df, ['age', 'zipcode'], k=5)
Transparency and Documentation
Model Card Generator
from dataclasses import dataclass
from typing import List, Dict
from datetime import datetime
@dataclass
class ModelCard:
"""Model card for transparency and documentation."""
model_name: str
version: str
description: str
intended_use: str
out_of_scope_uses: List[str]
training_data: str
evaluation_data: str
metrics: Dict[str, float]
fairness_metrics: Dict[str, Dict[str, float]]
limitations: List[str]
ethical_considerations: List[str]
created_date: str = None
def __post_init__(self):
if self.created_date is None:
self.created_date = datetime.now().isoformat()
def to_markdown(self) -> str:
"""Generate markdown documentation."""
md = f"""# Model Card: {self.model_name}
**Version:** {self.version}
**Created:** {self.created_date}
## Model Description
{self.description}
## Intended Use
{self.intended_use}
### Out-of-Scope Uses
"""
for use in self.out_of_scope_uses:
md += f"- {use}\n"
md += f"""
## Training Data
{self.training_data}
## Evaluation Data
{self.evaluation_data}
## Performance Metrics
| Metric | Value |
|--------|-------|
"""
for metric, value in self.metrics.items():
md += f"| {metric} | {value:.4f} |\n"
md += """
## Fairness Analysis
"""
for group, metrics in self.fairness_metrics.items():
md += f"\n### {group}\n"
for metric, value in metrics.items():
md += f"- {metric}: {value:.4f}\n"
md += """
## Limitations
"""
for limitation in self.limitations:
md += f"- {limitation}\n"
md += """
## Ethical Considerations
"""
for consideration in self.ethical_considerations:
md += f"- {consideration}\n"
return md
# Example
card = ModelCard(
model_name="Customer Churn Predictor",
version="1.0.0",
description="Predicts customer churn probability based on usage patterns.",
intended_use="Identify at-risk customers for retention campaigns.",
out_of_scope_uses=[
"Individual customer targeting without human review",
"Employment or credit decisions"
],
training_data="12 months of customer data (Jan-Dec 2021)",
evaluation_data="Holdout set from same period (20%)",
metrics={"accuracy": 0.87, "auc_roc": 0.92, "f1": 0.85},
fairness_metrics={
"Gender": {"accuracy_male": 0.88, "accuracy_female": 0.86},
"Age Group": {"accuracy_18-35": 0.89, "accuracy_36-55": 0.87, "accuracy_55+": 0.84}
},
limitations=[
"Performance degrades for customers with < 3 months history",
"Not validated for enterprise customers"
],
ethical_considerations=[
"Model predictions should be reviewed before customer contact",
"Avoid using for discriminatory practices"
]
)
print(card.to_markdown())
Responsible AI isn’t a one-time checkbox - it’s an ongoing commitment that must be embedded in every stage of the AI lifecycle. Microsoft’s tools and frameworks make it easier to build AI systems that are fair, reliable, and transparent.