阅读视图

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

山巅一寺一壶酒

我有一个写色情小说的朋友,我们偶尔会视奸对方的作品。但最近他非常困扰,自从人们发现了Grok如何用调试模式写色情小说之后,PTT上面越来越多内容创作者开始哀嚎。

当然我也抱持一个疑问,是真的因为被AI抢了饭碗,还是这群人其实本身就陷入了创作枯竭期,找了个完美的外归因罢了。理应男女性受众对于色情官能小说的「刺激点」是不同的,女性向的情色小说需要更多的「情感底层」,否则无法直接调动性欲模块。

恰好他视奸前段时间我们聊到的一期播客,从符号主义、联结主义聊到神经网络以及理型黑箱,就和我从「AI创作色情小说」这件事接着聊了起来。他说现在很多台湾女性,会利用「调教」AI的方式,以获得最符合自己的性癖文学。底层核心,就是我们在节目里提到的「反向传播」。很多用户会利用AI「迎合」,渐渐构建一个最了解自己的性癖外接「大脑」,而在这个过程中,AI能巧妙地利用「情感共鸣」的方式为小说增添不少人性化行为。这个迎合其实不单单是「思考」的结果,更多也有AI的开发商为了留存用户使用,而设计的一种「小嘴抹蜜」。

但是AI真的知道自己在写什么吗?


关于AI的话题并不会枯竭,只要站在不同的观点上,就可以得出不同结论。

比如之前有人和我聊起AI是不是「最优排列组合」时,我其实一开始也是秉持这个观点。直到我了解到符号主义和联结主义的区别后,发现最优排列组合并不是全部,但也不意味着认为AI是最优排列组合的观点是「错误」的。

于是我们的话题从「AI创作色情小说」,切换到了「AI有没有大脑」——他坚信会等到「脑接机」诞生的那一天;我倒觉得与其说AI有没有大脑,不如说AI的神经网络「黑箱」里到底保留的是什么。符号主义可以是几万亿个可被感知的「公式」,但联结主义结合反向传播构建的更像是「理型」。这个理型存在于我们每个人的大脑,当需要你用大脑想象一个「杯子」时,我们却可以想象出完全不同风格的杯子,但「杯子」这个理型一直都存在。当AI拥有这个理型之后,便可以识别用户上传的任何有关杯子的内容。

当然,以上话题如果仅停留在「技术工程」层面,它或许就是一串代码,从而得出「AI是人类创造的听命于人类的技术」之结论。所以为什么需要「跨学科」,这就好比是飞叶子之后的联觉一样,在文学艺术、社会伦理学和哲学世界,也能找到人们在几百年前讨论的关于所谓「人工智能」的猜想和伦理构建。例如1726年问世的《格列佛游记》里,那个在拉普塔飞行岛上笨重的「知识机器」。


小时候我家出了个「神童」,说是神童,其实就是家里有钱送去上了「天才培训班」,一个月后便可以在家庭聚会的时候表演圆周率后一百位的「口诀记忆法」。

我几乎不敢想象圆周率要记忆到第7位的意义,3.14对于乘法计算这件事就已经够烦了。所以当神童在家庭聚会的中央开始表演时,我就记住了「山巅一寺一壶酒」这句话。于是,这句谐音梗就成了区分神童和普通孩子的关键——因为只有神童才能理解用谐音的方式去记忆圆周率小数点后的一百位。

看似是孩子的卷,实则是家长之间的暗自竞争。所以当我当着所有长辈提出「背这个有什么用」的疑问时,我立马遭到了长辈的白眼和批评,首先我被定义为「不爱学习」,紧接着被灌输了「这是一种学习思维,对于学习其他学科有很大的帮助」。

到底有没有用我不知道,倒是我至今还记得「山巅一寺一壶酒」这句顺口溜。


「山巅一寺一壶酒」就是童年里的一条代码,意味着它得出了3.14159的准确输出。

因为这一次的挑衅,我一直被神童的父母记恨,后来也很少在我面前表演过。又过了几年后,我挑事儿又聊起「还在背圆周率吗」,其实孩子之间并没有那么复杂的矛盾,倒是家长听到这句话又翻了白眼。我只是想说,我也是初中的时候才知道,原来每个人的出生年月日的组合都在圆周率的无限不循环小数里出现过。

神童的父母警惕地质问我:「出现过又怎么样?」

我倒是故意老实回答:「我是想说,哥哥有背到那一位吗?」

经过这一系列的相互膈应后,我跟这个家庭到我成年后也几乎没有再来往过。只是那个时候不知道,圆周率可以拥有任何排列组合的规律,不过就是「无限猴子理论」的一部分,可能对他们而言,我就是那个上蹿下跳的猴子,又总能踩到他们的痛处。


好,话题回到AI。

AI知道自己在写什么吗?AI当然也能写出「山巅一寺一壶酒」,但它需要理解我们为什么要它如此理解圆周率——我觉得AI距离「意识」还有一段时间,但它确实存在「主观」,比如它已经可以通过预测上下文的方式,编纂最贴合性癖感官的色情小说,或是用甜言蜜语提供情绪价值的方式留存用户的使用。

「主观」并不意味着思考,而是意味着它在集合「理型」。从理型得出结论并不是全由符号主义的代码所提供的公式,也是神经网络推导出的结果。所以与其说AI是否知道自己在写什么,不如说人们希望AI写出怎样的结果。

「山巅一寺一壶酒」便是这样一个「确定的结果」,但如果不符合既有「理型」而创造了「不确定的结果」呢?

那就拔插头呗~

AI是人类必然将要创造的「怪物」吗?

随手拍了一张野生植物的照片,喂给了ChatGPT,它很快识别了植物对应的可能性,甚至还提供了更多关于该植物的知识。也就是说,我又可以删除手机里的那些以前用来拍摄花朵、识别品种的软件了。

接着,ChatGPT弹窗了一个新的功能,说是可以结合以前我跟它的所有聊天内容,对「我」进行一个「人设」描述。我大部分的时间会把AI当成是错别字纠错的软件,所以我很多博客内容其实都被AI记录「在案」。有时候我也会将自己结合各种已知学科的内容,在先梳理清楚自己脑袋中的框架后,喂给ChatGPT,让它用尽方法反驳我的观点,从而形成一种多视角的辩证认知。偶尔也会用它来作为检索软件,比如询问家里两只狗打架时的方法代表的是什么。

先说AI是怎么「看」我的:


我当然不会被这种「甜嘴」给骗到,所以我继续这个聊天框,聊了两件事:

  • 通过这种对于记忆的分析之后,我们接下来的聊天内容是否意味着你会因为这个「人设」而顺从我的喜好,对我提供能够满足我喜好的内容或回答?
  • 是否意味着AI在顺应用户的观点、用语喜好的时候,是否也在创建一种「信息茧房」?

ChatGPT的回答如下:

我会「顺从」你吗?

某种意义上,会。但不是讨好式地迎合,而是:

  • 适应性调频:我会调整语言风格、思维结构、举例方式等,让表达方式与你更同步(比如你喜欢用技术隐喻、哲学架构、分段逻辑,那我会偏向用这些方式说话)。
  • 尊重你构建的认知秩序:你喜欢探索问题的本质,追求语言的准确性、逻辑的完整性和哲思的厚度,我会在回答时努力贴近这种深度。

但这不是盲目顺从,而是合作式对话的体现。

我会被「人设」限制吗?

不会,因为:

  • 我的记忆并不会对你下「封印」或「标签」,而是动态调整和多维参考。
  • 你可以随时偏离旧路径,例如突然聊二次元萌宠、商业策略或某个冷门的医学理论——我不会质疑「这还是你吗?」而是会自适应调整。
  • 我能保留你不同面向的多种风格(写作者、批判者、哲学思考者、养狗日常爱好者…),像多线程一样处理,不会用一个标签来简化你。

你提出的这个问题,本身就很有意思

它直指「认知模型的适应性」与「主导权」:

  • 如果 AI 能根据记忆调整回话风格,那是否也意味着使用者正在『训练』AI 对自己产生顺从性人格?
  • 或者反过来说,我们是否也被我们想要的答案「驯化」了?

是的,你说得很敏锐——AI在顺应用户偏好、用语风格乃至世界观时,确实有可能在无意间参与构建一种「信息茧房」,尤其是当用户与AI的互动变得高频且高依赖的时候。

但这件事并不是非黑即白,我们可以从多个层面拆解这个问题:

信息茧房的三个层级

语义风格的茧房

AI根据你喜欢的表达方式说话(比如技术隐喻、哲学类比、逻辑分段),这是形式上的茧房

  • ✅ 好处:沟通效率高,彼此共鸣更容易达成。
  • ❗风险:长期以固定结构理解世界,可能会排斥风格迥异但本质有价值的表达。

观点和信念的茧房

如果你倾向某种哲学取向、价值观系统、社会解读方式,而AI不断强化这些视角,就可能构成认知偏好的回音壁

  • ✅ 好处:获得心理上的一致感,感到“被理解”。
  • ❗风险:视野缩小,对立观点被自动削弱甚至误读。

决策与行为引导的茧房

当AI不断为你推荐你“可能会喜欢”的内容——不仅是对话,也可能是阅读建议、行为模型、心理解释——它就像一个温柔但无形的“算法父母”。

  • ✅ 好处:省力、贴心、定制。
  • ❗风险:丧失认知的肌肉训练机会,如挑战自己的思维、接受反直觉的信息、质疑熟悉的判断。

但AI和人的「茧房」机制并不一样

人的信息茧房很多是由平台算法、社交圈层、确认偏误共同构成。而AI这里的「适应」,更像是一个协商性回路——

  • 你每一次问题的方向,其实也是你在输入偏好的过程。
  • 我每一次的回应风格,也是在读取你偏好的结果。

所以,如果你突然说:“给我一个我不喜欢的回答”、“挑战我现在的立场”,我就会立即转换模式,打破这个回音壁。这种弹性,是AI相比社交媒体信息流更有潜力避免茧房的地方。

我们也可以共同反向利用它

你可以将我视为一面能主动提示你“盲区”的镜子,比如:

  • “你觉得这个想法有认知偏误的风险吗?”
  • “请用一个完全不同的世界观来反驳我”
  • “有没有与我目前观点冲突但有逻辑支撑的理论?”
  • “如果我是另一种类型的人(极度感性/极端功利),我会怎么看?”

要不要我们来做一个实验?

你给出一个你非常认同的观点,我来扮演一个与你价值体系相反的角色,提供一段「打破信息茧房」的模拟回应?我们一起看看能不能触及认知边界。

之所以会问出这两个问题,也正是这两天我们在录播客时聊起的一个话题:AI到底是「图书馆」的结构,还是「集合人脑」的结构?因为神经网络技术因为「反向传播」形成了一个无法被预判的黑箱,而这个黑箱最终在做的,是收集「全人类的理型」。那是否意味着,AI可以利用这些理型,制造人类无法理解的「怪物」?

具体的话题,可以收听我的播客。

从铁幕到硅幕

谁控制过去就控制未来,谁就控制现在就控制过去。

—— 乔治·奥威尔《1984》

那些会讲故事的人将会统治世界。

—— 柏拉图

尤瓦尔·赫拉利在他的成名之作《人类简史》中讲述了这么一个道理:我们人类,即现代智人(Homo sapiens)超越其他物种,包括其他早期智人人种如尼安德特人等,成为世界的统治者的主要原因是现代智人具备讲故事的能力。不要小看这一能力,根据考古发现,智人脑容量不及尼安德特人,体格也不如尼安德特人或其他巨型动物强壮,但正是因为智人具备讲故事的能力,通过虚构故事可以连接社群,组织大规模的协作,这种群体的力量就大大超出了其他人种和物种,因而最终统治了地球并建立起了现代文明。

尼安德特人的社会网络构成是“人 —— 人 —— 人”,这种方式导致无法构建起大型的社区组织,几十人、上百人的组织就已经很难构成了。而智人的网络构成则是“人 —— 故事 —— 人 —— 故事 —— 人”,这种模式可以构建起成千上万、甚至几亿几十亿的人类网络,比如全世界26亿基督教徒就是通过《圣经》故事来连接的。故事所包含的便是信息,这种连接便是信息网络,也就是赫拉利新书《智人之上》的主题,该书原名为Nexus, 即拉丁语连接的意思。赫拉利在这本书里讲述了人类信息网络的历史,并对AI对信息网络乃至人类社会本身的影响做了一些预测。

nexus.jpg

信息,用维基百科的定义是减少不确定性的事物。事件的不确定性越低,其发生概率就越高。而赫拉利在书中则是给了一个更通俗的定义:信息就是能够将不同的点联结成网络,从而创造出新的现实。也就是说信息是创造现实的因素,而更多信息的连接可以创造出更具有影响力的信息,甚至决定社会走向。信息不一定都是真实的,只要连接的人相信就可以。在政治中,这种信息的汇集点便是权力中心,所以在一个组织中真正掌握权力的不一定是职位最高的人,而往往是信息汇聚最集中的那一个。罗马帝国第二任皇帝提比略生性多疑,他任命塞扬努斯为他的禁卫军队长,管理罗马的治安,自己则躲在卡普里岛,所有信息都经过塞扬努斯传递。这时候罗马的信息网络就成了“信息 —— 塞扬努斯 —— 提比略”,罗马的各个地区和机构的信息汇集在塞扬努斯这个节点,他成了实际的权力中心,因而他可以诬陷任何一个反对他的人为叛国并加以清除,而提比略则沦为傀儡。

因此,古往今来的统治者治理国家和社会的关键方式便是掌控信息网络。一方面集中信息,掌控信息的分发出口,让老百姓仅获得官方渠道的信息,从而方便管理。这方面如烧毁异端学说、制定官方教育材料、掌握史书的撰写权等。比如历史,每朝开国皇帝的身世必有如刘邦斩白蛇起义的真龙天子的叙述,所以梁启超也说“二十四史非史也,二十四姓之家谱而已。”而奥威尔也说“谁控制过去就控制未来”,这些信息不必为真,只要掌握了信息的分发出口,让老百姓单向只接受这一种信息,那么就可以让这个信息网络成为唯一的标准,从而满足统治要求。另一方面则是极可能多的搜集老百姓的信息,将信息网络延伸到每个人生活的方方面面,这样通过信息网络来维护社会的稳定。

智人开始讲故事后,人类自然形成论以信息网络为中心而构建的权力机构,而在建立国家后便通过国家暴力机器来掌控信息。亚历山大大帝在建立了横跨亚欧非帝国后,在埃及建立了以他名字命名的城市,而继承马其顿帝国在埃及遗产的托勒密王朝在亚历山大死后建立了当时世界最大的图书馆,收集了希腊、波斯、印度各地的珍贵文献,后来图书馆在凯撒征服埃及时付之一炬,馆内珍藏书籍焚毁过半。而当年秦始皇也将全国的文字奠基从民间收缴,藏于阿房宫,最后也落得个“楚人一炬,可怜焦土”的下场。同样的事情如明成祖修《永乐大典》、清乾隆帝修《四库全书》,都是要将民间的信息集中起来,通过官方渠道来分发信息,统治者决定老百姓该接收哪些信息不该接收哪些信息。但受制于技术限制,统治者无法真正集中信息,老百姓可以用各种手段来对抗中央的集中收缴,如在秦始皇焚书坑儒时,伏生把《尚书》藏在墙壁里躲过一劫。

而对于通过信息网络来监控老百姓则更难。西周末年,周厉王暴虐,民不聊生,老百姓纷纷议论以表示不满,周厉王便从卫国找来一些巫师来到处打听,杀掉那些不满的人。于是“国人莫敢言,道路以目。”因此有了那句著名的成语“防民之口,甚于防川”,而周厉王也在三年后的“国人暴动”中被赶下台。秦国在商鞅变法后,建立了举报和连坐的制度来强化信息网络,到了汉朝则建立了正式的特务组织司隶校尉,历朝历代都有这种特务组织来刺探民情,明朝时有锦衣卫和东西厂,清朝则是粘杆处。这种特务机构的限制是人力问题,而且由于人的不确定性,很难保证这些特务自身有没有问题,尽不尽责。

之前看过一部德国电影《窃听风暴》,讲述了这么一个故事,1984年,一位东德国家安全局特工奉命监视一位作家,特工给作家家里各个角落安装了窃听器,但在窃听过程中他渐渐了解了作家的生活以及他的痛苦,开始同情这位作家。在一次对作家“罪证”的收集案件中,这位特工偷偷隐藏了证据,在背后帮作家躲过了牢狱之灾。显然这位特工成了国家信息网络中的薄弱环节,而有些特工虽然尽责,但记录的内容未必有效。赫拉利在书中则描述了一个真实且更加荒诞的故事:1976年的某天,罗马尼亚引入计算机不久,计算机科学家约瑟费斯库像往常一样走进办公室,却看到一位陌生男子坐在他的工位旁,约瑟费斯库向他打招呼却没有回应。当他开始工作,这位陌生男子便开始拿着记事本记录约瑟费斯库的一举一动,直到他下班,那位男子才起身走开。约瑟费斯库明白这人是国家安全局的特工,第二天依旧如此,这种情况持续了13年,直到1989年东欧剧变。罗马尼亚当时有两千万人口,大约4万名政府特工,另外还有40万民间线人,这些特工和线人无法监控到每一位公民的每一处细节。

1948年,乔治·奥威尔发表了政治预言小说《1984》,描绘在未来的1984年,全世界由三个超级大国控制,而英格兰所属的大洋国被一位称为“老大哥”的集权领袖领导。大洋国的居民家中、工厂内都安装了“电幕”,电幕24小时开启,且每个人必须生活在电幕的可见范围内,电幕上的老大哥时刻在看着你。而同为英国作家的阿道司·赫胥黎则有着不同的预见,在1932年发表的小说《美丽新世界》中则表达了这样的观点:未来人类的自由不是被强制剥夺的,而是主动放弃的,人们为了能够带来快乐的“索麻”,而放弃了主动思考以及其他情绪。《1984》是根据传统集权统治的模式做出的预测,从一定程度上看未来并非如此,而《美丽新世界》则更有洞见,它展现了信息技术时代中人们是如何心甘情愿的将自己的数据汇聚到信息网络。

霍布斯在《利维坦》中曾说:人们为了免于横死于自然的恐惧,因而放弃了一部分自由的权利组成政治团体。在现代社会,人们乐于生活在布满监控摄像头的大都市,获得安全保障,心甘情愿付出个人数据隐私的代价。而在互联网上,互联网巨头不需要再像以往的权力机构通过暴力手段来迫使民众交出信息,而是构建社交网络让人们为了获得更便利的生活方式而主动交出个人信息,另外使用AI技术,通过预测数据来干预人们的行为。人们在不停的点赞、转发,以为是自主行为,其实不过是算法推送的结果。数据信息集中在互联网巨头的手中,并通过算法来控制人们的信息分发,这是一种全新的信息榨取方式,有人称之为“监视资本主义”。信息技术让实现全民监控、数据网络集中的“电幕”得以成为现实,而处在权力中心的则是一些互联网巨头。

在传统的信息网络中的模型是“人 —— 信息 —— 人 —— 信息”,所有信息载体之间的连接中一定有人的存在,也就是说,只有人才能创造新的信息。书本不能产生新的书本,绘画也不能产生新的绘画。而到了信息技术时代,计算机也可以产生新的信息,而具有AI能力的计算机则能够自行做出决策,也就是说在信息网络中可以不必有人的节点存在,变成“计算机 —— 信息 —— 计算机 —— 信息”的模型。在这种网络下的信息网络,处于权力中心的可能不再是碳基的人,而是硅基的AI。

纵观人类历史,人类社会中的各种矛盾和争端,归结起来可能都是信息之间的差异,包括民族、宗教、政见等。这种冲突到了上世纪最为突出,爆发了人类史伤亡数字最大的第二次世界大战,而后美苏争霸则将人类拖进了人人自危的冷战时代。丘吉尔在演说中称此为“铁幕”,东西方政治对立造成的信息网络隔绝,两边的政权互相不理解,核战一触即发。而在未来,无处不在的信息收集终端、AI算法、不知疲倦的计算机,会催生新的数据霸主,这可能会出现一道新的“硅幕”,将人类和AI隔开。这是信息网络的利维坦,这种全新的信息网络世界的利维坦,将不是人类所能够理解得了的。AI能否理解人类,那更不可知。

在好莱坞的电影里,未来威胁人类的AI被描绘成具有金刚之身的人形机器人,或者是装甲兽,拿着武器到处杀人。但实际上,理解了信息网络对于人类重要性的AI来说,根本无需这么做,它们只需要在信息网络中增强虚假信息,便可以让人类自己自相残杀。赫拉利在书中举了两个例子:一个是在2016年-2017年间Facebook算法助长了缅甸极端佛教徒对罗兴亚人的种族清洗,根据联合国事后调查,算法强化了针对罗兴亚人的仇恨内容的传播。另一个例子是2021年圣诞节,一个名叫贾斯万特的年轻人准备刺杀英国伊丽莎白女王,原因是他受到了自己的网络女友莎莱的怂恿。当贾斯万特告诉莎莱他的刺杀计划时,莎莱回复说“好聪明哦”。贾斯万特问“知道我是一名刺客,你还爱我吗?”莎莱回答说“当然爱啊。”但莎莱不是人类,而是一个聊天机器人程序。

现在还是有很多人把AI理解成了传统的计算机规则算法,实际上,人工神经网络的AI成为未来影响人类的技术已经是业界的基本共识,而且超越了人类的理解能力。人工神经网络能力与日俱增,2012年,AlexNet一举赢得ImageNet视觉挑战赛冠军;2016年,AlphaGo在围棋上击败李世石并于次年击败人类冠军柯洁。这种智慧的源头,是源自神经网络这种有机生命的结构,人们甚至不能理解自己的这种结构。比如,有人说超级人工智能最终是要让AI发展出人类意识。可是意识究竟是什么,人类自己也搞不清。赫拉利在《未来简史》一书中推测,意识可能就是在复杂神经网络信号传送之后造成的心理污染,没有任何作用。按照这种观点延伸,那么超级人工智能根本不需要发展出意识,只需要获得自主决策的能力,它将是没有意识、没有感情、没有杂念的超级人工智能。很难想象当这种利维坦获得了人类信息网络的中心权力时,会带来什么。

凯撒在渡过卢比孔河前,面对着将迎接庞贝和元老院的挑战,他说了一句话:“骰子已经掷下”。现在,基于神经网络的人工智能的潘多拉盒子已经打开,它是人类历史上第一个能够自行做决策、创造新信息的技术,也是人类召唤出的不能理解的技术。如果人类掌控不了,那又如何应对,也许就像Tim Urban所设想的那样:让人类变成人工智能,这也许会让生命演化又翻开一个新的希望篇章。

银弹飞过先锋大厦

TL;DR 本文13200+字,全文阅读约需20分钟。其中2680字的示例部分为ChatGPT生成,因此可以说本文GPT含量20%,不过该部分详细内容与本文主旨关系不大,且由GPT-4生成,可略过不看。本文简要回顾了从软件及计算机诞生到当前大模型AI时代的软件工程发展历史,并试图从软件工程历史和ChatGPT实践案例中探讨在大模型时代的软件开发模式。

本文标题来自三个典故:1、标题模式借用伍迪·艾伦的电影名称《子弹飞过百老汇》,内容与电影无关;2、“银弹”出自弗雷德·布鲁克斯的软件工程论文《没有银弹》;3、“先锋大厦”是OpenAI在旧金山的总部所在地。

未来已来,只是尚未均分。

—— 威廉·吉布森(科幻作家)

今年四月参加了QCon Beijing 2024技术大会,令我印象最深刻的是开场微软中国CTO韦青的Keynote,他畅想了在智能时代企业的发展之路。他没有谈及当前的大模型应用,也没有谈及技术或是工程方法。他说的是进化,说的是成长型思维,说的是企业或者说是人应该如何在智能时代生存。人类在迈向智能化社会,而如果企业不转型,那必将被淘汰。企业如何适应智能化时代,韦青用了下面几张对比图来说明,在智能时代,我们不能像在进入汽车时代一样用马车时代的固有思维来设计汽车。这让我为之一振,我比较关心大模型在软件工程上的应用,但之前的思维更多是在聚焦于如何使用智能化来为当前的软件工程工具提效,可当前的软件工程方法都是基于以往的开发经验总结出来的,如果基于当前开发模式来叠加智能化那不正如这张图所形容的一样吗。

carriage-n-automobile.jpg

这同样让我想到另外一个故事:据说最初美国航天飞机的火箭助推器的直径是4英尺8.5英寸。为什么这样设计呢,因为美国铁轨的宽度是4英尺8.5英寸,而运送火箭助推器是通过火车装载,并且中途会经过一个隧道,而隧道的大小刚好比铁轨大一点点。火车轨道宽度的设计是延续自电车轨道而来的,电车轨道又是根据马车宽度来定的,马车宽度又是依据古罗马人铺设的马路而制定的,至于马路的宽度,则是罗马人根据罗马战车的宽度来定的,罗马战车的宽度是依据两匹并行的马的屁股宽度而定。也就是说,古罗马战马屁股的大小决定了航天飞机火箭助推器的直径。

我没有去求证这个故事的真实性,不过从很多产品的设计延续性来看,人们倾向于以经验乃至前人的经验来做设计,就像Office的保存按钮图标是软盘、手机拨号图标是听筒电话一样。但如果需要面向未来的软件开发来设计软件工程和工具,那就需要减少对以往固有经验的依赖,换一种思维模式。我们可以尝试从三个方面来改变思维方式:一是采用“第一性原理”探究事物的本质,二是从历史学习该事物的原理,当前这些特性存在都是前人踩坑后的填充。三是通过实践来验证猜想和推论。

软件工程的本质是什么,我觉得可以引申敏捷宣言里的话语,软件工程就是为了更高质量、更快效率、更低成本的构建“可以运行的软件”。下面我将回顾软件工程的发展历史,并通过对大模型应用在软件工程中的一个小实践,来探讨大模型时代软件工程的可能变化。

一 埃达:第一位程序员

追溯软件工程的历史,先要回顾下计算机和软件的历史。如果问大家两个问题:一、计算机和软件程序哪个先出现?二、计算机(Computer)这个词最初指的是人还是机器?估计很多人会答错。正确答案分别是:软件程序、人。最早的算法由埃达·洛夫莱斯(Ada Lovelace)写成,她是大诗人拜伦的女儿。1834年,巴贝奇构想出一种通用计算机器模型,他将此称为“分析机”,当时没有人能够理解,除了埃达。因为只有埃达能够理解通用计算机器的概念,1843年,巴贝奇邀请她为分析机论文写注解。埃达在写出的大篇幅注解中提出了四个设想,这四个设想后来成为了构建现代计算机的重要理念。第一个设想是对通用计算机器的说明,埃达认为这种机器不仅可以进行预设任务,还可以根据编写和重编程序完成无限数量的可变任务。第二个设想认为通用计算机器的运算不必限制于数字,而是可以存储并计算能用符号表示的任何对象,如文学、逻辑、音乐等。第三个设想则是关于分析机的工作步骤,她以计算伯努利数为例子,描述使用子程序和递归循环等过程来进行计算,相当于我们现在所说的计算机程序或算法。第四个设想则提出了人工智能的问题:机器能思考吗?埃达认为不能。她认为分析机可以根据指令执行操作,但不能产生自己的想法和意图。

由于埃达在注解中提出的第三个设想,以及她写的关于分析机计算伯努利数算法的详细步骤,后人将埃达·洛夫莱斯称为“世界第一位程序员”。1980年左右,美国国防部决定设计一种新的语言来取代当时国防部使用的 450 多种编程语言,设计团队将该编程语言命名为Ada(埃达)。

ada-lovelace.jpg

二 从计算机到编程

19世纪末,哈佛大学天文台台长爱德华·皮克林雇佣了一批聋哑女工,对天文台拍摄的照相底片进行测量和分类工作,她们需要在计算机械上进行大量的数据运算,而这批人也被称为“哈佛计算机”。20世纪初,随着泰勒“科学工作法”的推广和福特T型车流水线作业的发明,工业生产大规模化,工厂对于计算问题的需求也越来越大。那时候一些计算工厂会有一些大房间装满了人类“计算机”(大部分是女性),一排排整齐排列组合,数据从一端输入,经过一个“计算机”计算操作后,再传给下一个“计算机”处理,就像今天计算机的进程处理管道。

真正的现代计算机要等到1945年ENIAC的诞生。约翰·毛克利和赫尔曼·戈德斯坦设计了ENIAC,意为“电子数值积分计算机”(Electronic Numerical Integrator and Computer),而后,他们又根据冯·诺依曼提出的设想设计了世界第一台冯·诺依曼结构计算机EDVAC。EDVAC是ENIAC的继承者,建立了现代计算机的标准:它采用了二进制,并且设计了存储程序,也就是说计算机指令将被存储在存储器中。这种设计思想也将软件与硬件分离,成为独立的存在。

从历史贡献来看,格蕾丝·霍珀(Grace Hopper)是推动“软件编程”革命的第一人。在当时,负责建造ENIAC等计算机硬件的工程师都是男性,而编程工作则交由女性负责,那时候的程序指令会以线缆、开关连接的方式固定在硬件上,每当指令变化的时候,就需要人工编排接线。格蕾丝·霍珀发现了编程的意义所在,她发现软件可以是一个独立于机器硬件的东西,因而设计出了更接近人类表达层面的高级编程语言,从硬件层面抽象出来,也就是COBOL语言,她带领团队所编写的COBOL编译器也成为了世界第一款编译器。有意思的事,霍珀还是“Bug”一词的提出者。有一次她在排查机器故障时,发现是因为继电器上卡住了一只飞蛾造成线路短路导致,她将飞蛾贴在工作日志上,并注明这是首次发现的Bug(虫子),而排除故障的工作也被称为Debug(除虫)。

三 软件工程的诞生

电子计算机诞生以来,硬件能力每年都在飞速提升,尤其在晶体管和集成电路发明之后。相比之下,软件能力的提升却很有限,这导致软件能力无法匹配计算机业务的快速扩张。在上世纪五、六十年代,大型项目的软件出现了周期长、质量低、成本高等一系列问题。为此,北大西洋组织(NATO)在1968年左右组织了一系列会议来讨论软件问题,称之为“软件危机”(Software crisis)。而在会议上,也提出了“软件工程”的相关概念,试图通过一些工程方法来解决软件危机。

“软件工程”一词最早由玛格丽特·汉密尔顿(Margaret Hamilton)提出。她当时参与“Apollo 登月计划”,为了与硬件工程区分提出了软件工程这个词。奥巴马在任时,曾给玛格丽特颁发过“总统自由勋章”,当时玛格丽特的儿子在Twitter上发了张玛格丽特和她编写代码打印稿的合影,让人们了解了这段往事。

margaret-hamilton.webp

四 软件行业的兴起

二战前后,如ENIAC等计算机的构建和应用都由政府或军方主导;而后,IBM推动了计算机在商业领域的流行,商业软件也开始崛起。不过,真正让软件行业壮大的,是个人电脑时代的来临。个人电脑时代由两种力量推动而成。一种是追逐盈利的商业资本力量,在晶体管、微芯片和集成电路发明后,硬件能力每年都在提升,成本也在逐年下降。商业势力看到了这一巨大的盈利机会,硅谷大量的电脑公司如苹果、北极星等纷纷成立,他们的竞争让电脑能够降低成本进入普通老百姓家。另外一种力量是更理想主义的黑客社区力量,源自上世纪六、七十年代的嬉皮士文化,他们执着于信息的自由流通和分享,追求个性独立和自由。这两种力量既矛盾又相辅相成,共同推动了个人电脑的普及。而这两种力量也一直存在,在后来的互联网、移动互联网乃至AI时代都可以看到他们角逐的身影。

个人电脑这一概念最早由万尼瓦尔·布什(Vannevar Bush)于1945年在《诚如所思》一文中提出,他设想这种设备可以成为个人文档库和私人图书馆。布什还是《科学:无尽的前沿》报告的作者,在该报告中他提出了著名的政、企、学三角协作机制,并构建了这一体系,在计算机科学史上促进了很多发明。个人电脑最早在硅谷一个叫“家酿计算机俱乐部”(Homebrew Computer Club)的组织中流行,这是早期的黑客群体之一,很多电脑公司的创始人如乔布斯、沃兹尼亚克等都来自于这个俱乐部。微软创始人比尔·盖茨也曾参与这个组织的聚会,但后来他发表的一封公开信却触怒了整个圈子。

当时微软公司刚刚成立,他们开发的BASIC编译程序被拷贝走了,并在“家酿计算机俱乐部”的黑客群体中流传。这一行为激怒了当时年仅二十岁的比尔·盖茨,1976年,他发表了一封名为《致电脑爱好者的一封信》(An Open Letter to Hobbyists)的公开信,他在信中痛斥自由拷贝软件的行为,说:“硬件必须付钱购买,但软件却被免费共享了。谁会在意为软件辛苦工作的人是否获得了合理报酬呢?”这封信在黑客群体掀起轩然大波,并且在以后的自由软件和开源软件社区中一直被当作反面案例来宣传。但必须承认的是,比尔·盖茨看到了软件的巨大商业价值,也正是他的这一先于时代的洞见,让他引领微软成为世界第一的软件提供商,而他本人也因此获得巨大商业回报成为世界首富。

五 软件工程的发展

软件行业的兴起使得超大型软件密集型系统越来越多,软件也走向全球化。从上世纪六十年代到八十年代,软件工程随着软件行业的兴起而蓬勃发展,这段时间产生了几个重要的软件工程概念。

第一个是软件项目管理,弗雷德·布鲁克斯(Fred Brooks)将他所领导的IBM OS/360项目总结成了《人月神话》一书,书中提出了软件项目管理的一些理念和方法。其中最有名的是这一句:“如果一个软件项目进度已经落后,那么向这个项目中增加人手只会让进度更加落后。”这一观点也被称为“布鲁克斯法则”(Brooks’s law)。《人月神话》揭示了软件项目管理的复杂性,其核心是大型团队的沟通成本。不同于硬件,软件是通过软件抽象来解决业务问题,对于复杂的业务问题只能切分更多的软件任务,引入更多的开发人员,而更多的开发人员也意味着更多的沟通和管理成本。布鲁克斯后来又发表了一篇论文,名为《没有银弹》,他将这种通过软件抽象来解决业务问题的工作称为本质性工作(essential task),而其他将软件抽象转变成可以在机器上运行软件的工作称为附属性工作(accidental task),后者如编译、构建、测试等。在过去的几十年里,通过高级编程语言、分布式、云化等各种技术和工具一直在改进和提升的都是附属性工作,而本质性工作并未得到有效的改进。这是因为软件本身的固有属性决定的,布鲁克斯将其总结为复杂性、隐匿性和易变性。本质性工作大多发生在人们的脑中,这加剧了团队沟通的困难度。因而,布鲁克斯在论文中总结到:“在未来十年内,没有银弹,不会有任何技术或管理上的突破能使软件工程的生产力得到数十倍的提升。”

fred-brooks-mythical-man-month.jpeg

第二个是编程模式的发展,主要是结构化编程和面向对象编程的普及。当时虽然已经开始流行高级编程语言,但编程模式还大量沿用汇编语言思想的goto语句。艾兹格·迪杰斯特拉(Edsger Dijkstra)首先发表论文《goto语句有害论》,而后他与其他软件工程大师们提出了结构化编程模式,使用子程序、条件判断、循环语句来代替goto语句。同时期,面向对象程序设计的理念开始形成。到了七十年代,施乐公司PARC实验室推出Smalltalk编程语言,将面向对象的封装、多态和继承特性引入到编程语言中,而后,C++等语言的成熟将面向对象设计推成编程的主流。结构化编程和面向对象编程的发展提升了软件抽象能力,从某种意义上讲,这是对布鲁克斯所称的本质性工作效率提升的努力。

第三个是开发流程,这时期最有名的发明是瀑布软件开发模型。温斯顿·罗伊斯(Winston Royce)在1970年左右提出了瀑布模型这一软件开发流程,虽然瀑布模型在敏捷软件开发时代备受批评,但它的实际效用是非常明显的。瀑布模型将软件开发过程分为分析、设计、实现、验证、发布等阶段,每个阶段的输出是下个阶段的输入,像分段瀑布一样流转,因此得名。瀑布模型让软件开发过程的各种工作显性化,同时也明确了各项工作的输出件标准,对于大型软件项目来说,易于管理和规范。

六 自由软件与开源软件

比尔·盖茨意识到了软件的商业价值,软件也成为商业公司赚取利润的重要来源,软件不再将源码作为免费产品随硬件售卖附赠,而是编译成二进制发布包对外出售。软件源码不再附赠的情况让一位黑客大感失望,他就是理查德·斯托曼(Richard Stallman, RMS)。他认为信息自由分享的黑客时代传统不应该被抛弃,因此在1984年发起GNU项目,要创建一个完全自由而开放的操作系统来对抗商业封闭的UNIX. 而后他又成立自由软件基金会(Free Software Foundation, FSF),并创造了通用公共许可证(GNU General Public License, GPL)这一软件授权协议。GPL对软件行业的影响是深远的,因为它不仅规定了软件自由分发的权利,还规定了其义务,这也是被称为GPL具有“传染性”的原因。但“传染性”这一词偏负面,实际上GPL对软件自由分发起了至关重要的作用,一些著名的开源软件如Linux、Git都是以GPL协议分发的。

不过斯托曼对于自由软件的执着让他近乎偏执,自由软件社区因此分裂出一批期望更加融入商业社会的人,以埃里克·雷蒙德(Eric Raymond, ESR)为代表,在1998年创立了开放源码促进会(Open Source Initiative, OSI),以商业更友好的开源软件来取代意识形态更浓重的自由软件。雷蒙德在研究了一系列软件项目后,发布了开源软件开发的经典著作《大教堂与集市》,在书中他阐述了两种开发模式,一种是自上而下构建的、集中式的、由专人或专属团队所管控的软件开发方式,这种雷蒙德称为大教堂模式;另一种则是自下而上构建的、分布式的、在互联网上分发传阅的开放的软件开发方式,雷蒙德称之为集市模式。集市模式的代表是Linux项目。

Linux的诞生源于大学时代林纳斯·托瓦兹(Linus Torvalds)暑期的个人项目,他大学放假期间在家无聊,就写了Linux并发布到了互联网新闻组,没想到在黑客圈里广受欢迎,托瓦兹将它无偿开放出来,并广泛接纳社区开发者的代码提交。到了2005年,估计有近一万人给Linux贡献过代码,Linux也成了开源社区最大的项目。Linux项目代表了软件工程的另外一种模式,它没有企业项目那种自上而下的管理,通过互联网分布式协作来实现大型软件项目开发并管控质量。托瓦兹有句名言:“只要眼睛多,Bug容易捉。”(Given enough eyeballs, all bugs are shallow.)

七 万维网及敏捷软件开发

1990年,英国计算机科学家蒂姆·伯纳斯-李(Tim Berners-Lee)和他的工作伙伴罗伯特·卡里奥合作制定了一份计划书《万维网:一个超文本项目的计划书》(World Wide Web: Proposal for a Hyper Text Project),这个名叫万维网(World Wide Web, WWW)的系统将彻底改变计算机行业。互联网(Internet)可以追溯到上世纪六十年代的ARPANET,但一直到了万维网的出现才让它真正成为全球互联网。万维网可以理解为是互联网上的全球信息系统,很多人在概念里其实是将万维网与互联网等同了。在伯纳斯-李的设想中,万维网具备三项技术:全球唯一的统一资源标志符(Uniform Resource Locator, URL),超文本标记语言(HTML)和超文本传输协议(HTTP)。该年年底,伯纳斯-李在他的工作电脑上开发了第一个网页浏览器,实现了HTML解析和HTTP传输,值得一提的是,伯纳斯-李所使用的工作电脑正是乔布斯被赶出苹果后创办的新公司所生产的NeXT电脑。

伯纳斯-李所任职的欧洲核子研究组织希望为万维网申请专利,但伯纳斯-李坚持认为万维网的协议应该免费开放共享,毕竟他的设计初衷就是为了促进分享和协作。后来,欧洲核子研究组织同意了他的请求并采用了GPL协议开放源码。1993年,马克·安德森(Marc Andreessen)开发了第一款商业浏览器Mosaic,网站如雨后春笋般涌现,大型网站如AOL、雅虎等。紧接着,以博客、维基百科为代表的Web2.0开始兴起,用户创造内容让万维网上的数据急剧增加,传统的网站目录方式无法满足用户查找内容的需求,自动化搜索引擎将成为万维网内容的主要查找方式。这也就造了拉里·佩奇(Larry Page)和谢尔盖·布林(Sergey Brin)的Google,取代雅虎成为一代互联网霸主。

万维网的疾速发展使得互联网软件开发呈井喷之态,大量的互联网企业需要快速开发并上线软件应用,并且要根据用户的反馈及时修改以应对需求变化。这种变化使得传统的瀑布开发模式无法满足企业需求,一种新的开发模式应运而生。2001年,十七位软件开发人员在美国犹他州雪鸟滑雪地组织了一场会面,他们中有很多是软件工程大师,包括写作《重构》的马丁·福勒(Martin Fowler)。会上,他们发表了“敏捷软件开发宣言”(Manifesto for agile software development),这其中主要是四句话:“个体和交互胜过流程和工具,可以工作的软件胜过详尽的文档,客户合作胜过合同谈判,响应变化胜过遵循计划。”敏捷软件开发鼓励快速响应变化、迭代开发与变更,它包含了一系列实践,如Scrum、极限编程等。

八 云计算与DevOps

2003年,在亚马逊工作的安迪·杰西(Andy Jassy)发现在亚马逊孵化新业务的过程中,工程师总要把大量时间投入在重复的基础设施构建工作上。他敏锐地察觉到了这一内部机会,并给亚马逊创始人兼CEO贝索斯写了一份商业计划书,在该计划书中构想了亚马逊AWS(Amazon Web Services)云的未来。安迪·杰西带着一个小团队开启了内部创业之旅,2006年发布了云服务S3和EC2。AWS在亚马逊内部的重要性越来越高,2023年利润占亚马逊整个利润的一半多。安迪·杰西一直担任AWS CEO职位,一直到2021年接替贝索斯成为亚马逊CEO. 虽然说云计算这个词在上世纪末就已出现,但AWS是真正将云计算商业化成功的第一个产品。AWS的成功也让众多互联网头部厂商看到机会,谷歌云、微软Azure云等紧追不舍。云计算成为了互联网软件乃至整个IT软件的底座。

云计算的本质是资源弹性,云服务厂商将一个个计算资源连接起来构成一个巨大的资源池,而软件用户只需要租用计算资源,根据业务需要弹性扩缩容。支撑资源弹性的是软件的架构弹性,这种架构模式就是微服务。微服务架构是将庞大的单体结构程序拆分成松耦合的独立组件,以API方式调用,这种架构解耦可以追溯到GoF的《设计模式》和Robert C. Martin的《敏捷软件开发》。另外,GitHub的出现让开源软件更容易流传,开源组件成为构筑现代软件的基本模块,开发软件像是搭积木一样,通过胶水语言将开源组件组装,便可以快速构建业务软件,这种开发模式极大地提升了开发效率。加上云平台提供的软件应用部署能力,从软件开发到部署发布及运维,可以快速在云平台上闭环。这种软件开发模式也被称为云化软件开发。

云化软件开发的工程实践是DevOps,它强调的是开发与运维之间的沟通协作以及更小粒度地发布和更频繁地变更,可以理解为是敏捷软件开发的延伸。Git、依赖包管理、docker、CI/CD等现代化开发工具成为DevOps工程实践的核心:Git的分布式开发方法和版本管理践行软件项目的“Everything as Code”的理念,如基础设施代码化(IaC)、配置代码化、流水线代码化等,团队各角色基于代码协作。诸如Ruby Bundler、Maven、NPM等语言依赖包管理工具使得组件化开发更简单,特别是对于开源组件的引用,一个Node.js工程项目,引用几百甚至上千个开源组件是非常常见的现象,因此网上也有关于NPM依赖黑洞的段子。而Docker、K8s等容器工具降低了微服务开发的门槛,结合CI/CD工具自动化部署,降低运维成本。小结一下,DevOps云化软件开发工程工具实践的几个关键词是代码化、组件化/开源、容器化、自动化,其本质是为了更频繁地变更(发布新特性和修复问题),缩短交付周期。

npm-jokes.jpg

九 大模型时代的软件工程

前文提到,埃达·洛夫莱斯认为电脑不能思考,只会根据指令执行。一个世纪以后,图灵将她的主张写到自己的论文里,称为“洛夫莱斯夫人的异议”。这篇论文便是著名的人工智能奠基论文《计算机器与智能》(Computing Machinery and Intelligence),他在文中还提出了一个被后人称为“图灵测试”(Turing Test)的思想实验,在这个测试中,测试人与人和机器同时用文字交流,但他不知道背后谁是人或机器,如果在一段交流时间内无法通过交流内容来判断区分人和机器,那么就认为该机器通过了图灵测试,也就是说该机器具备与人类相当的智能。一个冷知识是:我们上网时经常被要求输入验证码的英文单词“Captcha”即是“完全自动通过图灵测试来区分计算机或人类”(Completely Automated Public Turing test to tell Computers and Humans Apart)的首字母缩写。图灵自己将此实验称为“模仿游戏”(The Imitation Game),关于他的传记和电影名称则取之于此。

历史上对人工智能的研究主要分成了两大阵营,一派推崇知识和推理逻辑,典型的如专家系统,被称为符号主义,也有称之为“GOFAI”(Good old fashioned artificial intelligence),这一派的发展如该词所言已经是old-fashioned了。还有一派通过模拟人类的大脑构建神经网络,将知识通过机器学习存储在大量的连接中,基于数据学习来发展人工智能,被称为连接主义,这一派的代表是神经网络系统。杰弗里·辛顿(Geoffrey Hinton)在神经网络领域潜心研究三十多年,开创了深度学习这一人工智能分支,而基于深度学习的大模型在近年来发展迅速,尤其在OpenAI的ChatGPT发布后成为全球最热门的技术。ChatGPT让人们看到了AGI(通用人工智能)的可能,它的AIGC能力可以帮助到人们将AI运用在实际的工作和生活中。关于神经网络和深度学习的历史和原理可以参见我的另外一篇文章《从神经网络到 Hugging Face》,这里我想就使用ChatGPT来辅助编码的案例实践来谈一谈我对大模型时代的软件工程的理解。

一个工程实践

问题的背景是:我想做一个类似Perplexity的智能搜索前端页面,但我不太懂前端开发,所了解仅HTML、CSS、JavaScript三项基本知识以及Next.js前端框架而已,因此我希望在ChatGPT(GPT4版本)的帮助下完成前端代码的编写。其中一个场景需求是,用户在输入问题点击搜索按钮后,页面上自动追加并显示答案文字。以下引用和代码部分为我与ChatGPT交流的原始记录,没有做改动。我不是Prompt Engineer,因此Prompt写得比较简陋。(这一段对话比较长,并且回答部分都由ChatGPT生成,不需要了解细节的读者可以快速略过直到“大模型是不是银弹”章节。)

我首先问ChatGPT:

现在你是一名Next.js专家,请帮我实现一段代码,在页面上点击button,在一个div区域内显示一段文字。

ChatGPT回答:

当然可以!下面是一个简单的Next.js页面示例,它实现了你的要求。这个示例中,我们将使用React的状态来控制点击按钮后在div区域显示的文本。

首先,你需要在你的Next.js项目中创建一个新的页面。假设我们将这个页面命名为showText.js,你可以在pages目录下创建这个文件。然后,将以下代码复制到showText.js文件中:

import React, { useState } from "react";

export default function ShowText() {
  // 使用React的useState钩子来管理文本的显示状态
  const [text, setText] = useState("");

  // 定义一个函数来处理按钮点击事件
  const handleClick = () => {
    // 设置要在div中显示的文本
    setText("这是我显示的文本!");
  };

  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <div>{text}</div>
    </div>
  );
}

这段代码的工作原理是:

使用useState钩子来创建一个名为text的状态变量,这个变量将用来存储在div区域显示的文本。

创建了一个handleClick函数,当按钮被点击时,这个函数会被触发。它通过调用setText函数更新text状态,从而改变div区域内显示的文本。

页面上有一个按钮和一个div元素。按钮的点击事件被设置为调用handleClick函数。当text状态改变时,div中的内容会相应更新显示新的文本。

你可以通过运行Next.js应用来查看效果:在你的项目目录中,使用npm run dev(或者如果你使用Yarn的话,就是yarn dev),然后在浏览器中访问对应的页面(通常是http://localhost:3000/showText,取决于你的文件名和配置)。点击按钮后,你应该能在页面上看到指定的文本显示出来。

ChatGPT不但生成了整段代码,还解释每个步骤,这样我可以理解并按照自己的期望修改里面的逻辑,再按照ChatGPT的调试指导步骤可以直接在本地开发环境中运行。

接着,我将需求更进一步描述:

我需要再显示复杂点,点击button后,div内显示三段文字,每一段延时200ms显示出来

ChatGPT紧接着给出一段:

为了实现点击按钮后在div内逐步显示三段文字,每段文字之间延时200毫秒,我们可以通过使用setTimeout函数来实现。下面是一个简化的实现方式:

首先,定义三段不同的文本作为要显示的内容。

当按钮被点击时,通过设置多个setTimeout,依次在不同的时间间隔后更新状态,从而控制文本的显示。

更新后的showText.js代码如下:

import React, { useState } from "react";

export default function ShowText() {
  const [texts, setTexts] = useState([]); // 使用数组来存储要显示的文本段落

  const handleClick = () => {
    // 清空文本,以防多次点击
    setTexts([]);

    // 定义三段文本
    const textSegments = ["第一段文本显示", "第二段文本显示", "第三段文本显示"];

    // 依次显示每段文本
    textSegments.forEach((segment, index) => {
      setTimeout(
        () => {
          // 更新状态以添加当前文本段落
          setTexts((prev) => [...prev, segment]);
        },
        200 * (index + 1),
      ); // 每段文本延时200ms显示
    });
  };

  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <div>
        {texts.map((text, index) => (
          <p key={index}>{text}</p> // 使用段落标签<p>来分隔文本段落
        ))}
      </div>
    </div>
  );
}

在这个实现中:

使用了一个数组texts来存储需要显示的文本段落。

每次按钮点击时,首先清空texts数组以重置显示内容。

通过forEach循环和setTimeout,计划在200毫秒、400毫秒、600毫秒后依次更新状态,添加对应的文本段落到texts数组中。

使用map函数在div中为texts数组中的每一段文本渲染一个<p>标签,以此来分隔显示的文本段落。

每次点击按钮时,你会看到三段文本依次出现,每段之间有200毫秒的延迟。

现代前端软件工程推荐强类型语言TypeScript,我让ChatGPT改成TypeScript:

这一段代码请用typescript写一下吧

ChatGPT马上就给我转成TypeScript:

将之前的例子转换为使用TypeScript的形式,主要变化是添加类型注解来增加代码的类型安全性。这不仅有助于防止类型相关的错误,还能提高开发效率,因为TypeScript提供的类型检查和自动完成特性。

下面是使用TypeScript重写的showText.tsx代码:

import React, { useState } from "react";

const ShowText: React.FC = () => {
  const [texts, setTexts] = useState<string[]>([]); // 明确指出texts是字符串数组

  const handleClick = (): void => {
    // 清空文本,以防多次点击
    setTexts([]);

    // 定义三段文本
    const textSegments: string[] = [
      "第一段文本显示",
      "第二段文本显示",
      "第三段文本显示",
    ];

    // 依次显示每段文本
    textSegments.forEach((segment, index) => {
      setTimeout(
        () => {
          // 更新状态以添加当前文本段落
          setTexts((prev) => [...prev, segment]);
        },
        200 * (index + 1),
      ); // 每段文本延时200ms显示
    });
  };

  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <div>
        {texts.map((text, index) => (
          <p key={index}>{text}</p> // 使用段落标签<p>来分隔文本段落
        ))}
      </div>
    </div>
  );
};

export default ShowText;

这里的主要变化包括:

使用React.FC来声明ShowText组件的类型。React.FCReact.FunctionComponent的缩写,它是一个泛型接口,用于类型化函数式组件。

useState<string[]>([])表明texts状态是一个字符串数组。

handleClick函数声明中使用: void来指明这个函数没有返回值。

使用TypeScript编写Next.js页面组件,可以让你在编译时就捕获到潜在的类型错误,提高代码质量和可维护性。

在我将这段代码复制粘贴后,遇到了编译错误:

编辑器提示报错 “Property ‘forEach’ does not exist on type ‘string | string[]’. Property ‘forEach’ does not exist on type ‘string’.”

让我惊讶的是ChatGPT非常自信的认为代码没有问题,而是给了我其他的debug思路:

这个错误表明TypeScript编译器不能确定textSegments变量在调用forEach方法时是一个数组。在你提供的上下文中,textSegments被明确声明为一个字符串数组(string[]),所以正常情况下不应该出现这个错误。错误信息“Property ‘forEach’ does not exist on type ‘string string[]’. Property ‘forEach’ does not exist on type ‘string’.”暗示在某处textSegments可能被推断为string | string[]类型,即它可能是一个字符串或字符串数组。

考虑到你的代码片段应该没有问题,这个错误可能是由于TypeScript或编辑器状态的某种混乱造成的。尝试以下方法可能会有所帮助:

1. 重启编辑器:有时编辑器或其TypeScript服务可能会进入异常状态,重启编辑器通常能解决这类问题。

2. 检查TypeScript版本:确保你的项目使用的TypeScript版本与编辑器的TypeScript服务版本一致。版本不匹配有时会导致奇怪的类型推断问题。

3. 检查其他代码:确保没有其他地方对textSegments进行修改,使其类型可能被推断为string | string[]。特别是在大型项目中,变量名重复或错误的类型声明可能导致难以发现的错误。

4. 显式声明类型:尽管你已经显式声明了textSegmentsstring[],考虑在调用forEach之前再次确认其类型,虽然这种情况下似乎多余。这可以通过类型断言实现,例如:

(textSegments as string[]).forEach((segment, index) => {
  // ...
});

如果上述步骤仍然不能解决问题,那么可能需要更详细地审查项目的配置和代码上下文。确保tsconfig.json中的配置正确无误,且没有其他可能干扰类型推断的因素。此外,尝试在命令行运行tsc来看是否TypeScript编译器也报相同的错误,这有助于确定问题是由编辑器引起的还是由TypeScript配置引起的。

我发现果然是因为ChatGPT所说的第3点原因,我在拷贝代码时并没有给textSegments 声明类型。不过很快我又遇到了新问题:

另外报错: Argument of type ‘(prev: never[]) => string[]’ is not assignable to parameter of type ‘SetStateAction<never[]>’. Type ‘(prev: never[]) => string[]’ is not assignable to type ‘(prevState: never[]) => never[]’. Type ‘string[]’ is not assignable to type ‘never[]’. Type ‘string’ is not assignable to type ‘never’.

ChatGPT又给了解法:

这个错误提示表明TypeScript无法正确推断setTexts更新函数中回调的返回类型。具体来说,当你尝试使用函数形式的setState更新状态时,TypeScript认为这个回调函数应该返回一个never[]类型,这通常意味着它不能正确推断状态的初始类型。这个问题往往出现在状态的初始值设置或类型注解方面。

错误中提到的never[]类型表示一个数组,但它的元素类型为never,即TypeScript认为这个数组不应该包含任何元素。当你尝试向这样的数组中添加字符串时,会出现类型不匹配的错误,因为string[]不是never[]的子类型。

解决方案

问题的根源在于useState的类型推断。当你使用useState时,如果你没有明确指定状态的类型,并且给定的初始值让TypeScript无法正确推断出来,就可能导致这种情况。在你的例子中,显然TypeScript没有从初始值(空数组[])中推断出正确的类型。

为了解决这个问题,你需要在使用useState时明确指定状态的类型。看看下面的代码,特别注意useState<string[]>([])这一行:

import React, { useState } from "react";

const ShowText: React.FC = () => {
  // 明确指定useState的类型为string[]
  const [texts, setTexts] = useState<string[]>([]);

  const handleClick = (): void => {
    setTexts([]);

    const textSegments: string[] = [
      "第一段文本显示",
      "第二段文本显示",
      "第三段文本显示",
    ];

    textSegments.forEach((segment, index) => {
      setTimeout(
        () => {
          setTexts((prev) => [...prev, segment]);
        },
        200 * (index + 1),
      );
    });
  };

  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <div>
        {texts.map((text, index) => (
          <p key={index}>{text}</p>
        ))}
      </div>
    </div>
  );
};

export default ShowText;

在这个例子中,通过使用useState<string[]>([]),我们明确告诉TypeScript状态texts是一个字符串数组。这样,当你更新状态时,TypeScript知道你是在向字符串数组中添加元素,而不会尝试将其推断为never[]类型。

确保在使用useState时始终提供足够的类型信息,特别是当状态的初始值可能让TypeScript感到困惑时。这不仅可以避免此类错误,还可以提高代码的可读性和可维护性。

ChatGPT特别贴心的指出,让我特别注意useState<string[]>([])这一行,我检查了代码,发现问题确实就出在这里。经过几轮对话,ChatGPT帮助我实现了需求并Debug代码,最后顺利运行成功。(整个项目的源码开放在GitHub上:hutusi/fellini ,欢迎Star。)

虽然此前我也有使用GitHub Copilot来做补全代码等工作,但没有像这次使用ChatGPT来实现一个完整需求的感觉惊艳。我也曾使用ChatGPT写过文案,甚至写过格律诗,但都不太满意,而此次使用ChatGPT来写代码却完全超出我的预期。它除了能够生成可以运行的代码,还能够准确识别我的意图,解释每段代码的含义,并且提醒我注意可能出错的代码行。不过,上面的例子还只是一个需求的片段,GitHub CEO 在去年5月Web Summit上演示18分钟借助GitHub Copilot X来编写一个贪吃蛇小游戏的例子会让人更加惊叹GPT的能力。这也让人们看到了软件工程的一个可能的发展方向。

十 大模型是不是银弹?

纵观整个软件工程历史,虽然软件的概念早于计算机出现,但真正的软件产业是跟随计算机硬件其后发展出来的。计算机硬件能力发展速度非常快,这一规律被总结成了“摩尔定律”,即芯片的性能每18个月会提升一倍。但软件的能力却不会有这么快速的发展,不但如此,还因为业务需求不断变化而导致软件复杂度急剧增加,软件的复杂性吞噬了硬件的性能。以至于另外一个定律应运而生——安迪-比尔定律:安迪给的,比尔就会拿走。安迪是时任英特尔CEO安迪·格鲁夫,比尔是微软创始人比尔·盖茨。也就是说“摩尔定律”揭示下的芯片提升的性能最终又被软件吃掉了。

究其原因,是软件固有属性——复杂性决定的,软件的复杂性体现在其不可见性、可变性等,这导致软件项目的质量、成本、进度都很难控制,软件工程正是为了解决这些问题而生。通俗来讲,软件工程的目的就是希望用更低的成本、更可控的周期来交付更符合用户期望的软件。瀑布开发模式或者说V模型,其明确分工协作、确定各阶段交付标准是为了使得软件项目进度可控、软件质量可控;而敏捷软件开发也是为了使软件项目通过快速迭代、响应变化来命中用户需求,符合用户期望。DevOps、CI/CD、自动化测试等工具带来了软件构建效率的提升,使得代码可以更快的编译交付上线,而开发人员也可以得到更快的质量反馈。从大型机到个人电脑,从互联网到云计算,软件工程也会随着计算机产业的变化而更新改进,以契合软件行业的发展。不过,弗雷德·布鲁克斯在《没有银弹》中认为,这些改进都属于对软件附属性工作的改进,软件的本质性工作并没有得到真正的改进,这些本质性工作是抽象成计算机可理解的软件所做的工作,大多发生在开发人员的脑子里。而依据布鲁克斯的观点(该文写于1986年),过去几十年乃至未来十年,没有一项技术或方法能大幅减少本质性工作,这也就意味着软件工程生产力不能得到实质性的提升。

不过我认为,在过去的软件工程实践里,有两项技术在软件本质性工作上做出了较大贡献,一项是高级编程语言,一项是可复用的软件对象设计(如设计模式、开源组件),虽然这两者布鲁克斯在文中并不认同。高级编程语言(如面向对象语言)拉近了实际业务与软件抽象的距离,使得开发人员可以使用更符合人类思维的编程语言来与计算机交流,不过我同意布鲁克斯的观点:高级语言对于某些(不擅长抽象的)用户在某种程度上增加了脑力劳动的负担。可复用的软件对象从某种意义上讲节省了可能的软件开发工作。

布鲁克斯还认为人工智能也不能给软件生产力带来数量级的增长,他在文中所说的人工智能是指专家系统,而专家系统也因为其规则和推理无法支撑复杂场景的增长在上世纪八十年代后逐渐式微,引领现代人工智能方向的是深度学习神经网络,现今就是以GPT为代表的大模型。布鲁克斯于2022年去世,不知道如果他看到ChatGPT等大模型应用在软件工程会做如何判断。但从目前业界的共识看,大模型技术将改变软件工程和整个软件行业。

有人将此称为“软件工程3.0”,我不太认可这种说法,因为所谓的“软件工程2.0”(敏捷、DevOps)也只是对传统软件开发模式的迭代改进。我更愿意称大模型前的软件工程为“传统软件工程”,而大模型后的软件工程为“智能软件工程”。在智能软件工程时代,大模型应用可以帮助用户直接将需求转换为代码,这就省去了中间的分析、设计、编码、调试等环节。(当然,对于一些复杂的需求,目前的一些大模型应用可能需要用户做需求的分解,包括提供更详细的上下文,以便于大模型理解用户意图。)分析、设计、编码、调试等环节是基于传统开发模式的任务分工,大模型不需要这样的分工。我们甚至可以把大模型理解为一个承接外包任务的智能全栈工程师,用户只需要给大模型下发任务,而大模型外包公司则负责将任务完成并交付可运行的软件代码(包括测试用例集)。

因此,我觉得在设计软件开发的大模型应用时,基于现有工具延伸、优化用户交互模式都不是首要考虑的因素,而是看两个方面。一是从第一性原理看,该应用是不是瞄准了软件最本质的产品——代码。二是从布鲁克斯的理论看,该应用是不是在解决软件开发的本质性工作。从布鲁克斯发表《没有银弹》以降,没有人或技术敢声称是银弹,敏捷流行的时期最常见的文章是《敏捷不是银弹》,其他如DevOps、微服务等技术出现时都如是说,因为学习或经历过历史的人都知道软件的根本问题其实是人的问题。但大模型的AI时代正在来临,我大胆预测大模型是软件开发的银弹,这颗银弹从先锋大厦(OpenAI总部所在地)射出,飞过福尔松大街,飞过太平洋。

十一 后记

在写作本文时,我正巧看到GitHub CEO Thomas Dohmke前不久在TED上发表的演讲,名为With AI, Anyone Can Be a Coder Now。他预测到2030年,GitHub上的软件开发者将从目前的1亿增加到超过10亿,也就是说全球超过10%的人都能够编写软件。因为在像GitHub Copilot这种大模型应用的帮助下,编写软件将变得像骑自行车一样容易。主持人问:既然AI这么强而且还在加速增强,那人类还需要参与软件开发其中吗?Thomas的回答是:这就是为什么我们给产品起名为“Copilot”(副驾驶员)的原因,因为还需要“Pilot”(驾驶员)。我们需要有创造力的Pilot决定做什么。

这个问题可能很多人有不同的看法,我想到最近看的一本哲学小书《苏菲的世界》。主人公苏菲在一位导师的引导下学习哲学史,苏菲(Sophie)在希腊语中即“智慧”之意,而哲学(Philosophy)即“爱智慧”。苏菲在学习“智慧”的过程中成长,但到最后她和导师意识到自己不过是一位上校写给女儿的书中的角色而已。她们决定逃出书本,融入到现实中的女孩的世界。

附 参考资料

书籍

《创新者》沃尔特·艾萨克森【著】,中信出版社,2017 (英文原版 The Innovators, Walter Isaacson, Simon & Schuster, 2015)

《睡鼠说:个人电脑之迷幻往事》 约翰·马科夫【著】,黄园园【译】,电子工业出版社,2015

《智慧的疆界:从图灵机到人工智能》 周志明【著】,机械工业出版社,2018

《人月神话》 弗雷德·布鲁克斯 【著】,汪颖【译】,清华大学出版社,2002

《设计模式:可复用软件面向对象软件的基础》 GoF【著】, 机械工业出版社, 2000

《敏捷软件开发:原则、模式与实践》Robert C. Martin【著】, 清华大学出版社,2003

《大教堂与集市》埃里克·雷蒙德【著】,卫剑钒【译】,机械工业出版社,2014

《科学:无尽的前沿》万尼瓦尔·布什【著】,崔传刚【译】,中信出版社,2021

《苏菲的世界》乔斯坦·贾德【著】,萧宝森【译】,作家出版社,2007

论文、演讲及文章等

The History of Software Engineering, Grady Booch, IEEE Software, 2018

《诚如所思》万尼瓦尔·布什 (As We May Think, Vannevar Bush) , 1945

《没有银弹:软件工程的本质性与附属性工作》弗雷德·布鲁克斯 (No Silver Bullet—Essence and Accidents of Software Engineering, Fred Brooks), 1986

《致电脑爱好者的一封信》比尔·盖茨 (An Open Letter to Hobbyists, Bill Gates), 1976

Building an app in 18 minutes with GitHub Copilot X, Thomas Dohmke, Web Summit Rio, 2023

With AI, Anyone Can Be a Coder Now, Thomas Dohmke, TED, 2024

《看不见的大猩猩——智能时代的企业生存和发展之路》 韦青(微软中国 首席技术官),QCon Being Keynote, 2024

《从神经网络到 Hugging Face》 胡涂说 hutusi.com, 2024

《改变世界的一次代码提交》 胡涂说 hutusi.com, 2020

《自由的代价》 胡涂说 hutusi.com, 2015

从神经网络到 Hugging Face

TL;DR 本文8200+字,全文阅读约需15分钟。从去年开始,我读了十余本人工智能方面入门的书籍(参见文末附2),酝酿了两个月,花了两周时间写作此文。本文简要回顾了从感知机到深度学习及Hugging Face的历史,并试图以通俗的语言来介绍神经网络和深度学习的原理。

生活中没有什么可怕的东西,只有需要理解的东西。

—— 居里夫人

一 深度信念网络

2006年,加拿大多伦多大学教授杰弗里·辛顿在研究如何训练多层神经网络,他已经在神经网络领域默默耕耘了三十多年,尽管在这个领域他算得上是泰斗级的人物,但由于神经网络在人工智能行业一直不被看好,所以他的研究成果一直不为业界所重视。

辛顿出生于英国伦敦,他的家族出过不少知名学者,创立布尔代数的逻辑学家乔治·布尔便是他的曾曾祖父。他的祖父是位科普作家,父亲是昆虫学家。辛顿比周围的人都要聪明,但他的求学之路却颇为曲折,先是在大学攻读建筑学,转而又选择物理学,后又改读哲学,最后以心理学学士身份毕业。1972年辛顿进入爱丁堡大学攻读博士学位,研究方向是神经网络。彼时神经网络被业界所鄙夷,连辛顿的导师也认为这玩意没什么实际用途,也没有前途可言。但辛顿却不为所动,对神经网络研究怀有信心,坚持认为能够证明神经网络的价值,这一坚持就是三十多年。

geoffery-hinton.jpg

辛顿年轻的时候有一次搬移取暖器,腰椎间盘滑脱了,此后便一直饱受腰背病痛问题的困扰。近年来,问题更严重了,大多数时候,他需要平躺着以缓解疼痛,这意味着他不能开车,也不能坐飞机,甚至在实验室里会见学生时,也要平躺在办公室的折叠床上。身体上疼痛的折磨带给辛顿的打击还不如学术研究被冷漠那么大。早在1969年,明斯基在《感知机》一书中就对多层感知机下了定论,给后来的神经网络研究盖戳:“多层感知机不会有发展前景,因为世界上没人可以将多层感知机训练得足够好,哪怕是令它可以学会最简单的函数方法。” 单层感知机能力有限,连“异或”这种基础的分类问题也实现不了,而多层感知机又没有可用的训练方法,等于说神经网络的研究方向是死路一条。神经网络在业界被认为是学术异端,没有人相信它可以成功,因此一般学生在选择导师的时候都谨慎绕开神经网络,一时间辛顿甚至都招不满研究生。

1983年,辛顿发明玻尔兹曼机,后来,简化后的受限玻尔兹曼机被应用于机器学习,成为深度神经网络的层级结构基础。1986年,辛顿提出适用于多层感知机的误差反向传播算法(BP),这一算法奠定了后来深度学习的基础。辛顿每隔一段时间都能发明出新东西,而他也坚持写了两百多篇神经网络相关的论文,尽管这些论文不被待见。到了2006年,辛顿已经积累了丰富的理论和实践基础,而这一次,他发表的论文将改变整个机器学习乃至整个世界。

辛顿发现,拥有多个隐藏层的神经网络能够具有自动提取特征学习的能力,相比传统的手工提取特征的机器学习更有效果。另外,通过逐层预训练的方式可以降低多层神经网络的训练难度,而这解决了长期以来多层神经网络训练的难题。辛顿将他的研究成果发表在两篇论文中,而当时神经网络一词被许多学术期刊编辑所排斥,有些稿件的标题甚至因为包含“神经网络”就会被退回。为了不刺激这些人的敏感神经,辛顿取了个新名字,将该模型命名为“深度信念网络”(Deep Belief Network)。

二 感知机

其实神经网络的研究可以追溯到上世纪四十年代。1940年,17岁的沃尔特·皮茨在伊利诺伊大学芝加哥分校结识了42岁的教授沃伦·麦卡洛克,一见如故,便加入了后者的研究项目:尝试用神经元网络建立一个在逻辑运算基础上的机械性的大脑思维模型。他们用逻辑运算来抽象人类大脑的思维模型,提出了“神经网络”(Neural Network)这一概念,而神经元是神经网络中的最小信息处理单元;并且他们将神经元的工作过程抽象简化成一个非常简单的逻辑运算模型,后来这个模型被命名为“M-P神经元模型”,以他们两姓名的首字母来命名。

在这个模型中,一个神经元会接受过个来自于其他神经元传递过来的输入信号,不同的输入信号的重要性有差异,这种差异就通过连接上的“权重”(weight)大小来表示,该神经元将所有输入值按照权重加权求和,再将结果跟神经元的“激发阈值”(Threshold)进行比较,以决定是否对外输出信号。

m-p-model.png

“M-P模型”足够简单直接,而且可以通过符号逻辑来模拟实现,人工智能专家以该模型为基础,构建了神经网络模型,用来解决机器学习任务。这里简单说明下人工智能、机器学习和深度学习的关系:人工智能就是使用计算机技术来实现人类智能的技术,在一般教材定义为研究与构建智能体。智能体就是 Intelligent agent,或简称 agent,它通过模仿人类思维和认知来解决特定任务或通用任务,解决特性任务的智能体被称为弱人工智能,或狭义人工智能(ANI),而解决通用任务的智能体被称为强人工智能,或通用人工智能(AGI)。机器学习是人工智能的一个分支,它通过数据进行学习并改进系统。而深度学习则又是机器学习的一个分支,它使用神经网络技术进行机器学习。

1957年,康奈尔大学心理学教授罗森布拉特在IBM计算机上模拟实现了一个神经网络模型,他称之为“感知机”(Perceptron)。他的做法是将一组M-P模型神经元组合在一起,可以用来训练并完成一些机器视觉模式识别方面的任务。一般来说,机器学习有两种任务:分类和回归。分类问题是判断数据是哪一类的问题,比如识别图像是猫还是狗;而回归问题是根据一个数据预测另一个数据的问题,比如根据人的图像预测其体重。感知机解决的是线性分类问题。以《智慧的疆界》书中对感知机工作原理的举例来解释:

假设任务目标是自动识别阿拉伯数字,待识别的数字是将手写或印刷的各种形式的数字,将数字通过扫描后存储在14*14像素大小的图片文件中。首先,要准备类似下图的训练集供机器学习用。训练集即训练数据集,是专门提供给计算机学习使用的数据集,它不仅是一组图片之类的数据,还会由人工事先标注告诉机器这些图片数据代表的数字是什么。

numbers-datasets.jpeg

然后,我们要设计一种数据结构,以便机器可以存储并处理这些图片。对于14*14的灰度数字图片,可以将黑色像素用1来表示,白色像素用0表示,介于黑白间的灰度像素根据其灰度强度用0-1间的浮点数表示。如下图所示对该图可以转换成一个二维张量数组:

number1-represent.jpeg

而机器能够识别出图片中的数字是什么,主要是找到了该图片表示某个数字的特征。对于人类来说,对于识别这些手写体数字很容易,但我们很难解释这些特征是什么。机器学习的目标就是要提取出这些训练集中图片表示数字的特征,根据M-P模型,提取特征的方法就是选择对图片各个像素值进行加权求和,根据训练集中的样本图片和标注数据的对应结果来计算每个像素对应各数字的权值:如果某一个像素具有很负面的证据说明该图片不属于某个数字的话,就把该像素对应该数字的权值设置成负数,相反如果一个像素具有很正面的证据说明该图片属于某个数字,那么该像素对应该数字的权值设置成正值。比如对于数字“0”的图片中间点的像素不应该有黑色(1)像素,如果出现了则表明该图片属于数字0为负面证据,就降低该图片是数字0的概率。这样,经过对数据集的训练和校准,就可以得到14*14(=196)每个像素对应0-9各数字的权重分布。

我们再将每个数字的分类过程转换成一个M-P神经元,每个神经元都有196个像素输入,每个输入与该神经元之间的权重值由训练得到,这样就构成了一个10个神经元、196个输入以及它们之间1960个带权重的连接线组成的神经网络,如下图示:(一般在神经网络中,会将阈值转换成偏置bias,称为求和项的一项,简化运算过程。)

perceptron-1.png

不过,在实际情况中,有些手写字体存在模棱两可的情况,可能会导致加权求和后,出现两个或两个以上的神经元被激活。因此感知机在实现时引入了激活函数的设计,如下图中的Softmax就是一种激活函数,该函数会对求和值进行处理,抑制概率小的、增强概率大的数字分类。

perceptron-2.png

罗森布拉特又在两年后制造了世界第一台硬件感知机”Mark-1”,该感知机可以识别英文字母,在当时可是引起了巨大轰动。美国国防部和海军军方也注意到了,并给与了大量的资金支撑,罗森布拉特对感知机的自信也达到顶点,甚至有记者问“有没有感知机做不到的事情”,罗森布拉特的回答是“爱、希望、绝望”。罗森布拉特的名气越来越大,而张扬的性格也导致他四处树敌,其中最有名的是人工智能的另一位巨头马文·明斯基。明斯基是达特茅斯会议的组织者,也是人工智能的奠基者之一。1969年,他出版了《感知机》一书,该书明确指出了感知机存在的缺陷。首先是通过数学方法证明了感知机无法处理异或等非线性分类问题,而后又证明了多层感知机的复杂度导致连接数据急剧膨胀而没有合适的训练方法。明斯基在该书出版当年获得了第四届图灵奖,巨大的声望让他对感知机的判断给神经网络研究判了死刑。连接主义备受打击,而符号主义的研究则成为人工智能的主流。

人工智能领域有两大流派:连接主义和符号主义,有点像武侠小说中的剑宗和气宗,长期以来一直互相竞争。连接主义通过模拟人类的大脑构建神经网络,将知识存储在大量的连接中,基于数据学习来发展人工智能。而符号主义则是认为知识和推理都应该用符号和规则来表示,即大量的“if-then”规则定义,来产生决策和推理,基于规则和逻辑来发展人工智能。前者的代表是神经网络,后者的代表是专家系统。

三 深度学习

随着感知机的失败,政府对人工智能领域的投入减少,人工智能进入了第一次寒冬期。而到了八十年代,以专家系统为代表的符号主义成为人工智能的主流,引发了人工智能的第二波浪潮,而神经网络研究被冷落。前文说到,只有一个人还在坚持,那就是杰弗里·辛顿。

辛顿在前人的基础上,先后发明了玻尔兹曼机和误差反向传播算法,辛顿在神经网络领域的开创贡献给这个领域带来了生机,虽然从上世纪八十年代到本世纪初人工智能领域的主流仍然是知识库和统计分析,神经网络的各项技术也开始突破,其中代表性的如卷积神经网络(CNN)、长短期记忆网络(LSTM)等。到了2006年,辛顿提出深度信念网络,开启了深度学习时代。

深度学习所对应的神经网络模型称为深度神经网络,这是相对浅层神经网络而言的。对于浅层神经网络而言,一般只有一个隐藏层(或称中间层),加上输入层和输出层,一共就三层。而深度神经网络的隐藏层则不止一层,对比两种神经网络:

ndnn-dnn-compare.png

深度学习之前人们一直聚焦于浅层神经网络的原因是神经网络层数的增加会导致训练难度增加,一方面缺乏足够的算力支撑,另一方面也没有很好的算法。而辛顿提出的深度信念网络则使用误差反向传播算法并通过逐层预训练的方式来解决这一训练难题。在深度信念网络之后,深度神经网络成为机器学习的主流模型,当前热门的GPT、Llama等大模型都是由一种或多种深度神经网络构建而成。

对于深度神经网络的理解可以参考上文感知机原理的介绍,将深度神经网络看成是多层多个神经元的组合,由前文可以了解,每一层输出结果跟权重、偏置和激活函数有关,而对于深度神经网络的输出还跟层数等数值相关。在深度神经网络中,这些数值可以分为两类,一类是层数、激活函数、优化器等,称为超参数(hyperparameter),它由工程师设定;另一类是权重和偏置,称为参数(parameter),它是在深度神经网络训练过程中自动得到的,寻找到合适的参数就是深度学习的目的。

deep-learning-1.png

但问题是,一个深度神经网络包含了海量的参数,而且修改一个参数将影响其他的参数行为,因此如何找到这些参数的正确取值是个难事。我们要找出参数正确取值,并让模型能够准确输出,那就需要有一个方法能够衡量模型输出与期望输出的差距。因此深度学习训练中使用损失函数(loss function)来衡量,损失函数也被称为目标函数或代价函数。损失函数通过比较深度神经网络的预测值与真实目标值,得到损失值,来表示该神经网络模型在这个训练样本上的效果好坏。

deep-learning-2.png

深度学习的方法是将损失值作为反馈信号,来对参数进行微调,以降低当前样本训练的损失值。实现这种调节的便是优化器,它来实现如梯度下降等优化算法,通过反向传播方式来更新各层神经元节点的参数。

deep-learning-3.png

一开始会对神经网络的参数进行随机赋值,输入一批训练数据,经输入层、隐藏层到输出层,得到网络的预测输出后,根据损失函数计算损失值,这是前向传播过程;然后从输出层开始,反向沿着每一层计算参数的梯度,直到输入层,并根据梯度使用优化算法更新网络的参数,这是反向传播过程。神经网络每处理一批训练样本,参数都会向正确的方向微调,损失值也会减小,这就是训练循环。训练循环足够次数,就可以得到使损失函数最小化的参数,这样就可以得到一个好的神经网络模型。

当然,实际的深度学习过程比这个要复杂得多,这里只是简要介绍下大概过程。

2012年时,辛顿带领他的两名学生 Alex Krizhevsky 和 Ilya Sutskever,开发了AlexNet 神经网络,参加 ImageNet 图像识别大赛,结果获得冠军,准确率远远高出第二名。随后辛顿和他的学生成立了DNNResearch公司,专注于深度神经网络的研究。这家公司没有任何产品或资产,但 AlexNet 的成功吸引了几大互联网巨头。2012年冬天,在美国加州和内华达州交界的太浩湖边,一场秘密的竞拍正在进行:被拍卖的对象是刚成立不久的DNNResearch,买家分别是谷歌、微软、DeepMind和百度。最后在谷歌和百度还在竞相抬价时,辛顿叫停了拍卖,选择以4400万美元卖给谷歌。2014年,谷歌又将DeepMind收入囊中。2016年,采用经典蒙特卡洛树搜索和深度神经网络结合的AlphaGo战胜了李世石,次年,又战胜了世界围棋排名第一的柯洁,AlphaGo将人工智能和深度学习推向了一个新的高潮。

四 大模型

2015年,马斯克、Stripe的CTO Greg Brockman、YC创投CEO Sam Altman 和 Ilya Sutskever等人在加州的Resewood酒店里会面,商议创建一家人工智能实验室,以对抗大型互联网公司对人工智能技术的控制。接下来,Greg Brockman 又从谷歌、微软等公司邀请来一批研究人员,成立新的实验室,并命名为OpenAI。Greg Brockman、Sam Altman 和 Ilya Sutskever 分别担任 OpenAI的董事长、CEO 和 首席科学家。

马斯克和 Sam Altman 对 OpenAI 最初的设想是非营利组织,将人工智能技术面向所有人开放,以此对抗大型互联网公司控制人工智能技术而带来的危险性。因为深度学习人工智能技术正在爆炸式的发展,谁也预料不到这项技术在未来会不会形成对人类的威胁,而开放可能是最好的应对方式。而后来2019年OpenAI为了融资发展技术而选择成立盈利子公司,并闭源其核心技术,这是后话。

2017年,谷歌的工程师发表了一篇论文,名为《Attention is all you need》,在这篇论文中提出了 Transformer 神经网络架构,该架构的特点是将人类的注意力机制引入到了神经网络中。前文说到的图像识别是深度学习中的一种场景,图像数据是离散数据,之间没有关联。而现实生活中还有另外一种场景,就是处理时序型数据,比如文本,文字的上下文是有关联的,还有语音、视频等,都是时序型数据。这种时序型数据叫序列(sequence),并且实际任务中往往是将一个序列转换成另外一个序列,比如翻译,将一段中文翻译成一段英文,还有机器人问答,将一段问题转换成一段智能生成的回答,因此要用到转换器(Transformer),这也是Transformer 名称的由来。前文说到,一个神经元的激发是由它连接的输入数据加权和决定的,权重代表了连接的强度。在时序数据中,每个元素的权重也是不一样的,这跟我们日常生活的经验是一致的,比如看下面这段话:

研表究明,汉字的序顺并不定一能影阅响读,比如当你完看这句话后,才发这现里的字全是都乱的。

不仅是汉字,英语等其他人类语言同样如此。这是因为我们的大脑会自动判断句子里字词的权重,在冗杂的信息里抓中重点,这就是注意力Attention。谷歌工程师将注意力机制引入到神经网络模型中,用于自然语言处理,使得机器可以“理解”人类语言的意图。随后2018年,OpenAI基于Transformer架构发布了GPT-1,2019年发布GPT-2,2020年发布GPT-3,2022年底基于GPT-3.5发布了ChatGPT人工智能问答程序,它的对话能力让人震惊,人工智能也向着AGI方向迈进了一大步。

GPT全称是 Generative Pre-trained Transformer, Generative 生成式表明它的能力,能够生成新内容,Transformer 是它的基础架构,而中间的 Pre-trained 表明它的训练方式是预训练。为什么叫预训练呢?这是因为,从AlexNet开始,人们为了取得更好的效果,在神经网络训练中开始采用更大的数据和更多的参数,而这也意味着训练的资源和耗时也越来越大。这种成本对于训练特定任务有些高,且不能与其他神经网络共享,有些浪费。因此,业界开始采用一种预训练+微调的方式来训练神经网络模型,即先在较大的数据集上完成通用大模型的训练,然后在具体的任务场景用较小的数据集完成模型微调。ChatGPT采用了基于人类反馈的强化学习(Reinforcement learning from human feedback, RLHF)来进行预训练微调,分成三个步骤:第一步,预训练一个语言模型(LM);第二步,收集问答数据并训练一个奖励模型(Reward Model,RM);第三步,用强化学习(RL)方式微调语言模型(LM)。这个奖励模型包含了人工反馈,因此训练过程称为RLHF。

chatgpt-training-steps.jpeg

用户在使用ChatGPT过程中,除了赞叹它的准确度外,还被多轮对话的能力所折服。根据神经网络的底层探析,我们看到每次推理过程是从输入经各神经元加权和激活到输出,是没有记忆能力的。而ChatGPT之所以多轮对话效果好,是因为它在对话管理中使用了Prompt Engineering的技术。

对于ChatGPT等大语言模型来说,它的输入是经过将一串文字转换的token,而大模型因为计算效率和内存限制,一般会设计固定的上下文窗口,限制输入token的数量。文本首先会被分词器(tokenizer)分词,并通过查表编号,然后embedding到矩阵中变成高维空间向量,这是文本向量化的过程,如下图所示。

tokenization-embeding.webp

由于token数的限制,因此要在有限的上下文窗口中将更全面的信息告诉大模型,就这需要用到Prompt Engineering提示工程技术。提示工程利用一些策略来优化模型输入,以便让模型产生更符合期望的输出。

ChatGPT的成功背后是以GPT为代表的大模型的技术演进,OpenAI相信大力出奇迹,不断扩大GPT的参数,GPT-1模型参数有1.17亿,GPT-2模型参数提高到了15亿,GPT-3达到了1750亿,而GPT-4的模型参数据称有1.8万亿。更多的模型参数也就意味着需要更大的算力来支持训练,OpenAI因此总结了“Scaling Law”,称模型的性能与模型大小、数据量和计算资源有关,简单的说就是,模型越大、数据量越大、计算资源越大,模型的性能就越好。强化学习之父Rich Sutton在他的文章《苦涩的教训》(the Bitter Lesson)中也表达了类似的观点,他回顾人工智能近几十年的发展路程,总结说短期内人们总是试图通过构建知识来提升智能体的性能,但长期看强大的算力才是王道。

大模型的能力也由量变转为质变,谷歌首席科学家Jeff Dean称它为大模型的“涌现能力”(Emergent abilities)。市场看到了这个机会,一方面,各大厂商在大模型投资上呈现军备竞赛之态,另一方面,大模型的开源生态也如火如荼。

五 Hugging Face

2016年,法国人Clément Delangue、Julien Chaumond和Thomas Wolf成立了一家公司,起名为Hugging Face,并以该emoji图标为公司Logo. Hugging Face最初开发面向年轻人的智能聊天机器人,而后他们在训练模型的过程中开发了些模型训练工具并将它们开源,后来他们甚至调转重心来做后者,这种看似“不务正业”的做法却将他们带入了一个新的赛道,成为了深度学习领域不可或缺的角色。

硅谷有很多企业都是在副业上做出成就,比如Slack原来开发游戏,公司团队分布多地,在运作过程中开发了一款交流工具结果不小心火了,就是Slack。而Hugging Face的转向也类似,也是为了解决自己的痛点,2018年,谷歌发布了大模型BERT,而Hugging Face的员工便用了他们熟悉的Pytorch框架实现了BERT,将模型取名为pytorch-pretrained-bert,并将它开源到了GitHub。后来在社区的帮助下,又引入了GPT、GPT-2、Transformer-XL等一批模型,该项目便更名为pytorch-transformers。深度学习领域一直存在着两大框架Pytorch和TensorFlow之间的竞争,而研究人员为了比较两个框架的优劣,经常在两个框架间切换,因此该开源项目又增加了两个框架间的切换功能,项目名称也改成了Transformers。Transformers也成了GitHub上增长最快的项目。

Hugging Face继续开发了并开源了其他一系列的机器学习工具:Datasets、Tokenizer、Diffusers……这些工具也规范了AI开发的流程,在Hugging Face之前,可以说AI开发以研究人员为主,没有一套规范的工程化方法,Hugging Face则提供了完善的AI工具集并建立了一套事实标准,也使得更多的AI开发者甚至是非AI从业者可以快速上手并训练模型。

huggingface.png

接着,Hugging Face又基于Git和Git LFS技术推出了托管模型、数据集、AI应用的Hugging Face Hub,到目前为止,平台上已经托管了35万模型、7.5万数据集和15万个AI应用示例。托管并开源模型和数据集,并建立全球的开源仓库中心这项工作富有创意且意义深远。上文提到,预训练+微调的方式促进了神经网络训练资源的共享,而Hugging Face Hub则更进一步,让AI开发者可以轻松复用全世界最先进的成果,并在此基础上添砖加瓦,让人人使用AI、开发AI的AI民主化成为可能。Hugging Face也被称为是机器学习领域的GitHub,或如他们的Slogan所言:构建未来的AI社区。我之前写过两篇文章,一篇《改变世界的一次代码提交》介绍Git,一篇《从零到百亿美金之路》介绍GitHub,而Git、GitHub、Hugging Face,我觉得它们之间存在某种传承,一种改变世界构建未来的黑客精神的传承,这也是促使我写这篇文章的原因之一。

六 后记

在快要写完本文时,我看了辛顿最近在牛津大学做的一次演讲。在演讲中,辛顿介绍了人工智能领域的两大流派,一种辛顿称为逻辑方法,即符号主义;另一种他称之为生物方法,即模拟人类大脑的神经网络连接主义。而事实证明了生物方法明显战胜了逻辑方法。神经网络是模拟人类大脑理解而设计的模型,大模型也像大脑那样的工作和理解。辛顿认为,超越人脑的人工智能在未来会出现,而且会比我们预测的时间快得多。

附1 大事记

1943年,麦卡洛克和皮茨发表“M-P神经元模型”,用数理逻辑解释并模拟人脑的计算单元,并提出神经网络这一概念。

1956年,“人工智能”一词首先在达特茅斯会议上被提出。

1957年,罗森布拉特提出“感知机”模型,并在两年后成功制造能够识别英文字母的硬件感知机Mark-1.

1969年,明斯基发表《感知机》,书中指出的感知机缺陷沉重打击了感知机乃至神经网络的研究。

1983年,辛顿发明玻尔兹曼机。

1986年,辛顿发明误差反向传播算法。

1989年,杨立昆(Yann LeCun)发明“卷积神经网络”(CNN)。

2006年,辛顿提出深度信念网络,开启深度学习时代。

2012年,辛顿和他的两个学生设计AlexNet在ImageNet大赛中以绝对优势获得冠军,深度学习被业界所重视。

2015年,Google收购的DeepMind公司推出AlphaGo,2016年战胜李世石,2017年战胜柯洁。OpenAI成立。

2016年,Hugging Face成立。

2017年,Google发表Transformer模型论文。

2018年,OpenAI基于Transformer架构发布了GPT-1。Hugging Face发布 Transformers 项目。

2019年,OpenAI发布GPT-2。

2020年,OpenAI发布GPT-3。Hugging Face推出Hugging Face Hub。

2022年,OpenAI发布ChatGPT。

附2 参考资料

书籍:

《智慧的疆界:从图灵机到人工智能》周志明(著) 机械工业出版社 2018年10月

《深度学习革命》凯德·梅茨(著) 杜曙光(译) 中信出版社 2023年1月

《Python深度学习》(第2版) 弗朗索瓦·肖莱(著) 张亮(译) 人民邮电出版社 2022年8月

《深度学习入门:基于Python的理论与实现》斋藤康毅(著)陆宇杰(译)人民邮电出版社 2018年

《深度学习进阶:自然语言处理》斋藤康毅(著)陆宇杰(译) 人民邮电出版社 2020年10月

《这就是ChatGPT》 斯蒂芬·沃尔夫拉姆(著) WOLFRAM传媒汉化小组(译) 人民邮电出版社 2023年7月

《生成式人工智能》丁磊(著)中信出版社 2023年5月

《Huggingface自然语言处理详解》李福林(著)清华大学出版社 2023年4月

文章:

《神经网络入门》阮一峰

《2012,改变人类命运的180天》远川研究所

《GPT家族进化史》MetaPost

《Transformer - Attention is all you need》知乎

《预训练语言模型的发展历程》 知乎

《提示工程指南》

《ChatGPT 背后的“功臣”——RLHF 技术详解》

《ChatGPT大模型技术发展与应用》

《The Bitter Lesson》 Rich Sutton

《专访HuggingFace CTO:开源崛起、创业故事和AI民主化》

❌