本
文
摘
要
1 前言
从事CV算法这个职业已经三年有余了,如果加上毕业前玩命转行跳坑(生化环材)的那两年,到现在已经差不多快五年了。这五年来,CV越来越卷这个说法的声音越来越强,我个人也是比较赞成这个说法的。不论是出于什么原因,在这个各行各业都在寻求所谓的AI赋能的时代下,CV绝对是转行跳坑之人最容易与计算机产生关系的行业了。
正因为这样,这个所谓的卷,给我自己的感觉就是分母太多,炮灰太多。今年也陆陆续续面了不少人,有太多人我只能称之为”做过CV“,仅此而已。绝大部分人基础不牢、或是代码能力弱、或是对技术没有思考、或是项目经验太水。所以不求做一个手握几篇顶会的大神,做一个基本功过硬、项目不水的合格的CV算法工程师,在市场上还是会有一定竞争力的(不要被贵乎人均3篇CVPR打击到自信)。
写下这篇的文章的目的,也是来分享一下本人最近几年(尤其是工作这三年)的一些心得,希望可以为刚入行或正在转行的CVers提供一些参考。
后文分为基础技能和实战能力两个部分。基础技能篇包含我认为CV算法工程师一些必备的重要硬技能,可能不全,欢迎补充;实战能力篇包含我在实际项目中的一些体会。
2 基础技能
俗话说得好,基础不牢,地动山摇。我曾经就见过因为基础不牢间接导致项目夭折,甚至是丢掉工作的案例。因此无论是数理基础、图像处理/ML/DL基础,或是编程基础,都是越扎实越好。
2.1 理论基础必须夯实
这里把非coding的基础全部划归于理论基础,包括但不限于图像处理、机器学习、深度学习、模型设计、优化方法、评估指标、基础任务等等。这些基础不牢靠,其他能力再强也都是空中楼阁。
比如基本的图像操作,包括数字图像运算、滤波、ROI、仿射变换等等都必须了如指掌。这些虽然可能不作为这个岗位的核心竞争力,但也绝对是必备技能,只要涉及到CV,就绕不开这些。
再比如卷积、池化、BN、一些基础loss等等这些最基本的CNN操作,它们的原理、计算方式、特点都必须要做到烂熟于心,不能有一丝一毫的含糊。因为只有做到这样,才可能为你在项目里设计模型、设计loss这些工作中扫清障碍。举个简单例子,设计模型的时候要考虑每一层的感受野,要考虑浅层和深层的feature的差异,要根据不同的任务考虑平移不变性或平移等变性,要考虑每一层卷积的计算量,设计loss的时候可能要考虑困难样本的挖掘,这些都是基于牢固的基础才有可能完成。
除了基础,还需要掌握CV领域各个基础任务的常规算法。个人认为这里的基础任务至少包含分类、检测、分割、GAN。工业界大部分的实际任务都可以抽象成这几个基础任务或者是这几个基础任务的扩展,因此多了解一些它们的经典算法也有助于实际项目的快速开展。这里简单列举一些。
分类:AlexNet、VGG、ResNet、MobileNet、ShuffleNet、GhostNet、SqueezeNet等。
anchor-based检测:RCNN系列、SSD、YOLO系列(不包含YOLOv1)
anchor-free检测:YOLOv1、FCOS、CenterNet等
分割:FCN、SegNet、UNet、Deeplab系列等
除此之外,还有许多杂七杂八的知识点和技巧最好也能熟练掌握,比如CNN一些简单好用的小模块(SE、CBAM等)、模型蒸馏、Vit(self-attention)、算子融合、结构重参数等。
2.2 代码能力必须过关
斟酌了一下,这个小标题里用了「过关」而不是「过硬」,是因为我发现很多所谓的算法工程师,代码能力根本不及格。因此代码能力这块也是我想强调的重中之重。
我所秉承的代码两条原则:
尽量自己写代码!使用别人的代码必须自己先读懂,坚决杜绝不读代码就直接使用别人的代码!!先说第一条,有人会问网上代码都是现成的,为什么要自己写,我想说最简单的原因就是你是个工程师,写代码就是你的看家本领啊!而且不可能永远都用别人的代码吧,总有一些模块是必须自己实现的。
具体来说,自己写代码会让项目变的相对可控,毕竟代码是自己一行一行敲的,一旦需要修改或者优化都会更方便;自己写代码还有一个重要的好处,就是写代码的过程其实对各种原理深入理解的过程,你一旦能独立实现一个算法逻辑,那说明你百分之百掌握了它。
写代码是个潜移默化的过程,坚持自己写,写的久了,会发现自己的bug定位解决能力、项目代码架构、代码细节、算法原理各个方面都有提升。
所以千万不要犯懒,不要眼高手低,一定要自己多写,写的过程中多写test case多测,培养自己的动手能力。
再来看第二条,其实直接用别人的代码也是挺常见的一件事了,毕竟没必要重复造轮子。但我一直认为,程序员要时刻保证自己的代码是可控的,也就是一旦出了问题你能以最快的速度定位到。这就意味着,在使用别人代码之前,必须要认认真真的读一遍,至少要搞清楚每一行的输入输出都是什么,这样你在修改或者优化的时候,你才知道在哪改啊(毕竟大部分情况下别人的代码仅仅只是能用,但都不是最适配于你这个项目的)。
说到读代码,我发现有很多刚入行的朋友,自己写一点还勉强能写,但一旦读别人的代码就会有相当大的障碍。这个问题其实很好解决,只要掌握好方法并且克服心理恐惧,就可以了。
读一个项目的代码的时候,一定不要先去读一些核心方法,一定要先从整个项目的入口开始读起。用一个比较好用的IDE(比如我自己用jetbrains),从入口开始,碰到调用的模块就点进去看明白,一层一层的由外向内深入,读的过程中也可以配合着纸笔,边读边画一下项目的架构。
总而言之,无论是写代码还是读代码,都要一点一点的啃,没有捷径!只有不辞辛苦、不怕麻烦的人才能获得代码能力上的提升。
2.3 关于编程语言和工具
经常看到有人问做算法工程师,只会python行不行,除了python以外学个java好还是学个c++好之类的问题。先说结论:只会python不行,第二门语言必须是c++,sql最好也懂一点。想继续拓展的话java和golang都行,我个人推荐golang。
我自己在当年上学刚转行的时候也被同样的问题困扰了很久,记得那时候想学第二门语言,java看了看,c++看了看,甚至还有一段时间在想要不要把再matlab系统学一遍,也是走了很多弯路。
先说python。python确实相对其他语言简单一些,毕竟自己不用编译,语法也不难。但我的观点的是不要把它想的过分简单了,写的时候还是有很多基础细节点需要注意的(比如我很早之前在一个项目里被一个深浅拷贝的bug折腾了一周,就是典型的基础有问题)。除了python基础之外,平时用python的重中之重就是和各种package打交道了,opencv、numpy,pytorch/tf这些等等。这些要做到的就是常用的api最好非常熟悉,否则每次都要查用法的话效率真的会很低,自己也累。
这里多说两句:
因为每天都在面对矩阵运算,所以不管是numpy还是pytorch,设计到矩阵的操作比如cat、维度变换、切片、各种mask等这些操作一定要熟悉,否则会很难受。对于训练框架Pytorch/tf等等,不只是要熟悉api,还要了解编程范式,比如数据怎么load,训练流程怎么写,这些最好多读读别人的代码,训练框架的社区也挺成熟的,可以多去逛逛,最终最好形成自己的一套关于训练框架的编程范式。再说C++。刚才说了,C++是必备技能。原因很简单,一定要牢记自己的title是算法工程师,既然是工程师就离不开模型部署,部署就绕不开C++。关于部署与C++的关系,可以看我这条回答,这里就不重复了。我也是在工作之后的实际部署项目里学的C++,算是现学现卖,过程很痛苦,虽然到现在也没有精通,但起码是能独立部署模型,能用C++写业务逻辑,勉强够用了。关于C++的学习,我个人的一点愚见就是一定要多动手,多写,最好能去实战一两个项目,千万别想着速成,市面上那些什么多少天速成C++的一个都不要信。顺便吐个槽,C++这玩意儿真是一个月不写就手生。
除了C++本身之外,还需要懂一些编译的知识,至少要会用cmake编译项目,毕竟这玩意儿跟python不一样,你得自己编译。关于cmake编译,可以看「基础知识」gcc/g++编译过程及gcc/g++与cmake对应关系。
书的话,就看C primer plus和C++ Primer我觉得就够了。
3 实战能力
这一趴包含工业界实际项目中我的一些心得,主要强调软实力。大家都调侃深度学习是玄学,那在这种条件下,在实际项目中总结出一些心得感悟就显得尤为重要了。
3.1 时刻牢记以业务需求为导向
如果你的岗位是算法研究员或算法科学家等research岗位,那这一部分可以忽略。但算法工程师,毕竟不是纯研究职位,其主要职责还是要去服务于具体业务的。但我发现有很多人(也包括我自己)都会偶尔钻进牛角尖,追求最新最fancy的技术,而忽略的真实的业务需求。这并不能称之为一件坏事,但在实际业务中,这却是一种效率低下、针对性差的做法。不是说新技术就不能用,是不能以使用新技术为目的,而必须以匹配业务需求为导向,即使是新技术,也要考虑它是否可以满足具体项目中具体的需求。在这一部分我总结了以下两点。
必须明确项目上线后的应用场景。应用场景拆分的尽量细致一些,比如宏观上的场景有安防、交通、用户直播小视频等,细致一些比如室内or室外场景、用户是否露脸、用户距离镜头的距离等等。数据集尽量要匹配应用场景,最好使用线上数据,就算要用开源数据,也要尽可能的匹配特定的应用场景,切忌随便拿过来一个popular的数据集就直接用。必须要在效果和性能上做平衡。无论是移动端还是服务端的模型,大部分情况下我们都无法兼顾效果和性能。很多时候在推理算力的限制下,我们甚至要牺牲一些效果来满足性能要求。这个trade-off是算法工程师不得不面对的,有时候好不容易做出个不错的效果,但是手机端上帧率只有几帧,那也是没办法上线的。公司不会单为了你的这点功能去扩容,用户也不会因为你的功能去换一台高性能手机。满足性能要求是底线,如果觉得效果上有问题,并且在模型侧难以解决,可以尝试一些后处理的业务逻辑去弥补。3.2 调研/论文阅读
我个人认为,基于项目的发展顺序,调研工作应该分为竞品调研、数据调研和论文调研。见过有人不习惯或者懒得做调研,拿到任务乱试一气,几个月都没什么像样成果。所以千万不能忽视调研工作,调研做到位了,能起到事半功倍的效果。
先说竞品调研。无论是自己测试竞品app、demo、sdk还是直接打听,目的都是为了确定大方向,大方向指的是项目应该用分类做?分割做?检测做?分类+检测?需要后处理?GAN?总之就是在宏观上确定项目是CV中的哪类或哪几类任务。
再说数据调研。这里主要是收集数据,如果有线上的标注数据最好,如果没有那就根据具体场景去找一些开源数据,有的时候我们苦于只有原数据但没有标签,这个时候找一些AI开放平台的sdk去生成一波标签也不是未尝不可,但毕竟是机器生成,还是需要人工过一遍。当然了,在缺数据的情况下最好还是能向公司申请,从数据标注公司买数据,这是最省心省力的方式了。
最后说一下论文阅读。确定了大方向之后,肯定要去读一下这个方向的论文,看看有什么具体的做法。这里要明确两点。
不要读超出自己能力范围的论文,读论文要循序渐进。如果你对一个任务方向不是很熟悉,或者说,这个方向的过去几年的经典论文都没读过,那一定不要上来就看一些比较新的论文。我一开始就犯了这样的错误,对自己不熟的方向,直接就想看下最新的论文,看看能不能直接用上。结果就是很难看懂啊,很多方法、术语都不明白。所以急功近利是不可取的,这种情况下一定要按照时间顺序,依次读一下这个方向的经典论文。比如做生成任务,都不了解vanilla GAN就直接去看cycle GAN,那肯定是不行的。不要被五花八门的论文困住了,只去精读经典高引论文即可。之前很让我苦恼的一点是,尝试了很多论文中的各种方法,但模型就是没提升,甚至还下降,让我不禁感叹这年头水论文也太多了吧,全都是面向数据集优化,换个数据集换个场景全都GG。后来我看了 @周纵苇 大佬的 周纵苇:研习U-Net 这篇文章,非常精彩,里面有个醍醐灌顶的观点,简单总结一下:很多论文都是站在高引论文上做微创,读论文要重点读高引的具有0-1开创性工作的论文,并且要分析它的底层逻辑,比如Unet的拓扑结构。确实,微创的工作在工业界,大概率换个场景换个数据集就不行了,但开创性的工作之所以能得到高引,就是因为它坚实的底层结构使其通用性更强,基本上保证了下限。3.3 数据是最重要的
关于数据,最经典的莫过于那句「数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已」。其实在CV工业界我还想再加一句,数据也基本决定了下限,数据到位了,让狗随便写个模型都能起飞。虽然有点夸张,但其实就是想强调工业落地中数据是最重要的一环。
这里我必须说明的是,数据做到位,绝对不是无脑堆数据集。我很负责的说,无脑堆数据集增加数据量的做法,大概率作用不大。想要在数据上下功夫,数据量固然重要,但就像我之前说的,首先一定要考虑具体的应用场景,堆数据也要有目的性的堆,坚决杜绝小鹿乱撞。其次,要习惯于分析数据,计算数据集的各种统计信息,根据分析得到的认知,仔细设计数据前处理的过程(比如检测里anchor和gt的匹配过程)。
同时也要有分析bad case的习惯,每训练出一个模型都要去总结一下所有bad case的场景特性,比如是光照条件下效果差,运动条件下效果差,目标不全的情况下效果差,等等。根据bad case的场景特性,在数据上做针对性的优化,缺啥补啥。因此,数据增强也不是随便增强的,而是有针对性的。用到的每一种增强方法都应该有它背后想解决的问题,绝对不是别人做了个平移翻转,你就也要做个平移翻转。
3.4 分析问题和解决问题
分析问题和解决问题的能力基本会出现在每一条招聘的JD上。我一开始也觉得这一条只是用来凑字的,但其实,在工作中这一条至关重要。从每一次debug,到对项目整体效果和性能上的把控,分析和解决问题的能力都要发挥作用。
分析问题和解决问题听起来挺玄幻,但我认为,在算法工程师这个职业中,这项能力也是可以去拆解的。我个人的观点是:扎实的基础、良好的逻辑性、工匠精神以及经验的总结,共同构筑这项玄幻的能力。
首先,扎实的基础,这个不用多说了。比如想要优化一下模型,如果连CNN都不够熟悉,那其他的都是空谈,再分析也是乱分析。
其次,良好的逻辑性,是要透过现象看本质。要做的就是先去分析现象(比如模型效果到底哪里不好,在什么场景下不好),然后通过这些现象的总结,逐个分析可能的原因,比如是数据原因、还是模型设计的不好、还是训练手段或者loss设计的有问题?再比如若是loss的原因,具体是什么原因呢,是不是要从前向传播和反向传播两个角度再分析一下呢?这一点其实就像警察叔叔破案一样,层层深入。把所有可能的原因分析到位后,再逐个击破。
第三,工匠精神。列出这一点的原因的是在分析并解决深度学习的问题时,往往要做大量的实验,这些实验往往不能对猜想做到快速验证,因此会非常的耗费时间和精力。根据我的个人经历来看,做实验是平时工作中做的最多的事情了,非常容易产生厌烦情绪,所以还是要调整心态、不怕麻烦、勤动手、具备工匠精神。
最后,要善于总结经验。有的时候,不同的任务遇到的问题是相似的,如果善于总结经验,那遇到问题时有可能会用过去的任务经验快速解决。在模型问题上,我个人大体上的方法论是一定先从数据入手,往往数据问题得到解决后模型效果会有不小的提升;其次再去分析模型设计、训练技巧、loss函数这些问题,大多数情况下解决这些问题也会有一些提升,但是提升的幅度会偏小一些。
除去以上这四点,另一个小感悟是一旦遇到问题,一定要先尝试自行分析解决,尽量不要一上来就问leader或同事。自行分析解决的好处是,在这个过程中自己的感悟会更深,利于总结经验,也容易形成一套适合自己的方法论;同时这个过程也比较容易发现基础上的漏洞,起到查漏补缺的作用。自行尝试无果后,再去咨询一下leader或同事的想法,看看别人的idea,这样对自己的成长帮助是最大的。
4 结语
从劝退专业转码到现在已经有5年的时间了,工作也已三年有余,说长不长,说短不短。这一路上自己也走了许多弯路,遇到了各种各样的坑。这篇文章一定有许多经不起推敲的地方,但这确实是我这几年来基于自己的经历,得出的适用于我自己的经验心得。我相信每一个算法新人最终都会形成一套适用于自己的工作经验或方法论,也许这篇文章提到的内容并不适用于所有人,但也希望能起到抛砖引玉的作用,给刚入行迷茫的新人一些启发,帮助新人快速的找到适合自己的道路,少走弯路。
同时,工作三年作为一个里程碑式的节点,做这样一个记录我认为是很有意义的。期待接下来的三年大家都能有更长足的进步。