Усовершенствовать работу этой программы можно двумя способами. С одной стороны, можно увеличить количество шаблонов, с другой стороны, можно организовать разные реакции на некоторые из шаблонов (например, используя случайные числа).
В 1977 г. Кеннет Колби, основываясь на принципах организации "Элизы", создал программу, которая подобным образом вводила в заблуждение уже не клиентов психиатров, а самих докторов. Большинство из них после общения с его программой решили, что имели дело с реальным параноиком.
В 1996 г. Грег Гарви создал программную модель католического исповедника, которая опиралась на те же идеи, что и "Элиза".
Другие варианты "Элизы" можно найти в следующих книгах:
- Л. Стерлинг, Э. Шапиро. Искусство программирования на языке Пролог. — М.:Мир, 1990.
- Д. Марселлус. Программирование экспертных систем на Турбо-Прологе. — М.: Финансы и статистика, 1994.
Теперь рассмотрим еще один пример применения Пролога в области искусственного интеллекта. Создадим небольшую экспертную систему. Экспертными системами обычно называют программы, которые могут заменить эксперта в какой-то предметной области. Мы построим классификационную экспертную систему, которая будет пытаться угадать загаданное человеком животное. Если загаданное человеком животное окажется неизвестно нашей программе, у нее будет возможность пополнить свою базу знаний новой информацией.
В связи с тем, что мы планируем пополнять нашу базу знаний, мы будем по окончании работы сохранять ее в файл, а при начале работы считывать информацию из файла в оперативную память. Для этого мы воспользуемся изученными в предыдущей лекции внутренними (динамическими) базами данных.
Так как в Турбо Прологе в базе данных можно размещать только факты, представим правила, определяющие животных в виде фактов.
Определим два предиката внутренней базы данных, которые позволят нам хранить информацию о животных.
Один из них предназначен для хранения характеристик животных и будет иметь два аргумента: первый — номер свойства, второй — его словесное описание.
Небольшой базовый набор свойств может выглядеть, например, так:
cond(1,"кормит детенышей молоком").
cond(2,"имеет перья").
cond(3,"плавает").
cond(4,"ест мясо").
cond(5,"имеет копыта").
cond(6,"летает").
cond(7,"откладывает яйца").
cond(8,"имеет шерсть").
cond(9,"имеет полосы").
cond(10,"имеет пятна").
cond(11,"имеет черно-белую окраску").
cond(12,"имеет длинную шею").
cond(13,"имеет длинные ноги").
cond(14,"имеет щупальца").
Второй предикат будет хранить описание животных. Первый его аргумент — название животного, второй — список, элементами которого являются номера свойств, присущих данному животному.
Выглядеть эта база знаний может примерно следующим образом:
rule("гепард",[1,4,8,10]).
rule("тигр",[1,4,8,9]).
rule("жираф",[1,5,8,10,12,13]).
rule("зебра",[1,5,8,9,11]).
rule("страус",[2,14]).
rule("пингвин",[2,3,11]).
rule("орел",[2,6]).
rule("кит",[1,3,11]).
rule("осьминог",[3,14]).
По сути дела, в виде фактов записаны правила. Например, правило: "если животное кормит детенышей молоком, имеет копыта, пятна, длинную шею и ноги, то это жираф", записано в виде rule("жираф", [1,5,11,13,14]).
Во второй базе мы будем хранить ответы человека в следующем виде:
cond_is(N,'1') /* если загаданное животное имеет свойство
с номером N */
cond_is(N,'2') /* если загаданное животное не имеет
свойства с номером N */
Первую базу назовем knowledge, а вторую — dialog.
Процесс отгадывания задуманного животного будет проходить следующим образом. Будем последовательно перебирать животных, имеющихся в нашей базе знаний. Если загаданное животное обладает всеми характеристиками известного программе животного, делается вывод о том, кто был загадан. Если не удается сопоставить загаданному животному ни одно из животных, имеющихся в базе знаний, производится пополнение базы.
Вот как будет выглядеть реализация написанного выше.
animals:–
rule(X,L),
check(L),
nl,write("Я думаю это ",X),
nl,write("Я прав? (1 — да, 2 — нет)"),
read_true_char(C),C='1',!.
animals:–
nl,write("Я не знаю, что это за животное"),nl,
nl,write("Давайте добавим его в мою базу
знаний."),nl,
update.
Предикат check осуществляет проверку свойств, номера которых входят в список, указанный в качестве его единственного аргумента.
check([H|T]):–
test_cond(H),
check(T).
check([]).
Предикат test_cond проверяет наличие у загаданного животного свойства с номером, указанным в качестве его единственного аргумента. Если человеком ранее уже был дан ответ (положительный или отрицательный) по поводу наличия данного свойства, информация об этом имеется в базе данных. Если же в базе нет никакой информации о наличии данного свойства у загаданного животного, нужно задать человеку соответствующий вопрос и добавить его ответ в базу.
Вот как это можно записать.
test_cond(H):–
cond_is(H,'1'),!. /* в базе имеется
информация о наличии
данного свойства */
test_cond(H):–
cond_is(H,'2'),!,
fail. /* в базе имеется информация
об отсутствии данного свойства */
test_cond(H):– /* в базе нет никакой информации о данном
свойстве, получаем ее у человека */
cond(H,S),
nl,write("Оно ",S,"? (1 — да, 2 — нет)"),
read_true_char(A),
assert(cond_is(H,A)),
test_cond(H).
Предикат read_true_char осуществляет проверку нажатой пользователем клавиши, и если она отлична от '1' или '2', выводит соответствующее сообщение и повторно считывает символ с клавиатуры.
read_true_char(C):–
readchar(C1),
test(C1,C).
test(C,C):–
'1'<=C,C<='2',!.
test(_,C):–
write("Нажмите 1 или 2!"),nl,
readchar(C1),
test(C1,C).
Предикат update осуществляет добавление новой информации в базу знаний. Он читает название нового животного, с помощью предиката add_cond формирует список номеров свойств загаданного животного, добавляет соответствующий факт в базу знаний, сохраняет базу в файл.
Вот как он будет выглядеть:
update:–
nl,write("Введите название животного:"),
readln(S),
add_cond(L), /* указываем свойства животного */
assert(rule(S,L),knowledge),
/* добавляем информацию в базу
знаний*/
save("animals.ddb",knowledge)
/* сохраняем базу знаний в файл */.
Предикат add_cond с помощью предиката print_cond выводит уже имеющуюся в базе информацию о свойствах загаданного животного и спрашивает, известно ли еще что-нибудь о нем. В случае необходимости добавляет его новые характеристики, используя предикат read_cond.
add_cond(L):–
cond_is(_,'1'),!,
/* имеется информация о свойствах
животного */
nl,write("О нем известно, что оно: "),
print_cond(1,[],L1),
/* вывод имеющейся информации
о животном */
nl,write("Известно ли о нем еще что-нибудь?
(1 — да, 2 — нет)"),
read_true_char(C),
read_cond(C,L1,L).
add_cond(L):–
read_cond('1',[],L).
Предикат read_cond, используя предикат ex_cond, добавляет в список номера свойств, уже имеющихся в базе; используя предикат new_cond, добавляет в список номера новых свойств, а также описания самих свойств — в базу знаний.
read_cond('1',L,L2):–
ex_cond(1,L,L1,N),
new_cond(N,L1,L2),!.
read_cond(_,L,L):–!.
Основные предикаты мы рассмотрели, а вот как будет выглядеть вся программа целиком:
Листинг
14.2.
Самообучающийся определитель животных
(html,
txt)
В идеале экспертная система должна уметь объяснять пользователю свое решение, а также почему она задает тот или иной вопрос. Попробуйте добавить в нашу экспертную систему механизм объяснения.
Две версии этой программы, основанной на правилах (реализующие, соответственно, прямую и обратную цепочки рассуждений), можно найти в книге Д. Марселлус, Программирование экспертных систем на Турбо-Прологе. — М.: Финансы и статистика, 1994. Однако эти программы умеют распознавать только тех животных, которые заложены в них изначально. Возможности динамического пополнения базы знаний они не имеют. Для добавления описания нового животного требуется модификация программного кода.
Кроме того, я рекомендую читателям изучить программу GEOBASE, которая входит в состав и Турбо Пролога, и Visual Prolog. Эта программа содержит информацию по географии США и позволяет создавать запросы к базе данных на естественном (английском) языке.