В этой лекции речь пойдет о возможных применениях Пролога в области искусственного интеллекта. Конечно, ознакомиться с данной темой достаточно полно в рамках одной лекции мы не успеем. Однако хочется надеяться, что сможем пробежаться по верхушкам и рассмотреть пару простых примеров.
В 1950 году Алан Тьюринг в статье "Вычислительная техника и интеллект" (книга "Может ли машина мыслить?") предложил эксперимент, позднее названный "тест Тьюринга", для проверки способности компьютера к "человеческому" мышлению. В упрощенном виде смысл этого теста заключается в том, что можно считать искусственный интеллект созданным, если человек, общающийся с двумя собеседниками, один из которых человек, а второй — компьютер, не сможет понять, кто есть кто. То есть в соответствии с тестом Тьюринга, компьютеру требуется научиться имитировать человека в диалоге, чтобы его можно было считать "интеллектуальным".
Такой подход к распознаванию искусственного интеллекта многие критиковали, однако никаких достойных альтернатив тесту Тьюринга предложено не было.
Первый пример, который мы рассмотрим, будет относиться к области обработки естественного языка.
Пример. Создадим программу, имитирующую разговор психотерапевта с пациентом. Прообразом нашей программы является "Элиза", созданная Джозефом Вейценбаумом в лаборатории искусственного интеллекта массачусетского технологического института в 1966 году (названная в честь Элизы из "Пигмалиона"). Она была написана на языке Лисп и состояла всего из нескольких десятков строк программного кода. Эта программа моделировала методику известного психотерапевта Карла Роджерса. В этом подходе психотерапевт играет роль "вербального зеркала" пациента. Он переспрашивает пациента, повторяет его слова, позволяя ему самому найти выход из сложившейся ситуации, прийти в состояние душевного равновесия.
На самом деле эта программа пытается сопоставить вводимые пользователем ответы с имеющимися у нее шаблонами и, если ей это удается, шаблонно же отвечает.
Вейценбаум создал эту программу в качестве шутки. Многие пациенты, пообщавшись с детищем Вейценбаума, утверждали, что "Элиза" им помогла, и отказывались верить, что их собеседником был не психотерапевт, а компьютерная программа. Всю оставшуюся жизнь Вейценбаум пытался охладить восторженных поклонников его программы и убедить общественность, что машина не может мыслить.
Наша программа будет действовать по следующему алгоритму.
- Попросит человека описать имеющуюся у него проблему.
- Прочитает строку с клавиатуры.
- Попытается подобрать шаблон, которому соответствует введенная человеком строка.
- Если удалось — выдаст соответствующий этому шаблону ответ пользователю.
- Если подобрать шаблон не удалось — попросит продолжать рассказ.
- Возвращаемся к пункту 2 и продолжаем процесс.
Для решения этой задачи нам понадобится предикат, преобразующий строку, вводимую пользователем в список слов. Можно было бы воспользоваться модифицированной версией предиката str_a_list, рассмотренного нами в одиннадцатой лекции. Однако он использует предикат fronttoken, который в Турбо Прологе, в отличие от Визуального Пролога, из русских предложений выделяет не слова, а отдельные символы. Поэтому мы напишем новый вспомогательный предикат, который будет считывать символ за символом до тех пор, пока не встретит символ-разделитель (пробел, запятая, точка и другой знак препинания). Так как проверять совпадение очередного символа с символом-разделителем нам придется не раз, заведем список символов-разделителей. Поместим его в раздел описания констант и назовем separators (символы-разделители). После этого все символы до символа-разделителя будут помещены в первое слово строки, а все символы, идущие после символа-разделителя, обработаны подобным образом.
Кроме того, при переписывании строки в список ее слов мы переведем все русские символы, записанные в верхнем регистре (большие буквы), в нижний регистр (маленькие буквы). Это облегчит в дальнейшем процесс распознавания слов. Нам не придется предусматривать всевозможные варианты написания пользователем слова (например, "Да", "да", "ДА"), мы будем уверены, что все символы слова — строчные ("да").
При реализации этого предиката нам понадобится три вспомогательных предиката.
Первый предикат будет преобразовывать прописные русские буквы в строчные, а все остальные символы оставлять неизменными. У него будет два аргумента: первый (входной) — исходный символ, второй (выходной) — символ, полученный преобразованием первого аргумента.
При написании данного предиката стоит учесть, что строчные русские буквы расположены в таблице символов двумя группами. Первая группа (буквы от 'а' до 'п') имеют, соответственно, коды от 160 до 175. Вторая группа (буквы от 'р' до 'я') — коды от 224 до 239.
С учетом вышеизложенного предикат можно записать, например, так:
lower_rus(C,C1):–
'А'<=C,C<='П',!, /* символ C лежит между
буквами 'А' и 'П' */
char_int(C,I), /* I — код символа C */
I1=I+(160–128), /* 160 — код буквы 'а',
128 — код буквы 'А'*/
char_int(C1,I1).
/* C1 — символ с кодом I1 */
lower_rus(C,C1):–
'Р'<=C,C<='Я',!, /* символ C лежит между
буквами 'Р' и 'Я' */
char_int(C,I), /* I — код символа C */
I1=I+(224–144), /* 224 — код буквы 'р',
144 — код буквы 'Р'*/
char_int(C1,I1).
/* C1 — символ с кодом I1 */
lower_rus(C,C). /* символ C отличен от прописной русской
буквы и, значит, мы не должны его
изменять */
Второй предикат first_word будет иметь три аргумента. Первый (входной) — исходная строка, второй и третий (выходные) — соответственно, первое слово строки (не содержащее прописных русских букв) и остаток строки, полученный удалением из него первого слова.
Выглядеть его реализация будет следующим образом:
first_word("","",""):–!. /* из пустой строки можно
выделить только пустые
подстроки */
first_word(S,W,R):– /* W — первое слово строки S, R —
остальные символы исходной
строки S */
frontchar(S,C,R1),
/* C — первый символ строки S, R1 —
остальные символы */
not(member(C,separators)),!,
/* символ C не является
символом-разделителем */
first_word(R1,S1,R),
/* S1 — первое слово строки R1,
R — оставшиеся символы
строки R1 */
lower_rus(C,C1),
/* если C — прописная русская
буква , то C1 — соответствующая
ей строчная буква, иначе
символ C1 не отличается
от символа C */
frontchar(W,C1,S1).
/* W — результат "приклеивания"
символа C1 в начало строки S1 */
first_word(S,"",R):– /* в случае, если первый символ
оказался символом-разделителем, */
frontchar(S,_,R). /* его нужно выбросить, */
Третий предикат del_sep будет предназначен для удаления из начала строки символов-разделителей. У него будет два аргумента. Первый (входной) — исходная строка, второй (выходной) — строка, полученная из первого аргумента удалением символов-разделителей, расположенных в начале строки, если таковые имеются.
del_sep("",""):–!.
del_sep(S,S1):–
frontchar(S,C,R),
/* C — первый символ строки,
R — остальные символы */
member(C,separators),!,
/* если C является
символом-разделителем, */
del_sep(R,S1).
/* то переходим к рассмотрению
остатка строки */
del_sep(S,S) . /* если первый символ строки не является
символом-разделителем, то удалять
нечего */
И, наконец, предикат, преобразующий строку в список слов.
str_w_list("",[]):–!. /* пустой строке соответствует
пустой список слов, входящих
в нее */
str_w_list(S,[H T]):–
first_word(S,H,R),!,
/* H — первое слово строки S,
R — оставшиеся символы
строки S */
str_w_list(R,T).
/* T — список, состоящий из слов,
входящих в строку R */
Основную работу в программе будет осуществлять предикат recognize, задачей которого будет распознавать шаблон, которому можно сопоставить введенную строку. Этот предикат на входе будет получать список слов строки, а на выходе будет выдавать номер шаблона. По этому номеру другой предикат должен будет выдать на экран соответствующую реакцию (вопрос, реплику, уточнение).
Наша учебная программа будет распознавать одиннадцать шаблонов:
- Человек хочет закончить работу с программой. Об этой ситуации свидетельствует наличие в списке таких слов, как "пока", "свидания" (часть словосочетания "до свидания"). В ответ программа также прощается и выражает надежду, что она смогла чем-нибудь помочь.
- Человек испытывает какое-то чувство (наличие в списке слова "испытываю"). Программа реагирует вопросом о том, как давно человек испытывает это чувство.
- Если во вводимой строке встретились слова "любовь" или "чувства", то программа поинтересуется, не боится ли человек эмоций.
- При обнаружении слова "секс" во входном списке слов будет выдано сообщение о важности сообщения.
- В случае наличия слов "бешенство", "гнев" или "ярость", программа уточнит, что человек испытывает в данный момент времени.
- В ответ на краткий ответ ("да" или "нет") будет выдана просьба рассказать подробнее.
- Если в списке слов найдутся слова "комплекс" или "фиксация", программа отреагирует замечанием о том, что человек слишком много "играет".
- Появление слова "всегда" в строке, введенной человеком, приводит к ответной реакции — вопросу о том, может ли человек привести какой-нибудь пример.
- В случае, если человек упомянул кого-то из своих родных ("папа", "мама", "жена", "муж", "брат", "сестра", "сын", "дочь" и т.д.), программа попросит рассказать поподробнее о его семье. При этом упомянутый родственник будет помещен в базу данных, чтобы потом продолжить этот разговор.
- Если в процессе разговора была сделана запись во внутреннюю базу данных и в данный момент спросить больше не о чем, программа "вспомнит" об упомянутом родственнике и выдаст фразу: "ранее Вы упоминали ..."
- И, наконец, если введенная строка не подходит ни под один шаблон, программа просит продолжить рассказ.
А теперь запишем всю программу целиком.
Листинг
14.1.
Программа, имитирующая разговор психотерапевта с пациентом
(html,
txt)