ballagb19's picture
Upload captcha_solver/learning/trainer.py with huggingface_hub
dd712d2 verified
Raw
History Blame Contribute Delete
4.23 kB
"""Analyzes learning data and suggests/executes optimization actions.
Runs as part of the hourly learning cycle."""
from __future__ import annotations
import json
import os
from typing import Any
from .db import LearningDB
class Trainer:
def __init__(self, db: LearningDB | None = None) -> None:
self.db = db or LearningDB()
def analyze(self) -> dict[str, Any]:
"""Run analysis on all solver stats and return recommendations."""
summary = self.db.summary()
rankings = self.db.get_solver_ranking()
failures = self.db.get_recent_failures(limit=50)
recommendations: list[str] = []
actions: list[str] = []
# Group failures by solver
failure_by_solver: dict[str, list[dict]] = {}
for f in failures:
failure_by_solver.setdefault(f["solver_used"], []).append(f)
for solver, fails in failure_by_solver.items():
rate = len(fails) / max(summary["total_attempts"], 1) * 100
if rate > 30:
recommendations.append(
f"{solver}: {len(fails)} recent failures ({rate:.0f}%). Consider: lowering confidence threshold, adding preprocessing, or using fallback solver."
)
# Identify underperforming solvers
for r in rankings:
total = r["total"]
if total < 5:
continue
acc = r["correct"] / max(total, 1)
if acc < 0.4:
recommendations.append(
f"{r['solver_name']} on {r['captcha_type']}: {acc:.0%} accuracy ({r['correct']}/{total}). Flagged for review."
)
# Overall health
if summary["total_attempts"] > 0:
if summary["accuracy_pct"] < 50:
recommendations.insert(0, f"CRITICAL: overall accuracy {summary['accuracy_pct']}% — pipeline needs calibration.")
elif summary["accuracy_pct"] < 70:
recommendations.insert(0, f"WARNING: overall accuracy {summary['accuracy_pct']}% — room for improvement.")
return {
"summary": summary,
"rankings": rankings,
"failures_analyzed": len(failures),
"recommendations": recommendations,
"actions": actions,
"total_solvers": len(rankings),
}
def optimize(self, cycle_id: int) -> list[dict]:
"""Run one optimization pass. Logs each change."""
analysis = self.analyze()
changes: list[dict] = []
# 1. If a solver is dominating (high accuracy, high volume), note it
rankings = analysis["rankings"]
if rankings:
best = rankings[0]
if best["total"] > 10 and (best["correct"] / max(best["total"], 1)) > 0.8:
changes.append({
"action": "prioritize",
"before_val": "",
"after_val": best["solver_name"],
"before_acc": 0,
"after_acc": round(best["correct"] / best["total"], 3),
"notes": f"{best['solver_name']} is top performer on {best['captcha_type']} ({best['correct']}/{best['total']}). Priority increased."
})
# 2. If a solver has < 5 samples, flag for more testing
for r in rankings:
if r["total"] < 5 and r["total"] > 0:
changes.append({
"action": "collect_more",
"before_val": str(r["total"]),
"after_val": str(r["total"] + 10),
"before_acc": round(r["correct"] / max(r["total"], 1), 3),
"after_acc": 0,
"notes": f"{r['solver_name']} on {r['captcha_type']}: only {r['total']} samples. Need 10+ for reliable stats."
})
# Log all changes
for c in changes:
self.db.log_optimization(
cycle=cycle_id,
action=c["action"],
before_val=c["before_val"],
after_val=c["after_val"],
before_acc=c["before_acc"],
after_acc=c["after_acc"],
notes=c.get("notes", ""),
)
return changes