本文介绍了机器学习应用中的一些软件工程基础,快速浏览了最流行的一些架构模式、设计模式,以及面向对象设计的 SOLID 原则,目的是让读者尽可能多地了解构建可扩展软件的主要贡献因素。应用程序设计是否能够适应变化,是构建成功解决方案的关键,如果设计过程很仓促,项目结束时,一定会为犯下的错误交学费。
本专题将通过 两篇系列文章 ,介绍与体系结构和设计有关的软件工程基础知识,以及如何在机器学习管道(ML Pipeline)的每个步骤中应用这些基础知识:
第 1 部分 :问题陈述 |架构风格 |设计模式 |SOLID
第 2 部分 :构建机器学习管道
就像以前在 Steven Geringer 著名的 维恩图 中看到的,数据科学是 3 个学科的交集:计算机科学、数学 / 统计学和特定的领域知识。
拥有基本(甚至是高级)的编程技能,是将端到端的经验结合在一起的关键,但还不足以创建一个可以发布的应用程序。除非从 IT 背景 进入数据科学和机器学习(ML),并且在构建企业级的、分布式的、可靠的系统方面拥有实际经验,否则您的 Jupyter 笔记本( 译者 : 可创建和共享自己的文学化程序文档)没资格成为优秀的软件,遗憾的是,您也不能成为软件工程师!
虽然您打算构建的是一个很好的预测产品的 原型 ,但仍然需要通过工程技术的路线图来推动它。所需要的是一个由专业软件工程师组成的团队,可以将(一次性)概念验证转化为 高性能 、 可靠 、 松耦合 和 可扩展的 系统!
一切都是设计出来的 ; 但设计得好的东西很少!
在本系列中,我们将展示如何实现良好设计的一些想法。第一部分从基础知识开始,第二部分逐步设计整体架构。这里建议的架构是 技术无关的, 机器学习管道将被分解成责任明确的不同层,可以从许多技术堆栈中选择如何来实现每一层。
接下来看看成功的解决方案应该是如何实现的。
我们的主要目标是建立一个系统,具备以下特性:
▸减少延迟;
▸集成系统的其他部分(例如数据存储,报告,图形用户界面),但松耦合 ;
▸可以水平和垂直伸缩 ;
▸消息驱动,即系统通过异步、非阻塞的消息传递进行通信 ;
▸提供工作负载管理相关的高效计算 ;
▸容错和自我修复,即故障管理 ;
▸支持批量和实时处理。
我们将首先介绍一个反应式系统,并快速浏览最流行的架构模式。
反应式系统设计范式采用前后一致的方法来建立更好的系统,这些系统是根据“ 反应式宣言” 的原则进行设计的。每个反应式原则对应一个可扩展性的重要系统维度:
• 易响应的 → 时间
• 易扩展的 → 负载
• 可恢复的 → 错误
• 消息驱动的 → 通信。
SOA 将业务问题分解为服务,这些服务通过网络来共享信息。它们还共享代码(即公共组件),以保持反应式系统的一致性和特性,从而减少开发工作。
服务 提供者 发布合约,规定服务的特征以及如何使用服务。服务 使用者 可以在注册中心中找到服务元数据,开发所需的客户端组件来绑定和使用它。
协调器(Orchestrator)是一个综合服务,负责调用和组合其他服务。此外,编排(Choreography)采用去中心化的方法组合服务,服务通过消息 / 事件的交换进行交互。
流式架构包括以下组件:
Lambda(λ)架构旨在以集成的方式处理 实时 和过去聚合的 批量数据 。它分离了实时和批处理的职责,查询层提供了所有数据的统一视图。
这个概念很简单:生成数据时,会在存储之前对其进行处理,因此分析可以包括最后一秒、最后一分钟或最后一小时生成的数据,只处理传入的数据,而不是所有的数据。
微服务是一种架构风格,它将应用程序构造为小型、自主、松耦合和协作的服务集合,围绕业务领域进行建模。这些服务使用同步协议(HTTP/REST)或异步协议(AMQP)进行通信,可以彼此 独立 地开发和 部署 。每个服务都有自己的数据库,以便与其他服务分离。
REST 是一种用于开发 Web 服务 的架构风格,它建立在 Internet HTTP 的现有特性之上,允许以无状态的方式传输、访问和操作文本数据,即应用程序可以在不知道状态的情况下进行通信。
RESTful API 服务通过统一资源定位器(URL)公开,提供了创建、请求、更新或删除(CRUD)数据的功能。它最适合处理解耦(生成 / 消费的 ) 信息和(生成 / 使用信息的 ) 技术的系统。
我们将浅显地介绍这个主题,并且只讨论我在本系列的第二部分中可能提到的那些模式。(虽然我每天都在使用它们,但很难用简单的语言全部解释清楚)
软件设计模式是针对软件工程中常见问题的优化的、可重复的解决方案。它是一个解决问题的模板,可以在许多不同的情况下使用。
策略模式定义了一系列算法,将每个算法放在一个单独的类中,相互之间 可互换 。将行为封装在单独的类中,消除了任何条件语句,在运行时可以选择正确的算法(即策略)。
模板方法旨在从不同的过程中抽象出一个通用的过程。它定义了算法的 骨架 ,将某些步骤推迟到子类。子类可以覆盖某些行为,但不能更改骨架。
:star: 与策略模式的区别 :
责任链模式通过启用一个或多个 处理程序 来满足请求,避免将客户端(请求的发送者)与接收者耦合。这些处理程序连接到一个链中,每个处理程序都包含对链中下一个处理程序的引用。
观察者模式(缩写为 Publish/Subscribe 或简称 PubSub)通过定义对象之间的一对多依赖关系,可以轻松地进行通信 广播 ,当一个对象的状态发生变化时,它的所有依赖关系都会自动得到通知和更新。观察者负责注册它们所“观察”的事件。
建造者模式旨在 逐步 构造一个复杂的对象,分离构造与表示。实质上,它允许使用相同的代码,生成对象的不同类型和表示形式。
工厂方法定义了一个用于 创建对象 的接口,由子类来完成实例化。
抽象工厂关注如何创建 相关产品的系列, 不指定它们具体的类。
:star:️ 与抽象方法的区别 :
装饰模式动态地将新的职责附加到对象上,将对象放置在包含行为的特殊包装类(wrapper)中,因此不会影响原来方法的签名(继承上的组合)。
仓库模式解决了数据检索和持久化的代码集中化问题,并为 数据访问 操作提供了 抽象, 类似内存中域对象的集合,允许执行 CRUD 方法,消除了各种数据库问题。
想进一步了解模式?可以先从“Gang of Four”的书《 设计模式:可重用的面向对象软件的基础 》开始。下面的图展示了模式之间的关系,非常重要:
我们唯一的设计原则是 SOLID,它们对于每个软件开发人员来说都是必不可少的。
正如 Bob 大叔 所说: “ 它们不是法律,也不是完美的真理。它们类似于一个规则:每天一个苹果,医生远离我 ”。
这意味着,它们不是某种“魔法”,不是带来牛奶、蜂蜜和优质软件的应许之地,它们是健壮、持久软件的关键贡献者。
简而言之,这些原则围绕两个主要的概念展开,是成功企业应用程序的基石: 耦合 是类了解另一个类并与之交互的程度;而 内聚 表示类具有单一用途的程度。换一种说法:
- 耦合是关于类如何相互作用的;
- 内聚关注单个类的设计方式。
这是不言自明的,但说起来容易做起来难。我们总是想在现有类中添加新的行为,但这是一个引发灾难的处方:每个行为都可能成为未来变化的理由,因此行为越少,在变化时产生错误的机会就越少。
类应该对扩展“开放”,但是对修改“关闭”。要实现这一点可以通过继承,即创建一个子类,关闭对原始类进行修改,将自定义的代码添加到子类来引入新的行为。
当类 A 的行为扩展到子类 B 时,可以确保在不造成任何破坏的情况下用 B 替换 A。这点可能比较吸引人,特别是当把这一原则与开闭原则结合起来时。
接口和类必须尽可能特定,客户端的调用不依赖未使用的方法。这与单一责任原则是一致的。
高层的类不应该依赖于低层的类。它们都应该依赖于抽象。同样,抽象不依赖于细节,细节依赖于抽象。
我创建了下面这个快速参考图。如果想知道左边小符号的灵感来自哪里,请参看:“ SOLID 原则,用励志海报解释 ” —— 我喜欢作者在这些原则上增加的有趣改变???。
这里无法涵盖所有软件工程概念的详尽列表,它只是阅读下一篇文章的基础,我希望它能让读者了解构建可扩展软件的主要贡献因素。应用程序设计是否能够 适应变化 ,是构建成功解决方案的关键,如果设计过程很仓促,项目结束时,一定会为犯下的错误交付学费。
好的设计是易懂的。伟大的设计是透明的。
原文链接: https://towardsdatascience.com/being-a-data-scientist-does-not-make-you-a-software-engineer-c64081526372