无处不在的Python

无处不在的Python

1991年2月20号,后来被称为“仁慈的独裁者”的Guido van Rossum首次发布Python,当时这还只是作为一个名为Amoeba的操作系统项目中的辅助工具,Python甚至没有被正式立项。没有人会想到,Amoeba项目在1996年就停止开发,而Python却在20多年后发展成为最流行的编程语言之一。

(图片来自:http://t.cn/RHGUMx6

Guido van Rossum设计Python的初衷,是希望用它来简化系统管理工具的开发,这个目标确实达到了,大部分Linux操作系统以及macOS都内置Python,更不用说还有Fabric,Ansible, Saltstack这些基于Python系统的管理工具。然而,只把主业做好,从来不是Python的唯一目标。世界这么大,Python要去看看。

不务正业的Python

当Python不再仅仅作为系统管理工具,迈出跨界的第一步时,就再也停不下来,从Web开发到科学计算,从3D建模到人工智能,从嵌入式开发到云计算和大数据,几乎每一个角落,都能看到Python的身影。

系统管理工具

Python从一开始就是为了提高系统管理效率而开发的。内置的os模块可以在兼容大部分主流操作系统的情况下,提供基本一致的操作方式,系统管理员的脚本只需要写一次,就可以运行在不同的操作系统上。借助Python简洁高效的语法,Python在复杂系统管理任务上的开发效率要远远高于Shell,同时执行效率也是远高于需要不停进程切换的Shell。为了可以使用常用的Linux命令行工具,David Beazley用Python在几天之内在Windows上开发了一套Linux常用的工具集

对于复杂的系统管理工作,目前流行的配置管理工具,如Fabric,Ansible,Saltstack等,可以让运维人员使用几行命令,让成百上千台服务器俯首帖耳。随着DevOps运动的发展,“基础设施即代码”的理念越来越深入人心,借助于molecule或者testinfra这样的工具,开发者可以像运行单元测试一样,对基础设施的配置进行测试。

Web开发

全世界第一个网站在1989年上线,而万维网(World Wide Web)在1994年被发明出来。可以说,Python是和互联网一起长大的。作为动态语言,并且具有更高的抽象层次的Python和Perl,很快就被开发者们发现更适合用于开发网站,并在早期互联网的兴起过程中发挥重要作用。

随着互联网的发展,很多以互联网为生的编程语言被发明出来,比如Java,PHP,Ruby等,逐渐超越了Python和Perl这些前辈。不过Python在Web开发领域仍然占有一席之地。比如著名的一站式Web框架Django,轻巧的Flask,高并发性能的Tornado等。一些著名的网站,也是在Python技术的支持下运行着,比如Youtube、Reddit、Pinterest、Dropbox、豆瓣等。

科学计算

在Web技术大行其道,Java、PHP等编程语言来势汹汹之时,Python的注意力却没有完全放在Web上面,随随便便的就把先发优势给放弃,转而搞科学计算去了。

Python在设计之初的一个理念是,尽可能的用英语或者高中代数中约定的表达方式来编程,所以尽管一开始的应用场景并没有考虑科学计算,但学术界却盯上了Python。1995年,一个研究如何让Python支持高效矩阵运算的小组成立,Python的创始人——Guido van Rossum也在这个小组之中,为了让Python能够更好地支持矩阵操作,还专门设计和修改了一些Python语法。就在这一年,Numeric数值计算库发布,在2006年,Numeric正式改名叫做NumPy。

进入新世纪,围绕NumPy,更多科学计算相关的工具包被开发出来,比如用于科学计算的SciPy,用于符号计算的SymPy,用于数据分析的Pandas,用于数据可视化的Matplotlib,以及可以实时画图的交互式开发环境IPython Notebook等。在Matlab等专业软件主导整个科学计算领域多年之后,Python用近10年的时间,在学术界赢得了一席之地。而当其他通用编程语言在Web浪潮中厮杀结束,回头也想要到科学计算中抢一块领地时,却发现事情已经变得比当年要难得多了。

云计算

在后互联网时代,云计算和大数据是被提起最多的两个概念。互联网的普及,带来了信息生产和交换成本的大幅降低,也因此导致了信息大爆炸。为了进一步降低成本,并释放数据的价值,云计算和大数据技术应运而生。

提到云计算,不得不提的就是OpenStack,目前已经成为了事实上的开源私有云平台标准。OpenStack选择Python作为主要开发语言,其最主要的原因,是Python作为胶水语言,可以和操作系统很好的交互,同时集成其他语言编写的工具库也很容易,而且Python自身的生态足够健壮,像分布式任务管理,Web开发等都能够很好的支撑。出于同样的原因,目前市场份额最大的公有云平台AWS在发布之初就提供Python SDK支持。

大数据

大数据技术的兴起,让很多新技术有了露脸的机会。比如Scala、R、Julia这些语言,曾经都是小众语言,但因为他们在大数据方面的优势,吸引了大量的开发者。除了这些小众语言之外,Java和Python算是在传统强势编程语言中没有掉队的两个。

Python的不掉队,要得益于其在科学计算领域的积累,比如Pandas和SciPy,这些曾经用在学术界的工具,在大数据处理中同样变得重要。因此,像Spark, H2O这样的大数据处理工具,核心部分并不是Python编写,但为了满足工具使用者的需求,都花了巨大精力来开发Python接口。除此之外,完全基于Python的工具,虽然不多,但还是存在,比如Airbnb开发的数据可视化工具superset和任务编排工具airflow。

仿真模拟

在程序员圈子里一提到Python,第一反应就是“上手简单”。正是由于简单,每个人都会写一点,就算不会写,也可以看懂,因此大家并不以会Python为多么骄傲的事情。相比Python,程序员们更愿意炫耀自己擅长Clojure或者Haskell这样相对复杂的编程语言。

不过简单也有简单的好。正是由于上手简单,让Python成为各种仿真模拟软件首选的脚本语言。比如大名鼎鼎的图片处理工具GIMP,3D模型制作工具3ds Max,Blender,电影特效工具Houdini, Maya,电影后期合成工具Nuke等等,都以Python作为脚本。

人工智能

自从AlphaGo战胜李世乭开始,人工智能就火得一塌糊涂。然而,人工智能并不是什么新概念,其历史已经超过了半个世纪。在人工智能领域过去这几十年的发展中,传统的主流编程语言明明是Lisp,而后起之秀也是像Prolog这样的语言。但是当这一波人工智能真正开始流行起来时,人们发现,那些流行的框架和工具,要么是用Python写的,比如Theano,要么是C++写的,但是Python作为接口语言,比如TensorFlow,Caffe,MxNet等。仅有的非Python框架Torch,在2017年也抵不住压力,开发了PyTorch。

其他

在算法交易和量化投资领域,Python是最佳选择之一

在嵌入式领域,广受欢迎的程序员玩具树莓派,选择Python作为主要编程接口。

在软件开发管理中,构建工具Scons和事务追踪工具Trac虽然不算特别流行,但也处于活跃的状态。

尽管Python的主要应用场景都在命令行,但不代表其不能做桌面软件开发,借助于PyQt和Kivy这些图形库,Python也可以开发出漂亮的桌面软件。

借用一句流行语:Python这么厉害,咋不上天呢?

事实上,NASA确实在使用Python来开发软件,主要是用于系统集成和卫星、火箭等的测试自动化方面。

软件开发的万金油

看似Python没有在哪个领域成为最好的编程语言,但是Python在各个领域都数得上号。从最初的系统管理到互联网时代,云计算和大数据时代,再到现在的人工智能时代,只有“不务正业”的Python,在每一波浪潮之中都在。

在领域细分的时代,像Python这样的通而不专的语言在每一个领域之内收到的重视都不如领域中的主要语言。然而时代在变,数字化浪潮伴随着人工智能与物联网的东风,汹涌而来,企业为了能够快速应对,不得不做出改变。

业务没有线上与线下之分,只有已经数字化和即将数字化之分。

为了跟上需求的变化,原本的“分析-设计-开发-集成-测试-上线”这个瀑布式开发流程被不断压缩。在敏捷软件开发运动的推动下,首先是开发和测试的界限不再明确;后来DevOps运动兴起,开发与运维之间的墙被打破;实时大数据架构和数据驱动业务的出现,让曾经原本相对独立的数据团队,也要更紧密的参与到业务开发中。

当所有的角色都在同一个团队中,为了一个业务目标而写代码的时候,用同样的语言进行交流就变得异常重要。这时候,不掉队的Python,优势就显现出来了。“不务正业”的Python曾在不同的领域中都占有一席之地,这让分属于不同的部门、不同角色的团队成员有机会使用Python来统一工作语言。不论是开发、测试、运维,还是数据科学家,Python都是一个虽然不是最好、但还不错的选择。

低成本试错的最佳选择

曾经,编程本身就是一项业务;现在,编程只是一个用来实现想法的工具。当年可以因为一个新概念,炒出很多新语言,比如面向对象技术的出现,并没有给企业的业务带来什么巨大创新,但却掀起了一波技术浪潮;而现在,一项新技术被提出来,企业首先要考虑能够在业务上带来什么收益。

像人工智能这样的新概念(或者说是老概念新提法),对于企业来说,本身的门槛相对较高,业务收益尚不明朗,如果要投入大量的人力和资金去学习全新的技术,对企业来说,风险是非常大的。然而为了创新,企业不能故步自封。尤其是作为追赶者或者初创企业,在互联网赢家通吃的规则下,创新甚至和企业的生死息息相关。因此,低成本试错是企业成功的关键。

利用开源软件或云服务,避免重新创造轮子,对于企业降低试错成本是非常有效果的。因为Python语言本身非常适合作为服务集成的“胶水”语言,所以很多开源软件和云服务都选择Python作为接口语言(比如Spark、TensorFlow、AWS等)。如果企业希望利用这些技术进行创新试验,Python是一个非常好的选择。

Python之后是谁?

Python的流行,并不是从一开始就设计好的剧本。只是在特定的时间,对于编程语言有特定的需求,而刚好Python满足这样的需求。Python作为一门编程语言,并不比其他编程语言更优秀。

技术没有最好,只有最合适。

如果要预测未来什么编程语言更流行,就必须要知道未来对于编程语言有什么样的需求。当前对于编程语言的需求是能够让不同角色使用,并且可以集成或者重用已有技术或服务,而Python上手快,可读性高,和其他语言的互通性好,刚好能够满足这样的需求。

如果未来某一天,对于程序执行速度的关注,超过了其他需求,也许C语言或者Go语言,会成为更流行的语言;如果是对于编程语言的抽象能力有更高要求,也许Elixir、Scala或者Clojure会更加流行;如果是对程序员数量的需求更高,就需要降低入门难度,也许Java还会再次流行;再或者,未来对于编程的需求,对于目前所有编程语言来说,都没办法满足,就会有新的编程语言出现。所以,未来的编程会走向何方,要看企业对于编程提出什么样的需求。

结束语

也许Python还有很多缺陷,在每一个方面都不是特别出色,但是它可以串联所有的关键技术,降低引入新技术的成本,可以让团队的成员具有共同语言,企业何乐而不为呢?

纯粹的编程已经不再,玩跨界,Python可以,你可以么?


更多精彩洞见,请关注微信公众号:思特沃克

Share

洗白“黑科技”深度学习

2016年3月,由DeepMind研发的AlphaGo以4:1的战绩完胜世界围棋冠军李世乭,拉开人工智能疯狂席卷IT圈的序幕。5月,Tesla在开启Autopilot辅助驾驶模式后出现首例致死事故,将人工智能推上了风口浪尖。霍金、比尔盖茨、埃隆马斯克等科技大咖相继发言,讨论人工智能是否会对人类未来发展不利,OpenAI应运而生。10月,HBO电视剧《西部世界》的上映,再一次引爆大众对于人工智能的关注。由于媒体的夸张宣传,人们甚至觉得《终结者》中的世界就要到来。

伴随着这一波人工智能浪潮崛起的,是一种被称为“深度学习”的技术。不论是AlphaGo、自动驾驶,抑或是其他近期的人工智能突破,我们都能在其背后看到深度学习的影子。深度学习就像是人类打开的潘多拉盒子,放出了黑科技,席卷整个科技行业。

Gartner2016年新技术发展曲线报告中可以看出,和人工智能相关的技术,要么处于泡沫期的顶峰,要么处于正在走向泡沫期的路上。为什么人工智能会突然爆发?它会不会只是媒体吹出来的一个泡沫?作为人工智能再次兴起的核心技术突破,深度学习到底是什么“黑科技”?

从信息时代到智能时代

近20年间,互联网经历了一场“大跃进”。根据IDC做的统计:在2006年,全世界创造的数据量约为161 EB,预计每18个月这个数字会翻一番,在2010年达到988 EB(

(注:1024MB1GB,1024GB1TB,1024TB1PB,1024PB1EB,1024EB1ZB))。而事实上,根据IDC后来的报告,2010年达到的数字是1227EB。最近的一次IDC互联网报告是在2014年,其中提到2013年全世界产生的数据是4.4ZB,到2020年,这一数字将达到44ZB

物联网的发展正在加速这一过程。2013年,全世界接入互联网的设备将近200亿,到2020年,这一数字将达到300亿。而全世界所有的“物体”总数,大概是2000亿。这些设备通过其内嵌的传感器监控并收集数据,并上报到云计算中心。

云计算、大数据和物联网的窘境

我们正处于“数字化一切”的时代。人们的所有行为,都将以某种数字化手段转换成数据并保存下来。每到新年,各大网站、App就会给用户推送上一年的回顾报告,比如支付宝会告诉用户在过去一年里花了多少钱、在淘宝上买了多少东西、去什么地方吃过饭、花费金额超过了百分之多少的小伙伴;航旅纵横会告诉用户去年做了多少次飞机、总飞行里程是多少、去的最多的城市是哪里;同样的,最后让用户知道他的行程超过了多少小伙伴。这些报告看起来非常酷炫,又冠以“大数据”之名,让用户以为是多么了不起的技术。然而,我们实际上在只是做一件事:数(shǔ)数(shù)。

实际上,企业对于数据的使用和分析,并不比我们每年收到的年度报告更复杂。已经有30多年历史的商业智能(Business Intelligence),看起来非常酷炫,其本质依然是数数,并把数出来的结果画成图给管理者看。只是在不同的行业、场景下,同样的数字和图表会有不同的名字。即使是最近几年炙手可热的大数据处理技术,也不过是可以数更多的数,并且数的更快一些而已。

比如我们每天都在使用的搜索引擎。在自然语言处理领域,有一种非常流行的算法模型,叫做词袋模型(Bag of Words Model),即把一段文字看成一袋水果,这个模型就是要算出这袋水果里,有几个苹果、几个香蕉和几个梨。搜索引擎会把这些数字记下来,如果你想要苹果,它就会把有苹果的这些袋子给你。

当我们在网上买东西或是看电影时,网站会推荐一些可能符合我们偏好的商品或是电影,这个推荐有时候还挺准。事实上,这背后的算法,是在数你喜欢的电影和其他人喜欢的电影有多少个是一样的,如果你们同时喜欢的电影超过一定个数,就把其他人喜欢、但你还没看过的电影推荐给你。

搜索引擎和推荐系统在实际生产环境中还要做很多额外的工作,但是从本质上来说,它们都是在数数。那么,数数有什么问题么? 有。

数字的发明,让我们的祖先可以用简便的记法记录下物体的个数。比如有一个放牛娃,家里最初只有3头牛,他可以记住每一头牛的样子,每天回到家,扫一眼牛棚,就知道家里的牛丢没丢。后来,因为家里经营的好,放牛娃的牛有100头之多,随之而来的是无法记清每头牛的烦恼。如果没有发明数字,他可能要把每一只牛照着模样刻在石壁上,每天拉着一头头的牛到石壁边去对照,看有没有丢牛。当有了数字,放牛娃只需要记下“100”这个数字,再画一头牛就够了,以后每天数一下牛群里面牛的数量,再看看石壁上的数字是否一样。

数数,让放牛娃的工作变得简单,他不用把每一头牛的样子都刻在石壁上,减轻了工作量。可是这种办法并非万无一失,有一天,附近一个游手好闲的小混混从别处找来一头病牛,混到了放牛娃的牛群之中,同时又牵走了一头壮牛。放牛娃在一天结束、清点自己的牛群时,发现还是100头牛,不多不少,就心满意足的回家睡觉了。然而他却不知道,他的一头壮牛被小混混用病牛换走了。

对于主要以数数方式来使用数据的企业,同样面临着无法关注数据细节的问题。当数据量比较小的时候,可以通过人工查阅数据。而到了大数据时代,几百TB甚至上PB的数据在分析师或者老板的报告中,就只是几个数字结论而已。在数数的过程中,数据中存在的信息也随之被丢弃,留下的那几个数字所能代表的信息价值,不抵其真实价值之万一。过去十年,许多公司花了大价钱,用上了物联网和云计算,收集了大量的数据,但是到头来却发现得到的收益并没有想象中那么多。

深度学习的困境

我们所知的深度学习,本质上应该叫做“基于深度神经网络的机器学习”。为什么用了”深度学习”这个名字,而不是深度神经网络呢?其中一个原因是,“神经网络”这个词是一个禁忌。

神经网络算法的提出可以追溯到20世纪40年代。这一算法起源于生物学中对于动物大脑神经元的研究,因此早期也被称为人工神经网络(Artificial Neural Network)。最初的神经网络是逻辑电路搭建,到了60年代,由于计算能力不足,无法构建大规模神经网络,而小规模神经网络的表现又差强人意。随着其他机器学习方法的提出,很多科研人员开始转向其他方向,人工神经网络的研究陷入了停滞。

图1 典型神经元的结构

20世纪80年代,随着通用计算机的出现,人工神经网络的研究经历了一波复苏。在这个阶段,反向传播(Back Propagation)算法逐渐成熟。直到今天,反向传播算法都是训练神经网络的最主要方法。然而,依然受限于当时的硬件条件,神经网络的规模依然不大。同时,以支持向量机为代表的基于核方法的机器学习技术,表现出了不俗的能力,因此,大量科研人员再一次放弃了神经网络。

然而并不是所有的科学家都放弃了神经网络。在那些留守的科学家中,有一位刚刚拿到人工智能学位不久的年轻人,他曾在剑桥大学国王学院拿到实验物理学的学士学位,因为对认知科学抱有浓厚的兴趣,因此选择专攻人工智能。他坚信“既然大脑能够工作,神经网络算法也一定能工作。大脑不可能是被编程出来的。”当他的研究成果并不如预期时,他总是对质疑他的人回应:“再给我6个月,到时候我会证明它是可以工作的。”当几个6个月过去,神经网络的效果依然不好,他会说:“再给我5年,一定能行。”又是好几个5年过去,神经网络真的成了。这个人就是Geoffrey Hinton,深度学习之父。

神经网络在最初的几十年内都没有表现出过人的性能,主要面临着两个困难。首先是计算性能不足。实际上,在90年代,Hinton以及他的学生就已经在试验和后来深度神经网络类似的结构,其中就有大名鼎鼎的Yann LeCunn,他所提出的神经网络结构就是现在的“LeNet”。但是,增加神经网络的深度,就会让神经网络的训练速度变慢。在那个内存不过几十MB,GPU还没有出现的年代,要训练一个小规模的深度神经网络模型,需要花上数周甚至数月。

其次是训练数据不够多。在机器学习领域流传着一个传说,叫做“维度诅咒(Curse of Dimensionality)”,随着特征维度的增加,算法的搜索空间急剧变大,要在这样的特征空间中寻找适合的模型,需要大量的训练数据。神经网络要解决的问题,通常具有成千上万维的特征,我们假设有1000维特征,每一维特征有100个候选值,那么这个特征空间就是100的1000次方,可以想象,要在如此大的特征中寻找一个模型,需要多少数据,而这个特征空间规模不过是深度学习问题中比较小的。幸好我们所在的这个世界,可以通过一个非常有用的先验假设进行简化:我们这个世界的事物都是通过更小的事物组合而成的。我们知道,所有的物体都是由分子构成,分子由原子构成,原子由质子、中子和电子构成,等等。不仅实际的物体满足这一先验假设,抽象的概念也一样如此。因此深度神经网络利用了这一假设,通过将网络层数加深,每一层神经元都是前面一层神经元输出的组合,通过这样的假设,将整个搜索空间大大减小。然而,训练深度神经网络依然需要大量的数据,才能得到一个比较好的结果。

图2 深度神经网络构建层级化特征

取深度学习之长,补传统软件之短

来到21世纪,正如我们前面所说,在21世纪的十几年间,我们的数据量和计算能力都增长了不少,这为神经网络证明其能力提供了条件。事实上,在Hinton的带领下,神经网络在2000年之后逐渐开始在一些比较小众的领域获得成功。而真正对学术界产生震动的,是2012年,Hinton实验室的学生Alex Krizhevsky用基于深度神经网络的方法,在ILSVRC(ImageNet Large Scale Visual Recognition Challenge)图像识别挑战赛中一战成名,其网络结构也被人们称为AlexNet。在那之前,图像识别领域已经被基于支持向量机的算法霸占多年,而AlexNet不仅打败支持向量机,而且将错误率降低了将近一半。自此之后,图像识别算法的冠军就一直是深度学习算法。

图3 基于深度学习的算法,让图像识别精度在过去几年大幅度提升

除了在图像识别领域获得巨大成功,在短短的几年之内,在各种场景下,基于深度神经网络的算法都横扫其他机器学习算法。包括语音识别、自然语言处理、搜索引擎、甚至自动控制系统。DeepMind的Alex Graves团队在2014年的一篇论文中提出的神经图灵机(Neural Turing Machine)结构,以及后来在2016年提出的DNC(Deep Neural Computer)结构,甚至可以成功学习简单的算法,这不禁让我开始遐想有一天,计算机可以自己给自己编程。

深度学习给企业带来的影响

深度学习的端到端架构,降低了企业引入深度学习的成本

相比其他经典的机器学习算法来说,深度学习需要人工干预的比例小很多。比如,在经典机器学习中,特征工程占用了科学家们开发算法的大部分精力,对于某些问题,比如图像识别、语音识别,科学家们花了几十年时间来寻找性能更好的特征。深度学习改变了这一情况。深度学习接收原始数据,在神经网络的训练过程中,寻找最适合的特征。事实证明,机器自己找到的特征,比人类科学家用几十年找到的特征性能更好。正是由于深度学习的这一特点,深度学习的一个明显趋势,是端到端的解决问题。

比如下图所示的语音识别。经典语音识别需要对原始数据提取特征(比如梅尔倒谱系数),将提取到的特征建立时间序列模型(比如隐式马尔科夫模型),得到声学模型,然后根据发声词典,将输入信号映射为一些音节,最后,根据预先定义好的语言模型,将音节转换为有意义的文字。这其中,特征提取、时间序列建模、发声词典等都需要人工预先定义好,对于不同的语种,比如中文和英文,还要使用不同的模型。

图4 端到端的深度学习越来越流行

在深度学习流行起来的初期,语音识别流程中的特征提取以及时间序列建模等,都用深度神经网来替代了。到了最近几年,科学家发现,对于语音识别这样的问题,甚至流水线都是多余的,直接将原始数据接入到神经网络中,就能输出我们期望的文本,这样的结构要比人工设计流程得到的结果更好。

这种端到端的深度学习,在其他领域也被验证是可行的。比如自动驾驶技术,在MIT的自动驾驶项目中,就是用端到端的深度强化学习技术,输入是路况的所有信息,输出就是对汽车的指令,比如加速、刹车、方向盘角度等等。

深度学习的端到端架构,降低了企业引入深度学习的成本。过去,企业要引入机器学习,需要招聘一个科学家团队,同时还需要一个开发团队,将科学家所设计的算法模型翻译成生产环境代码。这样的开发模式不仅成本高,响应速度也非常慢。而深度学习的端到端架构,对于科学家的要求降低了很多,而且,由于不需要通过特征工程来寻找特征,开发周期也大大缩短。对于很多规模不大、但希望朝智能化演进的企业来说,先尝试引入深度学习是个不错的选择。

智能时代的产品研发将由算法驱动

在传统的软件开发中,用户的交互方式是确定的,业务流程也是确定的;当我们尝试将人工智能技术融入到产品中,需要面对大量的不确定性。

首先是和用户的交互方式将发生巨大变化。过去,我们通过按钮、表单等控件来确保用户是按照产品设计师的思路来使用软件的。随着深度学习在图像识别、语音识别、文本识别等方面的快速发展,未来,我们的软件在用户的交互过程中,将更多的使用自然语言、语音、手势、甚至是意识。具备触屏功能的智能手机的出现,掀起了一波用户体验升级的浪潮,所有应用开发者都在寻找在触屏应用中更自然的交互方式。而这一次,用户交互方式的升级将比触屏带来的影响更加深远。Amazon在这方面做出了开创性的尝试,其智能音箱Echo在设计之初就特意去掉了屏幕,让语音变成唯一的交互渠道。Facebook Messenger在发布了聊天机器人的平台之后,同样也给出了设计指导,开发者将以一种全新的方式去思考,软件应该如何与用户更好的沟通。

其次是企业的业务决策会越来越多的依赖人工智能。过去,企业要基于数据进行决策,需要搭建数据仓库,开发ETL程序,制作报表,等待分析师从各种各样的报表中找到有价值的信息,最后做出业务改进的决策。现在,我们有了深度学习这把强大的锤子,可以让我们对数据有更加深刻的洞察力;同时,实时流式大数据架构让我们可以更快速地做出反馈。企业如果可以利用好这两大利器,将释放出更大的潜力。

图5 算法驱动的产品架构

IT软件的运维也将迎来新的革命。软件系统越来越复杂、规模越来越大,对于运维人员的挑战就越来越高。在IT行业的早期,运维更多是修复性工作。后来,为了减少系统修复带来的损失,运维工作开始强调预防性,即根据历史维护记录,找到系统故障的规律,提前进行修复。然而,据统计,有规律的故障只占所有故障中的18%。因此,我们需要更好的识别并预测故障的能力,即预测性运维。深度学习在自动学习特征方面的优势,注定其在预测性运维领域也会发挥很大的作用。

深度学习不是终结者

深度学习在这几年越来越流行,尤其是在AlphaGo击败人类棋手之后,一些媒体甚至开始营造人工智能可能会取代人类的紧张氛围。然而,就目前的研究成果来看,想要发展出科幻电影中具备独立思考能力、甚至可以和人类谈恋爱的人工智能,还有很长一段距离。且不说情感、人格这类形而上的概念,尚未有严格的科学定义,更不用提人工智能能否具备这些属性。单从目前人类的工作是否会被人工智能所替代来看,至少当前的深度学习还有很多局限性,要想打破局限,让深度学习具有更大的作用,还有很多挑战等待解决。

挑战1:多功能神经网络

尽管深度学习已经让神经网络具备了很大的灵活性,然而深度学习目前还只能做到一个神经网络解决一个问题。比如训练一个神经网络要么只能识别图片,要么只能识别语音,不能同时识别。比如,我们可以给一个神经网络看一张图片,神经网络可以识别到图片中是猫还是狗;我们也可以给另一个神经网络听一段声音,这个神经网络可以识别出是声音中是猫还是狗的叫声;但是,现在还没有一个神经网络,既能通过视觉识别物体,还能通过听觉识别物体。尽管借助多任务学习(Multi-task learning)技术,神经网络可以在识别图片类别的同时,识别轮廓、姿态、阴影、文字等等相关的内容,相比我们人类多才多艺的大脑,现在的深度神经网络可以说是非常低能。

目前如果需要一个应用支持不同的能力,必须组合使用多个神经网络,这不仅对于计算资源是巨大的消耗,不同神经网络之间也难以形成有效的互动,比如图片中的狗、声音中的狗和一段文字中出现的狗,在各自的神经网络中都有不同的表示方式。而对于人类来说,这些其实都是同一个概念。

如何让神经网络能够同时实现多个目标,目前科学家们也都还没有答案,不过从人类大脑得到的启示是,通过某种方式,将负责不同功能的神经网络连接起来,组成更大的神经网络,也许可以解决这个问题。Google在ICLR 2017上的一篇论文,通过一个系数门矩阵将多个子网络连接起来,是在这个方向上的一个有趣尝试。

挑战2:终极算法

Pedro Domingos教授在《The Master Algorithm》一书中回顾了机器学习的5大流派:符号主义、连接主义、进化主义、贝叶斯主义、分析主义。这5类机器学习算法并没有绝对的优劣,不同的算法适用于不同的场景和问题。比如以神经网络为主的连接主义算法,对于视觉、听觉这类感知问题,具有更好的效果,但是却不擅长逻辑推理。而逻辑推理刚好是符号主义算法所擅长的。书中提出了一种终极算法,能够结合这五种主流机器学习,可以适用于更大范围的问题域。

深度学习正是连接主义发展而来,不过深度学习提供了可扩展性非常强的框架,以深度学习为基础,很有希望将其他几类机器学习算法融入进来。OpenAI在进行深度强化学习的实验过程中发现,使用进化主义的遗传算法替代经典的反向传播(BP)算法,模型可以更快的收敛,性能也更好;Google基于TensorFlow框架开发的概率编程工具库Edward,证明了概率图和神经网络可以无缝的结合在一起。

从目前的趋势看来,终极算法非常有希望。不过,事情不会总是这么顺利。当年物理学家们希望寻找大统一理论来结合自然界四种基本力,电磁力、强核力、弱核力很快就结合到一个模型中,然而最后引力却怎么都找不到结合的办法。当我们找到终极算法的时候,通用人工智能(Artificial General Intelligence)就离我们不远了。

挑战3:更少的人工干预

深度学习让机器学习不再依赖于科学家寻找特征,但调试深度神经网络依然需要很多人工的工作,其中最主要的就是调参。这里所说的调参,不是调节神经网络的每个神经元的参数,而是指调试超参数。超参数是用来控制神经网络的描述性参数,比如,神经网络的层数、每一层的神经元个数、学习率(Learning Rate)的大小、训练时间的长短等等。这些参数的微小差异,会给最终模型带来巨大的性能差异,而这部分工作大多需要靠经验完成,很难总结出有效的最佳实践。

然而这一状况在未来将会有所改善。既然神经网络可以用于学习参数,就应该可以学习超参数。DeepMind提出的Learning to Learn算法,使用神经网络来学习和调整学习率,可以让神经网络更快的收敛到理想的精度。正所谓,授人以鱼不如授人以渔。

结语

深度学习的火爆,吸引了越来越多的计算机科学家投身到这一领域。如果以目前学术成果的发展速度来预测,也许不超过10年,上述深度学习的挑战就会被解决。与其杞人忧天的担心人工智能会毁灭人类,不如提前布局,做好准备,迎接智能时代的到来。智能时代的IT系统,将是”具备自主性的IT系统,能够根据人类制定的目标,针对复杂业务变化,做出认为的最优选择。“如果深度学习的几大挑战能够在几年之内被解决,将大大加快未来IT系统实现的脚步。


更多精彩商业洞见,请关注微信公众号:ThoughtWorks商业洞见

Share

未来的IT系统:DITS – Differentiable IT System

不是我不明白,这世界变化快 ——《不是我不明白》崔健

IT系统的困境

IT技术是用来管理和处理信息的技术,IT系统是使用IT技术构建的系统。经过几十年的发展,IT系统越来越复杂,动辄几百万上千万行代码。然而,现代IT系统依然非常脆弱,一个业务上的变化,就可能导致一个子系统的重新设计;而一个颠覆性业务模式的出现,可能会导致整个IT系统被替换。

1-programmer

当我们在观察或了解昆虫以及其他低等生物时,会产生一种毛骨悚然的感觉:活动热闹非凡,可家里一个人都没有!——丹尼尔·丹尼特

设计实现IT系统的最大挑战来自于需求的变化。各种编程语言、设计模式,以及软件工程实践等,都在试图解决这个挑战。当前,用效率最高的软件开发方式——使用具有高级抽象能力的编程语言,采用响应式编程(Reactive Programming)范式和微服务架构,引入DevOps和持续交付等工程实践等开发一个新需求,也要以天为单位;如果要开发一个新的组件,可能需要以周为单位;而如果要开发一个全新的业务模式,至少要以月为单位。

对大部分程序员来说,各种新技术层出不穷,每一项都打着提高开发效率的旗号,然而事实是:程序员的工作并没有因为技术的进步而减少,相反,我们感觉自己越来越累,不仅要学习新技术,同时还要应对越来越频繁的需求变化。

于2000年左右出现的电子商务颠覆传统零售行业大概用了10年时间,2006年左右出现的社交网络颠覆门户网站大概用了五六年的时间,2012年左右出现的共享经济颠覆传统商业模式大概用了3年时间。一个合理但未经验证的猜想是:颠覆性业务模式的出现频率以及颠覆速度似符合摩尔定律,即每18个月,颠覆性业务模式的出现频率和颠覆速度会提高一倍。

2-speed-up

不管我们使用什么样的编程语言,采用什么样的架构,引入什么样的工程实践,一个业务需求的生命周期都要包含产品规划、业务分析、方案设计、开发、测试、上线。这样的研发周期,在不远的未来,将无法跟上业务需求变化的速度。

自然的启示

《自私的基因》一书提到,基因为了将自己的遗传信息传递下去,进化出各种载体——即生物体——以应对环境的变化,让基因得以生存并传递下去。对于一些早期的生物载体,基因通过硬编码的方式,将一些复杂功能固化到生物体的遗传信息中。比如有一种名为“掘地蜂”的昆虫,在产卵时会严格遵照预定的程序:

  • 用自己的刺麻痹一只蟋蟀;
  • 将蟋蟀拖到挖好的洞口;
  • 进洞查看,确保安全;
  • 回到洞口,把麻痹的蟋蟀拖进洞;
  • 产卵;
  • 密封洞口,走人。

这套程序看起来非常复杂,我们不禁要感叹,一个简单的生物体,竟然可以完成这么复杂的事情。然而,当掘地蜂进入洞穴查看时,实验人员将麻痹的蟋蟀移动几英寸,等掘地蜂从洞口出来,并不会直接将移动过的蟋蟀拖进洞,而是将其拖到洞口,然后再一次进洞检查。如果期间蟋蟀又一次被移动,哪怕只是一英寸,掘地蜂也会重复前面的过程。正如丹尼特所说:看起来“活动热闹非凡,可家里一个人都没有!”

3-lonely

《机器人叛乱》对这一观点提出了进一步的扩展。为了让生物载体能够更好地适应快速变化的环境,基因发展出了弱约束目标。相比硬编码复杂行为,基因只是给其载体下达要完成的目标,而不再是具体的操作计划,由载体针对当时的环境做出符合目标的最佳选择。如果基因会说话,基因会给我们的大脑发出类似的指令:“事情发展太快,没办法一一详述,大脑,你只需在考虑我们的通用目标(生存和繁殖)前提下,做你认为最好的选择。”

生物体适应能力越来越强,同时也导致环境变化越来越快,反过来迫使我们不得不发展出更强的适应能力,就像进入正反馈的自动控制系统,系统开始进入不稳定状态,直到崩溃,然后重新寻找平衡。

IT系统的转折点

程序员之于IT系统,就如同基因之于载体。基因在生物进化早期也遇到过这样的困难,尽管进化出了非常复杂的生物载体,但是对于颠覆性的环境变化(比如火山爆发、地壳运动、彗星撞地球等),这些载体依然表现的不堪一击。

5亿年前的寒武纪大爆发,大量生物出现在地球上,基因像是想通过这样的方式,发展出复杂的载体,来应对变化的环境,然而,复杂并没有带来期望的适应能力。结果大家都知道了,6500万年前,基因进化出来的最复杂、最具有统治力的载体——恐龙——灭绝了。今天我们发明了新语言、新框架、新实践,期望通过这样的方式可以更快的开发出更复杂的系统。然而,再复杂的系统,面对不停变化的混沌世界,依然不堪一击。

4-nature-cover-animation

是时候换个思路了。

我们也该考虑类似基因的弱约束目标,让IT系统具备一定的自主性,可以根据人类制定的目标,做出它认为最优的决策。具备自主性的IT系统,能够根据人类制定的目标,针对复杂业务变化,做出认为的最优选择。 这就是未来的IT系统。

DITS – Differentiable IT System

目前最有希望能够让IT系统具备自主性的技术,来自深度学习。深度学习通过建立层级化表征的方式,让计算机可以从经验中学习并理解这个世界。

[^1]深度学习和经典机器学习方法的最大区别在于,深度学习不需要某个领域的专家来设计特征提取规则,可以在输入数据和业务目标之间实现端到端的模型训练。对于深度学习的使用者来说,只需要准备好训练数据,设定好训练目标(即设定Loss Function,损失函数),确定深度神经网络的结构,然后进行训练,就可以得到满足目标的神经网络模型。

这其中的魔法发生在训练过程中。我们可以将复杂的神经网络用计算图的方式表达出来。在神经网络的末端,也就是输出层,通过损失函数计算出实际值和目标值之间的差别。得益于微积分中的链式法则,损失函数的结果误差可以在计算图中反向传播(Back Propagation),一层一层的调整神经网络参数,让神经网络在下一次的计算中距离目标更近一步。^2

counterpropagation

在反向传播的过程中,每一层网络参数的修正值是通过计算误差和参数之间的梯度得到的。一个函数在某个点存在梯度,需要在这一点保证函数可微分,也就是本文题目中的Differentiable。所以,深度神经网络的学习能力,来自神经网络的可微分性(Differentiable)

如果我们能够让IT系统具备可微分性,IT系统就可以基于人类指定的业务目标,从历史数据中学习,并不断更新自己,从而具备自主性。当变化来临,系统可以在没有人类参与的情况下自主学习并调整,最终适应变化。写出具有可微分性程序的编程方式,叫做可微分编程(Differentiable Programming)。[^3]这样的IT系统,称之为“可微分IT系统”,即DITS,Differentiable IT System。

当前,大部分AI技术只是作为独立的服务存在,用来辅助传统IT系统,比如图像识别、语音识别、自然语言处理等。而DITS本身就是一个AI系统,或者说,是一个内建AI能力的IT系统。

现在,我们经常使用A/B测试来改进程序,然而开发A/B测试代码、设计策略、收集反馈、分析、修改代码,一系列过程下来我们发现,用户点击红色按钮的比例比蓝色按钮多出30%,于是我们决定按钮使用红色。这就是所谓数据驱动的产品运营。

而对于DITS来说,我们可以设定弱约束目标为提高用户点击按钮的比例。每当用户执行一次操作,DITS就会计算这次操作和期望目标之间的差别,调整内部神经网络参数,从而自主地改进用户体验。更进一步,DITS甚至还可以对不同的用户展示不同的颜色,比如对于喜欢点红色按钮的用户展示红色,对于喜欢点蓝色按钮的用户展示蓝色。

这还只是一个具备自我学习能力的IT系统所做事情的一个简单例子,对于更复杂的业务场景,DITS所能做的,会超出我们的想象。

实现DITS

基于可微分性构造计算机程序,让计算机具备学习能力,目前是深度学习领域的热点。早期的尝试来自于DeepMind在2014年发表的神经图灵机(Neural Turing Machine,NTM)[^4],2016年DeepMind在Nature上发表了NTM的更新版本,可微分神经计算机(Differentiable Neural Computer,DNC)[^5]。DNC已经可以根据一些零散的路线信息,推测出地图上个点之间的连接情况,并计算最短路径。

6-code

另外一些基于记忆网络(Memory Network)的尝试也已经取得一些突破,比如Facebook在ICLR 2017上发表的递归实体网络(Recurrent Entity Network)[^6]可以通过所有Facebook提出的bAbI测试。

伦敦大学学院(University College London,UCL)的研究人员实现了一种具有可微分性的Forth语言解释器[^7],程序员编写Forth程序时,可以留下一些空白(slot),这些空白会被解释器在运行时填充,并且在运行过程中,解释器会根据程序的输入和输出调整这些空白部分的逻辑。

在工程领域,这一编程方式还处于早期阶段,尚不成熟,但已经有很多人在这一方向上做出尝试^8,以及ThoughtWorks的开源项目DeepLearning.scalaDeepDarkFantasy

要想完整的实现DITS,还需要很多努力,尤其是要想用DITS替代目前已有的复杂IT系统,还需要很长的一段路。不过,变革已然在酝酿之中。

在计算机被建造出来的早期,大部分人认为那只是学术领域的玩具,是只有书呆子模样的黑客们才能掌握的黑科技。然而,不过几十年的时间,计算机就完全改变了我们的生活。

今天的AI技术,包括Differentiable Programming和DITS,就像50年前的编程技术一样,还处于黑科技的范畴。然而,我们也许不用再等50年,就会看到今天的黑科技,将再一次改变世界。

One More Thing

《机器人叛乱》一书提到,“某些行为服务于载体的目标,它们未必有助于实现基因的目标”。那么,作为人类未来的信息载体——DITS——也会出现目标冲突的情况,到时候,我们人类要何去何从呢?

7-confuse

[^1]:《Deep Learning》,深度学习圣经

[^2]:对于细节感兴趣的读者,推荐阅读计算图和反向传播

[^3]: Differentiable Programming的说法最早出现在博客神经网络、类型和函数式编程

[^4]: Neural Turing Machine

[^5]: Hybrid computing using a neural network with dynamic external memory

[^6]: Tracking the World State with Recurrent Entity Networks

[^7]: Programming With a Differentiable Forth Interpreter

Share

我的搬家经历

问:搬家拢共分几步?

曰:三步,把东西装起来,搬过去,再拿出来摆上。

第一步和第三步主要看个人的能力,这第二步,看的是造化。

一年多前

那是一个明媚的春天,本来应该舒服躺在被窝里的我,不得不爬起来准备干一件大事——搬家。

在O2O平台上提前约好了搬家师傅,早早地起来,清点包裹,最后检查一遍房间里是不是还有落下的东西,一切准备就绪,就等着搬家师傅来了。

1-how-to-move-house-zero-waste

两个搬家师傅准时到来,穿着制服,还开了两个车,说是一个坐人,一个装货,专业! 我有将近二十个包裹,除了压缩袋装的几包衣服,还有几箱子书,住在7楼,没电梯,我掂量着要是自己搬,估计跑两三趟我就歇菜了。转眼一看两位师傅,拿出两条结实的宽带子,把四五个个箱子包裹缠在一起,借着楼梯台阶的高度差,绷住劲,一个发力站起来,稳稳当当的就下去了。专业!

东西搬上车,我们坐另一辆车,目的地不远,就几分钟的事。到了地方,两位师傅下车,跟搬东西下楼的套路一样,先把东西用宽带子缠上,然后四五个一起背起来。这次是5楼,还是没电梯,上楼比下楼要辛苦,但是两位师傅也没说什么,咬咬牙就搬上去了。就是专业!

这就是O2O,这就是互联网思维,生活变得更简单。欧耶!

半个月前

那是一个下过雨的秋日午后,本来应该关上手机,舒服窝在沙发里的我,不得不爬起来准备干一件大事——搬家。

鉴于一年前的良好体验,我还是选择了这一平台,提前约好了师傅,早早地起来,清点包裹。一年多的时间,东西又多了不少。为了避免不必要的问题,我特意给客服打电话确认,东西超过订单上要求的15个包裹怎么办。客服说的是只要车里能装下,就没问题。真好,服务还是那么周到!

约好了下午三点,两点半的时候,师傅打电话过来,我寻思着,提前半小时就到了?真是专业!

而这次搬家经历的良好体验,也就到此为止了。

这位师傅因为路上遇到撞车,迟到了一小时。到了之后,什么工具都没有,只能徒手一个一个的把东西搬下来。一会儿嫌我东西多,一会儿嫌我东西沉,最后实在搬不动,就把东西放在楼梯上开始拖着走。我提醒了两次,结果这小哥直接撂挑子不干了,把我的东西丢在那里就要走。时间已经快到晚上,这时候我再去找人来搬家不太现实,眼看着那小哥要开车走,我只好站在他车前拦住他,想要他给个说法。双方都僵持不下,找来警察调解也没什么结果,最后还是好不容易联系上客服,他们最后答应了要再派一个师傅过来。

第二个师傅在将近两个小时之后到了,还带了另一个专门搬东西的师傅,一看就是老江湖,见了面别的不说,先要加钱,网上订单里承诺的那些,这师傅一口咬定跟他没关系,仗着外面天已经黑了,这师傅放下一句“如果就这点钱我们没法搬”就要走。

2-arbitrary

我只能再去跟客服要说法,经过一番唇枪舌剑,平台同意给我免单,也就是按照这个师傅的价格,一小时加60块,最后平台把钱给师傅。师傅这才同意给我搬家,而且态度一百八十度大转弯。就在我奇怪为什么平台我免了单之后师傅态度变化会这么大的时候,师傅开始跟我商量,能不能在给平台回报的时候,把搬家的工作量多说一点。果然老谋深算,我只能在心里自愧不如。

从下午三点,到晚上十一点,终于把家搬完。

One More Thing

如果文章到这里就结束,那这篇文章就和天涯、微博上那些爆料贴没什么区别。但作为一个咨询师,而且自己亲自参与过几个O2O项目的设计和开发,我不禁开始反思。

事实上,整件事算下来,只有一个赢家,就是第二个搬家师傅。他完成了一个200元的订单,但却(可能)收到了500元的报酬。其他人全是输家:第一个师傅为他的任性付出了代价,白白浪费了时间和油钱;平台的人被我骂了一圈,领导也没好好休息,还做了把冤大头;我呢,从下午开始,直到凌晨收拾好床铺睡觉,消耗了时间、精力和好心情。

本来选择这一平台,是因为之前有过非常愉快的经历,可是时隔一年半,服务质量天差地别。回想这一年半的时间,从互联网O2O的行业来看,其实都存在着类似的变化。那时候正式O2O概念达到顶峰,保洁、搬家、按摩、理发,各种App和微信公众号层出不穷。而到了今年,大部分的O2O都死掉了,唯有手机约车、送外卖这两个O2O算是活了下来。

3-o2o

O2O,Online To Offline,顾名思义,希望通过线上良好的用户体验,带动线下业务的发展。这个模式听起来很合理,但怎么这些创业公司就死掉了呢?

经过一段时间的思考,我发现,O2O从一开始就是有问题的,不死才怪!

要解释这其中的道理,首先要思考一件事:到底什么样的商品,才适合放在网上卖?

最早放到网上卖的是书,原因很简单,书之间不会有特别大的差别,买家实际收到的书和在网上看到的样子,基本没有什么出入。因此,虽然看不见摸不着,大家还是相信自己不会买到残次品。

后来淘宝出现,几乎什么东西都可以在上面卖。良莠不齐的现象就出现了,良品和次品可能外观完全一样,只有当你拿到手,甚至用一段时间之后,才能比较出哪个更好。解决这个问题的办法,就是客户评价。品质好的商品,在销售一段时间之后,收获的好评更多,会吸引到更多的买家。为了扩大销量,商家自然希望得到更多的好评,所以就有了后来“一个差评引发的各种血案”。

4-chaping

到了O2O时代,把线下的服务放到线上去卖,问题再一次出现。

首先,服务无法标准化。

就像上面的故事中,订单里只定义了“15个编织袋”这一个标准,其他的都靠现场协商。相信不少人都有过这样的经历,网上下了个单,可能是和我一样的搬家,也可能是按摩,或者上门安装个家电,提供服务的人来了之后,总是会找这样那样的理由,让你额外加钱,当你试图跟他理论,有技巧的服务人员会说,你这种情况比较特殊,和一般人的不一样,得加钱;耿直一点的,会跟你吐吐槽,说接一单平台要扣掉多少多少,自己拿的很少;蛮横一点的,如果你表现的不那么合作,他会很生气,让你感觉好像自己做错了什么一样。这里面的根本原因,就是服务内容无法量化和标准化。

其次,服务作为商品,库存有限。

比如一位做家政的阿姨,满打满算,一天能有10个小时的工作时间就不错了,而在O2O疯狂烧钱的时候,用户量非常大,永远是供不应求。如果10个小时不管怎样都会被排满,这位阿姨不用靠高质量的服务来保留老客户。而为了提升总收入,阿姨会加快速度,把两个小时的活,一个小时干完,没准还能接点私活,赚点外快。那些敬业认真的阿姨看到别人干的轻松,赚的又多,也慢慢学“坏”了。于是,服务质量必然下滑。

最后,买家成为弱势群体。不论是买书,还是淘宝买商品,买家的心理都相对占优势。很多人面对面是不好意思指责别人错误的,但在线上评价商品时,却不存在这样的障碍。但对于O2O服务,很多时候提供服务的人会站在你旁边,看着你给他一个5星好评。这个人可能知道你家地址,了解你家的布局,甚至在提供服务的过程中,通过聊天了解你家里的情况,如果不给他一个好评,谁能保证他不会报复呢?

最后的结果就是,O2O服务线上线下存在严重的不一致,用户期望和实际服务质量之间存在巨大的偏差,而且这种偏差难以通过服务后的评价体系得到反馈。最终,市场上充斥着大量的低质量服务,用户群不再信任线上的介绍,市场萎缩,平台开始削减成本,大量服务外包,质量进一步下降。在经济学上,这种现象叫做“劣币驱逐良币”,其根本原因在于信息的不对称。

5-big-data

总结起来,一个O2O要想可持续运营,需要至少具备两个关键要素:

  • 商品或者服务内容可以量化
  • 有效的用户评价,可以对商家起到舆论监督作用

两者可以起到互相补充的作用,如果商品很符合第一个要素,那么对第二个要素的要求就可以降低一些,比如卖书;反之亦然,比如淘宝。然而本文中提到的搬家平台所提供的服务,不满足第一个要素,第二个要素又没有做好,最后提供的服务越来越差,就是情理之中了。

死局怎么破

前面的分析,看起来好像已经判了服务类O2O模式的死刑。难道,我们想要足不出户就享受优质服务的愿望不可能实现了么?

要想破此局,需要置之死地而后生。既然服务类O2O无法天生满足上述两大关键要素,我们就想办法让它满足。

先从第一点入手。如何让无形的服务变成可以量化的商品呢?答案是物联网和大数据。

6-shapesquare

以搬家为例,在每一个箱子或者编织袋上装个传感器,能够检测容器内所装物品的重量,还可以在搬运过程中记录震动的情况,我们就可以将搬家服务量化成:15个包裹,每个包裹不超过20kg,搬运过程中,保证每个包裹不会出现超过100N的冲击。如果这样定义一个订单,相信能减少很多纠纷。如果不考虑成本,最终极的解决方案,是可以让用户提前将所有要搬的东西拍照上传,服务器通过图像分析得到包裹信息,根据这个信息分配最适合的资源。而搬家师傅则可以佩戴一个类似Google Glass的设备,到了客户家里,设备可以自动识别出客户的包裹是否和上传的包裹一致,如果不一致,搬家师傅有权利拒绝提供服务。

第二个问题,其实是目前电商的一个通用问题,就是如何获得对所购买商品的准确评价。

实际上饿了么在这一点做的就不错,通过对用户的留言做情感分析,即使用户给了5分,但是在评论中提到“饭太凉了”这样的话,也依然会被认定是一条负面评价。这样即使有些用户碍于面子或其他原因,没有给出很低的分数,但通过对服务过程的描述进行分析,还是可能得到准确的评价。

奇点临近

在上面说的解决方案中,用到的传感器技术,计算机视觉技术、自然语言处理技术,都属于人工智能技术这个大范畴的一部分。事实上,人工智能技术在最近几年有非常大的技术突破。我们正在加速朝“万物皆可量化”的数字世界进发,所有的商业形式,生活方式,都可能因此发生改变。

你,准备好了么?

Share

Kubernetes救援 – 教你如何从新技术的坑里爬出来(下)

上文最后讲到,经过一番努力,排除了基础设施的问题,集中精力查找Kubernetes配置错误,现在要抓紧最后一根救命稻草——日志,期待能从日志中找到解决问题的蛛丝马迹。

希望来临

在按下浏览器刷新按钮时,我是非常忐忑的,如果日志没有任何异常提示,我就没有任何其他线索可以继续找下去了。

从master节点开始,etcd.log无异常,flanneld.log无异常,kubelet.log无异常,…,所有日志均无异常。我担心的事情终于发生了。

然后是minion1节点,flanneld.log无异常,kubelet.log无异常,…,所有日志还是无异常。我已经感觉到一丝丝绝望,甚至开始在心里暗骂,Google这群不靠谱的人,竟然错误都不记录到日志中!

最后是minion2节点,flanneld.log无异常,kubelet.log无异常,…,所有日志……等等!kube-proxy.log里那是什么!

E1129 06:06:19.727461    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727502    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727537    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727570    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727578    2540 proxysocket.go:138] Failed to connect to balancer: failed to connect to an endpoint.

终于抓到你了!

看到错误,我的第一反应是:一定是kube-proxy配错了。但是到底哪里错我,却没有任何思路。所以要还要仔细看下日志的消息。

盯着这个日志看了3秒钟,猛然发现,这个10.0.2.15根本不该出现在这里!这里对于不熟悉vagrant的读者多解释两句,创建虚拟设施的Vagrantfile在这里,为了让虚拟机之间可以通信,我配置了private_network,其实就是VirtualBox里的host-only网络,但是,vagrant为了管理虚拟机,会默认给每个虚拟机创建一个NAT网卡,这个网卡的IP就是10.0.2.15,所有的虚拟机都是这个IP,互相之间不能通过这块NAT网卡互联。这样每个虚拟机就会有两个网卡,一个是NAT网卡,一个是Private Network网卡。而NAT网卡是eth0,也就是系统默认网卡。Kubernetes集群内部通信是通过Private Network,而对外提供服务,是通过apiserver,用的也是Private Networks。这里出现了10.0.2.5,说明Kubernetes的kube-proxy的服务绑定了eth0网卡,而不是期望的eth1网卡!

为什么官方文档里没有提这件事?因为官方部署Ubuntu的文档针对的目标环境是裸机,裸机通常只有一块网卡,而即使有两块,不同机器之间也应该可以通过默认网卡互联。所以不会出现问题。

想到这,我立即去查如何设置kube-proxy绑定网卡,最直接的,就是在命令行里运行kube-proxy

vagrant@master:/opt/bin$ ./kube-proxy --help
Usage of ./kube-proxy:
      --alsologtostderr[=false]: log to standard error as well as files
      --bind-address=0.0.0.0: The IP address for the proxy server to serve on (set to 0.0.0.0 for all interfaces)
      --cleanup-iptables[=false]: If true cleanup iptables rules and exit.
      --healthz-bind-address=127.0.0.1: The IP address for the health check server to serve on, defaulting to 127.0.0.1 (set to 0.0.0.0 for all interfaces)
      --healthz-port=10249: The port to bind the health check server. Use 0 to disable.
      --hostname-override="": If non-empty, will use this string as identification instead of the actual hostname.
      --iptables-sync-period=30s: How often iptables rules are refreshed (e.g. '5s', '1m', '2h22m').  Must be greater than 0.
      --kubeconfig="": Path to kubeconfig file with authorization information (the master location is set by the master flag).
      --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace
      --log-dir="": If non-empty, write log files in this directory
      --log-flush-frequency=5s: Maximum number of seconds between log flushes
      --logtostderr[=true]: log to standard error instead of files
      --masquerade-all[=false]: If using the pure iptables proxy, SNAT everything
      --master="": The address of the Kubernetes API server (overrides any value in kubeconfig)
      --oom-score-adj=-999: The oom-score-adj value for kube-proxy process. Values must be within the range [-1000, 1000]
      --proxy-mode="": Which proxy mode to use: 'userspace' (older, stable) or 'iptables' (experimental). If blank, look at the Node object on the Kubernetes API and respect the 'net.experimental.kubernetes.io/proxy-mode' annotation if provided.  Otherwise use the best-available proxy (currently userspace, but may change in future versions).  If the iptables proxy is selected, regardless of how, but the system's kernel or iptables versions are insufficient, this always falls back to the userspace proxy.
      --proxy-port-range=: Range of host ports (beginPort-endPort, inclusive) that may be consumed in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen.
      --resource-container="/kube-proxy": Absolute name of the resource-only container to create and run the Kube-proxy in (Default: /kube-proxy).
      --stderrthreshold=2: logs at or above this threshold go to stderr
      --udp-timeout=250ms: How long an idle UDP connection will be kept open (e.g. '250ms', '2s').  Must be greater than 0. Only applicable for proxy-mode=userspace
      --v=0: log level for V logs
      --version=false: Print version information and quit
      --vmodule=: comma-separated list of pattern=N settings for file-filtered logging

扫了一眼,最有嫌疑的配置就是--bind-address,不过,从说明来看,这个配置默认0.0.0.0,应该是接收所有网卡的请求。有点矛盾,但死马当活马医,试试再说。于是修改/etc/default/kube-proxy,添加一个参数:

KUBE_PROXY_OPTS=" --master=http://172.28.128.3:8080  --logtostderr=true --bind-address=172.28.128.5"

重启kube-proxy。

Finger crossed。

刷新一下页面。shit!还是那样!

看来这个办法不行,改回原来默认的配置,要重新理一下思路。

补充理论

不能再这么“东一耙子西一扫帚”(东北人都懂)了,现在抓到了kube-proxy.log里的关键线索,错误一定跟它有关。这个时候不能急,要冷静,先想清楚到底是怎么回事。

回想一下Kubernetes的架构(是的,在玩Quick Start之前,我当然要基本了解下Kubernetes):

  1. 通过etcd做全局配置和服务发现;
  2. flannel负责给所有docker容器分配虚拟ip给pod,以避免管理port;
  3. 资源被划分为几个层次:应用被打包在docker中,多个docker组成一个pod;多个pod组成一个service;service对外提供服务。

这个kube-proxy到底在这个架构里是一个什么作用?看来又要施展我的Google技能了。

长话短说,搜了几篇没什么营养的入门文章之后,在官方文档中,找到了我想要的答案,总结一下内容:

  1. 每个service会在apiserver监听一个端口,同时还会分配一个service IP,供集群内部访问,service通过selector找到符合要求的pods,然后记录下pods的IP作为Endpoint;
  2. pod的IP和service的IP并不是在同一个IP段,这也是为什么在配置初始化脚本config-default.sh时,有两个IP段需要配置;
  3. kube-proxy监听etcd,并更新iptables,将访问master的service端口,或者集群内部通过service IP的访问请求转发到对应的Endpoint,这个Endpoint的IP是通过flannel控制分配的;

搞清楚kube-proxy的作用了,但是还有点云里雾里,到底是哪里出问题了呢?这时,那个重要的犯罪证据“不该出现的IP”10.0.2.15又一次浮现出来。有没有可能这个IP还出现在其他地方,被我之前漏掉了呢?

我决定在所有日志里搜索一下这个IP,可以用grep这个命令:

root@minion2:/var/log/upstart# grep -ir "10.0.2.15" .
./kube-proxy.log:E1128 11:28:55.091647    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727461    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727502    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727537    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727570    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./network-interface-eth0.log:DHCPREQUEST of 10.0.2.15 on eth0 to 255.255.255.255 port 67 (xid=0x681829be)
./network-interface-eth0.log:DHCPOFFER of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:DHCPACK of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:bound to 10.0.2.15 -- renewal in 34886 seconds.
./network-interface-eth0.log:DHCPREQUEST of 10.0.2.15 on eth0 to 255.255.255.255 port 67 (xid=0x18078729)
./network-interface-eth0.log:DHCPOFFER of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:DHCPACK of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:bound to 10.0.2.15 -- renewal in 37295 seconds.
./flanneld.log:I1128 11:20:37.077746 02534 main.go:188] Using 10.0.2.15 as external interface
./flanneld.log:I1128 11:20:37.078771 02534 main.go:189] Using 10.0.2.15 as external endpoint
./flanneld.log:I1128 11:20:37.094503 02534 etcd.go:129] Found lease (172.16.10.0/24) for current IP (10.0.2.15), reusing

果然在falnneld.log里还有漏网之鱼!看到这个搜索结果,在加上前面看到的kube-proxy原理,把这些线索串到一起,整个事情突然清晰起来:

  1. flanneld在启动时,把默认网卡eth0识别成要监听的网卡;
  2. kube-proxy想要访问某个pod时只有这个pod的虚拟IP,也就是flanneld分配的IP;
  3. 当请求到达kube-proxy时,kube-proxy想通过10.0.2.15这个IP跟flanneld进行通信,但是因为这个IP是vagrant的NAT网卡IP,所以失败了;
  4. 这也就是为什么那个完整的错误日志说的是:Dial failed: dial tcp 10.0.2.15:6443: connection refused

终于定位到你了,flanneld,你这个罪魁祸首!

最后的围剿

既然定位到了flanneld,剩下的事情就是怎么修复这个问题。还是先看看flanneld的参数都有哪些。

root@minion2:/opt/bin# ./flanneld --help
Usage: ./flanneld [OPTION]...
  -alsologtostderr=false: log to standard error as well as files
  -etcd-cafile="": SSL Certificate Authority file used to secure etcd communication
  -etcd-certfile="": SSL certification file used to secure etcd communication
  -etcd-endpoints="http://127.0.0.1:4001,http://127.0.0.1:2379": a comma-delimited list of etcd endpoints
  -etcd-keyfile="": SSL key file used to secure etcd communication
  -etcd-prefix="/coreos.com/network": etcd prefix
  -help=false: print this message
  -iface="": interface to use (IP or name) for inter-host communication
  -ip-masq=false: setup IP masquerade rule for traffic destined outside of overlay network
  -listen="": run as server and listen on specified address (e.g. ':8080')
  -log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
  -log_dir="": If non-empty, write log files in this directory
  -logtostderr=false: log to standard error instead of files
  -networks="": run in multi-network mode and service the specified networks
  -public-ip="": IP accessible by other nodes for inter-host communication
  -remote="": run as client and connect to server on specified address (e.g. '10.1.2.3:8080')
  -remote-cafile="": SSL Certificate Authority file used to secure client/server communication
  -remote-certfile="": SSL certification file used to secure client/server communication
  -remote-keyfile="": SSL key file used to secure client/server communication
  -stderrthreshold=0: logs at or above this threshold go to stderr
  -subnet-dir="/run/flannel/networks": directory where files with env variables (subnet, MTU, ...) will be written to
  -subnet-file="/run/flannel/subnet.env": filename where env variables (subnet, MTU, ... ) will be written to
  -v=0: log level for V logs
  -version=false: print version and exit
  -vmodule=: comma-separated list of pattern=N settings for file-filtered logging

看到-iface=""这个参数的说明,基本上已经可以确认,就是它了!照这个思路开始改:

root@minion2:/opt/bin# vi /etc/default/flanneld
root@minion2:/opt/bin# cat /etc/default/flanneld
FLANNEL_OPTS="-iface=eth1 --etcd-endpoints=http://172.28.128.3:4001"
root@minion2:/opt/bin# service flanneld restart
flanneld stop/waiting
flanneld start/running, process 4495
root@minion2:/opt/bin# tail -f /var/log/upstart/flanneld.log -n 50
I1128 11:20:37.076880 02534 main.go:275] Installing signal handlers
I1128 11:20:37.077355 02534 main.go:130] Determining IP address of default interface
I1128 11:20:37.077746 02534 main.go:188] Using 10.0.2.15 as external interface
I1128 11:20:37.078771 02534 main.go:189] Using 10.0.2.15 as external endpoint
I1128 11:20:37.094503 02534 etcd.go:129] Found lease (172.16.10.0/24) for current IP (10.0.2.15), reusing
I1128 11:20:37.096168 02534 etcd.go:84] Subnet lease acquired: 172.16.10.0/24
I1128 11:20:37.097640 02534 udp.go:222] Watching for new subnet leases
I1129 11:59:35.035222 02534 main.go:292] Exiting...
I1129 11:59:35.075646 04495 main.go:275] Installing signal handlers
I1129 11:59:35.078235 04495 main.go:188] Using 172.28.128.5 as external interface
I1129 11:59:35.078257 04495 main.go:189] Using 172.28.128.5 as external endpoint
I1129 11:59:35.093587 04495 etcd.go:204] Picking subnet in range 172.16.1.0 ... 172.16.255.0
I1129 11:59:35.096558 04495 etcd.go:84] Subnet lease acquired: 172.16.29.0/24
I1129 11:59:35.106917 04495 udp.go:222] Watching for new subnet leases
I1129 11:59:35.108491 04495 udp.go:247] Subnet added: 172.16.10.0/24

从这个日志提示看,这个改动应该是成功了,于是把其他两个虚拟机也修改一下。激动人心的时刻到了,终于能见到美丽的kube-ui,想想还有点小激动!

打开浏览器,刷新!

什么都没有发生!还是那个让我抓狂的页面!

Final Fight

这个结果对我确实是一个打击,但是我比较确信这个解决方案的方向是正确的。一个可能的原因是flanneld需要和其他组件配合,虽然flanneld已经改好,但其他配合的组件还需要修改。要验证这个假设,需要去了解Kubernetes启动的具体步骤,有两种方式可以选择:

  1. 看kube-up.sh脚本,找到启动过程中调用的所有配置,看看哪里有和flanneld关联的地方;
  2. Google搜索解决方案。

鉴于前面的经验,估计Google出来的结果可能帮助不大,所以我首选自己先看脚本。花了半个多小时,搞清楚整个启动脚本的结构,kube-up.sh只是一个入口,会根据KUBERNETES_PROVIDER环境变量的值,选择调用不同的配置脚本。kube-up.sh里的方法只是类似虚函数的空实现,具体的逻辑由各个平台对应的脚本重写实现。我用到的脚本都在kubernetes/cluster/ubuntu/路径下。不愧是Google出品,连Shell脚本都能做成面向接口编程。

读脚本的收获很大,但是并没有解决我的问题,因为Shell脚本的可读性实在是不可恭维。所以又要靠Google了。

在Google里输入“kubernetes fanneld iface”等关键字,没什么有价值的发现。既然直接找不行,那就间接找,换个关键字,“kubernetes multinode”,希望能找到一个详细讲手动部署多节点Kubernetes集群的说明。

功夫不负有心人,终于找到一篇,是讲如何用docker部署多节点Kubernetes集群,虽然这个文档也是通过自动化脚本进行配置,但它对每一个脚本都写了个说明文档,比如这个讲worker.sh脚本的。里面比较关键的是这一段:

mutli-node-docker-doc

总结起来就是:

  1. flanneld启动后,会把注册的子网写入到/run/flannel/subnet.env里;
  2. 启动docker时,要把这个子网信息作为参数传进去。

这就是那传说中的“丢失的一步”!照着这个文档修改配置:

root@minion2:/home/vagrant# cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.16.0.0/16
FLANNEL_SUBNET=172.16.29.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=false
root@minion2:/home/vagrant# cat /etc/default/docker
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.10.1/24 --mtu=1472"
root@minion2:/home/vagrant# vi /etc/default/docker
root@minion2:/home/vagrant# cat /etc/default/docker
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.29.1/24 --mtu=1472"

接着把另外两个节点也修改一下。别忘了重启docker服务。

打开浏览器,刷新!“嘭”!

kubernetes-dashboard

 

总结

看到Dashboard终于出现,内心早已汹涌澎湃,但表面上还是要很平静,毕竟我是专家啊。

回顾前面的整个过程,得到以下两条经验:

  1. 对于新技术爱好者,在Quick Start出问题时,不要放弃,更不要胡乱尝试。要按照下面的步骤来解决问题:
    • 列出所有怀疑对象;
    • 逐一排查,并记录所有可疑点,这个过程中最有力的两个工具:Google和系统日志;
    • 定位问题,尝试解决,如果失败,重新回到第一步;
  2. 对于系统设计者,在设计新系统是,要考虑以下几个方面:
    • 一定要提供完善的日志记录,供用户调试;
    • 即使提供傻瓜操作式工具,也要提供方式让高级用户查看细节,比如让用户查看自动化脚本;
    • 对于复杂的自动化脚本,要提供说明文档,解释原理。

One More Thing

发现这个问题的原因是自动配置脚本没有考虑多网卡的情况,于是给社区提交了issue。看,问题解决了,深入理解Kubernetes了,顺便还给社区做了贡献,一举三得!

Share