2017 年,有关人工智能的新闻再次震惊世界。
《自然》杂志上发表的一篇论文显示,英国 DeepMind 公司研发出了“AlphaGo Zero”,它在仅输入围棋规则、未输入任何人类棋谱的情况下,通过自学,就具备了完胜“阿尔法狗”的超强棋力。
经过短短 3 天的自我训练,AlphaGo Zero 就强势打败了此前战胜李世石的旧版 AlphaGo,战绩是 100:0。而经过 40 天的自我训练后,AlphaGo Zero 又打败了 AlphaGo Master 版本。要知道,“Master”曾击败过世界顶尖的围棋选手,甚至包括世界排名第一的柯洁。
人类几千年的围棋成就被计算机通过 40 天自我训练超越。计算机的学习能力如此强大,让人不禁想要了解计算机如何一步一步进化到今天的样子。
第一台计算机:埃尼亚克
世界上第一台电子计算机“埃尼亚克”(ENIAC,即 Electronic Numerical Integratorand Calculator“,电子数字积分计算机”的缩写)诞生的时间是 1946 年 2 月 10 日。
早在第一次世界大战期间,诺伯特·维纳(Norbert Wiener)在马里兰州的阿伯丁军械界试验场工作,参加火炮火力表测算的时候,就痛感高速自动计算手段的必要性。
1940 年,美军军械部又找上门来,请维纳去阿伯丁重新制订火力表,说希特勒的战斗机速度很快,飞行员们又大耍曲线飞行、翻筋斗的特技,盟军的高射炮根本打不着他们,需要制订新的火力表。维纳认为用手工和器械算肯定不行,只有研制高速计算机。他特意给时任罗斯福总统科学顾问的万·布什写了一封信,提出设计新型电子计算机的几条原则:
不是模拟式,而是数字式;
由电子元件构成,尽量减少机械部件;
采用二进制,而不是十进制;
内部存放计算表;
在计算机内部存贮数据。
数字式的加法和乘法装置、电子管元件、二进制、自动程序运算和内部存储数据,对现代计算机的设计有着深远的影响。万·布什点头称是,于是人类历史上大名鼎鼎的高速计算机——“埃尼亚克”诞生了。
图 1:埃尼亚克 (图片来源:http://detail.zol.com.cn/picture_index_1740/index17395727.shtml)
现代计算机的诞生
艾伦·麦席森·图灵(Alan Mathison Turing)的可计算性把计算问题都变成了数学计算问题。有了维纳的控制论,计算机就可以根据指令自动执行计算任务。克劳德·艾尔伍德·香农(Claude Elwood Shannon)的思想体现在模块化和等价性。他提出所有的数学问题都可以等价为逻辑运算(简单的二进制运算),而逻辑运算可以使用简单的逻辑电路单元方便地实现。
有了以上前提,就可以绕开复杂的十进制计算机的思路(巴贝奇差分机于 1822 年研制成功,是世界上第一台“会制表的机器”。运算的精确度达 6 位小数,但由于当时的技术条件限制,巴贝奇终其一生也没有完成精度更高的十进制计算机。)转而使用大量简单的逻辑电路的组合,实现与十进制计算机等价的计算。
约翰·冯·诺依曼 (John von Neumann)从织布机的“卡片”得到启示,将控制计算机的指令与数据同时放在存储器中。参加埃尼亚克研制小组后的 1945 年,在共同讨论的基础上,冯·诺依曼发表了一个全新的"存储程序通用电子计算机方案"–EDVAC(Electronic Discrete Variable Automatic Computer 的缩写)。冯·诺依曼计算机由控制器、运算器、存储器、输入设备、输出设备五部分构成,这一结构奠定了现代计算机的体系结构理念:
计算机处理的数据和指令一律用二进制数表示;
顺序执行程序,即在计算机运行过程中,程序和被处理的数据首先存入主存储器(内存),计算机执行程序时,将自动地并按顺序从主存储器中取出指令一条一条地执行,这一概念称作顺序执行程序。
计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。
图 2:冯·诺依曼体系结构
冯·诺依曼体系结构计算机是为逻辑和数值运算而诞生的,在数值处理方面已经达到很高的速度和精度,但对非数值数据的处理效率比较低。随着计算机体系结构研究方面进展,越来越多的非冯计算机相继出现,如光子计算机、量子计算机、神经计算机以及 DNA 计算机等等。
值得一提的是神经网络计算神经网络计算机,它能够模仿人类大脑的判断能力和适应能力,具有并行处理多种数据的能力,可以判断对象的性质与状态,并能采取相应的行动。
计算机思维
我们所说的计算机思维是指,了解计算机解决问题的方法,把现实世界的实际问题抽象为计算机可以解决的等价问题(等价性)。把复杂问题分解为许多模块化的小问题,解决了这些小问题,就得到了问题的解(模块化)。在云计算时代,掌握计算机思维、高效地利用计算机解决问题具有更大的优势。
下面我们就用一个例子,来说明计算机将如何解决现实世界中的问题。比如,你要安排一次旅行(旅行商问题:Travelling Salesman Problem),途经 n 个景点,有多少种行程安排?在计算机世界里这类问题的时间复杂是最高的,随着 n 取值的增加,计算复杂度随着指数方式增长,时间复杂度为 O(n!)
我列举一下随着景点数增加计算复杂度的变化,你可以感受一下这个恐怖的速度:
当 n=30 结果约为 2.65 后面 32 个 0,如果按照每秒计算 5000 个结果(埃尼亚克加法速度)需要计算 1.68 百万亿亿年,已经远远大于宇宙年龄的 138 亿年。
为了简单起见,n 取值为 4。我们整理一下思路,首先我们在 4 个景点中选择一个,然后在剩余的 3 个景点中选择一个,直到最后一个景点。所有的可能性为 4x3x2x1=4!
这是人类的思维方式,而计算机善于使用递归方法解决问题,如果想得到 4!只要知道 4x3! 就可以了,如果想知道 3!只要知道 3x2!,类似地,需要知道 2!的话,知道 2x1!就可以了。我们知道 1!=1,这样我们就把问题自顶向下逐步分解为简单问题,最后当遇到 1!就结束递归给出结果。
递归算法实现
递归是一种优雅的问题解决方法,比如一个董事长如何管理好一个公司,使用递归方法的思路是董事长只要管理好下属的 CEO、CFO 等人;CEO 如何管理好一个公司呢?只要管理好公司的副总就可以了;以此类推,最后每个员工管理好自己手里的工作,就解决了如何治理好一个公司的问题。这样是不是很优雅的解决方案呢?
递归算法有两个关键条件,第一是基线条件(base case)告诉程序何时停止递归。第二是递归条件(recursive case)告诉程序何时调用自己。
如前所说,计算机将指令和数据存入内存,然后按照顺序执行。计算阶乘的递归函数 Python 代码如:
如果计算机遇到一条递归令,如何执行呢?这里需要引入一个计算机编程概念:调用栈(call stack)。因为计算机顺序执行的特点,当遇到调用另外一条递归指令的时候,(x*factorial(x-1))就把这条指令放入调用栈,然后处理新的 factorial(x-1)递归指令。(注意调用栈的特点是先进后出。
下面分析一下遇到 factorial(4)时调用栈的变化。
图 3:处理递归指令时调用栈的变化情况
同样的方法处理 factorial(3)返回递归调用 factorial(2),将 factorial(2)放入调用栈;处理 factorial(2)返回 factorial(1),将 factorial(1)放入调用栈;当处理 factorial(1)时返回为 1,这时的调用栈的出栈处理过程如下最后返回结果为 24:
图 4:调用栈出栈过程
设计模式:经典问题的解决方案
你知道什么是 GOF(“四人帮”,全拼 Gang of Four) book 吗?
1994 年,由 Erich Gamma(苏黎世大学计算机博士,Eclipse 项目主要技术负责人)、Richard Helm(墨尔本大学计算机博士,IBM Watson 研究员)、Ralph Johnson(伊利诺伊大学教授,模式和重构领域大神) 和 John Vlissides(斯坦福大学计算机博士,IBM Watson 研究员) 四人合著出版了一本名为《 Design Patterns - Elements of Reusable Object-Oriented Software 》的书(中文译名:设计模式——可复用面向对象软件的基础),这本书凝聚了软件开发界几十年经验的结晶,已经成为面向对象技术的“圣经”,对后面出现的极限编程(敏捷开发的一种方法)有着深远的影响。
书中介绍了 23 种设计模式,这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。设计模式并不高深,我们使用的 Java 语言中的 MVC 就是一种设计模式。通过设计模式,可以实现代码编写工作的工程化。设计模式代表了最佳实践,可以使代码更加容易增加新功能而不必改动原有代码。
设计模式的本质是在已有方案之上的渐进式重构(《重构:改善既有代码的设计》作者: Martin Fowler 原作名:Refactoring: Improving the Design of Existing Code),从而控制业务逻辑复杂度和代码实现复杂度。但是随着业务发展,代码的业务逻辑复杂度增加,如何控制代码的实现复杂度和相应的易理解性、可维护性和可扩展性也成了需要考虑的问题。在设计模式领域最常遇到以下两个误区:
在一开始写代码之前使用设计模式,这违背了“在已有的方案上发现更好的方案”的原则,如果没有动手写代码,何谈更好的方案呢?
使用太多设计模式。这会导致方案逻辑分层更加复杂,引入多余的接口和分层。如果业务复杂度不高,使用一个简单直接的方案就足够了。设计模式不是万能的,只有出现代码的坏味道才有必要使用设计模式。
为什么选择策略模式?
在 20 多种设计模式中选择一个适合的模式,的确是很困难的事情。这需要平时多练习和思考,考虑设计模式是如何解决设计问题的,研究相似的设计模式和模式之间是如何关联的。策略模式是设计模式中行为型模式类型的一种。
同时,策略模式也符合开闭原则(Open Close Principle),即在程序需要进行拓展的时候,不必修改原有的代码,实现软件特性的热插拔效果。策略模式对于解决 if 条件分支泛滥的问题有很好的效果。
先看一下已有的业务模型和解决方案:现在已知景点之间的路径所经历的距离、时间、和成本,而且景点个数有可能很大的情况下,我们如何选择旅行商算法呢?
穷举法,优点是实现简单,找到所有的可能路径后计算最适合路径,当然缺点是当 n 数量增加时复杂度呈指数级增长;
动态规划算法,会降低当 n 增加时的计算量,但是也不适合 n 过大的情况;
贪心算法,可以处理 n 很大的情况,但是有可能得出的是局部的最优解而不是全局最优解。
算法各有优劣,不能在设计软件时确定使用哪种算法。如何实现用户在程序运行的时候选择算法和评价策略呢?最简单的方法是使用 if 分支语句把所有可能的条件罗列出来,但是算法和评价策略的选择是相互耦合的,造成 if 分支语句泛滥。伪代码如下:
随着算法实现的增加和评价策略的增多,代码行数呈几何式增长。存在的一个关键问题是:很难在不改变已有代码的基础上增加新功能。因为这样会导致代码复杂性增加,难以维护。
下面是使用策略模式后,实现旅行商问题的类图。如果增加一种算法,只需要增加一个新的 Strategy 接口的实现类就可以了,不需要修改已有代码。而对评价原则的增加只需要实现不同的 ContextInterface()接口就可以了。这样的程序风格是不是比那个又臭又长、难于增加新功能的 if 语句优雅了很多呢?
总结
我们知道,计算机的设计思想发展经历了图灵的可计算性、维纳的控制论、香农的等价性和模块化,最后由冯·诺依曼提出了现在计算机的体系架构。很有趣的一个事实是,埃尼阿克的设计原则是由电子元件构成,尽量减少机械部件;而多年以后的今天,软件的设计模式(设计模式是经典问题的解决方案,可以使软件研发实现工程化)同时又参考了机械部件的特性——热插拔设计思想(开闭原则)。数十载光阴过去,计算机的发展就像是经历了一个轮回,只是一切已不再是原点。作为人类智慧成果的最佳代表,人工智能已经占据了时代的主流,人类社会生活的各大领域将继续因它而发生广泛而深刻的变化。前路漫漫,未来可期。
作者简介
万金,《DevOps 实施手册:在多级 IT 企业中使用 DevOps》译者
15 年知名外企与中国企业的 IT 从业经验,包括 IBM,华为,ThoughtWorks 具有 8 年云计算相关经验,多系统的研发和运维经验,敏捷和 DevOps 专家。公众号:DevOps 手册。
评论 3 条评论