WWW.DISUS.RU

БЕСПЛАТНАЯ НАУЧНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

 

Pages:     | 1 |   ...   | 5 | 6 || 8 | 9 |   ...   | 14 |

« А. П. Частиков Т. А. Гаврилова Д. Л.Белов РАЗРАБОТКА ЭКСПЕРТНЫХ СИСТЕМ. СРЕДА CLIPS Санкт-Петербург «БХВ-Петербург» 2003 ...»

-- [ Страница 7 ] --

С помощью диалогового окна Watch Options (см. рис. 6.10) или менеджера правил можно задавать режим отображения активаций и/или запуска пра­вил. В этом случае пользователь будет получать соответствующее информа­ционное сообщение при добавлении правила в план решения задачи или при удалении правила из него, а также при каждом запуске правила.

6.6.5. Просмотр данных, способных активировать правило

CLIPS предоставляет возможность просматривать списки наборов данных (фактов или объектов), способных активировать заданное правило.

Определение 6.35. Синтаксис команды matches

(matches <имя-правила>)

Команда matches выводит информацию обо всех возможных наборах дан­ных, способных активировать это правило. Посмотрите на результаты вы­полнения данной команды для правил MakeC и MakeD (наличие фактов а, b и с обязательно), приведенные на рис. 6.15 и 6.16 соответственно.

На этом мы закончим изучение синтаксиса правил CLIPS и основных команд и функций для работы с ними. Более полное описание команд и функций для работы с правилами приведено в гл. 15 и 16.

 Рис. 6.15. Данные, активирующие правило MakeC -88

Рис. 6.15. Данные, активирующие правило MakeC

ГЛАВА 7. Глобальные переменные

Помимо фактов, CLIPS предоставляет еще один способ представления данных — глобальные переменные (globals). В отличие от переменных, связанных со своим значением в левой части правила, глобальная переменная доступна везде после своего создания (а не только в правиле, в котором она полу­чила свое значение). Глобальные переменные CLIPS подобны глобальным переменным в процедурных языках программирования, таких как С или ADA. Однако, в отличие от переменных большинства процедурных языков программирования, глобальные переменные в CLIPS слабо типизированы. Фактически переменная может принимать значение любого примитивного типа CLIPS при каждом новом присваивании значения. Данная глава пол­ностью посвящена способам создания глобальных переменных и приемам работы с ними.

7.1. Конструктор defglobal и функции для работы с глобальными переменными

С помощью конструктора defglobal в среде CLIPS могут быть объявлены глобальные переменные и присвоены их начальные значения. В дальней­шем значения созданных таким образом переменных будут доступны в лю­бых конструкциях CLIPS. Глобальные переменные могут использоваться в процессе сопоставления образцов, но их изменения не запускает этот процесс.

Определение 7.1. Синтаксис конструктора defglobal

(defglobal [<имя-модуля>] <определение-переменной>*)

<определение-переменной> ::= <имя-переменной> = <выражение>

<имя-переменной>::= ?* <значение-типа-symbоl>*

В одном конструкторе может быть объявлено произвольное количество пе­ременных. CLIPS позволяет использовать произвольное количество конст­рукторов defglobal. Необязательный параметр <имя-модуля> определяет модуль, в котором должны быть определены конструируемые переменные. Если имя модуля не задано, то переменные будут помещены в текущий мо­дуль. В случае если создаваемая переменная уже была определена, то старое определение будет заменено новым. Если при выполнении конструктора defglobal возникает ошибка, то CLIPS произведет добавление всех пере­менных, заданных до ошибочного определения.

Команды, использующие глобальные переменные, например, такие как ppdefglobal или undefglobal, применяют значение типа symbol, являющееся именем переменной без символов ? и * (например, max для переменной, определенной как ?*mах*).

Глобальные переменные могут быть использованы в любом месте, где могут быть использованы переменные, созданные в левой части правил с некото­рыми исключениями. Во-первых, глобальные переменные не могут исполь­зоваться как параметры в конструкторах deffunction, defmethod или обра­ботчиках сообщений. Во-вторых, глобальные переменные не могут использоваться для получения новых значений в левой части правил. Например, правило из примера 7.1 недопустимо.

Пример 7.1. Неверное использование глобальной переменной

(defrule example

(fact ?*х*) =>)

А применение глобальной переменной так, как представлено в примере 7.2, вполне возможно.

Пример 7.2. Допустимое применение глобальной переменной

(defrule example

(fact ?y & :(> ?у ?*х*)) =>)

Изменение глобальной переменной не приводит к запуску процесса сопос­тавления образцов. Например, если в базу знаний системы был добавлен факт (fact 3) и переменной ?*х* было присвоено значение 4, правило не будет активировано из-за того, что в системе отсутствует набор данных, удовлетворяющих правило. Если после этого переменной ?*х* присвоить значение 2, несмотря на то, что текущий набор данных удовлетворяет всем условиям правила, оно все равно не будет активировано, т. к. изменение глобальной переменной не привело к запуску процесса сопоставления об­разцов.

Рассмотрим пример использования конструктора defglobal.

Пример 7.3. Использование конструктора defglobal

(defglobal

?*x*=3

?*y*=?*x*

?*z*=(+?*x* ?*y*)

?*q*=(create$ a b c)

)

После выполнения данного конструктора в CLIPS появятся 4 глобальные переменные: х, у, z и q. Переменной х присваивается целое значение 3. Пе­ременной у — значение, сохраненное в глобальной переменной х (т. е. 3). Переменной z — сумма значений х и у (т. е. 6). Переменной q присваивает­ся значение, равное составному полю, содержащему 3 значения типа symbol (а, b и с), созданному с помощью функции create$. В случае если в конструкторе defglobal не было допущено синтаксических ошибок, то defglobal не возвращает никаких значений. Если ошибки имели место, то пользователь получит соответствующее сообщение. Обратите внимание, что переменная у не является указателем на переменную х, просто их значения в данный момент совпадают. Если изменить значение х, значения перемен­ных у и z, несмотря ни на что, останутся равными 3 и б соответственно.



Чтобы увидеть результат работы конструктора defglobal, можно воспользо­ваться командой list- defglobals, для вывода на экран списка всех глобаль­ных переменных. Добавьте еще один конструктор defglobal, объявляющий переменные вещественного и текстового типа, а также переменную со зна­чением типа symbol.

Пример 7.4. Глобальные переменные различных типов

(defglobal

?*d*=7.8

?*e*=”string”

?*f*= symbol

)

Выполните после этого команду (list-defglobals), а также команду (ppdefglobal q), которая выведет на экран определение конкретной пере­менной. Результат описанных действий должен соответствовать рис. 7.1.

Помимо приведенных выше команд, Windows-версия содержит два визуальных инструмента для контроля количества и состояния созданных глобальных переменных. Первый из этих инструментов — Globals Window (окно глобальных переменных), изображен на рис. 7.2. Для того чтобы сделать окно глобальных переменных видимым, используйте пункт Globals Window меню Window. Этот инструмент позволяет следить за изменением списка глобальных переменных, определенных в системе, например при трассиров­ке или отладки программы.

 Рис. 7.1. Результат выполнения команд list-defglobals -90

Рис. 7.1. Результат выполнения команд list-defglobals и ppdefglobal

Рис. 7.2. Окно глобальных переменных

Другой инструмент, предназначенный для работы с глобальными перемен­ными, называется Defglobal Manager (Менеджер глобальных переменных).Этот инструмент доступен в меню Browse, пункт Defglobal Manager. Его внешний вид представлен на рис. 7.3. Обратите внимание, что он выводит список глобальных переменных в алфавитном порядке. Общее количество переменных отображается в заголовке окна Defglobal Manager — 7 Items. С помощью этого инструмента можно просматривать определение глобальной переменной или удалять ее из системы. Если вы не хотите использовать менеджер глобальных переменных, то удалить созданные ранее глобальные переменные можно с помощью команды undefglobal.

 Рис. 7.З. Окно менеджера глобальных переменных-92

Рис. 7.З. Окно менеджера глобальных переменных

При выполнении команды reset все глобальные переменные получают на­чальные значения, определенные в конструкторе. Такое поведение системы можно изменить, для этого в диалоговом окне Execution Options сбросьте флажок Reset Global Variables.

Вы можете установить режим просмотра изменений значений глобальных переменных. Для этого установите флажок Globals в диалоговом окне Watch Options, как показано на рис. 7.4.

В этом случае, например при выполнении команды reset, вы увидите результат, приведенный на рис. 7.5.

Для полноценной работы с глобальными переменными необходимо рассмотреть еще одну важную функцию — bind. Эта функция позволяет уста­навливать переменным новые значения:

Определение 7.2. Синтаксис функции bind

(bind <имя-переменной> <выражение>*)

Рис. 7.4. Установка режима просмотра изменения глобальных переменных

 Рис. 7.5. Режим просмотра -94

Рис. 7.5. Режим просмотра изменения глобальных переменных

Параметр выражения является необязательным. Если он не задан, то пере­менной будет установлено начальное значение, заданное в конструкторе defglobal. В случае если выражение было задано, то его значение будет вы­числено и результат присвоен переменной. Если было задано несколько вы­ражений, все они будут вычислены, из их результатов будет составлено со­ставное поле, которое будет присвоено глобальной переменной.

Функция bind возвращает значение false в случае, если переменной по какой-то причине не было присвоено никакого значения. В противном случае функция возвращает значение, присвоенное переменной.

Поскольку переменные в CLIPS слабо типизированы, типы значений, при­сваиваемые одной и той же переменной, в разные моменты времени могут не совпадать.

В качестве примера попробуйте присвоить переменной х следующие значе­ния: (+ 5 10),

(create$ abcd), три отдельных выражения (с), (b) и (а), а так же не присваивать переменной вообще никакого выражения. Результаты описанных действий приведены на рис. 7.6.

 Рис. 7.6. Изменение типа глобальной переменной -95

Рис. 7.6. Изменение типа глобальной переменной

Обратите внимание на то, что глобальная переменная х в нашем примере постоянно меняла тип своего значения.

ГЛАВА 8. Функции

Как уже отмечалось, CLIPS поддерживает не только эвристическую парадигму представления знаний (в виде правил), но и процедурную парадигму, используемую в большинстве языков программирования, таких, например, как Pascal или С. Функции в CLIPS являются последовательностью дейст­вий с заданным именем, возвращающей некоторое значение или выпол­няющей различные полезные действия (например, вывод информации на экран). Как уже упоминалось в гл. 4, в CLIPS существуют внутренние и внешние функции. Внутренние функции реализованы средой CLIPS, по­этому их можно использовать в любой момент. Описание внутренних функ­ций приведено в гл. 15. Внешние функции — это функции, написанные пользователем. Внешние функции можно создавать как с помощью среды CLIPS, так и на любых других языках программирования, а затем подклю­чать готовые, откомпилированные исполнимые модули к CLIPS. Однако эта тема выходит за рамки данной книги. Подробную информацию о соз­дании внешних функций можно найти в книге "CLIPS Reference Manual, Volume II, Advanced Programming Guide". Для создания новых функций в CLIPS используется конструктор deffunction, описанный далее в этой главе.

8.1. Конструктор deffunction и способы работы с внешними функциями

Конструктор deffunction позволяет пользователю создавать новые функции непосредственно в среде CLIPS. Способ вызова функций, определенных пользователем, эквивалентен способу вызова внутренних функций CLIPS. Вызов функции осуществляется по имени, заданному пользователю. За именем функции следует список необходимых аргументов, отделенный од­ним или большим числом пробелов. Вызов функции вместе со списком аргументов должен заключаться в скобки. Последовательность действий оп­ределенной с помощью конструктора deffunction функции исполняется ин­терпретатором CLIPS (в отличие от функций, созданных на других языках программирования, которые должны иметь уже готовый исполнимый код).

Синтаксис конструктора deffunction включает в себя 5 элементов:

  • имя функции;
  • необязательные комментарии;
  • список из нуля или более параметров;
  • необязательный символ групповых параметров для указания того, что функция может иметь переменное число аргументов;
  • последовательность действий или выражений, которые будут выполнены (вычислены) по порядку в момент вызова функции.

Определение 8.1. Синтаксис конструктора deffunction

(deffunction <имя-функции>

[<комментарии>]

<обязательные-параметры>

[<групповой-параметр>]

<действия>)

<обязательные-параметры> ::= <выражение-простое-поле>
<групповой-параметр> ::= <выражение-составное-поле>

Функция, создаваемая с помощью конструктора deffunction, должна иметь уникальное имя, не совпадающее с именами других внешних и внутренних функций. Функция, созданная с помощью deffunction, не может быть пере­гружена (см. гл. 10). Конструктор deffunction должен быть объявлен до пер­вого использования создаваемой им функции. Исключения составляют только рекурсивные функции.

В зависимости от того, задан ли групповой параметр, функция, созданная конструктором, может принимать точное число параметров или число па­раметров не меньшее, чем некоторое заданное. Обязательные параметры определяют минимальное число аргументов, которое должно быть передано функции при ее вызове. В действиях функции можно ссылаться на каждый из этих параметров как на обычные переменные, содержащие простые зна­чения. Если был задан групповой параметр, то функция может принимать любое количество аргументов большее или равное минимальному числу. Если групповой параметр не задан, то функция может принимать число ар­гументов точно равное числу обязательных параметров. Все аргументы функции, которые не соответствуют обязательным параметрам, группируются в одно значение составного поля. Ссылаться на это значение можно, используя символ группового параметра. Для работы с групповым парамет­ром могут использоваться стандартные функции CLIPS, предназначенные для работы с составными полями (см. гл. 15), такие как length и nth. Определение функции может содержать только один групповой параметр.

Приведенный пример 8.1 демонстрирует описанные выше возможности работы с групповыми параметрами.

Пример 8.1. Использование группового параметра

(deffunction print-args (?a ?b $?c)

(printout t ?a “ “ ?b “ and “ (length ?c) “ extras: “ ?c

crlf))

(print-args 1 2)

(print-args a b c d)

(print-args a)

В данном примере с помощью конструктора deffunction определяется функция print-args, которая принимает два обязательных параметра: ?а и ?b, и имеет групповой параметр $?с. Функция выводит на экран свои обязательные параметры, а также число полей в составном параметре и его содержимое. Результат выполнения данного примера приведен на рис. 8.1.

 Рис. 8.1. Результат работы функции print-args Обратите -96

Рис. 8.1. Результат работы функции print-args

Обратите внимание, что вызов функции с числом параметров, меньшим минимального, приводит к сообщению об ошибке.

При вызове функции интерпретатор CLIPS последовательно выполняет действия в порядке, заданном конструктором. Функция возвращает значе­ние, равное значению, которое вернуло последнее действие или вычислен­ное выражение. Если последнее действие не вернуло никакого результата, то выполняемая функция также не вернет результата (как в приведенном выше примере). Если функция не выполняет никаких действий, то возвра­щенное значение равно false. В случае возникновения ошибки при выпол­нении очередного действия выполнение функции будет прервано и возвра­щенным значением также будет false.

Функции могут быть само- и взаимно рекурсивными. Саморекурсивная функция просто вызывает сама себя из списка своих собственных действий. В качестве примера можно привести функцию, вычисляющую факториал.

Пример 8.2. Использование рекурсии для вычисления факториала

(deffunction factorial (?a)

(if (or (not (integerp ?a)) (< ?a 0)) then

(printout t "Factorial Error!" crlf)

else

(if (= ?a 0) then

1

else

( * ?a (factorial (- ?a 1))))))

Взаимная рекурсия между двумя функциями требует предварительного объ­явления одной из этих функций. Для предварительного объявления функ­ции в CLIPS используется конструктор deffunction с пустым списком дей­ствий. В следующем примере функция foo предварительно объявлена и та­ким образом может быть вызвана из функции bar. Окончательная реализация функции foo выполнена конструктором после объявления функции bar.

Пример 8.3. Создание взаимно рекурсивных функций

(deffunction foo ())

(deffunction bar ()

(foo))

(deffunction foo ()

(bar) )

Внимательно следите за рекурсивными вызовами функций, слишком боль­шой уровень рекурсии может привести к переполнению стека памяти. Например, приведенный выше пример с функциями bar и foo приводит к результату, представленному на рис. 8.2, и аварийному завершению CLIPS.

 Рис. 8.2. Переполнение стека Обратите внимание, что на -97

Рис. 8.2. Переполнение стека

Обратите внимание, что на рис. 8.2 в главном окне CLIPS выводится информация о запуске каждой функции. Для установки этого режима восполь­зуйтесь диалоговым окном Watch Options. Для этого откройте диалоговое окно, выбрав пункт Watch из меню Execution, и установите флажок Deffunctions, как показано на рис. 8.3. Этот режим также позволяет просматривать аргументы, которые использовались при каждом конкретном вызове функции.

Рис. 8.3. Установка режима просмотра вызова функций

Так же как и для правил, предопределенных фактов, глобальных перемен­ных Windows-версия CLIPS предоставляет специальный инструмент для ра­боты с функциями — Deffunction Manager (Менеджер функций). Для запус­ка этого инструмента воспользуйтесь пунктом Deffunction Manager из меню Browse. В случае если в CLIPS не определена ни одна функция, данный пункт меню недоступен. Менеджер функций, отображающий функции, соз­данные нами в этой главе, изображен на рис. 8.4.





Рис. 8.4. Окно менеджера функций

Общее количество внешних функций отображается в заголовке окна менеджера — Deffunction Manager — 4 Items. С его помощью можно распечатать определение функции, удалить ее, а также установить режим просмотра вызова для отдельно выбранной функции.

ГЛАВА 9.Разработка экспертной системы AutoExpert

Мы рассмотрели довольно много возможностей среды CLIPS, а также способов их использования. Однако, как неоднократно говорилось ранее, среда CLIPS предназначается для создания экспертных систем, а по приведенным примерам из предыдущих глав не вполне очевидно, как именно с помощью CLIPS создавать экспертные системы. Данная глава восполнит этот пробел. В ней мы создадим полноценную, хотя и не очень сложную экспертную систему, проводящую диагностику неисправности мотора автомобиля по внешним признакам. Помимо этого наша диагностическая экспертная сис­тема должна также предоставлять пользователю соответствующие рекомен­дации по устранению неисправности.

Следует упомянуть, что реализация данной экспертной системы будет активно использовать управляющие команды CLIPS, такие как if-then-else и while. Однако, если вы знакомы с каким-нибудь процедурным языком программирования, вы без труда поймете приведенный ниже код. Подробно эти команды будут рассмотрены в гл. 16.

9.1. Исходные данные

Разработку любой экспертной системы следует начинать с выделения основных сущностей, имеющих значение при решении конкретной задачи и законов, скорее всего эмпирических, действующих над этими сущностями. В подавляющем большинстве случаев эту информацию получают при помощи эксперта, человека хорошо знающего и давно работающего в этой области. Методы получения информации от эксперта и ее обработка выходят за рамки настоящей книги, но эта тема не плохо освещена в других работах. Для решения нашей конкретной задачи предположим, что в результа­те бесед с экспертом в области установления неисправностей и ремонта автомобилей были установлены следующие эмпирические правила:

  1. Двигатель обычно находится в одном из 3-х состояний: он может работать нормально, работать неудовлетворительно или не заводиться.
  2. Если двигатель работает нормально, то это означает, что он нормально вращается, система зажигания и аккумулятор находятся в норме и никакого ремонта не требуется.
  3. Если двигатель запускается, но работает ненормально, то это говорит, по крайней мере, о том, что аккумулятор в порядке.
  4. Если двигатель не запускается, то нужно узнать, пытается ли он вращаться. Если двигатель вращается, но при этом не заводится, то это может говорить о наличии плохой искры в системе зажигания. Если двигатель даже не пытается заводиться, то это говорит о том, что искры нет в принципе.
  5. Если двигатель не заводится, но вращается, нужно проверить наличие топлива. Если топлива нет — то, скорей всего, для ремонта машины нужно просто заправиться.
  6. Если двигатель не заводится, нужно также проверить, заряжен ли аккумулятор, если нет, то его следует зарядить.
  7. Если двигатель не заводится, и существует вероятность плохой искры в системе зажигания, то необходимо проверить контакты. Контакты могут быть в одном из трех состояний — чистые, опаленные и грязные, в случае опаленных контактов их необходимо заменить, в случае если контакты грязные, их достаточно просто почистить.
  8. Если двигатель не заводится, искры нет и аккумулятор заряжен, то нужно проверить катушку зажигания на электрическую проводимость. В случае если ток не проходит через катушку, то ее необходимо заменить. Если катушка зажигания в порядке, значит необходимо заменить распределительные провода.
  9. Если двигатель запускается, но при этом ведет себя инертно, не сразу реагирует на подачу топлива, то необходимо прочистить топливную систему.
  1. Если двигатель запускается, но происходят перебои с зажиганием, то это говорит о наличии плохой искры в системе зажигания, для устранения данной неисправности необходимо отрегулировать зазоры между контактами.
  2. Если двигатель запускается и стучит, то необходимо отрегулировать зажигание.
  3. Если двигатель запускается, но не развивает нормальной мощности, то это может говорить об опаленных или загрязненных контактах (см. правило 7).
  4. Возможны ситуации, когда состояние двигателя нельзя описать приведенными выше факторами и машине может потребоваться более детальный анализ состояния.

Имея эти данные, приступим к решению поставленной задачи.

9.2. Сущности

Из приведенных выше правил можно выделить следующие сущности, имеющие значение при решении задачи.

  • Во-первых, для решения задачи экспертной системе необходимо знать, в каком состоянии находится машина, диагностика которой производится. Эксперт выделил три возможных состояния: нормальная работа двигателя, двигатель работает неудовлетворительно, не заводится (см. правило 1).
  • Во-вторых, большинство приведенных правил помимо состояния двигателя в целом используют понятие состояния вращения двигателя. Согласно этим правилам двигатель может находиться в одном из двух состояний, которые определяются в зависимости от того, способен он вращаться (работать) или нет.
  • В-третьих, в некоторых правилах (см. правила 4, 7, 8, 10) используется понятие состояния системы зажигания. Система зажигания может быть в одном из трех состояний: нормальное состояние, не регулярная работа и нерабочее состояние.
  • В-четвертых, в правилах 6 и 8 используется понятие — состояние акку­мулятора. Аккумулятор может быть в одном из двух состояний: заряженным и разряженным.

Для того чтобы решения данной задачи было более наглядным, мы не будем использовать шаблоны. Для представления в CLIPS всех перечисленных выше данных воспользуемся упорядоченными фактами CLIPS. Исходя из приведенного выше списка, нам могут понадобиться факты, приведенные в примере 9.1.

Пример 9.1. Факты, описывающие состояние автомобиля и его узлов

; Группа фактов, описывающая состояние машины

working-state engine normal ;нормальная работа

working-state engine unsatisfactory ;неудовлетворительная работа

working-state engine does-not-start ;не заводится

; Группа фактов, описывающая состояние двигателя

rotation-state engine rotates ;двигатель вращается

rotation-state engine does-not-rotate ;двигатель не вращается

; Группа фактов, описывающая состояние системы зажигания

spark-state engine normal ;зажигание в порядке

spark-state engine irregular-spark ;искра нерегулярна

spark-state engine does-not-spark ;искры нет

; Группа фактов, описывающая состояние системы питания

charge-state battery charged ;аккумулятор заряжен

charge-state battery dead ;аккумулятор разряжен

Обратите внимание, что факты, входящие в одну группу (содержат одинаковое первое поле), являются взаимоисключающими, т.е. наличие в системе сразу двух фактов из одной группы лишено смысла.

Их постановки задачи следует, что наша экспертная система должна предоставлять пользователю рекомендации, позволяющие устранить найденную неисправность. Из приведенных выше правил можно выделить следующие рекомендации: добавить топливо (правило 5); зарядить аккумулятор (правило 6); заменить или почистить контакты (правило 7 или правило 12); заменить катушку зажигания или распределительные провода (правило 8); прочистить топливную систему (правило 9); отрегулировать зазоры между контактами (правило 10); отрегулировать зажигание (правило 11). Необходимо помнить также о двух крайних случаях: ремонт не требуется в принципе; экспертная система не смогла поставить диагноз. Для представления всех этих рекомендаций будем использовать факты, представленные в примере 9.2.

Пример 9.2. Факты, описывающие рекомендации по ремонту автомобиля

repair “Add gas.”

repair “Charge the battery.”

repair “Replace the points.”

repair “Clean the points.”

repair “Replace the ignition coil.”

repair “Repair the distributor lead wire.”

repair “Clean the fuel line.”

repair “Point gap adjustment.”

repair “No repair needed.”

repair “Take your car to a mechanic.”

Все приведенные факты, использующиеся для предоставления пользователю рекомендаций по ремонту, во втором поле содержат текстовое значение с рекомендацией по ремонту.

Обратите внимание, что одни и те же рекомендации могут выводиться как правилом 7, так и правилом 12. Однако состояние машины при этой поломке отличается. Для того чтобы иметь возможность обрабатывать эту ситуацию с помощью одного правила CLIPS, введем еще два дополнительных факта.

Пример 9.3. Факты, описывающие мощность работы двигателя

symptom engine low-output ;низкая мощность

symptom engine not-low-output ;нормальная мощность

Кроме описанных выше фактов системе могут понадобиться факты, описы­вающие проявления неисправности. Однако в нашей версии экспертной системы таких фактов не будет. О том, как обойтись без них, вы узнаете в следующем разделе. Приведенный выше список фактов вполне достаточен для решения поставленной задачи. Приступим к следующему этапу — сбору исходной информации для диагностики.

9.3. Сбор информации

Как упоминалось выше, для работы нашей системы можно заставить поль­зователя вручную вводить факты, описывающие проявление возникшей не­исправности. Однако такой метод имеет ряд серьезных недостатков: пользо­ватель может забыть о каких-нибудь существенных деталях или, наоборот, указать слишком много информации, что может помешать нормальной работе системы. Кроме того, факты, описывающие проявление неисправно­сти, должны были бы иметь строго определенный формат, и система не смогла бы их обработать в случае ошибки со стороны пользователя.

В нашей экспертной системе мы реализуем правила диагностики, которые в зависимости от той или иной ситуации будут задавать пользователю необхо­димые вопросы и получать ответ в строго заданной форме. Дальнейшая ди­агностика будет производиться с учетом предыдущих ответов на вопросы, заданные пользователю. Эти ответы будут формировать описание текущей ситуации с помощью фактов, приведенных выше.

Для реализации подобной архитектуры будет необходимо реализовать функцию, задающую пользователю произвольный вопрос и получающую ответ из заданного набора корректных ответов. В примере 9.4 приведена одна из возможных реализаций такой функции.

Пример 9.4. Функция ask-question

(deffunction ask-question (?question $?allowed-values)

(printout t ?question)

(bind ?answer (read))

(if (lexemep ?answer)

then

(bind ?answer (lowcase ?answer)))

(while (not (member ?answer ?allowed-values)} do

(printout t ?question)

(bind ?answer (read))

(if (lexemep ?answer)

then

(bind ?answer (lowcase ?answer))))

?answer

)

Функция принимает два аргумента: простую переменную question, которая содержит текст вопроса, и составную переменную allowed-values с набором допустимых ответов. Сразу после своего вызова функция выводит на экран соответствующий вопрос и читает ответ пользователя в переменную answer. Если переменная answer содержит текст, то она будет принудительно приве­дена к прописному алфавиту. После этого функция проверяет, является ли полученный ответ одним из заданных корректных ответов. Если нет, то процесс повторится до получения корректного ответа, иначе функция вер­нет ответ, введенный пользователем.

Будет также очень полезно определить функцию, задающую пользователю вопрос и допускающий ответ в виде да/нет, т. к. это один из самых распро­страненных типов вопросов. С учетом реализации функции ask-question эта функция примет вид, представленный в примере 9.5.

Пример 9.5. Функция yes-or-no-p

(deffunction yes-or-no-p (?question)

(bind ?response (ask-question ?guestion yes no у n))

(if (or (eq ?response yes) (eq ?response y))

then

TRUE

else

FALSE)

)

Функция yes-or-no-p вызывает функцию ask-question с постоянным набо­ром допустимых ответов: yes, no, у и n. В случае если пользователь ввел от­вет yes или у, функция возвращает значение true, иначе — false. Обратите внимание, что поскольку функция yes-or-no-p использует функцию ask-question, то она должна быть определена после нее.

9.4. Диагностические правила

Для упрощения реализации нашей экспертной системы введем следующее ограничение: за один запуск система может предоставить пользователю только одну рекомендацию по исправлению неисправности. В случае если в машине несколько неисправностей, то систему нужно будет последова­тельно вызывать несколько раз, удаляя обнаруженную на каждом новом ша­ге неисправность.

Таким образом, одним из образцов всех диагностических правил будет (not (repair ? )), гарантирующий, что диагноз еще не постав­лен.

Первым реализуем правило, определяющее общее состояние двигателя (см. правило 1).

Пример 9.6. Правило determine-engine-state

(defrule determine-engine-state ""

(not (working-state engine ?) )

(not (repair ?) )

=>

(if (yes-or-no-p "Does the engine start (yes/no)? ")

then

(if (yes-or-no-p "Does the engine run normally (yes/no)? ")

then

(assert (working-state engine normal) )

else

(assert (working-state engine unsatisfactory) ) )

else

(assert (working-state engine does-not-start) ) )

)

Условный элемент (not (working-state engine ?) ) гарантирует, что общее состояние двигателя еще не определено. Если это так, то пользователю за­даются соответствующие вопросы и в систему добавляется факт, описы­вающий текущее общее состояние двигателя.

Теперь реализуем правило, определяющее, пытается ли двигатель вращать­ся, в случае если он не заводится.

Пример 9.7. Правило determine-rotation-state

(defrule determine-rotation-state ""

(working-state engine does-not-start)

(not (rotation-state engine ?) )

(not (repair ?) )

=>

(if (yes-or-no-p "Does the engine rotate (yes/no) ? ")

then

(assert (rotation-state engine rotates) )

(assert (spark-state engine irregular-spark) )

else

(assert (rotation-state engine does-not-rotate) )

(assert (spark-state engine does-not-spark) ) )

Это правило выполняется, в случае если общее состояние двигателя опреде­лено и известно, что он не заводится. Кроме того, условный элемент (not (rotation-state engine ?) ) гарантирует, что это правило еще не вызыва­лось. В зависимости от того или иного ответа пользователя правило добав­ляет соответствующий набор фактов (см. правило 4).

Далее реализуем довольно простые правила 5 и 6. Выполняемые ими дейст­вия вы поймете без дополнительных комментариев.

Пример 9.8. Правила determine-gas-level и determine-battery-state

(defrule determine-gas-level ""

(working-state engine does-not-start)

(rotation-state engine rotates)

(not (repair ?))

=>

(if (not (yes-or-no-p "Does the tank have any gas in it (yes/no)? "))

then

(assert (repair "Add gas.")))

)

(defrule determine-battery-state ""

(rotation-state engine does-not-rotate)

(not (charge-state battery ?))

(not (repair ?))

=>

(if (yes-or-no-p "Is the battery charged (yes/no)? ")

then

(assert (charge-state battery charged))

else

(assert, (repair "Charge the battery."))

(assert (charge-state battery dead)))

)

Обратите внимание, что правило determine-battery-state, помимо опреде­ления возможной неисправности, также применяется для добавления в сис­тему факта, описывающего текущее состояние аккумулятора, который мо­жет быть использован другими правилами.

При реализации правила 7 необходимо обратить внимание на то, что реко­мендации, предоставляемые этим правилом, подходят для двух в корне от­личающихся ситуаций. Во-первых, в случае если двигатель не заводится, и существует вероятность плохой искры в системе зажигания (правило 7). Во-вторых, в случае если двигатель запускается, но не развивает нормальной мощности (правило 12). Поэтому выполним реализацию этих правил так, как представлено в примере 9.9.

Пример 9.9. Правила determine-low-output и determine-point-surface-state

(defrule determine-low-output ""

(working-state engine unsatisfactory)

(not (symptom engine low-output | not-low-output))

(not (repair ?))

=>

(if (yes-or-no-p "Is the output of the engine low (yes/no)? ")

then

(assert (symptom engine low-output))

else

(assert (symptom engine not-low-output)))

)

(defrule determine-point-surface-state ""

(or (and (working-state engine does-not-start)

(spark-state engine irregular-spark))

(symptom engine low-output))

(not (repair ?))

=>

(bind ?response (ask-question "What is the surface state of the

points (normal /burned /contaminated)?"

normal burned contaminated))

(if (eq ?response burned)

then

(assert (repair "Replace the points."))

else

(if (eq ?response contaminated)

then

(assert (repair "Clean the points."))))

)

Правило determine-low-output определяет, имеет ли место низкая мощность двигателя или нет. Правило determine-point-surface-state адекватно реаги­рует на условия, заданные в правилах 7 и 12. Обратите внимание на исполь­зование условных элементов or и and, которые обеспечивают одинаковое поведение правила в двух абсолютно разных ситуациях. Кроме того, прави­ло determine-point-surface-state отличается от приведенных ранее тем, что непосредственно использует функцию ask-question, вместо yes-or-no-p, т. к. в данный момент пользователю задается вопрос, подразумевающий три варианта ответа.

Реализация оставшихся диагностических правил (8—11) также не должна вызвать у вас затруднений.

Пример 9.10. Оставшиеся диагностические правила

(defrule determine-conductivity-test ""

(working-state engine does-not-start)

(spark-state engine does-not-spark)

(charge-state battery charged)

(not (repair ?))

=>

(if (yes-or-no-p "Is the conductivity test for the ignition coil positive(yes/no)? ")

then

(assert (repair "Repair the distributor lead wire."))

else

(assert (repair "Replace the ignition coil.")))

)

(defrule determine-sluggishness ""

(working-state engine unsatisfactory)

(not (repair ?))

=>

(if (yes-or-no-p "Is the engine sluggish (yes/no)? ") then

(assert (repair "Clean the fuel line."))) ) (defrule determine-misfiring ""

(working-state engine unsatisfactory) (not (repair ?))

(if (yes-or-no-p "Does the engine misfire (yes/no)? ")

then

(assert (repair "Point gap adjustment."))

(assert (spark-state engine irregular-spark)))

)

(defrule determine-knocking ""

(working-state engine unsatisfactory)

(not (repair ?))

=>

(if (yes-or-no-p "Does the engine knock (yes/no)? ")

then

(assert (repair "Timing adjustment.")))

)

9.5. Последние штрихи

Внимательно взглянув на список правил, мы увидим, что некоторые прави­ла (2, 3 и 13) остались до сих пор не реализованными.

В качестве реализации правила 13 мы будем использовать правило no-repairs, приведенное в примере 9.11.

Пример 9.11. Правило no-repairs

(defrule no-repairs ""

(declare (salience -10) )

(not (repair ?) )

= >

(assert (repair "Take your car to a mechanic."))

)

Обратите внимание на использование приоритета при определении этого правила. Все правила, приведенные в предыдущем разделе, определялись с приоритетом, по умолчанию равным нулю. Использование для правила no-repairs приоритета, равного —10, гарантирует, что правило не будет вы­полнено, пока в плане решения задачи находится, по крайней мере, одно из диагностических правил. Если все активированные диагностические прави­ла отработали и ни одно из них не смогло подобрать подходящую рекомен­дацию по устранению неисправности, то CLIPS запустит правило no-repairs, которое просто порекомендует пользователю обратиться к более опытному механику.

Реализация правил 2 и 3 приведена ниже.

Пример 9.12. Правила normal-engine-state-conclusions и unsatisfactory-engine-state-conclusions

(defrule normal-engine-state-conclusions ""

(declare (salience 10) )

(working-state engine normal)

=>

(assert (repair "No repair needed."))

(assert (spark-state engine normal))

(assert (charge-state battery charged))

(assert (rotation-state engine rotates))

)

(defrule unsatisfactory-engine-state-conclusions ""

(declare (salience 10))

(working-state engine unsatisfactory)

=>

(assert (charge-state battery charged))

(assert (rotation-state engine rotates))

)

В этих правилах, наоборот, используется более высокий приоритет, что га­рантирует их выполнение до выполнения любого диагностического правила (естественно, только в случае удовлетворения условий, заданных в левой части правил). Это избавит нашу систему от лишних проверок, а пользова­теля от лишних вопросов.

Наша экспертная система фактически готова к работе. Единственное, чего ей не хватает, — это метода вывода итоговой информации и правила, сооб­щающего пользователю о начале работы. Ниже приведена реализация этих правил.

Пример 9.13. Правила system-banner и print-repair

(defrule system-banner ""

(declare (salience 10))

=>

(printout t crlf crlf)

(printout t "**********************************" crlf)

(printout t "* The Engine Diagnosis Expert System *" crlf)

(printout t "**********************************" crlf)

(printout t crlf crlf)

)

(defrule print-repair ""

(declare (salience 10))

(repair ?item)

=>

(printout t crlf crlf)

(printout t "Suggested Repair:")

(printout t crlf crlf)

(format t " %s%n%n%n" ?item)

)

9.6. Листинг программы

В данном разделе приведен полный листинг программы с подробными комментариями. Если у вас еще не сложилась целостная картина о том, как работает наша экспертная система, из каких частей она состоит, вниматель­но изучите приведенный ниже код.

Пример 9.14. Полный листинг программы

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

; Пример экспертной системы на языке CLIPS

;

; Приведенная ниже экспертная система способна

; диагностировать некоторые неисправности автомобиля и

; предоставлять пользователю рекомендации по устранению

; неисправности.

;

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

; Вспомогательные функции

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - -

; Функция ask-question задает пользователю вопрос, полученный

; в переменной ?question, и получает от пользователя ответ,

; принадлежащий списку допустимых ответов, заданному в $?allowed-values

(deffunction ask-question (?question $?allowed-values)

(printout t ?question)

(bind ?answer (read))

(if (lexemep ?answer)

then

(bind ?answer (lowcase ?answer)))

(while (not (member ?answer ?allowed-values)) do

(printout t ?question)

(bind ?answer (read))

(if (lexemep ?answer)

then

(bind ?answer (lowcase ?answer))))

?answer

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Функция yes-or-no-p задает пользователю вопрос, полученный

; в переменной ?question, и получает от пользователя ответ yes(у) или

; по(n). В случае положительного ответа функция возвращает значение TRUE,

; иначе — FALSE

(deffunction yes-or-no-p (?question)

(bind ?response (ask-question ?question yes no у n))

(if (or (eq ?response yes) (eq ?response y))

then

TRUE

else

FALSE)

)

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

; Диагностические правила

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-engine-state определяет текущее состояние двигателя

; машины по ответам, получаемым от пользователя. Двигатель может

; находиться в одном из трех состояний: работать нормально

; (working-state engine normal), работать неудовлетворительно

; (working-state engine unsatisfactory) и не заводиться

; (working-state engine ;does-not-start) (см. правило 1).

(defrule determine-engine-state ""

(not (working-state engine ?) )

(not (repair ?) )

=>

(if (yes-or-no-p "Does the engine start (yes/no) ? ")

then

(if (yes-or-no-p "Does the engine run normally (yes/no)? ")

then

(assert (working-state engine normal) )

else

(assert (working-state engine unsatisfactory) ) )

else

(assert (working-state engine does-not-start) ) )

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-rotation-state определяет состояние вращения двигателя по ответу,

; получаемому от пользователя. Двигатель может вращаться (rotation-state engine rotates)

; или не вращаться (spark-state engine does-not-spark) (см. правило 4).

; Кроме того, правило делает предположение о наличии плохой искры

; или ее отсутствии в системе зажигания

(defrule determine-rotation-state ""

(working-state engine does-not-start)

(not (rotation-state engine ?))

(not (repair ?))

=>

(if (yes-or-no-p "Does the engine rotate (yes/no)? ")

then

; Двигатель вращается

(assert (rotation-state engine rotates))

; Плохая искра

(assert (spark-state engine irregular-spark))

else

; Двигатель не вращается

(assert (rotation-state engine does-not-rotate))

; Нет искры

(assert (spark-state engine does-not-spark)))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-gas-level по ответу пользователя определяет

; наличие топлива в баке. В случае если топлива нет, пользователю

; выдается рекомендация по ремонту — машину необходимо заправить

; (repair "Add gas.") (см. правило 5). При появлении соответствующей

; рекомендации выполнение диагностических правил прекращается.

(defrule determine-gas-level ""

(working-state engine does-not-start)

(rotation-state engine rotates)

(not (repair ?) )

=>

(if (not (yes-or-no-p "Does the tank have any gas in it (yes/no) ? ") )

then

; Машину необходимо заправить

(assert (repair "Add gas.")))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-battery-state по ответу пользователя определяет,

; заряжен ли аккумулятор. В случае если это не так, пользователю

; выдается рекомендация по ремонту — Зарядите аккумулятор (repair

; "Charge the battery.") (см. правило 6).

; Кроме того, правило добавляет факт, описывающий состояние аккумулятора.

; Выполнение диагностических правил прекращается

(defrule determine-battery-state ""

(rotation-state engine does-not-rotate}

; Состояние аккумулятора еще не определено

(not (charge-state battery ?))

(not (repair ?))

=>

(if (yes-or-no-p "Is the battery charged (yes/no)? ")

then

; Аккумулятор заряжен

(assert (charge-state battery charged))

else

; Зарядите аккумулятор

(assert (repair "Charge the battery."))

; Аккумулятор разряжен

(assert (charge-state battery dead)))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-low-output определяет, развивает ли двигатель

; нормальную выходную мощность или нет и добавляет в систему факт,

; описывающий эту характеристику (см. правило 12).

(defrule determine-low-output ""

(working-state engine unsatisfactory)

; Мощность работы двигателя еще не определена

(not (symptom engine low-output | not-low-output))

(not (repair ?))

=>

(if (yes-or-no-p "Is the output of the engine low (yes/no)? ")

then

; Низкая выходная мощность двигателя

(assert (symptom engine low-output))

else

; Нормальная выходная мощность двигателя

(assert (symptom engine not-low-output)))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-point-surface-state определяет по ответу

; пользователя состояние контактов (см. правила 7, 12). Контакты могут

; находиться в одном из трех состояний: чистые, опаленные и

; загрязненные. В двух последних случаях пользователю выдаются

; соответствующие рекомендации.

; Выполнение диагностических правил прекращается.

(defrule determine-point-surfасе-state ""

(or (and (working-state engine does-not-start);не заводится

(spark-state engine irregular-spark));и плохая искра

(symptom engine low-output));или низкая мощность

(not (repair ?))

=>

(bind ?response (ask-question "What is the surface state of the

points (normal /burned /contaminated)?"

normal burned contaminated))

(if (eq ?response burned)

then

; Контакты опалены — замените контакты

(assert (repair "Replace the points."))

else

(if (eq ?response contaminated)

then

; Контакты загрязнены - почистите их

(assert (repair "Clean the points."))))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-conductivity-test по ответу пользователя определяет,

; пропускает ли ток катушка зажигания. Если нет, то ее следует заменить.

; Если пропускает, то причина неисправности — распределительные провода.

; Для нормальной работы правила необходимо убедиться, что аккумулятор

; заряжен и искры нет (см. правило 8)

; Выполнение диагностических правил прекращается.

(defrule determine-conductivity-test ""

(working-state engine does-not-start)

(spark-state engine does-not-spark) ;нет искры

(charge-state battery charged) ;аккумулятор заряжен

(not (repair ?) )

=>

(if (yes-or-no-p "Is the conductivity test for the ignition coil positive (yes/no) ? ")

then

; Замените распределительные провода

(assert (repair "Repair the distributor lead wire."))

else

; Замените катушку зажигания

(assert (repair "Replace the ignition coil.")))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-sluggishness спрашивает пользователя, не ведет ли

; себя машина инертно (не сразу реагирует на подачу топлива).

; Если такой факт обнаружен, то необходимо прочистить

; топливную систему (см. правило 9) и выполнение диагностических правил

; прекращается.

(defrule determine-sluggishness ""

(working-state engine unsatisfactory)

(not (repair ?))

=>

(if (yes-or-no-p "Is the engine sluggish (yes/no)? ")

then

; Прочистите систему подачи топлива

(assert (repair "Clean the fuel line.")))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-misfiring узнает — нет ли перебоев с зажиганием.

; Если это так, то необходимо отрегулировать зазоры между контактами

; (см. правило 10).

; Выполнение диагностических правил прекращается.

(defrule determine-misfiring ""

(working-state engine unsatisfactory)

(not (repair ?))

=>

(if (yes-or-no-p "Does the engine misfire (yes/no)? ")

then

; Отрегулируйте зазоры между контактами

(assert (repair "Point gap adjustment."))

; Плохая искра

(assert (spark-state engine irregular-spark)))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило determine-knocking узнает — не стучит ли двигатель.

; Если это так, то необходимо отрегулировать зажигание (см. правило 11).

; Выполнение диагностических правил прекращается.

(defrule determine-knocking ""

(working-state engine unsatisfactory)

(not (repair ?))

=>

(if (yes-or-no-p "Does the engine knock (yes/no)? ")

then

; Отрегулируйте положение зажигания

(assert (repair "Timing adjustment.")))

)

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

; Правила, определяющие состояние некоторых подсистем автомобиля

; по характерным состояниям двигателя

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило normal-engine-state-conclusions реализует правило 2

(defrule normal-engine-state-conclusions ""

(declare (salience 10))

; Если двигатель работает неудовлетворительно

(working-state engine normal)

=>

; то

(assert (repair "No repair needed.")) ; ремонт не нужен

(assert (spark-state engine normal)) ; зажигание в норме

(assert (charge-state battery charged)) ; аккумулятор заряжен

(assert (rotation-state engine rotates)) ; двигатель вращается

)

; Правило unsatisfactory-engine-state-conclusions реализует правило 3

(defrule unsatisfactory-engine-state-conclusions ""

(declare (salience 10))

; Если двигатель работает нормально

(working-state engine unsatisfactory)

=>

; то

(assert (charge-state battery charged)) ; аккумулятор заряжен

(assert (rotation-state engine rotates)) ; двигатель вращается

)

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ; Запуск и завершение

;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило no-repairs запускается в случае, если ни одно

; из диагностических правил не способно определить неисправность.

; Правило корректно прерывает выполнение экспертной системы и предлагает

; пройти более тщательную проверку (см. правило 13).

(defrule no-repairs ""

(declare (salience -10))

(not (repair ?))

=>

(assert (repair "Take your car to a mechanic."))

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило print-repair выводит на экран диагностическое сообщение

; по устранению найденной неисправности,

(defrule print-repair ""

(declare (salience 10))

(repair ?item)

=>

(printout t crlf crlf)

(printout t "Suggested Repair:")

(printout t crlf crlf)

(format t " %s%n%n%n" ?item)

)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Правило system-banner выводит на экран название экспертной системы

; при каждом новом запуске.

(defrule system-banner " каждом новом запуске."

(declare (salience 10) )

=>; каждом новом запуске.

(printout t crlf crlf)

(printout t "********************************************" crlf)

(printout t "* The Engine Diagnosis Expert System *" crlf)

(printout t "**********************************" crlf)

(printout t crlf crlf)

)

Помните, что среда CLIPS (по крайней мере, последняя версия 6.20) вос­принимает только символы английского алфавита. Все комментарии в при­веденном листинге даны на русском языке для наглядности, однако при вводе программы в таком виде CLIPS выведет сообщение об ошибке.

9.7. Запуск программы

Для запуска программы наберите приведенный в примере 9.14 листинг в каком-нибудь текстовом редакторе (лучше использовать встроенный ре­дактор CLIPS по причинам, упоминавшимся в гл. 6). Сохраните набранный файл, например, с именем auto.CLP.

После этого запустите CLIPS или, если он уже был у вас запущен, очистите его командой (clear). Загрузите созданный вами файл с помощью команды (load "auto.CLP"). Если файл был набран без ошибок, то вы должны уви­деть сообщения, представленные на рис. 9.1.

Рис. 9.1 демонстрирует успешную попытку загрузки файла конструкторов. Обратите внимание, что функция load вернула значение true. Если это нетак, значит, в синтаксисе определений функций или правил была допущена ошибка. Для загрузки вы также могли бы воспользоваться функцией load*. В этом случае на экран не выводилась бы информация, отражающая про­цесс загрузки.

 Рис. 9.1. Загрузка экспертной системы После удачной-100

Рис. 9.1. Загрузка экспертной системы

После удачной загрузки файла убедитесь, что все правила присутствуют в списке правил CLIPS, а функции — в списке функций. Легче всего это вы­полнить с помощью менеджеров правил и функций соответственно. Внеш­ний вид этих менеджеров показан на рис. 9.2 и 9.3.

Для того чтобы запустить нашу экспертную систему, достаточно выполнить команду reset, которая добавит факт initial-fact, необходимый для прави­ла system-banner, и команду run. После этого вы сразу увидите сообщение "The Engine Diagnosis Expert system", которое означает, что система начала работать, и получите серию вопросов, ответы на которые помогут эксперт­ной системе оценить текущее состояние вашей машины и подобрать соот­ветствующую рекомендацию по ремонту. Пример работы системы показан на рис. 9.4.

Обратите внимание, что если после завершения работы нашей экспертной системы в списке фактов CLIPS остаются факты, описывающие состояние автомобиля, их легко просмотреть с помощью команды Fact Window из ме­ню Window. Факты для нашего примера изображены на рис. 9.5.

 Рис. 9.2. Правила экспертной системы Рис. 9.3. -101

Рис. 9.2. Правила экспертной системы

Рис. 9.3. Функции экспертной системы

Для повторного запуска экспертной системы необходимо еще раз выпол­нить команды reset и run. Протестируйте экспертную систему, по-разному отвечая на ее вопросы. Чтобы лучше понять механизмы ее работы и логический механизм вывода CLIPS, перед запуском системы сделайте видимым окно фактов (Fact Window) и окно плана решения задачи (Agenda Window).

Приведенный в этой главе пример доступен в Интернете по адресу: www.ghg.net/clips/download/executables/examples/auto.clp.

 Рис. 9.4. Диалог с -103

Рис. 9.4. Диалог с экспертной системой

Рис. 9.5. Результаты работы экспертной системы

В данной главе был рассмотрен достаточно реальный пример построения экспертной системы в CLIPS. Он является наглядным подтверждением то­го, что с помощью фактов, функций и правил CLIPS можно построить вполне работоспособную систему. Однако CLIPS содержит и более сложные конструкции, такие как родовые функции, объекты и модули. С их по­мощью вы сможете строить еще более гибкие и мощные экспертные систе­мы. Изучением этих конструкций мы и займемся в следующей главе.

ЧАСТЬ IV. Дополнительные возможности CLIPS.

Глава 10. Родовые функции.

Глава 11. Объектно-ориентированный язык CLIPS.

Глава 12. Модули.

Глава 10. Ограничения.

Глава 10. Разработка экспертной системы CIOS.

ГЛАВА 10. Родовые функции

Помимо функций, описанных в гл. 8, CLIPS предоставляет еще один меха­низм, поддерживающий процедурную парадигму представления знаний, — родовые функции. Родовые функции подобны функциям, созданным с по­мощью конструктора deffunction. Они также могут использоваться для оп­ределения нового процедурного кода в CLIPS и могут быть вызваны как любые другие функции. Однако, в отличие от простых, родовые функции являются более мощным средством обработки данных, т. к. они способны выполнять различные наборы действий в зависимости от числа и типа по­лученных в данный момент аргументов. Родовые функции подобны пере­груженным операторам или функциям языка C++. Например, функция + может выполнять как операцию конкатенации строк, так и простое арифме­тическое сложение чисел. Родовые функции определяются с помощью кон­структоров defgeneric и defmethod, которые подробно будут описаны В данной главе. Родовые функции обычно состоят из нескольких компонентов, называемых методами. Каждый метод определяет последовательность дей­ствий, выполняющих обработку различных наборов аргументов. Родовая функция, которая имеет более одного метода, называется перегруженной.

Родовые функции могут содержать как системные методы, так и методы, определенные пользователем. Например, перегруженная функция + состоит из двух методов:

  • неявный метод, являющийся системной функцией, которая обрабатывает арифметическое сложение;
  • явный (определенный пользователем) обработчик сложения строк.

CLIPS не позволяет использовать функции, созданные с помощью конст­руктора deffunction, в качестве методов родовых функций. Конструкторы deffunction предоставляют возможность добавления в CLIPS новых функ­ций без использования концепции перегрузки. Родовая функция, имеющая только один метод, по своему поведению идентична функции, созданной с помощью конструктора deffunction.

В большинстве случаев методы родовых функций не вызываются напрямую, несмотря на то, что CLIPS предоставляет такую возможность с помощью функции call-specific-method (см. гл. 15). CLIPS самостоятельно распозна­ет вызов родовой функции и использует аргументы для поиска и запуска соответствующего метода. Этот процесс называется родовым связыванием.

10.1. Замечание относительно термина "метод"

Большинство объектно-ориентированных систем поддерживают процедур­ное поведение объектов через обработку сообщений (например, Smalltalk) или с помощью родовых функций (например, CLOS). Можно утверждать, что CLIPS поддерживает оба этих механизма, несмотря на то, что родовые функции и не являются составной часть языка COOL (объектно-ориентированный язык, поддерживаемый CLIPS) и могут использоваться без него. Родовые функции могут использовать классы в качестве аргументов своих методов, но они должны обеспечивать выдачу сообщений для мани­пуляции с объектами таких классов. (Изучением языка COOL мы займемся в следующей главе.)

Поддержка системой CLIPS обоих механизмов приводит к путанице в тер­минологии. В объектно-ориентированных системах, которые поддерживают только обработку сообщений, термин "метод" применяется для определения сообщения, реализованного в другом классе, по отношению к рассматри­ваемому. В системах, где поддерживаются только родовые функции, термин "метод" служит для определения различных реализаций поведения родовой функции.

Во избежание подобной путаницы в дальнейшем для указания на реализа­цию обработки сообщения в некотором классе будем использовать термин "обработчик сообщений". Термин "метод" станет употребляться только в кон­тексте родовых функций.

10.2. Рекомендации по использованию родовых функций

Запуск родовых функций требует от системы больших манипуляций, чем вызов системных функций или функций, определенных с помощью конст­руктора deffunction. Это происходит потому, что CLIPS должен сначала исследовать аргументы родовой функции и определить, какой из ее методов применим в данном случае. Вызов родовой функции может быть на 15—20% медленнее вызова обычной функции. Поэтому не используйте родовые функции в операциях, для которых время критично. Например, не вызы­вайте родовую функцию в цикле, если это возможно.

Кроме того, родовые функции всегда должны иметь, по крайней мере, два метода. В случае если перегрузка функции не требуется, используйте обыч­ные функции.

Если некоторый конструктор создается до определения родовой функции и использует системную или определенную пользователем функцию, которая впоследствии становится одним из методов родовой функции, родовое свя­зывание не применяется. Например, если родовая функция, перегружающая функцию +, определена после правила с функцией +, то правило всегда ста­нет вызывать системную функцию +. Однако если подобное правило будет определено после родовой функции, то при вызове функции + будет ис­пользоваться родовое связывание.

10.3. Создание родовой функции

Родовая функция состоит из заголовка (подобного предварительному объяв­лению функции) и нескольких методов (число которых теоретически может быть равным нулю). Заголовок родовой функции может быть либо явно оп­ределен пользователем, либо не явно объявлен определением метода. Объ­явление метода состоит из 6 элементов:

  • имя (которое отображает, к какой основной функции относится метод);
  • необязательный индекс;
  • необязательные комментарии;
  • набор ограничений для параметров;
  • необязательный групповой параметр для обработки переменного числа аргументов;
  • последовательность действий или выражений, которые будут выполнены в заданном порядке в момент вызова метода.

Ограничения параметров используются в процессе родового связывания для определения применимости метода к некоторому набору аргументов. Для создания заголовка родовой функции служит конструктор defgeneric, а для создания каждого нового метода родовой функции — конструктор defmethod.

Определение 10.1. Синтаксис конструктора defgeneric

(defgeneric <имя-функции>

[комментарии])

Определение 10.2. Синтаксис конструктора defmethod

(defmethod <имя-функции>

[<индекс>]

[<комментарии>]

(<ограничения-параметра>*

[<групповой-параметр>])

<действие>*)

<ограничения-параметров> ::= <простая-переменная> |

(<простая-переменная>

<ограничение-по-типу> *

[<ограничение-по-запросу>])

<групповой-параметр> ::= <составная-переменная> |

(<составная-переменная>

<ограничение-по-типу>*

[<ограничение-по-запросу >])

<ограничение-по-типу> ::= <имя-класса>

<ограничение-по-запросу> ::= <глобальная-переменная> |

<вызов-функции>

Родовая функция должна быть либо явно объявлена конструктором defgeneric, либо одним из своих методов, до того как она будет вызвана из другой функции, правила или обработчика сообщения. Исключение состав­ляют рекурсивные родовые функции.

10.3.1. Заголовок родовой функции

Родовая функция однозначно определяется по своему имени. В случае ис­пользования родовой функции в правиле, другой функции, обработчике со­общения или до объявления первого метода родовой функции необходи­мо явное создание заголовка родовой функции (с помощью конструктора defgeneric). В других случаях объявление первого метода родовой функции само не явно создает заголовок. Например, в случае если две родовые функции имеют методы, которые взаимно вызывают друг друга (взаимная рекурсия родовых функций), то необходимо явное объявление заголовков.

10.3.2. Индексы методов

Метод родовой функции однозначно определяется либо по имени и индек­су, либо по имени и ограничениям параметров. Каждому методу родовой функции назначается целый индекс, уникальный в группе всех методов этой функции. В случае если определен новый метод, который точно совпа­дает по ограничениям параметров и имени с другим методом этой функции, CLIPS автоматически заменит им существующий метод. Однако малейшее несовпадение в ограничениях параметров приведет к созданию нового ме­тода. Если нужно заменить некоторый уже существующий метод новым ме­тодом с другими ограничениями параметров, то в определении нового мето­да необходимо явно указать индекс существующего метода. При этом огра­ничения параметров нового метода должны не совпадать с ограничениями других методов той же функции. Если индекс не задан, CLIPS автомати­чески назначает индекс, который еще не был использован другими мето­дами родовой функции. Индекс, соответствующий методам родовой функ­ции, можно определить, например, с помощью команды list-defmethods (см. гл. 16).

10.3.3. Ограничения параметров метода

Каждый параметр метода может быть определен с некоторыми произволь­ными комплексными ограничениями или без них. Ограничения параметров применяются к аргументам родовой функции во время работы программы для определения того, какой именно метод должен принимать эти аргумен­ты. Параметр может иметь два типа ограничений: ограничение типа и ограничение запросом. Ограничение типа содержит классы аргументов, которые может принимать параметр. Ограничение запросом является определенным пользователем условным выражением, которое должно удовлетвориться для аргументов в момент вызова функции. Совмещение ограничений и их сложность прямо влияет на скорость родового связывания.

Если параметр не имеет ограничений, это означает, что метод может при­нимать любые значения в качестве данного аргумента. Однако каждый ме­тод родовой функции должен иметь определенные ограничения параметров, которые будут отличать его от других методов той же родовой функции. В противном случае, процесс родового связывания не сможет определить, какой именно метод необходимо вызывать. В случае если процесс родового связывания не смог подобрать соответствующий метод для некоторого на­бора аргументов, CLIPS сгенерирует ошибку.

Ограничение типа позволяет пользователю определить список типов (клас­сов), один из которых должен соответствовать (или являться суперклассом) аргументу родовой функции. Если в используемой вами конфигурации CLIPS не установлен COOL, то в качестве ограничения типа будут доступ­ны только следующие типы (классы): object, primitive, lexeme, symbol,

STRING, NUMBER, INTEGER, FLOAT, MULTIFIELD, FACT-ADDRESS И EXTERNAL-ADDRESS.

В гл. 11 все эти системные классы будут описаны подробно. Если COOL установлен, то, помимо перечисленных выше, будут доступны классы

INSTANCE, INSTANCE-ADDRESS, INSTANCE-NAME, USER, INITIAL-OBJECT, а также любой определенный пользователем класс. Родовая функция, которая исполь­зует только первую группу типов в своих методах, будет работать как с установленным COOL, так и без него. Классы, заданные в ограничении типа, должны быть определены до определения приоритета метода (см. разд. 10.4.2). CLIPS не поддерживает избыточность в списке ограничений типов аргумен­тов методов. Например, для представленного ниже метода ограничения типов аргументов избыточны, т. к. класс integer — подкласс number.

Пример 10.1. Избыточные ограничения типов

(defmethod foo ((?a INTEGER NUMBER)))

Если ограничение типа удовлетворяется для некоторого аргумента, то к не­му будет применено ограничение запросом (если оно задано). Ограничение запросом должно быть либо глобальной переменной, либо вызовом функ­ции. CLIPS вычисляет заданное выражение, и если полученный результат не равен false — ограничение полагается удовлетворенным.

Так как ограничения запросом вычисляются каждый раз при поиске соот­ветствующего метода, они не могут использоваться для произведения како­го-нибудь побочного действия, потому что вычисляемое ограничение может принадлежать методу, неподходящему к данной конкретной ситуации.

Поскольку все ограничения просматриваются слева направо, запрос с не­сколькими параметрами должен быть записан после ограничений типов всех используемых параметров. Этим правилом обеспечивается условие удовлетворения ограничений типов всех необходимых параметров. Напри­мер, метод из примера 10.2 не вычисляет ограничение запросом до тех пор, пока не удовлетворятся два соответствующих ограничения типа.

Пример 10.2. Использование ограничения запросом с двумя параметрами

(defmethod foo ((?a INTEGER ) (?b INTEGER(> ?a ?b))))

Если аргумент удовлетворяет всем своим ограничениям, то считается, что он применим для данного метода. Если все аргументы родовой функции применимы к ограничениям метода, метод полагается применимым для данного набора аргументов. В случае если существует более одного метода, применимого для некоторого набора аргументов, процесс родового связы­вания определяет некоторый упорядоченный список этих методов и исполь­зует первый метод из этого списка. Для создания списка служит приоритет методов, описанный в разд. 10.4.2.

В примере 10.3 первое обращение к родовой функции + вызовет выполне­ние системной функции + — неявный метод, выполняющий арифметиче­ское сложение. Второй вызов приведет к выполнению явного метода родо­вой функции, осуществляющего конкатенацию строк, т. к. оба аргумента являются строками. Третий вызов сгенерирует ошибку, поскольку явный метод для конкатенации строк принимает только два аргумента, а неявный метод для арифметического сложения не принимает строковые аргументы вообще.

Пример 10.3. Перегрузка системной функции +

(defmethod + ( (?а STRING) (?b STRING))

(str-cat ?a ?b) )

(+ 1 2)

(+ "foo" "bar")

(+ "foo" "bar" "woz")

10.3.4. Групповой параметр

В зависимости от того, задан ли групповой параметр, метод может прини­мать точное число параметров или число параметров не меньшее, чем неко­торое заданное. Обязательные параметры определяют минимальное число аргументов, которое должно быть передано методу при его вызове. В дейст­виях, выполняемых методом, можно ссылаться на каждый из этих парамет­ров так же, как на обычные переменные, содержащие простые значения. Если был задан групповой параметр, то метод может принимать любое ко­личество аргументов — большее или равное минимальному числу аргумен­тов. Если групповой параметр не задан, то метод может принимать число аргументов, равное числу обязательных параметров. Все аргументы метода, которые не соответствуют обязательным параметрам, группируются в одно значение составного поля. Ссылаться на это значение можно, указывая символ группового параметра. Для работы с групповым параметром могут использоваться стандартные функции CLIPS, предназначенные для работы с составными полями (см.гл. 15), такие как length и nth. Определение ме­тода может содержать только один групповой параметр.

Ограничения типом и запросом могут применяться к аргументам, сгруппи­рованным в групповом параметре, аналогично тому, как они употребляются с основными параметрами метода. Такие ограничения задаются для каждого отдельного поля результирующего составного значения (а не для всего зна­чения). Выражение, содержащее групповой символ, может быть применено в запросе.

Дополнительно в запросе может быть использована специальная перемен­ная ?current-argument для ссылки на отдельные аргументы, объединенные групповым символом. Это переменная существует только в ограничении запросом и не имеет значения в теле метода. Метод из примера 10.4 иллю­стрирует версию функции +, которая находит полусумму любого количества четных целых чисел.

Пример 10.4. Еще один вариант функции +

(defmethod +

(($?any INTEGER (evenp ?current-argument)))

(div (call-next-method} 2))



Pages:     | 1 |   ...   | 5 | 6 || 8 | 9 |   ...   | 14 |
 





<


 
2013 www.disus.ru - «Бесплатная научная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.