阅读视图

发现新文章,点击刷新页面。

荐书:The Blind Watchmaker

提起 Richard Dawkins, 大部分人想起的是他的第一本书《自私的基因》(the Selfish Gene)。他后来写的 the Blind Watchmaker 从另一个角度探讨了和进化论相关的一些问题,我最近看了后觉得同样有趣和重要。和自私的基因一样,这本书比较老,第一版出版于 1987 年,但是内容一点都没有过时。现在能买到的大部分是 2016 年出的 30 周年纪念版,作者在前言里说他几乎没找到需要修改的地方,看完之后我不得不同意。

这本书的书名来自于一个叫 the watchmaker argument 的支持造物主存在的论证。大致的内容是:如果你在路边捡到一块奇特的石头,尽管它的纹理或者形状很精妙,你也能相信它是通过一个自然的过程偶然形成的。毕竟天下有那么多石头,总有一些会具备看来奇特的性状。但是如果你在路边捡到一块表,你无论如何都不会相信这样精密的物件是一个自然过程的偶然结果,而不是来自于一个制表匠的刻意设计。自然界和生命的复杂度远远高于一块表,所以更不可能是偶然形成的,有意识有目的的造物主一定存在。这个论证最早出现在英国人 William Paley 的书里,但在他之前的牛顿、笛卡尔等人也都认为宇宙的运转和钟表类似,上帝就是钟表匠,科学家发现的只是上帝设计的规则。看这本书让我想起正好 20 年前的冬季我和导师到 Rutgers 开会,回 New Haven 的路上下着大雪,所以他只能慢慢开车。路上的几个小时里我们在聊为什么宗教吸引了那么多人,大概就是因为人类很难理解几百万年到几亿年这个区间里自然的演化过程能产生的结果,于是必须求助于造物主来解释生命的存在。

我初到美国的时候,最让我惊讶的事之一是在科技发展最前沿的国家竟然有一些州在争论是否应该在中小学教进化论,或者是否应该同时教神创论。直到现在其实也没有多少改变,现在美国大选最主要的议题之一是女性的堕胎权,而且反对者的依据来源是宗教而不是科学和伦理。我一直认为宗教对教育和社会的影响是美国的 bear case 里排前面的。在中国宗教离大部分人的生活很远,对教育更是没有影响力,总的来说是正向的事。但是因为少有争议和质疑,在大众文化中也就缺乏有意义的讨论。大部分人把进化论作为事实简单接受,从没考虑过其中的细节,比如像眼睛这样精密而脆弱的器官,是如何通过进化过程形成的。另外物种的边界在于同一物种个体间可以通过交配产生后代,不同物种的个体间无法产生健康的后代,那么如果不同物种是从共同祖先进化而来,那新的物种刚分化出来时岂不是无法繁衍?

这本书耐心并有说服力地回答了上述这些细节问题,一一拆解了进化论反对者的各种质疑。可以说在看过 the Selfish Genethe Blind Watchmaker 之后我才算真正理解了进化和自然选择,对自己在这方面的知识有一种踏实和完整的感觉。

(旧文)也说王垠退学

好几天前就在网上看到关于王垠从清华退学的新闻。本来我对这类事情是不会去关心的,认为又是媒体的炒作把事情闹大。昨天因为在一个朋友的blog上看到也在讨论这件事,所以忍不住去看了王垠的退学信1。我的第一反应是「原来是他!」。

在多年以前王垠还没有那么出名的时候,我就已经在他的主页上看过他写的关于 GNU/Linux, TeX/LaTeX, 和 Mutt 的文章,当时就觉得他这样一个对计算机科学充满激情的人将来必定有所作为。虽然我接触计算机以及对计算机科学产生浓厚的兴趣比他要早很多,常庆幸自己很早就明白喜欢从事什么行业,不过和他对这些东西的熟悉程度,对不明白的问题的钻研精神,以及那一股狂热劲相比,我真是觉得惭愧。

王垠发表在主页和 blog 上给清华的退学信中说到了很多中国的教育以及中国的大学存在的问题。可以说其中涉及到体制的部分大多数是客观和真实的,是很多学生的亲身体会,甚至可以说要是他到读博士才意识到这些问题,已经有些晚了。一些清华的学生和校友针对王垠的退学信设立了一个 blog -清华梦依然在,集中了一些反面的看法。双方各执一词,其实争论主要是集中在一些细节问题,还有王垠信中对清华和他们实验室两位导师的评价,对中国教育大环境存在的问题,其实很少有人真正持不同意见,只是大家有不同的解释。我不在清华,不了解具体情况,所以对涉及到清华和他们实验室的具体情况,自然没资格评论了。

王垠的信让我回想起自己本科的时候。我在武汉大学读的本科,武大在学术上不如清华,类似王垠说的问题表现得可能还更明显一些。那时候的我年轻气盛,嫉恶如仇,想法和态度大约和现在的王垠差不多:不喜欢很多学校里的教授,看不惯学校的很多事情。我从大二结束以后就很少去上课,因为总是觉得有些不屑,认为学不到什么东西。每个学期系里开什么课,我就去书店买对应的影印版英文教材看,到期末最后一节课去听老师划划重点,然后就去考试。往往到了一个学期结束还问同寝室的同学教某门课的老师是男是女。我虽然不像王垠,但也算是个敢作敢为的人,大四时在 BBS 上发过一些帖子,其中有说到武大的计算机科学系教的不是计算机科学,而就是一个电脑培训班。我们院的党委副书记同时也是 BBS 上院版的副版主,他常常把我封了,有时还打电话到寝室教训我。这种事情发生得多了,后来就成朋友了。将要毕业的时候,校学生会从应届毕业生里面找了包括我在内的三个人去给低年级同学做学习经验交流。当时因为院校合并等一系列事情,学生对学校的怨气挺大的,自由问答的时候有个学生问我学校的种种不好对我有没有什么影响。当时在教五楼的礼堂,我对着下面的几百学生说:“武大要强起来就得靠学生自己。每个学生为着自己的理想去奋斗,每个人都做好自己的事,武大自然就强了。靠现在行政大楼里面那帮人去决策,武大不可能强起来的”,不知道有没有把邀我去的那个学生会学习部的 mm 吓到。那时候我还没有拿到毕业证。

大三时一个在日本工作了多年回到武大的教授对本科生开了一门面向对象软件工程的课,主要讲UML的基础和应用一类的。因为我一向对软件开发很有兴趣,他讲的那些东西当时在国内算是比较新的。那时他的实验室刚重新启动,正在用人之际,他也比较鼓励本科生的参与,而我的专业和英语都还不错,所以从大三的某个时候开始一直到大四结束,我都一直在武汉大学的软件工程国家重点实验室做些事。那里研究生太多,导师也顾不过来,更不用说我这个本科生了。实验室老板对我很好,很照顾,只是他实在太忙,连他自己的研究生都指导不过来。那时我在那里做了很多翻译之类基本没有技术含量的活。科研方面就和王垠说的一样,就是读很多国外的 paper,然后考虑是不是能对一些小的方面进行改进,不可能会有什么突破性的结果。就这样一边做这些事情,一边应付每个学期的考试,一边考 TOEFL,GRE,GRE CS Sub,申请,办出国手续,一直到毕业。

凡事都有两面。王垠做出退学的决定,我想他只看到了一面。我本科时所处的环境,促使我养成了独立学习,独立思考的习惯和能力。我在软件工程实验室虽然常常在做些无谓的事情,不过大量的阅读使我对软件工程发展的概况有了全面的认识。王垠对他实验室的教授有颇多抱怨。本科时有一个数学系的教授在瞒着我的情况下把我交的作为一门课的 term project 的程序带到高交会做演示,后来还是一个去了的人说起我才知道。到后来买方提出一些要求,他因为搞的是数学,不熟悉编程,无法解决,才又来找到我。当时曾因此很不愉快,并非我想从那个程序得到什么商业利益,而是因为我最不喜欢被人欺瞒。可是要是不是因为他开了那门课,我也不太会对那个领域进行深入的学习,也就不会认识我现在的导师。所以要是没有他,我现在会是另外一个情况,或许差一些,或许好一些。人生际遇,从独立的一件事是难以判断祸福的。最重要的是以平和的心态看待问题,不管处于什么样的环境,都要 make the best out of it。

环境的不尽如人意并不是一件坏事,以其说“天将降大任于是人”之类的空话,不如看看真实的例子。很多人说中国的教育体制不能培养出世界一流的科学家,真是这样吗?现在在清华大学高等研究所的王小云在对 MD5 和 SHA-1 等一系列 hash function 的分析方面得到了突破性的进展,现在美国搞 computer science 的人基本都听说过她的名字,密码学界就更不用说了。可以说她和她学生的研究代表着理论密码学的 state of the art。 Wikipedia上说:

At the rump session of CRYPTO 2004, she and co-authors demonstrated collisions in MD5, SHA-0 and other related hash functions. (A collision occurs when two distinct messages result in the same hash function output). They received a standing ovation for their work.

在一个学术会议上,所有人起立为一个 talk 鼓掌,这是很少见的。她的简历上说:

Education:

  • B.S., Mathematics Department , Shandong University, 1987.
  • M.S., Mathematics Department , Shandong University, 1990.
  • Ph. D, Mathematics Department , Shandong University, 1993.

Employment Record:

  • Lecturer, Mathematics Department, Shandong University, 7/1993-6/1995.
  • Assistant Professor, Mathematics Department, Shandong University, 7/1995-6/2001.9.
  • Professor, School of Mathematics & System Sciences, Shandong University,7/2001.9-Present.

她更本没有在国外接收过教育或者做过研究。据我所知,她今年准备去美国参加 CRYPTO’05 的时候还被据签了,她的新成果是 Adi Shamir 代为宣读的。她出生就在山东,从大学一直到成为正教授都一直在山东大学,一所在中国都算不上一流的大学。她的主要成果也是在山东大学信息安全研究中心做出的。清华学生所拥有的环境是让中国很多别的学校的学生羡慕不已的,我想不会不如王小云成长起来的环境吧。客观条件可以起一定作用,可是要成为什么样的人还是取决于自己。王垠说:

我觉得自己一个学生力量太小,曾经试图找大师帮忙。我找到 Andy Yao,述说我的苦衷。结果他对我说:「别试图去改造环境!你没有这个能力。改造好你自己就不错了。」改造好我自己,可是怎么改?所以我决定先换一个环境,到一个真正搞研究的地方去体会,去学习。

其实姚期智说得没有什么不对。在大环境下多数人的力量都是薄弱的,难以去把环境变得让自己喜欢,何况每个人的看法和喜好是不一样的。人不能控制环境,但是可以控制自己,不管环境怎样,始终可以努力做自己想做的事。在觉得出了问题的时候,怪罪于环境之前,恐怕首先要找找主观原因。毕竟在同样或者更不好的环境下,还是有别的人可以做得很好。每个人都把自己份内的事情做好了,环境自然也就变好了。

王垠说他打算退学后出国找一个喜欢的学校做他心目中真正的科学研究。我想他会失望的,因为理想的学术殿堂不存在,失望的结果可能有两种,他或许会明白一些事情,从此有一个比较平和的心态;或许会做出更极端的选择。2他信中说的很多问题都是 universal 的,有人的地方都存在这些问题,只是因为中国发展变化得太快,所以各方面的矛盾也表现得更为明显和极端。美国也是一个功利的国家。在美国教授同样要靠发 paper 申请 funding 和拿 tenure,很多研究生同样要做很多无聊的表面工作。很多美国实验室的老板,特别是还没有拿到 tenure 的教授,不管学生愿不愿意都要逼着学生干他觉得需要完成的活,比起国内有过之而无不及。我认识的一些别的学校的中国学生,放暑假想要回国探亲老板都不让,就算让也只给很短的假期。况且在美国你的学费生活费都是靠导师从科研经费出的,和导师关系搞僵了就得卷铺盖走了。3王垠在信中说国外大学都有 common room,而国内没有。另外他觉得学生之间的讨论很重要,而实验室组织的讨论每个学生讲讲对自己看的论文的看法,他又觉得那样的讨论不好。有没有 common room 只是一个形式,形式决定不了内容。我就基本不去 common room ,因为我不喜欢人多的地方,我喜欢一个人静静地思考。有时在走道遇到一个人想说什么事情,就靠着墙一说就是一两个小时,也没有觉得需要一个 common room。在国外大学里的学术讨论其实和他所描述的他们实验室的讨论差不多。通常也就是一个人说一说对某篇论文的看法,经常也是很长时间没有什么结果,很多想法被提出来被否定掉。要找一个他心目中理想的科学研究的殿堂,恐怕穷其一生也找不到。科学研究中的大部分工作都是要静下心来独立完成的。讨论的真正目的是在有一定结果的时候告诉别人,让别人挑刺,所谓的 peer review。他说导师不鼓励学生之间的讨论就不知道别人在做什么,什么已经做了,什么没做,有些什么有趣的问题。以什么样的方式做研究是自己的事,导师鼓励不鼓励只是一个参考意见,毕竟已经不是小学生了。我的导师每个星期和我见一次面,听我说说自己的点子,给我一些指点,除此之外都是我自己安排。我觉得对计算机科学来说,到了博士阶段,正确的研究方式是独立查找资料、独立思考、独立完成工作,这个只是个人的看法,但有一点我可以肯定:要知道他所说的那些东西,最好的方法不是讨论,是 Google。

到现在为止我在美国生活了三年多,最大的收获有两方面,都和学术无关。首先是看到的事情多了,有了比较的基础,心态成熟和平和了很多,看问题不像以前那么片面。另外明白了什么东西才是对自己最重要的。学习,工作,科研并不是对我最重要的,财富也好,学术声誉也好也不是最值得追求的;亲情,友情,爱情,以及一般人之间的关爱才是最值得一个人珍视的。事业只是生活的一部分,生命中有很多珍贵的东西值得去追求,一个人只有首先做好一个普通人,一个普通的好人,他在专业上的expertise才能对社会发挥正面的作用。

我并不是要写那么长一篇文章批评王垠。这三天来正正反反的文章已经很多了,只是联系到自己,颇有感触。对王垠我首先是欣赏的,当年看他写的关于 Linux 和 TeX 的文章,受益很多,现在看到他的退学信,觉得他是个有激情、有勇气的理想主义者。中国需要很多能坚持自己理想,不被环境所同化的人。可是一个人光有理想和激情是不够的,在坚持原则的同时,还要有一颗宽厚、平和、和乐观的心,这样才不会因为对现实的失望而最终放弃自己的理想。我本来不喜欢李敖,不过他在清华的演讲其中一段让我很感动:

富兰克讲了一句话,非常动人,他说,哪里有自由,哪里就是我的祖国。告诉大家,富兰克林是错误的,这句话要被我李敖改写,怎么说,这里是我的国家,我要使它自由。

他说的虽然是自由,不过这段话应用到别的方面也是实用的。中国是问题不少,不过再怎么样总比几十年前好多了吧。如果当年那批知识分子都说:「哇,中国怎么这样?我闪」,那么也就不会有现在这个可以算是(或者说正在变得)繁荣富强的中国。(不要骂我在美国还说得那么虚伪,我是毕业后就要回国的。)古人有句话是「势利纷华,不近者为洁,近之而不染者为尤洁;智械机巧,不知者为高,知之而不用者为尤高。」所以逃避环境并不是最好的选择。最后祝愿王垠在以后的路上可以为自己的心找到一个位置。也希望在这件事平息下来以后,中国的学校,教育者和决策者们能对王垠提出来的问题有认真地思考,不要简单地归咎于一方。


  1. 水木清华的原帖已经找不到了,这是后来别人在知乎的转载。 ↩︎

  2. 近 20 年后再回头看,很遗憾发生的是后者。 ↩︎

  3. 我留学的年代大部分中国学生都是靠全额奖学金来支付学费和生活费。当时我父母没有支付任何我在美国的费用(也无法支付),后来我还汇了从奖学金里存下的一万美元回家。现在富裕的家庭很多,自费留学的人也很多,所以这一条就不是很适用了。 ↩︎

王垠传播的「自然视力恢复法」真的有用吗?

最近我偶然看到了王垠 Substack 上的《不要去医院验光》,当然因为没付费订阅我只能看到开头几段1。然后点上面的链接到了他博客上的「自然视力恢复法」。考虑到这两篇文章的危害,我觉得值得写一点东西给看我博客的人做一些参考。

王垠这个名字很多人都不陌生了,他从清华退学的时候我还在读博,当时还写了一篇文章来评论这件事,~不过现在已经找不到了~2。我那时看他写的宣布退学的文章留下的印象是他是个很聪明但视野有些片面、想法有些偏激的年轻人,没想到他后来变得越来越极端和愤世嫉俗,成了阴谋论的传播者。他的聪明和在计算机方面的一些天分让他成为了一个 influencer,颇有一些追随他意见的人。而当他推广的看法超出计算机领域,有可能实际影响别人的生活时,就危险了。

首先验光之所以要散瞳,就是为了测量在肌肉彻底放松的状态下眼睛的度数,这样才能让眼睛在戴眼镜后也能尽量放松,不存在他说的「度数会验得比实际需要的度数大」。而且医生也往往会透过放大的瞳孔检查其他眼科疾病。散瞳并不像他说的一样是「故意的欺骗行为」。他在文章里说:

而且散瞳用的阿托品会麻痹肌肉,这种药物应该也是有毒物质。偶尔一次可能还好,经常往眼睛里滴这种东西,不知道是什么后果。

如果不知道的话,恐怕不应该凭想象说某种药物「应该」也是有毒物质。

他在自然视力恢复法一文中提倡的基本观点是,近视是由于眼球外部肌肉长期紧张导致眼球变形造成的,近视的人应该戴度数浅一些的眼镜或者不戴眼镜,这样可以逐步让眼睛恢复正常。他说:

近视产生的根本原因,是长时间紧张地看近距离的物体,而跟光线,遗传什么的都没有直接的关系。

现代医学广泛接受的结论是:遗传是近视最主要的风险因素。

王垠在文章里引用的主要依据是 William Bates 写的 Prefect Sight Without Glasses。这本书是 1920 年由作者自出版的,里面说的近视治疗方法被称为 Bates method。而 1929 年 FTC 就发出对他的投诉,指控他进行虚假或误导性的宣传。在这一百年里相关的临床试验都表明他在书里说的那些练习对治疗近视是无效的。确实有一些近视的人在不戴眼镜一段时间后报告说视力比原来有所提高,但这个现象有很合理的与主流医学相洽的解释。不戴眼镜一段时间后,因为看到的东西是模糊的,大脑会在处理视觉信号时进行补偿来适应光学上的失焦。这就好像你可以用 Photoshop 来提高照片的锐度,或者用计算机视觉模型来让原本有些模糊的图像变得看起来更清晰。但这种提升是有限的,也不是对近视的治疗,更不是提倡不戴眼镜或者降低度数的合理原因。

Bates 的书篇幅很长,我以其中一章为例来说明它有多边缘、荒谬和危险。第 17 章是 Vision Under Adverse Conditions a Benefit to the Eye,可以翻译为「不利条件下的视力:对眼睛的益处」。在这一章里,他说包括阳光在内的强光对眼睛都是无害的,所有的不适感都是暂时的,直视太阳不但不会对眼睛造成永久伤害,还能帮助恢复视力。

Bates 书里的图 47:一个 37 岁的妇女和一个 4 岁的儿童直视太阳而没有不适。(本文注:请勿尝试!)

Bates 书里的图 47:一个 37 岁的妇女和一个 4 岁的儿童直视太阳而没有不适。(本文注:请勿尝试!

他甚至还说有一部分患者如果是把阳光直接聚焦在眼睛上效果会更好。

Bates 书里的图 48:用放大镜把阳光聚焦到一个病人的眼睛上。(本文注:请勿尝试!)

Bates 书里的图 48:用放大镜把阳光聚焦到一个病人的眼睛上。(本文注:请勿尝试!

Bates 所在的年代抗生素还没发明,可以说现代医学还没开始,他的书和观点在后来广受主流医学界否定。引用他写的内容来给别人提供医学建议实在是非常不负责任。

总结一下,术业有专攻,健康相关的问题还是要听医生的,如果对一个医生有疑问就多问几个医生,不要受互联网上非专业人士(包括我在内)写的东西影响。也不要相信某个行业在全世界范围内联合起来秘密牟利这样的阴谋论。


  1. 伪科学竟然还要钱 🤷 ↩︎

  2. 后来意外从 Wayback Machine 找到了。 ↩︎

从高考志愿到职业选择

读博士的时候我们系每年春季都会有一两天让在读的博士生留出特定时段待在自己的办公室,因为一些接到耶鲁的 offer 但还没有做决定的学生会到学校参观,他们可以到系里走访在读的学生,通过提问交谈更全面地了解学校和院系来帮助他们做决定。有一次走进来一个头发已经有点白的人,我还以为是本校其他系的教授,而他告诉我他拿到了我们系的 offer,所以来看一看。他多年前毕业于一所知名法学院,在纽约已经是一位成功的律师,但是他后来对计算机更感兴趣,所以决定不再做律师而从头开始学计算机。和我面谈时他已经完成计算机科学的本科学业,并被几个学校的博士项目录取。我问他是不是希望做计算机犯罪之类与法学相关的领域,他说不是,就是想完全转行。我对这件事印象深刻,一直记得。在我学生时代的认知里,高考时的选择基本上决定了一生的方向,而这个人在人生到了一半的时候不是只改变了方向,而是回到一个新的起点重新开始。

今年我一位侄女高考,所以问了我一些关于学校和专业选择的问题。我的基本建议是优先考虑报一所好的学校,专业是其次,尽量和兴趣接近就行。因为学校决定了未来四年的学习环境,这是很难再改变的因素。而大学里通常所有课都是可以选的,想在另一个专业再拿个学位也不是不行。这是我在走过这条路后回过头来按理性的思维给的建议,但是我自己高考的时候却是反其道而行,所有志愿填的都是计算机相关专业,并且不服从调配。当时我们还是先提交志愿后参加高考,不确定性更高。那时自己的概念里大学专业就决定了未来的从业领域,所以即使被录取到差一些的学校也不想在专业方面做妥协。所幸没有考得太离谱,还是被按第一志愿录取了。

我曾经觉得自己在这方面很幸运,在中国的同龄人里较早开始接触计算机,从小就知道未来想做什么,所以在别人纠结专业的时候我完全不需要选择,而自己的兴趣也正好是发展很快、机会很多的领域。但是随着年龄和经历的增长,我越来越觉得因为过早地在某方面产生强烈兴趣而排除了其他可能性未必是一件好事。Nike 创始人 Phil Knight 在他的自传 Shoe Dog 里说:

I feel sorry for the people that know exactly what they’re going to do from the time they are sophomores in high school. I think the process really needs to go through a time period before you really find what it is.

他所同情的大概就是我这类人。我在中学之后偏科是比较严重的,注意力都在计算机和英语上,到了最近这些年反倒因为对其他领域的好奇心而看了很多书来填补知识的欠缺。我比较后悔的一件事是在有时间有条件的学生时代没有更多地向广度扩展知识体系。

如果你关注 Charlie Munger 的文章或演讲,会知道他在很多场合强调过多学科思维的重要性。他认为具备多学科的基础知识和思维模式对于做正确的决策是至关重要的。只精于一门的人,一方面容易管中窥豹,拿着锤子就觉得什么都是钉子1,另一方面在某方面的资历越高,就越容易自信、越看不到自己的盲区、越可能在自己的称职范围之外做错误的决定。Liberal arts 教育对本科的定位是有所侧重的通识教育,所以很多美国大学都是先进入学校学习一段时间后再选专业。耶鲁的很多本科生到最后一年才选定专业,并且本专业的学分只需要占到毕业所需总学分的三分之一,所以拿双学位是很常见的事。在我们系即使是博士,前两年也要上各方面的课程,必须通过所有四个领域的综合考试2才能选择导师和具体的研究方向。我的导师在计算机科学的多个方向都有很高的成就,所以无论我选择在哪个方向深入下去,他都可以指导我。有一次我告诉他我对很多东西都挺感兴趣的,苦恼于如何决定博士论文的主题。他对我说「不用担心,大部分人的问题是专业化得太早,那并不是一件好事」。

很多父母都会努力让孩子「不要输在起跑线」上,就好像人生是一场赛跑。如果这么看的话,人生的轨迹就应该是线性的,每个阶段做的事情都应该是以上一个阶段为基础,不断积累,这样才能把到达终点时的某个指标最大化(可能是金钱、名望、或其他用来衡量人生的标尺)。以这样的人生观,本文开头的那个律师就不应该放弃已经有丰厚回报的事业到另一个领域从零开始。但是人生并不是一个比赛,往回看的时候充实感来自于在有限的人生里对多种可能性的探索,对大千世界的体验,以及对自己好奇心的满足。我很尊敬那些追随内心,不介意成为另类,在任何年龄都有勇气再次成为新手的人。


  1. 币圈和链圈大概有不少人属于这种类型。 ↩︎

  2. 分别为:计算理论、程序设计语言、人工智能和科学计算。 ↩︎

浅谈 Apple Intelligence

过去一两年有多位投资人朋友和我讨论过 AI 相关的创业机会。我的观点一直都是:大的机会基本上是巨头的,小公司没有特别好的机会。

当技术上的突破让小公司有机会颠覆大公司时,新技术最初的应用都在巨头看不上的新兴细分市场。随着这些市场快速扩大,小公司在成长起来后迅速地进入主流市场抢占原主导者的份额。随着原本不存在的个人电脑市场兴起的 Intel、Microsoft 等是最好的例子。近年来 AI 的发展在技术上有很大突破,但商业上的局面却没有给创业公司颠覆性的机会。一方面这是巨头们从一开始就重视的领域,投入很大;另一方面机器学习本身就需要大量的资源和数据,所以大公司或者他们投资的企业往往更有能力持续产出最好的成果。

另一个原因是新 AI 技术的应用往往是对现有场景和过程的改进或补充,没有创造出以前不存在的全新场景,所以对于已经掌握了用户关系的产品来说,后来者无法形成威胁。我喜欢举的例子是 Adobe 能在 Photoshop 中增加 AI 功能,Stable Diffusion 和 Midjourney 却不可能做出替代 Photoshop 的产品。在这种情况下,谁掌握了与消费者的直接关系,谁就掌握了市场。没有什么是比随身携带的手机和消费者关系更密切的,在可见的未来仍然如此。手机不仅仅是设备,还是包括应用和开发者在内的整个生态。Humane 和 Rabbit 之类的公司错误就在于试图做一个完全独立于手机但是又无法让用户不用手机的产品。Apple 的产品与消费者之间的密切关系决定了短期在技术上有没有走在最前面不是特别重要。OpenAI 做不出 iPhone 的替代品,更别说 iOS 的整个生态。但 Apple 可以把 OpenAI 的能力整合进自己的产品。如果 OpenAI 不愿意合作,还有 Claude 等众多选项,虽然不一定是最好,但差别也不大。

Apple 的设备会把一部分非个性化的请求发送给 ChatGPT。有一些媒体说与 Apple 的合作对 OpenAI 是利好,但是我认为 OpenAI 的收益是非常短期的。第一,Apple 与 OpenAI 的协议规定他们不能存储用户数据1;第二,Apple 在技术上也有措施避免 OpenAI 把同一用户的多次请求关联到一起。所以 OpenAI 得到的基本上仅限于财务收益,和用户之间建立不起有意义的关系。Apple 之所以在使用 ChatGPT 的时候明确告诉用户,一方面是为了透明,让用户知道信息发送到了哪里;另一方面恐怕也是要避免为 ChatGPT 引起的问题背锅。考虑到 Apple 走的从芯片到整机、到软件、到服务的垂直集成路线,以及 OpenAI 与 Microsoft 的密切关系,Apple 一定会在尽可能短的时间内用自己的方案替代 ChatGPT。可以说在战略上 OpenAI 是比较被动的,这是个他们无法拒绝的 offer。无论接不接受,他们自己的独立 C 端产品在 Apple 的平台上都不再有存在的意义,但自己赚这笔钱总比让竞争对手赚好。而且无论如何 Tim Cook 都会说「我们找了市面上最好的 partner 合作」,OpenAI 可不愿把这个背书给别人。

现在的 OpenAI 让我想起以前的 Nuance. Nuance 曾经是语音识别做得最好的公司。据说 Google Voice Search 最早是用 Nuance,但同时 Google 也用 Nuance 返回的结果训练了自己的语音识别系统,最后向第三方推出了语音识别服务,成了 Nuance 的竞争对手。过程是否真是这样我没能找到实证。很多知名汽车品牌的车机系统和 Siri 的早期版本都使用过 Nuance 做语音识别,但这家公司后来一直没发展到很大,最后被 Microsoft 收购了。即使是现在股价已经上天的 Nvidia,从长期来看在整个生态中的地位也并不是坚不可摧。芯片设计生产和相关的底层技术门槛当然很高,但是往后绝大部分面向消费者的 AI 应用的集成点会是 Apple、Google、Microsoft 在操作系统层面提供的 API,而不是 CUDA.

Apple Intelligence 是目前为止最让我兴奋的 AI 产品。之前的各种 chatbot 都仅限于给用户提供答案,实际根据这些答案执行动作还要靠用户,而 Apple 则有条件让 AI 代替人完整执行一些任务。当然这样让 AI 的决策直接造成现实中的结果是有风险的,如何尽量把人工操作在流程中减少,同时又让风险可控,这是个需要仔细平衡的问题。Apple 的另一个独特优势是可以访问用户的大量私有数据,从而能帮助用户完成高度个性化的任务。说到这个,Google 曾经有个叫 Desktop Search 的产品,是帮助用户检索 Windows 和 Mac 上的本地文件的,后来被关闭了,如果留到现在会很有价值。

Apple Intelligence 带来的可能是类似 2007 年 iPhone 一代发布所引起的从功能机到智能机那样的重要变化。Apple 的商业模式不依赖于所谓 user engagement2,在隐私保护方面也有比较好的 track record,在几大巨头里或许是最适合推动这个历史进程的公司,结果应该会更符合大众利益。


  1. 虽然以 OpenAI 的 ethical standard,我很怀疑这一条能执行得有多严格。 ↩︎

  2. 比如 Apple 还有 Screen Time 这样的功能来帮助用户避免过度使用他们的产品。这方面的讨论详见我写的关于搜索和广告的文章。 ↩︎

2024 年,我为什么开始为搜索付费

我 2007 年至 2010 年在 Google 工作的时候,中国的同事们都说百度无良,欺骗用户,把广告显示得和原生搜索結果几乎一样、难以区分。Google 的搜索广告在 2013 年之前一直是通过背景色与原生结果明确区分的。

带背景色的 Google 广告

带背景色的 Google 广告

后来背景变得和原生结果一样,改为用彩色的标签来标识广告。无论在用背景色的时代,还是用标签的时代,Google 都做过很多试验来通过调整颜色提高转化率。从 2020 年起,Google 的广告变成这样了:

现在的 Google 广告

现在的 Google 广告

不但视觉上和原生广告完全融合,而且首屏已经被广告占满。这个结果可以说是必然的,不做任何试验就能知道广告样式越接近于原生结果,点击率必然越高。之前做的种种实验无非是 Google 创立之初的价值观和与之矛盾的商业模式相纠结的漫长而耗资巨大的过程。

很多人都读过 Google 的两位创始人在创立公司之前写的 PageRank 论文,但很少人会看附录。这篇论文的附录 A 是 Advertising and Mixed Motives

… The goals of the advertising business model do not always correspond to providing quality search to users. … It is clear that a search engine which was taking money for showing cellular phone ads would have difficulty justifying the page that our system returned to its paying advertisers. … we expect that advertising funded search engines will be inherently biased towards the advertisers and away from the needs of the consumers.

Since it is very difficult even for experts to evaluate search engines, search engine bias is particularly insidious. … Furthermore, advertising income often provides an incentive to provide poor quality search results. … This of course erodes the advertising supported business model of the existing search engines. … But we believe the issue of advertising causes enough mixed incentives that it is crucial to have a competitive search engine that is transparent and in the academic realm.

不难看出在刚有 Google 的时候两位创始人认为基于广告的商业模式与用户的利益和高质量的搜索结果是有根本冲突的。只是当时 Google 还不是一家公司,所以他们可以义正辞严地批评其他搜索引擎并说明 Google 做为一个无广告的、学术界的搜索引擎的重要性。他们似乎认为靠广告盈利和作为学术性的、非盈利的服务是一个搜索引擎的唯二选择。

广告成为互联网的主要或者说默认的商业模式对用户和行业生态是个悲剧。这个话题值得单独写一篇文章来探讨,但简单地说:靠广告得到收入让用户因为不用支付钱这种容易衡量价值的资源而有一种「免费」的错觉。但同时用户往往在付出更加宝贵的资源,比如时间和隐私。这些资源虽然价值更高,但因为难以衡量,所以大部分人在付出时并不像对金钱一样敏感。向用户直接收费的产品往往会通过使用频次、时长等信息来了解用户习惯,从而在产品设计上进行取舍,但不会为提高这些指标而做对用户没价值的改动。对于以广告盈利的产品来说,因为提高广告收益是终极目标,优化的是与此相关的用户行为指标,而不是对用户的价值。这种利益的错位在基于推荐的产品中比搜索引擎更加严重。为了引导利于广告收益的行为模式,无论是设计产品的人还是推荐内容的算法往往会让用户更多接触到符合自己已有观点的信息,形成信息茧房,限制用户的视野,加剧社会的极化。所有公司都会宣称把用户价值放在第一位,对有的公司来说是产品为用户提供的价值,对更多的公司来说实际是用户给广告主提供的价值,取决于哪一种含义是和商业模式对齐的。

因为以上的原因,我很愿意支持那些有简单和健康的商业模式又为用户提供高价值的优秀产品。所以去年试用 Kagi 之后我开始付费,到现在已经用了半年多,累计省了很多时间,感觉再也回不到 Google。Kagi 是一个订阅制的无广告的搜索引擎。它和过去出现的 DuckDuckGo、StartPage 等 Google 的替代产品有两个显著差异:

  1. 搜索结果质量大部分时候优于而不是劣于 Google;
  2. 收入来自于用户付费。其他产品要么缺乏商业模式,要么和 Google 一样收入靠广告。

我就偷懒借 Vlad 博客里的两个例子来说明。

当你搜索一个技术问题时,你想看到的是官方文档或者同行的技术博客,而不是一些工具和云服务的广告。

当你在购物前做研究时,你希望看到的是论坛上真实用户的评价和评测网站上的对比,而不是一堆电商平台的下单链接。

除了总体上更好的搜索体验以外,Kagi 对我来说有三个比较特别的优势。

第一是强大的个性化功能。每个用户可以按自己的需要把个别网站在搜索结果中置顶、提权、降权或屏蔽。比如如果你经常查找编程相关的内容,可能会想把 Stack Overflow 或者 MDN 提权或置顶。

第二是完善的家长监护功能。我小孩会花过多的时间看 YouTube 上的 Minecraft 视频,虽然我可以在 Screen Time 把 YouTube 禁用,他还是会用 Google 结果里的内嵌视频看。我买了 Kagi 的 family plan,设置好他的儿童账号,在 Kagi 把 YouTube 禁掉,再在 Screen Time 把 Google 禁掉就完美解决了这个问题。

第三是如果订阅 Kagi Ultimate,除了搜索外还可以使用 OpenAI、Anthropic、Google、Mistral AI 的所有语言模型,价格只相当于其中一个的订阅费用,是很划算的。

自从有搜索引擎以来,主流的搜索引擎都是免费的,所以大部分人也都已经习惯,为搜索付费听起来是件不可思议的事。就连英文媒体介绍 Kagi 也用 premium search engine 来描述,暗示并不针对大众用户。但是有足够多的人愿意订阅在线流媒体服务,Netflix 能成为市值近三千亿美元的公司。相较而言,与学习、工作、生活息息相关的搜索价值要大得多。Kagi 在五月份宣布已经实现盈利,商业上的可持续性得到了初步验证。如果你也觉得 Google 的体验越来越差,也希望给你提供信息的服务动机和你的利益而不是广告主的利益一致,可以考虑用钱包投票支持一下 Kagi 这样的产品。如果这样的商业模式成为主流,用户在真正意义上成为各种网络服务的客户,互联网生态可能会变得很不一样。

运气与努力

我一直很喜欢 Out of the Gobi1 的作者单伟建。他的三本书我都读过。他在文革时被流放到戈壁六年,失去了上中学的机会,但从没放弃学习。没有放弃对更好未来的追求。在文革结束后他抓住了机会到美国留学,在金融界取得了很高的成就,并创立了亚洲最大的私募基金集团 PAG。他在母校旧金山大学的一次演讲中说2,在任何领域取得成功,最重要的是三方面:

  1. 坚持终生学习。他本人的经历最能说明这一点。如果他在离开学校后没坚持学习就沒有后来的一切。
  2. 好的判断力(good judgement),他特别提到这比 Angela Duckworth 强调的毅力(grit)3更加重要。方向对了努力才有意义。没有人是生来就有好的判断力的,正确的决策要建立在知识的学习和积累上,所以这可以算上一条的补充。
  3. 运气。很多人把成功归功于自己的才能和努力,却没有意识到好运在其中的重要性。忽视了这一点就难以保持谦虚,难以不断学习。

不少很成功的人都公开谈过运气在自己经历中扮演的角色。Charlie Munger 在 2017 年的一次演讲4中说,大部人只要每天起来后都努力做事,有一些自律并且持续学习,最终都会发展得很不错。但是目标不能定得太高,像成为总统或者成为亿万富豪之类的事,运气的影响太大,成功的可能性太低。Warren Buffet 也在回答一个关于资本增值税的问题时说,「卵巢彩票」恐怕是影响一生最大的因素,而他和 Charlie Munger 都赢了卵巢彩票。5李光耀曾经在采访中说胚胎形成的时候已经决定了一个人特质的 70%,因为领袖是天生的而不是教出来的。6我不完全同意他关于个人特质的观点,但那一刻除了决定基因外,更重要的还决定了后天的成长环境,确实对人的命运起到了近乎决定性的作用。

我最早接触计算机从 DOS 用起,直到后来的 Windows 3.0/3.1、Windows 95。我中学时看了 Bill Gates 的传记和他本人写的《the Road Ahead》,他的故事也是最早让我产生创业梦想的原因。Bill Gates 的成功除了自己的天分和努力外,也离不开他出生的时代和家庭背影。他 13 岁的时候就读的湖滨中学由家长集资购买了一台可以访问通用电气西雅图分部的 PDP-10 小型机的终端。当时微处理器、Atari、Intel 都还不存在,他学编程的时候全美国没有几个同龄人能接触到计算机。他父亲是成功的律师,当时已经在做天使投资,她母亲虽然从没全职在商业公司任职,但是因为家族是很成功的银行家,在很多公司的董事会里。他童年时每天家里晚餐的客人不是 CEO 就是议员。后来他母亲与 IBM 董事长的社交关系也帮助他得到了 IBM 的合同7,这是微软起飞最重要的一步。在那之后微软的快速成功也很仰赖于合作伙伴的局限性和竞争对手的失误。微软一开始的重心在程序设计语言上,唯一的产品是 BASIC 的解释器,对操作系统并不重视。在与 IBM 合作的初期,他们推荐 IBM 去找开发 CP/M 操作系统的 Digital Research 合作。而 Digital Research 的 CEO 因为未知原因没有见 IBM 派去的代表,所以双方没有达成合作,微软只能另想办法。当时有一家当地硬件公司 Seattle Computer Products 克隆了 CP/M 用来测试 Intel 8086 处理器,这个操作系统叫 QDOS8。微软向他们购买了 QDOS,并雇它的作者来基于 QDOS 开发了 MS-DOS,而从它开始的操作系统成了微软帝国的根基。可以说微软的成功是非凡的天赋、正确的策略、和令人难以置信的好运合力作用的结果。

就我自己的经历而言,哪怕是一些和别人比起来很小的成功,运气也起了重要作用。比如我从本科毕业后能去耶鲁读博,就是一件很巧合的事。我读本科的时候美国的研究生院对中国各大学的了解还不多,往往只会招收比较熟悉的几所学校的毕业生。耶鲁的计科系在那之前只招收过清华,中科大,南大这几所大学的学生。因为不了解其它学校的情况,所以 GPA 等也就无法比较。我能被录取,我后来的导师的推荐信起了关键作用。起因是我在本科时和他有一些关于他一篇论文的 email 讨论,后来又参加了同一个学术会议,他听了我的演讲后主动和我说如果我以后申请研究生院,他可以给我写推荐信。申请研究生院的人中,很少有人能得到这样被深入了解的机会。如果没有这样的机会,我自己的努力恐怕是无论如何都不够的。我上大学时只是因为英语和计算机方面比别人先行一步,所以能有一些时间在本科就参与一些研究,开始写论文而已。以当时的水平能做的事很粗浅,真要有多出色的成果是不太可能的。当时没想到的一点是,我得到录取也算是给后人开了一条路,后面几年就能在系里遇到低年级的武大校友了。

明白了运气的重要性,就知道不是人人生而能得到平等的机会,在遇到处境不如自己的人,不能假没这种差别是聪明或努力程度的不同造成的,应该知道要善待弱者。我们这一代 80 年前后出生的中国人,父母都经历了文革,很多人受的教育有限,对下一代的教育也就不太重视。我遇到过有些比我聪明也比我努力的人在年少的时候因为父母的观念,或者迫于家庭的财务压力没有得到好的教育。有的能凭借自己的努力加上后来的机会,克服早年的逆境,有的就没有那么幸运。所以我一直觉得自己是极其幸运的,也很感念父母始终把我的教育放在第一位,在自己的生活上做了很多牺性。

当然,说运气很重要,并不是指它总是唯一决定因素,只能听天由命。运气和很多其他东西一样都是呈正态分布的,真正一直很倒霉和一直很走运的人都很少,而好运的人也不乏浪费很多机会的。单伟建说他是极度幸运的,这当然是很谦虚的说法。在宝贵的少年时期经历文革本身就是极大的不幸,而且他还因为和领导关系不好而失去了离开戈壁去求学的第一次机会,所以只能说有一些不幸中的大幸给他提供了关键机会,而他因为自己坚持学习抓住了机会。拿我当年申请研究生院来说,如果之前没有足够努力,既使得到了展现自己的机会,恐怕也没有优点可被看到。大部分人都是既有不幸,又有机遇。机会到来的时候,有准备的人更容易抓住。

大家向成功的人或公司学习的时候,往往会带着一种偶像情节,希望通过复制他们的所有特点和行为方式来复制成功。所以学 Steve Jobs 的 CEO 们往往先学到了他性格上的弱点,学阿里等企业的公司,往往先学会了一种畸形的企业文化,因为最流于表面、最没有深度的东西最容易学。成功当然是值得学习的,但是需要分辨不同因素起到的作用:有哪些是因为出现在正确的时间正确的地点,又具备了正确的条件,被时代的趋势所推动;有哪些是偶然事件;有哪些是主观作用。对主现因素,应该分清楚成功者是因此而成功,还是虽然如此但还是成功了。这样才知道该学的到底是什么。

不少略有小成的人,大概因为成功来得容易,同時又看不到偶然性的作用而认为自己有一套可复制的系统或者方法论可以高价教给别人。又或许他们其实很清醒,只是不想错过利用别人渴望成功的心态让自己更加富有或增强个人品牌的机会。

不久前一个朋友说有个曾在 IBM 做过中层管理者的人建入群费 200 的微信群教人职场和生活方面的经验。很快就有几千粉丝入群,后来大概觉得价格定低了,又建了一个入群费一万的 VIP 群,马上就有大几十人报名。现在可以说是学习最容易、成本最低的时代。无论哪方面的信息在互联网上都唾手可得,而且大部分是免费的。当然,好的老师、课程、书籍是有价值的,因为虽然大部分信息都能免费找到,如果被组织成更体系化、更容易理解的形式,吸收起来就更快,也更容易让人得到启发。但是没有任何微信群是值一万进群费的。9

商业模式往往是以某种信息不对称为基础。互联网消除了很多信息上的不对称后,就有人人为制造出一种信息不对称的错觉和神秘感。卖成功学的人往往要用各种理论包装一番,因为如果看起来很简单就无法赚钱。成功的要点单伟建和 Charlie Munger 的几句话已经总结得很好,简单但不容易,如果能长期坚持就会比大多数人成功。


  1. 我以前写的书评。如果有条件的话,这本书建议看英文版。国内中文版你懂的。 ↩︎

  2. 见 YouTube: Dr. Weijian Shan at the University of San Francisco - May 18, 2023 ↩︎

  3. 有兴趣可以看 Grit: The Power of Passion and Perseverance 或者她的 TED Talk。 ↩︎

  4. A Conversation with Charlie Munger and Michigan Ross ↩︎

  5. Here’s why Warren Buffett says that he and Charlie Munger are successful ↩︎

  6. Lee Kuan Yew: The Grand Master’s Insights on China, the United States, and the World ↩︎

  7. How Bill Gates’ mom helped Microsoft get a deal with IBM in 1980 – and it propelled the company’s huge successWhy you shouldn’t imitate Bill Gates if you want to be rich ↩︎

  8. QDOS 的全称是 Quick-and-Dirty Operating System。 ↩︎

  9. 更不用说是传授 IBM middle management 经验的微信群。 ↩︎

刷新了一下对内容审查粒度的认知

我看书大部分时候是用 Kindle,偶尔也会从京东、多抓鱼,或者嘉里中心的现代书店买纸质原版书。新书的扉页背面基本都会盖这样一个章。

扉页背面的审查印章

扉页背面的审查印章

我原本以为这基本是一个形式,获准进口的成年人类别的纸质书无论是什么内容都会在这一页上盖这个章。直到前几天在一本很厚的书中间的一页看到这个章以及被涂改液覆盖的脚注。

被审查涂改的页面

被审查涂改的页面

没想到我国内容审查的粒度竟然那么细。很多人都会问为什么中国的税率那么高,社会福利安全网却还比较差。从这件事的人工成本就能想见政府运行的成本有多高,应该说大家的税还是没有白交的…

从某个时候起,中国的任何互联网产品只要有任何形式的 UGC(包括头像昵称等),就会有很高的审核成本。这类产品早就不是小创业公司能做的了。

如果你好奇,这本书是 Behave,是一本讲人类行为的科普读物。涂改液覆盖的内容是:

(11) Mao-induced Chinese famines, 1.4 million

排在人为灾难年化死亡人数的第 11 位。

离开心动和 TapTap

在 LeanCloud 被心动收购快三年的时候,我离开了心动和 TapTap。

Removed from LeanCloud GitHub organization

Removed from LeanCloud GitHub organization

一段时间前我看到 Will Larson 写的 Deciding to leave your job,其中讲到决定是否离开时应该问自己的四个问题:

  • Has your rate of learning significantly decreased?
  • Are you consistently de-energized by your work?
  • Can you authentically close candidates to join your team?
  • Would it be more damaging to leave in six months than today?

这几个问题可以说贴切地描述了我当时的状态。我对于公司的管理风格和文化有比较强的看法,过去创业最重要的原因也是要实现自己的这些想法。所以如果在这些方面对所在的公司失去认同,就很难专注地把工作做好,很容易把注意力转移到个人自身的提高上。这或许可以算是我的缺点。所以应该说离开的决定在内心早就做了,只是一直没考虑确定具体时间,也一直没好意思提出。在国庆前正好我创业时的合伙人俊文也说准备离开,所以国庆后我就一起提出了。毕竟他离开就意味着很多工作要交接,组织结构也要调整,以其过几个月再调整第二次,不如一次到位。过去三年内外部环境都变了很多,我所负责的业务对现在的 TapTap 来说确实价值很有限,我们离开也好给公司需要做的一些改变让路。

之后我很快加入了 EMQ。这是一家做跨境支付的公司,国内还有一家做 IoT 的同名公司,所以很容易混淆。我在 10 多年前还在负责 AVOS 中国的时候就通过 Steve Chen 介绍认识了 EMQ 的创始人 Max,当时他还没创业。EMQ 和 LeanCloud 是同一年开始的,后来 Max 多次邀请过我加入,虽然时机都不太合适,但我一直关注着这家公司的发展。这次时机对双方来说都很合适,所以我就决定加入了。过去很多人都说我离钱比较远,那么这对我是个离钱近一些学习 financial services 的机会。

EMQ 是一家比较西式的创业公司,有 80 多人,开发团队有十多人,在台湾、香港、新加坡、印度、欧洲和东南亚都有同事。让我比较意外的是产品和运营团队的不少同事也都有很不错的技术背景。我们的技术栈包括 Erlang、Python、TypeScript、Clojure、Postgres 和 AWS,目前前后端都有职位在招聘,如果你有兴趣可以把简历发到 engineering-hiring@emq.com 和我们联系。春节之后在上海应该会有办公室,此外我们也会考虑很优秀的远程候选人。

如何反转一个链表?

「如何反转一个链表?」是一个在面试中被问滥的问题。我参与的面试中偶尔也有我们自己的面试官问。 如果你去别的公司面试被问到这个问题,要是给出的答案是(以 Python 为例):

def reverse(l):
 l.reverse()
 return l

或者是:

def reverse(l):
 return l[::-1]

肯定会被拒掉。面试官所预期的是你自己定义节点,再定义链表:

class Node:
 def __init__(self, data):
 self.data = data
 self.next = None

class LinkedList:
 def __init__(self):
 self.head = None
 # 以下略,问 ChatGPT 就可以了。

不过既然是个被问滥的问题,如果遇到不妨尽量给出一个面试官没见过的答案。 比如,构造链表:

def cons(h, t):
 return lambda f: f(h, t)

取头:

def car(l):
 return l(lambda h, _: h)

取尾:

def cdr(l):
 return l(lambda _, t: t)

反转:

def reverse(l):
 rev = lambda l, r: rev(cdr(l), cons(car(l), r)) if l else r
 return rev(l, None)

以上就是完整答案。为了展示方便写个打印链表的函数:

def printl(l):
 toStr = lambda l: str(car(l)) + ' ' + toStr(cdr(l)) if l else ''
 print('(', toStr(l),')')

写个例子试一下:

l = cons(1, cons(2, cons(3, cons(4, None))))
printl(l)
printl(reverse(l))

输出是:

( 1 2 3 4 )
( 4 3 2 1 )

这应该是自己构造链表的最短答案了,但是有一定风险被面试官以奇怪的理由据掉。如果你是面试官,又想问这道题,就得了解各种实现方式,避免把不该拒的人拒了。🙃

如何高效地协作开发:一些 Google 的实践

这是我一年前发在 TapTap 内部 Confluence 的一篇文章,经过一些修改后公开出来,希望对更多人有价值。

Google 的很多软件工程实践都在对外发布的各种 Tech Talk、CppCon 的演讲以及多本已出版的书里提到过(比如 Software Engineering at Google、Site Reliability Engineering 等),所以这篇文章的内容并不算新鲜事,只是贡献一些个人视角。另外我在 Google 工作已经是 10 多年前的事,现在可能已经变化很大,但我认为 2000~2010 年的时候是 Google 最有创造力、最高效、对人才的吸引力也最强的时候。

一点背景

有时言必谈前公司的人会有点让人讨厌,不过无论是之前创业,还是现在,讨论起工程方面的事情都很难避免提起 Google 是怎么做。Google 和所有公司一样,并不是所有方面都做得很好,比如产品能力以及饱受诟病的客户服务。但就是因为在 engineering 方面领先大部分公司很多,所以削弱了其他方面的弱点带来的影响。Google 取得成功的大部分产品基本都是在技术实现上大幅领先同时代的产品,从而实现用户体验上的领先,早期产品中最有代表性的是 Search、Maps 和 GMail。Google 的 engineering culture 也对包括 Facebook 等在内的大量硅谷公司以及国内包括字节这样的公司产生了深远影响。

我在 Google 的三年是在一个叫 Google Web Server(简称 GWS)的团队。这个项目可以说是 Google 历史最悠久的项目,从 Google 存在开始就有 GWS,到现在 20 多年,Google 的 HTTP header 里 server 还是 GWS,应该还是同一个项目、同一个 code base 和 binary。

GWS in Google’s HTTP header

GWS in Google’s HTTP header

一开始的时候 Google Search 就是 GWS。后来从它里面拆出了一部分放到前面承担类似 SLB 的角色,叫 GFE(Google FrontendEnd);又把实现单纯搜索的部分拆了出来叫做 Superroot。GWS 更多地成为了一个实现搜索相关的整体业务逻辑的服务,它后面有 15~20 个后端服务,除了 Superroot 外,还有广告、拼写检查、搜索词修正、query rewrite、用户偏好等等。经过多年的演变,GWS 的开发语言也从 Python 变成了 C,再变成 C++,后来又为了方便快速做试验内嵌了 Python 解释器。在整个过程中,GWS 从来没被真正意义上重写过,因为 Google 一直都有大量的业务需求等着实现,不可能有停下来重写或者重构的机会。所有大的改变都必须以渐进的方式来实现,包括换语言。尽管只有 Google 一家公司在用,但是 2010 年的时候 GWS 就支撑了全球 13% 的活跃网站,是排在 Apache、Nginx、IIS 之后的第四大 web server,因为 Google host 的网站很多都是 GWS 来 serve 的,包括自定义域名的 custom search 以及 enterprise search。

几乎所有在搜索结果页用户可见的改动都会需要改 GWS 的代码。这就涉及到了很多其他项目和团队,除了和搜索有紧密关系的广告外,还有地图、新闻、财经,甚至小到汇率转换、计算器。Google 的搜索框不仅是狭义的搜索,其实功能是很多的。这就导致项目非常复杂,改动也非常频繁。GWS 的二进制文件编译出来有 1G 多,在当年已经超过了 gcc 和 gdb 的上限,需要使用内部改过的版本。项目每周发布一个新版,平均有大几百个 changelist。Changelist 是 Perforce 的名词,相当于是 GitHub 的 pull request 或者 GitLab 的 merge request,是一次 code review 的单位,大小通常是多个 commit。

这样一个项目听起来似乎需要一个庞大的团队来维护,而且很容易成为瓶颈。我 2007 年加入的时候,团队有不到 20 人。2010 年我离开的时候有 40 人左右(负责的范围也变大了),分布在 Mountain View、芝加哥、匹兹堡、纽约。除了 GWS 外,这个团队还维护着一些服务端的基础组件,比如我们开发维护的一个项目是 Google 大部分 C++ 服务的基础,提供了标准的健康检查、feature flag、监控变量管理、实验框架等功能。团队成员还有时间发起一些 20% project,比如我和另外两位同事合作开发了一个 serve Google 所有静态文件的新服务,把这部分职责从 GWS 分离出来。听起来这么多的事情由那么小的团队来做是不可能的。秘诀在于 GWS 每周大几百个 changelist 中大部分并不是这个团队里的人写的代码。

GWS team 不负责实现其他产品在搜索里的需求,但是会花大量时间 review 其他产品的工程师提交到 GWS 的改动。由于每周的改动量巨大,很多人会花一半以上的工作时间在 code review 上。GWS team 自己也做开发,但是责任是不同的,日常开发大概有几类

  • 为了提高 GWS 的性能和稳定性,或者为了团队本身的开发效率做的重构和改进
  • 为了让其他团队能更容易地修改 GWS 而开发的模块化功能,比如 OneBox、ManyBox、Universal Search
  • 为搜索和广告的整体业务需求增加的功能,比如试验框架
  • 上一段所说的输出给其他项目的基础组件以及从 GWS 衍生出的新项目

因为这是我的第一份全职工作,其实在 Google 期间并没有觉得有多好,以为大公司都是这样的。但是离开之后才觉得,能有这段各团队之间能顺滑地高效协作的工作经历是很幸运的。这样的工作模式在大部分其他公司很难完全复制,因为它需要一些很强的基础设施做支持。以我们目前的工作模式,很难想象能达到同样的吞吐量,可能也很难复制同样的模式。但是分析一下别人做得好的地方还是能给我们提供一些方向上的参考,做一些努力能提高的空间还是很大的。下面我就介绍一下支撑 Google 团队间高效协作最重要的几个方面。

代码管理和安全

Google 的每个工程师都可以访问全公司 99.9% 的代码,这是决定 Google 的工作方式最根本的条件。剩下只有少数人能访问的那部分叫 HIP (High-value Intellectual Property),主要是防 SPAM 的逻辑。这部分代码如果泄露了,很快就会被恶意网站利用使得搜索质量显著下降,并且没有可以迅速补救的办法。其他所有项目的所有代码都是对所有工程师开放的。

Google 全公司共用一个代码库,叫做 google3,用 Perforce 做版本管理,但是自己在 Perforce 的命令行客户端 p4 之外包了一个工具叫 g4(为了描述方便,以下 Git 和 Perforce 里 submit、merge 等名词区别就不纠结了,从上下文意思应该清楚)。每次一个工程师在自己本地创建一个 workspace 的时候,g4 会用 OverlayFS 从 NFS 上把最新版本的整个 google3 映射到本地。从工程师的视角,整个 Google 的代码树就是自己电脑上一个只读的大目录。当他 checkout 某个路径进行编辑的时候,g4 会把对应的子目录实际复制到本地,这样在他的视角那个目录就变成可读写的了。所以任何一个工程师都可以编译、测试、修改公司的任何一个项目。

很多人都会问,这样代码不会泄露吗?在那么大的公司可以肯定一定是会泄露的。2010 年前后有很多 Google 中国的人去百度,人民搜索(云壤)、盘古搜索也都是 Google 的人出来做的,要说没有知识产权外泄谁都不信。但是对公司而言,让自己跑得更快远比让竞争对手跑得慢一点更重要。所以大部分情况下保密措施应该是以不伤害效率为前提的。对用户数据的保密除外,但是保护用户数据的措施通常不会影响到大部分人的工作效率。

当时内部邮件列表每个人都可以建,项目管理工具的项目每个人都可以建,工程师可以看任何一个数据中心的任何机器上任何服务的日志,甚至可以动态修改它的 feature flag。理论上这些都可以被滥用或者误用,但是滥用往往缺乏动机,误用可以从设计上避免。一个大原则是风险可控或可逆的事情默认是没有流程的,只有实际发生了问题,证明必要时才会靠引入流程来解决。有了流程就需要有人审批有人执行,如果它解决的问题不常发生、有其他方案或者产生的危害不如流程带来的成本,那么设立流程就是不理性的。

共用一个开放的 repo 目的是让每个工程师都能访问、修改、运行公司的任何项目。要做到这一点,还需要每个项目都使用同样的基本工具。

构建工具

Google 使用的构建工具叫 Blaze,支持 Google 当时允许使用的四种语言:C++、Java、Python、JavaScript(Closure)。后来 Blaze 开源了,改名叫 Bazel,由社区增加了对更多语言的支持。

无论哪个项目,编译的方式都是在 google3 目录运行 blaze build [path],而 blaze test [path] 是运行项目里的所有测试。一个工程师如果需要对某个他不熟悉的项目做一些小的修改,需要的知识是很少的,只要做完改动后确保新的代码有测试覆盖,所有老的测试能通过,就可以提交到 code review 的系统了。

每个项目会有一个 Wiki 页面介绍怎么在本地运行和调试,通常来说是描述需要传递什么命令行参数来连接本服务依赖的线上或测试环境的其他服务。对于需要实际运行、进行人工验证的复杂改动,看了 Wiki 以后就能自己在本地跑一个实例,这类改动在 code review 时也通常会要求提供一个本地 demo 的地址。所以任何一个工程师都可以把 Google 的任何一个服务从源代码编译并运行起来。

自动化测试

GWS 作为一个 C++ 的项目,测试覆盖率保持在 90% 以上,这是非常不容易的。用静态语言的项目测试难度比动态语言大很多,因为对象的属性和方法无法动态替换,想要能在测试中 mock 掉 side effects 需要在设计上做更多的努力。自动化测试的好处相关的书上有很多,就不赘述了。我只说两点:第一,对自动化测试的要求确实可以产生更好的设计,比如鼓励面向接口的设计;第二,对于 Google 那样的协作模式来说,自动化测试不是一件锦上添花的事,而是必须。因为无论是去修改其他项目的信心,还是让其他人来修改自己的项目的信心,都来自于很高的测试覆盖率。

确保自己项目的稳定性和质量的方式不是不让别人改,而是把自己关心的东西加到测试里去。比如当时我们有一个用来表示用户请求的类叫 GWSRequest,一个 GWSRequest 对象的生命周期就是整个请求处理的过程,所以这个类自然就成了存这个过程中产生和消费的各种信息的地方。时间一久它的属性就越来越多,还出现了多个属性重复存了同样的信息的情况。后来我们加了一个测试 STATIC_ASSERT_LE(sizeof(GWSRequest), MAX_GWSREQUEST),试图往 GWSRequest 加新属性的改动在编译时就会失败。如果有人想改 MAX_GWSREQUEST 的值,就需要说明为什么是必须的。有的比较卷的团队为了控制代码的复杂度,还把自己 code base 的行数上限放到了测试里。如果有人增加了 10 行代码,就需要重构其他地方的代码来省出 10 行,或者提供一个好的理由来提高上限。

Code Review

Code review 是每个 Google 的工程师日常工作中很核心的部分。每一个改动都需要经过除作者外至少另一个人的 review 才会 merge。无论是对发明 Python 的 Guido van Rossum 还是发明 C 的 Ken Thompson, 都是如此。google3 代码树中每个项目的目录有一个 OWNERS 文件,里面是一些工程师的 ID。当有人提交一个 changelist 后,系统会自动把这个 changelist 分配给涉及到的每个目录的一个 owner 来 review,每个 owner review 通过,并且涉及到的各目录的测试都成功之后才能 merge。Guido van Rossum 加入 Google 后前几年的成果就是开发了内部的 code review 工具 Mondrian,可见这件事对 Google 的重要程度。

做 Review 的人主要关心几件事:

  • 改动的业务逻辑是否正确,具体实现在性能和可读性方面是否合理;
  • 是否符合 Google 的代码规范以及本项目附加的一些条件;
  • 新的代码是否有足够的自动化测试覆盖;
  • 用户可见的改动是否被产品经理批准(会要求提供 Buganizer 的链接,Buganizer 是 Google 自己开发的类似 Jira 的系统)。

除了日常的 code review 外,每个新员工会需要学习公司的代码规范,并通过工作中会用到的每个语言的可读性 review。方式是准备一个百行以上的 changelist,提交给一个有资格做 readability review 的工程师,通过之后才有权限提交用在生产环境的代码。有权做这类 review 的是在一个叫 readability committee 里的资深工程师,他们也负责制定公司的代码规范。比如当时给我做 C++ readability review 的是Generic Programming and the STL 的作者 Matt Austern。我刚入职时同组的同事告诉我 Guido van Rossum 到 Google 后第一次 Python readability review 没有通过,不知真假。

Code review 除了让项目的 owner 保证代码质量,让其他项目可以快速推进需要的改动外,还起到传播知识的作用。很多时候一些在公司多年的工程师的 reviewer 是入职不久的新人,做 review 的人往往也能从别人的代码里学到很多东西。

版本管理和发布流程

Google 在内部做版本管理的方式就是不做版本管理,所有项目都 live at head。当然,每个项目都有对公司外的版本发布流程,live at head 是对内依赖的策略。一般不会出现一个项目依赖一个组件的 1.1,而另一个项目依赖这个组件的 1.2 这样的情况。每个项目在每次编译的时候,都会把所有依赖从最新的代码编译,使用到的依赖的版本是由编译的时间点决定的。这自然就意味着每一次提交后的代码状态都应该作为稳定版本对待。

一般外部的开源项目,会分不同的版本号,由用户决定什么时候升级,并且由用户做升级所需要的改动。按照 2015 年的数据,Google 有 20 亿行代码,其中的依赖关系错综复杂。而 C++ 对于一个项目用到的两个组件依赖同一个组件的不同版本的场景(菱形依赖)支持是很脆弱的。所以不太可能使用 SemVer 之类的版本方案,除了纯技术上的问题外,每个组件要为内部用户维护多个版本也是成本巨大的。所以 Google 把版本管理完全倒了过来,每个项目/组件都只要维护一个最新版,所有的改动最重要的原则是不能破坏任何测试。所以如果有人在一个共享组件里做了向前不兼容的改动,就会需要在同一个 changelist 里把整个代码库里所有调用到这个接口的地方改过来。有的改动会动到十多个项目,需要十几个 owner 来 review,这并不少见。当然 Google 有强大的代码搜索工具来辅助这样的事。据几年后 Google 工程师在 CppCon 上的分享,他们还用 MapReduce 做了大规模重构工具,不过是在我离开之后了。

没有内部版本管理就意味着要保证每个版本的稳定。Code review 的工具会保证每次 merge 前都运行了所有涉及的测试,但是有时还是会发生代码 merge 之后导致测试失败的情况。比如可能有 race condition,也就是两个人几乎同时 merge 了会相互影响的代码;也可能失败的测试和改动的文件不在一个目录,导致 merge 前没有运行。GWS team 有个工程师轮值的角色叫警长(sheriff),一旦 GWS 的任何测试变红,本周的警长就会找出来是谁干的,并把对应的改动撤销。后来有人做了工具把这个流程半自动化了,一旦有测试失败,就会自动生成一个 changelist 把对应改动撤销并发 code review 给警长,他只要确认、跑测试、点 merge 就行。再后来有的团队把这个过程完全自动化了,不用人工干预。

GWS 每周会做一次 binary push,也就是二进制文件的发布。流程是每周一早上负责发布的工程师从当前的代码做一个发布分支编译出一个二进制文件,交给 QA 开始测试,发现 bug 就把修复 cherry pick 到发布分支。由于大部分逻辑错误都会在开发过程中的自动化测试发现,QA 阶段发现的大部分是通过截图比对工具找到的某条线移了一个像素之类的问题。QA 流程通过后,就从单数据中心单台机器开始灰度,逐步发布到全球。如果到周四下班还没完成发布,本周的发布就会被放弃,下周再重复同样的流程。发生发布失败的情况很少,如果发生会作为事故来开会复盘。

除了 binary push 外,还有 data push,也就是数据发布。数据包括 GWS 的配置文件、各种黑名单白名单、模版文件等。Data push 比较轻量,每天有多次,都是采用灰度发布到全球的方式。

以上这样 live at head 的方式意味着不太可能有长期存在的功能分支。如果一个工程师在独立功能分支上开发了几周,那基本上是不太可能合并回主线的。无论是多大的新功能,都会需要拆成很多小的 changelist,高频地提交到主线。只是未完成的功能会用一个 flag 屏蔽掉,在生产环境不会运行。所有用户可感知的改动都是用与试验一致的方式发布,从单个数据中心千分之一的流量开始灰度到全量。如果发现问题,只要做 data push 把 flag 的值改回来就行,因为老的 code path 已经在线上运行了很久,所以改回来一定没问题。这让发布很安全,大家也有信心做大胆的尝试。

关于项目管理和排期

排期这个词在 Google 其实很少出现,离开 Google 之后都在小创业公司就更不会出现。如果大部分事情都需要项管排期,一件很小的事情也可能被排到一两个月后,而这件小事可能 block 了很多其他工作。把一个工程师的时间按开发任务线性地排列,完全做完一件事再开始做另一件事,也并不是高效的方式。工程师确实是需要专注的、避免多任务切换的时间,但这样的时间应该是以小时计,而不是以天或周计的。一个工程师应该把任务分割成尽可能小的单元,写完代码和测试后及时提交给别人 review,并且也需要 review 其他人的 merge request。从天和周的维度看,本来就是需要在多件事情之间切换的。

在 Google 的三年多里,我们团队的 project manager 对于工程师来说存在感一直比较低,项管不会过多干预个人的工作计划。每个工程师都相当于自己的项管,工程师之间会互相就优先级进行沟通达成共识和妥协。这样的好处是工作量小但 block 了其他人的事情会被快速完成;实际做事情的人用专业的语言沟通,不需第三者传话,也不容易造成误解;一些重要但不紧急的事情,比如重构、还技术债,也可以由工程师在日常工作中穿插地推进。我无法想象在 Google 当时的团队能按集中排期来安排工作。过去十多年里,硅谷比较成功的互联网公司都是用与 Google 相似的方式来工作的,这不是偶然现象。网状的沟通协作与传统的树状比,表面可能感觉混乱,但是因为不容易形成瓶颈、沟通中信息损耗少,效率是很高的。

我们能先从哪些事做起

Google 的人喜欢说 Google 的工程实践是为连续运行十年以上的软件设计的。10 年不是很长时间,已经基本站稳脚跟的公司需要探索在开发协作上更 scalable 的方式。我们不太可能去复制 Google 的所有东西,Google 的方式也未必放在所有公司都是适用的,但是有一些方向性的结论在整个业界是得到了共识的,我们也应该朝那个方向去努力。以下是我们可以在公司推动改进的一些方向。

在工程师团队里培养测试文化

在这个年代写完代码就交给 QA 去测试,靠人工来保证质量,是很落后的方式,因为它不能 scale。人工的测试做十次就是十倍的成本,哪怕每次内容都一样。自动化测试是所有人都知道好,很少人实际做,更少人能做好的事。Google 也不是从一开始就把测试做得很好,而是由一小群人努力地在全公司推动起来。最可见的一件事是 Testing on the Toilet,内部简称 TotT。他们在全球办公室的几百个马桶前都装上了这样的海报,这样大家在上厕所的同时还能学习如何写测试。久而久之,重视的人越来越多,开始在 code review 中执行测试覆盖率的要求,大部分的项目都逐步建立起高质量的测试。

让自己的项目更容易被别人改动

要改代码首先要看得到。我一直认为代码应该默认是对内公开的,虽然可能会增加泄露风险,但和效率的提高比是微不足道的。如果市值几千亿美元的公司都能对每个工程师开放代码,对大部分公司而言部门之间还互相捂得很严就太小气了。Git 本身不是为 Monorepo 设计的(虽然最近有一些支持,以及微软有全球最大的 Git repo),可能短期我们也难以大范围转为 monorepo,但是 GitLab 把 repo 组织成树状的设计一定程度上是为了在权限管理上模拟 monorepo 按目录管理的方式,是很容易做到默认 owner 可读写,其他人可读,让任何人都可以通过 merge request 改任何代码的。

让别人更容易改动还包括提供内部文档,让别人知道如何修改、运行、调试(别人包括团队新人、其他团队、未来的自己);提高可读性和模块化,让别人容易理解;提高测试覆盖率,让别人不容易改坏。

尽量提交代码而不是需求

现在经常会遇到有一件事需要涉及多个项目的改动,把需求提到其他项目,发现要排到很久以后了。解决优先级上的冲突,让对自己重要的任务早点完成的最好办法就是把一件事的开发收缩到同一个团队,不管要改的代码在哪里,都尽可能由同一个团队完成开发,由 owner review。从经济上来说,由需求方投入资源也是更合理的方式。这样的工作方式偏爱能力全面的团队和工程师,在 Google 一个人经常性地提交 2~3 种语言的代码是很常见的。

当然,也不是所有的事情都能收敛到同一个团队进行。有的任务是需要对多个项目进行根本性的改动的,就会需要来自多个团队的工程师形成一个临时的小团队了。

建立代码规范

很多公司通常是从很小的时候就会建立公司范围的代码规范,因为有了一致的规范,才好使用同样的工具,阅读和修改其他团队的代码时才能比较容易。如果在规模较大时再从零开始做这件事会非常困难。可行的路径可能是自下而上的,各个团队先建立自己的规范,协作得多的团队可以取长补短,从能建立共识的部分形成共同的规范,逐步扩大适用的范围。

关于 LeanCloud 被心动/TapTap 收购

其实这是知乎上的回答。公司并购之后由于团队变大了很多,特别忙,所以博客和播客都断更了好久。

其实我们官网的公告已经把主要的前因后果介绍得比较清楚了,没有太多其它的新内容可说。

其它答案里说的大公司同类竞争之类倒不是主要原因。从我们创业开始,阿里、腾讯就陆续有过多次形态类似的产品尝试,后来字节也有相似的产品,但是都并没有很成功。选择不用 LeanCloud 的用户大部分是会用云主机等形态更底层的云服务的。所以本质上我们主要是和更传统的云服务竞争,而不是和形态相似的产品竞争。

LeanCloud 创业的几年中,各方面都有不少改进的空间,但是在资源有限的条件下,我觉得在国内环境能做的范围内,产品、团队和文化都还是有一些做得不错的地方,各方面被同行借鉴得也不少。但是我们在财务上并不成功,至少没有达到 venture-backed 的创业公司的增长期望。一方面团队创始人都是技术出身,公司一直都没有过在市场、BD、销售方面有资深经验的人才,虽然有一两次碰到比较合适的人,但都因为各种原因没有实现;另一方面也有行业发展的客观原因,做一些横向对比就能发现。Starbucks 前 CEO Howard Schultz 说过,公司文化等等很重要,但是一家公司在财务上能 deliver 是一切的基础,不然其他东西都是不可持续的。

LeanCloud 并不是第一次收到并购邀约。过去曾有 A 股上市公司,加密货币和区块链最火爆的时候也有这个领域的公司,不过都没推进到谈价格的阶段。因为两个前提要有保证:第一是产品的未来发展和对现有用户服务的连续性;第二是文化和管理上我们创始人自己和团队得愿意加入对方。

知道和使用 LeanCloud 的游戏公司数量很少,但是如我们的公告上说,这部分公司贡献了将近一半的收入。其实我们一直以来都很想拓展这个领域的客户,但是一直都没找到好办法。与 TapTap 的结合对 LeanCloud 来说是一个让我们的产品真正进入游戏行业的很好的机会。对所有现有用户来说,也意味着我们未来有充足的资源继续改进产品。

心动从做「天天打波利」开始就用 LeanCloud 的服务,一开始我和前 CTO 沈晟沟通得比较多,从去年开始聊并购后和两位创始人及其他高管都有交流。他们所期望的公司管理方式和文化和我们自己还是很一致的。创业的人大多有两方面的目标:一方面当然是财务上的;另一方面是创造一家符合自己自己理想的公司。只是很多人做着做着就忘了第二点了。我觉得心动/TapTap 是一家很难得的公司:两位创始人在坚持朝着自己的理想迈进,而同时财务上又能 deliver。其实因为财务上的空间比较大,心动和 TapTap 在管理和文化上在做一些比 LeanCloud 更加大胆的尝试,只是因为公司一直比较低调,所以在互联网圈子里大部分求职者对这家公司还缺乏了解。

LeanCloud 团队目前已经和原 TDS(TapTap Developer Services)团队合并,LeanCloud 的各项服务也已经引入 TDS,详情可以看我们两天前办的 TapTap 开发者沙龙的报道现在 TDS 正在积极扩充团队,欢迎优秀人才加入,详情请见我们的招聘网站

small talk #2:聊聊用 M1 芯片的新 Mac

在 small talk 的第二期我们聊了一下搭载苹果 M1 芯片的 Mac。我们回顾了各自使用 M1 Mac 的感受和遇到的问题,后半部分讨论未来可能的行业影响以及购买方面的建议。

欢迎留言给我们提出反馈以及以后希望听到的话题:

  • 语音留言(你的留言可能会出现在以后的节目里)
  • 文字留言:请在本帖下面发评论

如何收听

本期主播

讨论中提到的相关链接

怀念两位老师:Stan Eisenstat 和 Paul Hudak

最近难过地得知 Stan Eisenstat 教授在 12 月 17 日去世,我在 2017 年回学校时还去找他聊了会儿天。这也让我想起几年前去世的 Paul Hudak。这两位教授虽然不是我正式的导师,但都对我影响很大,所以就想写一写我对他们的记忆。

Stan Eisenstat

Stan Eisenstat

我在去耶鲁上学之前就在 Joel Spolsky 的 Joel on Software 里读到过 Stan Eisenstat。他教的 CS323: Systems Programming and Computer Organization 在学生中是一门传奇性的课程。选课的人往往在那个学期会需要花大量时间熬夜甚至通宵来完成他布置的几个大作业,而在课程完成后都会觉得收获很多。进入耶鲁后我抓住机会申请做了这门课的助教。当年(1998 - 2002)我在国内接受的计算机科学本科教育说实话和美国好的大学比还有很大差距,直到后来国内大学有越来越多留学回来的人加入,这个差距才缩小,所以其实读博期间还要弥补一些知识面上的裂缝。给本科生课程做助教对于提升自己也是非常有益的。他在一个学期里会让学生完成几个大作业:实现一个 UNIX shell,一个 LZW 文件压缩/解压程序,重新实现 make 等等。每个项目 Stan 都会准备好一套测试,学生提交完后他跑一遍测试就能马上得出正确性的得分。他也准备了详尽的代码风格文档,所有的作业和考试都有正确性和风格两部分分数。到期末他会让助教自己设计一个期末项目给学生作为期末考,助教也要负责给这个项目写一套自动化测试来给学生提交的代码打分。所以每一届学生遇到的期末项目都不同,而每个助教也有自己的发挥空间,参与这个过程的每个人都很有收获。我现在还记得给学生出的题目是做一个支持用 telnet 登录的多线程 BBS 服务器,我写了一个 telnet 机器人来测试他们实现的各项功能。Stan 在耶鲁计算机科学系将近 50 年,这门课和他教的 CS223: Data Structures and Programming Techniques 一直都是计科系的核心编程课,无数学生在 Zoo(系里的机房)耗费了无数夜晚来完成这两门课的作业。

Paul Hudak

Paul Hudak

我对 Paul Hudak 也是在去耶鲁前就有很多了解。我本科的毕业设计是函数式语言的课题,所以找了一些 Haskell 的资料看。Paul 是 Haskell 的主要设计者,当时他正好出了一本新书。那时市面上还几乎没有正式出版的 Haskell 的书,国内更是完全没有。我给他发邮件问问题时提了一句很遗憾在中国买不到他的书。过了两周多就收到了他给我寄过来的书。后来进了耶鲁我选了另一位教授开的 functional programming 课,因为这方面基础薄弱学得比较差。后来 Paul 教这门课时我又去报名做助教,相当于自己也又学了一遍。虽然后来我没有选择在程序语言方面深入下去,但因为他的原因一直保持着对这个领域的兴趣和关注,所以当 Clojure 这样更具实用性的函数式语言出现时我很早接触并应用到实践中,后来创业时也选择它作为主要开发语言,使团队能在早期很高效地开发出产品。

Paul Hudak’s book

Paul Hudak’s book

耶鲁的计科系格外地重视教育。这句话听起来似乎有点奇怪,所有的大学都应该是重视教育的,但其实各个学校在研究和教学上的投入还是很不一样的。很多地方的教授更愿意把时间投入到研究上,多发几篇论文,而教学更多地是必须完成的义务和负担。耶鲁的很多教授,包括 Paul、 Stan、Stan 的妻子 Dana Angluin(也是同系教授)、我导师 Mike Fischer 都对教学有一种很纯粹的热爱,他们年复一年地打磨同一门课程,不吝惜花大量的时间做到尽善尽美,把培养出的学生看作最重要的遗产。我在 Yale Daily News 上看到,Stan 对学生的爱持续到了生命最后一刻,他要求等期末考结束再宣布他去世的消息,所以他离世后系里还不知道,第二天在 Zoom 上的虚拟节日聚会里大家还录制了一些祝他早日康复的消息。

small talk #1: 聊聊你的私有云

我们一直都想做一个中文播客节目,但是因为最初提出想法的人都比较腼腆,一直没有启动。最近我来推动开始了这件事。

播客名称叫 small talk,除了闲聊的意思外,也有个编程语言叫 Smalltalk,算是有些双关。这个播客的目的不在于宣传产品或公司,只是聊一些有趣的技术话题。会长期参与的除了我自己外还有 V2EXLivid 以及我在 LeanCloud 的同事王子亭。我们也会邀请其它嘉宾来临时参与,欢迎在评论里留言建议以后的话题。

第一期的话题是聊聊每个人家里的私有云。为了快速发布,片头片尾音乐等细节就省略了,以后再慢慢迭代。目前发布在以下平台:

或者可以直接播放:

其中提到的软件和网站有:

如何在 Emacs 里做所有事

很长时间之前我在公司内挖了个坑说要做个 Emacs 和 org-mode 的内部分享1。后来一直因为拖延症没有做。上周我和其他同事讨论准备做播客的事,就又想起这件事,周末花了点时间录成一个视频放在 YouTube

这也是我做的第一个视频教程,看完别忘了点击订阅 😁。很快我们也会发布第一期播客,请关注。


  1. 忘了具体原因,大概是有一次受不了新同事在编辑器方面的品味。😏 ↩︎

Remark Ninja: 一个简单的评论系统

几年前一位 LeanCloud 的用户做了一个基于 LeanCloud 的评论系统叫 Valine。后来在中文独立博客,特别是用 Hexo、Jekyll 这样的静态博客框架的用户中很流行。但是最近一两年因为监管变严,无法自己备案域名的国内用户不得不迁移到国际节点。另外免费的开发版服务本意是用于开发测试,而不是需要持续运行的正式项目,所以用户们想了一些比较 hack 的办法来绕过开发版限制。大多数人都会直接 copy 网上搜到的教程,不做任何修改,这样也给 LeanCloud 带来了一些奇怪的问题。比如我们花了不少时间来分析为什么每天总是在同一时刻有大量用户的应用会开始运行定时任务。

我用业余时间断断续续做了一个叫 Remark Ninja 的评论系统,目前到了可以开放给其他人用的状态。我自己的博客、LeanCloud 的博客,以及一些自己发现 Remark Ninja 的第三方网站已经用了一段时间。由于我自己在前端基本只用 React,所以目前只提供了 React 组件Gatsby 是一个基于 React 的功能强大灵活的静态网站框架,我个人很推荐。非 React 的网站可以在局部用 React,或者直接用 RESTful API

除了博客以外,以 thread 方式组织评论的网站都可以使用这个服务。目前提供了以下功能:

  • 基本的评论和管理功能
  • 给站长的新评论提醒
  • 给原评论者的新回复提醒
  • Markdown 支持
  • 没有用 Google Analytics 之类收集用户数据的服务

计划中的功能:

  • 垃圾评论过滤

由于前端水平有限,React 组件做得比较简陋,欢迎提 pull request

Woman、man、camera、TV:如何做一个完整的深度学习应用

前段时间 Trump 的这个采访成为社交媒体焦点的时候,我正好在复习一些 neural network 的材料,于是想到可以用一些新的开源工具做一个识别 woman、man、cemara、TV 的完整应用试试。这个例子足够小,可以在很短时间完成,很适合用来说明如何做一个完整的深度学习应用。完成的应用部署在 https://trump-sim.jishuq.comLeanCloud的一个云引擎实例上)。

做这个应用分为三步:先用一些图片完成模型的训练,然后把模型导出,做一个后端的 API 用来识别图片,再做一个前端用来上传图片和显示结果。

准备训练数据

Jupyter notebook 是个很流行的用来做数据分析和机器学习的交互式环境,它可以把 Markdown 文档和 Python 代码放在一个笔记本里,也可以以图表、图片等友好的方式显示代码的运行结果。这里也会用到 FastAI,它是一个基于 PyTorch,提供了很多网络和文件批量操作便捷接口的开源库。这篇文章就是在 Jupyter notebook 里写的,所以你可以直接 clone 这个 repo、安装依赖、启动 Jupyter notebook。

git clone https://github.com/hjiang/trump-sim-notebook
pip install -r requirements.txt
jupyter notebook

我们还会用到 Bing image search API 来获取做训练的图片,你需要自己注册并申请一个免费的 API KEY。当然,因为搜索到的图片是在很多第三方网站上的,所以你需要能无障碍地访问中国之外的网站。🤷‍♂️

把你的 Bing image search API key 放在项目目录下的 .env 里,以免在代码里泄露出去:

BING_SEARCH_API_KEY=XXXXXXXX....

然后在 Python 里读进来

import os
from dotenv import load_dotenv
load_dotenv()
key = os.getenv('BING_SEARCH_API_KEY')

写一个函数用来搜索图片:

from azure.cognitiveservices.search.imagesearch import ImageSearchClient
from msrest.authentication import CognitiveServicesCredentials
from fastcore.foundation import L

def search_images_bing(key, term, min_sz=128):
 client = ImageSearchClient('https://api.cognitive.microsoft.com', CognitiveServicesCredentials(key))
 return L(client.images.search(query=term, count=150, min_height=min_sz, min_width=min_sz).value)

实际验证一下, 搜一张 Artemis 的图片:

from torchvision.datasets.utils import download_url
from PIL import Image
import fastai2.vision.widgets

results = search_images_bing(key, 'Artemis')
urls = results.attrgot('content_url')
download_url(urls[0], '', 'artemis.jpg')
image = Image.open('artemis.jpg')
image.to_thumb(128, 128)

确认图片下载没问题后,我们把关心的四类图片下载到 /objects 下面的四个目录里。

from fastai2.vision.utils import download_images
from pathlib import Path

object_types = 'woman','man','camera', 'TV'
path = Path('objects')

if not path.exists():
 path.mkdir()
 for o in object_types:
 dest = (path/o)
 dest.mkdir(exist_ok=True)
 results = search_images_bing(key, o)
 download_images(dest, urls=results.attrgot('content_url'))

你可能会看到一些图片下载失败的信息,只要不是太多都可以忽略。网络上有的图片是损坏的,或者是 Python image library 不支持的格式,需要把它们删除。

from fastai2.vision.utils import get_image_files
from fastai2.vision.utils import verify_images

fns = get_image_files(path)
failed = verify_images(fns)
failed.map(Path.unlink);

预处理

在开始训练前,需要告诉 FastAI 如何标注图片,并加载到它的数据结构中。下面的代码完成以下几件事:

  • 使用父目录名(parent_label)来标注每个图片。
  • 保留 20% 的图片作为验证集(validation set),其它的作为训练集(training set)。训练集就是用来训练神经网络的数据,验证集用于衡量训练好的模型在遇到新数据时的准确度。这两个集合不能有重叠。
  • 把图片缩小以提高效率

最后一行代码会显示验证集的前三个图片。

from fastai2.data.block import DataBlock, CategoryBlock
from fastai2.vision.data import ImageBlock
from fastai2.data.transforms import RandomSplitter, parent_label
from fastai2.vision.augment import Resize

objects = DataBlock(
 blocks=(ImageBlock, CategoryBlock),
 get_items=get_image_files,
 splitter=RandomSplitter(valid_pct=0.2, seed=42),
 get_y=parent_label,
 item_tfms=Resize(128))

dls = objects.dataloaders(path)
dls.valid.show_batch(max_n=3, nrows=1)

在做图像识别的时候往往还会对图片做一些随机的缩放、裁剪等变换,以便产生足够多的数据来提高训练效果。可以从下面代码的结果看到对同一个图片做不同变换的结果。

from fastai2.vision.augment import aug_transforms, RandomResizedCrop

objects = objects.new(
 item_tfms=RandomResizedCrop(224, min_scale=0.5),
 batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
dls.train.show_batch(max_n=6, nrows=2, unique=True)

训练数据

接下来终于可以开始训练了。对于图像识别这样的应用场景来说,往往不会从零开始训练一个新的模型,因为有大量的特征是几乎所有应用都需要识别的,比如物体的边缘、阴影、不同颜色形成的模式等。通常的做法是以一个预先训练好的模型为基础(比如这里的 resnet18),用自己的新数据对最后几层进行训练(术语为 fine tune)。在一个多层的神经网络里,越靠前(靠近输入)的层负责识别的特征越具体,而越靠后的层识别的特征越抽象、越接近目的。下面的最后一行代码指训练 4 轮(epoch)。

如果你有 Nvidia 的显卡,在 Linux 下,并且安装了合适的驱动程序的话,下面的代码只需要几秒到十几秒,否则的话就要等待几分钟了。

from fastai2.vision.learner import cnn_learner
from torchvision.models.resnet import resnet18
from fastai2.metrics import error_rate
import fastai2.vision.all as fa_vision

learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(4)
epoch train_loss valid_loss error_rate time
0 1.928001 0.602853 0.163793 01:16
epoch train_loss valid_loss error_rate time
0 0.550757 0.411835 0.120690 01:42
1 0.463925 0.363945 0.103448 01:46
2 0.372551 0.336122 0.094828 01:44
3 0.314597 0.321349 0.094828 01:44

最后输出的表格里是每一轮里训练集的 loss,验证集的 loss,以及错误率(error rate)。错误率是我们关心的指标,而 loss 是控制训练过程的指标(训练的目标就是让 loss 越来越接近于 0)。需要这两个不同的指标是因为 loss 要满足一些错误率不一定满足的条件,比如对所有参数可导,而错误率不是一个连续函数。loss 越低错误率也越低,但他们之间没有线性关系。这里错误率有差不多 10%,也就是准确率是 90% 左右。

接下来我们要看看验证集里到底有哪些图片识别错了,下面的代码会打印出 confusion matrix。在这个矩阵里,对角线的数字是正确识别的图片数,其它地方的是识别错误的图片数。

from fastai2.interpret import ClassificationInterpretation

interp = ClassificationInterpretation.from_learner(learner)
interp.plot_confusion_matrix()

从输出的矩阵可以看到一共有 11 个错误,其中男女性别错误有 4 个,此外电视和其它几类的混淆也很多。🤔

下面我们把 loss 最高的图片显示出来看看具体有什么问题。

interp.plot_top_losses(12, nrows=4)

输出的结果反映出了从互联网上抓来的数据存在的典型问题:噪声太多。比如电视的搜索结果里有电视遥控器、电视盒子、电视剧海报,还有一些是完全无关的结果。

FastAI 提供了一个 cleaner 可以帮助我们对比较小的数据集做手动清洗。它可以把整个数据集中 loss 最高的图片列出来让用户可以手动修改标签或者删除。

from fastai2.vision.widgets import ImageClassifierCleaner

cleaner = ImageClassifierCleaner(learner)
cleaner

注意 cleaner 只是做标记,你需要用 Python 代码来做实际处理。我通常就直接把有问题的图片标记为 delete 然后删除。

for idx in cleaner.delete(): cleaner.fns[idx].unlink()

清理完之后重复训练的过程。

objects = DataBlock(
 blocks=(ImageBlock, CategoryBlock),
 get_items=get_image_files,
 splitter=RandomSplitter(valid_pct=0.2, seed=42),
 get_y=parent_label,
 item_tfms=Resize(128))

objects = objects.new(
 item_tfms=RandomResizedCrop(224, min_scale=0.5),
 batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(3)
epoch train_loss valid_loss error_rate time
0 1.663555 0.510397 0.201835 01:11
epoch train_loss valid_loss error_rate time
0 0.458212 0.226866 0.091743 01:32
1 0.358364 0.145286 0.036697 01:31
2 0.281517 0.146477 0.036697 01:32

如果你注意到 error_rate 在后面的 epoch 有上升的话,可以降低 fine_tune 的参数以达到最好的效果。因为如果训练轮数过多,模型会对训练集 over fit,在遇到新数据时错误率会变高。从上面的输出可以看到准确率提高到了 96% 以上。

达到满意的准确率后就可以把模型导出用到线上了。下面这行代码会把模型保存到 export.pkl

learner.export()

后端 API

后端 API 是这个项目最简单的一部分,只有一个 endpoint。加载前面导出的模型,收到新图片时用模型来预测分类就可以。

trump = load_learner('model.pkl')

@app.route('/api/1.0/classify-image', methods=['POST'])
def classify():
 image = request.files['image']
 res = trump.predict(image.read())
 response = jsonify({'result': res[0]})
 response.status_code = 200
 return response

完整的代码在 GitHub 上。按照文档部署到 LeanCloud 云引擎就行。

前端网站

前端也比较简单,只需要一个页面让用户上传照片,在浏览器里把照片缩小然后发送给后端 API 就可以。完整的 React 项目在 GitHub,主要的代码在 App.js。限于篇幅就不详细说明了,只附上一张运行的截图:

给读者的作业

你可能已经注意到上面的后端 API 服务是无状态的,没有存储任何数据,所以其实识别的过程可以在前端完成。如果你有兴趣的话,可以调研一下如何把 PyTorch 模型转化为 JavaScript 可用的模型,尝试在浏览器里直接识别照片。在真实的应用中,这样的方式由于不需要向服务端传输任何数据,可以完美地保护用户隐私,这也是 Apple 在推动的 on-device machine learning 的方向。

图片识别是机器学习可以解决的最简单的一类问题,因为有很多现成的结果可以重用,新的应用即使只有少量训练数据也能达到比较好的效果。还有很多其它类型的问题没有那么容易得到让人满意的结果。LeanCloud 目前正在开发机器学习方面的新产品,以帮助开发者更容易地发掘数据的价值。你如果对此感兴趣,可以关注我们的微博、微信公众号、Twitter,或者注册成为 LeanCloud 用户。不久后我们会公布更多信息,并邀请一些用户试用新产品。

❌