过去这五年

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

2013 - 2014

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

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

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

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

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

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

2014 - 2015

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

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

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

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

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

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

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

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

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

这一年的暑假,我先后去了英国和美国。前者是书院组织去 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。我的初衷是想利用这个机会参与一些工作来积攒经历,但很快便了解到台湾这边非一线实验室的运作方式跟我们所熟悉的节奏相差很大。在 ML Lab,硕士和博士生们唯一的目标似乎就是自己的毕业论文(比较接近文献综述),而并不会为了往一线会议/期刊投稿去做项目,与工业界的合作更是完全没有。而那位愿意接纳我的教授虽然非常热情,但他对我所说的「交流」的理解似乎更接近于「见学」。他的期望就是我能在有空的时候来实验室待着,参与讨论班等集体活动,然后找点感兴趣的课题做一些独立钻研,也不会给我任何指导。

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

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

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

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

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

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

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

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

2016 - 2017

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

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

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

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

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

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

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

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

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

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

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

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

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

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

在复旦的最后一个学期,我还做了两个比较值得一提的项目。第一个是给复旦艺术团写的票务管理系统。这是我第一次独立(虽然为了赶进度请戍爷帮忙写了部分样式,但那不超出我的能力范围)写出一个投入了实际生产的 Web 应用。这套用 Express 快糙猛地写出一个 MVP 的路子使我具备了基本的全栈能力。另一个项目则是在 GDM Lab 跟着肖老师做的毕业论文:在 Bing 搜索日志数据上做的基于 Seq2Seq 模型的搜索关键词生成。这是我作为一个声称做机器学习的人第一次从头到尾做完一个涉及深度学习的项目,这使得 DL 对我而言不再神秘。

2017 - 2018

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

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

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

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

Cohen 开的 805 叫做 ML with Large Datasets,这是我在 CMU 上过最有收获的课。这门课的中心思想,也是第一节课的主旨,就是扩大数据集给模型性能带来的提升往往超越不同模型之间的性能差异,继而介绍了种种让模型更 scalable 的套路和技巧。Cohen 有着丰富的工业界经验,虽然他的 Slides 制作水平和授课水平都堪忧,但作业的设计和几个 Notes 的质量在 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 的 faculty 所相信的教育哲学和他们强加在项目上的一些观念实在过于奇葩。在受到种种限制的前提下去做一个自己并不认为有价值的项目已经足够令人沮丧了,还要带着四个水平比自己差一大截的队友(其中还有人品存疑的),我只能说当年没能申到更好的项目的恶果终究是要彰显的。

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

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

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

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

Buy me an Asahi Beer :)