人工智能编程范式:Common Lisp案例学习
Peter Norvig
前言
范式(paradigm):名词,一个实例或者一个模式,样本;特别是指一个出类拔萃的典型样例。这本书涉及的有三个方面:人工智能领域,或者说AI;计算机编程技术;编程语言Common Lisp。本书可以给读者的内容有,对于AI的一些主要问题和技术的理解,一些重要的AI编程程序理解,还有使用Common Lisp来创建,读取,修改程序的能力。本书的所有的程序示范样例都是使用一种好的编程风格范式来编写。在AI的研究历史上也有使用广泛应用的技术来解决重要问题的优秀程序。
仅仅是以博雅教育,素质教育需要来说,一门课程尽量要包含在一本集文化之大成的书籍中,所以这本书,就是来定义AI课程的一本集文化之大成的书籍。但这并不是暗示说,书中写的程序是最好的,仅仅是说他们是最具有代表性的。
换个角度来说,这本书是一本是一本高度技术性的知识纲要,这些知识会让一个中级的Lisp程序员成为专家的过程中有所助益。第一部分和第二部分的布置,让新手可以快速上手,但如果是完完全全的新手的话,即使是配合这些材料,也可能会举步维艰。对于真正的新手来说,还好是有至少五本入门的书籍来参考,详情请见后面的参考书目。
一般来说,教计算机编程的方式是这样,首先解释要用的语言的语法构成,之后给学生看看十行规模的代码,然后就要求学生开始写程序。本书中,我们采用的方法是学习读写代码的最好方法(就是反过来,增强读代码能力的好的方式就是去写)。在对Lisp的简短介绍之后,我们马上开始进入复杂的程序,然后要求读者去理解代码,修改代码。
本书预设的前提就是,当你理解了什么是好的代码,并有一些有趣的想法的时候,你就可以写一些有用的代码了。也就是说以写散文的角度写代码。有一句话,写在Pascal语言软件工具的封面上,是Kernighan和Plauger说的:
优秀的编程技巧是如何习得的,是从经典的程序中见习得到的,优秀的代码是如何实践一般的概念和好的编程实现,让代码清晰易懂,容易修改维护,人性化,而且可靠高效。对经典代码的认真学习和模仿就可以习得更好的技巧。
高傲的工匠往往只是展现完成的杰作,却把创造过程中,一开始犯下的错误和不可避免的弯路给隐藏掉了。不幸的是,这样的遮遮掩掩成为了后来的学习者的障碍;当一个研究数学的学生看到教科书上那数十行的证明过程的时候,除了惊叹他简洁的美之外,但是也没有办法学到构建这一个证明的过程。本书试图展现的就是一个完整的开发过程,展露无遗。每一章的开头都有一个简略版本的程序,不是通用的程序,只在一些例子中可用。之后,我们就会对这些缺点进行分析,然后递增地构建一个基本程序的完整版本。因此,读者不仅能够看到程序最终的结果,而且也可以看到如何从错误中学习,讲一个初级的未完成的程序进行精炼。还有,读者如果觉得一些特定的章节太难而不得不跳过的话,评估一些问题的价值,不要被细节所烦扰。
本书所著述的是一些松散的知识集合,被称作AI编程技术,但是有一点要认识到,那就是这些知识之间是没有非常明确的分界线的。一个人在成为一个优秀的AI程序员之前,必定已经是一个优秀的程序员。因此,书中的一些主题(特别是在第三部分和第五部分)并没有AI的相关内容,只有一些对AI行业从业者的基本背景。
为什么用Lisp?为什么用Common Lisp?
Lisp,是最古老的编程语言之一,却在当下还广泛使用的使用。Lisp的版本很多,在基本的特性部分是相同的,但是在细节上差异却很大。本书使用的Common Lisp是一个Lisp的分支版本,他被认为是Lisp的标准版本。选择Lisp主要有三个理由:
首先,Lisp是AI编程领域最流行的语言,特别是在美国。如果你想学一门语言,肯定是学大家都在用的,通用的,而不是只有你一个使用的语言。
第二,Lisp语言在定义新的对象的时候生成更加优雅。特别是Lisp可以针对手上的问题定义新的语言特性。对于AI编程来说,语言新定义是个特别有用的特性,因为需要经常处理复杂的信息,这些信息是以普通文本的方式出现的。 Lisp是少数允许将程序代码和数据完全等同的语言之一。根据各个语言的定义,所有的语言都会提供一个定义程序的方式,但是很多其他语言都会限制程序使用的方式,或者限制程序定义的范围,或者要求程序员显式声明没有意义的细节。
第三,Lisp可以在很短的时间没就开发出程序原型来。Lisp程序是简洁的,不用处理底层的细节。Common Lisp提供了大量预先定义好的对象,包括700多个函数。编程环境来讲也是很好的。(比如调试工具,可调编译器,集成的编辑器,还有窗口系统的接口)。动态的交互式的Lisp环境,可以方便地在程序开发的时候试验和更改程序。
这里必须提到的是,在欧洲和日本,Prolog语言在AI工程领域已经和Lisp一样流行了。Prolog在可塑性和简洁性上和Lisp相差无几。最近,Lisp也开始走向世界,而Prolog也慢慢传进了美国。之后来说,一个AI从业者将会要同事掌握两种语言。本书将会在地十一章和十二章介绍Prolog的关键概念,并且在后续的章节使用这些概念。
另一个非常流行的Lisp方言是Scheme,但是主要是用在编程语言设计和技术的教育实验领域,不太用来进行大型项目的开发。Scheme会在第22章和23章出现。至于其他的Lisp方言,比如Franz Lisp,MacLisp,InterLisp,ZetaLisp,还有Standard Lisp,一般都被认识已经淘汰了。还有一些Lisp方言是作为语言的嵌入式扩展延续了下来。例如,GNU的Emacs就是使用elisp的,而计算机制图软件AutoCAD就是用Autolisp,一个Xlisp的衍生版本。在未来,或许Scheme会成为一个流行的扩展语言,他简短精悍,而且功能强大,并且有一个官方认可的标准定义。
有一个轶事是说,Lisp(还有Prolog)是专用的语言,而其他像Pascal和Care语言都是通用的语言。实际上,却是反过来说才对。Pascal和C语言都是专用的语言,他们在一个冯诺依曼结构的计算机上操作寄存器和内存。他们语法的主体是算是表达式和布尔表达式,当他们系统一些结构化数据结构的时候,对流程抽象和控制抽象的机制就显得匮乏了。另外,他们的设计师面向状态的,也就是,通过赋值语句改变变量的值来计算结果。
在另一方面,Lisp对于数学运算没有特定的语法。加操作和乘操作和其他操作是一样的,比如追加,字符处理都一样的语法。但是Lisp提供了所有编程中需要的东西:定义数据结构,函数,还有两者的结合。
这种赋值主导,面向状态的编程风格Lisp也是可以实现的,但是另外还有面向对象,基于规则,和函数式编程风格都在Lisp中有支持。这种灵活性来自于Lisp的两个关键特性:第一,Lisp具有强大的宏,可以用来扩展语言基础,通过宏定义,在新的编程风格发明之后,旧的语言逐渐死去,新的语言更迭,但是Lisp只要定义几个宏就能跟上步伐了。宏之所以强大是因为Lisp程序是由一种简单的数据结构组成的:列表,在早期,大部分的程序操作都是通过这种列表数据结构来解释的。当今,Lisp已经是编译更加常用于解释了,程序员也更加依赖Lisp 的第二个特性:函数。当然,其他语言也有函数,但是Lisp的特点在于他允许在程序运行中创建新的函数。
语言的灵活性让Lisp跟上了编程风格的变迁,但是更加重要的是,Lisp可以适应你的特定编程问题。在其他语言中,都是你的问题来适应编程语言,用Lisp则是可以用语言扩展来适应问题。
由于灵活性,Lisp已经成为了快速建模领域的翘楚,比如AI,图形和UI建模。Lisp已经是前沿编程领域的统治性语言,有些问题非常的复杂,以至于还没有一个清晰的解决方案来开始项目进程。AI的很多问题就是这样。
根据你的观点来看,Common Lisp的规模可能是优点,也可能是缺点。在David Touretzky写的入门书籍中,侧重点是放在简洁上。他选择去写一些简洁的轻量程序,而不是引入一个深奥的新特性,这种方法完全适用于初学者,但是本书的读者应该都已经过了初学者这一阶段。也就是说,新特性的引入将会在合适的时候暴露给读者。大多数时候,新特性就如介绍的一样来描述,但是有时候对于低级函数如何工作的解释会转移程序工作的理解的注意力。在给与被当做成人来看待之后,读者也需要承担起责任来,在不熟悉的来源寻找自己对术语的解释和理解。
本书的大概
本书共分为五个部分
第一部分是介绍Common Lisp编程语言本身。
第1章给出一个快速的入门,用一些精简的例子来展示Lisp的特性。有经验的读者可以跳过。
第2章是一些进一步的例子,展示Lisp原语是如何组织成一个程序的。初学者的话要认真的学习,即使是有经验的程序员也要看一下来熟悉一下编程感觉还有我的编程风格。
第3章是Lisp原语的概览。可以大概浏览一下,然后当做一个参考手册,后面有提到的再来查阅。
第一部分是有意做的很精简的,更多的空间来留给AI编程。那就意味着需要一个更加详细的参考手册(或者说在线帮助)来了解语言本身的一个深奥特性了。后面会推荐一些参考书。
读者或许也想参考地25章,那里有调试和错误处理的提示。
第二部分包含的是四个早期的AI程序,全部使用基于规则的模式匹配技术。一开始是相对简单的版本,之后会不断地改进他们渐渐变成跟家复杂的程序,读者可以一步步学习到高级的编程技巧。
第4章展示一个GPS(General Problem Solver)通用问题解决的重构。这个实现遵循STRIPS(stanford Research Institute Problem Solver)方法。
第5章描述的是ELIZA,是一个模仿人的对话程序。之后的第6章是在GPS和ELIZA中使用的技术总结的章节,后续的程序中都可以将他们作为工具使用。
第7章是一个解决高校水平的代数单词程序,叫做Student。
第8章开发一个MACSYMA程序的子集来做符号代数,包含微分和积分。对于高等数学有感不适的同学可以跳过这一章。
第三部分我们先放放AI,来看看一个通用的工具,是的编程更加的高效。那些对这部分精通的读者可能就是一个高级的Lisp程序员了。
第9章是一个高效技术的详细学习,将缓存,索引,编译和延迟计算连接起来。第10章包括的内容是低层的效率问题,比如使用生命,避免垃圾收集和选择正确的数据结构。
第11章介绍Prolog语言。这是有这双重目的:展示如何给另一个语言写一个解释器,还有介绍Prolog的重要特性,这样在合适的时候就可以使用了。第12章说的是Prolog的编译器可以比解释器快上20倍到200倍。
第13章概略介绍一下面向对象的编程风格,之后会浏览Common Lisp对象系统(CLOS)。
第14章讨论面向逻辑和面向对象编程的优点和限制,并且用第三部分的技术来开发一个知识展示系统。
第四部分是一些高级的AI程序
第15章使用第三部分的技术来实现一个更加高效的MACSYMA实现。他会使用一些权威格式的概念,并且使用一系列更加特殊的函数来代替很通用的重写规则方法。
第16章是EMYCIN专家系统shell,一个后端的基于规则的链接系统。医疗专家系统MYCIN也会简要介绍。
第17章介绍多面体的Waltz行标记算法(使用Huffman-Clowes标记)。强制传输和回溯的方法也会讨论。
第18章展示一个出色游戏程序Othello。其中使用的技术,alpha-beta搜索,适用于很多种双人游戏。
第19章是自然语言处理的介绍。包括上下文无关的语法,自上而下或者自下而上的分析,图表分析,还有一些语义揭示和参考。
第20章是扩展之前章节的语义范围,并引入逻辑语法,用的是第11章开发的Prolog编译器。
第21章是一个适当综合的用逻辑语法格式描述的英语语法。这里会讨论一些现实的综合问题的简单想法。
第五部分包含的材料对于AI来说是次要的,但是对于Lisp程序员是很重要的。
第22章是介绍Lisp方言Scheme。开发一个简单的Scheme解释器,之后是一个完全的尾递归解释器,然后是一个显式操作continuation和支持call/cc的解释器。第23章是一个Scheme编译器。
第24章是介绍那些单独在ANSI Common Lisp中的特性。包括loop宏,还有错误处理,打印,序列化,包设备。
第25章是一个错误处理和调试的Lisp程序。
文献列出了200多个文件,综合索引。另外,附录提供了一个公开的可获得的Lisp程序目录。
如何使用本书
本书预期的读者范围是很宽的:任何想要成为一个高级Lisp程序员的人,任何想要成为一个高级AI从业者的人,下面是一些推荐的学习路径:
AI课程的入门导论课程:专注在第一部分和第二部分,机上至少一个第四部分的例子。
AI高级编程课程:专注在第一,二,四和五部分,跳过了一些不感兴趣的章节,时间上允许的话加上第三部分。
高级编程语言课程:专注在第一部分和第五部分,还有第三部分的一些选择章节。
¬ . For the Professional Lisp Programmer: Read as much of the book as possible, and
refer back to it often. Part III and chapter 25 are particularly important.
对于专业的Lisp程序员:尽量多看看书中的每一个细节,仔细复习。第三部分和第25章特别重要。
可以作为参考的书籍和文献
最精准的参考书籍是Steele写的Common Lisp the Language。从1984年到1990年,这本书就是Common Lisp的非官方定义,但是到了1990年,情况因为Common Lisp the Language第二版的问世而复杂了很多。这本书也是Steele写的,包含了ANSI子委员会X3J13的推荐部分,这些章节就是定义了一个Lisp的标准。这些推荐内容包括很多较小的改动和澄清定义,也有一些全新的材料,面向对象的编程,错误条件处理,还有loop宏。新的材料将书的厚度从465页增加到了1029页。
知道ANSI推荐的内容被完全接受,Common Lisp的用户一直处在一种分裂的,互相不兼容的标准中:就是原始的Common Lisp和ANSI Common Lisp。本书中的大部分代码是对两种标准都兼容的。一个ANSI函数最经常的使用的就是loop宏。ANSI函数map-into,complement和reduce也是用的,只是比较少。本书包括了所有函数的定义,即使是那些使用原始的Common Lisp系统的代码仍然可以运行。
Common Lisp the Language的确是一个成文的标准,但是有时候简练的语言或许会成为初学者的一个障碍。Common Lisp:the Reference,用很多实用的例子提供了完整的语言参考。Robert Wilensky写的Common LISPcraft和Artificial Intelligence Programming也包含了简短的Common Lisp函数的总结。他们都不是很综合,但这也可能是耗时,因为就会让读者更加直接的看到重要的函数(至少作者这么认为)。
看书的时候手边有个电脑是个很好的注意,自己来尝试和实践书上的例子。电脑是蛮有用的,因为Lisp是自生成文档的,通过函数apropos,describe,还有documentation。很多实现也提供更多的扩展文档。使用help命令或者菜单就可以获得。
下面我推荐五本入门的Lisp教科书。第一本比其他的更加挤出一些。
Common Lisp: A Gentle Introduction to Symbolic Computation,最最适合初学者的教科书,也包括那些不是学计算机的学生。
A Programmer's Guide to Common Lisp,适合那些有编程经验,但是没有Lisp经验的读者。
Common LISPcraft,更加综合,但仍然适合作为入门和参考书籍。
Common Lisp,Wade L. Hennessey,胡乱的包含了一些主题,但是他实现的启蒙讨论确实其他书籍没有的。
LISP第三版,包含了最全面的编程建议,但是不是一个综合的参考书。也许对初学者很困难。包括了一部分AI的例子。
还有一些书籍是展示如何写AI程序和工具的,但是没有一本是具有这本书的深度的。尽管如此,专业的AI程序员会想要熟悉下面的只是,根据增序排列。
LISP,同上
Programming Paradigms in Lisp,Rajeev SangaI,体现不一样的Lisp编程风格,展现一些有用的AI工具。
Programming for Artificial Intelligence,Wolfgang Kreutzer,Bruce McKenzie,包含了一些基于规则和模式匹配的基本系统,但是有Lisp,Prolog和Smalltalk的内容,因此没有介绍语言的细节。
.Artificial Intelligence Programming (2d edition),Eugene Charniak, Christo-
pher Riesbeck, Drew McDermott, and James Meehan,包括了150页的Lisp概览,之后是AI工具的高阶讨论,只是没有任何程序。
AI in Practice: Examples in Pop-11,Allan Ramsey,Rosalind Barrett,五个高级AI程序的高质量实现。不太凑巧的是使用的一种不流行的语言。
当前的文本结合了最后两个的长处:表述了真实的AI程序和构建程序所需要的工具。更进一步,表述是以一种增量的形式递进,一开始是简单的版本,之后会更加完整精致。
习题的一些小贴士
习题从头到尾都有。读者可以通过做这些习题来测试自己理解学习的水平。练习的分级是用规模来分级,分别是s,m,h,d,可以解释为题目的复杂程度或者是需要解决问题所花销的时间。
代号 | 难度 | 需要花销的时间 |
---|---|---|
[s] | 简单 | 秒 |
[m] | 中等 | 分钟 |
[h] | 困难 | 小时 |
[d] | 机器困难 | 天 |
从习题所需要花销的时间这个角度可以看出概念是不是很好地被理解了。如果对着在概念山不清晰,可能就需要花费几个小时的时间来理解一个m标记的问题。习题在每一个小节的末尾都会出现。
致谢
(这个就不看了,作者感谢了很多人,基本上都是我不认识的人)