写点什么

构建可扩展的机器学习系统(一):你所需的架构设计知识

  • 2019-05-20
  • 本文字数:4030 字

    阅读完需:约 13 分钟

构建可扩展的机器学习系统(一):你所需的架构设计知识

本文介绍了机器学习应用中的一些软件工程基础,快速浏览了最流行的一些架构模式、设计模式,以及面向对象设计的 SOLID 原则,目的是让读者尽可能多地了解构建可扩展软件的主要贡献因素。应用程序设计是否能够适应变化,是构建成功解决方案的关键,如果设计过程很仓促,项目结束时,一定会为犯下的错误交学费。

正文

本专题将通过两篇系列文章,介绍与体系结构和设计有关的软件工程基础知识,以及如何在机器学习管道(ML Pipeline)的每个步骤中应用这些基础知识:


第1部分:问题陈述 |架构风格 |设计模式 |SOLID

第2部分:构建机器学习管道

介绍

就像以前在 Steven Geringer 著名的维恩图中看到的,数据科学是 3 个学科的交集:计算机科学、数学/统计学和特定的领域知识。



数据科学维恩图[©Steven Geringer]


拥有基本(甚至是高级)的编程技能,是将端到端的经验结合在一起的关键,但还不足以创建一个可以发布的应用程序。除非从 IT 背景进入数据科学和机器学习(ML),并且在构建企业级的、分布式的、可靠的系统方面拥有实际经验,否则您的 Jupyter 笔记本(译者: 可创建和共享自己的文学化程序文档)没资格成为优秀的软件,遗憾的是,您也不能成为软件工程师!


虽然您打算构建的是一个很好的预测产品的原型,但仍然需要通过工程技术的路线图来推动它。所需要的是一个由专业软件工程师组成的团队,可以将(一次性)概念验证转化为高性能可靠松耦合可扩展的系统!


一切都是设计出来的; 但设计得好的东西很少!


在本系列中,我们将展示如何实现良好设计的一些想法。第一部分从基础知识开始,第二部分逐步设计整体架构。这里建议的架构是技术无关的,机器学习管道将被分解成责任明确的不同层,可以从许多技术堆栈中选择如何来实现每一层。


接下来看看成功的解决方案应该是如何实现的。

问题陈述

我们的主要目标是建立一个系统,具备以下特性:


▸减少延迟;

▸集成系统的其他部分(例如数据存储,报告,图形用户界面),但松耦合

▸可以水平和垂直伸缩

▸消息驱动,即系统通过异步、非阻塞的消息传递进行通信

▸提供工作负载管理相关的高效计算

▸容错和自我修复,即故障管理

▸支持批量和实时处理。

架构风格

我们将首先介绍一个反应式系统,并快速浏览最流行的架构模式。

反应式系统

反应式系统设计范式采用前后一致的方法来建立更好的系统,这些系统是根据“反应式宣言”的原则进行设计的。每个反应式原则对应一个可扩展性的重要系统维度:


• 易响应的 → 时间


• 易扩展的 → 负载


• 可恢复的→ 错误


• 消息驱动的 → 通信。



反应系统的特性

面向服务架构(SOA)

SOA 将业务问题分解为服务,这些服务通过网络来共享信息。它们还共享代码(即公共组件),以保持反应式系统的一致性和特性,从而减少开发工作。


服务提供者发布合约,规定服务的特征以及如何使用服务。服务使用者可以在注册中心中找到服务元数据,开发所需的客户端组件来绑定和使用它。


协调器(Orchestrator)是一个综合服务,负责调用和组合其他服务。此外,编排(Choreography)采用去中心化的方法组合服务,服务通过消息/事件的交换进行交互。



SOA

流式(Streaming)架构

流式架构包括以下组件:


  • 生产者:生成和发送消息的应用程序

  • 消费者:订阅和使用消息的应用程序

  • 主题:类别特定的记录流,存储成有序和不可变的记录序列,通过分布式集群进行分区和复制

  • 流处理器:以特定方式处理消息的应用程序(例如数据转换、ML 模型等)。



流式架构

Lambda 架构

Lambda(λ)架构旨在以集成的方式处理实时和过去聚合的批量数据。它分离了实时和批处理的职责,查询层提供了所有数据的统一视图。


这个概念很简单:生成数据时,会在存储之前对其进行处理,因此分析可以包括最后一秒、最后一分钟或最后一小时生成的数据,只处理传入的数据,而不是所有的数据。



Lambda 架构

微服务架构

微服务是一种架构风格,它将应用程序构造为小型、自主、松耦合和协作的服务集合,围绕业务领域进行建模。这些服务使用同步协议(HTTP/REST)或异步协议(AMQP)进行通信,可以彼此独立地开发和部署。每个服务都有自己的数据库,以便与其他服务分离。



微服务架构

REST 架构

REST 是一种用于开发 Web 服务的架构风格,它建立在 Internet HTTP 的现有特性之上,允许以无状态的方式传输、访问和操作文本数据,即应用程序可以在不知道状态的情况下进行通信。


RESTful API 服务通过统一资源定位器(URL)公开,提供了创建、请求、更新或删除(CRUD)数据的功能。它最适合处理解耦(生成/消费的 ) 信息和(生成/使用信息的 ) 技术的系统。



REST 架构

设计模式

我们将浅显地介绍这个主题,并且只讨论我在本系列的第二部分中可能提到的那些模式。(虽然我每天都在使用它们,但很难用简单的语言全部解释清楚)


软件设计模式是针对软件工程中常见问题的优化的、可重复的解决方案。它是一个解决问题的模板,可以在许多不同的情况下使用。

策略

策略模式定义了一系列算法,将每个算法放在一个单独的类中,相互之间可互换。将行为封装在单独的类中,消除了任何条件语句,在运行时可以选择正确的算法(即策略)。


  • 使用说明:业务规则有不同的实现,或者需要算法的不同变体。



策略模式

模板方法

模板方法旨在从不同的过程中抽象出一个通用的过程。它定义了算法的骨架,将某些步骤推迟到子类。子类可以覆盖某些行为,但不能更改骨架。


  • 使用说明:遵循一系列一致的步骤,各个步骤可能有不同的实现。


⭐ 与策略模式的区别


  • 模板:子类编译时选择算法。

  • 策略:控制运行时选择算法。



模板方法模式

责任链

责任链模式通过启用一个或多个处理程序来满足请求,避免将客户端(请求的发送者)与接收者耦合。这些处理程序连接到一个链中,每个处理程序都包含对链中下一个处理程序的引用。



  • 使用说明:多个对象可以处理一个请求,处理程序(或序列)的优先级事先不知道。



责任链模式

观察者

观察者模式(缩写为 Publish/Subscribe 或简称 PubSub)通过定义对象之间的一对多依赖关系,可以轻松地进行通信广播,当一个对象的状态发生变化时,它的所有依赖关系都会自动得到通知和更新。观察者负责注册它们所“观察”的事件。


  • 使用说明:当改变某个对象时同时需要改变其他对象,不知道多少对象需要改变。



观察者模式

建造者

建造者模式旨在逐步构造一个复杂的对象,分离构造与表示。实质上,它允许使用相同的代码,生成对象的不同类型和表示形式。


  • 使用说明:虽然各个构造步骤有所不同,但可以使用相同的整体构建过程来构建多种复杂对象。



建造者模式

工厂方法

工厂方法定义了一个用于创建对象的接口,由子类来完成实例化。


  • 使用说明:对象的具体类型和依赖关系事先不知道。



工厂方法模式

抽象工厂

抽象工厂关注如何创建相关产品的系列,不指定它们具体的类。


  • 使用说明:不同的规则采用不同的实现,这些规则要么是未知的,要么是可扩展的。


⭐️ 与抽象方法的区别


  • 抽象工厂:创建其他工厂,这些工厂反过来创建从基类派生的对象。

  • 抽象方法:创建从特定基类派生的对象。



抽象工厂模式

装饰

装饰模式动态地将新的职责附加到对象上,将对象放置在包含行为的特殊包装类(wrapper)中,因此不会影响原来方法的签名(继承上的组合)。


  • 使用说明:运行时为对象分配额外的行为,不会破坏使用这些对象的代码。



装饰模式

仓库

仓库模式解决了数据检索和持久化的代码集中化问题,并为数据访问操作提供了抽象,类似内存中域对象的集合,允许执行 CRUD 方法,消除了各种数据库问题。


  • 使用说明:分离业务逻辑和访问数据的代码。



资源库模式

小奖励

想进一步了解模式?可以先从“Gang of Four”的书《设计模式:可重用的面向对象软件的基础》开始。下面的图展示了模式之间的关系,非常重要:



设计模式之间的关系

SOLID

我们唯一的设计原则是 SOLID,它们对于每个软件开发人员来说都是必不可少的。


正如Bob大叔所说:它们不是法律,也不是完美的真理。它们类似于一个规则:每天一个苹果,医生远离我 ”。


这意味着,它们不是某种“魔法”,不是带来牛奶、蜂蜜和优质软件的应许之地,它们是健壮、持久软件的关键贡献者。


简而言之,这些原则围绕两个主要的概念展开,是成功企业应用程序的基石:耦合是类了解另一个类并与之交互的程度;而内聚表示类具有单一用途的程度。换一种说法:


- 耦合是关于类如何相互作用的;

- 内聚关注单个类的设计方式。

单一责任原则

一个类应该有一个,而且只有一个变化的理由


这是不言自明的,但说起来容易做起来难。我们总是想在现有类中添加新的行为,但这是一个引发灾难的处方:每个行为都可能成为未来变化的理由,因此行为越少,在变化时产生错误的机会就越少。

开闭原则

能够扩展类的行为,而无需对其进行修改


类应该对扩展“开放”,但是对修改“关闭”。要实现这一点可以通过继承,即创建一个子类,关闭对原始类进行修改,将自定义的代码添加到子类来引入新的行为。

里氏替换原则

派生类必须可替代基类


当类 A 的行为扩展到子类 B 时,可以确保在不造成任何破坏的情况下用 B 替换 A。这点可能比较吸引人,特别是当把这一原则与开闭原则结合起来时。

接口隔离原则

创建特定于客户端的细粒度接口


接口和类必须尽可能特定,客户端的调用不依赖未使用的方法。这与单一责任原则是一致的。

依赖倒置原则

依赖抽象,而不是具体实现


高层的类不应该依赖于低层的类。它们都应该依赖于抽象。同样,抽象不依赖于细节,细节依赖于抽象。

小奖励

我创建了下面这个快速参考图。如果想知道左边小符号的灵感来自哪里,请参看:“SOLID原则,用励志海报解释” —— 我喜欢作者在这些原则上增加的有趣改变???。



SOLID

脚注

这里无法涵盖所有软件工程概念的详尽列表,它只是阅读下一篇文章的基础,我希望它能让读者了解构建可扩展软件的主要贡献因素。应用程序设计是否能够适应变化,是构建成功解决方案的关键,如果设计过程很仓促,项目结束时,一定会为犯下的错误交付学费。


好的设计是易懂的。伟大的设计是透明的。


原文链接https://towardsdatascience.com/being-a-data-scientist-does-not-make-you-a-software-engineer-c64081526372


2019-05-20 17:226867
用户头像

发布了 43 篇内容, 共 34.5 次阅读, 收获喜欢 136 次。

关注

评论

发布
暂无评论
发现更多内容
构建可扩展的机器学习系统(一):你所需的架构设计知识_AI&大模型_Semi Koen_InfoQ精选文章