# 算法工程师

## AI算法工程师**如何从头到尾完成一个产品需求**

### 1、数据准备：**拿到数据和标注**

* 通过日志或埋点记录直接获取
* 组织人工进行标注
* 让标注人员充分理解 Label 的含义，不能有模棱两可的情况出现
* 明白标注一定会有错误。对于错误的标注，可以先跑模型然后预测数据集，将标错的样本按置信度排序后，重新标注。这样重复几个来回，错误的标注就会越来越少。
* 标注工具可参考：<https://github.com/hscspring/AIToolBox#dataannotation>
* 确定数据的分布，如果是极度不均衡的那就要准备采样方案。
* 有些任务可能需要在已标注数据的基础上重新构造数据集，这种情况需要准备构造方案。比如句子对模型，我们实际拿到的标注数据可能是句子加某个 Label，同一个 Label 的表示句子相似，不同 Label 表示不相似。

### 2、数据分析：**熟悉任务的数据** ；**分析数据与 Label 之间的关联性** 。

* 有时候数据并不是我们想象中的样子，比如用户的 query、或者 ASR 之后的文本、或者来自于网页的内容，不同的数据有不同的特色。
* 用户的 query 可能会有错误，这些错误可能往往是音近或形近导致的，也有可能有拼音输入；ASR 之后的文本可能有很多无效的口气词，当然错误是免不了的，可能更多的是发音不准导致的错误；而网页的文本则一般比较规范，不像 ASR 那么口语化，其中的错误往往也是句法、语义层面的。
* 以分类任务为例，数据样本和每个 Label 之间的关联体现在什么层级？是一些字词就能区分，还是需要字词的顺序？还是需要理解整句语义？是不是需要先进行纠错，不纠错会不会对结果产生影响？
* 只有清楚了这些才有可能选择适合该任务的模型，训练完之后也能够对预期做出检验。比如假设只要单纯地使用字词就可以区分 Label，那 TextCNN 也许就完全足够了；如果句子本身可能有一些无效词，那 TextCNN 的效果也许比 Bert 还好。这也很容易理解，因为 Bert 的预训练是建立在正常文本上的，而且是从整体考虑的，对非正常文本这有时候反而会失效。
* 这个除了经验外，基本也只能自己去看数据了，而且即便是有经验也最好再熟悉一次，比如不同的 ASR 产品也许犯错的种类不一样呢。实际过程中可以对样本按类别进行均匀采样，然后去观察数据。

### 3、测试方案

**想清楚测试方案** 。至少应该包括以下内容：

1. 正常的精准、召回、AUC。根据关注精准还是召回，如何权衡。多种方法（比如规则+模型）配合使用时，如何确定阈值以实现整个系统效果最优。这里需要重点关注的是尽量让不同的方法互补，交叉最少。

是否有任务需要特别关注的指标？比如推荐要评估多样性、覆盖率等。从系统使用的角度考虑。比如句子对相似度模型，我们在实际使用时往往并不是直接给一个句子对，然后判断它们是不是相似。往往会有其他一些应用场景，比如在 QA 时用来查询最相似的 Answer。这种情况我们应该测试的是召回方面的效果。

1. 如何测试模型的有效性？即如何评估模型真正学到了期望学到的东西。这对于之后的改进有非常重要的意义。有时候我们会上来就用一个当前效果最好的模型（我以前就经常），但很少思考为什么这个模型可以。这个模型的哪个结构或配置起了作用？为什么会起作用？是不是和我们预期（分析数据阶段）的相符？无论我们使用什么模型，都应该深入探究起内部机理，比如 TextCNN，我们可以把 MaxPooling 的位置给标记出来，不同的 Kernel 该位置的分布是什么样的，这些位置能否代表整句话输出对应的 Label。只有明确了这些机理，做到了心中有数，才有可能根据不同的任务和数据调整模型的结构。
2. 如何测试模型的性能？能否满足目标，比如多少并发下的平均响应时间。
3. 如何做回归测试？主要针对系统不同组件更新对其他部分和整体的影响。大部分情况下，我们面临的都是一个系统，模型负责的任务可能只是其中的一个个组件。很自然地，当某个组件发生变化时，很有可能会对依赖它的其他组件产生影响。比如召回和精排，如果召回模块出了问题，后面的精排模块再怎么样结果都可能是有问题的。所以，我们在上线新的版本或使用新的模型时，**一定要考虑到它可能对系统整体造成的影响** 。具体就是在模型发布在测试环境后要跑回归测试，确定模块之间衔接符合预期。
4. 如何做线上 A/B 测试？主要测试组件更新后线上是否真的有效。

### 4、模型算法

关于模型训练我们依然要考虑一些因素：

**资源** 。最直观的是机器资源，有条件的可能使用 TPU，没条件的可能只能用普通的 CPU，或者要去租赁 GPU。资源的不同除了训练模型的代码不同外，其实更重要的是训练策略的不同。比如有几乎无限制的资源时，我们可能会多跑几组模型，多试几组参数，甚至会重新预训练大模型，自己做蒸馏压缩。当然除了机器资源，还有人力配备、时间节点等等都要予以考虑。

**多模型** 。当面对一个任务，尤其是复杂任务时，我们经常会有不止一个想法（模型结构）需要验证，自然需要跑多组模型看效果。即便不是这样，最好也能尝试多个侧重点不一样的模型，这样更容易验证自己的想法。比如，我们可以同时跑 CNN 和 LSTM+CNN 判断时间顺序到底有没有起作用。

**预处理** 。这块内容和任务强相关，但总的来说中文任务一般包括以下几个方面：

* 字级别还是词级别的 Token。字相比词没有那么稀疏，词表也比较小，缺点是损失了部分语义。因为中文的基本构成单位是词，尤其是涉及到实体，拆开单独看可能完全是另一种意思。而词相比字，除了词表很大外，还会有未登录词的问题，另外，还需要单独做分词任务。具体使用字还是词要看任务确定。
* 数据清洗。主要是去掉一些无关的文本，比如超链接、图片、特殊符号等等。
* 数据归一。这个主要是处理具体的、特定的文本。比如句子级别的任务中，实体（包括人名、地名、时间、方位等）可能并没有太多意义；部分任务中具体的数字可能需要规范到不同的范围。
* 调参。俗称炼丹，主要是对同一个模型使用不同的超参数，比如 Embedding 维度，TextCNN 的 Kernel Size、Filter Size，Bert 的层数，各种 hidden size、激活函数等等。除此之外，还应该包括部分组件的调整，比如增加归一化、使用 Dropout等等。使用不同参数前依然应该遵守分析数据的方法，假定参数调整了结果会咋样，然后通过实际结果去验证我们的假设。

### 5、模型部署：**将一个或多个深度学习模型部署为微服务**

在部署时，推荐使用容器化部署方案，使用 k8s 或类似的集群框架对服务进行管控。

* 部署方便。完全不用考虑不同环境可能造成的冲突，所有的服务相互隔离。部署时通过 YAML 配置服务，实现一键全自动部署。
* 便于扩展。水平扩展可以直接添加实例，垂直扩展修改资源限制，所有配置均可通过配置文件完成，完全实现资源配置化。而集群资源不够时，直接添加节点主机即可。
* 便于管控。通过 Istio 等组件非常容易实现流量和服务管控。比如可以很容易地配置实现灰度发布，进行线上 A/B 测试，而且这些功能都是和业务解耦的。
* 节约资源。因为集群的服务其实是共享节点资源的，所以高峰时期服务会自动多占用资源（当然不会超过配置的限制），低谷时期就自动释放资源。这样其实最大限度地利用了可利用的资源，节约了成本。
* 管理方便。从管理机器变成管理服务，只要配置好相应的服务，机器只是无状态的节点，多一个少一个挂一个重启一个对服务没有影响。而且集群还支持非常细粒度的权限控制，使用权限可以按需下发到部门或个人。

### 6、工程开发

**写好测试**

* 代码更鲁棒。主要体现在边界和非法输入测试环节，它会强迫我们去考虑各种可能错误的输入。
* 代码更清晰。比如有个函数同时做了几件事，在写测试的时候就会发现很不好写，因为几件事可能互相有影响导致输出不同的结果，这就能逼迫你重构代码。
* 代码更系统。主要是指多个函数（或组件）组合或管道完成一项功能时，结果不符合预期的情况。此时如果某个函数（或组件）有完整的测试，那我们就很清楚地知道该函数（组件）一定没问题，问题肯定出在它之前或之后。
* 出错更容易排查。只要发现 Bug，很容易就知道问题出在哪里，因为报错信息一般能提示到具体的方法或函数，根据报错信息结合已知的测试范围很容易就确定到底是什么原因导致的错误。

总的建议是每个函数都应该有测试，且至少应该包括：正常功能测试、边界测试、非法输入测试，底线是核心代码必须要有测试。

**写好注释**

**写好文档** ：这里指的是 “设计文档”，而不是接口或使用文档，后者我一般会倾向于使用自文档。设计文档至少应该编写两次，第一次是在项目或任务开始前，第二次是在结束后。它主要记录项目的目的、整体的构思、所使用的的技术、架构等，它是战略层面的指导方针，编写该文档的过程其实是理清自己思路的过程。如果发现自己无法完成该文档的编写，那很有可能是有些地方根本没想清楚，或者目标或需求很不明确。

**模块化** ：与此相类似的还有组件化、微服务化，不过模块化是针对代码功能层面的。模块化有不少优点，比如：系统整体结构清晰，功能结构一目了然；模块之间相互解耦，便于开发和维护；模块可复用程度高，减少代码重复。感觉就像 “代码即注释” 一样，程序员天然就会在不知不觉地模块化。

**DRY** ：对这条原则的基本感悟是：如果代码重复第二次，就应该考虑将其变为独立功能的函数；如果重复第三次，涉及到的所有代码应该已经很好地被重构了。如果有能更省事的方法，就尽量尝试去用，比如泛型编程、宏、模板。

**分层和抽象** ：比如分词可能是一个底层服务，情感分类模型可能就是上一层的服务，对话机器人可能是更高一层的服务。不同层的抽象程度不同，底层服务相比高层服务往往更加抽象，因为它们可能被多个不同的上层调用。这样设计的主要目的还是清晰、隔离、解耦、重用，服务彼此独立且又相互依赖，通过组合形成完整的系统。

### 8、运维监控

**运维就是我们的感官，系统现在的 “状态” 如何全靠它来展示** 。这里的状态主要可以分为两个方面：数据流转和服务负载。

数据流转是指流程中各个节点的输入和输出情况，节点可能是某一个服务，也可能是一个组件，甚至是一个模型，关注的是流转的内容是否正常。服务负载是指流程中各个节点的流量情况，关注的是流量的大小。这块内容之前涉及的很少，只是简单地用过一下 Istio，暂时也没有太多的感受。先记录几个自己的直观认知。

* 配置化。主要针对运维，理想状态下每个模块都有对应的配置文件，整个系统的组织就是对配置文件的组织。这在 k8s 里面是自然的，我们可以通过配置文件很方便地指定环境变量、资源配置、服务策略等，然后可以用命令行工具一键（或自动）启动或更新。与此相反的是人工手动操作或代码里写死，要尽量避免类似的行为，如果需要通过外部变量影响模块内部行为或模式，最好将这些变量映射到外部配置文件。
* 系统化。主要针对监控，当我们设计监控系统时，不仅要考虑关注的节点，还要考虑这些节点状态信息如何传输、存储、读取、展示，以及数据的流动是否会影响系统性能等多个方面。可以做个很简单的实验，一个简单的循环操作，每个循环打印结果和不打印结果性能会相差数十倍，如果要写信息，性能相差可能会更大。一个反面的例子就是无脑打印日志，不仅充斥着大量乱七八糟的无效信息，还严重影响性能。

### 9、更新迭代

除了代码层面的可扩展性，还包括版本管理和热更新

正常情况下，版本管理应该至少考虑以下几个方面：

* 需求推动。需求往往来自于三个方面：业务方、运营方和技术规划。无论哪种需求，我们一般都会放在需求池中，然后根据优先级规划不同的版本。版本的迭代可以按周、双周或月，不过个人不太推荐太长的迭代周期。一来容易懈怠，二来可能有新的重要需求不断出现，三来回退麻烦。比较推荐双周一版本，时间分配上，一周用来开发，一周用来规划、测试。
* 语义化版本。这个本应该是软件开发的常识，其主要思想是将软件的变动记录为 主版本号.次版本号.修订号 的形式，主版本号主要是针对不兼容的改动；次版本号是针对向下兼容的功能新增；修订号是针对向下兼容的功能修正。具体可参考：语义化版本 2.0.0 | Semantic Versioning。这其实是一种基于约定的方法，作为开发人员只要一看版本号就能掌握关于该版本的基本信息。

集群环境下热更新比较方便，我们这里主要说的是模型的更新。具体又包括以下几个方面。

* 模型全量更新。主要是指模型整体升级，比较推荐 tensorflow/serving: A flexible, high-performance serving system for machine learning models，不仅性能优秀，而且可以通过监控模型文件的变化自动升级到新的版本。同时，还支持 RPC 和 RestFul 两种接口，支持版本控制，支持多个模型，简直是业界良心。
* 模型在线学习。主要指模型根据线上数据实时或准实时更新模型的情况。
* 更新毛刺。主要指模型更新前后线上请求出现的延迟抖动现象。一般在规模很大时才会出现。爱奇艺团队针对 Tensorflow Serving 有过不错的改进尝试，被 Tensorflow 官方公号发表，具体参见：[社区分享 | TensorFlow Serving 模型更新毛刺的完全优化实践](https://mp.weixin.qq.com/s?__biz=MzU1OTMyNDcxMQ==\&mid=2247491800\&idx=1\&sn=9c5edb0ef8fb7d4a1b27f1a11d3af272\&scene=21#wechat_redirect)。

## 建议

## 明确自己的发展方向和所需技能点

对于新人，前3年是基础知识和技能的了解和吸收，如果工作合适，那需要做的就是快速了解你所在的领域，然后逐步深入学习。

有所深入，例如我做NLP，尤其是NLU这块，那文本分类、NER、语义相似度之类的常见方法和前沿技术肯定都要了解，丰富自己的武器库，在解决各种问题的时候有足够高的效率，同时对对应任务的数据、常出现的问题要有比较深入的了解，自己一直保持一个习惯，就是每天都要看一些case，专题各有不同，在线的随机query、目前方案做不到的bad case、专题某个领域的专题样本等，所谓的经验其实就是见过一些问题，可以预判会出哪些问题然后知道怎么去解决。

有些广度，了解一些和自己所熟悉的方向接近的事情，甚至还有些深入。例如即使做的是NLP，因为自己做的搜索所以还是要对排序有些了解，如果自己做的对话，那对对话管理一套还是要了解的，技术上，则一些通用的存储和检索工具，如mysql、redis等，甚至到hive、spark一系列的技术，这里就不是局限在“学python还是学c++”的问题了，有时间、有需要随学随用，重要的是不给自己设限，不要认为这是XXX的工作所以我可以不了解，这就限制了自己了。

换个推荐系统的例子吧，大部分学生工作前和工作后的对“推荐系统”的理解差距会非常大，进去前各种模型表征用户行为特性，公平性问题等，但是实际上到了工作中就是“sql boy”，这点NLP也有类似的但是推荐系统尤为明显，主要原因是学校和工程差距大吧，但这是固有的问题，明确回来后就要去开始熟悉和成长，各种数据涉及到的技术要快速掌握，甚至可以专题学习，spark、hadoop等，至于推荐系统需要的技术，现有的项目几乎都有，随便那一段就能学，多和负责这一模块的大佬交流，自然就有了成长，我们应该逐步明白推荐系统不只是有排序，还有试探，还有冷启动，还有物料和用户画像的维护等。

## 不能脱离数据

看数据是一个意识，能有这个意识就已经是新的蜕变，毕竟数据是算法的根本：

* 他是我们深入了解现状的重要窗口。
* 模型训练的根本，质量和数量直接影响了效果。

因此不要抗拒看数据，有的时候简单分析100条就几分钟就完事了，没有很多时间也没有很枯燥，还能去发现一些之前没想到的有意思的情况（你能想到，语义相似度的数据里居然有这样的pair吗：你好吗-how are you），有足够的数据理解，对于科研是思路的启发，对工作落地那就是技术方案设计的依据了。

但是，真正难的其实应该是分析的思路和角度。同样的实地考察不同人会有不同的结论从而采取了不同的解决方案，很想展开聊，然而可能因为我段位不够，而且问题多种多样，我暂时还没有总结出比较完整的分析方法论，还需要持续修炼吧。

## NLP方向的成长

感觉可能会有人会要到这个东西，简单给大家列举一下新人可以了解和学习的一些建议吧。

* 了解1-2个基本任务，知道他的多种方案并且能明确分析他们的区别和应用场景。
* 会解决问题，能分析出一个目前效果不佳的原因并进行优化（绝对不是调参换模型那么简单！）
* 性能意识，知道自己用的方法性能是什么样的，是否符合需求。
* 会基本的数据处理和挖掘，至少能把想法翻译为代码。
* 大规模预训练语言模型只有很少一部分项目会用到，当然越成熟大团队越可能使用。

工程上：

* 代码是要会写的，不要问是java、c++还是python，这里需要的不仅是会代码本身，需要的是快速掌握和现炒现卖的能力。（当然系统学习是可以在业余时间补充）
* 基本的代码会写。
* 编译、打包、部署。
* 基本的数据库要懂，mysql、hive、redis等，原理倒是不优先，重要的是会用，会存会取。
* 计算机网络和数据结构与算法，没学过的最好补一下，有个基本的认识。

## 推荐系统的成长

推荐系统也顺带简单聊一下。

* 推荐系统是系统工程，不是只有一两个部分就能完成任务的，能尽快了解所在产品的全貌就尽可能快速了解。
* 多看数据，多看内部已有的策略。
* 了解1-2个模型任务的特征、离线训练数据构造、训练过程、在线使用的全流程，确保自己能学会。
* AB实验的指标设计、计划安排。

工程上：

* 推荐系统大都用java，和上面一样做，没什么好说的。
* 推荐系统非常依赖数据，因此sql一定一定要熟练掌握，另外spark、hadoop也要懂。
* 计算机网络和数据结构与算法，没学过的最好补一下，有个基本的认识。

## 面试

常见问题

* 数据量，问：处理过的最大数据量
* 部署：训练好的模型如何部署至业务
* 更新监控：线上模型如何更新和监控效果
* 算法选择：现在业务有XX问题，选择何种算法解决

职业未来发展方向

* 算法专家——原理和业务
* 解决方案架构师——懂数据和技术
* CIO——管理
* 数据科学家——前沿科学问题 前两种更常见

一般面试比较看中的是： 1、技术方向是否与目前企业的项目（业务场景）一致 2、以往的项目经验、比赛经验 3、学习能力、业务理解能力等其他软性因素

入门能回答下列问题： 1、X算法的原理是什么？ 2、Y问题要用什么算法解决？ 3、Z场景下如何应用算法？ 回答问题所需知识 微积分、线性代数、概率论、神经网络 etc. 分 类、聚类、回归、生成 etc. 采集、处理、训练、优化、部署、反馈 etc

算法的基本认识。树模型一定要牢固掌握。方向相关的深度学习模型，以及深度学习模型中 一些小trick，如dropout、正则、Batch-Norm等。 过硬的代码能力。python，java语言，数据结构 数据处理和分析的能力。spark sql和hive，excel 关于hive，掌握一些常用的函数的使用方法，如 concat\_ws,row\_number,case..when,if,get\_json\_object等等 对于spark sql，掌握其运行的基本原理，以及一些常见问题的处理方法。首先，学会如何 处理数据倾斜，其次，学习如何尽量减少spark任务的空间占用，同时加速spark任务运行速 度。spark可以通过很多语言来实现，不过我建议还是学习一下scala吧，毕竟可以和java无 缝衔接。 模型的积累和迁移能力。在工作中，模型的积累同样是必要的，如Transformer、Bert、 DIEN等都是这几年才提出的新模型，对这些模型，我们需要不断跟进积累。同时，光了解这 些模型怎么做的还不行，关键是了解这些模型为什么要这么做，还需要与自己的业务相结 合，针对一个业务问题，如果你能快速想到一个可行的解决方案，那么说明你的积累还算凑 合。同时学会反思能不能将其部分的思想应用于你的业务问题，并对现有的模型进行改进。 产品能力 软实力包括思维能力、沟通能力、表达能力、文化修养、学习能力、团队协作能力等等

1.编程 书：《计算机程序的构造和解释》，视频：SICP课程（Berkeley的61A）前三章及其练习 2.计算机架构 《计算机系统要素》前六章及其项目练习，Nand2Tetris网站有书，Coursera上视频 Patterson《计算机组成与设计》，Berkeley的CS61C课程“计算机架构中的伟大思想” 3.算法与数据结构 steven skiena《算法设计手册》，skiena的视频课程，Tim Roughgarden的课程 （coursera），Leetcode，《怎样解题》 4.数学知识 Laszlo Lovasz的课程笔记（离散数学），《计算机科学中的数学》MIT有视频课程和笔记 Essence of linear algebra系列视频，Gilbert Strang的著作和视频 5.操作系统 《操作系统导论》 6.计算机网络 《计算机网络：自顶向下方法》，尤其其中的“Wireshark labs” Introduction to Computer Networking，stanford

1.了解NLP的最基本知识 Jurafsky和Martin的Speech and Language Processing Chris Manning 的 introduction to information retrieval 不需要记住所有细节，但轮廓需要了解 2. 了解早年经典的NLP模型以及论文 1) chris dyer 组的Incorporating structural alignment biases into an attentional neural translation model (NAACL16) 3. 了解机器学习的基本模型 Pattern Recognition and Machine Learning 4. 多看NLP其他子领域的论文 MT，信息抽取，parsing，tagging，情感分析，MRC等等 5. 了解CV和data mining领域的基本重大进展 NLP领域里面一些重要的文章其实或多或少借鉴了CV里面的思想 graph embedding近两年崛起于data mining领域

参考：

[一个「菜鸟」转行AI的成长心得！](https://mp.weixin.qq.com/s/p65FvXQxXMEFFXwMBAYJTg)

[心法利器\[30\] | 算法新人如何在工作中成长](https://mp.weixin.qq.com/s/SE8NKbLyGIsv-Yyj7WfRmw)

[两年美团算法大佬的个人总结与学习建议](https://cloud.tencent.com/developer/article/1423341)

[经验 | 初入NLP领域的一些小建议](https://mp.weixin.qq.com/s/rGLuqF_Bm2B9J_SblcwO-w)
