コンテンツにスキップ
Wikipedia

「A*」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
2行目: 2行目:
{{pathnav|探索|最良優先探索|frame=1}}
{{pathnav|探索|最良優先探索|frame=1}}
[[Image:Astar progress animation.gif|thumb|A*探索アルゴリズム]]
[[Image:Astar progress animation.gif|thumb|A*探索アルゴリズム]]
'''A*(A-star(削除) , (削除ここまで)エースター)探索アルゴリズム'''は、[[グラフ (データ構造)|グラフ]][[探索]][[アルゴリズム]]の一つ。
'''A*(A-star(追記) 、 (追記ここまで)エースター)探索アルゴリズム'''(追記) (エースターたんさくアルゴリズム) (追記ここまで)は、[[グラフ (データ構造)|グラフ]][[探索]][[アルゴリズム]]の一つ。
[[最良優先探索]]を拡張した[[Z*]]に、さらにf値として「現時点までの距離」gと「ゴールまでの推定値」hの和を採用したもの(削除) 。 (削除ここまで)<ref>Pearl(削除) , (削除ここまで)Judea(削除) . (削除ここまで) "Heuristics(削除) : (削除ここまで) intelligent search strategies for computer problem solving(削除) . (削除ここまで)" (削除) ( (削除ここまで)1984(削除) ). (削除ここまで)</ref>(削除) (削除ここまで)h は ヒューリスティック関数と呼ばれる。
[[最良優先探索]]を拡張した[[Z*]]に、さらにf値として「現時点までの距離」g(追記) (追記ここまで)と「ゴールまでの推定値」h(追記) (追記ここまで)の和を採用したもの<ref>(追記) {{Cite book |last= (追記ここまで)Pearl(追記) |first= (追記ここまで)Judea (追記) |authorlink=ジューディア・パール |title= (追記ここまで)"Heuristics intelligent search strategies for computer problem solving" (追記) |year= (追記ここまで)1984(追記) |isbn=0201055945 |language=English ||publisher=Addison-Wesley }} (追記ここまで)</ref>(追記) 。 (追記ここまで)h は ヒューリスティック関数と呼ばれる。


== 概要 ==
== 概要 ==
A* アルゴリズムは、「グラフ上でスタートからゴールまでの道を見つける」というグラフ探索問題において、 ヒューリスティック関数 ''h(n)'' という探索の道標となる関数を用いて探索を行うアルゴリズムである。hは各頂点nからゴールまでの距離のある妥当な推定値を返す関数で、解くグラフ探索問題の種類に応じてさまざまなhを設計することが出来る。 例えば、カーナビなどで用いられる単純な二次元の地図での探索では、hとしてユークリッド距離 <math>\sqrt{x^2 + y^2}</math> を使うことができ、この値は道に沿った実際の距離のおおまかな予測になっている。しかし、高次元空間でのグラフ探索を効率的に行うためには、より高度に設計された関数を用いる必要がある。例えば、[[15パズル]]においては[[マンハッタン距離]]や[[パターンデータベース]]、[[STRIPS]]プランニングにおいては[[FFヒューリスティック]]<ref name="#1">Hoffmann, Jörg, and Bernhard Nebel. "The FF planning system: Fast plan generation through heuristic search." Journal of Artificial Intelligence Research (2001): 253-302.</ref>(削除) , (削除ここまで)[[Merge-and-Shrink]]<ref name="#1"/>(削除) , (削除ここまで)[[Landmark-Cut]]<ref>Helmert, Malte, and Carmel Domshlak. "Landmarks, critical paths and abstractions: what's the difference anyway?." ICAPS. 2009.</ref> などがある。
A* アルゴリズムは、「グラフ上でスタートからゴールまでの道を見つける」というグラフ探索問題において、 ヒューリスティック関数 ''h(n)'' という探索の道標となる関数を用いて探索を行うアルゴリズムである。h(追記) (追記ここまで)は各頂点(追記) (追記ここまで)n(追記) (追記ここまで)からゴールまでの距離のある妥当な推定値を返す関数で、解くグラフ探索問題の種類に応じてさまざまな(追記) (追記ここまで)h(追記) (追記ここまで)を設計することが出来る。 例えば、カーナビなどで用いられる単純な二次元の地図での探索では、h(追記) (追記ここまで)としてユークリッド距離 <math>\sqrt{x^2 + y^2}</math> を使うことができ、この値は道に沿った実際の距離のおおまかな予測になっている。しかし、高次元空間でのグラフ探索を効率的に行うためには、より高度に設計された関数を用いる必要がある。例えば、[[15パズル]]においては[[マンハッタン距離]]や[[パターンデータベース]]、[[STRIPS]]プランニングにおいては[[FFヒューリスティック]]<ref name="#1">Hoffmann, Jörg, and Bernhard Nebel. "The FF planning system: Fast plan generation through heuristic search." Journal of Artificial Intelligence Research (2001): 253-302.</ref>(追記) 、 (追記ここまで)[[Merge-and-Shrink]]<ref name="#1"/>(追記) 、 (追記ここまで)[[Landmark-Cut]]<ref>Helmert, Malte, and Carmel Domshlak. "Landmarks, critical paths and abstractions: what's the difference anyway?." ICAPS. 2009.</ref> などがある。


A* アルゴリズムは、[[ダイクストラ法]]を推定値付きの場合に一般化したもので、h が恒等的に0である場合はもとのダイクストラ法に一致する。
A* アルゴリズムは、[[ダイクストラ法]]を推定値付きの場合に一般化したもので、h が恒等的に(追記) (追記ここまで)0(追記) (追記ここまで)である場合はもとのダイクストラ法に一致する。


A*の探索効率はhの正確さに左右される。
A*(追記) (追記ここまで)の探索効率は(追記) (追記ここまで)h(追記) (追記ここまで)の正確さに左右される。
もしもhがまったくでたらめな値を返すならば、探索はゴールとはあさっての方向に進んでしまい、現実的な時間内(削除) ( (削除ここまで)一時間、一週間、一年(削除) ) (削除ここまで)では解を発見できない場合がある。しかし、いくらおかしな方向に探索が進んだとしても、いつかは必ず解を発見できる保証がある(削除) ( (削除ここまで)完全性(削除) ) (削除ここまで)
もしも(追記) (追記ここまで)h(追記) (追記ここまで)がまったくでたらめな値を返すならば、探索はゴールとはあさっての方向に進んでしまい、現実的な時間内(追記) ( (追記ここまで)一時間、一週間、一年(追記) ) (追記ここまで)では解を発見できない場合がある。しかし、いくらおかしな方向に探索が進んだとしても、いつかは必ず解を発見できる保証がある(追記) ( (追記ここまで)完全性(追記) ) (追記ここまで)
一方、hが常に正しい値''h*''を返す場合、計算機は「迷うこと無く=分岐をすること無く」グラフ上の最短経路を発見することができる。そのようなhのことを、パーフェクト・ヒューリスティクスとよぶ<ref>How Good is Almost Perfect?. M Helmert, G Röger - AAAI, 2008</ref>。
一方、h(追記) (追記ここまで)が常に正しい値(追記) (追記ここまで)''h*''(追記) (追記ここまで)を返す場合、計算機は「迷うこと無く=分岐をすること無く」グラフ上の最短経路を発見することができる。そのような(追記) (追記ここまで)h(追記) (追記ここまで)のことを、パーフェクト・ヒューリスティクスとよぶ<ref>How Good is Almost Perfect?. M Helmert, G Röger - AAAI, 2008</ref>。
現実に用いられる有用なhは、これらの中間の位置にある。
現実に用いられる有用な(追記) (追記ここまで)h(追記) (追記ここまで)は、これらの中間の位置にある。


== 歴史 ==
== 歴史 ==
31行目: 31行目:
|month=July
|month=July
|issn=0536-1567
|issn=0536-1567
}}</ref>の中で最初に記述された。A* というこの一風変わった名前は、この論文でスタートからゴールまでの最短経路を確実に見つけるアルゴリズムを'''許容的'''(削除) ( (削除ここまで){{lang-en-short|admissible}}(削除) ) (削除ここまで)と呼び、論文の数式中に 許容的なアルゴリズムの[[集合]]を '''A''' と表し、その'''A'''の中でも評価回数が最適になる物を '''A<sup>*</sup>''' と表記していたためである<ref name="nillson1980"/>。
}}</ref>の中で最初に記述された。A* というこの一風変わった名前は、この論文でスタートからゴールまでの最短経路を確実に見つけるアルゴリズムを'''許容的'''(追記) ( (追記ここまで){{lang-en-short|admissible}}(追記) ) (追記ここまで)と呼び、論文の数式中に 許容的なアルゴリズムの[[集合]]を '''A''' と表し、その'''A'''の中でも評価回数が最適になる物を '''A<sup>*</sup>''' と表記していたためである<ref name="nillson1980"/>。


== A*の考え方 ==
== A*の考え方 ==
39行目: 39行目:
:''f* (n)= g* (n) + h* (n)''
:''f* (n)= g* (n) + h* (n)''


と置くことが出来る。ここで ''g* (n)'' はスタートノードから ''n'' までの最小コスト、''h* (n)'' は''n'' からゴールノードまでの最小コストである。もし ''g* (n)'' の値と ''h* (n)''の値を知っていれば、全体の最短経路''f* (n)'' は容易に求まる。しかしながら実際には ''g* (n)'' と ''h* (n)'' をあらかじめ与えることは出来ない。そこで ''f* (n)'' を次のような推定値 ''f (n)'' に置き換えることを考える。
と置くことが出来る。ここで ''g* (n)'' はスタートノードから ''n'' までの最小コスト、''h* (n)'' は(追記) (追記ここまで)''n'' からゴールノードまでの最小コストである。もし ''g* (n)'' の値と ''h* (n)''の値を知っていれば、全体の最短経路(追記) (追記ここまで)''f* (n)'' は容易に求まる。しかしながら実際には ''g* (n)'' と ''h* (n)'' をあらかじめ与えることは出来ない。そこで ''f* (n)'' を次のような推定値 ''f (n)'' に置き換えることを考える。


:<math>f(n) = g(n) + h(n)</math>
:<math>f(n) = g(n) + h(n)</math>


ここで ''g(n)'' はスタートノードから ''n'' までの最小コストの推定値、''h(n)'' は ''n'' からゴールノードまでの最小コストの推定値である。この場合 ''g'' に関しては探索の過程で更新を加えることにより''g*''に近づけてゆくことができるが、(削除) (削除ここまで)''h*'' は、実際にゴールに辿り着くまでは誰にもわからない。そこで、(削除) (削除ここまで)''h(n)'' には人間が(削除) ( (削除ここまで)ある程度妥当性を持つように(削除) ) (削除ここまで)設計した推定値を与えることにする。このアルゴリズムを '''A*探索アルゴリズム'''といい、(削除) (削除ここまで)''h (n)'' を[[ヒューリスティック]]関数という。
ここで ''g(n)'' はスタートノードから ''n'' までの最小コストの推定値、''h(n)'' は ''n'' からゴールノードまでの最小コストの推定値である。この場合 ''g'' に関しては探索の過程で更新を加えることにより(追記) (追記ここまで)''g*''(追記) (追記ここまで)に近づけてゆくことができるが、''h*'' は、実際にゴールに辿り着くまでは誰にもわからない。そこで、''h(n)'' には人間が(追記) ( (追記ここまで)ある程度妥当性を持つように(追記) ) (追記ここまで)設計した推定値を与えることにする。このアルゴリズムを '''A*探索アルゴリズム'''といい、''h (n)'' を[[ヒューリスティック]]関数という。


== アルゴリズムの流れ ==
== アルゴリズムの流れ ==
A* のアルゴリズムの実装を以下に示す。このOPENリスト実装は後に述べるように遅いことを記しておく。
A* のアルゴリズムの実装を以下に示す。この(追記) (追記ここまで)OPENリスト実装は後に述べるように遅いことを記しておく。
# スタートノード(<math>S</math>(削除) (削除ここまで))を作成する。また計算中のノードを格納しておくための[[優先度付きキュー]] '''OPENリスト'''と、計算済みのノードを格納しておく'''CLOSEリスト'''を用意する。(削除) ( (削除ここまで)名前は「リスト」だが、これらの実際のデータ構造は必ずしも[[連結リスト]]である必要はない。(削除) ) (削除ここまで)
# スタートノード(<math>S</math>)を作成する。また計算中のノードを格納しておくための[[優先度付きキュー]] '''OPENリスト'''と、計算済みのノードを格納しておく'''CLOSEリスト'''を用意する。(追記) ( (追記ここまで)名前は「リスト」だが、これらの実際のデータ構造は必ずしも[[連結リスト]]である必要はない。(追記) ) (追記ここまで)
# ゴールは必ずしも1つである必要はないので、ゴール条件を満たすノード集合を <MATH>G</MATH> と呼ぶことにする。
# ゴールは必ずしも1つである必要はないので、ゴール条件を満たすノード集合を <MATH>G</MATH> と呼ぶことにする。
# スタートノードをOpenリストに追加する、このとき <math>g(S)</math> = <math>0</math> であり <math>f(S)</math> = <math>h(S)</math> となる。また Closeリストは空にする。
# スタートノードを(追記) (追記ここまで)Openリストに追加する、このとき <math>g(S)</math> = <math>0</math> であり <math>f(S)</math> = <math>h(S)</math> となる。また Closeリストは空にする。
# Openリストが空なら探索は失敗とする(スタートからゴールにたどり着くような経路が存在しなかったことになる)。
# Openリストが空なら探索は失敗とする(スタートからゴールにたどり着くような経路が存在しなかったことになる)。
# Openリストに格納されているノードの内、最小の <math>f(n)</math> を持つノード <math>n</math> を1つ取り出す。同じ<math>f(n)</math>を持つノードが複数ある場合、タイブレーク手法によりどれかのノードを選択する。
# Openリストに格納されているノードの内、最小の <math>f(n)</math> を持つノード <math>n</math> を1つ取り出す。同じ(追記) (追記ここまで)<math>f(n)</math>(追記) (追記ここまで)を持つノードが複数ある場合、タイブレーク手法によりどれかのノードを選択する。
# <math>n \in G </math> であるなら探索を終了する。それ以外の場合は <math>n</math> を Close リストに移す。
# <math>n \in G </math> であるなら探索を終了する。それ以外の場合は <math>n</math> を Close リストに移す。
# <math>n</math> に隣接している全てのノード(ここでは隣接ノードを <math>m</math> とおく)に対して以下の操作を行う
# <math>n</math> に隣接している全てのノード(ここでは隣接ノードを <math>m</math> とおく)に対して以下の操作を行う
## <math>f'(m) = g(n) + COST(n,m) + h(m)</math> を計算する、ここで <math>COST(n,m)</math> はノード n から m へ移動するときのコストである。また g(n) は <math>g(n) = f(n) - h(n)</math> で求めることができる。
## <math>f'(m) = g(n) + COST(n,m) + h(m)</math> を計算する、ここで <math>COST(n,m)</math> はノード n から m へ移動するときのコストである。また (追記) <math> (追記ここまで)g(n)(追記) </math> (追記ここまで) は <math>g(n) = f(n) - h(n)</math> で求めることができる。
## m の状態に応じて以下の操作を行う
## m の状態に応じて以下の操作を行う
##* m が Openリストにも Closeリストにも含まれていない場合 <math>f(m) \leftarrow f'(m)</math> とし m を Openリストに追加する。このとき m の親を n として記録する。
##* m が Openリストにも Closeリストにも含まれていない場合 <math>f(m) \leftarrow f'(m)</math> とし m を Openリストに追加する。このとき m の親を n として記録する。
##* m が Openリストにある場合、<math>f'(m) < f(m)</math> であるなら、m をOpenから削除し、<math>f(m) \leftarrow f'(m)</math> に置き換え、再びOpenに挿入する(削除) (Open (削除ここまで)が正しくソートされていることを保証するため(削除) ) (削除ここまで)。また、記録してある m の親を n に置き換える。
##* m が Openリストにある場合、<math>f'(m) < f(m)</math> であるなら、m を(追記) (追記ここまで)Open(追記) (追記ここまで)から削除し、<math>f(m) \leftarrow f'(m)</math> に置き換え、再び(追記) (追記ここまで)Open(追記) (追記ここまで)に挿入する(追記) (Open (追記ここまで)が正しくソートされていることを保証するため(追記) ) (追記ここまで)。また、記録してある m の親を n に置き換える。
##* m が Closeリストにある場合、<math>f'(m) < f(m)</math> であるなら <math>f(m) \leftarrow f'(m)</math> として m を Openリストに移動する。また記録してある m の親を n に置き換える。
##* m が Closeリストにある場合、<math>f'(m) < f(m)</math> であるなら <math>f(m) \leftarrow f'(m)</math> として m を Openリストに移動する。また記録してある m の親を n に置き換える。
# 3 以降を繰り返す。
# 3 以降を繰り返す。
# 探索終了後、発見されたゴール <math>n_G</math> から親を順次たどっていくと S から ゴール <math>n_G</math> までの最短経路が得られる。
# 探索終了後、発見されたゴール <math>n_G</math> から親を順次たどっていくと S から ゴール <math>n_G</math> までの最短経路が得られる。


以上の流れを見れば、アルゴリズムが手続き的で、並列化が非常に難しいことがわかる。しかし、近年では [[HDA*]]<ref>Kishimoto, Akihiro, Alex S. Fukunaga, and Adi Botea. "Scalable, Parallel Best-First Search for Optimal Sequential Planning." ICAPS. 2009.</ref>(削除) , (削除ここまで)[[PBFS]] などの並列手法が開発され、特にHDA*は768コア以上の大規模並列計算環境にもスケールすることが実証されている。
以上の流れを見れば、アルゴリズムが手続き的で、並列化が非常に難しいことがわかる。しかし、近年では [[HDA*]]<ref>Kishimoto, Akihiro, Alex S. Fukunaga, and Adi Botea. "Scalable, Parallel Best-First Search for Optimal Sequential Planning." ICAPS. 2009.</ref>(追記) 、 (追記ここまで)[[PBFS]] などの並列手法が開発され、特に(追記) (追記ここまで)HDA*(追記) (追記ここまで)は768コア以上の大規模並列計算環境にもスケールすることが実証されている。


== 実際に使われているOPEN/CLOSEリストの実装 ==
== 実際に使われているOPEN/CLOSEリストの実装 ==


OPEN/CLOSEリストに登録されているノードの数が多い場合、ノードの展開ごとに子ノードmをOPENからCLOSEへ移動(削除) ( (削除ここまで)あるいは逆(削除) ) (削除ここまで)するのは非常に高価な操作である。
OPEN/CLOSEリストに登録されているノードの数が多い場合、ノードの展開ごとに子ノード(追記) (追記ここまで)m(追記) (追記ここまで)をOPENから(追記) (追記ここまで)CLOSE(追記) (追記ここまで)へ移動(追記) ( (追記ここまで)あるいは逆(追記) ) (追記ここまで)するのは非常に高価な操作である。
たとえば、OPEN/CLOSEリストが2分探索木(削除) ( (削除ここまで)[[赤黒木]]など(削除) ) (削除ここまで)で実装されている場合、まずノードの探すのに <math>O(log N)</math> かかり(削除) ( (削除ここまで)ノードのIDで検索(削除) ) (削除ここまで)、また削除にも<math>O(log N)</math>かかる。配列の場合には削除により大きなコストがかかる。
たとえば、OPEN/CLOSEリストが2分探索木(追記) ( (追記ここまで)[[赤黒木]]など(追記) ) (追記ここまで)で実装されている場合、まずノードの探すのに <math>O(log N)</math> かかり(追記) ( (追記ここまで)ノードのIDで検索(追記) ) (追記ここまで)、また削除にも(追記) (追記ここまで)<math>O(log N)</math>(追記) (追記ここまで)かかる。配列の場合には削除により大きなコストがかかる。


しかし、データ構造を工夫することで、より効率よい実装を行うことが出来る。ノードをOPEN/CLOSEリスト間で行ったり来たりさせる代わりに、以下のように実装する<ref>Burns, E. A., Hatem, M., Leighton, M. J., & Ruml, W. (2012, July). Implementing fast heuristic search code. In Fifth Annual Symposium on Combinatorial Search. https://www.aaai.org/ocs/index.php/SOCS/SOCS12/paper/view/5404/5173</ref>:
しかし、データ構造を工夫することで、より効率よい実装を行うことが出来る。ノードを(追記) (追記ここまで)OPEN/CLOSEリスト間で行ったり来たりさせる代わりに、以下のように実装する<ref>Burns, E. A., Hatem, M., Leighton, M. J., & Ruml, W. (2012, July). Implementing fast heuristic search code. In Fifth Annual Symposium on Combinatorial Search. https://www.aaai.org/ocs/index.php/SOCS/SOCS12/paper/view/5404/5173</ref>:
# それぞれのノードに、ノードの状態として OPEN, CLOSE の2種類のタグをもたせる。全てのノードは始めOPENである。
# それぞれのノードに、ノードの状態として OPEN, CLOSE の2種類のタグをもたせる。全てのノードは始めOPENである。
# ノードに一意な整数IDをもたせる。
# ノードに一意な整数(追記) (追記ここまで)ID(追記) (追記ここまで)をもたせる。
# また、IDからノードを<math>O(1)</math>で参照できる[[ハッシュテーブル]]を用意する。
# また、ID(追記) (追記ここまで)からノードを(追記) (追記ここまで)<math>O(1)</math>(追記) (追記ここまで)で参照できる[[ハッシュテーブル]]を用意する。
# OPENにノードを追加する際には、m が Openリストに含まれているかは検証せず、重複を許して追加する。かつ、このときノードではなくIDのみを追加する。
# OPEN(追記) (追記ここまで)にノードを追加する際には、m が Openリストに含まれているかは検証せず、重複を許して追加する。かつ、このときノードではなく(追記) (追記ここまで)ID(追記) (追記ここまで)のみを追加する。
## ID は整数1つ分なので、重複のオーバーヘッドを最小化出来る。
## ID は整数1つ分なので、重複のオーバーヘッドを最小化出来る。
# OPENからノードをpopする際、popしたノードの状態がOPENであれば展開を行うが、CLOSEであれば単にスキップする。
# OPEN(追記) (追記ここまで)からノードを(追記) (追記ここまで)pop(追記) (追記ここまで)する際、pop(追記) (追記ここまで)したノードの状態が(追記) (追記ここまで)OPEN(追記) (追記ここまで)であれば展開を行うが、CLOSE(追記) (追記ここまで)であれば単にスキップする。
## このことにより、重複があっても同じノードを無駄に展開しないようにできる。
## このことにより、重複があっても同じノードを無駄に展開しないようにできる。
## このことにより、OPENリストは、f値による[[優先度付きキュー]]の下に先入れ先出し/[[FILO|先入れ後出し]]のキューを用意することで実装できる。
## このことにより、OPENリストは、f値による[[優先度付きキュー]]の下に先入れ先出し/[[FILO|先入れ後出し]]のキューを用意することで実装できる。
85行目: 85行目:
== 性質 ==
== 性質 ==


A*の性質はhの性質によって大きく左右される。
A*(追記) (追記ここまで)の性質は(追記) (追記ここまで)h(追記) (追記ここまで)の性質によって大きく左右される。


* A*はダイクストラの一般化であり、ダイクストラと同様、グラフに負のコストの辺があれば完全性を失う。その場合には[[ベルマン–フォード法]]を用いる。
* A*(追記) (追記ここまで)はダイクストラの一般化であり、ダイクストラと同様、グラフに負のコストの辺があれば完全性を失う。その場合には[[ベルマン–フォード法]]を用いる。
* h(n) は常に非負でなくてはならない。
* h(n) は常に非負でなくてはならない。
* あるヒューリスティクス h(n) が 全てのノード n について 真のゴール距離 h*(n) を上回らない場合、hはAdmissible/'''許容的'''であると言う。
* あるヒューリスティクス h(n) が 全てのノード n について 真のゴール距離 h*(n) を上回らない場合、h(追記) (追記ここまで)(追記) (追記ここまで)Admissible/'''許容的'''であると言う。
:<math>\forall n,0 \le h(n) \le h^*(n)</math>
:<math>\forall n,0 \le h(n) \le h^*(n)</math>
このとき、A*の返す経路は[[最適]]、つまり最短経路である。
このとき、A*の返す経路は[[最適]]、つまり最短経路である。
* あるヒューリスティクス h(n) について、全てのノード n と、それに隣接しているノード m について <math> h(n) \le cost(n,m) + h(m) </math> である場合、そのhはconsistent/'''無矛盾'''であるという。
* あるヒューリスティクス h(n) について、全てのノード n と、それに隣接しているノード m について <math> h(n) \le cost(n,m) + h(m) </math> である場合、その(追記) (追記ここまで)h(追記) (追記ここまで)はconsistent/'''無矛盾'''であるという。
* consistent なhは、常にadmissibleである(削除) 。 (削除ここまで)<ref>Russel, Norvig, Artificial intelligence: a modern approach</ref>
* consistent な(追記) (追記ここまで)h(追記) (追記ここまで)は、常に(追記) (追記ここまで)admissible(追記) (追記ここまで)である<ref>Russel, Norvig, Artificial intelligence: a modern approach</ref>(追記) 。 (追記ここまで)
* ある2つのヒューリスティック関数 h1, h2 について、(削除) (削除ここまで)<math>\forall n,h_1(n) \le h_2(n) </math> が成り立つ時、 h2 は h1 を支配する(dominate)とよぶ。このとき、特に両者が許容的な場合、h2 を用いた探索はより効率的だろうと考えられている。しかし、いくつかのコーナーケースではこのことは成り立たない(削除) 。 (削除ここまで)<ref>Robert C. Holte. Common Misconceptions Concerning Heuristic Search, SoCS 2010</ref>
* ある2つのヒューリスティック関数 h1, h2 について、<math>\forall n,h_1(n) \le h_2(n) </math> が成り立つ時、 h2 は h1 を支配する(追記) (追記ここまで)(dominate)(追記) (追記ここまで)とよぶ。このとき、特に両者が許容的な場合、h2 を用いた探索はより効率的だろうと考えられている。しかし、いくつかのコーナーケースではこのことは成り立たない<ref>Robert C. Holte. Common Misconceptions Concerning Heuristic Search, SoCS 2010</ref>(追記) 。 (追記ここまで)


このアルゴリズムは[[CPU]]の使用率、[[主記憶装置|メモリ]]の使用率など、計算負荷は高いが、問題に応じた適切なヒューリスティック関数を用いることにより、問題に対しての最適化が可能である。
このアルゴリズムは[[CPU]]の使用率、[[主記憶装置|メモリ]]の使用率など、計算負荷は高いが、問題に応じた適切なヒューリスティック関数を用いることにより、問題に対しての最適化が可能である。

2022年8月1日 (月) 02:02時点における版

曖昧さ回避 天体の「いて座A*」とは異なります。
A*探索アルゴリズム

A*(A-star、エースター)探索アルゴリズム(エースターたんさくアルゴリズム)は、グラフ 探索 アルゴリズムの一つ。 最良優先探索を拡張したZ*に、さらにf値として「現時点までの距離」g と「ゴールまでの推定値」h の和を採用したもの[1] 。h は ヒューリスティック関数と呼ばれる。

概要

A* アルゴリズムは、「グラフ上でスタートからゴールまでの道を見つける」というグラフ探索問題において、 ヒューリスティック関数 h(n) という探索の道標となる関数を用いて探索を行うアルゴリズムである。h は各頂点 n からゴールまでの距離のある妥当な推定値を返す関数で、解くグラフ探索問題の種類に応じてさまざまな h を設計することが出来る。 例えば、カーナビなどで用いられる単純な二次元の地図での探索では、h としてユークリッド距離 x 2 + y 2 {\displaystyle {\sqrt {x^{2}+y^{2}}}} {\displaystyle {\sqrt {x^{2}+y^{2}}}} を使うことができ、この値は道に沿った実際の距離のおおまかな予測になっている。しかし、高次元空間でのグラフ探索を効率的に行うためには、より高度に設計された関数を用いる必要がある。例えば、15パズルにおいてはマンハッタン距離パターンデータベースSTRIPSプランニングにおいてはFFヒューリスティック [2] Merge-and-Shrink [2] Landmark-Cut [3] などがある。

A* アルゴリズムは、ダイクストラ法を推定値付きの場合に一般化したもので、h が恒等的に 0 である場合はもとのダイクストラ法に一致する。

A* の探索効率は h の正確さに左右される。 もしも h がまったくでたらめな値を返すならば、探索はゴールとはあさっての方向に進んでしまい、現実的な時間内(一時間、一週間、一年)では解を発見できない場合がある。しかし、いくらおかしな方向に探索が進んだとしても、いつかは必ず解を発見できる保証がある(完全性)。 一方、h が常に正しい値 h* を返す場合、計算機は「迷うこと無く=分岐をすること無く」グラフ上の最短経路を発見することができる。そのような h のことを、パーフェクト・ヒューリスティクスとよぶ[4] 。 現実に用いられる有用な h は、これらの中間の位置にある。

歴史

A* アルゴリズムは1968年Peter E. HartNils J. NilssonBertram Raphael の三人が発表した論文[5] の中で最初に記述された。A* というこの一風変わった名前は、この論文でスタートからゴールまでの最短経路を確実に見つけるアルゴリズムを許容的(: admissible)と呼び、論文の数式中に 許容的なアルゴリズムの集合A と表し、そのAの中でも評価回数が最適になる物を A* と表記していたためである[6]

A*の考え方

スタートノードから、あるノード n を通って、ゴールノードまでたどり着くときの最短経路を考える。このときこの最短経路のコストを f* (n) とおくと、

f* (n)= g* (n) + h* (n)

と置くことが出来る。ここで g* (n) はスタートノードから n までの最小コスト、h* (n)n からゴールノードまでの最小コストである。もし g* (n) の値と h* (n)の値を知っていれば、全体の最短経路 f* (n) は容易に求まる。しかしながら実際には g* (n)h* (n) をあらかじめ与えることは出来ない。そこで f* (n) を次のような推定値 f (n) に置き換えることを考える。

f ( n ) = g ( n ) + h ( n ) {\displaystyle f(n)=g(n)+h(n)} {\displaystyle f(n)=g(n)+h(n)}

ここで g(n) はスタートノードから n までの最小コストの推定値、h(n)n からゴールノードまでの最小コストの推定値である。この場合 g に関しては探索の過程で更新を加えることにより g* に近づけてゆくことができるが、h* は、実際にゴールに辿り着くまでは誰にもわからない。そこで、h(n) には人間が(ある程度妥当性を持つように)設計した推定値を与えることにする。このアルゴリズムを A*探索アルゴリズムといい、h (n)ヒューリスティック関数という。

アルゴリズムの流れ

A* のアルゴリズムの実装を以下に示す。この OPENリスト実装は後に述べるように遅いことを記しておく。

  1. スタートノード( S {\displaystyle S} {\displaystyle S})を作成する。また計算中のノードを格納しておくための優先度付きキュー OPENリストと、計算済みのノードを格納しておくCLOSEリストを用意する。(名前は「リスト」だが、これらの実際のデータ構造は必ずしも連結リストである必要はない。)
  2. ゴールは必ずしも1つである必要はないので、ゴール条件を満たすノード集合を G {\displaystyle G} {\displaystyle G} と呼ぶことにする。
  3. スタートノードを Openリストに追加する、このとき g ( S ) {\displaystyle g(S)} {\displaystyle g(S)} = 0 {\displaystyle 0} {\displaystyle 0} であり f ( S ) {\displaystyle f(S)} {\displaystyle f(S)} = h ( S ) {\displaystyle h(S)} {\displaystyle h(S)} となる。また Closeリストは空にする。
  4. Openリストが空なら探索は失敗とする(スタートからゴールにたどり着くような経路が存在しなかったことになる)。
  5. Openリストに格納されているノードの内、最小の f ( n ) {\displaystyle f(n)} {\displaystyle f(n)} を持つノード n {\displaystyle n} {\displaystyle n} を1つ取り出す。同じ f ( n ) {\displaystyle f(n)} {\displaystyle f(n)} を持つノードが複数ある場合、タイブレーク手法によりどれかのノードを選択する。
  6. n G {\displaystyle n\in G} {\displaystyle n\in G} であるなら探索を終了する。それ以外の場合は n {\displaystyle n} {\displaystyle n} を Close リストに移す。
  7. n {\displaystyle n} {\displaystyle n} に隣接している全てのノード(ここでは隣接ノードを m {\displaystyle m} {\displaystyle m} とおく)に対して以下の操作を行う
    1. f ( m ) = g ( n ) + C O S T ( n , m ) + h ( m ) {\displaystyle f'(m)=g(n)+COST(n,m)+h(m)} {\displaystyle f'(m)=g(n)+COST(n,m)+h(m)} を計算する、ここで C O S T ( n , m ) {\displaystyle COST(n,m)} {\displaystyle COST(n,m)} はノード n から m へ移動するときのコストである。また g ( n ) {\displaystyle g(n)} {\displaystyle g(n)} g ( n ) = f ( n ) h ( n ) {\displaystyle g(n)=f(n)-h(n)} {\displaystyle g(n)=f(n)-h(n)} で求めることができる。
    2. m の状態に応じて以下の操作を行う
      • m が Openリストにも Closeリストにも含まれていない場合 f ( m ) f ( m ) {\displaystyle f(m)\leftarrow f'(m)} {\displaystyle f(m)\leftarrow f'(m)} とし m を Openリストに追加する。このとき m の親を n として記録する。
      • m が Openリストにある場合、 f ( m ) < f ( m ) {\displaystyle f'(m)<f(m)} {\displaystyle f'(m)<f(m)} であるなら、m を Open から削除し、 f ( m ) f ( m ) {\displaystyle f(m)\leftarrow f'(m)} {\displaystyle f(m)\leftarrow f'(m)} に置き換え、再び Open に挿入する(Open が正しくソートされていることを保証するため)。また、記録してある m の親を n に置き換える。
      • m が Closeリストにある場合、 f ( m ) < f ( m ) {\displaystyle f'(m)<f(m)} {\displaystyle f'(m)<f(m)} であるなら f ( m ) f ( m ) {\displaystyle f(m)\leftarrow f'(m)} {\displaystyle f(m)\leftarrow f'(m)} として m を Openリストに移動する。また記録してある m の親を n に置き換える。
  8. 3 以降を繰り返す。
  9. 探索終了後、発見されたゴール n G {\displaystyle n_{G}} {\displaystyle n_{G}} から親を順次たどっていくと S から ゴール n G {\displaystyle n_{G}} {\displaystyle n_{G}} までの最短経路が得られる。

以上の流れを見れば、アルゴリズムが手続き的で、並列化が非常に難しいことがわかる。しかし、近年では HDA* [7] PBFS などの並列手法が開発され、特に HDA* は768コア以上の大規模並列計算環境にもスケールすることが実証されている。

実際に使われているOPEN/CLOSEリストの実装

OPEN/CLOSEリストに登録されているノードの数が多い場合、ノードの展開ごとに子ノード m をOPENから CLOSE へ移動(あるいは逆)するのは非常に高価な操作である。 たとえば、OPEN/CLOSEリストが2分探索木(赤黒木など)で実装されている場合、まずノードの探すのに O ( l o g N ) {\displaystyle O(logN)} {\displaystyle O(logN)} かかり(ノードのIDで検索)、また削除にも O ( l o g N ) {\displaystyle O(logN)} {\displaystyle O(logN)} かかる。配列の場合には削除により大きなコストがかかる。

しかし、データ構造を工夫することで、より効率よい実装を行うことが出来る。ノードを OPEN/CLOSEリスト間で行ったり来たりさせる代わりに、以下のように実装する[8] :

  1. それぞれのノードに、ノードの状態として OPEN, CLOSE の2種類のタグをもたせる。全てのノードは始めOPENである。
  2. ノードに一意な整数 ID をもたせる。
  3. また、ID からノードを O ( 1 ) {\displaystyle O(1)} {\displaystyle O(1)} で参照できるハッシュテーブルを用意する。
  4. OPEN にノードを追加する際には、m が Openリストに含まれているかは検証せず、重複を許して追加する。かつ、このときノードではなく ID のみを追加する。
    1. ID は整数1つ分なので、重複のオーバーヘッドを最小化出来る。
  5. OPEN からノードを pop する際、pop したノードの状態が OPEN であれば展開を行うが、CLOSE であれば単にスキップする。
    1. このことにより、重複があっても同じノードを無駄に展開しないようにできる。
    2. このことにより、OPENリストは、f値による優先度付きキューの下に先入れ先出し/先入れ後出しのキューを用意することで実装できる。
    3. ノードの検索がおこなわれず、かつ削除が先頭あるいは末尾にしかおこらないので、効率的な実装が可能である。
  6. CLOSEリストは使用しない。
  7. f値による優先度付きキューは、辺のコストが1である場合には単にf値ごとの可変配列にしたほうが高速な操作が可能である。
    1. 辺のコストに大幅なばらつきがある場合には、ヒープとして実装したほうがよい。

性質

A* の性質は h の性質によって大きく左右される。

  • A* はダイクストラの一般化であり、ダイクストラと同様、グラフに負のコストの辺があれば完全性を失う。その場合にはベルマン–フォード法を用いる。
  • h(n) は常に非負でなくてはならない。
  • あるヒューリスティクス h(n) が 全てのノード n について 真のゴール距離 h*(n) を上回らない場合、h は Admissible/許容的であると言う。
n , 0 h ( n ) h ( n ) {\displaystyle \forall n,0\leq h(n)\leq h^{*}(n)} {\displaystyle \forall n,0\leq h(n)\leq h^{*}(n)}

このとき、A*の返す経路は最適、つまり最短経路である。

  • あるヒューリスティクス h(n) について、全てのノード n と、それに隣接しているノード m について h ( n ) c o s t ( n , m ) + h ( m ) {\displaystyle h(n)\leq cost(n,m)+h(m)} {\displaystyle h(n)\leq cost(n,m)+h(m)} である場合、その h はconsistent/無矛盾であるという。
  • consistent な h は、常に admissible である[9]
  • ある2つのヒューリスティック関数 h1, h2 について、 n , h 1 ( n ) h 2 ( n ) {\displaystyle \forall n,h_{1}(n)\leq h_{2}(n)} {\displaystyle \forall n,h_{1}(n)\leq h_{2}(n)} が成り立つ時、 h2 は h1 を支配する (dominate) とよぶ。このとき、特に両者が許容的な場合、h2 を用いた探索はより効率的だろうと考えられている。しかし、いくつかのコーナーケースではこのことは成り立たない[10]

このアルゴリズムはCPUの使用率、メモリの使用率など、計算負荷は高いが、問題に応じた適切なヒューリスティック関数を用いることにより、問題に対しての最適化が可能である。

部分問題に分割する場合

分割統治法のように、部分問題に分割したうえで全体問題を解いた方が効率的な問題もある。A* 同様の通常の状態遷移はどれかがゴールに到達すれば良いので OR と呼び、部分問題に分割する場合は全ての部分問題が解けないといけないので AND と呼ぶと、探索木が AND/OR 木になる。AND で状態を分割する際、ゴールも分割する必要がある。

同じ状態が2度出現した場合に1つのノードにまとめると AND/OR グラフになる。閉路のない AND/OR グラフに対する A* アルゴリズムに対応する物が1968年に開発され[11] 1980年AO*アルゴリズム と命名された[6] 。閉路のある AND/OR グラフに対する A* アルゴリズムに対応する A0アルゴリズム1976年に開発された[12] 。AND ノードのコストを「辺のコスト+部分問題のコストの最大値」や「辺のコスト+部分問題のコストの総和」などの単調非減少関数で定義すると[13] 、ヒューリスティック関数が許容的であれば、A* 同様、最適解が求まる。なお、閉路のない AND/OR グラフは文脈自由文法(タイプ-2 文法)[14] 、閉路のある AND/OR グラフは制限のない文法(タイプ-0 文法)に1対1対応することが証明されている。

関連項目

参照

  1. ^ Pearl, Judea (1984) (English). "Heuristics intelligent search strategies for computer problem solving". Addison-Wesley. ISBN 0201055945  
  2. ^ a b Hoffmann, Jörg, and Bernhard Nebel. "The FF planning system: Fast plan generation through heuristic search." Journal of Artificial Intelligence Research (2001): 253-302.
  3. ^ Helmert, Malte, and Carmel Domshlak. "Landmarks, critical paths and abstractions: what's the difference anyway?." ICAPS. 2009.
  4. ^ How Good is Almost Perfect?. M Helmert, G Röger - AAAI, 2008
  5. ^ Peter E. Hart; Nils J. Nilsson; Bertram Raphael (July 1968). "A Formal Basis for the Heuristic Determination of Minimal Cost Paths". IEEE Transactions on Systems Science and Cybernetics 4 (2): 100-107. doi:10.1109/TSSC.1968.300136. ISSN 0536-1567 . http://ai.stanford.edu/~nilsson/OnlinePubs-Nils/PublishedPapers/astar.pdf . 
  6. ^ a b Nils J. Nilsson 著、白井良明, 辻井潤一, 佐藤泰介 訳『人工知能の原理』日本コンピュータ協会、1983年1月15日(原著1980年)。ISBN 4-88917-026-X 
  7. ^ Kishimoto, Akihiro, Alex S. Fukunaga, and Adi Botea. "Scalable, Parallel Best-First Search for Optimal Sequential Planning." ICAPS. 2009.
  8. ^ Burns, E. A., Hatem, M., Leighton, M. J., & Ruml, W. (2012, July). Implementing fast heuristic search code. In Fifth Annual Symposium on Combinatorial Search. https://www.aaai.org/ocs/index.php/SOCS/SOCS12/paper/view/5404/5173
  9. ^ Russel, Norvig, Artificial intelligence: a modern approach
  10. ^ Robert C. Holte. Common Misconceptions Concerning Heuristic Search, SoCS 2010
  11. ^ Nils J. Nilsson (August 1968). "Searching problem-solving and game-playing trees for minimal cost solutions". Information Processing 68 : proceedings of IFIP Congress 1968 (Amsterdam : North-Holland) 2: 1556-1562. 
  12. ^ Giorgio Levi; Franco Sirovich (January 1976). "Generalized and/or graphs". Artificial Intelligence 7 (3): 243-259. doi:10.1016/0004-3702(76)90006-0 . http://www.sciencedirect.com/science/article/pii/0004370276900060 . 
  13. ^ Vipin Kumar; Dana S. Nau; Laveen N. Kanal (August 1985). A General Paradigm for AND/OR Graph and Game Tree Search . http://www.cs.utexas.edu/ftp/AI-Lab/tech-reports/UT-AI-TR-85-9.pdf . 
  14. ^ Patrick A. V. Hall (July 1973). "Equivalence between AND/OR graphs and context-free grammars". Communications of the ACM 16 (7): 444-445. doi:10.1145/362280.362302 . http://dl.acm.org/citation.cfm?id=362302 . 
ソート
比較ソート
線形時間ソート
並行ソート
非効率的
グラフ
探索
リスト
グラフ
文字列
最短経路問題
最小全域木
最大フロー問題
最小カット問題
線型計画問題
順序統計量
計算幾何学
種類
その他
カテゴリ

AltStyle によって変換されたページ (->オリジナル) /