August 19, 2017

实习与鸡

最近堆积的文章太多了。三个月的实习,还剩下最后的三个周,想尝试做一个小小的总结。听闻我的 mentor 发现了我的博客,并可以用 Google Translate 读懂大概,有些惊恐。不过他最近非常忙,估计没有时间再来到访吧,所以我就照我的真实想法写了。

这几个月在做一个项目,从实习一开始就接触,估计要做到实习结束,虽然也做了别的项目,但是其实绝大多数时间都在做这件事情。现在回想起来,掉入了很多坑,走了很多弯路。一开始接到这个项目的时候,觉得两三周就可以做完,结果两三周拖到了一个月,一个月拖到了两个月…直到现在。

这篇文章的开始,我会对项目的内容,我一开始的大致想法,走过的弯路和调入的坑做简单的介绍。不过,这篇文章并不会写我最后的做法。最后,有一些个人经历、感悟,以及做项目至今的总结。一开始写的比较啰嗦,所以如果对此不感兴趣可以直接跳过看后面的部分。

开始项目

大概来说是一件什么事情呢?在农场中养着很多鸡,这些鸡被放在不同的区域。每一批鸡生长的地方,会放入一个圆形的体重传感器。鸡上上下下传感器的时候,传感器会把体重实时的变化记录下来,传感器也有一定的平滑降噪功能。不过每个数据点上鸡的数量是不确定的。现在希望能够计算出每个数据点上鸡的平均体重。公司之前有一个简单的处理方案,但是有问题,希望我能重新想一个方案。

我得到了 50 多个传感器的历史数据。每个传感器的数据大概涵盖三四个月,其中每天大概每隔几十秒会有一个实时体重的数据点。其他的信息包括每个传感器中鸡开始生长的日期,以及这个类型的鸡的行业生长标准数据。传感器一直放在固定的地方,一批鸡走之后数据不会清零,所以会记录下好几批鸡体重的信息。对现在的已有的数据,一个传感器最多会有 150 天左右的历史数据。而对于某个常见种类的鸡,它们从出生到成熟被宰割的时间大概是四五十天,所以就可能会包含两三批次鸡生长过程的体重。程序的开始,我对几个批次进行了切割,确保切割后每个数据框只有一个批次鸡的体重数据。

要计算出每个数据点上鸡的平均体重,首先可以预测每一天这批鸡的平均体重。如果可以对每一天收集到的鸡的所有体重画直方图,理想状态的直方图顶点应该正好代表 N 只鸡的体重 (N = 1, 2, 3, ..),这样,找出每天一只鸡的根据直方图得到的预测体重,再对整个时间序列做回归,问题就很好解决了。但是实际情况没有那么“理想”,实际直方图中的顶点比较杂乱,有些比较难找出最能代表一只鸡的那个顶点数值。

一开始,我采用了一个非常糟糕的做法:将这些顶点与行业标准做比较,找出一个最接近行业标准的顶点。使用行业标准做判定的第一个障碍在于,如果不知道鸡的生长日期,也就是不知道鸡在这一天的生长天数,而行业标准却是针对每天的数值,就无从比较行业标准。如果不知道这一批鸡的生长日期,我的做法是强行数据的第一天设为鸡生长的第一天,然后用行业标准比较并挑选最佳点。结果发现实际上可能数据的第一天是实际鸡生长的很多天,结果很糟糕。另外还有一种情是,而如果生长日期是错的,预测结果就是错的。实际情况中,农场提供的生长日期时而不准,这就导致预测会出错。

使用行业标准还有一个最大的问题,就是这么做逻辑上是有问题的。行业标准的作用更多应该是结果验证,而不是通过行业标准得到体重。如果一批鸡生长太过偏离标准体重,也是这个算法最应该起作用的时候,通过此方法却可能返回了一个正常的体重。我一开始也意识到了这一点。然而,这的确是我当时能想出的最好的方案了,所以就硬着头皮坐了下去。我在这个方法上耗费了超过一周的时间,后来将这个方法完全废弃掉了,所写的几百行代码也就彻底作废。

灵机一动

于是我抛弃了行业标准的做法,也不知道针对没有生长日期的情况,应该怎么寻找最满足一只鸡的那个顶点值,一直觉得堵。想了很久也没得到结果,问题就卡在了这里。后来那个周,去了一趟波哥大,连着周末休了四天小长假。在波哥大寒冷的 Airbnb 里,突然灵机一动,想到了一个粗暴而可能有效的解决方案,不需要生长日期和行业标准就可以判别最符合一只鸡体重的那个顶点。

我的做法是针对每一批次的鸡,按天处理每个数据点的体重信息,并分为两种情况:

针对大概10天后的体重:我做了一个大胆而未验证的假设,就是去除噪音项之后,曲线中第一个顶点的体重大概可以代表1只鸡的体重。因为 10 天之后鸡的个头相对较大,最大可能性是只有1只鸡站在sensor上。大部分情况下这种处理是准确的,不过也有少量情况这种处理选取的这个代表1只鸡体重的顶点差强人意。

针对大约前10天的数据:这个时候鸡的体重比较小,环境噪音的干扰较大,同时此时鸡的个头小,往往会有很多只鸡同时在 sensor 上。所以代表1只鸡体重的那个顶点十分难找,我一直希望能够从曲线中直接找出这个顶点,但是还是发现自己写的程序无法处理。所以我使用了折中的做法:1) 如果知道鸡的生长天数,那么获取针对鸡生长天数的行业标准体重数值,拿行业标准体重和找到各顶点的体重对比,将最接近行业标准的顶点体重作为最能代表1只鸡体重。2) 如果不知道鸡的生长天数,就使用10天之后体重的时间序列做回归预测,得到前10天中每天1只鸡的平均体重。

对我的新想法,我迫不及待地想实现它。那天晚上我从波哥大做了 10 个小时大巴,第二天早晨六点到了麦德林,我感到很清醒,于是洗洗漱漱就去公司了。那是一个繁忙的周,我花了一天的时间参观工厂,半天的时间参观农场,留给我实现这个想法的时间很少,但我还是实现了出来,效果还可以。去农场不算一段开心的经历,我被迫做了不算繁重,但也绝不算得上轻松的体力活。但是亲眼看见农场和传感器,还是带给了我启发。农场那边说,当鸡生长天数小的时候,鸡本身比较小,因此传感器上最多可以站六只鸡;但是当鸡长大了,个子比较大,一般来说传感器上就只会有一只鸡,这为我的这个想法提供了支持。

问题似乎可以大体解决了。虽然还有一些问题:首先,有的数据总体会比较杂乱,回归会不准确;其次,如果一个批次的数据点太少,预测不出来;最后不太重要的一点,行业标准数据被浪费掉了。当时解决问题大概一个月,我很高兴,不过后来发现,我高兴的还是太早了,那时我还没有意识到,问题远远没有解决,留下的还是数不清的问题,和数不清的坑。

需求变了?

接下来的一个周,我重新和 mentor 交流我的想法,得到了一个惊恐的消息。原来我理解的需求一直是错的。

之前,我的理解一直是计算出 50 多个传感器的历史数据中的平均体重。然而那个周我才得知,最终这个问题应用的场景是动物(现在是鸡)的实时体重预测。每两个小时,用api中pull data,获取想要得到的传感器中的数据(可以是所有数据,也可以是最近几天的数据),接着,通过程序预测这些鸡的平均体重,并从而推断出这两个小时中每个数据点数据的平均体重。因为获取了这些数据可以计算出很多信息。

两个问题看似很相似,实现起来有很多不同。其中比较重要的一个方面是,实时预测导致了做回归只能预测未来的数据,而不能预测过去的数据。这个时候,真是有一种“杀死”产品经理的冲动(仅仅玩笑)。我也在反思,为什么不能一开始确认清楚需求呢?虽然其实我能理解,实现历史数据问题,对解决实时预测问题是必经的过程;道路虽然崎岖了一点,但也是有些必要的。但从另一方面也说明了,明确需求是多么重要;还有就是,沟通,沟通,沟通有多么重要。

所以那几天,我其实有些绝望。那个时候做这个项目已经接近一个月,但是大半成果其实都废弃掉了,而实时预测的逻辑更加复杂, 我不知道怎么下手重新修改一团糟的代码。除此之外,那天当我和我的 mentor 沟通的时候,我发现其实他对整个流程有思路,思路也是正确的。我不清楚为什么他们没有一开始告诉我,在当我走弯路的时候一味让我顺着走下去,我能想到的唯一解释就是他们其实也不知道这是弯路,只是顺着我的思路往下分析而已。

发了整整两天呆,终于有了大概的思路。具体的新思路就不在本文中写了,但其实能够发现,这种情况肯定是需要分情况讨论,并不断对结果做验证。首先,我开始实现最主要情况的代码,花了大概两天实现出来,效果整体来说很好,很顺利;接着继续写其他情况的代码,这个时候需要不断分情况讨论,交叉验证。又写了两天代码,还是觉得很乱,于是停止了继续写代码,准备做 UML 图。多少有点奇怪:写了一个多月代码才开始画 UML ?建模不应该是写代码之前先做的事情吗?原则上讲是这样的,中后期我也一直在记录不完整的 Dev Doc,但是这个问题一直没有什么很好的思路,也就无从建模。这可能也是之后进度一直缓慢,花费的时间比我想象中多很多的原因。不过总之,我花了一天多的时间花了一副很大的 Activity Diagram,理清了思路,剩下的,大概就只剩下继续吭哧吭哧写代码了。

艰难时刻

在这个问题上纠缠了这么久,有的时候真的觉得很艰难。一开始我觉得这个项目不难,写一些代码就可以了,后来发现似乎是要写一个小型系统,难度顿时大了很多。有的时候写着代码,真的觉得写不动了,又卡住了,心里很苦。有的时候我会跟坐在我旁边的美国实习生说,我真的很羡慕你的 mentor,可以经常来找你,关心你一下。或许是我的两个 mentor 每天非常忙,或许是觉得我是研究生可以独立完成工作,他们不会主动关心我的任何进展,加上没有定期的例会,这就导致我需要主动找他们,跟他们交流我的进展,或者我遇到的阻碍。虽然我觉得这不是什么坏事,本身我也不是一个需要任何关怀的人,但有的时候也会自我质疑,这么写代码是不是有问题的?真的要把这么一个 project 就这么交给一个“手无寸铁”的实习生吗?

我也有自己的问题:一开始上来,连需求分析也没有,第一天就开始写代码;写了一半代码才开始做概要设计,不得以抛弃一部分代码;写了一大半代码才开始做详细设计,又重新添加另一部分代码。不得不说,流程就是错的,走了很多弯路,写了很多无用的代码,也浪费了很多时间。这个时候想起了上个学期学了 Object Oriented Analysis and Design 的课,当时一直觉得这门课没什么用,讲了很多过于理论的东西,还和其他同学一起吐槽。不过现在发现,当时学的很多内容都用得上了,比如软件开发流程),比如当时画过的很多 UML。

写到第二个月的时候,开始处理不同情况的各种验证和后续处理方法,真的觉得写不动了。已经写了太久了,真的太久了,那一堆代码看了写了太久了,以至于再也不想看它们、再去修改他们。后来那天,所有的质疑突然涌上来了,质疑到怀疑这两个月自己所做的一切,觉得我是那个不被在乎的,不受重视的,觉得我所做的一切都没有意义…然后拉着我的一个同事,出去喝饮料,大诉苦水。我其实很感谢我的同事,他其实很忙,但是还是抽出两个小时的时间听我说了一些没有意义的话。后来的一天,我干脆跟我的 mentor 请了一天的假,什么也不想干,在家里看些 youtube 视频,昏昏沉沉睡了一整天。

睡到第二天的时候,突然一切都想开了。可能之前只是太累了。过去的几个周,我开始意识到我在这里的时间越来越短,需要游玩的地方还有很多,于是将日程安排的越来越紧凑。每天睡不足觉,周末又总是和新认识的朋友或者同事到各处游玩,暴晒一天。我不是一个体力充沛的人,在周末,往往需要一些一个人独处的时光,为这一周补充能量。然而,当这样的时间都不复存在,人也就变得越来越疲惫,以往令我愉悦的事情此刻变成了厌倦。

更多的讨论:要不要 Agile?

后来冷静下来,也和朋友讨论了一个问题:要不要 agile?上份实习中,我一直在用 agile;而这份实习,完全没有 agile,倒是有点不适应。我开始思考,有些事情如果用 agile,可以规避的掉吗?没有 agile,我从来不参加任何会议,写 project 几乎得不到任何方向上的指导,于是自己写的很累,也走了很多弯路,在一个坑里陷入一个星期,然后自己从坑里爬出来。Get things done 和 move fast 的确很重要,然而陷入一个问题无能为力的苦,也是无以言表的。

朋友一直在用 agile,但他跟我说他彻彻底底厌烦了。一个 ticket 或一个 ticket 的写,每次写一部分,要担心写慢了会 block 住其他人,还要烦 conflict。完整的话,他是这么说的:“每天早上 standup 得说自己干了什么,然而软件开发不像是房地产开发,很多时候很多坑很多莫名其妙的小错误,需要 debug 一整天。 对于很多 senior dev 来说,他们可以把软件开发玩成搭房子一样,因为他们当年从坑里爬出来了。但对与 junior 和 intern 来说,经常大半天 debug 一个自己不小心写出的 error,导致第二天报告时没有 deliverable,会给自己造成不必要的压力,也会让管理人员有不正确的观感。”

我想,或许 agile 的问题是 pushed too much,而没有 agile 就完全要靠自主了。对于我朋友来说,他觉得 agile 实际上并没有任何效率上的提高,反而带给他软件开发巨大的压力;而对我来说,不 agile 给我的问题是这几个月来的确全靠自我驱动。有的时候写的高兴了,一天进展迅猛;有的时候卡壳了,一个周都写不了几行可用代码。有的时候我在想,如果使用 agile,会不会我就不会开发这么久了?后来又想,或许是的,然而这可能是在多人共同分摊时间的基础上。总之,对我来说团队合作似乎总比一个人去想去做有效率一些。

结尾

其实我想非常感谢我的 mentor。他们给了我非常多的帮助,会在我卡壳的时候给予我很多宝贵的建议。他们对我十分宽容,尽管我知道自己做的十分不好,但他们仍旧给我很多信心,在当我感到挫败的时候鼓励我做的已经很好了。这应该是我离工业界写代码接触最近的实习,刚刚到达的时候我对 Python 的掌握几乎是最基本的,现在对这门语言熟悉了很多。经历了很多艰难时刻,但我觉得总体上还是乐在其中,而且学到了很多东西。

实习也将我混乱了很久的作息终于调整过来了。每天晚上 11 点睡,早晨 7 点起,简直是再完美不过的作息。作息规律了,每天也能集中注意力写代码,时间不知不觉过去了,代码越写越多,似乎听上去是件不错的事情。

最后一天,找了组里的 Manager 谈了一下自己的真实想法。其实提完建议之后自己很担心,不过后来想着反正要走了,把真实想法说出来也无妨,索性Manager很大度没有把我拉黑。实习结束后,和组里其他成员去吃了饭,接着去了 bar 喝酒(在球池还不幸把鞋搞丢了),然后大家一起去了 Disco,我虽然不会跳,但借着酒劲蹦跶了几个小时也无比开心。或许也是觉得可以借酒消愁,于是尝试着喝了很多。现在回忆起来,都是一段十分开心的回忆。

comments powered by Disqus