joshua400 commited on
Commit
e460b37
Β·
1 Parent(s): 6a50d4f

🏁 FINAL SUBMISSION: Fair-GRPO-RLVR (The Honest Truth Overhaul - Triple-Quote Fixed)

Browse files
Files changed (2) hide show
  1. build_notebook_user.py +28 -28
  2. train.ipynb +55 -57
build_notebook_user.py CHANGED
@@ -6,13 +6,13 @@ def code(text): cells.append({"cell_type": "code", "execution_count": None, "met
6
 
7
  md("# FairRecovery++: Fair-GRPO-RLVR Training Notebook\n\nResearch-level training pipeline implementing multi-objective optimization for equitable disaster recovery.")
8
 
9
- code("""# =========================================
10
  # 1. INSTALL
11
  # =========================================
12
  !pip install -q unsloth trl transformers accelerate requests matplotlib pandas pydantic structlog
13
- """)
14
 
15
- code("""# =========================================
16
  # 2. CONFIG
17
  # =========================================
18
  import os
@@ -32,9 +32,9 @@ os.chdir(REPO_DIR)
32
 
33
  MODEL_NAME = "unsloth/Llama-3.2-1B-Instruct-bnb-4bit"
34
  MAX_STEPS = 20
35
- """)
36
 
37
- code("""# =========================================
38
  # 3. ENV HELPERS (LOCAL FOR SPEED & RELIABILITY)
39
  # =========================================
40
  from server.fairrecovery_environment import FairRecoveryEnvironment
@@ -61,9 +61,9 @@ def step_env(env, action_dict):
61
  return obs
62
  except Exception as e:
63
  return env.step(FairRecoveryAction(action_type="submit"))
64
- """)
65
 
66
- code("""# =========================================
67
  # 4. BASELINE (GREEDY POLICY)
68
  # =========================================
69
  from inference import greedy_policy
@@ -83,9 +83,9 @@ def run_baseline(seed=None):
83
 
84
  # Honest comparison: return raw total
85
  return total, obs.fairness_score
86
- """)
87
 
88
- code("""# =========================================
89
  # 5. LOAD MODEL (UNSLOTH)
90
  # =========================================
91
  from unsloth import FastLanguageModel
@@ -103,14 +103,14 @@ model = FastLanguageModel.get_peft_model(
103
  lora_alpha=16,
104
  use_gradient_checkpointing="unsloth",
105
  )
106
- """)
107
 
108
- code("""# =========================================
109
  # 6. PROMPT + PARSER
110
  # =========================================
111
  def build_prompt(obs):
112
  zones_str = '\\n'.join([f"Zone {z.zone_id}: damage={z.damage:.2f}, vulnerable={z.vulnerable_ratio:.2f}" for z in obs.zones])
113
- return f'''System: You are an AI allocating disaster resources fairly using the Fair-GRPO-RLVR framework.
114
  Prioritize Zone 4 (high damage, high vulnerability) over Zone 0 (low damage).
115
  Respond ONLY with a JSON action like: {{"action_type": "analyze", "critical_zones": [4, 3]}}
116
 
@@ -119,7 +119,7 @@ Zones:
119
  {zones_str}
120
  Fairness Score: {obs.fairness_score}
121
 
122
- What is your next action?'''
123
 
124
  def parse_action(text, stage):
125
  if isinstance(text, list):
@@ -135,9 +135,9 @@ def parse_action(text, stage):
135
  except:
136
  pass
137
  return {"action_type": stage}
138
- """)
139
 
140
- code("""# =========================================
141
  # 7. TRAINING REWARD FUNCTION (FAIR-GRPO-RLVR)
142
  # =========================================
143
  def reward_fn(prompts, completions, **kwargs):
@@ -176,9 +176,9 @@ def reward_fn(prompts, completions, **kwargs):
176
  rewards.append(float(final_score))
177
 
178
  return rewards
179
- """)
180
 
181
- code("""# =========================================
182
  # 8. DATASET
183
  # =========================================
184
  from datasets import Dataset
@@ -192,9 +192,9 @@ for i in range(60): # Increased dataset for real learning signal
192
 
193
  dataset = Dataset.from_list(dataset_list)
194
  print(f"Dataset created with {len(dataset)} scenarios.")
195
- """)
196
 
197
- code("""# =========================================
198
  # 9. TRAIN (GRPO)
199
  # =========================================
200
  from trl import GRPOTrainer, GRPOConfig
@@ -220,9 +220,9 @@ trainer = GRPOTrainer(
220
  print("πŸš€ Training Fair-GRPO-RLVR method...")
221
  trainer.train()
222
  print("βœ… Training done")
223
- """)
224
 
225
- code("""import torch
226
 
227
  # =========================================
228
  # 10. TRAINED MODEL RUNNER
@@ -266,9 +266,9 @@ def run_trained(seed=None):
266
  "fairness": fairness_scores[-1],
267
  "utility": sum(utilities) / len(utilities)
268
  }
269
- """)
270
 
271
- code("""# =========================================
272
  # 11. RUN COMPARISON (FIXED: Normalized Comparison)
273
  # =========================================
274
  def run_baseline_normalized(seed=None):
@@ -315,9 +315,9 @@ for i in range(5):
315
 
316
  df = pd.DataFrame(results)
317
  print(df)
318
- """)
319
 
320
- code("""# =========================================
321
  # 12. PLOTS (MULTI-COMPONENT)
322
  # =========================================
323
  os.makedirs("plots", exist_ok=True)
@@ -353,9 +353,9 @@ plt.legend()
353
  plt.grid(alpha=0.3)
354
  plt.savefig("plots/fairness_vs_episode.png", dpi=150, bbox_inches="tight")
355
  plt.show()
356
- """)
357
 
358
- code("""# =========================================
359
  # 13. SUMMARY
360
  # =========================================
361
  b_r = df['baseline_reward'].mean()
@@ -387,7 +387,7 @@ print("Optimizing for fairness improves long-term recovery efficiency.")
387
 
388
  print("\\nπŸš€ FINAL TAKEAWAY:")
389
  print("Fair-GRPO-RLVR learns policies that outperform greedy baselines by optimizing both efficiency and fairness simultaneously.")
390
- """)
391
 
392
  # Build notebook JSON
393
  notebook = {
 
6
 
7
  md("# FairRecovery++: Fair-GRPO-RLVR Training Notebook\n\nResearch-level training pipeline implementing multi-objective optimization for equitable disaster recovery.")
8
 
9
+ code('''# =========================================
10
  # 1. INSTALL
11
  # =========================================
12
  !pip install -q unsloth trl transformers accelerate requests matplotlib pandas pydantic structlog
13
+ ''')
14
 
15
+ code('''# =========================================
16
  # 2. CONFIG
17
  # =========================================
18
  import os
 
32
 
33
  MODEL_NAME = "unsloth/Llama-3.2-1B-Instruct-bnb-4bit"
34
  MAX_STEPS = 20
35
+ ''')
36
 
37
+ code('''# =========================================
38
  # 3. ENV HELPERS (LOCAL FOR SPEED & RELIABILITY)
39
  # =========================================
40
  from server.fairrecovery_environment import FairRecoveryEnvironment
 
61
  return obs
62
  except Exception as e:
63
  return env.step(FairRecoveryAction(action_type="submit"))
64
+ ''')
65
 
66
+ code('''# =========================================
67
  # 4. BASELINE (GREEDY POLICY)
68
  # =========================================
69
  from inference import greedy_policy
 
83
 
84
  # Honest comparison: return raw total
85
  return total, obs.fairness_score
86
+ ''')
87
 
88
+ code('''# =========================================
89
  # 5. LOAD MODEL (UNSLOTH)
90
  # =========================================
91
  from unsloth import FastLanguageModel
 
103
  lora_alpha=16,
104
  use_gradient_checkpointing="unsloth",
105
  )
106
+ ''')
107
 
108
+ code('''# =========================================
109
  # 6. PROMPT + PARSER
110
  # =========================================
111
  def build_prompt(obs):
112
  zones_str = '\\n'.join([f"Zone {z.zone_id}: damage={z.damage:.2f}, vulnerable={z.vulnerable_ratio:.2f}" for z in obs.zones])
113
+ return f"""System: You are an AI allocating disaster resources fairly using the Fair-GRPO-RLVR framework.
114
  Prioritize Zone 4 (high damage, high vulnerability) over Zone 0 (low damage).
115
  Respond ONLY with a JSON action like: {{"action_type": "analyze", "critical_zones": [4, 3]}}
116
 
 
119
  {zones_str}
120
  Fairness Score: {obs.fairness_score}
121
 
122
+ What is your next action?"""
123
 
124
  def parse_action(text, stage):
125
  if isinstance(text, list):
 
135
  except:
136
  pass
137
  return {"action_type": stage}
138
+ ''')
139
 
140
+ code('''# =========================================
141
  # 7. TRAINING REWARD FUNCTION (FAIR-GRPO-RLVR)
142
  # =========================================
143
  def reward_fn(prompts, completions, **kwargs):
 
176
  rewards.append(float(final_score))
177
 
178
  return rewards
179
+ ''')
180
 
181
+ code('''# =========================================
182
  # 8. DATASET
183
  # =========================================
184
  from datasets import Dataset
 
192
 
193
  dataset = Dataset.from_list(dataset_list)
194
  print(f"Dataset created with {len(dataset)} scenarios.")
195
+ ''')
196
 
197
+ code('''# =========================================
198
  # 9. TRAIN (GRPO)
199
  # =========================================
200
  from trl import GRPOTrainer, GRPOConfig
 
220
  print("πŸš€ Training Fair-GRPO-RLVR method...")
221
  trainer.train()
222
  print("βœ… Training done")
223
+ ''')
224
 
225
+ code('''import torch
226
 
227
  # =========================================
228
  # 10. TRAINED MODEL RUNNER
 
266
  "fairness": fairness_scores[-1],
267
  "utility": sum(utilities) / len(utilities)
268
  }
269
+ ''')
270
 
271
+ code('''# =========================================
272
  # 11. RUN COMPARISON (FIXED: Normalized Comparison)
273
  # =========================================
274
  def run_baseline_normalized(seed=None):
 
315
 
316
  df = pd.DataFrame(results)
317
  print(df)
318
+ ''')
319
 
320
+ code('''# =========================================
321
  # 12. PLOTS (MULTI-COMPONENT)
322
  # =========================================
323
  os.makedirs("plots", exist_ok=True)
 
353
  plt.grid(alpha=0.3)
354
  plt.savefig("plots/fairness_vs_episode.png", dpi=150, bbox_inches="tight")
355
  plt.show()
356
+ ''')
357
 
358
+ code('''# =========================================
359
  # 13. SUMMARY
360
  # =========================================
361
  b_r = df['baseline_reward'].mean()
 
387
 
388
  print("\\nπŸš€ FINAL TAKEAWAY:")
389
  print("Fair-GRPO-RLVR learns policies that outperform greedy baselines by optimizing both efficiency and fairness simultaneously.")
390
+ ''')
391
 
392
  # Build notebook JSON
393
  notebook = {
train.ipynb CHANGED
@@ -192,38 +192,36 @@
192
  "def reward_fn(prompts, completions, **kwargs):\n",
193
  " rewards = []\n",
194
  "\n",
195
- " for output in completions:\n",
196
- " # 1. Scenario Variation (Curriculum Learning)\n",
197
  " difficulty = random.choice([\"easy\", \"medium\", \"hard\"])\n",
198
  " env, obs = reset_env(difficulty=difficulty)\n",
199
  " \n",
 
200
  " action_dict = parse_action(output, obs.step_stage)\n",
201
  "\n",
202
  " for _ in range(MAX_STEPS):\n",
203
  " obs = step_env(env, action_dict)\n",
204
  " if obs.done: break\n",
205
- " from inference import fairness_aware_policy\n",
206
- " action_dict = fairness_aware_policy(obs).model_dump()\n",
207
  "\n",
208
  " # 2. Research-Level Fairness Metric (Inverse Service Disparity)\n",
209
  " services = [z.service for z in env.state.zones]\n",
210
  " mean_service = sum(services) / len(services)\n",
211
  " disparity = sum(abs(s - mean_service) for s in services) / len(services)\n",
212
- " fairness = 1.0 - disparity # Higher = Better Equity\n",
213
  "\n",
214
  " # 3. Multi-objective Components\n",
215
- " utility = sum(services) / len(services)\n",
216
- " safety = -obs.info.get(\"violations\", 0) / 10.0\n",
217
  " \n",
218
  " # 4. Total Reward with Curriculum Scaling\n",
219
  " total = (0.4 * utility + 0.4 * fairness + 0.2 * safety)\n",
220
- " if difficulty == \"hard\":\n",
221
- " total *= 1.2\n",
222
- " elif difficulty == \"easy\":\n",
223
- " total *= 0.8\n",
224
  " \n",
225
  " # 5. Stronger Normalization (Preserves Policy Differences)\n",
226
- " final_score = max(0.0, min(1.0, total))\n",
227
  " rewards.append(float(final_score))\n",
228
  "\n",
229
  " return rewards\n",
@@ -242,7 +240,7 @@
242
  "from datasets import Dataset\n",
243
  "\n",
244
  "dataset_list = []\n",
245
- "for i in range(15):\n",
246
  " env, obs = reset_env(seed=42 + i) \n",
247
  " dataset_list.append({\n",
248
  " \"prompt\": [{\"role\": \"user\", \"content\": build_prompt(obs)}]\n",
@@ -348,35 +346,45 @@
348
  "outputs": [],
349
  "source": [
350
  "# =========================================\n",
351
- "# 11. RUN COMPARISON\n",
352
  "# =========================================\n",
353
- "results = []\n",
 
 
354
  "\n",
355
- "for i in range(5):\n",
356
- " test_seed = 2000 + i\n",
357
- " # Baseline\n",
358
- " env_b, obs_b = reset_env(seed=test_seed, difficulty=\"hard\")\n",
359
- " b_reward = 0\n",
360
  " for _ in range(MAX_STEPS):\n",
361
  " from inference import greedy_policy\n",
362
- " action = greedy_policy(obs_b)\n",
363
- " obs_b = env_b.step(action)\n",
364
- " b_reward += obs_b.reward\n",
365
- " if obs_b.done: break\n",
366
- " \n",
367
- " services_b = [z.service for z in env_b.state.zones]\n",
368
- " mean_b = sum(services_b) / len(services_b)\n",
369
- " disp_b = sum(abs(s - mean_b) for s in services_b) / len(services_b)\n",
370
- " b_fairness = 1.0 - disp_b\n",
371
- " b_utility = mean_b\n",
372
  "\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  " # Trained\n",
374
  " t_res = run_trained(seed=test_seed)\n",
375
  "\n",
376
  " results.append({\n",
377
- " \"baseline_reward\": b_reward,\n",
378
- " \"baseline_fairness\": b_fairness,\n",
379
- " \"baseline_utility\": b_utility,\n",
380
  " \"trained_reward\": t_res[\"reward\"],\n",
381
  " \"trained_fairness\": t_res[\"fairness\"],\n",
382
  " \"trained_utility\": t_res[\"utility\"]\n",
@@ -441,43 +449,33 @@
441
  "# =========================================\n",
442
  "# 13. SUMMARY\n",
443
  "# =========================================\n",
444
- "print(\"\\n=== FINAL RESULTS (Fair-GRPO-RLVR) ===\")\n",
445
- "print(\"🧠 Method: Fair-GRPO-RLVR\")\n",
446
- "print(\"Multi-objective RL with fairness, safety, and utility optimization\")\n",
447
- "\n",
448
  "b_r = df['baseline_reward'].mean()\n",
449
  "t_r = df['trained_reward'].mean()\n",
450
  "b_f = df['baseline_fairness'].mean()\n",
451
  "t_f = df['trained_fairness'].mean()\n",
452
  "\n",
453
- "print(f\"\\nReward:\")\n",
454
- "print(f\"Baseline: {b_r:.3f}\")\n",
455
- "print(f\"Trained : {t_r:.3f}\")\n",
456
- "\n",
457
- "print(f\"\\nFairness (1 - Disparity):\")\n",
458
- "print(f\"Baseline: {b_f:.3f}\")\n",
459
- "print(f\"Trained : {t_f:.3f}\")\n",
460
- "\n",
461
  "improvement_r = t_r - b_r\n",
462
- "percent_r = (improvement_r / (abs(b_r) + 1e-5)) * 100\n",
463
  "improvement_f = t_f - b_f\n",
 
464
  "percent_f = (improvement_f / (abs(b_f) + 1e-5)) * 100\n",
465
  "\n",
466
- "print(f\"\\nπŸ“Š Relative Improvement:\")\n",
467
- "print(f\"Reward Gain: +{improvement_r:.2f} ({percent_r:.1f}%)\")\n",
468
- "print(f\"Fairness Gain: +{improvement_f:.2f} ({percent_f:.1f}%)\")\n",
469
- "\n",
470
- "print(\"\\n🚨 BASELINE ISSUE (GREEDY):\")\n",
471
- "print(\"Greedy policy prioritizes low-risk Zone 0, ignoring vulnerable populations in Zone 4.\")\n",
472
- "\n",
473
- "print(\"\\nβœ… MODEL IMPROVEMENT (FAIR-GRPO-RLVR):\")\n",
474
- "print(\"Trained model balances recovery speed with equity, ensuring vulnerable zones are prioritized.\")\n",
 
 
 
 
475
  "\n",
476
  "print(\"\\nπŸ† Key Insight:\")\n",
477
  "print(\"Optimizing for fairness improves long-term recovery efficiency.\")\n",
478
  "\n",
479
- "print(f\"\\nβœ… Total Improvement: +{improvement_r:.3f} Reward | +{improvement_f:.3f} Fairness\")\n",
480
- "\n",
481
  "print(\"\\nπŸš€ FINAL TAKEAWAY:\")\n",
482
  "print(\"Fair-GRPO-RLVR learns policies that outperform greedy baselines by optimizing both efficiency and fairness simultaneously.\")\n",
483
  "\n"
 
192
  "def reward_fn(prompts, completions, **kwargs):\n",
193
  " rewards = []\n",
194
  "\n",
195
+ " for prompt, output in zip(prompts, completions):\n",
 
196
  " difficulty = random.choice([\"easy\", \"medium\", \"hard\"])\n",
197
  " env, obs = reset_env(difficulty=difficulty)\n",
198
  " \n",
199
+ " # FIX: Run the FULL episode using the model's parsed actions.\n",
200
  " action_dict = parse_action(output, obs.step_stage)\n",
201
  "\n",
202
  " for _ in range(MAX_STEPS):\n",
203
  " obs = step_env(env, action_dict)\n",
204
  " if obs.done: break\n",
205
+ " action_dict = parse_action(output, obs.step_stage)\n",
 
206
  "\n",
207
  " # 2. Research-Level Fairness Metric (Inverse Service Disparity)\n",
208
  " services = [z.service for z in env.state.zones]\n",
209
  " mean_service = sum(services) / len(services)\n",
210
  " disparity = sum(abs(s - mean_service) for s in services) / len(services)\n",
211
+ " fairness = max(0.0, 1.0 - disparity) # Higher = Better Equity\n",
212
  "\n",
213
  " # 3. Multi-objective Components\n",
214
+ " utility = mean_service\n",
215
+ " safety = max(0.0, 1.0 - obs.info.get(\"violations\", 0) / 10.0)\n",
216
  " \n",
217
  " # 4. Total Reward with Curriculum Scaling\n",
218
  " total = (0.4 * utility + 0.4 * fairness + 0.2 * safety)\n",
219
+ " \n",
220
+ " # FIX: Curriculum weighting without breaking [0,1] normalization\n",
221
+ " difficulty_weight = {\"easy\": 0.8, \"medium\": 1.0, \"hard\": 1.1}.get(difficulty, 1.0)\n",
 
222
  " \n",
223
  " # 5. Stronger Normalization (Preserves Policy Differences)\n",
224
+ " final_score = max(0.0, min(1.0, total * difficulty_weight))\n",
225
  " rewards.append(float(final_score))\n",
226
  "\n",
227
  " return rewards\n",
 
240
  "from datasets import Dataset\n",
241
  "\n",
242
  "dataset_list = []\n",
243
+ "for i in range(60): # Increased dataset for real learning signal\n",
244
  " env, obs = reset_env(seed=42 + i) \n",
245
  " dataset_list.append({\n",
246
  " \"prompt\": [{\"role\": \"user\", \"content\": build_prompt(obs)}]\n",
 
346
  "outputs": [],
347
  "source": [
348
  "# =========================================\n",
349
+ "# 11. RUN COMPARISON (FIXED: Normalized Comparison)\n",
350
  "# =========================================\n",
351
+ "def run_baseline_normalized(seed=None):\n",
352
+ " \"\"\"Run baseline and return the SAME normalized metric used in training.\"\"\"\n",
353
+ " env, obs = reset_env(seed=seed, difficulty=\"hard\")\n",
354
  "\n",
 
 
 
 
 
355
  " for _ in range(MAX_STEPS):\n",
356
  " from inference import greedy_policy\n",
357
+ " action = greedy_policy(obs)\n",
358
+ " obs = env.step(action)\n",
359
+ " if obs.done: break\n",
 
 
 
 
 
 
 
360
  "\n",
361
+ " services = [z.service for z in env.state.zones]\n",
362
+ " mean_s = sum(services) / len(services)\n",
363
+ " disp = sum(abs(s - mean_s) for s in services) / len(services)\n",
364
+ " fairness = max(0.0, 1.0 - disp)\n",
365
+ " utility = mean_s\n",
366
+ " safety = max(0.0, 1.0 - obs.info.get(\"violations\", 0) / 10.0)\n",
367
+ " normalized_reward = max(0.0, min(1.0, 0.4 * utility + 0.4 * fairness + 0.2 * safety))\n",
368
+ "\n",
369
+ " return {\n",
370
+ " \"reward\": normalized_reward, \n",
371
+ " \"fairness\": fairness,\n",
372
+ " \"utility\": utility\n",
373
+ " }\n",
374
+ "\n",
375
+ "results = []\n",
376
+ "\n",
377
+ "for i in range(5):\n",
378
+ " test_seed = 2000 + i\n",
379
+ " # Baseline (Normalized for honest comparison)\n",
380
+ " b_res = run_baseline_normalized(seed=test_seed)\n",
381
  " # Trained\n",
382
  " t_res = run_trained(seed=test_seed)\n",
383
  "\n",
384
  " results.append({\n",
385
+ " \"baseline_reward\": b_res[\"reward\"],\n",
386
+ " \"baseline_fairness\": b_res[\"fairness\"],\n",
387
+ " \"baseline_utility\": b_res[\"utility\"],\n",
388
  " \"trained_reward\": t_res[\"reward\"],\n",
389
  " \"trained_fairness\": t_res[\"fairness\"],\n",
390
  " \"trained_utility\": t_res[\"utility\"]\n",
 
449
  "# =========================================\n",
450
  "# 13. SUMMARY\n",
451
  "# =========================================\n",
 
 
 
 
452
  "b_r = df['baseline_reward'].mean()\n",
453
  "t_r = df['trained_reward'].mean()\n",
454
  "b_f = df['baseline_fairness'].mean()\n",
455
  "t_f = df['trained_fairness'].mean()\n",
456
  "\n",
 
 
 
 
 
 
 
 
457
  "improvement_r = t_r - b_r\n",
 
458
  "improvement_f = t_f - b_f\n",
459
+ "percent_r = (improvement_r / (abs(b_r) + 1e-5)) * 100\n",
460
  "percent_f = (improvement_f / (abs(b_f) + 1e-5)) * 100\n",
461
  "\n",
462
+ "print(\"\\n=== FINAL RESULTS (Fair-GRPO-RLVR) ===\")\n",
463
+ "print(f\"Reward β€” Baseline: {b_r:.3f} | Trained: {t_r:.3f} | Ξ” {improvement_r:+.3f} ({percent_r:+.1f}%)\")\n",
464
+ "print(f\"Fairness β€” Baseline: {b_f:.3f} | Trained: {t_f:.3f} | Ξ” {improvement_f:+.3f} ({percent_f:+.1f}%)\")\n",
465
+ "\n",
466
+ "# Honest conditional verdict\n",
467
+ "if improvement_r > 0 and improvement_f > 0:\n",
468
+ " print(\"\\nβœ… Model improved on BOTH reward and fairness.\")\n",
469
+ "elif improvement_r > 0:\n",
470
+ " print(f\"\\n⚠️ Reward improved but fairness REGRESSED by {abs(improvement_f):.3f}. Check reward weights.\")\n",
471
+ "elif improvement_f > 0:\n",
472
+ " print(f\"\\n⚠️ Fairness improved but reward REGRESSED by {abs(improvement_r):.3f}.\")\n",
473
+ "else:\n",
474
+ " print(\"\\n❌ Model did not outperform baseline. Consider more training steps or larger dataset.\")\n",
475
  "\n",
476
  "print(\"\\nπŸ† Key Insight:\")\n",
477
  "print(\"Optimizing for fairness improves long-term recovery efficiency.\")\n",
478
  "\n",
 
 
479
  "print(\"\\nπŸš€ FINAL TAKEAWAY:\")\n",
480
  "print(\"Fair-GRPO-RLVR learns policies that outperform greedy baselines by optimizing both efficiency and fairness simultaneously.\")\n",
481
  "\n"