本文不适合经验丰富的软件工程师。本文面向的数据科学家和机器学习(ML)从业者,像我一样,他们没有软件工程背景。
我们在工作中大量使用 Python。为什么?因为它对 ML 和数据科学社区非常出色。
它正在成为现代数据驱动分析和人工智能(AI)应用程序中增长最快的主要语言。
然而,它也被用于简单的脚本目的,比如自动化操作、测试假设、创建用于头脑风暴的交互式图表、控制实验室仪器等。
但问题在于。
用于软件开发的 Python 和用于脚本编写的 Python 并不完全相同——至少在数据科学领域如此。
脚本编程(大多数情况下)是你为自己编写的代码。软件是你(和其他团队成员)为他人编写的代码集合。
明智的做法是承认,当(大多数)数据科学家,他们没有软件工程背景时,编写用于 AI/ML 模型和统计分析的 Python 程序,他们往往是为了自己。
他们只是想快速找到隐藏在数据中的模式的核心。不加深思地进行,不考虑普通用户的需求——用户。
他们编写了一段代码来生成丰富美观的图表。但他们并没有从中创建一个函数,以便以后使用。
他们从标准库中导入了大量的方法和类。但他们并没有通过继承创建自己的子类并为其添加方法以扩展功能。
函数、继承、方法、类——这些是稳健的**面向对象编程(OOP)**的核心,但如果你只是想创建一个包含数据分析和图表的 Jupyter 笔记本,它们是可以有所回避的。
你可以避免使用 OOP 原则的最初痛苦,但这几乎总是会使你的 Notebook 代码无法重用和扩展。
简而言之,那段代码只对你有用(直到你忘记了具体编码的逻辑),而对其他人没有任何帮助。
不过,可读性(从而可重用性)至关重要。这是你所生产的代码真正的价值所在。不仅仅是为了自己,而是为了他人。
实际上,数据科学家Will Koehrsen刚刚写了一篇关于这个理念的精彩文章。
《代码大全:软件构建实用手册》的经验教训及其在数据科学中的应用。
最重要的是,数以百计的热门 MOOC 或在线数据科学和 AI/ML 课程也没有强调这方面的编码,因为这对年轻的、充满热情的学习者来说感觉是一种负担。他/她来到这里是为了学习酷炫的算法和神经网络优化,而不是 Python 中的面向对象编程(OOP)。因此,这一方面仍然被忽视。
那么,你可以做些什么呢?
我不是软件工程师,也从未在我的生活中担任过这一职务。因此,当我开始探索机器学习和数据科学时,我写了大量粗糙、不可重用的代码。
我正在逐渐改进自己的编码风格,通过简单的增强使其对全世界的任何人更有用。
我发现,开始在你的数据科学代码中混合 OOP 原则并不需要太多努力。
即使你一生中从未上过软件工程课程,一些理念也可能会自然地浮现。你所需要做的就是站在他人的角度思考,考虑那个人如何以建设性的方式使用你的代码。
-
如果你的分析中有一个代码块出现了多次(无论是完全相同的形式还是稍有变化的形式),你能将其制作成一个函数吗?
-
当你制作这样的函数时,哪些参数将会被传递?其中哪些可以是可选的?默认值是什么?
-
如果你遇到一种情况,不知道需要传递多少参数,你是否使用了 Python 提供的*args、**kwargs?
-
你是否为那个函数写了文档字符串,以便让其他人知道该函数的功能和期望的参数,或许还包括一个示例?
-
当你收集了一堆这样的实用函数后,你是否还在使用同一个 Notebook,还是 切换到一个新的、干净的 Notebook 并仅调用“from my_utility_script import func1, func2, func3”(你是否将 my_utility_script 创建为一个简单的 Python 文件,而不是 Jupyter Notebook)?
-
你是否将my_utility_script放在一个目录中, 在同一目录下放置一个*init.py* 文件(即使是空白的),并将其设为 Python 模块以便像 NumPy 或 Pandas 一样可以导入?
-
你是否考虑不仅仅是从像 NumPy 和 TensorFlow 这样的优秀包中导入类和方法,而是 向它们添加你自己的方法并扩展其功能?
我说的这些到底是什么意思?让我们通过一个简单的案例来演示——一个使用fashion MNIST 数据集的深度学习图像分类问题。
详细的 Notebook 见我的 Github 仓库。鼓励你浏览它,并为自己的使用和扩展进行分叉。
代码对构建出色的软件至关重要,但未必适合 Medium 文章,因为你阅读这些文章是为了获得见解,而不是进行调试或重构练习。
因此,我将挑选一些代码片段,并尝试指出我在此 Notebook 中如何尝试编码之前详细介绍的一些原则。
核心机器学习任务很简单——为 fashion MNIST 数据集构建一个深度学习分类器,这是对原始著名 MNIST 手写数字数据集的有趣变体。Fashion MNIST 包含 60,000 张 28 x 28 像素的训练图像——与时尚相关的物体,例如帽子、鞋子、裤子、T 恤、连衣裙等。它还包括 10,000 张用于模型验证和测试的测试图像。
Fashion MNIST (https://github.com/zalandoresearch/fashion-mnist)
但是如果围绕这个核心机器学习任务有一个更高阶的优化或视觉分析问题——模型架构复杂性如何影响达到期望准确度所需的最小训练轮次?
应该让读者明白,我们为什么要关注这样的问题。因为这与整体业务优化相关。训练神经网络不是一个简单的计算问题。因此,调查 为达到目标性能指标所需的最少训练努力,以及架构选择如何影响这一点 是有意义的。
在这个例子中,我们甚至不会使用卷积网络,因为一个简单的密集连接神经网络可以实现相当高的准确度,实际上,稍微不理想的性能是为了说明我们提出的高阶优化问题的主要点。
所以,我们必须解决两个问题 -
-
如何确定达到期望准确度目标所需的最小轮次?
-
模型的具体架构如何影响这个数字或训练行为?
为了实现目标,我们将使用两个简单的面向对象原则,
-
创建一个 从基类对象继承的类
-
创建实用函数并从紧凑的代码块中调用它们,这些代码块可以呈现给外部用户以进行高阶优化和分析。
这里我们展示了一些代码片段,以说明如何利用简单的面向对象原则来实现我们的解决方案。这些片段带有注释,便于理解。
首先,我们继承一个 Keras 类,并编写我们自己的子类,添加一个用于检查训练准确度的方法,并根据该值采取行动。
这个 简单回调 结果是 动态控制训练轮次——当准确率达到所需阈值时,训练会自动停止。
我们将 Keras 模型构建代码放在一个实用函数中,以便通过一些函数参数的简单用户输入,可以生成具有任意层数和架构(只要它们是密集连接的)的模型。
我们甚至可以将编译和训练代码放入一个实用函数中,以 方便地在高阶优化循环中使用这些超参数。
接下来是可视化的阶段。在这里,我们仍然遵循功能化的实践。通用的绘图函数将原始数据作为输入。然而,如果我们有特定的绘图目的,如绘制训练集准确率的演变并显示它如何与目标进行比较,那么我们的绘图函数应该仅接受深度学习模型作为输入并生成所需的图表。
一个典型的结果如下所示,
现在我们可以利用之前定义的所有函数和类,将它们整合在一起完成更高阶的任务。
因此,我们的最终代码将非常紧凑,但它将生成与上述相同的有趣的损失和准确率图表,针对各种准确率阈值和神经网络架构。
这将使用户能够使用极少量的代码来生成关于性能指标(在此情况下为准确率)和神经网络架构的可视化分析。这是构建优化机器学习系统的第一步。
我们生成了一些案例用于调查,
我们最终的分析/优化代码简洁且易于高层用户理解,不需要了解 Keras 模型构建或回调类的复杂性。
这就是面向对象编程的核心原则——抽象复杂性层次,我们能够为我们的深度学习任务实现这一点。
请注意,我们如何将print_msg=False
传递给类实例。在初步检查/调试时我们需要基本的状态打印,但在优化任务中我们应该静默执行分析。如果我们在类定义中没有这个参数,那么我们将没有办法停止打印调试信息。
我们展示了一些代表性结果,这些结果是从执行上面的代码块自动生成的。它清楚地展示了通过极少量的高级代码,我们能够生成可视化分析,以判断各种神经网络架构在不同性能指标水平上的相对性能。这使得用户在不调整底层函数的情况下,能够轻松地根据自己的性能需求判断模型的选择。
另请注意每个图表的自定义标题。这些标题清晰地阐明了目标性能和神经网络的复杂性,从而使分析变得简单。
这只是对绘图实用函数的小小补充,但这展示了在创建这些函数时需要细致规划。如果我们没有为该函数规划这样的参数,就无法为每个图生成自定义标题。这种对 API(应用程序接口)的细致规划是良好面向对象编程的一部分。
到目前为止,你可能在使用 Jupyter 笔记本,但你可能希望将这个练习转化为一个整洁的 Python 模块,你可以随时导入。
就像你写“from matplotlib import pyplot”一样,你可以在任何地方导入这些实用函数(Keras 模型构建、训练和绘图)。
我们展示了一些简单的良好实践,借鉴了面向对象编程,以应用于深度学习分析任务。对于经验丰富的软件开发人员来说,这些做法可能显得微不足道,但这篇文章是为那些可能没有这种背景但应该理解将这些良好实践融入他们的机器学习工作流程的重要性的初学数据科学家们准备的。
冒着重复自己的风险,让我再次总结一下好的实践,
-
每当有机会时,将重复的代码块转化为实用函数。
-
仔细考虑函数的 API,即需要哪些最少的参数集以及它们如何服务于更高层次的编程任务。
-
不要忘记为函数编写文档字符串,即使它只是一个单行描述。
-
如果你开始积累许多与同一对象相关的实用函数,考虑将该对象转化为类,并将这些实用函数作为方法。
-
每当有机会时扩展类功能,通过继承来完成复杂的分析。
-
不要停留在 Jupyter 笔记本上。将它们转化为可执行脚本并放入小模块中。养成模块化工作的习惯,这样可以方便地被任何人、任何地方重用和扩展。
谁知道呢,当你积累了足够多的有用类和子模块时,你可能会在 Python 包存储库(PyPi 服务器)上发布一个实用包。那时你将有资格炫耀发布一个原创的开源包 :-)
如果你有任何问题或想法,请通过tirthajyoti[AT]gmail.com联系作者。此外,你还可以查看作者的GitHub 代码库,获取 Python、R 或 MATLAB 中的有趣代码片段和机器学习资源。如果你像我一样,对机器学习/数据科学充满热情,请随时在 LinkedIn 上添加我或在 Twitter 上关注我。
个人简介:Tirthajyoti Sarkar 是 ON Semiconductor 的高级首席工程师,专注于基于深度学习/机器学习的设计自动化项目。
原文。经授权转载。
相关内容:
-
使用 Python 进行优化:如何用最少的风险赚取最多的财富?
-
如何在 Python 中检查回归模型的质量?
-
数学编程——提升数据科学水平的关键习惯
1. 谷歌网络安全证书 - 快速进入网络安全职业道路。
2. 谷歌数据分析专业证书 - 提升你的数据分析水平
3. 谷歌 IT 支持专业证书 - 支持你所在组织的 IT 需求