过去这五年

本博客已经迁移到新域名 linghao.io。请前往新博客阅读本文:https://linghao.io/posts/five-year-summary-2013-2018/

这是一篇对过去五年的忠实记录。有成功也有失败,有欢笑也有泪水。作为一篇很大程度上为自己而写的回忆录,倘若对你有些许启发,便是无憾。

本文谢绝转载。

2013 - 2014

在高中准备了两年信息学竞赛却没能拿到全国赛(NOI)资格之后,我通过计算机特长生夏令营保送来到了复旦。那时保送生有一个选择是提前半年过去读预科,上三门数学课并进行 ACM 训练。而我并不打算在大学继续参加算法竞赛,同时也觉得利用高三的时间提高英语能力更为重要,就没有选择去读预科。那一年我的自主学习成果还是不错的:英语水平突飞猛进;掌握了微积分和线性代数的基础知识;开始学习 Python 并尝试写出了一些自动化脚本;还在无意中浅尝了机器学习这一领域。

与那段时光相比,我在复旦的第一年可以说是浑浑噩噩。人生第一次离家独立生活;从三线城市来到一线城市被新鲜事物包围而感到迷失;对 CS 的学术/职业道路缺乏概念,看不清未来在哪;复旦特色的大类加通识教育导致课表上堆满了不感兴趣的课;…… 种种因素叠加起来,我的大一虽然过得还算开心,但在个人发展上几乎一事无成。不仅结结实实吃了三个 C 档成绩使得 GPA 彻底不能看,还经历了许多的半途而废:加入了一打感兴趣的社团却都没有坚持参加,一时兴起买的树莓派在新鲜感褪去之后也惨遭废弃等。单从结果来看,大一做的唯一正确的选择居然是选了一门网球课,从而跟之后成为室友兼良师益友的余神混熟,并且连带着认识了戍爷和 Farter 这两位未来室友。

第二个学期的时候,在余神和戍爷的推荐下,我加入了复旦学生网(STU)这个神奇的组织。我在那里得到了技术启蒙,也结交了好几位至今仍然志同道合的朋友。那时的我简单地满足于写出能工作的脚本,在 STU 与众不同的报名环节还提交了参考朋友代码开发的 Pixiv 图片批量下载工具作为自信作。然而刚加入 STU 没多久,余神就随手表演了如何闭着眼睛写出一个选课信息爬虫。当我还在依葫芦画瓢使用 urllib2 的时候,余神早已建立了使用 requestspyquery 等工具编写爬虫的最佳实践,并且对 HTTP 等原理层面的知识也已经有了切实的掌握。我完全折服于能够将业务逻辑级别的代码写得如此熟练和优美的这种能力,也认识到了自己在技术上是何等的无知。在同一时期,戍爷和 QS 学长的前端入门教学也成为了我进入 Web 开发领域的起始点。

尽管还是看不清远方在哪,脚下的道路已经开始变得具体而坚实。我本能般地混入了一个人人都比我强的圈子,跟随余神脚步租了第一个服务器,购入了第一个域名 dnc1994.com,搭建了第一个基于 WordPress 的博客。恰好也是在这个时候,为复旦 CS 不尽人意的教学质量所失望的我,接触到了 MOOC 这一事物的存在。

诚然,这一时期的我很容易沉浸于低效率的学习方式中。为了驱散懂得太少的焦虑,我曾经周期性地制订不切实际的计划,比如浪费假期的大把时间去阅读一些以我的基础完全跟不上的教科书等。类似的现象在过去的五年中时有发生。值得欣慰的是,随着自身的成长,我逐渐能够正视自身的劣势,保持清醒的头脑来修正错误的学习方式。

大一结束时,在夹杂着乱码小插曲的宿舍分配之后,我和余神、戍爷、Farter 四个人组成了带有传奇色彩的 709 寝室。709 的每一个人都很独特,而我应该是其中最为普通,也是技术实力最弱的一个。但也正因如此,我在接下来的三年中学到了终身受用的东西。

2014 - 2015

大二第一个学期,我参加了第一次托福考试,拿到了自己都有些吃惊的 110 分。那是我横跨三年的留学计划的开端。想去留学有诸多动机,但我已经记不清究竟是先下了留学的决心才报的托福,还是先随便报着被成绩鼓励到了才下的决心。不论如何,在不晚于第二个学期到来之前,我坚定了去北美留学的目标。

差不多在同一时间,我在高三到大一两年的探索中所接受的信息和观念,终于内化并形成了具体的想法。我第一次对于自己想要成为一个怎样的人有了清晰的认识,并将那时的想法写在了这个知乎回答里。四年过后,我的想法依然没有改变。这大概就是我的确找到了能够定义自己的叙事的最有力证明了吧。

15 年 3 月,通过朋友介绍,我认识了一位同样也是从复旦到 CMU 的学长,向他请教了许多留学相关的问题。在对整个流程有了基本的系统性认识之后,我意识到除了 GPA 和英语成绩这些硬性条件,自己非常欠缺的就是经历上的不足。我开始发挥自己擅长制定和执行计划的优势,早早报名了 15 年 10 月的托福和 GRE 考试,并部署了周期长但均摊负担轻的备考策略。但也正是从这个时间点开始,我产生了一种「一步落后,步步落后」的焦虑感。

由于大一没有留学的意向,并且自负地蔑视刷 GPA 的行为,我对自己不喜欢的科目非常不重视,导致当时的 GPA 只有 3.4 出头。另一方面,学校官方提供的海外交流项目也基本都在大二第一个学期的时候完成了申请和分配,而对交流机会的稀缺性缺乏敏感的我在那时没有提出参加交流的意向。等到第二个学期,我才发现春学期几乎没有可去的项目。这种被人逐渐甩开的焦虑感,催生了之后一系列不那么明智的选择。

这个时期,我开始投入大量精力到 MOOC 上。从数据挖掘到机器学习再到 Web 开发,我都是在 Coursera 上入门的。正是由于在林轩田老师的机器学习基石 & 技法这两门课上系统性地接触了机器学习,我才得以在 CS 这个巨大的学科下找到了自己真正热爱的方向。

开始学习 MOOC 一半是机缘巧合,另一半则是源于高三时代使用 MIT OCW 学习线性代数而体会到的世界一流名校教学资源的优越性;而把 MOOC 作为爱好坚持下去则主要有两个原因:一是复旦校内优质教学资源的缺位,二是我本人对英语的热爱。我曾经说过,适合大学生、与实际运用联系紧密、还能作为长期输入输出来源的英语学习方式,非 MOOC 莫属了。我最终完成的 MOOC 总数不下 50 门,也因为对 Coursera 的热爱得到了跟其中国区负责人近距离交流的机会。出国以后我也发现刷 MOOC 的经历使得我不需要任何适应就能融入以英语为主导的学术交流环境。但是反思一下,Coursera 也好别的平台也好,在初期(12、13 年)过后就很少再有进阶级别的课程了。用 MOOC 入门一个新的领域或许还无可厚非,但当时的我却沉浸于刷证书的快感不能自拔。这也是一定程度上的逃避现实,因为周围没有比自己刷得更凶的人,所以能够带来一种虚假的优越感。我刷掉的后 30 门课完全可以用来做更有价值的事情。

也是在第二学期,为了积累实验室经历,我加入了王新老师的 SONIC Lab,在周扬帆老师组里跟进 Mobile Computing 相关的工作。这是一个缺乏考虑却歪打正着地给我带来了许多收获的选择。由于大一时跟余神一同参加的最后无疾而终的腾飞计划就是找的王新老师作为指导,所以在仅仅以「加入一个实验室」为目的搜寻时很容易就通过这层关系联系上了正在招小朋友的周老师。

在周老师实验室的那一个学期,我每周需要阅读布置的一些论文并去跟老师和学长做讨论。那时我没有什么正儿八经的项目经验,也没有做过科研,对 Mobile Computing 这个领域更是毫无了解。我经常不能正确理解一项工作的难点在哪或是意义几何。而周老师的确是一位非常好的导师,他总是能耐心地讲解我的想法在哪里出了偏差,还传授了我许多阅读论文的技巧。可以说是周老师教会了我如何阅读论文,而这一技能令我在其他领域也受益无穷。

学期快结束时,周老师开始向我布置实际的开发工作,需要在 Android 上实现对一些性能指标的监控。在尝试动手之后我发现,尽管阅读论文本身能给我带来理性愉悦,但这个领域终究不是我所感兴趣的。过去的经历让我很清楚自己不可能做好不喜欢的事情,并且那时我也已经找到了喜欢的方向(机器学习)。在一阵纠结之后,我离开了实验室。

这一年的暑假,我先后去了英国和美国。前者是书院组织去 Hertfordshire 的项目,乏善可陈;后者则是经由一个留学社团联系的去 Stanford 的项目。事后看来,那四周的时间和金钱成本花得不算太值。被信息不对称限制了想象力的我,并不知道可以自己联系教授去做暑研。(当然那个时候不比留学门槛水涨船高的现在,知道并会去实践这个套路的也是少数人。)英国的项目对我自然是毫无帮助,而美国的项目也经历了一波三折。最初项目承诺的是由 Stanford CS 的一位教授主持,后来由于主办方的失误导致了时间冲突,最终被迫改成由 Stanford d.school 的 Michael Barry 教授主持的 Design Thinking 项目。说得直白一点,这个项目基本上就是教授赚外快的副业,其内容也是名副其实的「游学」,由洛杉矶/旧金山观光、入门级别的 design/business 短课程和参观硅谷公司三部分组成。

即便如此,这段经历也并非一无是处。在硅谷的所见所闻,一定程度上支撑了我出国留学的目标:第一,我亲身体验了自己设想中的未来工作环境(参观了 Google、Facebook、LinkedIn、Twitter 等公司);第二,我认识了 Polarr 的创始人 Borui Wang,近距离体会了创业者的特有气质。我至今仍记得的一个细节,是在闲聊时我向他描述了学校里一些不尽人意的现状之后,他淡淡地说了一句,那么为什么不退学呢。看着他的眼神我很明白,这么说并不是在嘲讽,而是的确认为这是一个可行的选项。当时的我感叹道,有朝一日我也要成为在这种前提下有底气选择退学这一选项的人。

这个时期还有一件值得提的事情,是我对 MOOC 的发展有了接近盖棺定论的看法。MOOC 的初衷是作为改革传统教育的实验,探索通往个性化教育的道路。但它诞生之后,显然更多的人看中的是原本非公开的大量优质教育资源。最初那一批学校开课给公众留下的深刻印象使得 MOOC 不可避免地带上了公益性,而在后续发展中商业性也开始被挖掘。MOOC 平台需要足够数量的「水课」来赚取利润并维持一定的公益形象,而内容提供方的学校也不可能把所有的资源都抖出来。这里的本质矛盾在于个性化教育必然要求倾注在单个学生身上的资源变多,但商业化 MOOC 平台却都是规模制胜论。MOOC 不得不跟上原本就存在偏差的定位,现在大家对它的理解也都不一样。有人觉得是把好的课程免费带给每个人,也有人觉得只是换一种形式接受传统教育,还有人只是把它作为一种收割智商税的手段。最早那批想要探索新教学方式的人似乎已经绝迹了。想明白这些问题以后,我不再将这个阶段的 MOOC 看作是能够改变教育未来的事物,它也从此开始逐渐淡出我的生活。三四年后来看,我的想法跟现实并没有太大的出入。

2015 - 2016

大三开始的时候,我对自己接下来要做什么就有比较清晰的把握了:搞定英语考试,积累更多机器学习相关的经历,找一份暑假实习。

大概是九月底十月初那会,在同学的推荐下,我加入了肖仰华老师的 GDM Lab / 知识工场。有着之前周老师对我的训练,我在讨论班时做论文汇报还是很得心应手的,也使肖老师对我产生了比较高的期待。之后我实际承担了两个 NLP 项目。讽刺的是,正是在 GDM Lab 的经历使我开始意识到自己不适合走纯学术路线。

在那两个项目中,我用脚本语言来处理数据和拼凑不同模块的能力得到了充分的锻炼,但也仅此而已了。我发现自己既不适合解决开放性过强的问题(也有部分原因在于反馈的缺失),也不能从以发表论文为最终目标的工作中收获满足感。我依然享受跟进学术进展的理性愉悦,在阅读和理解论文上做得还不错,也有自信做技术上的实现,但我还是很难成为一个好的研究者。并且实验室普遍低质量的胶水代码也跟我的技术审美不太契合。或许在这之前我还不能斩钉截铁地说自己是否打算申请 PhD,这段经历则让我彻底打消了这个念头。

第一学期时我还做了一个决定,那就是不论去哪,第二学期我都要出去交流。这么想主要是当时在学习和生活上都有许多烦心的事情,因为学分修得比较足就想换个环境调整一下身心。于是我去了国立交通大学(NCTU)。因为只有三门课,我在一番套瓷之后加入了 NCTU Machine Learning Lab。我的初衷是想利用这个机会参与一些工作来积攒经历,但很快便了解到台湾这边非一线实验室的运作方式跟我们所熟悉的节奏相去甚远。在那边硕士和博士生们的唯一关注点似乎就是自己的毕业论文(其内容也比较接近文献综述),而并不会为了往一线会议/期刊投稿去做项目,与工业界的合作更是完全没有。而愿意接纳我的简仁宗教授虽然非常热情,但他对我所说的「交流」的理解似乎更接近于「见学」。他的期望就是我能在有空的时候来实验室待着,参与讨论班等集体活动,同时找些感兴趣的课题做一些独立研究,也不给我任何指导。

这时的我已经跨过那个焦虑自己一步落后步步落后的阶段了,心态就变得比较度假。在台湾的四个月,除了体会风土人情以外,我将主要精力花在了 Kaggle 上面。对于只学过机器学习理论的我来说,完整实践建模的整个过程无疑是很有价值的经历。我投入了大量的时间去阅读攻略经验并加以实践,最后误打误撞地在第一次比赛中进入了前 5%。这段经历对我所坚信的「做自己喜欢的事情才能做得好」的准则也是一次很好的检验。

在废寝忘食训练模型的过程中,我试图在 Windows 上安装 XGBoost。当时网上能找到的安装攻略几乎没有一篇是在我的环境下可行的。一通折腾之后,我终于通过拼凑两篇博文中的细节得到了一个适用范围较广的安装流程。那时我内心最直观的感受是,这种抓耳挠腮花费一整晚后终于在互联网的某个角落找到解决问题的关键的感觉,实在是过于愉悦了。我想将这份愉悦传递下去,而不是让自己曾经踩过的坑继续困扰更多的人。于是我将安装流程写成了博文,至今已经有几十个人向我表示感谢。这种成就感坚定了我继续产出类似干货内容的决心。

于是在那次 Kaggle 比赛结束以后,我整理了一部分参考文献,结合大量自己摸索出来的细节(尤其是现有攻略中较少谈及的部分,比如 Stacking 的实现细节等)写成了这篇 Kaggle 入门指南,并在随后补上了对应的英文版。这篇文章的影响力完全超出了我的想象。先是英文版被 Kaggle 官方和 KDNuggets 等在数据科学圈子里比较有名的社区转发,接着就是国内外各路网站纷纷来请求授权转载(当然也有非授权转载甚至商业盗用)。后来甚至还有人民邮电出版社和机械工业出版社的编辑联系我希望能出版一本介绍 Kaggle 的书。也因为这篇文章,我的博客在已经很久没有更新过英文干货内容的前提下每个月还能有上千的海外访问量。

很快到了找暑假实习的时候。最初我因为迷信 MSRA 对出国的帮助,通过同学联系了一位做短文本理解的研究员,想要跟着他做一些深度学习项目,但最终因为无法承诺全职实习六个月而失去了这次机会。在开始找普通工业界实习时,同样出于对外企的迷信,我没有找 BAT 之类的公司,而是投了 Intel、Microsoft 等,但最后大部分公司都没有给我回复。后来我通过了一直特别感兴趣的 Splunk 的面试,却因为 HR 的工作失误没有能够去成实习。这时的我难免有些着急,正好当时也在找工作的戍爷向我推荐了做建站工具的创业公司 Strikingly。

Strikingly 的招聘流程令人惊艳。我印象尤其深刻的,是之后成为同事的数据工程师 Young 在面试时直接把实际工作中见到的钓鱼站点发给我,让我思考可以用什么策略去过滤它们。当时还未能从学术派思维中转变过来的我,不假思索地开始设想一整个完整的建模流程,而 Young 却告诉我其实靠简单的启发式规则就可以得到很不错的结果。回头来看,这是我第一次在面试中学到新的知识。纵观之后的面试经历,所有能让人学到东西的团队都不会太差。走完招聘流程,我成功被这家公司圈粉了,于是拿到 offer 后立马就接了下来。

当时的 Strikingly 主营业务是针对移动端优化的建站工具。用户在建站之后可以在每个网站各自的 Dashboard 中查看一些流量统计数据。我的上手项目就是将这个三年没更新的模块进行升级,为用户展示更多的数据。简单来说,我需要先在前端埋点将访问事件发送到我们使用的第三方服务,再在后端补充对应的查询 API,最后修改 Dashboard 的前端代码来展示新增的统计数据。当时主要有这样一些挑战:

  • Dashboard 模块前端用的是 CoffeeScript(当时公司已经停止在新项目中使用),后端 API 是集成在主服务(Ruby on Rails)里的。两端的技术我都是第一次接触。
  • 我抱有一种来到 Strikingly 就是做数据建模的幻想,从心理上抗拒所有不直接对这一目标有贡献的工作。起初由于前后端代码都可以依葫芦画瓢所以我还能勉强接受,但做到一半发现后端 API 由于改动较大需要重新设计,并且跟我结对的后端工程师不恰当地试图将比较炫技的写法传授给对 Ruby 毫无经验的我,这使得我对需要达到的代码质量产生了混乱和错位的预期。
  • 在一个高度模块化的庞大系统中 debug 时往往需要对比各个服务的 log,之前没有类似经验的我感到十分吃力。
  • 公司内部对项目不是特别重视。由于原本对前端实现没有要求,经验有限的我就写得比较粗糙(尤其是样式)。而在项目快收尾时的一次会议上,CTO 经过评估又决定前端需要重构,并将任务交给了当时在一同实习的戍爷。从代码质量的层面来说这么做更为合理,也减轻了我的负担,但对我的积极性的确是一个打击。
  • 我不适应创业公司的开发节奏,对 Ownership 的理解不够到位。我一度在自己的工作被别人 block 之后不去积极推动进度却转而开始忙活已经构想好的 Modeling 项目。这一点被我们的产品主管 Teng 狠狠地批评了。

好在随着时间的流逝,我开始抛弃掉那些不切实际的幻想,并逐渐建立起一种对自己的项目负责的成熟态度。到开发基本完成进入 QA 和部署环节时,我已经能够驾轻就熟,跟 QA Lead 争论什么是 bug 什么是 feature,自己动手把新版本部署到生产环境,还在上线的当天做 hot fix。在发给全体用户的邮件里看到自己开发的 feature 上线时,那种创造了能够传递给千百万用户的价值的成就感让我明白,我想要追求的就是这种做产品的感觉。

2016 - 2017

大四的第一个学期过得非常忙碌。在重中之重的留学申请之外,我还得同时处理好学业和实习。

鉴于身边有太多被中介坑害的例子,我的性格又不允许他人经手对自己未来能够产生重要影响的事情,我很早就决定要 DIY 申请。(这个时间点的我已经非常习惯和享受这种拓荒的感觉了。)由于可以预见到开学之后自己的时间将会非常有限,我早在八月初就开始了选校和文书的准备。借助几位学长学姐作为反馈的来源,我的个人陈述一共改了八稿。最初的两三稿写得异常痛苦,一边整理自己三年的经历,一边是无尽的后悔和丧气。在跨越什么都写不出来的阶段以后,接着就进入了截然相反的什么都想写上去的阶段。选经历,改写法,挑语病,再到最后调整可读性。不可否认的是,在这个过程中或多或少会陷入一种情绪从而过度投入时间成本,但回想起来那种直面自我的痛苦的确是有益的经历。

由于一切都规划得很早,我的申请季过得非常平稳。在九月底开始填网申之前,我手上已经有了所需要的全部组件,剩下的仅仅是将它们拼凑成型。值得一提的是,我将之前用来开 TOEFL/GRE 备考小讲座的微信群发展成了一个留学信息交流群。当时也有一些人数多得多的交流群,但信噪比都实在太低,而且我个人十分看重的那种将自己踩过的坑拿出来分享的例子更是少之又少。而我的群由于大部分成员原本就互相熟悉,又有我带头做毫无保留的分享,从而得以创造独特得多的价值。在申请季后期,我开始帮朋友审阅和修改 PS/CV,最后还写了一篇 DIY 申请总结

Strikingly 的工作则在这个时期陷入僵局。一方面固然是由于我被留学申请占用了大量时间,没有投入足够的热情和精力。另一方面也是因为,在最初的甜蜜期过去以后,理想和现实之间的差距开始开始令我感到无从下手。

Strikingly 将我招入公司时,是期望我能够建立一些增长相关的预测模型。我们的终极目标是预测一个用户的 Life Time Value(LTV),也即他/她在整个周期内能为公司创造多少价值。作为第一步,我尝试建立一个预测用户是否会取消服务(churn)的模型。第一次挑战现实世界中的数据科学问题,我自然是兴致勃勃。得益于 Strikingly 相对完整的技术基建和 Young 的帮助,我很快开始使用在参加 Kaggle 比赛时就已经非常熟悉的一套流程来进行建模。然而,种种意想不到的挑战接踵而至:

  • 由于没有现成的 OLAP 服务,我需要编写繁琐的查询语句来读入数据。由于之前对 SQL/ORM 的掌握仅仅是能够写基本业务逻辑的水平,所以尽管在 Young 的帮助下将一些常用的查询类型封装成了库,但直到实习结束我在这方面的熟练度仍然没有什么提高。
  • 由于一些数据是用第三方服务来统计的,所以这部分的查询模式跟存在自家数据库上的那些数据有一定差别,创造了额外的工作量。而且其中一个第三方服务由于技术水平不足,不能提供令人满意的查询性能,一时半会又看不到迁移的希望,这就使得问题更加严重。
  • Strikingly 的用户基数本就不大,而 Churn Prediction 主要针对付费用户,这就使得可用的数据量少之又少。尽管我使用了各种 Sampling 和 Validation 技巧,最终训练得到的模型性能仍然不太令人满意,而且我对其结果是否具有足够的统计显著性也没有太大的信心。
  • 整个数据管道和建模流程从软件工程的角度来看问题多多。比如没有对 ETL 的正确性做验证(曾经导致我在错误的数据上浪费数天时间),没有统一的将模型部署成服务的约定和流程,也没有任何方式去监控模型上线以后的实际性能(我离职后模型很快就下线了)。

归根结底,在那个时间点公司对于要搭建一个怎样的数据团队的理解是很肤浅的,正好又碰上我这个初出茅庐的数据挖掘工程师,最终只能产出这样的成果也比较遗憾。但在这里得到的经验教训却是我本科阶段最为宝贵的财富之一。同时,Strikingly 在如何做出好的产品、如何跟他人合作写出好的代码、如何构建好的工程师文化等等方面给了我无可替代的启迪,也直接影响了我对今后的工作体验的预期。更完整的实习感想可见这个知乎回答

16 年底,逐渐丧失在 Strikingly 继续工作的热情的我,借着学期结束提出了离职。差不多同一时间,一家做医保反欺诈的创业公司(暂且称为 L 司)联系了我,并催生了一段非常不愉快的经历。

L 司的行径可以用坑蒙拐骗来概括(一定程度上也是当下创业浪潮的一个缩影)。先是 CTO 在最初跟我交谈时极度夸大公司的团队资源和已经积累的建模经验。他们号称坐拥的来自北美多家著名公司的资深机器学习专家,无一例外都没有脱离原公司,只是挂个名号并象征性地起一点顾问的作用,也看不出来能提供多有价值的领域知识。并且,我在加入公司后才发现,他们并没有积累简单的匹配规则以外的建模经验。这本身虽然不构成问题,但与最初的沟通是完全矛盾的。

更为夸张的是,公司在跟某甲方合作时,用其一贯的套路虚假宣传我是「海归的高材生」。并且这一点并没有事先知会我,所以我在被甲方问到时也是一脸懵逼。说到实际工作内容,甲方内部 IT 基建水平之低令人叹为观止,我还有幸目睹了两个技术负责人疯狂撕逼甩锅,仿佛是上了一堂社会实践课。总而言之,天真的我被听上去很 fancy 的工作内容所欺骗,在不到两周大开眼界的体验后愤然离职。

17 年新年过后,申请结果开始逐一揭晓。客观地说,我申请到的项目基本符合我的实力中容易量化和验证的那部分。尽管我很确定自己比很多拿到 MCDS 录取的人都要强,但这种预设了错位期望的选拔过程所导致的结果已经很难令我失望了。出于一直以来对 CMU 的向往,我打算在 MITS 和 SESV 中挑选一个。由于对自己的实力比较自信,也觉得选择 MITS 之后未来一年的生活方式会更接近自己的设想,我选择了去主校区度过最后一年的学生时光。

接完 offer 没多久,我开始了在 NVIDIA 的实习。说来惭愧,这段经历从初衷到执行到结果都非常的功利。我原本没预料到在 L 司的实习会结束得如此突然。离职后过了一段时间,我感觉是时候找点事情做了。与此同时,由于之后要在美国求职,就想着往简历上放一个 big name 公司会比较有帮助。于是在同学的介绍下,我去了 NVIDIA 上海的 Computing Architecture 组。

NVIDIA 的面试也是非常值得一提的。第一轮中,面试官先是让我写了一个裸的矩阵乘法,然后开始循循善诱让我思考如何通过对体系结构知识的理解来从代码细节的层面去优化算法。因为是接触得不多的领域,我在面试中紧张无比。但结束之后回想一下,由于面试官引导得足够好,我不仅答上来了大部分内容,还学到了许多新知识。而第二轮面试几乎是相同的套路,只是算法变成了求卷积。

在 NVIDIA 的工作比预期的要更为无聊和缺少反馈,实习体验在很大程度上取决于 mentor。我当时的任务是要魔改 Caffe 写一个原型出来验证一个想法的可行性,但还没来得及做完 mentor 那边从另一个角度出发做的 benchmarking 就给这个想法判了死刑。这段经历中,我收获的仅仅只是 C++ 的熟练度以及对一个过气深度学习框架的了解。

在复旦的最后一个学期,我还做了两个比较值得一提的项目。第一个是给复旦艺术团写的票务管理系统。这是我第一次独立写出一个投入了实际生产的 Web 应用(一年多以后依然在使用)。这套用 Express 快糙猛地写出一个 MVP 的路子使我具备了基本的全栈能力。另一个项目则是在 GDM Lab 跟着肖老师做的毕业论文:在 Bing 搜索日志数据上做的基于 Seq2Seq 模型的搜索关键词生成。这是我作为一个声称做机器学习的人第一次从头到尾做完一个深度学习的项目,也是一次对 DL + NLP 的前沿进行跟进的经历。

2017 - 2018

还没从复旦毕业的时候,我就开始远程上 CMU 的招牌课程 15213(CSAPP)了。尽管对课程内容已经不能再熟悉,但实际上起正版的 513 来,还是会为 CMU 的 IT 基建所折服。

CMU 是一所很独特的学校,周围的人大多有着一种我喜欢的质朴。在这里的时光被学期的开始和结束而清晰地分隔开来,因为每个学期上了什么课在很大程度上就定义了你的日常。MITS 这个奇葩项目需要在一些毫无用处的地方花费一些时间,在此略过不提。

第一个学期我主要上了三门 10 开头(Machine Learning Department)的课。其中 10-601 是入门级别的机器学习课程,乏善可陈。另外两门课都值得稍微一提。

Russ Salakhutdinov 教授开的 10-707 是入门级别的深度学习课程。这门课的作业需要从零实现一个深度学习框架并用其做一些探索性的实验,这类作业只要认真去写必然收获丰富。在 Project 中,我跟两个国人队友设计了一种全新的 Dropout 算法,并用非常不严谨的实验证明了它比原始的 Dropout 泛化能力更好,收敛速度也更快(强行无视了它的计算方式要复杂得多)。令人意外的是这篇报告居然获得了满分,这也让我对 TA 的水平感到怀疑。事实上,这门课是 CMU 很多课程都有的一个令人不悦的现象的代表:教授和 TA 团队有许多做得不好的地方,但他们似乎不愿意接受任何批评。学生里面也总是会跳出来一些和事佬,发表一些类似于「你们知道教授和 TA 们有多努力吗」之类的观点。诸如此类的现象让我对 MLD 的教学质量逐渐失望。

William Cohen 教授开的 10-805 叫做 ML with Large Datasets,是我在 CMU 上过最有收获的课。这门课的中心思想,也是第一节课的主旨,就是扩大数据集给模型性能带来的提升往往超越不同模型之间的性能差异,继而介绍了种种让模型更 scalable 的套路和技巧。Cohen 有着丰富的工业界经验,虽然他的 Slides 制作水平和授课水平都堪忧,但作业的设计和几个讲义的质量在 10 的课中算是上乘。从理性愉悦的层面来讲,我是非常喜欢像 lazy updates 这类工程上的实现技巧的。在 Project 中,我跟两个印度队友做了一个 VQA 相关的数据集论文,扩充了前一年 EMNLP 一篇文章构造的数据集,并非常扣题地展现了更大的数据集加上 scalable 的模型可以达到更好的性能。最终文章的质量其实相当不错,Cohen 给了 A+,还建议我们去投 NAACL,但由于两个队友没有意向所以作罢。

这个学期我还尝试找了一下工作,很尴尬并且意想不到的是,我只拿到了 Google 一家的面试并且还通过了。虽然就此失去了 compete offer 的机会,我还是选择了真香。面试之前我做了 50 题左右的 LeetCode 来刷熟练度,感受是这种愚蠢的应试模式实在令人厌恶。

第二个学期我主要上了 Distributed Systems(DS)和 Advanced Cloud Computing(ACC)。这个学期的工作量大了很多,平均每周有 3 个 due。DS 需要 debug 底层细节,并跟 Autolab 斗智斗勇;而 ACC 则是每次写作业都要开一个集群并小心翼翼地控制运行时间,的确比较催人脱发。通过这两门课,我头一回对分布式系统产生了兴趣,也或深或浅地掌握了其中比较关键的一些概念和技术。ACC 最后还做了一个优化集群资源调度器的 Project,在写报告的时候我甚至产生了一种做科研的快感。

最后的暑假学期,我唯一的课业要求就是完成 Capstone Project。这可能是整个 MITS 项目体验最差的部分,其本质原因在于 ISR 的教职员工所相信的教育哲学和他们强加在项目上的一些观念实在过于奇葩。在受到种种限制的前提下去做一个自己并不认为有价值的项目已经足够令人沮丧了,还要带着四个水平比自己差一大截的队友(其中还有人品存疑的),我只能说当年没能申到更好的项目的恶果终究是要彰显的。

我不否认这三个月作为 Tech Lead + PM 的经历对我是有价值的。我在并不理想的环境下切实体会到了软件工程中老生常谈的几个难题,对技术管理有了初步理解,也感受到了同理心的重要性,掌握了一些沟通技巧。项目本身在技术层面也是我的一次成功的实验:我上手了新的前端框架 Vue 并独立开发了功能逻辑颇为复杂的前端,设计了一个模块化的系统架构并把 AWS 上常用的服务都踩了一遍坑,还尝试了没有用过的部署和运维工具。同一年前的抢票系统相比,这次的全栈体验更为完整,也更为现代。我当然也不会否认带着团队接连拿到 A 的满足感是确凿存在的。只是这段经历的意义更大程度上在于让我理解了为什么 Linus 需要通过语言侮辱来表达对代码质量的不满,为什么要远离有毒的人和社交关系。有些事情无药可救,也就不要勉强。

回过头来看在 CMU 的这一年,我最大的收获可能是发现了在机器学习以外还有分布式系统、软件工程等能给我带来理性愉悦的方向。我逐渐开始能够不带偏见地去欣赏特定技术方案背后的美,而这大概是一种在技术上成熟的体现。但更多地,我需要好好反思自己对时间的利用。这一年里,我的时间先后被学业、求职和发牢骚所主宰,所剩不多的空闲也被我拿来沉迷 DOTA2。从表面上看,我从 CMU 毕业,也拿到了 Google 的 offer,似乎正在向人生巅峰迈进。然而我内心却很清楚,这一年浪费的时间太多太多。我体会比较深的一点是,CMU 日常有丰富的学术讲座可供自由参加,其中有许多跨学科的课题对激发创意非常有帮助。而我却没有好好把握从而错过了很多开拓视野的机会。

好在这一年的最后,我终于摆脱了这种不断减速的进步状态。伴随着与几个志同道合的朋友的交流,我开始回顾初心,梳理已有的成果和信息,连点成线,开始展望和规划未来。

就用这篇略显流水的回忆录,纪念我人生中最后五年也是最重要的求学时光。希望未来的五年可以走更少的弯路,做更好的人。

Buy me an Asahi beer :)