最初に見た解き方

[紹介](/jp/about/)に書いた場面がこのモデルです。数独を解くAIが、最初のマスから横へ順々に答えを埋めていきました。答えをすでに知っている幼い子どものようでした。

正解表で作った学習データ

最初の学習データは dataset_sudoku.py で作りました。エンジンがパズルと正解を一緒に作り、盤の中で最も前にある空きマス(左上から横の順)に正解の数字を書いたものを1問として保存します。そのマスを埋めたら次の空きマスに進み、盤が埋まるまで繰り返します。

やさしい・ふつう・むずかしいの300局から13,497問ができ、Qwen3-VL-2B-Instruct に LoRA で学習させました(lora_stoku)。

プロンプトには打てる手のリストも入れました。しかし正解はいつも最初の空きマスでした。(0,3) に5と9のどちらも入れられても、正解表が9なら9が正解です。なぜ9なのかは、データのどこにもありません。

むずかしいパズル1局を埋めた順序。赤いマスはその瞬間の盤だけでは決められなかったマス
むずかしいパズル1局を埋めた順序。赤いマスはその瞬間の盤だけでは決められなかったマス

その瞬間に解けるマスだったのか

13,497問の正解マスを、その瞬間の盤だけを見て改めて調べました。

  • 候補の数字が一つしかないマス: 9,502個(70%)
  • 行・列・ボックスの中でその数字が入れる場所が一つしかないマス: 2,127個(16%)
  • 二つの方法では決められず、正解表が必要なマス: 1,868個(14%)

正解表が必要なマスは、むずかしいで21%、ふつうで12%、やさしいで5%ほどでした。人ならこうしたマスは飛ばして、確実なマスから埋めます。モデルは同じ場所に正解表の数字を書くように学びました。

難易度別に、正解マスがその瞬間に確定できたかどうか
難易度別に、正解マスがその瞬間に確定できたかどうか

この段階の実際のコード

最初の学習データを作る関数です。empties[0] が左上から数えた最初の空きマスで、値は engine.solution から取ります。

dataset_sudoku.pypython · 42行
def generate_game_samples(
    engine: SudokuEngine,
    difficulty: str,
    game_id: int,
    image_dir: Path,
) -> list[dict]:
    engine.new_game(difficulty)
    board = [row[:] for row in engine.puzzle]
    given_mask = [[engine.is_given(r, c) for c in range(9)] for r in range(9)]
    samples: list[dict] = []
    step = 0

    while True:
        empties = [(r, c) for r in range(9) for c in range(9) if board[r][c] == 0]
        if not empties:
            break

        r, c = empties[0]
        value = engine.solution[r][c]

        img = board_to_image(board, given_mask)
        img_name = f"g{game_id:05d}_s{step:02d}.png"
        img_path = image_dir / img_name
        img.save(img_path)

        answer = json.dumps({"row": r, "col": c, "value": value}, ensure_ascii=False)
        prompt = build_sudoku_prompt(board, given_mask)

        samples.append(
            {
                "id": f"g{game_id:05d}_s{step:02d}",
                "image_path": str(img_path.resolve()),
                "prompt": prompt,
                "answer": answer,
                "difficulty": difficulty,
            }
        )

        board[r][c] = value
        step += 1

    return samples

次の記事は「2. 試行錯誤では学べなかった」です。