同じパズル、二つの方法

やさしいパズル一つ(空きマス35)を、3回目の記事の 8B LoRA(15サイクル)で2回解きました。

  • 1回目: 数字1から9まで順に、VLM が置けないマスを書き出し、場所が一つだけ残ったマスを埋めます。
  • 2回目: 同じ方法に加えて、マスを埋めるたびに、空きマスが一つ残ったボックス・行・列をコードがすぐに埋めます。

1回目: 空きマス2つで止まる

16回判定する間に38手を出し、33手が正解でした。空きマスを2つ残して、それ以上打つ場所を見つけられませんでした。置けないマスを漏れなく当てた判定は16回中9回でした。

間違えた5手は、すべて置けるマスを置けないと誤って書いたことから生まれました。場所が一つ余計に消されたので、見当違いのマスが唯一の場所として残ったのです。デモは間違えた手を盤に載せず、記録だけしました。

2回目: 10回の判定で完走

2回目は10回の判定で35マスをすべて埋めました。VLM の判定で15マス、続けてコードが20マスを埋め、間違えたマスはありませんでした。漏れなく当てた判定は10回中8回でしたが、漏れたマスが答えを変えることはありませんでした。

数字2で2マスを埋めると、空きマスが一つだけ残ったボックス・行・列ができ、コードが5マスを続けて埋めました。数字7と2周目の数字3でも5マスずつ続きました。判定すべきことが減ったので、モデルが間違える機会も減りました。

2回目の解き方でマスを埋めた順序。金色は VLM の判定、緑はコード
2回目の解き方でマスを埋めた順序。金色は VLM の判定、緑はコード

前の答えが次の答えを作る

1回目の記事のモデルは左上から横に埋め、前に埋めた答えを次のマスの根拠に使いませんでした。2回目の解き方は盤のあちこちに散らばって埋まり、いま埋めたマスが次のマスの根拠になります。人が解く順序と同じです。

同じパズルで判定回数ごとに残った空きマス
同じパズルで判定回数ごとに残った空きマス

この段階の実際のコード

2回目で VLM の判定の後に動くコードです。ボックス・行・列に空きマスが一つだけ残ったら、足りない数字で埋めます。

vlm_digit_strategy.pypython · 55行
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())
    ]

次の記事は「5. 人を助けるAI」です。