在LispWorks中进行编程27:ElizaChatbot
Category:UI界面编写ElizaChatbot
这是一个叫做Eliza的心理治疗师聊天机器人。它是经典人工智能程序ELIZA的简单版本,由Joseph Weizenbaum于1964年至1966年在麻省理工学院人工智能实验室[1]撰写,可以合理地声称是所有聊天机器人的母亲。
像一个好的心理治疗师一样,伊丽莎一般试图扭转你对它说的话,然后把它还给你,听起来像是有人类的理解,却没有给你任何真实的信息。这里的程序有一套初始规则,但可以很容易地扩展到给Eliza更多的话题。
样本输出
以下是Eliza chatbot的一些示例输出。人工输入在“}”提示符后写入,计算机的回复在括号中:
} hellohello. how can i help ?} I hate my computerwhat makes you hate your computer ?} I wish it would run fasterwhy would it be better if it would run faster ?} I feel I waste a lot of timedo you often feel you waste a lot of time ?} most daysyou say most days ?} yestell me more.} bye
通过一小组规则,它很快就会重复出现,但您可以轻松添加更多规则来扩展其保留的功能。
该程序
程序中的主程序是模式匹配例程match,它在用户的输入中查找模式。它需要一个模式字符串和一个输入字符串,如果匹配则返回t,否则返回nil:
> (match ‘(all is well) ‘(all is well))t > (match ‘(all is well) ‘(all not well))nil
模式可以包含匹配零个或多个单词的通配符“*”; 每个通配符后跟一个变量名,该变量名在全局变量* bindings *中被赋予匹配的子字符串:
> (match ‘(* x chase * y) ‘(dogs chase cats and sheep))t > *bindings*((X DOGS) (Y CATS AND SHEEP))
这是匹配的定义:
(defun match (pat in) (if (null pat) (null in) (if (eq (first pat) ‘*) (wildcard pat in) (if (eq (first pat) (first in)) (match (rest pat) (rest in)) nil))))
例行匹配的工作原理如下:
- 如果模式为零,则输入为零时成功。
- 否则,如果模式的第一个元素是’*,则调用通配符。
- 否则,如果模式和输入的第一个元素匹配,则调用匹配其余模式和输入。
- 否则匹配失败。
以下是处理通配符匹配的通配符的定义 :
(defun wildcard (pat in) (if (match (rest (rest pat)) in) (progn (setf *bindings* (bind (first (rest pat)) nil *bindings*)) t) (if (null in) nil (if (match pat (rest in)) (progn (setf *bindings* (bind (first (rest pat)) (first in) *bindings*)) t) nil))))
例程通配符调用bind以在* bindings *中进行绑定。
(defun bind (var value bindings) (if (null bindings) (list (if value (list var value) (list var))) (let* ((key (first (first bindings))) (values (rest (first bindings))) (new (swap value))) (if (eq var key) (cons (cons key (cons new values)) (rest bindings)) (cons (first bindings) (bind var new (rest bindings)))))))
* bindings *中的绑定由一系列列表组成,每个列表由一个变量后跟其值组成。例如:
((X DOGS) (Y CATS AND SHEEP))
这指定X的值是(DOGS)而Y的值是(CATS AND SHEEP)。
该绑定例程将现有绑定的列表,并返回一个新的列表值加入到值列表VAR:
CL-USER > (bind ‘x ‘cat ‘((x dog) (y sheep)))((X CAT DOG) (Y SHEEP))
绑定例程做另外一件事; 当它添加一个值时,它调用swap来改变视点,通过替换* perspective *中的单词对列表中的单词 :
(defvar *viewpoint* ‘((I you) (you I) (me you) (am are) (was were) (my your)))
这使得匹配的字符串可以被程序回显。这是swap的定义 :
(defun swap (value) (let* ((a (lookup value *viewpoint*))) (if a (first (rest a)) value)))
它使用例程查找:
(defun lookup (key alist) (if (null alist) nil (if (eq (first (first alist)) key) (first alist) (lookup key (rest alist)))))
例程subs获取一个列表并替换* bindings *中的变量值。因此,假设上面显示的* bindings *的值我们可以写:
CL-USER > (subs ‘(I think y are chased by x))(I THINK CATS AND SHEEP ARE CHASED BY DOGS)
这是subs,它也使用lookup:
(defun subs (list) (if (null list) nil (let* ((a (lookup (first list) *bindings*))) (if a (append (rest a) (subs (rest list))) (cons (first list) (subs (rest list)))))))
Eliza规则在变量* rules *中定义:
(defparameter *rules* ‘(((* x hello * y) (hello. how can I help ?)) ((* x i want * y) (what would it mean if you got y ?) (why do you want y ?)) ((* x i wish * y) (why would it be better if y ?)) ((* x i hate * y) (what makes you hate y ?)) ((* x if * y) (do you really think it is likely that y) (what do you think about y)) ((* x no * y) (why not?)) ((* x i was * y) (why do you say x you were y ?)) ((* x i feel * y) (do you often feel y ?)) ((* x i felt * y) (what other feelings do you have?)) ((* x) (you say x ?) (tell me more.))))
每条规则包括:
模式:例如:
(* x i want * y)
一个或多个响应的列表:例如:
(what would it mean if you got y ?)(why do you want y ?)
程序找到第一个匹配的规则,然后使用例程random-elt随机选择一个响应:
(defun random-elt (list) (nth (random (length list)) list))
最后,这是运行Eliza的程序:
(defun eliza () (loop (princ “} “) (let* ((line (read-line)) (input (read-from-string (concatenate ‘string “(” line “)”)))) (when (string= line “bye”) (return)) (setq *bindings* nil) (format t “~{~(~a ~)~}~%” (dolist (r *rules*) (when (match (first r) input) (return (subs (random-elt (rest r))))))))))
它循环,反复执行以下操作:
- 阅读用户的输入行。
- 通过添加括号并使用我们之前未提及的过程(read-from-string)将其转换为列表 。
- 清除*绑定*中的绑定。
- 找到匹配的* rules *中的第一条规则。
- 将* bindings *中的值替换为随机选择的回复。
- 使用格式打印回复。
我们使用的格式模式是:
“~{~(~a ~)~}~%”
这包括以下组件:
- 〜{…〜}打印列表中的每个元素。
- 〜(…〜)将大写字母转换为小写字母。
- ~a打印元素。
- 〜%打印换行符。
键入以下命令运行整个程序:
(eliza)
以下是整个Eliza程序的单个文件:
http://mip.i3geek.com