ryo hatano jaist april-may, 2012s1220010/prolog_3.pdfprolog...

24
人工知能特論 - Prolog 演習 - Ryo Hatano JAIST April-May, 2012 レポート解説 + 補足資料

Upload: others

Post on 10-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

人工知能特論- Prolog 演習 -

Ryo HatanoJAIST

April-May, 2012

レポート解説 + 補足資料

Page 2: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

1

目次

1. 訂正と補足

2. 解説– load(File)– findpath(PATH)

3. おわりに

Page 3: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

2

訂正と補足

• consult について

– 1回目の資料p.14と, 2回目の資料p.25のconsult は同じ

ものです

– さらに, 1回目の資料の記法は誤りです

– また, 第2回の配布資料のconsult(F) の説明が大雑把

過ぎたので補足します.

(誤) consult[‘file_name’]. → (正) consult(‘file_name’).

consult 記法の訂正

ファイルF中のすべての節(項)を読み込み, 読み込んだ節を常にDBの末尾に加える. ストリーム操作, DBの操作が一体となった様な動作をする.

consult(F) の補足

Page 4: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

3

解説

1. load(X)a. 再帰版

b. 失敗駆動ループ版

2. findpath(PATH)a. 使用済み規則を削る版

b. 失敗駆動ループ版

c. setof版

Page 5: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

4

1. load(FILE) について

• 繰り返し処理, ストリーム・DBの操作, 条件分岐等を駆使してconsult 相当の機能を実装せよという課題

• しかし, consult をそのまま呼び出すのは, 質問応答の仕様

を満たさないという点で間違い

?- load('graph.txt').% graph.txt compiled 0.00 sec, 2,036 bytestrue.

load(FILE):- consult(FILE).

減点はしませんが, 以後仕様をよく確認するようにしましょう.

インタプリタ

consult をそのまま利用した例

↑SWI-Prologではcompile 確認のメッセージが出力される

なお, consult をストリーム操作で挟むなどの解答をした方は, こちらの説明不足で誤解させてしまった面があるため, 解答の方針があっていれば良い事とします.

Page 6: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

5

再帰版

load(FILE):-see(FILE),read_from_file, seen,see(user).

(1)ストリームの操作read_from_file:-

read(T),add_db(T).

add_db(end_of_file):-!. add_db(T):-

assertz(T), read_from_file.

(2)データの読み込み

(3)DBへの追加

←再帰の停止条件

(4) end_of_file が来るまでread ~ assertz を繰り返す

• 大枠の処理と, 再帰(ループ)を分けると見通しが良い

Page 7: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

6

再帰版(短)

• うまく再帰の処理をまとめると, 規則節を減らすことができる

load(FILE):-see(FILE),not(read_from_file)seen,see(user).

(1)ストリームの操作

read_from_file:-read(T),T ¥== end_of_file,

assertz(T), read_from_file.

(2)データの読み込み

end_of_fileが来ると失敗して

再帰ループを抜ける

(3)DBへの追加

Page 8: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

7

失敗駆動ループ

• バックトラックの特性を生かして繰り返し処理を実現する方法

• バックトラックを発生させる方法– fail (必ず失敗する述語)を使う

– 閉世界仮説の特性を利用

– その他, 失敗を含む述語・演算処理を使う

• 繰り返しを発生させる方法– repeat (新しい実行分岐を生成する述語)を使う

– 複数の実行分岐を持つ述語をはさむ

• 繰り返しを抜ける方法– カットを用いた条件分岐を作り, 片方を成功させる

– 全マッチングの失敗後, not を用いて結果を反転させる

(上記を適宜組み合わせて実現する)

Page 9: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

8

失敗駆動ループ例

fact(1). fact(2). fact(3).floop:- fact(X), writeln(X), fail.

複数の実行分岐 + fail 例

?- not(floop).123true.

←not をつけると, 他の述語に組み込む場合に便利

←予め実行分岐がいくつ存在するか分かっている場合に有効

インタプリタ

?- repeat, read(X), writeln(X), X == end_of_file, !.|: hoge.hoge|: end_of_fileX = end_of_file.

repeat + 演算の失敗例 (インタプリタ)

←ユーザーからの入力を待つプロンプト. 最後にピリオドが必要.

←Ctrl + Z で end_of_file が入力される

Page 10: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

9

失敗駆動ループ版

• repeat, fail を組み合わせてループを形成する

load(FILE):-see(FILE),read_from_file, seen,see(user).

(1)ストリームの操作read_from_file:-

repeat,read(T),add_db(T),!.

add_db(end_of_file):-!. add_db(T):-

assertz(T), fail.

(2)データの読み込み

(3)DBへの追加

←ループの脱出条件

(4) fail を踏むとバックトラックが発生し, repeat で再び実行分岐が生成される.

Page 11: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

10

失敗駆動ループ版(短)

• repeat, 演算の失敗を組み合わせてループを形成する

load(FILE):-see(FILE),repeat,read(T),assertz(T),T == end_of_file,seen,!. ←最後にカットを置かないと, 別解が生成できてしまう

(無限ループのもと)

←ファイルの末尾をreadするまで, 失敗する

Page 12: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

11

解説

1. load(X)a. 再帰版

b. 失敗駆動ループ版

2. findpath(PATH)a. 使用済み規則を削る版

b. 失敗駆動ループ版

c. setof版

Page 13: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

12

2. findpath(PATH) について

• 繰り返し処理, DB操作, 比較演算, リスト操作等を駆使して

山登り法の探索プログラムを実装せよという課題

• load(FILE)の課題に比べ, 解くべき部分問題が増えているため, 仕様の大雑把さから考えられない複雑さを持つ

• よって, 解くべき部分問題を特定して, それらをうまく組み合

わせられるかどうかが問題解決の鍵となる– この手の直感的に方針が浮かんでこない様な規模の問題に取り組

む場合, いきなりプログラムを組み始めて発見的に解こうとしたり, Prolog コードで問題を考え始めると大抵ドツボに嵌る

– 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けてそれぞれ方針を立て, 順次実装を行い, 最後に組み合わせると比較

的解きやすい

Page 14: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

13

1. 終了条件は何か?→これ以上ヒューリスティック関数の値が小さなノードが隣に存在しない

2. 探索の大枠をどう組むか?→終了条件に達するまでループさせる

3. ループのうちわけは?→移動すべきノードを特定→移動すべきノードに移動

4. 移動すべきノードとは何か?→移動可能なノードの候補の中で, 最小のヒューリスティック関数の値を持つノード

5. 移動可能なノードの候補をどのように得るか?→移動可能なノードの一覧を列挙する

6. 最も小さなヒューリスティック関数の値を持つノードをどう特定するか?→現在のノードと, 移動可能なノードの一覧中のノードが持つヒューリスティック関数の値をひとつずつ比較し, 最小の物を選ぶ

部分問題の特定の仕方の例

このように, 終了条件→大枠→大枠の中身→その中身… と問題を明確にして細分化していくと方針が立てやすい. そして, 一度に解くべき問題を少なくしていくと, 割り当てるべき個々の機能がはっきりしてくる.

→再帰が実装候補に挙がる

→再帰で実現可能. 再帰の引数にノードを示す変数が必要

→リストの結合, 再帰が実装候補に挙がる

→リストの分割, 比較演算, 再帰が実装候補に挙がる

g に到達したから終了するわけではありません.

Page 15: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

14

大枠の処理

• 再帰を用いて探索終了までの大枠の処理を組む

findpath(PATH):-init,!,hc_search(s, [], PATH).

init:-retractall(guess(_,_)), assertz(guess(init,100)).

(1)プログラム全体の初期化

:-dynamic guess/2.:-dynamic edge/2.:-dynamic h/2.

(2)探索を開始(初期条件を与える)

hc_search(Goal, Log, [Goal]):-not(assert_min(Goal, [Goal|Log])), guess(Goal, _),!.

hc_search(Src, Log, [Src|Tail]):-guess(Dest,_),hc_search(Dest, [Src|Log], Tail).

(4)探索の停止条件の判定

(5)移動ログを追記して次のノードに移動

(3)最小値をassert する

Page 16: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

15

解説

1. guess(Node, Cost) という, 次に移動すべきノードの候補を保存する述語を導入する. Nodeには移動すべきノード名が入り, Cost にはそのヒューリスティック関数値が入る. この述語を, assert/retractを駆使してC言語で言うところのグローバル変数の様に扱い, 値の一時保存・読み出しに利用する. 具体的には, 最小値の決定(2), 探索の停止条件の判定(3), ノードの移動(4)で用いられる.

2. hc_search(Src, Log, PATH) という山登り法を実装する述語を導入する. Srcは現在のノードであり, Log はPATHの出力および閉路の判定に用いられる. 探索開始時には, 初期条件として s を与える. なお, この段階で探索の停止条件として g を与えることは間違い. 山登り法では, 探索を進めるか止めるかをヒューリスティック関数値のみで判定するため, 探索開始時(またはプログラム実装時)に目標となるノードを停止条件として直接指定してはならない.

3. assert_min(Src, Log) という, 副作用として最小値を持つノードをguess/2 として保存し, 必ず失敗する述語を導入する. Srcは現在のノードであり, Logはそれまでに辿ったノードの記録である. なお, 必ず失敗する理由は, 複数の解答例を出す上で都合が良いからであり, 実装の方針によってはこれを失敗にする必要はない.

4. 隣接ノードのヒューリスティック関数値が現在のノードの値より低い場合, 現在のノードとguess/2 のノードは一致しない. よってhc_search/2のバックトラックが発生し, (5) ノードの移動が行われる.

Page 17: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

16

解説 (続き)

• 以後, 最小値の候補を副作用として出力するassert_min/2 に関連して解説を行う.

• assert_min/2 は以下の仕様を満たす

assert_min(Src, Log) – Src … 現在のノード

– Log … それまでに辿ったノードの記録

– 副作用として最小値をguess/2 としてassert する

– 必ず失敗する

(大枠の処理は同じ)

Page 18: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

17

使用済み規則を削る版

• 調べ終わったedge/2 をretract でDBから削除し, 最小値をguess/2 に保存する

assert_min(Src, Log):-edge(Src, Dest),retract(edge(Src, Dest)),h(Dest, Cost),guess(_, Cost_old),not(memmber(Dest, Log)), not(check_and_assert(Cost, Cost_old, Dest)),assert_min(Src, Log).

check_and_assert(Cost, Cost_old, Dest):-Cost < Cost_old,retractall(guess(_,_)),assertz(guess(Dest, Cost)),fail.

←読み込んだ検査対象を上から消していく

コストの比較か, fail で必ず失敗する→not で結果が反転して必ず成功する

←再帰を繰り返すと, 必ず見つからなくなって失敗する

←最小値を保存

←閉路のチェック

Page 19: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

18

失敗駆動ループ版

• バックトラックを繰り返して最小値を持つノードを保存する

assert_min(Src, Log):-edge(Src, Dest), h(Dest, Cost), guess(_, Cost_old), not(member(Dest, Log)), Cost < Cost_old, retractall(guess(_,_)), assertz(guess(Dest, Cost)), fail.

←バックトラックを繰り返して全て隣接ノードを探索すると, 失敗する

コストの比較か, fail で必ず失敗する.コストの比較に成功した場合, 最小値が更新される.

Page 20: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

19

setof 版

• 集合述語setof またはそれ相当の機能を用いて隣接ノードのリストを生成し, 最小値を持つノードを保存する

assert_min(Src, Log):-setof(Dest, edge(Src, Dest), Dests),%my_setof(Src, Dests),!,assert_min0(Src, Log, Dests).

assert_min0(_, _, []):-!.assert_min0(Src, Log, [Dest|Tail]):-

h(Dest, Cost),guess(_, Cost_old),not(member(Dest, Log)),not(check_and_assert(Cost, Cost_old, Dest)),assert_min0(Src, Log,Tail).

←再帰の停止条件

←使用済み規則を削る版の後半とほぼ同じ仕組み(隣接ノードリストの先頭を取り出す処理が加わっている)

←隣接ノードのリストを作る

Page 21: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

集合述語(Set predicates)

• 生成した解を, リストに集めて使うための述語

• 引数に述語Pを取るため, 高階述語として機能する

– bagof(X, P, L) 目標Pが成立するような全ての対象XのリストLを作る

– setof(X, P, L) bagof のリストをソートして, 重複した項を削除した

ものを作る

20

class(bird, eagle). class(bird, ostrich).class(fish, shark).

プログラム例

←左端のX と P 中のX は一致させる必要がある

インタプリタ

?- setof(X, class(bird, X), L).L = [eagle, ostrich]

Page 22: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

21

setof相当の機能を作る

• setofの引数として渡していた述語を, 内部に直接埋め込む

my_setof(Src, Dests):-my_setof0(Src, [], Dests),not(Dests = []). ←setof は出力が空のリストのとき, false を返す

my_setof0(Src, Tail, Dests):-edge(Src, Dest),not(member(Dest, Tail)),!,my_setof0(Src, [Dest|Tail], Dests).

my_setof0(_, Dests, Dests).

調べられる隣接ノードがなくなったらバックトラックが発生し, それまでの結果をDests に保持したままmy_setof0/3 へ移り, 成功して再帰を抜ける.

←↓Dest が前に調べた内容でなければ結果に追加

←最後に, それまでの結果をコピーする

(Srcが, my_setof0/3 を実行中ずっと同じ点に注意)無論, 記述しやすさ, 使いやすさという点では高階述語のほうが扱いやすい

Page 23: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

再帰を用いたリストの作り方について

22

前向きのリストの構築方法では, 再帰の途中で更新されたリストの内容を扱えるが後向きの方は再帰の終了条件から処理が戻ってくるまでリストの内容を扱えない. PATHの出力に用いるリストは, これらの方法を用いて構築している

f(L, L, 0). f(L, R, N):-

N1 is N-1,f([N1|L], R, N1).

前向きに構築する方法

←結果のコピーが必要. 3番目の変数は終了条件

b([], 0).b([N1|R], N):-

N1 is N-1,b(R, N1).

後向きに構築する方法

←終了条件に達すると,1つめの変数の内容から順にリストを構築していく

?- f([], L, 3).L=[0,1,2]?- b(L,3).L=[2,1,0]

←次の再帰を呼び出すときにリストを構築する

←再帰の結果を得るときにリストを構築する

インタープリタ

Page 24: Ryo Hatano JAIST April-May, 2012s1220010/Prolog_3.pdfProlog コードで問題を考え始めると大抵ドツボに嵌る – 探索問題, 経路の記録, 閉路の考慮の3種類の問題に大きく分けて

おわりに

今回, 字面の説明に含まれる曖昧さ, 定義の理解の甘さをそのままにして躓いた方が大勢います. 人によってはプログラミング以前に, 問題の検討の仕方そのものにも問題があったのではないでしょうか. 問題の検討の仕方を明確にしていなかった方は, この機会に自身が検討した問題

の種類と内容, 手続きを目に見える形にしてみましょう. そして, 検討した順番や検討要素に, どのような問題があったのかを考えてみてください. 特に, 今回取り扱った問題(2)は複合的な問題設定となっているため, その部分問題を発見し検討する事が必須でした. ここでは発見した部分問題が解ける条件は何で, そのためにはどのような手続きが必要かという仕様を明確にしなければなりません. 無論, 最初から順番どおりに仕様を決めていくことは難しいでしょう. しかし, 仕様の要素を陽に列挙することは可能だし, その依存関係を解決していけばある程度形にはなります. 解答が得られなければ, 自身が解いた過程をまとめましょう. 解答例と照らし合わせて, 今後の知見を得るための糧とすることも十分可能なはずです. また, 厳密な定義は, 出典を明確にした参考書, 式などから調べる癖をつけましょう.

配布資料は説明用の要約である場合が多く, 十分に定義を記述している教科書等に比べると説明が不足し勝ちです. 極端に説明が短い場合は特に注意してください. 研究でもそうですが, 基本的な用語の定義や事実関係の調査はネット, 要約した資料だけで済ませてはいけません. 理解の一助とする事は良いですが, それが大雑把な内容であると常に注意を払うべきです. 23

注意深くなりましょう