随着数据和计算能力的崛起,“机器学习”(ML)和“深度学习”(deep learning)这两个术语已经热议了好几年。虽然追随ML的潮流似乎很酷,但公司的第一步是评估业务是否真的能从中受益 —— 这是一个独立的职位。既然你的公司已经决定ML是一个必要的下一步,那么作为一个ML工程师,现在是时候考虑为生产系统构建ML过程的真正内容了。希望这篇文章能帮助你理解这些问题。
在这篇文章中,只要提到“初创”这个词,就意味着软件“产品”公司,除非特别提到软件“服务”公司。软件产品公司专注于构建自己的软件产品,而软件服务公司(如代理或咨询公司)则为客户构建软件。虽然这篇文章是为早期软件产品初创公司的ML工程师写的,但其中的一些考虑可能仍然适用于其他阶段或公司类型。
找到合适的工具
有多个机器学习软件可供选择,从开源工具如PyTorch, TensorFlow以及scikit-learn到管理机器学习服务的平台如,Google AI平台,亚马逊SageMaker和Azure机器学习平台。仅使用PyTorch和TensorFlow,就有许多像Hugging Face的transformer这样的开源库,它们提供了现成的模型作为起点。此外,ML在不同领域的研究论文每年都会发表,其中一些提供了开源代码。Papers With Code:https://paperswithcode.com/是查找带代码的论文的好资源。
在选择合适的工具时要考虑的关键因素有:
- 文档的质量
- 工具的开发状态(maintained vs. halted or deprecated,问题的严重程度,等等)
- 围绕该工具的其他工具的生态系统
- 开发人员社区对这个工具的参与是否积极
- 你对该工具的熟悉程度
- 使用该工具的团队规模
- 与工具有关的货币成本
就我个人而言,我觉得如果你是一家处于早期阶段的初创公司,你不需要权衡和找出所有这些因素。你总是可以从一个强大的候选工具开始,然后从那里开始。此外,如果你认为收益大于成本,则可以从一开始就使用托管ML服务。
何时开始做机器学习 vs. 机器学习周围的操作
在第一次开始时,最好从一个简单的基线模型开始。从更简单的模型开始可以帮助你调试pipeline中的问题,并帮助你确定更耗时的解决方案是否值得。那么如何建立一个简单的基线模型呢?
首先,“简单”是相对的。在某些情况下,简单实际上意味着一个简单的模型,例如硬编码一些启发式。而在其他情况下,模型本身可能很复杂,但是很容易应用。一些最广泛使用的数据集拥有最先进的模型,这些模型是开源的,列在研究论文或排行榜等地方,斯坦福问答数据集(SQuAD)就有这样一个排行榜。一种方法是查看一些顶级的解决方案,看看你是否能找到附加到相关研究论文的代码。
在早期启动阶段,你可能没有时间立即构建ML流程。通常情况下,你需要专注于让你的投资者和客户很容易就能看到的东西运行起来。调优过程很少出现在他们的脑海中。所以不要担心你最初的部署是否完美,只需要有一个工作成果 —— 一个看得见的最终产品。在基本产品构建之后,你可以更多地担心ML周围的流程,因为通常需要更多的停机时间来对ML流程进行小的、渐进的改进。
相反,如果你是一个代理商,出错的空间就更小,因为你要将完成的产品交付给不同的客户,并试图事先修复所有的bug。在交付一个或一组客户端产品之后,你将转移到下一个客户端合同上,并且通常没有足够的精力来进行进一步的改进。尽管如此,你还是要迅速行动。为了进展得更快,最好有更精细的ML过程。因此,对于代理商模型,从长远来看,也许在优化和自动化上预先花费更多的时间可以节省时间。
实验管理中的考虑
在ML中管理实验不是一件小事,当你在运行尽可能多的实验时,你的项目工作区很容易变得混乱。然而,在初创阶段,你没有几个月的时间来做数百个实验。你只需要推动一些更好的东西,然后尽快更新。不管怎样,进行某种实验管理总比什么都不做要好。以下是管理ML实验时需要考虑的一些事项。
模型版本
在Toucan AI,我们使用GitHub来存储我们代码的版本。GitHub很棒,但它不是用来对大型数据文件进行版本控制的。尽管存储库可以达到100GB,但GitHub建议将存储库的大小保持在1GB以下,此外,单个文件不能超过100MB。
你可以使用其他云存储选项,如谷歌云存储或Amazon S3。使用云提供商的命令行工具或web用户界面,只需创建一个bucket(文件夹),允许对其对象(文件或文件夹)进行版本控制。但是,如果希望将云存储中的文件与GitHub上的项目存储库同步,则需要额外的手工工作。
因此,我们选择了最自然的集成,它结合了Git平台和其他云存储选项的最佳特性:数据版本控制(DVC),被标记为“机器学习项目的开源版本控制系统”。DVC是一个命令行工具,它的子命令与Git子命令非常相似。在Git平台和云存储设置完成后,你可以运行DVC的‘add’和‘push’命令来设置版本,并在云存储中存储文件或文件夹。同时,可以通过DVC文件引用在Git项目存储库中跟踪大型数据文件。DVC的一个优点是只需要一些额外的类Git命令,这与现有的Git工作流没有太大的区别。
实验文档化
如果你正在进行超参数调优,则很容易忽略在指定日期为某个模型运行的特定设置。你可能还需要回顾你为准备或预处理上述模型所需的数据集所做的工作。你的Jupyter Notebooks有描述性的文件名,但是它仍然需要相当多的时间来处理首先发生的事情,或者如果你将预处理A或B应用于实验7。
一种解决方案是,在创建新的Notebook时,将Notebooks编号作为文件名的一部分(我喜欢使用“01_”步骤),以后可以重新编号。对你的Notebooks编号有一个明确的命名约定对你的同事(以及未来的你)了解你是如何进行实验的非常有帮助。除了在实验中为Notebooks编号外,我们还使用开源平台MLflow来提供查看实验超参数和度量结果的web界面。
此外,在记录实验时,力求逻辑结构和简洁。充分利用文件夹结构和名称来组织你的Notebooks和训练脚本。假设当读者查看你的Notebook时,他们会从头到尾地阅读,所以要删除你临时插入的任何“草稿”单元格。根据经验,将Notebook上的试验限制在一个模型和一个数据集上,如果当前的笔记本太长,则创建一个新的笔记本。尽量让你最后的笔记本版本不包含训练或推理代码,这些应该放在可以在Notebook中调用的独立脚本中。最后,当使用像MLflow这样的软件生成实验记录时,尝试将运行实验的笔记本自动引用到生成的实验输出文件中。
测试框架
更好的度量结果并不总是与真实样本中改进的推理性能相关。此外,在生产ML系统中,ML模型并不是独立操作的:例如,你可能将启发式、预处理和缓存作为pipeline的一部分。因此,当尝试改进你已经拥有的ML模型时,你会意识到,生成适合真实世界的推理样本需要大量的时间。你需要深入研究更大的生产代码,以发现你试图改进的模型实际上在什么地方被调用。然后,你不希望只检查模型本身的输入和输出,还要检查整个ML系统的pipeline。你的“更好”模型如何影响整个系统,它是更好还是更坏?
为了将重点放在模型改进上,而不是提出推理样本或担心破坏生产pipeline中的某些东西,我们需要有一个自动化的系统或端到端测试框架。
在Toucan AI,由于我们的主要产品是AI销售代理,测试覆盖主要逻辑分支的样本对话就足够了,同时也提供了一种回归测试的形式。我们目前正在开发一个命令行接口(CLI)工具,它将在一系列示例对话中运行pytest断言。使用一个命令,所有的对话都可以被测试,如果任何测试用例中断,我们可以手动更新测试或者认为我们的“更好的”模型实际上并不更适合生产。
简而言之,有一个适当的测试框架对于理解当前和实验模型在生产ML系统中的表现是至关重要的。有了一个合适的测试框架,你的模型改进pipeline应该更有效地推进,允许你比以前运行更多的实验。
使用工具快速演进
从快速发展的库中提取代码并将其写入使用该库的修改过的旧版本的生产系统是很困难的。如何修改一个快速发展的库以满足你的需要并尽可能高效地应用它的最新更新?
我觉得没有正确的答案,只有许多不同的途径。一种方法是把他们的代码和你的代码结合起来,让它工作。另一种方法是使用他们的代码并完全升级旧版本,但这通常需要更长的时间。简而言之,考虑一下你有多少时间进行重构,以及重构的优先级是什么。在你自己的代码库和快速发展的工具变得更加稳定之后,你应该关注优先级,并考虑完整的重构。
实验整理
当你专注于取得成果时,往往很容易忽略整洁。考虑下一组要运行的实验,以及它的超参数集。发生了一个错误?没问题,更改输出文件夹上的时间戳并重新运行实验。然而,你最终得到的是由于试验不完整而生成的额外文件或文件夹。之后,你在MLflow中滚动一长串记录,寻找完成的实验,结果却让他们摸不着头脑。
解决方案是自动删除不想保存的所有试运行。例如,最好在第一次训练迭代完成执行之前就删除失败的运行。为了我们的同事和未来的自己,我们都应该尽我们最大的努力保持实验池的整洁。
关注点的分离
当你研究并尝试各种ML项目以希望改进你的模型时,你将遇到相互冲突的Python包需求。你最初可能在两个开发人员之间共享一个云服务器,但这很快就变得不方便了,因为你的安装可能会覆盖你的同事的运行环境。
进入Docker生态系统,这是一个轻量级的容器化软件平台,用于管理你的项目环境和依赖项。通过为每个ML模型和应用程序服务使用单独的Docker容器,我们可以主动减少“它在我的机器上工作”问题的数量,并防止项目之间发生依赖冲突。与其设置更多的开发服务器,你的每个同事可以在单个共享服务器上设置自己的Docker容器,如果这样做更划算的话。
此外,你可能想知道,为什么选择Docker而不是Conda,而且Conda还允许你使用不同的包版本创建不同的环境。我们选择Docker是因为它提供的工具更适合于生产和处理云的环境。如果要在远程机器上使用Conda,则必须先与机器连接并处理文件传输。但是,只需在Docker中使用几个命令,你就可以对本地文件进行更改,并将它们反映到远程机器的Docker容器中。此外,运行项目所需的一切都在Dockerfile或Docker Compose文件中指定。
另一方面,对于Conda,如果不引用README,就不清楚是否需要其他步骤。最后,利用Docker Compose的强大功能,如果ML项目需要运行其他服务,它们可以在其他Docker容器中单独运行,并根据Docker Compose文件设置相互通信。据我所知,在Conda中不能跨环境通信。
准备好做扩展
作为一个处于早期阶段的初创公司,你现在可能不需要扩大规模,但最好是开始考虑可以扩大规模的技术。其中一种技术是Celery,这是一个异步任务队列系统,可以将任务分配给多个工作者。目前,对于每种类型的服务(服务器、客户机、embeddings模型等),我们都有一个单独的worker,但是如果有必要的话,为相同的服务启动更多的worker应该不需要太多的工作。通过嵌入进行缓存会成为瓶颈吗?没问题,让我们启动另一个嵌入的Celery工作程序,或者增加当前工作程序的“并发”计数,它允许多个子进程并行运行。在我们的Toucan AI配置中,一个Celery worker在一个Docker容器中运行,因此也遵循关注点分离。
除了允许你的生产系统扩展之外,Celery还非常适合执行长时间运行的任务,比如ML模型推断任务。与允许服务器响应挂起不同,服务器响应(代理的应答)可以立即返回给与Toucan AI代理对话的最终用户,而异步任务(如缓存机制)可以在后台悄悄运行。此外,我们使用Celery beat来运行我们每天计划的分析工作者任务。
与同事和未来的你合作
随着ML研究的不断发布,作为ML工程师,你如何让你的团队成员保持一个从尝试到使用的模型或技术的循环?没有什么魔法可以将他们获得的所有知识、经验和洞察力传授给你。但是你能做的就是交流。经常交流。
尽可能的交流,尤其是在写文档的时候。因为通常情况下,你是在做你自己的项目,所以你现在正在做的事情可能和你的同事正在做的事情并不完全相关。然而,将来他们可能需要审查或扩展你已经实现的内容。甚至可能是你几个月后,不得不对你自己的项目做出改变,而你已经忘记了其中的关键部分。文档,文档,文档。怎么强调都不为过。
另一方面,肯定会有文档不够用的时候。如果你有一些东西不确定,你想要他们的诚实的意见,你觉得说话是一个更有效的沟通媒介的时候,注意你的同事的精神集中的状态,寻求与他们讨论项目的方向。从一开始就尽可能的清晰,这对于防止误解,徒劳的工作,和悔恨是非常重要的。
作为机器学习工程师的内部斗争
作为一名机器学习工程师,你必须学会权衡好你想要修复的东西的想法以及让流程变得更好以完成当前的需求之间的关系。你必须学会接受采取最直接的方式来完成工作。例如,我很想花时间改进第三方的训练/评估代码,但当时,我只需要采用最短路径来查看我们的推理结果是否会得到改进。
由于我有web开发的背景,所以大多数情况下我必须编写自己的代码,但是在ML工程中,我必须学习如何应用其他人的代码。当你经常使用的代码不是你自己的 —— 通常是学生和研究人员花了几个月或几年研究出来的代码 —— 有时很难不觉得自己很失败,尤其是当你试图理解不是直接部署到生产系统中的那方面的代码的时候。
在一天结束的时候,只要记住我们是天性好奇的生物,想要学习比你需要学习的更多是可以的。如果有你想探索的途径,与你的队友保持透明是关键。一个好的工作环境不会因为你想要学习更多而责备你,如果你足够及时地实现了目标。只要你有优先考虑的事情,尽量少担心,多享受。
结论
为生产系统构建ML流程并不简单。尽管在这篇文章中提到了所有的内容,有时候,你能做的最好的决定就是简单地做一个决定。如果没有成功,那就继续下一条路。不管怎样,我希望这篇文章能够帮助我们更好地理解各种不同的想法。