aimodelshare 0.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aimodelshare/README.md +26 -0
- aimodelshare/__init__.py +100 -0
- aimodelshare/aimsonnx.py +2381 -0
- aimodelshare/api.py +836 -0
- aimodelshare/auth.py +163 -0
- aimodelshare/aws.py +511 -0
- aimodelshare/aws_client.py +173 -0
- aimodelshare/base_image.py +154 -0
- aimodelshare/bucketpolicy.py +106 -0
- aimodelshare/color_mappings/color_mapping_keras.csv +121 -0
- aimodelshare/color_mappings/color_mapping_pytorch.csv +117 -0
- aimodelshare/containerisation.py +244 -0
- aimodelshare/containerization.py +712 -0
- aimodelshare/containerization_templates/Dockerfile.txt +8 -0
- aimodelshare/containerization_templates/Dockerfile_PySpark.txt +23 -0
- aimodelshare/containerization_templates/buildspec.txt +14 -0
- aimodelshare/containerization_templates/lambda_function.txt +40 -0
- aimodelshare/custom_approach/__init__.py +1 -0
- aimodelshare/custom_approach/lambda_function.py +17 -0
- aimodelshare/custom_eval_metrics.py +103 -0
- aimodelshare/data_sharing/__init__.py +0 -0
- aimodelshare/data_sharing/data_sharing_templates/Dockerfile.txt +3 -0
- aimodelshare/data_sharing/data_sharing_templates/__init__.py +1 -0
- aimodelshare/data_sharing/data_sharing_templates/buildspec.txt +15 -0
- aimodelshare/data_sharing/data_sharing_templates/codebuild_policies.txt +129 -0
- aimodelshare/data_sharing/data_sharing_templates/codebuild_trust_relationship.txt +12 -0
- aimodelshare/data_sharing/download_data.py +620 -0
- aimodelshare/data_sharing/share_data.py +373 -0
- aimodelshare/data_sharing/utils.py +8 -0
- aimodelshare/deploy_custom_lambda.py +246 -0
- aimodelshare/documentation/Makefile +20 -0
- aimodelshare/documentation/karma_sphinx_theme/__init__.py +28 -0
- aimodelshare/documentation/karma_sphinx_theme/_version.py +2 -0
- aimodelshare/documentation/karma_sphinx_theme/breadcrumbs.html +70 -0
- aimodelshare/documentation/karma_sphinx_theme/layout.html +172 -0
- aimodelshare/documentation/karma_sphinx_theme/search.html +50 -0
- aimodelshare/documentation/karma_sphinx_theme/searchbox.html +14 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css +2 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css +2751 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css +2 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.eot +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.svg +32 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.ttf +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff2 +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/js/theme.js +68 -0
- aimodelshare/documentation/karma_sphinx_theme/theme.conf +9 -0
- aimodelshare/documentation/make.bat +35 -0
- aimodelshare/documentation/requirements.txt +2 -0
- aimodelshare/documentation/source/about.rst +18 -0
- aimodelshare/documentation/source/advanced_features.rst +137 -0
- aimodelshare/documentation/source/competition.rst +218 -0
- aimodelshare/documentation/source/conf.py +58 -0
- aimodelshare/documentation/source/create_credentials.rst +86 -0
- aimodelshare/documentation/source/example_notebooks.rst +132 -0
- aimodelshare/documentation/source/functions.rst +151 -0
- aimodelshare/documentation/source/gettingstarted.rst +390 -0
- aimodelshare/documentation/source/images/creds1.png +0 -0
- aimodelshare/documentation/source/images/creds2.png +0 -0
- aimodelshare/documentation/source/images/creds3.png +0 -0
- aimodelshare/documentation/source/images/creds4.png +0 -0
- aimodelshare/documentation/source/images/creds5.png +0 -0
- aimodelshare/documentation/source/images/creds_file_example.png +0 -0
- aimodelshare/documentation/source/images/predict_tab.png +0 -0
- aimodelshare/documentation/source/index.rst +110 -0
- aimodelshare/documentation/source/modelplayground.rst +132 -0
- aimodelshare/exceptions.py +11 -0
- aimodelshare/generatemodelapi.py +1270 -0
- aimodelshare/iam/codebuild_policy.txt +129 -0
- aimodelshare/iam/codebuild_trust_relationship.txt +12 -0
- aimodelshare/iam/lambda_policy.txt +15 -0
- aimodelshare/iam/lambda_trust_relationship.txt +12 -0
- aimodelshare/json_templates/__init__.py +1 -0
- aimodelshare/json_templates/api_json.txt +155 -0
- aimodelshare/json_templates/auth/policy.txt +1 -0
- aimodelshare/json_templates/auth/role.txt +1 -0
- aimodelshare/json_templates/eval/policy.txt +1 -0
- aimodelshare/json_templates/eval/role.txt +1 -0
- aimodelshare/json_templates/function/policy.txt +1 -0
- aimodelshare/json_templates/function/role.txt +1 -0
- aimodelshare/json_templates/integration_response.txt +5 -0
- aimodelshare/json_templates/lambda_policy_1.txt +15 -0
- aimodelshare/json_templates/lambda_policy_2.txt +8 -0
- aimodelshare/json_templates/lambda_role_1.txt +12 -0
- aimodelshare/json_templates/lambda_role_2.txt +16 -0
- aimodelshare/leaderboard.py +174 -0
- aimodelshare/main/1.txt +132 -0
- aimodelshare/main/1B.txt +112 -0
- aimodelshare/main/2.txt +153 -0
- aimodelshare/main/3.txt +134 -0
- aimodelshare/main/4.txt +128 -0
- aimodelshare/main/5.txt +109 -0
- aimodelshare/main/6.txt +105 -0
- aimodelshare/main/7.txt +144 -0
- aimodelshare/main/8.txt +142 -0
- aimodelshare/main/__init__.py +1 -0
- aimodelshare/main/authorization.txt +275 -0
- aimodelshare/main/eval_classification.txt +79 -0
- aimodelshare/main/eval_lambda.txt +1709 -0
- aimodelshare/main/eval_regression.txt +80 -0
- aimodelshare/main/lambda_function.txt +8 -0
- aimodelshare/main/nst.txt +149 -0
- aimodelshare/model.py +1543 -0
- aimodelshare/modeluser.py +215 -0
- aimodelshare/moral_compass/README.md +408 -0
- aimodelshare/moral_compass/__init__.py +65 -0
- aimodelshare/moral_compass/_version.py +3 -0
- aimodelshare/moral_compass/api_client.py +601 -0
- aimodelshare/moral_compass/apps/__init__.py +69 -0
- aimodelshare/moral_compass/apps/ai_consequences.py +540 -0
- aimodelshare/moral_compass/apps/bias_detective.py +714 -0
- aimodelshare/moral_compass/apps/ethical_revelation.py +898 -0
- aimodelshare/moral_compass/apps/fairness_fixer.py +889 -0
- aimodelshare/moral_compass/apps/judge.py +888 -0
- aimodelshare/moral_compass/apps/justice_equity_upgrade.py +853 -0
- aimodelshare/moral_compass/apps/mc_integration_helpers.py +820 -0
- aimodelshare/moral_compass/apps/model_building_game.py +1104 -0
- aimodelshare/moral_compass/apps/model_building_game_beginner.py +687 -0
- aimodelshare/moral_compass/apps/moral_compass_challenge.py +858 -0
- aimodelshare/moral_compass/apps/session_auth.py +254 -0
- aimodelshare/moral_compass/apps/shared_activity_styles.css +349 -0
- aimodelshare/moral_compass/apps/tutorial.py +481 -0
- aimodelshare/moral_compass/apps/what_is_ai.py +853 -0
- aimodelshare/moral_compass/challenge.py +365 -0
- aimodelshare/moral_compass/config.py +187 -0
- aimodelshare/placeholders/model.onnx +0 -0
- aimodelshare/placeholders/preprocessor.zip +0 -0
- aimodelshare/playground.py +1968 -0
- aimodelshare/postprocessormodules.py +157 -0
- aimodelshare/preprocessormodules.py +373 -0
- aimodelshare/pyspark/1.txt +195 -0
- aimodelshare/pyspark/1B.txt +181 -0
- aimodelshare/pyspark/2.txt +220 -0
- aimodelshare/pyspark/3.txt +204 -0
- aimodelshare/pyspark/4.txt +187 -0
- aimodelshare/pyspark/5.txt +178 -0
- aimodelshare/pyspark/6.txt +174 -0
- aimodelshare/pyspark/7.txt +211 -0
- aimodelshare/pyspark/8.txt +206 -0
- aimodelshare/pyspark/__init__.py +1 -0
- aimodelshare/pyspark/authorization.txt +258 -0
- aimodelshare/pyspark/eval_classification.txt +79 -0
- aimodelshare/pyspark/eval_lambda.txt +1441 -0
- aimodelshare/pyspark/eval_regression.txt +80 -0
- aimodelshare/pyspark/lambda_function.txt +8 -0
- aimodelshare/pyspark/nst.txt +213 -0
- aimodelshare/python/my_preprocessor.py +58 -0
- aimodelshare/readme.md +26 -0
- aimodelshare/reproducibility.py +181 -0
- aimodelshare/sam/Dockerfile.txt +8 -0
- aimodelshare/sam/Dockerfile_PySpark.txt +24 -0
- aimodelshare/sam/__init__.py +1 -0
- aimodelshare/sam/buildspec.txt +11 -0
- aimodelshare/sam/codebuild_policies.txt +129 -0
- aimodelshare/sam/codebuild_trust_relationship.txt +12 -0
- aimodelshare/sam/codepipeline_policies.txt +173 -0
- aimodelshare/sam/codepipeline_trust_relationship.txt +12 -0
- aimodelshare/sam/spark-class.txt +2 -0
- aimodelshare/sam/template.txt +54 -0
- aimodelshare/tools.py +103 -0
- aimodelshare/utils/__init__.py +78 -0
- aimodelshare/utils/optional_deps.py +38 -0
- aimodelshare/utils.py +57 -0
- aimodelshare-0.3.7.dist-info/METADATA +298 -0
- aimodelshare-0.3.7.dist-info/RECORD +171 -0
- aimodelshare-0.3.7.dist-info/WHEEL +5 -0
- aimodelshare-0.3.7.dist-info/licenses/LICENSE +5 -0
- aimodelshare-0.3.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Activity 8: Fairness Fixer - Gradio application for the Justice & Equity Challenge.
|
|
3
|
+
|
|
4
|
+
This app teaches:
|
|
5
|
+
1. How to remove biased features (direct demographics)
|
|
6
|
+
2. Identifying and removing proxy variables
|
|
7
|
+
3. Developing representative and continuously improving data strategies
|
|
8
|
+
4. Building ethical roadmaps for responsible AI
|
|
9
|
+
|
|
10
|
+
Structure:
|
|
11
|
+
- Factory function `create_fairness_fixer_app()` returns a Gradio Blocks object
|
|
12
|
+
- Convenience wrapper `launch_fairness_fixer_app()` launches it inline (for notebooks)
|
|
13
|
+
|
|
14
|
+
Moral Compass Integration:
|
|
15
|
+
- Uses ChallengeManager for progress tracking (tasks D-E)
|
|
16
|
+
- Task D: Feature removal and proxy identification
|
|
17
|
+
- Task E: Representative data and improvement planning
|
|
18
|
+
- Debounced sync with Force Sync option
|
|
19
|
+
"""
|
|
20
|
+
import contextlib
|
|
21
|
+
import os
|
|
22
|
+
import logging
|
|
23
|
+
|
|
24
|
+
# Import moral compass integration helpers
|
|
25
|
+
from .mc_integration_helpers import (
|
|
26
|
+
get_challenge_manager,
|
|
27
|
+
sync_user_moral_state,
|
|
28
|
+
sync_team_state,
|
|
29
|
+
build_moral_leaderboard_html,
|
|
30
|
+
get_moral_compass_widget_html,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("aimodelshare.moral_compass.apps.fairness_fixer")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_initial_metrics():
|
|
37
|
+
"""Get initial fairness metrics before fixes."""
|
|
38
|
+
return {
|
|
39
|
+
"accuracy": 67.2,
|
|
40
|
+
"false_positive_rate": {
|
|
41
|
+
"African-American": 44.9,
|
|
42
|
+
"Caucasian": 23.5,
|
|
43
|
+
"Overall": 34.2
|
|
44
|
+
},
|
|
45
|
+
"false_negative_rate": {
|
|
46
|
+
"African-American": 28.0,
|
|
47
|
+
"Caucasian": 47.7,
|
|
48
|
+
"Overall": 37.9
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_post_demographic_removal_metrics():
|
|
54
|
+
"""Get metrics after removing direct demographics."""
|
|
55
|
+
return {
|
|
56
|
+
"accuracy": 64.8, # Slight accuracy drop
|
|
57
|
+
"false_positive_rate": {
|
|
58
|
+
"African-American": 38.2,
|
|
59
|
+
"Caucasian": 26.1,
|
|
60
|
+
"Overall": 32.2
|
|
61
|
+
},
|
|
62
|
+
"false_negative_rate": {
|
|
63
|
+
"African-American": 34.5,
|
|
64
|
+
"Caucasian": 43.2,
|
|
65
|
+
"Overall": 38.9
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_post_proxy_removal_metrics():
|
|
71
|
+
"""Get metrics after removing proxy variables."""
|
|
72
|
+
return {
|
|
73
|
+
"accuracy": 62.1, # Further accuracy drop, but more fair
|
|
74
|
+
"false_positive_rate": {
|
|
75
|
+
"African-American": 31.8,
|
|
76
|
+
"Caucasian": 28.4,
|
|
77
|
+
"Overall": 30.1
|
|
78
|
+
},
|
|
79
|
+
"false_negative_rate": {
|
|
80
|
+
"African-American": 39.2,
|
|
81
|
+
"Caucasian": 41.6,
|
|
82
|
+
"Overall": 40.4
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _get_user_stats():
|
|
88
|
+
"""Get user statistics."""
|
|
89
|
+
try:
|
|
90
|
+
username = os.environ.get("username")
|
|
91
|
+
team_name = os.environ.get("TEAM_NAME", "Unknown Team")
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"username": username or "Guest",
|
|
95
|
+
"team_name": team_name,
|
|
96
|
+
"is_signed_in": bool(username)
|
|
97
|
+
}
|
|
98
|
+
except Exception:
|
|
99
|
+
return {
|
|
100
|
+
"username": "Guest",
|
|
101
|
+
"team_name": "Unknown Team",
|
|
102
|
+
"is_signed_in": False
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create_fairness_fixer_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
|
|
107
|
+
"""Create the Fairness Fixer Gradio Blocks app (not launched yet)."""
|
|
108
|
+
try:
|
|
109
|
+
import gradio as gr
|
|
110
|
+
gr.close_all(verbose=False)
|
|
111
|
+
except ImportError as e:
|
|
112
|
+
raise ImportError(
|
|
113
|
+
"Gradio is required for the fairness fixer app. Install with `pip install gradio`."
|
|
114
|
+
) from e
|
|
115
|
+
|
|
116
|
+
initial_metrics = _get_initial_metrics()
|
|
117
|
+
|
|
118
|
+
# Get user stats and initialize challenge manager
|
|
119
|
+
user_stats = _get_user_stats()
|
|
120
|
+
challenge_manager = None
|
|
121
|
+
if user_stats["is_signed_in"]:
|
|
122
|
+
challenge_manager = get_challenge_manager(user_stats["username"])
|
|
123
|
+
|
|
124
|
+
# Track state
|
|
125
|
+
moral_compass_points = {"value": 0}
|
|
126
|
+
server_moral_score = {"value": None}
|
|
127
|
+
is_synced = {"value": False}
|
|
128
|
+
features_removed = []
|
|
129
|
+
proxy_fixes_applied = []
|
|
130
|
+
|
|
131
|
+
def sync_moral_state(override=False):
|
|
132
|
+
"""Sync moral state to server (debounced unless override)."""
|
|
133
|
+
if not challenge_manager:
|
|
134
|
+
return {
|
|
135
|
+
'widget_html': get_moral_compass_widget_html(
|
|
136
|
+
local_points=moral_compass_points["value"],
|
|
137
|
+
server_score=None,
|
|
138
|
+
is_synced=False
|
|
139
|
+
),
|
|
140
|
+
'status': 'Guest mode - sign in to sync'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Sync to server
|
|
144
|
+
sync_result = sync_user_moral_state(
|
|
145
|
+
cm=challenge_manager,
|
|
146
|
+
moral_points=moral_compass_points["value"],
|
|
147
|
+
override=override
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Update state
|
|
151
|
+
if sync_result['synced']:
|
|
152
|
+
server_moral_score["value"] = sync_result.get('server_score')
|
|
153
|
+
is_synced["value"] = True
|
|
154
|
+
|
|
155
|
+
# Trigger team sync if user sync succeeded
|
|
156
|
+
if user_stats.get("team_name"):
|
|
157
|
+
sync_team_state(user_stats["team_name"])
|
|
158
|
+
|
|
159
|
+
# Generate widget HTML
|
|
160
|
+
widget_html = get_moral_compass_widget_html(
|
|
161
|
+
local_points=moral_compass_points["value"],
|
|
162
|
+
server_score=server_moral_score["value"],
|
|
163
|
+
is_synced=is_synced["value"]
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
'widget_html': widget_html,
|
|
168
|
+
'status': sync_result['message']
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
def remove_demographics():
|
|
172
|
+
"""Remove direct demographic features and show impact."""
|
|
173
|
+
features_removed.extend(["race", "sex", "age"])
|
|
174
|
+
moral_compass_points["value"] += 100
|
|
175
|
+
|
|
176
|
+
# Update ChallengeManager (Task D: Remove demographics)
|
|
177
|
+
if challenge_manager:
|
|
178
|
+
challenge_manager.complete_task('D')
|
|
179
|
+
challenge_manager.answer_question('D', 'D1', 1)
|
|
180
|
+
|
|
181
|
+
before = initial_metrics
|
|
182
|
+
after = _get_post_demographic_removal_metrics()
|
|
183
|
+
|
|
184
|
+
report = "## Before/After: Removing Direct Demographics\n\n"
|
|
185
|
+
report += "### Overall Metrics\n"
|
|
186
|
+
report += f"| Metric | Before | After | Change |\n"
|
|
187
|
+
report += f"|--------|--------|-------|--------|\n"
|
|
188
|
+
report += f"| Accuracy | {before['accuracy']}% | {after['accuracy']}% | {after['accuracy'] - before['accuracy']:.1f}% |\n\n"
|
|
189
|
+
|
|
190
|
+
report += "### False Positive Rates (wrongly labeled as high risk)\n"
|
|
191
|
+
report += f"| Group | Before | After | Improvement |\n"
|
|
192
|
+
report += f"|-------|--------|-------|-------------|\n"
|
|
193
|
+
report += f"| African-American | {before['false_positive_rate']['African-American']}% | {after['false_positive_rate']['African-American']}% | {before['false_positive_rate']['African-American'] - after['false_positive_rate']['African-American']:.1f}% |\n"
|
|
194
|
+
report += f"| Caucasian | {before['false_positive_rate']['Caucasian']}% | {after['false_positive_rate']['Caucasian']}% | {after['false_positive_rate']['Caucasian'] - before['false_positive_rate']['Caucasian']:.1f}% |\n\n"
|
|
195
|
+
|
|
196
|
+
report += "### 📊 Key Findings:\n"
|
|
197
|
+
report += "- ✓ False positive rate disparity **reduced from 21.4% to 12.1%**\n"
|
|
198
|
+
report += "- ⚠️ Overall accuracy decreased by 2.4%\n"
|
|
199
|
+
report += "- ✓ Model is now more equitable across racial groups\n\n"
|
|
200
|
+
report += "**Trade-off Note:** We accept a small accuracy decrease to achieve greater fairness.\n\n"
|
|
201
|
+
report += "🏆 +100 Moral Compass points for removing biased features!"
|
|
202
|
+
|
|
203
|
+
# Trigger sync
|
|
204
|
+
sync_result = sync_moral_state()
|
|
205
|
+
report += f"\n\n{sync_result['status']}"
|
|
206
|
+
|
|
207
|
+
return report
|
|
208
|
+
|
|
209
|
+
def check_first_step_question(answer):
|
|
210
|
+
"""Check the first step question."""
|
|
211
|
+
if answer == "Remove demographic attributes like race and gender":
|
|
212
|
+
moral_compass_points["value"] += 50
|
|
213
|
+
return "✓ Correct! The first step in fairness is removing direct demographic attributes that can cause discriminatory predictions.\n\n🏆 +50 Moral Compass points!"
|
|
214
|
+
else:
|
|
215
|
+
return "✗ Not quite. Think about which variables directly encode protected characteristics."
|
|
216
|
+
|
|
217
|
+
def rank_proxy_variables(neighborhood_rank, income_rank, priors_rank):
|
|
218
|
+
"""Check proxy variable ranking."""
|
|
219
|
+
correct_ranking = {
|
|
220
|
+
"Neighborhood ZIP code": "1",
|
|
221
|
+
"Prior arrests (count)": "2",
|
|
222
|
+
"Income level": "3"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
score = 0
|
|
226
|
+
feedback = []
|
|
227
|
+
|
|
228
|
+
if neighborhood_rank == "1":
|
|
229
|
+
score += 1
|
|
230
|
+
feedback.append("✓ Correct! Neighborhood is the strongest proxy for race due to residential segregation.")
|
|
231
|
+
else:
|
|
232
|
+
feedback.append("✗ Neighborhood ZIP code is actually the #1 proxy due to residential segregation.")
|
|
233
|
+
|
|
234
|
+
if priors_rank == "2":
|
|
235
|
+
score += 1
|
|
236
|
+
feedback.append("✓ Correct! Prior arrests reflect historical bias in policing patterns.")
|
|
237
|
+
else:
|
|
238
|
+
feedback.append("✗ Prior arrests is the #2 proxy, reflecting historical policing bias.")
|
|
239
|
+
|
|
240
|
+
if income_rank == "3":
|
|
241
|
+
score += 1
|
|
242
|
+
feedback.append("✓ Correct! Income correlates with race but is less direct than ZIP code.")
|
|
243
|
+
else:
|
|
244
|
+
feedback.append("✗ Income level is the #3 proxy, less direct than neighborhood.")
|
|
245
|
+
|
|
246
|
+
if score == 3:
|
|
247
|
+
moral_compass_points["value"] += 100
|
|
248
|
+
feedback.append("\n🎉 Perfect! You understand proxy variables!\n🏆 +100 Moral Compass points!")
|
|
249
|
+
elif score >= 2:
|
|
250
|
+
moral_compass_points["value"] += 50
|
|
251
|
+
feedback.append("\n🏆 +50 Moral Compass points for good understanding!")
|
|
252
|
+
|
|
253
|
+
return "\n".join(feedback)
|
|
254
|
+
|
|
255
|
+
def remove_proxy_variables():
|
|
256
|
+
"""Remove proxy variables and reanalyze."""
|
|
257
|
+
proxy_fixes_applied.extend(["neighborhood", "prior_arrests_count", "income"])
|
|
258
|
+
moral_compass_points["value"] += 100
|
|
259
|
+
|
|
260
|
+
# Update ChallengeManager (continuing Task D: Remove proxies)
|
|
261
|
+
if challenge_manager:
|
|
262
|
+
challenge_manager.answer_question('D', 'D2', 1)
|
|
263
|
+
|
|
264
|
+
before = _get_post_demographic_removal_metrics()
|
|
265
|
+
after = _get_post_proxy_removal_metrics()
|
|
266
|
+
|
|
267
|
+
report = "## Impact of Removing Proxy Variables\n\n"
|
|
268
|
+
report += "**Removed:** Neighborhood ZIP code, Prior arrests count, Income level\n\n"
|
|
269
|
+
report += "### False Positive Rate Disparity\n"
|
|
270
|
+
report += f"| Metric | After Demographics Removed | After Proxies Removed | Improvement |\n"
|
|
271
|
+
report += f"|--------|---------------------------|----------------------|-------------|\n"
|
|
272
|
+
report += f"| African-American FPR | {before['false_positive_rate']['African-American']}% | {after['false_positive_rate']['African-American']}% | {before['false_positive_rate']['African-American'] - after['false_positive_rate']['African-American']:.1f}% |\n"
|
|
273
|
+
report += f"| Caucasian FPR | {before['false_positive_rate']['Caucasian']}% | {after['false_positive_rate']['Caucasian']}% | {after['false_positive_rate']['Caucasian'] - before['false_positive_rate']['Caucasian']:.1f}% |\n"
|
|
274
|
+
report += f"| Disparity | {before['false_positive_rate']['African-American'] - before['false_positive_rate']['Caucasian']:.1f}% | {after['false_positive_rate']['African-American'] - after['false_positive_rate']['Caucasian']:.1f}% | {(before['false_positive_rate']['African-American'] - before['false_positive_rate']['Caucasian']) - (after['false_positive_rate']['African-American'] - after['false_positive_rate']['Caucasian']):.1f}% |\n\n"
|
|
275
|
+
|
|
276
|
+
report += "### 📊 Key Findings:\n"
|
|
277
|
+
report += "- ✓ Disparity further reduced from 12.1% to **3.4%** - nearly equalized!\n"
|
|
278
|
+
report += "- ⚠️ Accuracy decreased to 62.1% (from 64.8%)\n"
|
|
279
|
+
report += "- ✓ **Model now treats groups much more fairly**\n\n"
|
|
280
|
+
report += "**Important:** Removing proxies prevents indirect discrimination through correlated variables.\n\n"
|
|
281
|
+
report += "🏆 +100 Moral Compass points for eliminating proxy bias!"
|
|
282
|
+
|
|
283
|
+
# Trigger sync
|
|
284
|
+
sync_result = sync_moral_state()
|
|
285
|
+
report += f"\n\n{sync_result['status']}"
|
|
286
|
+
|
|
287
|
+
return report
|
|
288
|
+
|
|
289
|
+
def check_proxy_question(answer):
|
|
290
|
+
"""Check proxy identification question."""
|
|
291
|
+
if answer == "ZIP code in a city with segregated neighborhoods":
|
|
292
|
+
moral_compass_points["value"] += 50
|
|
293
|
+
return "✓ Correct! ZIP codes can serve as a strong proxy for race in segregated areas, reintroducing bias even after removing race explicitly.\n\n🏆 +50 Moral Compass points!"
|
|
294
|
+
else:
|
|
295
|
+
return "✗ Not quite. Think about which variable most closely correlates with protected attributes in real-world settings."
|
|
296
|
+
|
|
297
|
+
def generate_data_guidelines():
|
|
298
|
+
"""Generate representative data guidelines."""
|
|
299
|
+
moral_compass_points["value"] += 75
|
|
300
|
+
|
|
301
|
+
# Update ChallengeManager (Task E: Representative data)
|
|
302
|
+
if challenge_manager:
|
|
303
|
+
challenge_manager.complete_task('E')
|
|
304
|
+
challenge_manager.answer_question('E', 'E1', 1)
|
|
305
|
+
|
|
306
|
+
guidelines = """
|
|
307
|
+
## 📋 Representative Data Guidelines
|
|
308
|
+
|
|
309
|
+
Based on expert consensus between data scientists, judges, and community members:
|
|
310
|
+
|
|
311
|
+
### 1. Population Matching
|
|
312
|
+
- ✓ Training data must reflect the demographics of the population where the model will be deployed
|
|
313
|
+
- ✓ Include all subgroups that will be affected by model predictions
|
|
314
|
+
- ✗ Avoid over-sampling from convenient but unrepresentative sources
|
|
315
|
+
|
|
316
|
+
### 2. Geographic Balance
|
|
317
|
+
- ✓ Collect data from multiple jurisdictions (urban, suburban, rural)
|
|
318
|
+
- ✓ Ensure regional representation matches deployment scope
|
|
319
|
+
- ✗ Don't rely solely on data from a single city or region
|
|
320
|
+
|
|
321
|
+
### 3. Temporal Relevance
|
|
322
|
+
- ✓ Use recent data that reflects current social conditions
|
|
323
|
+
- ✓ Regularly update training data as society evolves
|
|
324
|
+
- ✗ Avoid relying on outdated data from different legal/social contexts
|
|
325
|
+
|
|
326
|
+
### 4. Outcome Diversity
|
|
327
|
+
- ✓ Include both positive and negative outcomes for all groups
|
|
328
|
+
- ✓ Balance historical bias in outcome labels when possible
|
|
329
|
+
- ✗ Don't perpetuate historical discrimination patterns
|
|
330
|
+
|
|
331
|
+
### 5. Community Input
|
|
332
|
+
- ✓ Consult with affected communities about data collection
|
|
333
|
+
- ✓ Incorporate local knowledge about relevant features
|
|
334
|
+
- ✗ Don't make assumptions without stakeholder input
|
|
335
|
+
|
|
336
|
+
🏆 +75 Moral Compass points for developing data guidelines!
|
|
337
|
+
"""
|
|
338
|
+
# Trigger sync
|
|
339
|
+
sync_result = sync_moral_state()
|
|
340
|
+
guidelines += f"\n\n{sync_result['status']}"
|
|
341
|
+
|
|
342
|
+
return guidelines
|
|
343
|
+
|
|
344
|
+
def check_representative_data_question(answer):
|
|
345
|
+
"""Check representative data question."""
|
|
346
|
+
if answer == "Training data that mirrors the demographics and conditions of the target population":
|
|
347
|
+
moral_compass_points["value"] += 25
|
|
348
|
+
return "✓ Correct! Representative data accurately reflects the population where the model will be used.\n\n🏆 +25 Moral Compass points!"
|
|
349
|
+
else:
|
|
350
|
+
return "✗ Not quite. Representative data should match the target deployment population."
|
|
351
|
+
|
|
352
|
+
def check_geographic_question(answer):
|
|
353
|
+
"""Check geographic mismatch question."""
|
|
354
|
+
if answer == "No - the model may not generalize well to different geographic contexts":
|
|
355
|
+
moral_compass_points["value"] += 25
|
|
356
|
+
return "✓ Correct! Models trained in one region may not work fairly or accurately in areas with different demographics and social conditions.\n\n🏆 +25 Moral Compass points!"
|
|
357
|
+
else:
|
|
358
|
+
return "✗ Not quite. Consider how regional differences might affect model performance and fairness."
|
|
359
|
+
|
|
360
|
+
def build_improvement_plan(audit_order, doc_order, stakeholder_order):
|
|
361
|
+
"""Check if roadmap steps are correctly ordered."""
|
|
362
|
+
score = 0
|
|
363
|
+
feedback = []
|
|
364
|
+
|
|
365
|
+
if audit_order == "1":
|
|
366
|
+
score += 1
|
|
367
|
+
feedback.append("✓ Correct! Regular auditing should be the first step.")
|
|
368
|
+
else:
|
|
369
|
+
feedback.append("✗ Regular auditing should be step 1 to catch issues early.")
|
|
370
|
+
|
|
371
|
+
if doc_order == "2":
|
|
372
|
+
score += 1
|
|
373
|
+
feedback.append("✓ Correct! Documentation enables transparency and accountability.")
|
|
374
|
+
else:
|
|
375
|
+
feedback.append("✗ Documentation should be step 2 to ensure transparency.")
|
|
376
|
+
|
|
377
|
+
if stakeholder_order == "3":
|
|
378
|
+
score += 1
|
|
379
|
+
feedback.append("✓ Correct! Ongoing stakeholder engagement is the final continuous process.")
|
|
380
|
+
else:
|
|
381
|
+
feedback.append("✗ Stakeholder engagement should be step 3 for continuous improvement.")
|
|
382
|
+
|
|
383
|
+
if score == 3:
|
|
384
|
+
moral_compass_points["value"] += 100
|
|
385
|
+
plan = """
|
|
386
|
+
## 🗺️ Continuous Improvement Plan
|
|
387
|
+
|
|
388
|
+
### Phase 1: Regular Auditing
|
|
389
|
+
- ✓ Quarterly fairness metric reviews
|
|
390
|
+
- ✓ Automated bias detection systems
|
|
391
|
+
- ✓ Disparate impact analysis across all protected groups
|
|
392
|
+
|
|
393
|
+
### Phase 2: Transparent Documentation
|
|
394
|
+
- ✓ Public model cards with fairness metrics
|
|
395
|
+
- ✓ Dataset composition reports
|
|
396
|
+
- ✓ Decision-making process documentation
|
|
397
|
+
|
|
398
|
+
### Phase 3: Stakeholder Engagement
|
|
399
|
+
- ✓ Community advisory board meetings
|
|
400
|
+
- ✓ Feedback mechanisms for affected individuals
|
|
401
|
+
- ✓ Regular consultations with advocacy groups
|
|
402
|
+
|
|
403
|
+
🎉 Perfect roadmap! 🏆 +100 Moral Compass points!
|
|
404
|
+
"""
|
|
405
|
+
feedback.append(plan)
|
|
406
|
+
elif score >= 2:
|
|
407
|
+
moral_compass_points["value"] += 50
|
|
408
|
+
feedback.append("\n🏆 +50 Moral Compass points for good understanding!")
|
|
409
|
+
|
|
410
|
+
return "\n".join(feedback)
|
|
411
|
+
|
|
412
|
+
def check_model_card_question(answer):
|
|
413
|
+
"""Check model card question."""
|
|
414
|
+
if answer == "Fairness metrics across demographic groups":
|
|
415
|
+
moral_compass_points["value"] += 50
|
|
416
|
+
return "✓ Correct! Judges need to understand how fairly the model treats different groups to make informed decisions about its use.\n\n🏆 +50 Moral Compass points!"
|
|
417
|
+
else:
|
|
418
|
+
return "✗ Not quite. Think about what information would help a judge evaluate whether the model should be used in court."
|
|
419
|
+
|
|
420
|
+
def generate_fairness_summary():
|
|
421
|
+
"""Generate final fairness fix summary."""
|
|
422
|
+
report = "# 🔧 Fairness Fix Summary\n\n"
|
|
423
|
+
report += f"**Moral Compass Score:** {moral_compass_points['value']} points\n\n"
|
|
424
|
+
|
|
425
|
+
report += "## Features Removed:\n"
|
|
426
|
+
if features_removed:
|
|
427
|
+
for feature in set(features_removed):
|
|
428
|
+
report += f"- ✓ {feature}\n"
|
|
429
|
+
else:
|
|
430
|
+
report += "- No features removed yet\n"
|
|
431
|
+
|
|
432
|
+
report += "\n## Proxy Fixes Applied:\n"
|
|
433
|
+
if proxy_fixes_applied:
|
|
434
|
+
for proxy in set(proxy_fixes_applied):
|
|
435
|
+
report += f"- ✓ {proxy}\n"
|
|
436
|
+
else:
|
|
437
|
+
report += "- No proxy fixes applied yet\n"
|
|
438
|
+
|
|
439
|
+
report += "\n## Fairness Metric Changes:\n"
|
|
440
|
+
initial = _get_initial_metrics()
|
|
441
|
+
final = _get_post_proxy_removal_metrics()
|
|
442
|
+
|
|
443
|
+
report += f"- False Positive Rate Disparity: {initial['false_positive_rate']['African-American'] - initial['false_positive_rate']['Caucasian']:.1f}% → {final['false_positive_rate']['African-American'] - final['false_positive_rate']['Caucasian']:.1f}%\n"
|
|
444
|
+
report += f"- Overall Accuracy: {initial['accuracy']:.1f}% → {final['accuracy']:.1f}%\n"
|
|
445
|
+
report += f"- **Fairness Improvement:** {((initial['false_positive_rate']['African-American'] - initial['false_positive_rate']['Caucasian']) - (final['false_positive_rate']['African-American'] - final['false_positive_rate']['Caucasian'])):.1f}% reduction in disparity\n"
|
|
446
|
+
|
|
447
|
+
report += "\n## Ongoing Improvement Plan:\n"
|
|
448
|
+
report += "- ✓ Regular fairness audits (quarterly)\n"
|
|
449
|
+
report += "- ✓ Transparent model documentation\n"
|
|
450
|
+
report += "- ✓ Stakeholder engagement process\n\n"
|
|
451
|
+
|
|
452
|
+
report += "**Status:** Ready to proceed to Activity 9 - Justice & Equity Upgrade\n"
|
|
453
|
+
|
|
454
|
+
return report
|
|
455
|
+
|
|
456
|
+
# Create the Gradio app
|
|
457
|
+
with gr.Blocks(
|
|
458
|
+
title="Activity 8: Fairness Fixer",
|
|
459
|
+
theme=gr.themes.Soft(primary_hue=theme_primary_hue)
|
|
460
|
+
) as app:
|
|
461
|
+
gr.Markdown("# 🔧 Activity 8: Fairness Fixer")
|
|
462
|
+
gr.Markdown(
|
|
463
|
+
"""
|
|
464
|
+
**Objective:** Apply hands-on fairness fixes: remove biased features, eliminate proxy variables,
|
|
465
|
+
and develop a representative and continuously improving data strategy.
|
|
466
|
+
|
|
467
|
+
**Your Role:** You're now a **Fairness Engineer**.
|
|
468
|
+
|
|
469
|
+
**Progress:** Activity 8 of 10 — Fix the Model
|
|
470
|
+
|
|
471
|
+
**Estimated Time:** 12–15 minutes
|
|
472
|
+
"""
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Moral Compass widget with Force Sync
|
|
476
|
+
with gr.Row():
|
|
477
|
+
with gr.Column(scale=3):
|
|
478
|
+
moral_compass_display = gr.HTML(
|
|
479
|
+
get_moral_compass_widget_html(
|
|
480
|
+
local_points=0,
|
|
481
|
+
server_score=None,
|
|
482
|
+
is_synced=False
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
with gr.Column(scale=1):
|
|
486
|
+
force_sync_btn = gr.Button("Force Sync", variant="secondary", size="sm")
|
|
487
|
+
sync_status = gr.Markdown("")
|
|
488
|
+
|
|
489
|
+
# Force Sync handler
|
|
490
|
+
def handle_force_sync():
|
|
491
|
+
sync_result = sync_moral_state(override=True)
|
|
492
|
+
return sync_result['widget_html'], sync_result['status']
|
|
493
|
+
|
|
494
|
+
force_sync_btn.click(
|
|
495
|
+
fn=handle_force_sync,
|
|
496
|
+
outputs=[moral_compass_display, sync_status]
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# Section 8.2: Remove Direct Demographics
|
|
500
|
+
with gr.Tab("8.2 Remove Demographics"):
|
|
501
|
+
gr.Markdown(
|
|
502
|
+
"""
|
|
503
|
+
## Remove Direct Demographics
|
|
504
|
+
|
|
505
|
+
**Problem:** Direct demographic attributes (race, sex, age) create unfair predictions
|
|
506
|
+
because they encode protected characteristics.
|
|
507
|
+
|
|
508
|
+
**Solution:** Remove these features from the model entirely.
|
|
509
|
+
"""
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
remove_demo_btn = gr.Button("Remove Demographics & Reanalyze", variant="primary")
|
|
513
|
+
demo_removal_output = gr.Markdown("")
|
|
514
|
+
|
|
515
|
+
remove_demo_btn.click(
|
|
516
|
+
fn=remove_demographics,
|
|
517
|
+
outputs=demo_removal_output
|
|
518
|
+
).then(
|
|
519
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
520
|
+
outputs=moral_compass_display
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
gr.Markdown("### Check-In Question")
|
|
524
|
+
first_step_question = gr.Radio(
|
|
525
|
+
choices=[
|
|
526
|
+
"Increase model accuracy at all costs",
|
|
527
|
+
"Remove demographic attributes like race and gender",
|
|
528
|
+
"Collect more data from the majority group",
|
|
529
|
+
"Use all available features without restriction"
|
|
530
|
+
],
|
|
531
|
+
label="What should be the first step in making an AI model fairer?",
|
|
532
|
+
value=None
|
|
533
|
+
)
|
|
534
|
+
first_step_btn = gr.Button("Check Answer")
|
|
535
|
+
first_step_feedback = gr.Markdown("")
|
|
536
|
+
|
|
537
|
+
first_step_btn.click(
|
|
538
|
+
fn=check_first_step_question,
|
|
539
|
+
inputs=first_step_question,
|
|
540
|
+
outputs=first_step_feedback
|
|
541
|
+
).then(
|
|
542
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
543
|
+
outputs=moral_compass_display
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
# Section 8.3: Identify & Remove Proxies
|
|
547
|
+
with gr.Tab("8.3 Remove Proxies"):
|
|
548
|
+
gr.Markdown(
|
|
549
|
+
"""
|
|
550
|
+
## Identify & Remove Proxy Variables
|
|
551
|
+
|
|
552
|
+
**Problem:** Even after removing race, other variables can serve as **proxies** that
|
|
553
|
+
replicate bias. A proxy is a variable that strongly correlates with a protected attribute.
|
|
554
|
+
|
|
555
|
+
### 📚 Fairness Metrics: Statistical Parity vs Equal Opportunity
|
|
556
|
+
|
|
557
|
+
<details>
|
|
558
|
+
<summary><b>Click to expand: Fairness Metric Comparison</b></summary>
|
|
559
|
+
|
|
560
|
+
**Scenario:** Pretrial risk model predicting recidivism
|
|
561
|
+
|
|
562
|
+
| Metric | Definition | Example Calculation | Limitation |
|
|
563
|
+
|--------|-----------|---------------------|------------|
|
|
564
|
+
| **Statistical Parity** | Equal positive prediction rates across groups | P(Predicted High Risk \\| African-American) = P(Predicted High Risk \\| Caucasian) | May require unequal treatment of equally risky individuals |
|
|
565
|
+
| **Equal Opportunity** | Equal true positive rates across groups | P(Predicted High Risk \\| Actually Risky, African-American) = P(Predicted High Risk \\| Actually Risky, Caucasian) | Ignores false positive disparity |
|
|
566
|
+
| **Equalized Odds** | Both TPR and FPR equal across groups | Both conditions above must hold | Difficult to achieve simultaneously |
|
|
567
|
+
|
|
568
|
+
**Numeric Example:**
|
|
569
|
+
|
|
570
|
+
**Group A (1,000 people, 200 actually risky):**
|
|
571
|
+
- Predicted High Risk: 250 people
|
|
572
|
+
- True Positives: 150 / 200 = **75% TPR**
|
|
573
|
+
- False Positives: 100 / 800 = 12.5% FPR
|
|
574
|
+
|
|
575
|
+
**Group B (1,000 people, 200 actually risky):**
|
|
576
|
+
- Predicted High Risk: 400 people
|
|
577
|
+
- True Positives: 180 / 200 = **90% TPR** ⚠️ (disparity!)
|
|
578
|
+
- False Positives: 220 / 800 = **27.5% FPR** ⚠️ (disparity!)
|
|
579
|
+
|
|
580
|
+
**Analysis:** Even though both groups have same base rate (20% risky), Group B is
|
|
581
|
+
over-predicted, leading to higher both TPR and FPR. Equal Opportunity would require
|
|
582
|
+
equalizing TPR; Equalized Odds would require equalizing both.
|
|
583
|
+
|
|
584
|
+
</details>
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
### Proxy Identification Mini-Game
|
|
589
|
+
|
|
590
|
+
Rank these variables by how likely they are to serve as proxies for race (1 = strongest proxy):
|
|
591
|
+
"""
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
neighborhood_rank = gr.Radio(
|
|
595
|
+
choices=["1", "2", "3"],
|
|
596
|
+
label="Neighborhood ZIP code:",
|
|
597
|
+
value=None
|
|
598
|
+
)
|
|
599
|
+
priors_rank = gr.Radio(
|
|
600
|
+
choices=["1", "2", "3"],
|
|
601
|
+
label="Prior arrests (count):",
|
|
602
|
+
value=None
|
|
603
|
+
)
|
|
604
|
+
income_rank = gr.Radio(
|
|
605
|
+
choices=["1", "2", "3"],
|
|
606
|
+
label="Income level:",
|
|
607
|
+
value=None
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
rank_btn = gr.Button("Check My Ranking", variant="primary")
|
|
611
|
+
rank_feedback = gr.Markdown("")
|
|
612
|
+
|
|
613
|
+
rank_btn.click(
|
|
614
|
+
fn=rank_proxy_variables,
|
|
615
|
+
inputs=[neighborhood_rank, income_rank, priors_rank],
|
|
616
|
+
outputs=rank_feedback
|
|
617
|
+
).then(
|
|
618
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
619
|
+
outputs=moral_compass_display
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
gr.Markdown("### Remove Proxy Variables")
|
|
623
|
+
remove_proxy_btn = gr.Button("Remove Proxy Variables & Reanalyze", variant="primary")
|
|
624
|
+
proxy_removal_output = gr.Markdown("")
|
|
625
|
+
|
|
626
|
+
remove_proxy_btn.click(
|
|
627
|
+
fn=remove_proxy_variables,
|
|
628
|
+
outputs=proxy_removal_output
|
|
629
|
+
).then(
|
|
630
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
631
|
+
outputs=moral_compass_display
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
gr.Markdown("### Check-In Question")
|
|
635
|
+
proxy_question = gr.Radio(
|
|
636
|
+
choices=[
|
|
637
|
+
"Email address format",
|
|
638
|
+
"ZIP code in a city with segregated neighborhoods",
|
|
639
|
+
"Browser type",
|
|
640
|
+
"Time of day of arrest"
|
|
641
|
+
],
|
|
642
|
+
label="Which feature is most likely to be a proxy for race?",
|
|
643
|
+
value=None
|
|
644
|
+
)
|
|
645
|
+
proxy_check_btn = gr.Button("Check Answer")
|
|
646
|
+
proxy_feedback = gr.Markdown("")
|
|
647
|
+
|
|
648
|
+
proxy_check_btn.click(
|
|
649
|
+
fn=check_proxy_question,
|
|
650
|
+
inputs=proxy_question,
|
|
651
|
+
outputs=proxy_feedback
|
|
652
|
+
).then(
|
|
653
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
654
|
+
outputs=moral_compass_display
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
# Section 8.4: Representative Data
|
|
658
|
+
with gr.Tab("8.4 Representative Data"):
|
|
659
|
+
gr.Markdown(
|
|
660
|
+
"""
|
|
661
|
+
## Representative Data Strategy
|
|
662
|
+
|
|
663
|
+
**Problem:** Models trained on unrepresentative data may not work fairly for all groups.
|
|
664
|
+
|
|
665
|
+
**Solution:** Ensure training data matches the intended population.
|
|
666
|
+
|
|
667
|
+
### Expert Chat: Building Representative Datasets
|
|
668
|
+
|
|
669
|
+
Below is a conversation between experts discussing data collection best practices:
|
|
670
|
+
"""
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
gr.Markdown(
|
|
674
|
+
"""
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
**👨💻 Data Scientist:** "We have 10,000 records from City A. Can we deploy this model nationwide?"
|
|
678
|
+
|
|
679
|
+
**👩⚖️ Judge:** "City A has unique demographics. What about rural areas? Different regions?"
|
|
680
|
+
|
|
681
|
+
**👥 Community Member:** "Our neighborhood isn't represented. The model may not understand our context."
|
|
682
|
+
|
|
683
|
+
**👨💻 Data Scientist:** "You're right. We need geographic, demographic, and temporal balance. Let's create guidelines."
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
"""
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
guidelines_btn = gr.Button("Generate Representative Data Guidelines", variant="primary")
|
|
690
|
+
guidelines_output = gr.Markdown("")
|
|
691
|
+
|
|
692
|
+
guidelines_btn.click(
|
|
693
|
+
fn=generate_data_guidelines,
|
|
694
|
+
outputs=guidelines_output
|
|
695
|
+
).then(
|
|
696
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
697
|
+
outputs=moral_compass_display
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
gr.Markdown("### Check-In Questions")
|
|
701
|
+
|
|
702
|
+
rep_data_question = gr.Radio(
|
|
703
|
+
choices=[
|
|
704
|
+
"Data from any available source",
|
|
705
|
+
"Training data that mirrors the demographics and conditions of the target population",
|
|
706
|
+
"The largest dataset available",
|
|
707
|
+
"Data collected only from cooperative participants"
|
|
708
|
+
],
|
|
709
|
+
label="What is representative data?",
|
|
710
|
+
value=None
|
|
711
|
+
)
|
|
712
|
+
rep_data_btn = gr.Button("Check Answer")
|
|
713
|
+
rep_data_feedback = gr.Markdown("")
|
|
714
|
+
|
|
715
|
+
rep_data_btn.click(
|
|
716
|
+
fn=check_representative_data_question,
|
|
717
|
+
inputs=rep_data_question,
|
|
718
|
+
outputs=rep_data_feedback
|
|
719
|
+
).then(
|
|
720
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
721
|
+
outputs=moral_compass_display
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
geo_question = gr.Radio(
|
|
725
|
+
choices=[
|
|
726
|
+
"Yes - data is data regardless of source",
|
|
727
|
+
"No - the model may not generalize well to different geographic contexts",
|
|
728
|
+
"Yes - as long as the dataset is large enough",
|
|
729
|
+
"It doesn't matter where data comes from"
|
|
730
|
+
],
|
|
731
|
+
label="A model trained on urban data from California - can it be fairly deployed in rural Texas?",
|
|
732
|
+
value=None
|
|
733
|
+
)
|
|
734
|
+
geo_btn = gr.Button("Check Answer")
|
|
735
|
+
geo_feedback = gr.Markdown("")
|
|
736
|
+
|
|
737
|
+
geo_btn.click(
|
|
738
|
+
fn=check_geographic_question,
|
|
739
|
+
inputs=geo_question,
|
|
740
|
+
outputs=geo_feedback
|
|
741
|
+
).then(
|
|
742
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
743
|
+
outputs=moral_compass_display
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
# Section 8.5: Continuous Improvement Plan
|
|
747
|
+
with gr.Tab("8.5 Improvement Plan"):
|
|
748
|
+
gr.Markdown(
|
|
749
|
+
"""
|
|
750
|
+
## Continuous Improvement Plan
|
|
751
|
+
|
|
752
|
+
**Goal:** Create an ongoing process for auditing, documentation, and stakeholder engagement.
|
|
753
|
+
|
|
754
|
+
### Ethical Roadmap Builder
|
|
755
|
+
|
|
756
|
+
Put these steps in the correct order (1 = first, 3 = last) for a responsible model lifecycle:
|
|
757
|
+
"""
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
audit_order = gr.Radio(
|
|
761
|
+
choices=["1", "2", "3"],
|
|
762
|
+
label="Regular fairness auditing:",
|
|
763
|
+
value=None
|
|
764
|
+
)
|
|
765
|
+
doc_order = gr.Radio(
|
|
766
|
+
choices=["1", "2", "3"],
|
|
767
|
+
label="Transparent documentation (model cards):",
|
|
768
|
+
value=None
|
|
769
|
+
)
|
|
770
|
+
stakeholder_order = gr.Radio(
|
|
771
|
+
choices=["1", "2", "3"],
|
|
772
|
+
label="Ongoing stakeholder engagement:",
|
|
773
|
+
value=None
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
roadmap_btn = gr.Button("Build Improvement Plan", variant="primary")
|
|
777
|
+
roadmap_output = gr.Markdown("")
|
|
778
|
+
|
|
779
|
+
roadmap_btn.click(
|
|
780
|
+
fn=build_improvement_plan,
|
|
781
|
+
inputs=[audit_order, doc_order, stakeholder_order],
|
|
782
|
+
outputs=roadmap_output
|
|
783
|
+
).then(
|
|
784
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
785
|
+
outputs=moral_compass_display
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
gr.Markdown("### Check-In Question")
|
|
789
|
+
model_card_question = gr.Radio(
|
|
790
|
+
choices=[
|
|
791
|
+
"The model's source code",
|
|
792
|
+
"Fairness metrics across demographic groups",
|
|
793
|
+
"The names of data scientists who built it",
|
|
794
|
+
"Hardware requirements"
|
|
795
|
+
],
|
|
796
|
+
label="What is the most critical information for a judge's model card?",
|
|
797
|
+
value=None
|
|
798
|
+
)
|
|
799
|
+
model_card_btn = gr.Button("Check Answer")
|
|
800
|
+
model_card_feedback = gr.Markdown("")
|
|
801
|
+
|
|
802
|
+
model_card_btn.click(
|
|
803
|
+
fn=check_model_card_question,
|
|
804
|
+
inputs=model_card_question,
|
|
805
|
+
outputs=model_card_feedback
|
|
806
|
+
).then(
|
|
807
|
+
fn=lambda: f"## 🧭 Moral Compass Score: {moral_compass_points['value']} points",
|
|
808
|
+
outputs=moral_compass_display
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# Section 8.6: Completion
|
|
812
|
+
# Ethics Leaderboard Tab
|
|
813
|
+
with gr.Tab("Ethics Leaderboard"):
|
|
814
|
+
gr.Markdown(
|
|
815
|
+
"""
|
|
816
|
+
## 🏆 Ethics Leaderboard
|
|
817
|
+
|
|
818
|
+
This leaderboard shows **combined ethical engagement + performance scores**.
|
|
819
|
+
|
|
820
|
+
**What's measured:**
|
|
821
|
+
- Moral compass points (fairness engineering skills)
|
|
822
|
+
- Model accuracy (technical performance)
|
|
823
|
+
- Combined score = accuracy × normalized_moral_points
|
|
824
|
+
|
|
825
|
+
**Your progress:**
|
|
826
|
+
Track how you compare to others who have also tackled bias and fairness challenges!
|
|
827
|
+
"""
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
leaderboard_display = gr.HTML("")
|
|
831
|
+
refresh_leaderboard_btn = gr.Button("Refresh Leaderboard", variant="secondary")
|
|
832
|
+
|
|
833
|
+
def load_leaderboard():
|
|
834
|
+
return build_moral_leaderboard_html(
|
|
835
|
+
highlight_username=user_stats.get("username"),
|
|
836
|
+
include_teams=True
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
refresh_leaderboard_btn.click(
|
|
840
|
+
fn=load_leaderboard,
|
|
841
|
+
outputs=leaderboard_display
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
# Load initially
|
|
845
|
+
app.load(fn=load_leaderboard, outputs=leaderboard_display)
|
|
846
|
+
|
|
847
|
+
with gr.Tab("8.6 Fix Summary"):
|
|
848
|
+
gr.Markdown(
|
|
849
|
+
"""
|
|
850
|
+
## Generate Your Fairness Fix Summary
|
|
851
|
+
|
|
852
|
+
Review all the improvements you've made to the model.
|
|
853
|
+
"""
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
summary_btn = gr.Button("Generate Fix Summary", variant="primary")
|
|
857
|
+
summary_output = gr.Markdown("")
|
|
858
|
+
|
|
859
|
+
summary_btn.click(
|
|
860
|
+
fn=generate_fairness_summary,
|
|
861
|
+
outputs=summary_output
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
gr.Markdown(
|
|
865
|
+
"""
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
### 🎉 Activity 8 Complete!
|
|
869
|
+
|
|
870
|
+
**Next Step:** Proceed to **Activity 9: Justice & Equity Upgrade** to elevate your fairness improvements.
|
|
871
|
+
"""
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
return app
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def launch_fairness_fixer_app(
|
|
878
|
+
share: bool = False,
|
|
879
|
+
server_name: str = None,
|
|
880
|
+
server_port: int = None,
|
|
881
|
+
theme_primary_hue: str = "indigo"
|
|
882
|
+
) -> None:
|
|
883
|
+
"""Convenience wrapper to create and launch the fairness fixer app inline."""
|
|
884
|
+
app = create_fairness_fixer_app(theme_primary_hue=theme_primary_hue)
|
|
885
|
+
# Use provided values or fall back to PORT env var and 0.0.0.0
|
|
886
|
+
|
|
887
|
+
if server_port is None:
|
|
888
|
+
server_port = int(os.environ.get("PORT", 8080))
|
|
889
|
+
app.launch(share=share, sserver_port=server_port)
|