360 likes | 648 Views
Логическое программирование. Факультет Прикладной математики и физики Кафедра Вычислительной математики и программирования Московский авиационный институт (государственный технический университет). Игра в 8. :- pred final(probstate::out) is det. final([[1,2,3],[4,0,5],[6,7,8]]).
E N D
Логическое программирование Факультет Прикладной математики и физики Кафедра Вычислительной математики и программирования Московский авиационный институт (государственный технический университет)
Игра в 8 :- pred final(probstate::out) is det.final([[1,2,3],[4,0,5],[6,7,8]]). :- pred init(probstate::out) is det.init([[1,3,5],[4,2,8],[6,7,0]]). :- type row == list(int). :- type probstate == list(row).
Игра в 8 - 2 :- pred move(probstate::in, probstate::out) is nondet. move(A,B) :- move_lr(A,B). move(A,B) :- move_ud(A,B). :- pred move_z(row::in, row::out) is nondet. move_z([0,X|T],[X,0|T]). move_z([X,0|T],[0,X|T]). move_z([X|T],[X|R]):-move_z(T,R). :- pred move_lr(probstate::in, probstate::out) is nondet. move_lr([A|T],[B|T]) :- move_z(A,B). move_lr([A|T],[A|R]) :- move_lr(T,R). :- pred move_h(row::in, row::in, row::out, row::out) is nondet. move_h([0|T],[A|R],[A|T],[0|R]). move_h([A|T],[0|R],[0|T],[A|R]). move_h([X|T],[Y|R],[X|T1],[Y|R1]) :- move_h(T,R,T1,R1).:- pred move_ud(probstate::in, probstate::out) is nondet. move_ud([A,B,C],[A1,B1,C]) :- move_h(A,B,A1,B1). move_ud([A,B,C],[A,B1,C1]) :- move_h(B,C,B1,C1).
Мораль • Алгоритмы поиска (в особенности поиск в глубину) эффективно реализуются на языках логического программирования • Базовые алгоритмы поиска обладают взаимоисключающими достоинствами и недостатками • Алгоритм поиска с заглублением сочетает в себе достоинства обоих методов, хотя и производит исключительно много ненужных вычислений • Чтобы решать реальные задачи, нужно иметь методы поиска, использующие знания о направлении поиска.
Алгоритмы поиска в нагруженных графах Алгоритмы поиска в нагруженных графах Алгоритмы поиска в нагруженных графах Алгоритмы поиска в нагруженных графах
Граф дорог между городами road(a,d,4).road(a,f,2). road(b,c,4).road(b,d,4). road(c,i,4). road(d,e,3). road(e,i,3). road(f,g,2). road(g,h,2).road(h,i,3). move(A,B,N) :- road(A,B,N); road(B,A,N).
Нагруженные графы • Нагруженным графом (направленным) называется пара <U,V>, где U — множество вершин, VU×U×R — множество дуг. • Длиной пути {xi}i=0n в нагруженном графе G=<U,V> называется величина L= li, где <xi-1,xi,li> V. Задача поиска в нагруженном графе ставится следующим образом: Пусть имеется нагруженный граф G, заданный предикатом move/3. Необходимо по двум заданным вершинам a и b найти кратчайший путь (или пути в порядке возрастания длины) из a в b в графе G.
Представление пути в нагруженном графе prolong(C:[X|T],C1:[Y,X|T]):- move(X,Y,A), not(member(Y,[X|T])), C1 is C+A. ln([_],0). ln([A,B|T],C) :- move(A,B,C1), ln([B|T],C2), C is C1+C2. ln(C:_,C). • Для поиска кратчайшего пути надо уметь сравнивать пути по длине • 2 способа: • Хранить длину пути вместе с путём L=3:[a,b,c] • Каждый раз вычислятьдлину предикатом ln(L)
Поиск с весовой функцией (Unified Cost Search, UCS) path(X,Y,P):- bdth([[X]],Y,P). bdth([[X|T]|_],X,[X|T]). bdth([P|QI],X,R) :- findall(Z,prolong(P,Z),T), append(QI,T,QO),!, bdth(QO,X,R). bdth([_|T],Y,L) :- bdth(T,Y,L). • Наиболее очевидное решение – в «стандартном» алгоритме поиска (в ширину) продлевать в первую очередь кратчайшие в смысле весовой функции пути. • Эквивалентный подход – поддерживать очередь путей в отсортированном состоянии (очередь с приоритетами).
Поиск с весовой функцией - реализация path_bst(Start,Finish,Res):- bst([0:[Start]],Finish,_:Res). bst([C:[Finish|T]|_],Finish,C:[Finish|T]). bst([Path|Rest],Finish,R) :- findall(Z,prolong(Path,Z),NewPathes), place(NewPathes,Rest,NewQueue), !, bst(NewQueue,Finish,R). bst([P|T],F,R) :- bst(T,F,R).
Вставка в очередь place([],L,L). place([X|T],L,R) :- insert(X,L,L1), place(T,L1,R). insert(C:P,[C1:P1|R],[C:P,C1:P1|R]) :- C<C1, !. insert(C:P,[X|T],[X|R]) :- insert (C:P,T,R). insert(C:P,[],[C:P]).
Порядок просмотра путей | ?- search_bst(a,i,R). 2-->[2:[f,a],4:[d,a]] 2-->[4:[d,a],4:[g,f,a]] 3-->[4:[g,f,a],7:[e,d,a],8:[b,d,a]] 3-->[6:[h,g,f,a],7:[e,d,a],8:[b,d,a]] 3-->[7:[e,d,a],8:[b,d,a],9:[i,h,g,f,a]] 3-->[8:[b,d,a],9:[i,h,g,f,a],10:[i,e,d,a]] 3-->[9:[i,h,g,f,a],10:[i,e,d,a],12:[c,b,d,a]] R = [i,h,g,f,a] ? ; 4-->[10:[i,e,d,a],12:[c,b,d,a],12:[e,i,h,g,f,a],13:[c,i,h,g,f,a]] R = [i,e,d,a] ? ; 5-->[12:[c,b,d,a],12:[e,i,h,g,f,a],13:[c,i,h,g,f,a],13:[h,i,e,d,a],14:[c,i,e,d,a]] 5-->[12:[e,i,h,g,f,a],13:[c,i,h,g,f,a],13:[h,i,e,d,a],14:[c,i,e,d,a],16:[i,c,b,d,a]] 5-->[13:[c,i,h,g,f,a],13:[h,i,e,d,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],16:[i,c,b,d,a]] 5-->[13:[h,i,e,d,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],16:[i,c,b,d,a],17:[b,c,i,h,g,f,a]] 5-->[14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a],16:[i,c,b,d,a],17:[b,c,i,h,g,f,a]] 5-->[15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a],16:[i,c,b,d,a],17:[b,c,i,h,g,f,a],18:[b,c,i,e,d,a]] 5-->[15:[g,h,i,e,d,a],16:[i,c,b,d,a],17:[b,c,i,h,g,f,a],18:[b,c,i,e,d,a],19:[b,d,e,i,h,g,f,a]] 5-->[16:[i,c,b,d,a],17:[b,c,i,h,g,f,a],17:[f,g,h,i,e,d,a],18:[b,c,i,e,d,a],19:[b,d,e,i,h,g,f,a]] R = [i,c,b,d,a] ? ; 6-->[17:[b,c,i,h,g,f,a],17:[f,g,h,i,e,d,a],18:[b,c,i,e,d,a],19:[b,d,e,i,h,g,f,a],19:[e,i,c,b,d,a],19:[h,i,c,b,d,a]] …. 2-->[23:[f,g,h,i,c,b,d,a],24:[e,d,b,c,i,h,g,f,a]] 1-->[24:[e,d,b,c,i,h,g,f,a]] 0-->[]
Проблема • В рассмотренных алгоритмах поиска движение происходит в направлении кратчайшего пути, но не по направлению к цели • Аналогично поиску пути в городе без компаса • Для повышения эффективности поиска надо иметь возможность как-то задать направление поиска • Такие методы поиска называются информированными
Граф дорог с заданным направлением h(a,10). h(b,7). h(c,4). h(d,5). h(e,3). h(f,8). h(g,6). h(h,3). h(i,0). В данном примере функция h/2 задается для фиксированной конечной вершины. Для универсального алгоритма поиска надо задавать функцию h как функцию текущей и конечной вершин.
Жадный алгоритм поиска wt([P|_],C) :- h(P,C). place([],L,L). place([X|T],L,R) :- insert(X,L,L1), place(T,L1,R). insert(P,[P1|R],[P,P1|R]) :- wt(P,C), wt(P1,C1), C<C1,!. insert(P,[X|T],[X|R]) :-insert(P,T,R). insert(P,[],[P]). search_grd(Start,Finish,Res):- greedy([[Start]],Finish,Res). greedy([[Finish|T]|_],Finish,[Finish|T]). greedy([Path|Rest],Finish,R) :- findall(Z,prolong(Path,Z),NewPathes), place(NewPathes,Rest,NewQueue), !,greedy(NewQueue,Finish,R). greedy([P|T],F,R) :- greedy(T,F,R). • Будем продлевать первым путь, ближайший к цели с точки зрения эвристики
Порядок просмотра путей жадным алгоритмом | ?- search_grd(a,i,R). 2-->[[d,a],[f,a]] 3-->[[e,d,a],[b,d,a],[f,a]] 3-->[[i,e,d,a],[b,d,a],[f,a]] R = [i,e,d,a] ? ; 4-->[[h,i,e,d,a],[c,i,e,d,a],[b,d,a],[f,a]] 4-->[[c,i,e,d,a],[b,d,a],[g,h,i,e,d,a],[f,a]] 4-->[[b,d,a],[g,h,i,e,d,a],[b,c,i,e,d,a],[f,a]] 4-->[[c,b,d,a],[g,h,i,e,d,a],[b,c,i,e,d,a],[f,a]] 4-->[[i,c,b,d,a],[g,h,i,e,d,a],[b,c,i,e,d,a],[f,a]] R = [i,c,b,d,a] ? ; 5-->[[e,i,c,b,d,a],[h,i,c,b,d,a],[g,h,i,e,d,a],[b,c,i,e,d,a],[f,a]] 4-->[[h,i,c,b,d,a],[g,h,i,e,d,a],[b,c,i,e,d,a],[f,a]] 4-->[[g,h,i,e,d,a],[b,c,i,e,d,a],[g,h,i,c,b,d,a],[f,a]] 4-->[[b,c,i,e,d,a],[g,h,i,c,b,d,a],[f,a],[f,g,h,i,e,d,a]] 3-->[[g,h,i,c,b,d,a],[f,a],[f,g,h,i,e,d,a]] 3-->[[f,a],[f,g,h,i,e,d,a],[f,g,h,i,c,b,d,a]] 3-->[[g,f,a],[f,g,h,i,e,d,a],[f,g,h,i,c,b,d,a]] 3-->[[h,g,f,a],[f,g,h,i,e,d,a],[f,g,h,i,c,b,d,a]] 3-->[[i,h,g,f,a],[f,g,h,i,e,d,a],[f,g,h,i,c,b,d,a]] R = [i,h,g,f,a] ? ; 4-->[[e,i,h,g,f,a],[c,i,h,g,f,a],[f,g,h,i,e,d,a],[f,g,h,i,c,b,d,a]] .... 1-->[[f,g,h,i,c,b,d,a]] 0-->[]
Особенности алгоритма • Жадный алгоритм учитывает оптимальносить пути с точки зрения эвристики, но не учитывает вес пути • Не обеспечивается нахождение кратчайшего пути • Хорошо бы иметь алгоритм: • Который находит кратчайший путь (допустимый) • Который делает это оптимальным образом, с учётом направления на цель
Алгоритм A f(X) = g(X) + h(X) A g(X) • Где • g(X) – длина текущего пути от начальной вершины до X • h(X) – эвристическая функция • f(X) – весовая функция, по которой осуществляется отбор пути … X … … Z h(X) В качестве весовой функции при выборе направления поиска будем рассматривать
Реализация алгоритма A wt(C:[A|T],H) :- h(A,Z), H is C+Z. place([],L,L). place([X|T],L,R) :- insert(X,L,L1), place(T,L1,R). insert(P,[P1|R],[P,P1|R]) :- wt(P,C), wt(P1,C1), C<C1,!. insert(P,[X|T],[X|R]) :-insert(P,T,R). insert(P,[],[P]). path_a(Start,Finish,Res):- a([0:[Start]],Finish,_:Res). a([C:[Finish|T]|_],Finish,C:[Finish|T]). a([Path|Rest],Finish,R) :- findall(Z,prolong(Path,Z),NewPathes), place(NewPathes,Rest,NewQueue), !, a(NewQueue,Finish,R). a([P|T],F,R) :- a(T,F,R).
Порядок просмотра путей алгоритмом A | ?- search_ast(a,i,R). 2-->[4:[d,a],2:[f,a]] 3-->[2:[f,a],7:[e,d,a],8:[b,d,a]] 3-->[7:[e,d,a],4:[g,f,a],8:[b,d,a]] 3-->[4:[g,f,a],10:[i,e,d,a],8:[b,d,a]] 3-->[6:[h,g,f,a],10:[i,e,d,a],8:[b,d,a]] 3-->[9:[i,h,g,f,a],10:[i,e,d,a],8:[b,d,a]] R = [i,h,g,f,a] ? ; 4-->[10:[i,e,d,a],8:[b,d,a],12:[e,i,h,g,f,a],13:[c,i,h,g,f,a]] R = [i,e,d,a] ? ; 5-->[8:[b,d,a],12:[e,i,h,g,f,a],13:[h,i,e,d,a],13:[c,i,h,g,f,a],14:[c,i,e,d,a]] 5-->[12:[e,i,h,g,f,a],13:[h,i,e,d,a],12:[c,b,d,a],13:[c,i,h,g,f,a],14:[c,i,e,d,a]] 5-->[13:[h,i,e,d,a],12:[c,b,d,a],13:[c,i,h,g,f,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a]] 5-->[12:[c,b,d,a],13:[c,i,h,g,f,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a]] 5-->[16:[i,c,b,d,a],13:[c,i,h,g,f,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a]] R = [i,c,b,d,a] ? ; 6-->[13:[c,i,h,g,f,a],14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a],19:[e,i,c,b,d,a],19:[h,i,c,b,d,a]] 6-->[14:[c,i,e,d,a],15:[d,e,i,h,g,f,a],15:[g,h,i,e,d,a],19:[e,i,c,b,d,a],19:[h,i,c,b,d,a],17:[b,c,i,h,g,f,a]] ... 3-->[23:[c,b,d,e,i,h,g,f,a],24:[e,d,b,c,i,h,g,f,a],23:[f,g,h,i,c,b,d,a]] 2-->[24:[e,d,b,c,i,h,g,f,a],23:[f,g,h,i,c,b,d,a]] 1-->[23:[f,g,h,i,c,b,d,a]] 0-->[]
Допустимость алгоритма поиска • Алгоритм поиска называется допустимым (admissible), если он в первую очередь находит оптимальный путь в смысле заданного критерия. • Поскольку на функцию h(.) не накладывается никаких ограничений, возможна ситуация, когда «хороший» (оптимальный) путь не рассматривается, т.к. ему соответствует бОльшее значение h(.) • Чтобы алгоритм поиска A был допустимым, необходимо наложить ограничения на h(.)
Критерий допустимости A* • f(X)=g(X)+h(X), где • g(X) – длина текущего пути от A до X • h(X) – показатель близости конечной вершины Z • Раз мы складываем g(.) и h(.), логично требовать, чтобы они были одной размерности • Реально интересно минимизировать длину оптимального пути f*(X) = g*(X)+h*(X), где • g*(X) – длина кратчайшего пути из A в X • h*(X) – длина кратчайшего пути из X в Z • Теорема. Алгоритм поиска A является допустимым, если для всех вершин графа 0 h(x) h*(x); в этом случае алгоритм поиска называется алгоритмом A*.
Информированность • h(x)=0 - удовлетворяет условию допустимости • Получаем UCS • Чем больше значение функции h(·), тем больше информации об особенностях пространства поиска мы сообщаем алгоритму • Определение. Алгоритм эвристического поиска с функцией h1(x) называется более информированным по сравнению с алгоритмом с функцией h2(x), если для всех вершин графа x имеет место h1(x) h2(x).
Монотонность • Определение.Эвристическая функция h(x) называется монотонной, если h(e)=0 для конечного состояния e, и для любых вершин u,v лежащих на одном пути имеет место |h(u)-h(v)|cost(u,v), где cost(u,v) — стоимость пути от u до v. • Теорема. Любая монотонная эвристика допустима. • Действительно, для некоторого пути a1a2... ane из свойства монотонности следует, что h(ai-1)-h(ai) cost(ai-1,ai), что при суммировании дает h(a1) = h(a1)-h(e) cost(a1,e) = h*(a1).
Пример: эвристика для игры в 8 dist([],[],0). dist([A|T],[B|R],Z) :- dist(T,R,D), ndiff(A,B,E), Z is D+E. ndiff([],[],0). ndiff([A|T],[A|R],Z) :- ndiff(T,R,Z), !. ndiff([_|T],[_|R],Z) :- ndiff(T,R,D), Z is D+1. h(S,R) :- final(F), dist(S,F,R). • Длина пути измеряется в количестве шагов • Эвристическая функция не должна быть больше, чем число шагов до финиша • Предлагаем использовать количество фишек не на своих местах
Алгоритм IDA* • Идею итерационного заглубления можно использовать для реализации A* • Тонкие моменты: • В ID приращение идет по числу шагов (целочисленное), в IDA* - по длине пути • Имеет смысл устанавливать каждый раз некоторый диапазон возможных длин => не гарантируется допустимость в строгом смысле
Метод градиентного спуска • В некоторых случаях пространство состояний имеет очень сложную природу, что не позволяет хранить пути из состояний в памяти • Пример: экспертные системы • Пример: состояние работающей программы • В этих случаях возможно использование алгоритмов поиска, которые рассматривают только одно направление движения • Движение в оптимальном направлении даёт нам алгоритм градиентного спуска • Оптимальное направление может задаваться эвристической функцией • Проблема: остановка в локальном максимуме/минимуме
Представление списка путей деревом [a,b,c] [a,b,d,e] [a,b,d,f] t(a,[t(b,[l(c),t(d,[l(e),l(f)])])]) • В очереди путей часто присутствует множество путей с общим началом • За счёт представления таких путей деревом можно сильно выиграть по памяти
Tree Search search_t(S,F,P) :- t_search(F,l(S),P). t_search(Finish,Tree,Path):- expand(Finish,[],Tree,Tree1,Path), ( nonvar(Path); var(Path), t_search(Finish,Tree,Path)). expand(Finish,Past,l(Finish),_,[Finish|Past]). expand(_,Past,l(Current),t(Current,Sub),_):- findall( NextLeave, t_prolong(NextLeave,[Current|Past]), Sub). expand(Finish,Past,t(Current,Sub),t(Current,Sub1),Path):- expand_all(Finish,[Current|Past],Sub,[],Sub1,Path). t_prolong(l(Next),[Current|Past]):- move(Current,Next), not(member(Next,Past)). expand_all(_,_,[],[T|TT],[T|TT],_). expand_all(Finish,Path0,[T|TT],TT1,Sub1,Path):- expand(Finish,Path0,T,T1,Path), ( nonvar(Path); var(Path),!, expand_all(Finish,Path0,TT,[T1|TT1],Sub1,Path); expand_all(Finish,Path0,TT,TT1,Sub1,Path) ).
Closed Vertex List • В рассмотренных методах поиска мы не производили обход уже пройденных вершин в рамках одного пути • При откате назад путь укорачивается, и возможно повторное рассмотрение вершины в рамках другого пути • Для запоминания пройденных вершин на протяжении всего поиска можно использовать список closed list • Глобальные переменные • Представление путей парами
Использование глобального списка пройденных вершин get_global(G,X) :- value(G,X).store_global(G,X) :- retractall(value(G,_)), asserta(value(G,X)).create_global(G,X) :- asserta(value(G,X)). search_cdpth(A,B,L) :- create_global(closed,[]), cdpth([A],B,L). cdpth([X|T],X,[X|T]). cdpth(P,F,L) :- cprolong(P,P1), cdpth(P1,F,L). cprolong([X|T],[Y,X|T]) :- get_global(closed,C), move(X,Y), not(member(Y,C)), store_global(closed,[Y|C]). get_global(G,X) :- value(G,X). store_global(G,X) :- retractall(value(G,_)), asserta(value(G,X)). create_global(G,X) :- asserta(value(G,X)).
Поиск с представлением путей парами search_cbdth(X,Y,L) :- cbdth([pair(X,nil)],[],Y,R,C), reconstruct(R,C,L). cbdth([pair(Y,Z)|_],C,Y,pair(Y,Z),C). cbdth([pair(S,P)|Q],C,Y,R,CC) :- findall(Z,prolong(S,Q,C,Z),L), append(Q,L,NewQ),!, cbdth(NewQ,[pair(S,P)|C],Y,R,CC). cbdth([_|Q],C,Y,R,CC) :- cbdth(Q,C,Y,R,CC). prolong(S,Q,C,pair(X,S)) :- move(S,X), not(member(pair(X,_),Q)), not(member(pair(X,_),C)). reconstruct(pair(X,nil),_,[X]). reconstruct(pair(X,Y),L,[X|T]) :- member(pair(Y,Z),L), reconstruct(pair(Y,Z),L,T).
Представление путей парами 1 --> [pair(a,nil)] : [] 2 --> [pair(d,a),pair(f,a)] : [pair(a,nil)] 3 --> [pair(f,a),pair(e,d),pair(b,d)] : [pair(d,a),pair(a,nil)] 3 --> [pair(e,d),pair(b,d),pair(g,f)] : [pair(f,a),pair(d,a),pair(a,nil)] 3 --> [pair(b,d),pair(g,f),pair(i,e)] : [pair(e,d),pair(f,a),pair(d,a),pair(a,nil)] 3 --> [pair(g,f),pair(i,e),pair(c,b)] : [pair(b,d),pair(e,d),pair(f,a),pair(d,a),pair(a,nil)] R = [i,e,d,a] ? ; 3 --> [pair(i,e),pair(c,b),pair(h,g)] : [pair(g,f),pair(b,d),pair(e,d),pair(f,a),pair(d,a),pair(a,nil)] 2 --> [pair(c,b),pair(h,g)] : [pair(i,e),pair(g,f),pair(b,d),pair(e,d),pair(f,a),pair(d,a),pair(a,nil)] 1 --> [pair(h,g)] : [pair(c,b),pair(i,e),pair(g,f),pair(b,d), pair(e,d),pair(f,a),pair(d,a),pair(a,nil)]
Мораль • Эвристические алгоритмы поиска позволяет сократить пространство перебора за счет введения некоторой «внешней» эвристической функции • Для сохранения свойства допустимости эвристическую функцию нужно определять «с умом» • Компромисс между критерием допустимости и информированностью • Возможно комбинирование эвристических методов с более эффективными способами представления путей в памяти • Для сложных случаев используют градиентный эвристический спуск (hill climbing)