先服务好 15%,接着服务所有人
自从揽下所有与 MegEngine 文档有关的活后,一边小步快走,做着各种尝试,一边思考着各种与文档(Documentation)有关的问题。单兵作战的时候拥有绝对的自由,可以经常在脑海中做各种假设、对比与改进,觉得想得差不多了就试一试,行之有效的话就简单做个总结。折腾一段时间后,勉强称得上是完成了这方面认知的升级。前几天发现一个有趣的现象:新版本文档中的一些设计思路,已经被同行进行了借鉴。这其实是好事,类似于科研工作者从早期阅读别人的文章、复现别人的实验开始,不断地尝试、思考、受挫、挣扎、反思、打磨,慢慢地已经做出了被人关注和认可的东西。从这个视角来看,做科研和做产品的相关性还挺强的。跑得快固然重要,但可别指望别人家的产品给你挂个 Acknowledgement 表示感谢。
我很乐意看到大家一起去思考和实践,毕竟一个人跑得快,一群人才能跑得远。
远古版本的 MegEngine 文档长这样:https://megengine.org.cn/api/0.6/zh/api.html.
用现在的眼光来看,不论是样式内容设计,还是背后的 DevOps 流程,绝对是严重过时的,因为在此之前我们调研的对象不够丰富,也没能产生创新性的思路。一个比较严重的失误是完全将文档看成是互联网产品的一部分。模仿,直接决定了上限;缝合,直接拉低了下限。早期这样做实在是无奈之举,能比较不受阻碍地解决文档从无到有的燃眉之急,“搞出蘑菇弹,挺直腰杆子!” 但在这个过程中忽视了核心问题:文档作为一类特殊的软件,究竟如何为特定的用户群体提供服务与体验,最终发挥作用?胡乱替用户做假设是一种傲慢的行为,要搞清楚真正的需求,离不开实地考察;要避免无意义的忙碌,就需要停下脚步,多回头看看。
我们可以用一张表来表示体系完整的文档应该覆盖到哪些情景:
学习阶段 | 使用阶段 | |
---|---|---|
实践步骤 | 教程 🤔 | 指南 📖 |
理论知识 | 解释 📝 | 参考 📚 |
- 教程(Tutorial):指导读者通过一系列步骤完成项目(或有意义的练习)的课程;
- 指南(Guide):引导读者完成解决常见问题所需步骤的指南(How-to 系列);
- 参考(Reference):用于 API 查询和浏览,方便网络引擎检索的百科全书;
- 解释(Explaination):对于特定主题、特性的概念性说明。
在 MegEngine 文档中,新手入门(Getting started)板块负责陪伴用户度过初期的学习阶段,里面主要是各种教程,并没有专门划分出纯粹的理论解释部分,因为我不希望强行引入 “必须事先掌握” 的知识。通常这些内容只会在某些地方昙花一现,至于是否要深入了解,交给用户去判断,无论如何选择都不会影响后续的学习。一种我不太看好的做法是一开始便专门用很多的篇幅去讲纯粹的理论知识(很少有人能讲好),市面上的一些 ML/DL 培训课程喜欢刻意强调数学的重要性,比如在进行正式课程的授课之前总要先讲几个课时的微积分、线性代数、概率论,最离谱的讲了大半本凸优化。我觉得喜欢搞这种形式的老师通常在意的是如何卖弄自己的学识而非如何传授自己的知识,即目的不在于问 “我解释清楚了没有”,而是问 “你听明白了吗?反正我是懂的。” 使用填鸭式的教育方式,压力就来到了学习者这边,我不认同,也不期待文档中出现这样的教程。优秀的文档不应该变成教科书或习题集。
写出针对特定人群的教程很容易,写好具有泛化能力的教程很难。实际上,旧版本的 MegEngine 文档提供了一套非常完整的从数据加载到训练、推理部署(以及量化)的教程,讲师们还专门录制了视频,但实践起来得到的反馈并不理想——大概半年前,我在为中国农业大学的几名研究生提供 MegEngine 入门支持时,她们问我有没有相关的教程,我把当时官网的教程给了她们,被吐槽说帮助并不大。后来意识到这些明面上的“教程”其实只能算是零零碎碎的代码示例(Example), 并没有提供统一的学习视角,也没能形成 i+1 式的循序渐进的讲解体系。 为什么没能这样写教程呢?一是因为当时没有充足的时间和人力来思考如何做好这件事情,大家忙着完成从无到有的任务;二是因为 MegEngine 是旷视自研的工业级框架,大部分时候得到的反馈都来自研究院那边非常有经验的高级研究员,或者是业务线那边的同事,炼丹时基本上没有人需要阅读入门教程,顶多看看快速上手页面对 API 有个大致的概念,日常吐槽的是特定情况下精度、性能的问题,或者一些高级特性用不明白。
如今我认同这样一个观点:文档中就应该提供为零基础用户服务的教程,这是用户基数最大的群体。毕竟高级用户不是凭空产生的,回想当年我们学习某个知识迷迷惑惑的阶段,是不是突然看到一篇好文章,里面的只言片语或许就让自己茅塞顿开了?早期我们寄希望于社区用户,但得到的几篇教程质量都不达标,我甚至一度怀疑是不是因为国内开发者写博客的门槛太低了导致平均质量已经降下来了。后来觉得还是要从自己身上找原因,Pytorch 最初的教程是核心开发者 Soumith Chintala 自己写的,如今由 Stanford 的 Justin Johnson 进行了补充,MegEngine 也应该有一套官方自己的入门教程。半年前 MegEngine 的官网架构中,教程、文档、API 是三个独立开的东西,大家都在摸着石头过河。而现在的文档,是一个特别大的概念(在上面的表格中你应该了解过了),有充分的发挥想象力的空间,处处可改进,最终要实现的目标是:大家香,才是真的香。
回到给农大学生做支持这件事上,我总不能和对方说:“你们先去把 Pytorch 教程看一遍,然后就会用 MegEngine 了。” 然后露出一个尴尬又不失礼貌的微笑吧?只好让她们先去看 Andrew 的 DL 视频,看不懂的地方就答疑,自己琢磨着开始写 MegEngine 版本的教程作为框架侧的练习材料。这时遇到了第二个难点: 人的思维一旦变复杂,很难还原到初学者视角,我们容易把自己的常识误以为是别人也已经掌握的知识,从而在写教程的时候写出很抽象的内容。 纯粹以个人理解视角写出来的东西也不能算得上是面向大众的教程,很多时候只能说是个人笔记。如果读过糟糕代码风格 + 只言片语的算法题解博客,又接触过良好代码风格 + 切中要点解释的算法题解博客,感触应该会比较深。好在我个人比较幸运,之前曾有过课程助教的经验,平日里也习惯了多角度观察,所以切换视角这个事情对我来说已经不是那么困难。基本上白天写完一篇教程,晚上就会找到些写得不友好的地方。
另外要注意的是,获取教程受众的反馈很重要。 国人比较谦虚,能力越强的人越是喜欢自己把问题研究清楚,等到视野开阔理解能力变强后,就算教程存在着瑕疵,也被过往经验自动完善掉,很难意识到是教程本身存在着不合理的地方。我一直和几个研究生说 “如果觉得没写清楚的地方就多反馈,不要怕问蠢问题。这不会麻烦到我,而是在互帮互助。” 实际上大家的理解能力都在线,真正焦虑的反而是我,怕没讲明白学生却不好意思说,宁可自己花额外的时间去消化,这种情况应该被视作潜在的教学事故。
不是所有人都是天生的教育工作者,Andrew 在 Linkedin 上有一篇很热门的文章,讲的是如何通过刻意练习,让自己成为更受学生欢迎的老师。即使是同样的材料,给不同的讲师去教授,起到的效果也可能截然不同。比如我见到过一些老师用 CS231N 的课件去给学生讲课,但完全体会不到 Andrej Karpathy 讲课时的感觉。再举个例子,直到现在,我还是会偶尔想起当年上算法分析课时的那名教授,他主要研究计算机网络方向,和我们说因为写文章要用到算法,最近开始研究算法导论了,就顺带申请教授这门课,希望和大家一起进步… Ohh 我亲爱的老师,时间就是性命。无端的空耗别人的时间,无异于谋财害命。
一定要使用自己熟悉的教学材料,而不是用大家都觉得好的材料去给学生做临时翻译,一是没有原汁原味的体验,二是容易让其他人对教学材料和原始作者形成抵触和误解。 在写零基础教程系列的时候,我一直在研究如何能够让它变得更加 Beginner friendly, 一个用户说要不要直接用李沐的《动手学深度学习》或者是 Coursera 上面 Deep Learning Specialization 的编程作业,我认为这些材料都很好,但不适合放在 MegEngine 文档里,决定还是自己搞。最初的几版往往都在加内容,担心举例不够;而最后往往都在做减法,去掉一切当前不用过度关注的数学细节。现在看似是完成大体内容了,而我却只能说差强人意,还需要经过长期的检验和更新,自己也不是什么领域大牛,特别怕写出来的东西误人子弟,早就做好了挨骂的准备。教程最后的问题都倾向于让人去主动思考而不是留客观题,因为我是反对培养做题家的, 在学习新知识的初期,建立基本的直觉和浓厚的兴趣是重中之重,至于如何启发思考,把握内容的节奏感和分寸感(甚至像艺术品一样去打磨它们),则是更进阶的话题。好消息是,我们如今能够从研究实习生那儿获得教程的支持,其实任何人都可以尝试提供写教程的思路。
从学习阶段进入使用阶段后后,教程的使命就结束了,用户指南和 API 参考成为了文档中经常被光顾的地方。这就好像一个神经网络模型已经得到了充分的训练,接着我们只需要用训练好的模型去执行推理任务即可,那些费时良久的反向传播和参数优化时光都变成了过去式。除了用户指南,MegEngine 文档中还提供了开发者指南,因为 MegEngine 是一个开源项目,我们希望社区中的开发者能够借助文档中的信息更有效地参与进来,为 MegEngine 的发展作出贡献。指南的写法,和教程还真有挺大的区别。可惜的是由于各种原因,到现在为止,MegEngine 的文档也只是完成了整体拼图中的极小的一块,依旧是“路漫漫其修远兮“,需要“上下求索”。但如今我们有思路啦,找到方向啦,不再是当初摸着石头过河的状态了,并且我坚信它能按照预期设想伴随着 MegEngine 持续发展下去。
前面用了很多篇幅来讲教程,是因为这是前段时间的工作重心,亦对后续的工作有很大启发。然而我们也说过了,教程只是文档世界的一小块拼图,想要更好地理解文档建设的意义和目的,可以有很多理解的视角,这次我选择引用前不久看到的一句话作为解释:
用户通常只会用到软件中 15 % 的功能,而不同类型的用户使用的往往是同一个软件中那不同的 15% 部分。
道理挺容易懂的,以 Microsoft PowerPoint 为例,初级用户往往只需要用到基本的功能,比如图片、文字和页面的编辑;随着需求的改变,有些人研究大纲视图、讲义备注和演讲者模式,有些人研究动画效果,有些人研究如何优雅地插入
从 15% (不用在意这个值是否客观)这个点出发,可以引申出非常多的思考:
- 一个用户在刚接触到新产品时,学习的是最简单的功能,界面的友好程度、功能上手的难易程度等会直接决定用户的留存。但是由于对不同的用户这 15% 是不同的部分,因此需要分别提供对应的入口。而且用户的属性不是一成不变的,作为软件供应商,还要思考如何帮助用户发展自我,从初级萌新转变为高级资深玩家。这个时候,提供不同入口的教程尤其重要。
- 从软件研发的角度来看,知道自己在干什么很重要。或许我们正专注于实现这 15% 的关键特性,脑海中知道其中所有的技术细节。但我们有义务形成这样一种大局观,即我的设计在整个系统中扮演着什么样的角色,它又是怎样深刻地影响到上下游各个环节的,可以理解成系统架构的意识。这个时候,信息流程类文档尤其重要,我开始尝试推广切实可行的 MEP 方案。
- 虽然整个软件系统由很多个所谓的 15% 模块所组成,一些基础设施(底层设施)的 15% 模块是不直接面向用户的,研发过程中不可避免地会面临人员流动的情况。我们如何保障在不破坏原有大体设计思路的情况下进行持续稳定的开发和迭代?如何保障公开接口稳定性?这个时候,设计文档尤其重要,理应存在于开发者指南中。
MegEngine 文档或许已经能够满足某类 15 % 用户的需求了,但这还远远不足够。
我们需要建立这样一个认知, 写文档本质上依旧是在为解放自己的生产力进行铺垫 —— 聪明的程序员都懂得如何偷懒,巴不得能用脚本尽可能地去自动化一切工作。我们可以研发出深度学习框架来简化训推流程,用户也可以基于此提供更加高级的接口或库。但我们往往会忽略这样一个事实:在一个团队中,研发人员的日常绝不仅限于纯粹地去实现代码逻辑功能,很多时候都需要和人交流,大致可以分为以下几种情景:
- 我们要教会使用者如何去用我们开发出来的东西,因此教程必不可少。
- 我们要实现一个东西,因此需要找人讨论实现方案,提供 Proposal / Prototype 实现。其中存在着信息的收集、同步、讨论、达成一致决策的过程,如果不采取任何形式的总结,将来很有可能需要向其他的想要了解技术细节的人解释 “当初为什么要这样做(不那样做)” ,更糟糕的情况是,你可能需要将同样的事情向不同的人解释了一遍又一遍;而人的记忆不是永远可靠的(人脑也不是用来机械记忆的工具),我们无法保证将来总是能回忆起曾经的各种决定。一旦产生元老级别的人员流动,很多未来得及记录的信息就像青春岁月一样飘散在风里了。
- 我们要读懂别人的实现,碰到了困难,然后跑过去问实现者。对方可能不一定有空,但强者往往又表现得十分友善,基本上有问必答,最终会占据掉原本用来安排进行其它工作的时间;当然你也可以询问其他了解这块的人,但口口相传的方式更加无法保障信息的准确性, 我们需要制定流程规范,目的是为了提升整体的工作效率,口头约定并不具有约束效果;
- 随着工作经验的丰富,我们开始指导实习生的日常工作。一些我们觉得属于常识的东西,可能对于新人来说就是全新的事物,尤其是限定于项目中的一些细节,毕竟一些坑可能永远不会在其它项目中遇见。虽然我认同 “该踩的坑终究还是要自己踩一遍”的说法,这样印象会比较深刻。但作为过来人,提供辅助视角作为参考,会更有帮助;甚至我们可以刻意地去设计一些优化过的坑来帮助实习生更好地成长,减少其它的干扰项。
即使是写教程,也会因为我们之前提到的 15% 理论而需要达到 “因材施教“ 的效果。例如有的用户当前只想学习如何训练模型、有的用户只想学习如何推理部署;再细化一些,有的用户只想知道多机怎么用,而有的用户只想搞明白量化的原理。这个时候,文档需要针对这不同的 15% 用户,分别提供最合适的入门教程。比较常见的情况是,用户在弄明白如何去训练一个模型后,有可能开始对模型部署感兴趣了;而对另外一部分的用户来说,可能完全没有阅读过训练教程,只希望能够用现成的模型进行部署。同样地,从学习阶段过渡到使用阶段后,用户更需要的是简明的指南。
文档的另外一个作用是服务于项目本身的开发者,可我们是那么地讨厌写文档,又那么地讨厌别人的项目没好好写文档。这时候不得不对比一下“直接交流”这种形式,很多人都会以口头交流更高效为由拒绝写文档。诚然,相较于打字聊天这种形式,面对面的交流可以和帮助我们保持专注和高效。但如果是频繁地解释已经实现的东西,其长期效益未必高过写文档。毕竟不是所有人都是语言大师,交谈过程中废话的比例有时候会比预想的多得多,想要强行地让别人兼容自己的思维模式,亦或者是做到对他人的兼容,都是极端困难的,需要大量的练习。如果不在正式的交流开始之前拉齐双方的认知,可能聊着聊着发现原来不在同一个频道。
很多时候在一场对话中,我们可能会觉得 “怎么讲了这么久,对方还是不明白?是不是理解能力有问题啊?” ,而对方可能早就在内心吐槽 “这个人讲的是什么东西啊?根本没把东西讲清楚,是不是表达能力有问题啊?他是不是根本不知道我问的是什么?”,总之锅肯定是对方的就没问题啦!与人相处是值得一生去探究的话题,而对话时进行的一些假设其实是我们与生俱来的弱点,对他人抱有严格的期待,而容易忽视掉自己身上不足的部分。不欢而散的情况也是有可能产生的,这其实还算好,更糟糕的情况是为了避免尴尬而不懂装懂,误认为这次交流是有效的,结果又要用更多的交流来填坑,我们真的做到科学高效了吗?
我在与人相处的时候有一个比较常用的小技巧,即借助于对方已经输出的事物来快速寻找共识。怎么理解这句话呢?要想了解一个人,直接问另外一个人 “你觉得这个人怎么样?” 得到的答案难免带点主观,要么被打哈哈糊弄过去;直接去和对方交流呢又会比较花时间,因此我倾向于去寻找并阅读他/她对外输出的内容,比如邮件、演讲、讲座、论文、博客、歌单、视频等等,尝试去构筑对方的认知体系,习惯他/她的表达方式,看能否找到融合点,这个探索过程通常是让人欣喜的,我们也在拓宽自己的可接受范围。应用于文档上面,则演变成我们在讨论技术问题的时候可以交流双方对同一篇文档的理解是否一致,不一致的话则求同存异,最好的情况是大家都得到了新的思路或者是观点,同时文档的内容也能够更新。如果觉得文档写得不好,吐槽文档的内容就是了(即对事不对人),然后一起分析怎样去改进它——只是交流而不做记录的话,很容易遗忘甚至是产生错误的记忆。通过总结文档的形式,让每一次的交流都能以某种形式沉淀下来,长期来看会节省非常多的时间成本,如若帮助到了别人,无形之中可能就放大了之前讨论的价值,推荐大家都试一试这种无情绪压力负担的输出方式。
我还想稍微提一下版式设计方面的细节,这是一种无意识地改善用户体验的途径。在这个创意无处不在的时代,越来越多的人成为设计师。简历、论文、PPT、个人主页、博客、活动海报、给客人的邮件、名片……,处处都在考验你的设计能力。几乎每次提到产品设计时,我都会和人安利 Robin Williams 的 《The Non-Designer’s Design Book》,时刻注意并尝试练习满足 “亲密性”、“对齐“、“重复“和“对比” 四大原则。在文档中体现的比较明显的地方是:我们会经常用到一些视觉样式元素,比如 note/warning 来避免过多的重复,形成局部的对比,否则用户读起来会很累。整个文档的配色风格也需要一致,字体图片的样式、段落的间距,处处都是可供改善的细节。
我还想要强调一下已知常见文档形式的局限性,以及现代化文档的发展趋势。由于历史原因,很多文档要求以纯文本的形式进行提供,这样对文本编辑器友好。但在一些概念解释的情景下,往往是一图胜千言的,因此我们需要主动地拥抱表达形式的变化,尝试用更加新颖的媒体手段进行创作和表达。一种比较好的实践模式是,首先想清楚你想要输出内容的范围,对应的受众有哪些。这个时候可以列举出一个模糊的大纲(可以参考一些写作范式),但不用要求它一开始就在内容上达到很完美的程度。经过一段时间的折腾后,我们应该能够得到最原始的文本,往往只能用于审视内容结构,无法达到直接发布的程度,我们可以根据版式设计的原则去添加合适的图片,正确的配图能帮助读者更好地理解内容。
如今不可忽视的一种内容表达载体的形式是视频,比较常见的做法是在一篇教程中,标题下方是一个嵌入的视频,紧接着是对应的文字脚本或者内容总结,将来的 MegEngine 文档应该也会进行这方面的尝试。在线教育是大势所趋,但可惜的是很多人入场探索,却只学习到了视频的术,认知停留在“我们需要录个像,把人脸放上去让用户感到被重视”的程度。一些人的目的本就是趁着这波红利卷了钱就跑,并不试图进一步明白怎样通过视频更加有效地传递信息,哪里有什么教育的初心?一些不专业的细节处理往往还会往视频中引入过多的噪声,降低信噪比,加大用户的认知成本。比如 PPT 风格的讲解视频内容中最容易被忽视的一个因素是音频的质量,试想一下我们在进行会议的时候,经常会出现一些人都发言不清晰的情况,这个时候恐怕就没有精力去捕捉里面的细节信息了,成本太高;所以糟糕的音质比糟糕的画面更容易让人失去耐心。音频后期技巧还包括去除口水音、去除齿音等技巧,其实如果一个人的普通话不标准,也有可能劝退一些听众。BGM 也很重要,用得好是加分项,用得敷衍就容易折磨观众的耳朵。在细节处不求做的完美,但不能差到拉低了整体的下限。
如果是实拍视频,还需要考虑到摄影(画面)方面的一些细节。比如一些代码演示的视频中,如果出现了敲错代码,或者是讲着讲着发现思路不清晰甚至讲错的情况,请务必重新录制,严谨一些!不要让观众因为你的失误而付出代价。至于视频画面的内容, 3Blue1Brown 的风格是我目前最推荐的用来解释抽象概念的途径,在 MegEngine 的入门教程中也有推荐。我们应该以工业级的标准去打磨我们的视频质量,想想这个镜头想要表达什么,需要用到什么样的视觉效果才是最自然的,节奏感对不对,是不是这个情节可以放在后面作为压轴?画面中的留白、实际内容比例也会直接影响到观众的体验… 只有我们将可能存在的干扰项优化到极致了,用户才能专注于感知核心内容的表达。直接鼓捣出一个视频是很困难的,得一步一个脚印慢慢走。而我们的写作,其实就是积累原始表达内容的方式,写作的过程中我们的头脑会一直在思考,刚开始可能会觉得这让人痛苦,但久而久之,你会发现找个地方安静地写作,是浮躁世界静下心来与自己对话的一种方式。
另外,将来的人们在发布内容时,一定会做更多在交互性上面的尝试,正如 https://distill.pub/ 现在所做的(编辑最近写了一篇《Distill Hiatus》,认为大多数类型的文章的未来可能是自我出版,值得一读)。从把一件事情做完,到把一件事情做好,基本上会慢慢地走上一条未曾设想的道路,我们在不断地拓展自己的认知边界,最终完成意识形态上的升级,将来在做其它事情的时候,或许能得到一定的启发。