O mesmo quebra-cabeça, dois métodos

Resolvemos um quebra-cabeça fácil (35 casas vazias) duas vezes com o LoRA do 8B do post 3 (15 ciclos).

  • 1ª rodada: para os dígitos de 1 a 9, em ordem, o VLM lista as casas onde o dígito não pode entrar, e as casas que são o único lugar que sobrou são preenchidas.
  • 2ª rodada: o mesmo método, mas, sempre que uma casa é preenchida, o código preenche imediatamente qualquer caixa, linha ou coluna que tenha ficado com só uma casa vazia.

1ª rodada: parou com 2 casas vazias

Em 16 julgamentos, ele fez 38 jogadas, e 33 estavam corretas. Ele parou com 2 casas vazias, sem encontrar mais onde jogar. Dos 16 julgamentos, 9 listaram as casas impossíveis sem deixar nenhuma de fora.

As 5 jogadas erradas vieram todas de marcar por engano uma casa possível como impossível. Com um lugar a mais riscado, uma casa errada sobrava como único lugar. A demonstração não colocava as jogadas erradas no tabuleiro; apenas as registrava.

2ª rodada: completou em 10 julgamentos

A 2ª rodada preencheu as 35 casas em 10 julgamentos. Os julgamentos do VLM preencheram 15 casas, o código preencheu em seguida mais 20, e não houve nenhuma casa errada. Dos 10 julgamentos, 8 estavam completos, mas as casas que ficaram de fora não mudaram nenhuma resposta.

Quando o dígito 2 preencheu duas casas, surgiram caixas, linhas e colunas com uma só casa vazia, e o código preencheu cinco casas seguidas. Com o dígito 7 e com o dígito 3 da segunda volta, também foram cinco casas encadeadas em cada um. Com menos julgamentos a fazer, o modelo teve menos chances de errar.

A ordem em que as casas foram preenchidas na 2ª rodada. Dourado é o julgamento do VLM, verde é o código
A ordem em que as casas foram preenchidas na 2ª rodada. Dourado é o julgamento do VLM, verde é o código

As respostas anteriores criam as próximas

O modelo do post 1 preenchia na horizontal a partir do canto superior esquerdo e nunca usava as respostas anteriores como base para a próxima casa. A 2ª rodada preenche casas espalhadas pelo tabuleiro, e cada casa recém-preenchida vira a base para a próxima. É a mesma ordem em que as pessoas resolvem.

Casas vazias restantes de acordo com o número de julgamentos no mesmo quebra-cabeça
Casas vazias restantes de acordo com o número de julgamentos no mesmo quebra-cabeça

O código real desta etapa

Este é o código que roda depois de cada julgamento do VLM na 2ª rodada. Se uma caixa, linha ou coluna ficar com só uma casa vazia, ele a preenche com o dígito que falta.

vlm_digit_strategy.pypython · 55 linhas
def _missing_one(values: list[int]) -> int | None:
    present = {v for v in values if v != 0}
    miss = [d for d in range(1, 10) if d not in present]
    return miss[0] if len(miss) == 1 else None


def last_empty_unit_moves(
    board: list[list[int]],
) -> list[tuple[int, int, int, str]]:
    """박스·행·열에 빈칸이 정확히 1개면 빠진 숫자로 채움."""
    found: dict[tuple[int, int], tuple[int, str]] = {}

    def add(r: int, c: int, digit: int, reason: str) -> None:
        key = (r, c)
        if key not in found:
            found[key] = (digit, reason)
        elif found[key][0] == digit:
            prev = found[key][1]
            if reason not in prev:
                found[key] = (digit, f"{prev}+{reason}")

    for br in range(3):
        for bc in range(3):
            cells = [
                (r, c)
                for r in range(br * 3, br * 3 + 3)
                for c in range(bc * 3, bc * 3 + 3)
            ]
            empties = [(r, c) for r, c in cells if board[r][c] == 0]
            if len(empties) != 1:
                continue
            digit = _missing_one([board[r][c] for r, c in cells])
            if digit is not None:
                add(empties[0][0], empties[0][1], digit, "code_last_box")

    for r in range(9):
        empties = [(r, c) for c in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for c in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_row")

    for c in range(9):
        empties = [(r, c) for r in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for r in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_col")

    return [
        (r, c, digit, reason)
        for (r, c), (digit, reason) in sorted(found.items())
    ]

O próximo post é "5. Uma IA que ajuda as pessoas".