| |
| """#16 Curriculum Learning — easy-to-hard scheduling. |
| |
| Reference: Bengio 2009; Hacohen 2019; SuperLoss (Castells 2020). |
| Düşük-uncertainty / yüksek-quality örneklerle başla, zorlaşır. |
| |
| Kullanılan alanlar: |
| - quality_score (composite) |
| - uncertainty_score (heuristic / model-based) |
| - class_frequency_tier |
| """ |
| import json |
| from pathlib import Path |
|
|
| ROOT = Path("/arf/scratch/stakan/hitit-proje") |
|
|
| class CurriculumSampler: |
| """Epoch'a göre sample pool'u genişleyen curriculum sampler.""" |
| |
| def __init__(self, records, difficulty_fn=None): |
| self.records = records |
| self.difficulty_fn = difficulty_fn or self._default_difficulty |
| self.sorted_indices = self._sort_by_difficulty() |
| |
| def _default_difficulty(self, r): |
| """Low = easy, high = hard.""" |
| qs = r.get('quality_score', 0.5) |
| us = r.get('uncertainty_score', 0.5) |
| tier = r.get('class_frequency_tier', 'mid') |
| tier_bonus = {'head': 0.0, 'mid': 0.2, 'tail': 0.5, 'rare': 0.8}.get(tier, 0.3) |
| |
| return (1 - qs) * 0.4 + us * 0.3 + tier_bonus * 0.3 |
| |
| def _sort_by_difficulty(self): |
| difficulties = [(i, self.difficulty_fn(r)) for i, r in enumerate(self.records)] |
| difficulties.sort(key=lambda x: x[1]) |
| return [i for i, _ in difficulties] |
| |
| def epoch_indices(self, epoch, total_epochs=100, warmup_ratio=0.2): |
| """Curriculum: warmup_ratio kadar sadece en kolay, sonra linear genişle.""" |
| n = len(self.records) |
| warmup_epochs = int(total_epochs * warmup_ratio) |
| |
| if epoch < warmup_epochs: |
| |
| cutoff = n // 2 |
| else: |
| |
| progress = min(1.0, (epoch - warmup_epochs) / (total_epochs - warmup_epochs)) |
| cutoff = int(n // 2 + progress * (n - n // 2)) |
| |
| return self.sorted_indices[:cutoff] |
|
|
| def main(): |
| |
| recipe = { |
| "name": "Curriculum learning — quality+uncertainty+tier difficulty", |
| "difficulty_formula": "(1 - quality_score) * 0.4 + uncertainty_score * 0.3 + tier_bonus * 0.3", |
| "tier_bonus": {"head": 0.0, "mid": 0.2, "tail": 0.5, "rare": 0.8}, |
| "schedule": "First 20% epochs: easiest 50%. Then linear expansion to full pool by end.", |
| "expected_gain": "+0.2-0.5% (literature mixed results)", |
| "fields_used": ["quality_score", "uncertainty_score", "class_frequency_tier"], |
| } |
| with open(ROOT / "datasets/processed/curriculum_recipe.json", 'w') as f: |
| json.dump(recipe, f, indent=2, ensure_ascii=False) |
| print("Curriculum sampler hazır") |
|
|
| if __name__ == '__main__': |
| main() |
|
|