Spaces:
Sleeping
Sleeping
File size: 4,843 Bytes
b3eaf23 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | """Hourly learning cycle: test solvers, record results, analyze, optimize.
Run locally: python scripts/run_learning_cycle.py
Run via GH Action: triggered by .github/workflows/learn.yml
This simulates captcha solves across all solver types to gather
accuracy data over time. The learning DB is stored at data/learning.db
and exported as a GitHub Actions artifact.
"""
from __future__ import annotations
import json
import os
import random
import sys
import time
from pathlib import Path
# Add parent to path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from captcha_solver.learning.db import LearningDB
from captcha_solver.learning.trainer import Trainer
def generate_math_samples(count: int = 20) -> list[dict]:
"""Generate math captcha test cases: one per method (regex, llm, tesseract)."""
samples = []
ops = [
(lambda a, b: a + b, "+"),
(lambda a, b: a - b, "-"),
(lambda a, b: a * b, "*"),
]
for _ in range(count):
a = random.randint(1, 99)
b = random.randint(1, min(99, a if False else 99))
op_fn, op_str = random.choice(ops)
if op_str == "-" and b > a:
a, b = b, a
result = op_fn(a, b)
samples.append({
"type": "math",
"hint": f"{a} {op_str} {b} = ?",
"expected": str(result),
})
return samples
def simulate_solve(sample: dict, solver: str) -> dict:
"""Simulate a solver attempt (no real models needed for CI)."""
start = time.time()
# Simulate latency
latency = random.uniform(0.05, 0.5)
time.sleep(latency * 0.01) # 1% of real time for speed
# Simulated accuracy per solver
accuracy_map = {
"regex": 0.92,
"llm": 0.88,
"tesseract": 0.65,
"florence2": 0.75,
"moondream2": 0.50,
"qwen": 0.70,
"whisper": 0.80,
}
accuracy = accuracy_map.get(solver, 0.5)
is_correct = random.random() < accuracy
answer = sample["expected"] if is_correct else str(random.randint(0, 999))
elapsed = int((time.time() - start) * 1000)
return {
"answer": answer,
"expected": sample["expected"],
"correct": is_correct,
"latency_ms": elapsed,
"confidence": accuracy if is_correct else accuracy * 0.3,
}
def run_cycle(output_dir: str | Path | None = None) -> dict:
"""Run one full learning cycle.
Args:
output_dir: if set, saves results as JSON here (for GH artifact).
"""
db = LearningDB()
trainer = Trainer(db)
# Start cycle
cycle_id = db.start_cycle()
print(f"[cycle {cycle_id}] starting...")
solvers = ["regex", "llm", "tesseract", "florence2", "moondream2", "qwen", "whisper"]
samples = generate_math_samples(30)
total = 0
correct = 0
latencies = []
for sample in samples:
solver = random.choice(solvers)
result = simulate_solve(sample, solver)
db.record_attempt(
captcha_type=sample["type"],
solver_used=solver,
answer=result["answer"],
expected=result["expected"],
correct=result["correct"],
confidence=result.get("confidence"),
latency_ms=result["latency_ms"],
hint=sample.get("hint"),
run_source="learning_cycle",
)
total += 1
if result["correct"]:
correct += 1
latencies.append(result["latency_ms"])
status = "OK" if result["correct"] else "FAIL"
print(f" [{solver:12}] {sample['hint']:20} -> {result['answer']:6} ({status})")
avg_lat = sum(latencies) / max(len(latencies), 1)
db.finish_cycle(cycle_id, total, correct, avg_lat)
# Analyze
analysis = trainer.optimize(cycle_id)
print(f"\n[cycle {cycle_id}] complete: {correct}/{total} correct ({correct/max(total,1)*100:.1f}%), avg {avg_lat:.0f}ms")
result = {
"cycle_id": cycle_id,
"total": total,
"correct": correct,
"accuracy_pct": round(correct / max(total, 1) * 100, 1),
"avg_latency_ms": round(avg_lat),
"changes": analysis,
"stats": db.summary(),
"recommendations": analysis,
}
if output_dir:
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
(out / "cycle_result.json").write_text(json.dumps(result, indent=2, default=str), encoding="utf-8")
print(f"\nresult saved to {out / 'cycle_result.json'}")
return result
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--output", "-o", default=None, help="Output directory for results JSON")
args = parser.parse_args()
result = run_cycle(args.output)
print(f"\naccuracy: {result['accuracy_pct']}% over {result['total']} solves")
|