亚马逊AWS官方博客

使用 DJL (Deep Java Library) 和 Spring Boot 在您的微服务中采用机器学习

很多 AWS 客户(包括初创公司和大型企业)都正在其现有应用程序中采用机器学习和深度学习。行业的创新速度促使各企业采用机器学习,其涉及的业务使用案例从客户服务(包括从图像和视频流进行对象检测、情绪分析)到欺诈检测与协作不等。然而,直到最近,采用学习曲线仍然相当陡峭,需要用新的编程语言(例如 Python)和框架开发内部技术专业知识,从而对从编写代码到构建、测试和部署的整个软件开发声明周期产生级联效应。本博客文章中所述的方法可使企业利用现有的才能和资源(框架、管道和部署)来集成机器学习功能。

简介

Spring Boot 是用于微服务开发的最常用且使用最广泛的开源框架,对分布式系统的实施进行了简化。

尽管此框架具有广泛的吸引力,但将它在本机与 Machine Learning (ML) 轻松集成的选项很少。现有解决方案(例如库存 API)通常不满足定制的应用程序要求,且开发定制解决方案很耗时,且不具有成本效益。

开发人员采用多种方式将机器学习功能集成到现有的应用程序中。以推理为例,当前选项从使用库存 API 到将使用远程调用 API 包装基于 Python 或 C++ 的应用程序用不等。库存 API 虽然基于稳健的模型,但可能不太适合您的域或行业,从而导致在生产中发现问题,且解决这些问题的选项很少。在其他情况下,当大规模运行推理(例如,在流式传输应用程序或延迟敏感型微服务中)时,出于性能原因,进行远程调用可能不是可行选项。

认识到这一挑战后,AWS 创建了几个开源项目,以促进 Java 和微服务采用 ML,从而最终帮助我们的客户、合作伙伴以及整个开源社区。这些计划与 AWS 目标紧密契合,利用历来成本高昂且难以被许多组织采用的技术,并使这些技术能被更加广泛的受众使用。

在本博客文章中,我们将演示 Java 用户如何使用适用于 Deep Java Library (DJL)Spring Boot Starter 将 ML 集成到他们的 Spring 应用程序中。我们将回顾如何实际应用这些框架及如何将 ML 功能集成到微服务中,从而演示深度学习在对象检测和分类中的常见使用案例。

DJL 概述

Deep Java Library (DJL) 是一个适用于深度学习的开源、高级别、无框架无关的 Java API。它容易入门,并且对于 Java 开发人员来说使用非常简单。DJL 提供本机 Java 开发体验和功能,就像其他常规 Java 库一样。

DJL 提供方便的抽象层来使用最常见的 AI/ML 框架,例如 Apache MXNetPyTorchTensorFlow。然而,它不仅是在现有库(其中一些提供 Java API / 绑定)上使用方便。使用 DJL API,您将获得均匀一致的层,它可以与上述所有框架进行交互,从而使您能够交换您选择的框架,而不对客户端代码造成任何影响。

这一独特的功能结合相对丰富的模型园存储库(带有预训练模型的存储库)使 ML 工程师能够为手边的任务查找最佳模型,无论底层模型实施如何。

有关 DJL 的更多信息,请参阅 DJL GitHub 存储库常见问题

DJL Spring Boot Starter

Spring Boot Starter 是为您的项目所需的所有 Spring 和相关技术提供的一站式服务,使您不必再搜寻示例代码并对依赖性描述符进行复制粘贴操作。请参阅 Spring Boot 正式文档了解有关入门版的更多信息。

按照此定义,DJL Spring Boot Starter 将在 Spring 中开始使用 DJL 所需的所有依赖项提供为一个构件。除了依赖项管理之外,该入门版还包括自动配置功能,通过该功能,可以基于用户提供的配置文件自动连接依赖项,并使它们成为 Spring 应用程序上下文中的内容。

依赖项管理

DJL 库的特定于平台的,但它提供基于目标操作系统自动查找正确依赖项的方式。DJL 还可以配置不同的底层引擎(例如 MXNet、PyTorch 或 TensorFlow);用户应该在使用入门版之前进行此选择。然而,即使进行了此选择后,也可以通过修改 Maven(或 Gradle)依赖项来更改底层引擎和目标操作系统架构,而不对您的代码造成任何影响。

入门版依赖项管理的组织方式可为用户提供最大的灵活性。

对于 MXNet 入门版,支持以下操作系统分类器:适用于 Mac OS X 的 osx-x86_64、适用于通用 Linux 的 linux-x86_64、适用于 Windows 发行版的 win-x86_64,以及适用于自动检测目标操作系统的 auto。最后一个选项需要在运行时连接到外部构件存储库(例如 Maven Central),这对于安全限制严格且传入受限的系统来说可能是个问题。

下面是适用于 Linux 架构的 MXNet 依赖项的示例,针对容器工作负载进行了优化:

<parent>
  <artifactId>spring-boot-starter-parent</artifactId>
  <groupId>org.springframework.boot</groupId>
  <version>2.2.6.RELEASE</version>
</parent>

<properties>
  <java.version>11</java.version> <!-- 11 is the lowest supported java version, however 12 and 13 should work fine -->
  <jna.version>5.3.0</jna.version> <!-- Required to override default JNA version for Spring Boot parent-->
</properties> 

<dependency>
  <groupId>ai.djl.spring</groupId>
  <artifactId>djl-spring-boot-starter-mxnet-linux-x86_64</artifactId>
  <version>${djl.starter.version}</version> <!-- e.g. 0.2 -->
</dependency>

将在运行时下载正确构件的 Auto 依赖项:

<dependency>
  <groupId>ai.djl.spring</groupId>
  <artifactId>djl-spring-boot-starter-mxnet-auto</artifactId>
  <version>${djl.starter.version}</version> <!-- e.g. 0.2 -->
</dependency>

PyTorch 用作底层引擎时,入门版依赖项为:

<dependency>
  <groupId>ai.djl.spring</groupId>
  <artifactId>djl-spring-boot-starter-pytorch-auto</artifactId>
  <version>${djl.starter.version}</version> <!-- e.g. 0.2 and above -->
</dependency>

Gradle 依赖项看起来类似。重要的是,在 gradle.properties 内将 JNA 版本设置为“jna.version=5.3.0”,因为 Spring Boot 的父级 POM 使用旧版 JNA,它不能与 DJL 入门版一起工作。下面是 Gradle 构建文件 build.gradle.kts 的 示例(在本示例中,使用 Kotlin DSL),前提是假设已注册 Spring Boot 插件:

plugins {
  ...
  id("org.springframework.boot")
}
repositories {
  mavenCentral() // released artifacts are available from maven central
}

dependencies {
  implementation("ai.djl.spring:djl-spring-boot-starter-mxnet-auto:0.2")
}

Spring 自动配置

在您的 Spring Boot 应用程序中正确配置依赖项后,下一步是配置内容并适当连接它们以进行注入。配置与 DJL 相关的内容并将它们提供在 Spring 应用程序上下文中很容易,但它需要库的内部知识以及适当确定范围的各个类的特性,例如,有些内容具有线程安全性,有些则应按照请求/线程确定范围。为帮助进行此配置,DJL Spring Boot 入门版提供了自动配置

此组件与依赖项组件是分开的,需要显式依赖项。我们这样做有几个原因:

  1. 有些开发人员更喜欢完全控制配置选项,且可能不希望 Spring 施展“自动魔力”。在此情况下,入门版将仅支持基本依赖项集,并且使开发人员显式连接组件。
  2. 自动配置组件对于所有类型的 DJL 配置都是通用的:无论底层目标操作系统或实际引擎如何,自动配置组件都保持相同。因此,使用相同的自动配置,开发人员可以将底层依赖项作为单步操作进行交换,而对代码不产生任何影响。

在 Maven 中声明自动配置的依赖项:

<dependency>
  <groupId>ai.djl.spring</groupId>
  <artifactId>djl-spring-boot-starter-autoconfigure</artifactId>
  <version>${djl.starter.version}</version>
</dependency>

或者在 Gradle build.gradle.kts 中:

dependencies {
  implementation("ai.djl.spring:djl-spring-boot-starter-autoconfigure:${djl.starter.version}")
}

声明依赖项后,Spring Boot 框架将自动查找配置并连接所需的组件。当前,为了进行推理,它将从模型园存储库中查找模型,并创建一个可随时用于运行推理的预测工具。

用户应该提供标准的 Spring 配置(application.ymlapplication.properties)模型来使用受支持的应用程序类型之一:

  QUESTION_ANSWER(NLP.QUESTION_ANSWER),
  TEXT_CLASSIFICATION(NLP.TEXT_CLASSIFICATION),
  IMAGE_CLASSIFICATION(CV.IMAGE_CLASSIFICATION),
  OBJECT_DETECTION(CV.OBJECT_DETECTION),
  ACTION_RECOGNITION(CV.ACTION_RECOGNITION),
  INSTANCE_SEGMENTATION(CV.INSTANCE_SEGMENTATION),
  POSE_ESTIMATION(CV.POSE_ESTIMATION),
  SEMANTIC_SEGMENTATION(CV.SEMANTIC_SEGMENTATION);

例如,为了在图像中运行对象检测,用户可以将应用程序类型设置为 OBJECT_DETECTION。与 DJL 相关的配置应在 djl 根下提供命名空间,例如,如果使用 application.properties,则为 djl.application-type=OBJECT_DETECTION

下面是 DJL 自动配置的 yaml 配置示例:

djl:
    # Define application type
    application-type: OBJECT_DETECTION
    # Define input data type, a model may accept multiple input data type
    input-class: java.awt.image.BufferedImage
    # Define output data type, a model may generate different out put
    output-class: ai.djl.modality.cv.output.DetectedObjects
    # Define filters that matches your application's need
    model-filter:
      size: 512
      backbone: mobilenet1.0
    # Override default pre-processing/post-processing behavior
    arguments:
      threshold: 0.5 # Display all results with probability of 0.5 and above

IDE 支持

配置创建不需要完全手动进行——DJL Spring Boot Starter 在 Spring IDE 插件的帮助下为大多数 IDE 提供配置内容协助(在 IntelliJ IDEA 上进行测试,但预期在 Eclipse 中与 STS 和 NetBeans IDE 结合使用)。

IDE 支持

对于 IntelliJ,您可以使用 Ctrl+Space 进行自动填写,将 Ctrl+J 用于任何属性的快速文档。

简单的应用程序演练

下面是基于简单的 Spring Boot 应用程序的代码示例,演示了使用 DJL 和 MXNet 进行的单次对象检测。

依赖项根据 Maven 依赖项部分设置。配置设置与示例中的 application.yml 相同。应用程序是只有一个类的常控制台 Spring Boot 应用程序(注意不需要其他代码)。

注册预测工具进行对象检测

  @Resource
  private Supplier<Predictor> predictorProvider;

try-with-resources 数据块中使用预测工具,以确保每次推荐使用后都将其关闭,从而可以注入预测工具的供应商以方便进行实例化。

Java 中的对象检测

try (var predictor = predictorProvider.get()) {
    var results = predictor.predict(ImageIO.read(this.getClass()
          .getResourceAsStream("/puppy-in-white-and-red-polka.jpg")));

    for(var result : results.items()) {
        LOG.info("results: {}", result.toString());
    }
}

上述代码将对提供的图像运行对象检测(预计在类路径中),并用以下形式将结果输出到记录器中:

a.d.s.e.console.ConsoleApplication: results: class: "dog", probability: 0.90820, bounds: {x=0.487, y=0.057, width=0.425, height=0.484}

构建和运行

假设您检查了存储库且现在位于存储库的根目录中:

> git clone git@github.com:awslabs/djl-spring-boot-starter.git 
> cd djl-spring-boot-starter/djl-spring-boot-console-sample
> ../mvnw package
> ../mvnw spring-boot:run

或者,您可以直接使用 java -jar 命令运行它:

java -jar target/djl-spring-boot-console-sample-${version}.jar

更加复杂的应用程序示例

下面是基于利用 DJL Spring Boot Starter 的 REST API 示例的示例代码,演示了一次 RESTful API 实施,该实施可以从 Amazon Simple Storage Service (Amazon S3) 存储桶中获取图像并将对象检测结果存储回 S3 中。此 API 表示可以采用任何图像参考(已上传到预定义的 Amazon S3 存储桶中)并对其运行对象检测的微服务。可以直接通过 Postman 等 REST 客户端或在移动或 Web 应用程序内利用它。

API 部分将 Gradle 用作其构建系统,并利用 Spring MVC 进行 REST API 实施。为简单起见,将控制器作为阻塞调用实施。对于大容量生产使用,建议使用反应性样式的 API 实现,例如 WebFlux。对于您的 Spring Boot 应用程序中的 DJL 组件,将显示一个显式编程配置示例。

Web 应用程序部分作为 Kotlin Spring MVC 应用程序实施,并通过反应性 REST API 客户端调用后端 API。

用户将看到一个文件列表和一个选择任何文件进行上传和对象检测的选项,API 输出直接显示在下面:

对象检测的结果将应用回图像中,以突出显示已进行过对象检测且对象已上传到 S3 存储桶的区域:

在后续博文中,我们将回顾如何对这些应用程序进行容器化以及如何组织全面的 CI/CD 管道部署到 Amazon Elastic Kubernetes Service (Amazon EKS) 中。

结论

在 DJL 和 Spring Boot 中采用机器学习是一种简单而强大的方法,利用这种方法,客户可以通过 DJL 和 Spring Boot 提供的方便的抽象层来结合久经沙场的现有微服务技术堆栈与最成熟的深度学习框架(如 MXNet、PyTorch 和 Tensorflow)。

参考

  1. DJL Spring Boot Starter—Spring Boot 入门版是根据 Apache 2.0 许可证发布的一种开源软件,可使 Spring Boot 开发人员开始使用 DJL 进行推理。
  2. DJL Spring Boot 演示—包含 Java API 的演示存储库,它利用入门版和 Gradle 以及基于 Kotlin 的小型 Web 应用程序。
  3. DJL—DJL (Deep Java Library),GitHub 上的主要存储库,根据 Apache 2.0 许可证发布。