探索木・8パズル
ノートブックの作成
Google Colaboratory を起動し,新規にノートブックを作成してください. ノートブックのタイトルは AI-4 とします. ノートブックの作成方法は第1回の資料を参照してください.
最初に Simple AI をインストールします. セルで下記のコマンドを実行してください.
!pip install simpleai
8パズル
今回は 8パズル(8 Puzzle) を取り上げましょう. 8パズルは,前回の水差し問題に比べ,状態の組み合わせが多く,よい難易度の高い問題であると言えます. 水差し問題では,次に示す14通りの状態にしか遷移できません($(1,1)$などの状態には到達不可能).
$(0, 0)$ $(0, 1)$ $(0, 2)$ $(0, 3)$ $(0, 4)$ $(1, 0)$ $(1, 4)$ $(2, 0)$ $(2, 4)$ $(3, 0)$ $(3, 1)$ $(3, 2)$ $(3, 3)$ $(3, 4)$
一方で,8パズルはマス目にブランクを含めた9通りのタイルを配置することができるため,状態数は$9!=362,880$通りとなります. この状態空間において,初期状態から目的状態を探索することになります.
$$ 9! = 9 \times 8 \times \cdots \times 1=362880 $$
See the Pen Tower of Hanoi by Naoto Mukai (@nmukai) on CodePen.
この問題を 状態空間モデル で表現します.
状態空間モデル
タイルの数字を$x_i$で表す($i$はタイルの位置). 例えば初期状態と目的状態を下記で与える. タイルがないマスは$b(ブランク)$と表していることに注意すること. $$ 初期状態: (x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9) = (3, 1, 5, b, 7, 4, 6, 8, 2) $$ $$ 目的状態: (x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9) = (b, 1, 2, 3, 4, 5, 6, 7, 8) $$ タイルに対する行動(操作)はブランクの位置に応じて決まる. 例えば,ブランクが 左上($x_1$) にあるときは,上($x_2$)と左($x_4$)のタイルと入れ替えることができる.
- A1: $(b, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9) \rightarrow (x_2, b, x_3, x_4, x_5, x_6, x_7, x_8, x_9)$
- A2: $(b, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9) \rightarrow (x_4, x_2, x_3, b, x_5, x_6, x_7, x_8, x_9)$
- A3: $(x_1, b, x_3, x_4, x_5, x_6, x_7, x_8, x_9) \rightarrow (b, x_1, x_3, x_4, x_5, x_6, x_7, x_8, x_9)$
- A4: $(x_1, b, x_3, x_4, x_5, x_6, x_7, x_8, x_9) \rightarrow (x_1, x_3, b, x_4, x_5, x_6, x_7, x_8, x_9)$
- A5: $(x_1, b, x_3, x_4, x_5, x_6, x_7, x_8, x_9) \rightarrow (x_1, x_5, x_3, x_4, b, x_6, x_7, x_8, x_9)$
実装
クラスの定義
最初にライブラリをインポートします.
前回と同様に,探索問題の型 を表すSearchPrblem
と
幅優先探索 を表すbreadth_first
をインポートします.
from simpleai.search import SearchProblem, breadth_first
次に 初期状態 と 目的状態 を定義します. 初期状態は$(3, 1, 5, b, 7, 4, 6, 8, 2)$, 目的状態は$(b, 1, 2, 3, 4, 5, 6, 7, 8)$です. ここでは,$b$を$0$で表現していることに注意してください.
START = (
3, 1, 5,
0, 7, 4,
6, 8, 2
)
GOAL = (
0, 1, 2,
3, 4, 5,
6, 7, 8
)
インポートしたSearchProblem
クラスをオーバライドして,
8パズルを表すEightPuzzle
を定義します.
オーバライドするメソッドは,actions
,result
,is_goal
の3種類です.
class EightPuzzle(SearchProblem):
def actions(self, state):
...
def result(self, state, action):
...
def is_goal(self, state):
...
actions(self, state)
ブランクの位置に応じた行動のリストを返します.
行動は全部で24パターンが存在します.
また,状態state
のインデックスは0番から始まることに注意してください.
例えば,state[0]
は$x_1$を表しています.
def actions(self, state):
if state[0] == 0: #左上
actions = ["A1", "A2"]
if state[1] == 0: #上
actions = ["A3", "A4", "A5"]
if state[2] == 0: #右上
actions = ["A6", "A7"]
if state[3] == 0: #左
actions = ["A8", "A9", "A10"]
if state[4] == 0: #中央
actions = ["A11", "A12", "A13", "A14"]
if state[5] == 0: #右
actions = ["A15", "A16", "A17"]
if state[6] == 0: #左下
actions = ["A18", "A19"]
if state[7] == 0: #下
actions = ["A20", "A21", "A22"]
if state[8] == 0: #右下
actions = ["A23", "A24"]
return actions
result(self, state, action)
行動action
に応じて,タイルを入れ替えます.
例えば,行動A1
のときは,左上と上のタイルを入れ替えます.
def result(self, state, action):
# タプルをリストに変換
state = list(state)
if action == "A1": #左上と上を入れ替え
state[0], state[1] = state[1], state[0]
elif action == "A2": #左上と左を入れ替え
state[0], state[3] = state[3], state[0]
elif action == "A3": #上と左上を入れ替え
state[1], state[0] = state[0], state[1]
elif action == "A4": #上と右上を入れ替え
state[1], state[2] = state[2], state[1]
elif action == "A5": #上と中央を入れ替え
state[1], state[4] = state[4], state[1]
elif action == "A6": #右上と上を入れ替え
state[2], state[1] = state[1], state[2]
elif action == "A7": #右上と右を入れ替え
state[2], state[5] = state[5], state[2]
elif action == "A8": #左と左上を入れ替え
state[3], state[0] = state[0], state[3]
elif action == "A9": #左と中央を入れ替え
state[3], state[4] = state[4], state[3]
elif action == "A10": #左と左下を入れ替え
state[3], state[6] = state[6], state[3]
elif action == "A11": #中央と上を入れ替え
state[4], state[1] = state[1], state[4]
elif action == "A12": #中央と左を入れ替え
state[4], state[3] = state[3], state[4]
elif action == "A13": #中央と右を入れ替え
state[4], state[5] = state[5], state[4]
elif action == "A14": #中央と下を入れ替え
state[4], state[7] = state[7], state[4]
elif action == "A15": #右と右上を入れ替え
state[5], state[2] = state[2], state[5]
elif action == "A16": #右と中央を入れ替え
state[5], state[4] = state[4], state[5]
elif action == "A17": #右と右下を入れ替え
state[5], state[8] = state[8], state[5]
elif action == "A18": #左下と左を入れ替え
state[6], state[3] = state[3], state[6]
elif action == "A19": #左下と下を入れ替え
state[6], state[7] = state[7], state[6]
elif action == "A20": #下と中央を入れ替え
state[7], state[4] = state[4], state[7]
elif action == "A21": #下と左下を入れ替え
state[7], state[6] = state[6], state[7]
elif action == "A22": #下と右下を入れ替え
state[7], state[8] = state[8], state[7]
elif action == "A23": #右下と右を入れ替え
state[8], state[5] = state[5], state[8]
elif action == "A24": #右下と下を入れ替え
state[8], state[7] = state[7], state[8]
# リストをタプルに変換
return tuple(state)
is_goal(self, state)
状態state
がGOAL
と一致すれば目的状態に達したと判断します.
def is_goal(self, state):
return state == GOAL
解の探索
幅優先探索(探索木)breadth_first
を用いて解を探索します.
problem = EightPuzzle(initial_state=START)
result = breadth_first(problem, graph_search=True)
探索が終了したら,最終状態,パス,コストを表示します.
最終状態は(0, 1, 2, 3, 4, 5, 6, 7, 8)
となっており目的状態に到達できたことがわかります.
このとき,初期状態から目的状態までのコスト(ステップ数)は13です.
print("State:", result.state)
print("Path:", result.path())
print("Cost:", result.cost)
State: (0, 1, 2, 3, 4, 5, 6, 7, 8)
Path: [(None, (3, 1, 5, 0, 7, 4, 6, 8, 2)), ('A8', (0, 1, 5, 3, 7, 4, 6, 8, 2)), ('A1', (1, 0, 5, 3, 7, 4, 6, 8, 2)), ('A4', (1, 5, 0, 3, 7, 4, 6, 8, 2)), ('A7', (1, 5, 4, 3, 7, 0, 6, 8, 2)), ('A17', (1, 5, 4, 3, 7, 2, 6, 8, 0)), ('A24', (1, 5, 4, 3, 7, 2, 6, 0, 8)), ('A20', (1, 5, 4, 3, 0, 2, 6, 7, 8)), ('A11', (1, 0, 4, 3, 5, 2, 6, 7, 8)), ('A4', (1, 4, 0, 3, 5, 2, 6, 7, 8)), ('A7', (1, 4, 2, 3, 5, 0, 6, 7, 8)), ('A16', (1, 4, 2, 3, 0, 5, 6, 7, 8)), ('A11', (1, 0, 2, 3, 4, 5, 6, 7, 8)), ('A3', (0, 1, 2, 3, 4, 5, 6, 7, 8))]
Cost: 13
探索木(解の探索過程)
8パズルの探索木は次の図のようになります.
初期状態(3, 1, 5, 0, 7, 4, 6, 8, 2)
から目的状態(0, 1, 2, 3, 4, 5, 6, 7, 8)
までの探索過程は次の図で表すことができます.
初期状態(3, 1, 5, 0, 7, 4, 6, 8, 2)
は,左にブランクがあるため,適用可能な行動は,A8
,A9
,A10
のいずれかです.
A8
を適用すると,左と左上を交換するため,状態は(0, 1, 5, 3, 7, 4, 6, 8, 2)
になります.
A9
を適用すると,左と中央を交換するため,状態は(3, 1, 5, 7, 0, 4, 6, 8, 2)
になります.
A10
を適用すると,左と左下を交換するため,状態は(3, 1, 5, 6, 7, 4, b, 8, 2)
になります.
この新しく生成された状態に対し,さらに適用可能な行動を繰り返し,目的状態(0, 1, 2, 3, 4, 5, 6, 7, 8)
に到達すると探索は終了です.
水差し問題では探索した状態数が 12 であったことに対し,8パズルで探索した状態数は 2115 となり,探索空間が大きくなっていることがわかります.
課題
下記の初期状態から目的状態まで探索しなさい.
START = (
1, 5, 0,
3, 7, 4,
6, 8, 2
)
Google Colaboratoryで作成した AI-4.ipynb を保存し, ノートブック(.ipynb) をダウンロードして提出しなさい. 提出の前に必ず下記の設定を行うこと.
- ノートブックの設定で「セルの出力を除外する」のチェックを外す
- ノートブックの変更内容を保存して固定