Программирование на языке Пролог для искусственного интеллекта - Иван Братко
Шрифт:
Интервал:
Закладка:
% 'нет' означает нет ни одного решения
формат( Пер, Имя, Имя, Перем, [Пер/Имя | Перем]) :-
var( Пер), !.
формат( Атом, Имя, Атом, Перем, Перем) :-
atomic( Атом), !,
atomic( Имя).
формат( Цель, Форм, Вопрос, Перем0, Перем) :-
Цель =.. [Функтор | Apг1],
Форм =.. [Функтор | Форм1],
формвсе( Apг1, Форм1, Арг2, Перем0, Перем),
Вопрос =.. [Функтор | Арг2].
формвсе( [], [], [], Перем, Перем).
формвсе( [X | СпХ], [Ф | СпФ], [В | СпВ], Перем0, Перем) :-
формвсе( СпХ, СпФ, СпВ, Перем0, Перем1),
формат( X, Ф, В, Перем1, Перем).
запрос_перем( []).
запрос_перем( [Переменная/Имя | Переменные]) :-
nl, write( Имя), write( '='),
read( Переменная),
запрос_перем( Переменные).
выд_трассу( []) :-
nl, write( 'Это был ваш вопрос'), nl.
выд_трассу( [Цель по Прав | Трасса] ) :-
nl, write( 'Чтобы проверить по' ),
write( Прав), write( ', что'),
write( Цель),
выд_трассу( Трасса).
конкретный( Терм) :-
нумпер( Терм, 0, 0). % Нет переменных в Терм'е
% конкретизация( Т1, Т2) означает, что Т2 - конкретизация Т1,
% т.е. терм Т1 - более общий, чем Т2, или той же степени
% общности, что и Т2
конкретизация( Терм, Терм1) :-
% Терм1 - частный случай Терм'а
копия( Терм1, Терм2),
% Копия Терм1 с новыми переменными
нумпер( Терм2, 0, _), !,
Терм = Терм2. % Успех, если Терм1 - частный случай Терм2
копия( Терм, НовТерм) :-
% Копия Терм' а с новыми переменными
asserta( copy( Терм) ),
retract( сору( НовТерм) ), !.
посл_индекс( 0). % Начальный индекс для "сказано"
след_индекс( Инд) :- % Следующий индекс для "сказано"
retract( посл_индекс( ПослИнд) ), !,
Инд is ПослИнд + 1,
assert( посл_индекс( Инд) ).
Рис. 14.11. Оболочка экспертной системы: Вопросы к пользователю и ответы на вопросы "почему".
Отношение, подобное нумпер, часто входит в состав пролог-системы в качестве встроенной процедуры. Если это не так, то его можно реализовать программно следующим способом:
нумпер( Терм, N, Nплюс1) :-
var( Терм), !, % Переменная?
Терм = пер/N,
Nплюс1 is N + 1.
нумпер( Терм, N, М) :-
Терм =.. [Функтор | Аргументы], % Структура или атом
нумарг( Аргументы, N, M).
% Пронумеровать переменные в аргументах
нумарг( [], N, N) :- !.
нумарг( [X | Спис], N, M) :-
нумпер( X, N, N1),
нумарг( Спис, N1, М).
14.5.4. Процедура выдать
Процедура
выдать( Ответ)
приведенная на рис. 14.12, показывает пользователю окончательный результат консультационного сеанса и дает объяснения типа "как". Ответ включает в себя как ответ на вопрос пользователя, так и дерево вывода, демонстрирующее как система пришла к такому заключению. Сначала процедура выдать представляет пользователю свое заключение. Затем, если пользователь пожелает узнать, как это заключение достигнуто, то печатается дерево вывода в некоторой удобной для восприятия форме - это и есть объяснение типа "как". Форма объяснения показана в примере разд. 14.4.3.
14.5.5. Драйвер верхнего уровня
И наконец, для того, чтобы иметь удобный доступ к оболочке из интерпретатора Пролога, нам необходима процедура, выполняющая функцию "драйвера". На рис. 14.13 показано, как могла бы выглядеть предназначенная для этой цели процедура эксперт. Драйвер эксперт производит запуск трех основных модулей оболочки (рис. 14.10–14.12) и координирует их работу. Например:
?- эксперт.
Пожалуйста, спрашивайте: % Приглашение пользователю
X это животное и голиаф это X. % Вопрос пользователя
Это правда: голиаф имеет шерсть?
...
% Выдача заключения консультационного сеанса и
% объяснения типа "как"
выдать( Ответ) :-
nl, заключение( Ответ),
nl, write( 'Хотите узнать, как?'),
принять( Ответ1),
( Ответ1 = да, !, отобр( Ответ);
true). % Показ решающего дерева
заключение( Ответ1 и Ответ2) :- !,
заключение( Ответ1), write( 'и'),
заключение( Ответ2).
заключение( Заключение было Найдено) :-
write( Заключение).
% "отобр" отображает полное решающее дерево
отобр( Решение) :-
nl, отобр( Решение, 0), !. % Отступ 0
отобр( Ответ1 и Ответ2, H) :- !, % Отступ H
отобр( Ответ1, H),
tab( H), write( 'и'), nl,
отобр( Ответ2, H).
отобр( Ответ был Найден, H) :- % Отступ H
tab( H), печответ( Ответ), % Показ заключения
nl, tab( H),
write( 'было'),
отобр1( Найден, H). % Показ доказательства
отобр1( Выведено из Ответ, H) :- !,
write( Выведено), write( 'из'), % Показ имени правила
nl, H1 is H + 4,
отобр( Ответ, H1). % Показ "предшественника"
отобр1( Найдено, _ ) :-
% Найдено = 'сказано' или 'найдено как факт'
write( Найдено), nl.
печответ( Цель это правда) :- !,
write( Цель). % На выходе 'это правда' опускается
печответ( Ответ) :- % Отрицательный ответ
write( Ответ).
Рис. 14.12. Оболочка экспертной системы: Отображение окончательного результата и объяснение типа "как".
14.5.6. Одно замечание по поводу программы-оболочки
В некоторых местах нашей программы-оболочки обнаруживается недостаток той "декларативной ясности", которая так характерна для программ, написанных на Прологе. Причина состоит в том, что нам пришлось предусмотреть в этой программе довольно жесткое управление процессом функционирования оболочки. Ведь, согласно нашему замыслу, экспертная система должна была не только находить ответы на вопросы, но и делать это некоторым разумным с точки зрения пользователя способом. В связи с этим нам пришлось реализовать вполне определенное поведение системы в процессе решения задач, а не просто некоторое отношение ввода-вывода. В результате получилась программа более процедурного характера, чем обычно. Все это может послужить примером ситуации, когда, не имея возможности рассчитывать на собственные процедурные механизмы Пролога, мы вынуждены взять на себя детальное описание процедурного поведения системы.
14.5.7. Цели с отрицанием
Использование знака отрицания в левых частях правил, а следовательно, и в вопросах, обрабатываемых процедурой рассмотреть, представляется естественным и его следует разрешить. В качестве первой попытки можно предложить следующий способ работы с отрицанием целей:
рассмотреть( не Цель, Трасса, Ответ) :- !,
рассмотреть( Цель, Трасса, Ответ1),
обратить( Ответ1, Ответ).
% Получить обратное истинностное значение
обратить( Цель это правда было Найдено,
( не Цель) это ложь было Найдено).
обратить( Цель это ложь было Найдено,
( не Цель) это правда было Найдено).
% Процедура-драйвер верхнего уровня
эксперт :-
принять_вопрос( Вопрос),
% Ввести вопрос пользователя
( ответ_да( Вопрос);
% Попытка найти положительный ответ
ответ_нет( Вопрос) ).
% Если нет положительного ответа, то найти отрицательный
ответ_да( Вопрос) :-
% Искать положительный ответ на Вопрос
статус( отрицательный),
% Пока еще нет положительного ответа
рассмотреть( Вопрос, [], Ответ), % Трасса пуста
положительный( Ответ), % Искать положительный ответ
статус( положительный),
% Найден положительный ответ
выдать( Ответ), nl,