|
29 | 29 | model = artifacts["model"] |
30 | 30 | X_test = np.load("X_test.npy") |
31 | 31 | y_test = np.load("y_test.npy") |
32 | | -sf_test = pd.read_csv("sf_test.csv").squeeze() |
| 32 | +sf_test = pd.read_csv("sf_test.csv") |
| 33 | +sf_gender = sf_test["gender"] |
| 34 | +sf_race = sf_test["race"] |
33 | 35 |
|
34 | 36 | y_pred = model.predict(X_test) |
35 | 37 | overall_acc = accuracy_score(y_test, y_pred) |
36 | 38 | print(f"Overall accuracy: {overall_acc:.4f}") |
37 | 39 |
|
38 | 40 | # TODO: Build a MetricFrame with accuracy, precision, and recall broken down by |
39 | | -# the sensitive feature (gender) and print per-subgroup results |
| 41 | +# the sensitive features (gender and race) and print per-subgroup results |
40 | 42 | metrics = { |
41 | 43 | "accuracy": accuracy_score, |
42 | 44 | "precision": lambda y_true, y_pred: precision_score(y_true, y_pred, average="weighted", zero_division=0), |
|
50 | 52 | sensitive_features=sf_test, |
51 | 53 | ) |
52 | 54 |
|
53 | | -print(f"\nPer-subgroup metrics (gender):") |
| 55 | +print(f"\nPer-subgroup metrics (gender x race):") |
54 | 56 | print(mf.by_group.to_string()) |
55 | 57 |
|
56 | 58 | # TODO: Compute demographic_parity_difference and equalized_odds_difference |
57 | | -dpd = demographic_parity_difference(y_test, y_pred, sensitive_features=sf_test) |
58 | | -eod = equalized_odds_difference(y_test, y_pred, sensitive_features=sf_test) |
| 59 | +# for both gender and race so we can compare the gaps |
| 60 | +dpd_gender = demographic_parity_difference(y_test, y_pred, sensitive_features=sf_gender) |
| 61 | +eod_gender = equalized_odds_difference(y_test, y_pred, sensitive_features=sf_gender) |
| 62 | +dpd_race = demographic_parity_difference(y_test, y_pred, sensitive_features=sf_race) |
| 63 | +eod_race = equalized_odds_difference(y_test, y_pred, sensitive_features=sf_race) |
59 | 64 |
|
60 | | -print(f"\nDemographic parity difference : {dpd:.4f} (threshold: {DPD_THRESHOLD})") |
61 | | -print(f"Equalized odds difference : {eod:.4f} (threshold: {EOD_THRESHOLD})") |
| 65 | +print(f"\nDemographic parity difference (gender) : {dpd_gender:.4f} (threshold: {DPD_THRESHOLD})") |
| 66 | +print(f"Equalized odds difference (gender) : {eod_gender:.4f} (threshold: {EOD_THRESHOLD})") |
| 67 | +print(f"Demographic parity difference (race) : {dpd_race:.4f} (threshold: {DPD_THRESHOLD})") |
| 68 | +print(f"Equalized odds difference (race) : {eod_race:.4f} (threshold: {EOD_THRESHOLD})") |
62 | 69 |
|
| 70 | +# TODO: Save a fairness report with the per-group metrics and gap comparisons to disk |
| 71 | +report = { |
| 72 | + "sensitive_attribute": "gender", |
| 73 | + "overall_accuracy": overall_acc, |
| 74 | + "demographic_parity_difference_gender": dpd_gender, |
| 75 | + "equalized_odds_difference_gender": eod_gender, |
| 76 | + "demographic_parity_difference_race": dpd_race, |
| 77 | + "equalized_odds_difference_race": eod_race, |
| 78 | + "by_group": mf.by_group.round(4).reset_index().to_dict(orient="records"), |
| 79 | +} |
| 80 | +with open("fairness_report.json", "w") as f: |
| 81 | + json.dump(report, f, indent=2) |
63 | 82 |
|
64 | | -# TODO: Implement the fairness gate. Exit with code 1 if either metric exceeds its threshold. |
65 | | -failed = [] |
66 | | -if dpd > DPD_THRESHOLD: |
67 | | - failed.append(f"demographic_parity_difference {dpd:.4f} > {DPD_THRESHOLD}") |
68 | | -if eod > EOD_THRESHOLD: |
69 | | - failed.append(f"equalized_odds_difference {eod:.4f} > {EOD_THRESHOLD}") |
| 83 | +# TODO: Implement the fairness gate. Exit with code 1 if any metric exceeds its threshold. |
| 84 | +metric_names = np.array([ |
| 85 | + "demographic_parity_difference (gender)", |
| 86 | + "equalized_odds_difference (gender)", |
| 87 | + "demographic_parity_difference (race)", |
| 88 | + "equalized_odds_difference (race)", |
| 89 | +]) |
| 90 | +values = np.array([dpd_gender, eod_gender, dpd_race, eod_race]) |
| 91 | +thresholds = np.array([DPD_THRESHOLD, EOD_THRESHOLD, DPD_THRESHOLD, EOD_THRESHOLD]) |
| 92 | +exceeded = values > thresholds |
70 | 93 |
|
71 | | -if failed: |
| 94 | +if exceeded.any(): |
72 | 95 | print("\nFairness check FAILED:") |
73 | | - for f in failed: |
74 | | - print(f" - {f}") |
| 96 | + for name, value, threshold in zip(metric_names[exceeded], values[exceeded], thresholds[exceeded]): |
| 97 | + print(f" - {name} {value:.4f} > {threshold}") |
75 | 98 | sys.exit(1) |
76 | 99 |
|
77 | 100 | print("\nFairness check passed.") |
0 commit comments