Eliza 重现江湖!!史上第一个通过图灵测试的AI

「前言:创造 Eliza 的目的不是真的想要写一个代替心理学家的AI,而是想向人们证明:

人工智能,是一个幻觉。

Intelligence, is nothing but an illusion.」

Eliza 是 MIT 的教授 Joseph Weizenbaum 1966年写的一个AI 程序,模拟一个 精神分析师,可以和人类对话,而且真假难辨(如下图所示)。

Eliza 的 Demo

和其他人工智能应用一样,Eliza 也是用人工智能语言 LISP 写的。笔者已经将 Eliza 代码重新改写成 Scheme 并运行成功,如下面两个视频所示。

eliza_demo

eliza_demo2

Eliza 的原理

AI ,其实非常简单,无非是识别人类的行为模式,根据特定的模式转化成自己的回应。

Eliza 的程序就是不断循环四个步骤:1. 读取一条输入;2. 找到输入里的模式;3. 将输入模式转化成回复;4. 将回答输出在屏幕上。

而模式识别无非就是更抽象的“等于”。

​​​​​​​

所以,我们首先可以这么定义

(defun simple-equal (x y )
	"Are x and y equal? (Don't check inside strings.)" 
	(if (or (atom x) (atom y))
		(eql x y)
		(and (simple-equal (first x) (first y))
			   (simple-equal (rest x) (rest y)))))

(defun pat-match (pattern input)
	"Does pattern match input? Any variable can match anything." 
	(if (variable-p pattern)
			t
			(if (or (atom pattern) (atom input))
				(eql pattern input)
				(and (pat-match (first pattern) (first input))
						 (pat-match (rest pattern) (rest input))))))

(defun variable-p (x)
	"Is X a variable (a symbol beginning with *?*)?"
	(and (symbolp x) (equal (char (symbol-name x) 0) #\?)))

让我们来测试一下:

;; 下面让我们来测试一下
> (pat-match '(I need a ?X) '(I need a vacation)) 
Τ
> (pat-match '(I need a ?X) '(I really need a vacation)) 
NIL

这个虽然告诉我们模式匹配上了,但是却没告诉我们到底匹配上了个啥。有经验的程序员会想起 Lisp 里已经有相应的函数可以在这派上用场了,substitute, subst, 和 sublis.  sublis 是最适合的,它是唯一一个可以同时替换多个值的。sublis 有两个参数,第一个是一对替换前后的新旧值,第二个是待替换的语句,以列表的形式传进去,比如。
 

> (sublis '((?X . vacation))
'(what would it mean to you if you got a ?X?))
(WHAT WOULD IT MEAN TO YOU IF YOU GOT A VACATION ?)

现在,我们定义两个返回的常量和 binding,也就是怎么把变量和变量指代的语句结合在一起。
 

(defconstant fail nil "Indicates pat-match failure")
(defconstant no-bindings '((t . t))
	"Indicates pat-match success, with no variables.")
(defun get-binding (var bindings)
	"Find a (variable . value) pair in a binding list." 
	(assoc var bindings))
(defun binding-val (binding)
	"Get the value part of a single binding." 
	(cdr binding))
(defun lookup (var bindings)
	"Get the value part (for var) from a binding list." 
	(binding-val (get-binding var bindings)))
(defun extend-bindings (var val bindings)
	"Add a (var . value) pair to a binding list." 
	(cons (cons var val) bindings))

定义了 binding 以后,再写模式匹配就好写了,首先,如果句子,也就是输入为空,则返回空;其次如果模式是变量,则句子里的任何值都返回,这里用 match-variable 实现匹配这个动作,注意,虽然这里用的是 lisp 的另外一个方言,common-lisp,但是思想没有变;再次,如果模式和待匹配的句子都是列表,那么就动用递归,继续依次向下匹配;最后,如果以上的情况都不符合,则返回 fails. 现在的 pat-match 是这样子的。。。

(defun pat-match (pattern input &optional (bindings no-bindings)) 
	"Match pattern against input in the context of the bindings" 
	(cond ((eq bindings fail) fail)
			  ((variable-p pattern) (match-variable pattern input bindings))
				((eql pattern input) bindings)
				((and (consp pattern) (consp input))
					(pat-match (rest pattern) (rest input)
						(pat-match (first pattern) (first input)
							bindings)))
				(t fail)))

(defun match-variable (var input bindings)
	"Does VAR match input? Uses (or updates) and returns bindings." 
	(let ((binding (get-binding var bindings)))
		(cond ((not binding) (extend-bindings var input bindings)) 
					((equal input (binding-val binding)) bindings)
					(t fail))))

让我们来测试一下,

> (pat-match '(i need a ?X) '(i need a vacation))
((?X . VACATION) (T . T ) )

可以成功返回变量的值了,这里的(T . T)是 no-binding 的残留部分,虽然没有大碍,但是我们还是想去掉这块冗余的部分,只需要把 extend-binding 稍微加一句就可以了,这里就是说,如果没有值了,就是别硬加了。

(defun extend-bindings (var val bindings)
	"Add a (var . value) pair to a binding list." 
	(cons (cons var val)
		;;Once we add a "real" binding, we can get rid of the dummy no-bindings 
		(if (eq bindings no-bindings)
			nil bindings)

再测试一下

> (sublis (pat-match '(i need a ?X) '(i need a vacation)) 
				 '(what would it mean to you if you got a ?X ?))
(WHAT WOULD IT MEAN TO YOU IF YOU GOT A VACATION ?)
> (pat-match '(i need a ?X) '(i really need a vacation)) 
NIL
> (pat-match '(this is easy) '(this is easy)) 
((T . T))
> (pat-match '(?X is ?X) '((2 + 2) is 4)) 
NIL
> (pat-match '(?X is ?X) '((2 + 2) is (2 + 2))) 
((?X 2 + 2))
> (pat-match '(?P need . ?X) '(i need a long vacation))
((?X A LONG VACATION) (?P . I))

别忘了,事情永远没有看起来那么简单。现实世界中,有的输入可能没有那么标准,我们想要实现这样的匹配:

> (pat-match '((?* ?p) need (?* ?x))
						 '(Mr Hulot and I need a vacation))
((?P MR HULOT AND I) (?X A VACATION))

换句话说就是,我们希望实现开头也可以匹配多个词,
这个只需要在 pat-match 多加一条 cond 条件判断语句就行了

(defun pat-match (pattern input &optional (bindings no-bindings)) 
	"Match pattern against input in the context of the bindings" 
	(cond ((eq bindings fail) fail)
		  ((variable-p pattern) 
			  (match-variable pattern input bindings))
		  ((eql pattern input) bindings) 
		  ((segment-pattern-p pattern) 		;; 就是这一句
			  (segment-match pattern input bindings)) 
		  ((and (consp pattern) (consp input))
				(pat-match (rest pattern) (rest input)
					(pat-match (first pattern) (first input)
							bindings))
				(t fail)))

(defun segment-pattern-p (pattern)
	"Is this a segment matching pattern: ((?* var) . pat)" 
	(and (consp pattern)
		(starts-with (first pattern) '?*)))

在写 segment-match 的时候,最难的是判断前面的匹配到什么地方结束。我们认真思考一下就会知道,这取决于后面的是否与下一个 pattern 匹配,如果永远不匹配,则返回 fail,否则就找到后面的匹配从哪里开始,这就是前面的匹配部分结束的地方。这里用递归调用 segment-match 来实现后面的是否匹配,简称 b2。需要注意的是,当 b2 匹配失败的时候,该怎么处理,我们不能简单的就放弃了,因为有可能如果前面的 segment-match 匹配的文字更长的话,后面的就能匹配上了,所以,我们需要尝试让前面匹配更长的文字,然后看看后面的是否匹配上。这里我们引入了一个变量 start,表示前面 segment-match 的长度,初始值是0,然后每次失败就增加1:

(defun segment-match (pattern input bindings &optional (start 0)) 
	"Match the segment pattern ((?* var) . pat) against input."
	(let ((var (second (first pattern))) 
		  (pat (rest pattern))) 
		(if (null pat)
			(match-variable var input bindings)
		;; We assume that pat starts with a constant
		;; In other words, a pattern can't have 2 consecutive vars
			(let ((pos (position (first pat) input :start start :test #'equal)))
				(if (null pos) 
					fail
					(let ((b2 (pat-match 
										 pat (subseq input pos) 
										 bindings))) 
						;; If this match failed, try another longer one
						;; If it worked, check that the variables match
						(if (eq b2 fail)
							(segment-match pattern input bindings (+ pos 1)) 
							(match-variable var (subseq input 0 pos) b2))))))))

测试一下

> (pat-match '((?* ?p) need (?* ?x))
						 '(Mr Hulot and I need a vacation))
((?PMRHULOTANDI) (?XAVACATION))

> (pat-match '((?* ?x) is a (?*?y)) '(what he is is a fool)) 
((?X WHAT HE IS) (?Y FOOD)

但是呢,事情还没完,看下面这个例子

> (pat-match '((?* ?x) a b (?* ?x)) '(1 2 a b a b 1 2 a b)) 
NIL

这个例子之所以失败是因为,一开始 ?x 匹配上了 (1 2), 后面的pattern (a b)也对上了,但是后来的 ?x (a b 1 2 a b)就与之前的 ?x (1 2)对不上了. 我们应该在 b2 失败前调用 match-variable,保证它会再测试一次 segment-match。

(defun segment-match (pattern input bindings &optional (start 0 ) ) 
	"Match the segment pattern ((?* var) . pat) against input."
	(let ((var (second (first pattern))) (pat (rest pattern))) 
		(if (null pat)
			(match-variable var input bindings)
			;; We assume that pat starts with a constant
			;; In other words, a pattern can't have 2 consecutive vars 
			(let ((pos (position (first pat) input 
													:start start :test #'equal)))
				(if (null pos)
					fail
					(let ((b2 (pat-match 
										 pat (subseq input pos)
										 (match-variable var (subseq input 0 pos) bindings))))
					;; If this match failed, try another longer one 
					(if (eq b2 fail)
						(segment-match pattern input bindings ( + pos 1 )) 
						b2)))))))

现在我们看到这个例子可以成功返回正确值了

> (pat-match '((?* ?x) a b (?* ?x)) '(1 2 a b a b 1 2 a b)) 
((?X 1 2 A B))

现在我们的模式匹配写好了,下面来定义一些模式,以及模式的响应吧。我们可以定义一种数据结构,称为 rule 规则,包括一种模式和对应的响应值。比如“如果你发现了 A,那么就随机的回答 B 或者 C。” 我们以列表的形式表示规则,第一个元素表示模式,剩下的是可能的响应列表。如下所示:

(defun rule-pattern (rule) (first rule)) 
(defun rule-responses (rule) (rest rule))

下面是一个规则,当在 Eliza 运行的时候,比方说用户输入“I want to test this program”,
它会用自己的模式匹配到 ?y 是“to test this program”, 然后代入回复中,随机的回复一句,
比如,“What would it mean if you got to test this program?”

(((?* ?x) I want (?* ?y))
(What would it mean if you got ?y) (Why do you want ?y)
(Suppose you got ?y soon))

下面我们知道规则怎么运行了,那该怎么选择规则呢?这个方法有很多,原始的 Eliza 代码给每个规则匹配了一个优先级,按照优先级顺序从高到低匹配。我们现在为了简单起见,就不匹配优先级了,直接按顺序试,选择第一个匹配成功的规则来执行。下面是一部分规则的列表。

(defparameter *eliza-rules* 
	'((((?* ?x) hello (?* ?y))
		(How do you do. Please state your problem.)) 
	(((?* ?x) I want (?* ?y))
		(What would it mean if yougot ?y)
		(Why do you want ?y) 
		(Suppose you got ?y soon)) 
	(((?* ?x) if (?* ?y))
		(Do you really think its likely that ?y) 
		(Do you wish that ?y)
		(What do you think about ?y)
		(Really-- if ?y)) 
	(((?* ?x) no (?* ?y))
		(Why not?) 
		(You are being a bit negative)
		(Are you saying "NO" just to be negative?)) 
	(((?* ?x) I was (?* ?y))
		(Were you really?) 
		(Perhaps I already knew you were ?y )
		(Why do you tell me you were ?y now?)) 
	(((?* ?x) I feel (?* ?y))
		(Do you often feel ?y ?)) 
	(((?* ?x) I felt (?* ?y))
		(What other feelings do you have?))))

下面我们终于可以开始定义 Eliza 了,就像 ChatGPT 一样,它其实是一个循环,不断的重复四个步骤:1. 等待用户输入;2. 读取输入;3. 根据规则匹配输入,编写回复;4. 把结果打印出来。所以,其实非常简单,我们又加了一点点,比如打印提示符,提醒用户输入;我们用 flattern 函数,把回复的列表重新格式化成句子;最重要的是把用户输入的第一人称“我”转换成第二人称“你”,如下所示:
 

(defun eliza ()
	"Respond to user input using pattern matching rules." 
	(loop
		(print 'eliza>)
		(write (flatten (use-eliza-rules (read))) ipretty t)))
(defun use-eliza-rules (input)
	"Find some rule with which to transform the input." 
	(some #'(lambda (rule)
		(let ((result (pat-match (rule-pattern rule) input)))  
			(if (not (eq result fail))
				(sublis (switch-viewpoint result)
								(random-elt (rule-responses rule))))))
		*eliza-rules*))

(defun switch-viewpoint (words)
	"Change I to you and vice versa, and soon."
	(sublis '((I . you) (you . I) (me . you) (am are))
					 words))
(defun flatten (the-list)
	"Append together elements (or lists) in the list." 
	(mappend #'mklist the-list))
(defun mklist (X)
	"Return X if it is a list, otherwise (x)." 
	(if (listp X)
		X
		(list X)))
(defun mappend (fn the-list)
	"Apply fn to each element of list and append the results." 
	(apply #'append (mapcar fn the-list)))
(defun random-elt (choices)
	"Choose an element from a list at random." 
	(elt choices (random (length choices))))

Eliza 全部的规则:

(defparameter *eliza-rules* 
	'((((?* ?x) hello (?* ?y))
		(How do you do. Please state your problem.)) 
		(((?* ?x) computer (?* ?y))
			(Do computers worry you?) 
			(What do you think about machines?) 
			(Why do you mention computers?)
			(What do you think machines have to do with your problem?))
		(((?* ?x) name (?* ?y))
			(I am not interested in names))
		(((?* ?x)sorry (?*?y))
			(Please don't apologize) 
			(Apologies are not necessary) 
			(What feelings do you have when you apologize))
		(((?* ?x) I remember ( ? * ?y))
			(Do you often think of ?y)
			(Does thinking of ?y bring anything else to mind?)
			(What else do you remember) 
			(Why do you recall ?y right now?) 
			(What in the present situation reminds you of ?y)
			(What is the connection between me and ?y ))
		(((?* ?x) do you remember (?* ?y))
			(Did you think I would forget ?y ?)
			(Why do you think I should recall ?y now) 
			(What about ?y) 
			(You mentioned ?y))
		(((?* ?x) if (?* ?y))
			(Do you really think its likely that ?y) 
			(Do you wish that ?y) 
			(What do you think about ?y) 
			(Really-- if ?y))
		(((?* ?x) I dreamt (?* ?y))
			(Really-- ?y) 
			(Have you ever fantasized ?y while you were awake?) 
			(Have you dreamt ?y before?))
		(((?* ?x) dream about (?* ?y))
			(How do you feel about ?y in reality?))
		(((?* ?x) dream (?* ?y))
			(What does this dream suggest to you?) 
			(Do you dream often?) 
			(What persons appear in your dreams?)
			(Don't you believe that dream has to do with your problem?))
		(((?* ?x) my mother (?* ?y))
			(Who else in your family ?y) 
			(Tell me more about your family))
		(((?* ?x) my father (?* ?y))
			(Your father) 
			(Does he influence you strongly?)
			(What else comes to mind when you think of your father?))
		(((?* ?x) I want (?* ?y))
			(What would it mean if you got ?y)
			(Why do you want ?y)
			(Suppose you got ?y soon))
		(((?* ?x) I am glad (?* ?y))
			(How have I helped youto be ?y)
			(What makes you happy just now) 
			(Can you explain why you are suddenly ?y))
		(((?* ?x) I am sad (?* ?y))
			(I am sorry to hear you are depressed) 
			(I'm sure it's not pleasant to be sad))
		(((?* ?x) are like (?* ?y))
			(What resemblance do you see between ?x and ?y))
		(((?* ?x) is like (?* ?y))
			(In what way is it that ?x is like ?y)
			(What resemblance do you see?)
			(Could there really be some connection?) 
			(How?))
		(((?* ?x) alike (?* ?y))
			(In what way?) 
			(What similarities are there?))
		(((?* ?x) same (?* ?y))
			(What other connections do you see?))
		(((?* ?x) I was (?* ?y))
			(Were you really?) 
			(Perhaps I already knew you were ?y ) 
			(Why do you tell me you were ?y now?))
		(((?* ?x) was I (?* ?y))
			(What if you were ?y?)
			(Do you think you were ?y) 
			(What would it mean if you were ?y))
		(((?* ?x) I am (?* ?y))
			(In what way are you ?y)
			(Do you want to be ?y ?))
		(((?* ?x) am I (?* ?y))
			(Do you believe you are ?y)
			(Would you want to be ?y) 
			(You wish I would tell you you are ?y)
			(What would it mean if you were ?y))
		(((?* ?x) am (?* ?y))
			(Why do you say "AM?") 
			(I don't understand that))
		(((?* ?x) are you (?* ?y))
			(Why are you interested in whether I am ?y or not?) 
			(Would you prefer if I weren't ?y)
			(Perhaps I am ?y in your fantasies))
		(((?* ?x) you are (?* ?y))
			(What makes you think I am ?y ?)
		(((?* ?x) because (?* ?y))
			(Is that the real reason?) 
			(What other reasons might there be?) 
			(Does that reason seem to explain anything else?))
		(((?* ?x) were you (?* ?y))
			(Perhaps I was ?y) 
			(What do you think?) 
			(What if I had been ?y))
		(((?* ?x) I can't (?* ?y))
			(Maybe you could ?y now)
			(What if you could ?y ?))
		(((?* ?x) I feel (?* ?y)) 
			(Do you often feel ?y ?))
		(((?* ?x) I felt (?* ?y))
			(What other feelings do you have?))
		(((?* ?x) I (?* ?y) you (?* ?z))
			(Perhaps in your fantasy we ?y each other))
		(((?* ?x) why don't you (?* ?y))
			(Should you ?y yourself?)
			(Do you believe I don't ?y) 
			(Perhaps I will ?y in good time))
		(((?* ?x) yes (?* ?y))
			(You seem quite positive) 
			(You are sure) 
			(I understand))
		(((?* ?x) no (?* ?y))
			(Why not?) 
			(You are being a bit negative) 
			(Are you saying "NO" just to be negative?))
		(((?* ?x) someone (?* ?y)) 
			(Can you be more specific?))
		(((?* ?x) everyone (?* ?y))
			(surely not everyone) 
			(Can you think of anyone in particular?) 
			(Who for example?) 
			(You are thinking of a special person))
		(((?* ?x) always (?* ?y))
			(Can you think of a specific example) 
			(When?)
			(What incident are you thinking of?) 
			(Really-- always))
		(((?* ?x) what (?* ?y))
			(Why do you ask?) 
			(Does that question interest you?)
			(What is it you really want to know?) 
			(What do you think?) 
			(What comes to your mind when you ask that?))
		(((?* ?x) perhaps (?* ?y))
			(You do not seem quite certain))
		(((?* ?x) are (?* ?y))
			(Did you think they might not be ?y) 
			(Possibly they are ?y))
		(((?* ?x))
			(Very interesting) 
			(I am not sure I understand you fully) 
			(What does that suggest to you?) 
			(Please continue) 
			(Go on) 
			(Do you feel strongly about discussing such things?))))

附:ELIZA'S principal developer, MIT professor Joseph Weizenbaum, published a paper on ELIZA in the January 1966 issue of the Communications of the Association for Computing Machinery. The introduction to that paper is reproduced in its entirety here:

It is said that to explain is to explain away. This maxim is nowhere so well fulfilled as in the area of computer programming, especially in what is called heuristic programming and artificial intelligence. For in those realms machines are made to behave in wondrous ways, often sufficient to dazzle even the most experienced observer. But once a particular program is unmasked, once its inner workings are explained in language sufficiently plain to induce understanding its magic crumbles away; it stands revealed as a mere collection of procedures, each quite comprehensible. The observer says to himself, "I could have written that." With that thought he moves the program in question from the shelf marked "intelligent," to that reserved for curios, fit to be discussed only with people less enlightened than he.

The object of this paper is to cause just such a re-evaluation of the program about to be "explained." Few programs ever needed it more.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值