Aprender jogando

Abandonamos a memorização da ordem e deixamos o modelo jogar o tabuleiro sozinho. Quando o modelo faz uma jogada, o servidor a compara com a resposta e, se estiver errada, um oráculo (um código que sabe a resposta) faz a jogada correta no lugar dele. Com os registros acumulados dessa forma, o treinamento do LoRA continuava a cada 3 partidas.

Rodamos 34 ciclos ao longo de três dias. A proporção de jogadas corretas do modelo foi de 2,0–3,6% por ciclo, e de 1.695 em 62.842 (2,7%) no total. Por mais que treinássemos, ela não subia.

Encontrar uma casa certa de uma vez

Em seguida, fizemos o modelo escolher diretamente uma casa 100% certa no tabuleiro inteiro (lora_cell). Rodamos 7 ciclos, colocando de volta os problemas que ele errava, mas a taxa de acerto em 50 problemas de teste foi de 6–16%. Quando o colocamos para fazer 15 jogadas em tabuleiros reais, ficou entre 0 e 33%.

Também tentamos um currículo que separava dígitos e casas e ensinava primeiro a parte fácil. O acerto de dígitos caiu de 34% para 26%, e o acerto de casas não saiu de 0%.

Taxa de acerto em 34 ciclos de treinamento por tentativa e erro e em 7 ciclos de busca de casas certas
Taxa de acerto em 34 ciclos de treinamento por tentativa e erro e em 7 ciclos de busca de casas certas

O que o oráculo ensinou

Nessa época, escrevi separadamente no solve.md como as pessoas resolvem sudoku. Nesse documento, descrevi assim o motivo da falha.

O oráculo conhece valores com probabilidade abaixo de 20% e treina o VLM com esses valores, então os resultados treinados não mostram diferença, e por isso parece que o VLM não tem nenhuma regularidade sobre exatamente por que essa resposta fica desse jeito.

O oráculo sempre sabe a resposta. Ele não se importa se aquela casa pode ser deduzida a partir do tabuleiro atual. Com respostas misturadas que não têm base no tabuleiro, não havia regra para o modelo aprender. Era o mesmo problema da folha de respostas do post 1.

O código real desta etapa

Este é um ciclo do loop que joga 3 partidas e depois treina. Ele joga as partidas, extrai dados de treinamento dos registros acumulados e continua o treinamento do LoRA.

vlm_play_train_loop.pypython · 38 linhas
results, play_accuracy = run_games(
    session,
    games=args.games_per_cycle,
    difficulty=args.difficulty,
    max_steps=args.max_steps,
    verbose=args.verbose,
    save_images=None,
    game_id_start=state.game_id_next,
    accuracy=play_accuracy,
    human=args.human,
)
state.game_id_next += args.games_per_cycle
print_summary(results, play_accuracy)
print(f"\n사이클 {cycle} 시도정답률(리젝 포함): {play_accuracy.fmt()}")

# ...

total_lines = count_replay_lines(replay_path)
cycle_jsonl = cycle_sft_dir / f"cycle_{cycle:04d}.jsonl"
kept, new_total, by_outcome = export_trainable(
    replay_path,
    cycle_jsonl,
    since_line=state.replay_line_offset,
)

# ...

t0 = time.monotonic()
exit_code = run_training(
    dataset_jsonl=cycle_jsonl,
    base_model=args.base_model,
    adapter_dir=adapter_in,
    output_dir=args.output_dir,
    device=args.device,
    epochs=args.train_epochs,
    batch_size=args.batch_size,
    grad_accum=args.grad_accum,
)

O próximo post é "3. Transformando a resolução humana em regras".