本文經過一個圖像分類模型爲實例,引導您一步步完成在 Apache Spark 上利用 DJL 在大數據生產環境中部署 TensorFlow,PyTorch,以及 MXNet 等模型。html
深度學習在大數據領域上的應用日趨普遍,但是在 Java/Scala 上的部署方案卻屈指可數。亞馬遜開源項目團隊另闢蹊徑,利用 DJL 幫助用戶部署深度學習應用在 Spark 上。只需10分鐘,你就能夠輕鬆部署 TensorFlow,PyTorch,以及 MXNet 的模型在大數據生產環境中。java
Apache Spark 是一個優秀的大數據處理工具。在機器學習領域,Spark 能夠用於對數據分類,預測需求以及進行個性化推薦。雖然 Spark 支持多種語言,可是大部分 Spark 任務設定及部署仍是經過 Scala 來完成的。儘管如此,Scala 並無很好的支持深度學習平臺。大部分的深度學習應用都部署在 Python 以及相關的框架之上,形成 Scala 開發者一個很頭痛的問題:究竟是全用Python寫整套 spark 架構呢,仍是說用 Scala 包裝 Python code 在 pipeline 裏面跑。這兩個方案都會增長工做量和維護成本。並且,目前看來,PySpark 在深度學習多進程的支持上性能不如Scala的多線程,致使許多深度學習應用速度都卡在了這裏。node
今天,咱們會展現給用戶一個新的解決方案,直接使用 Scala 調用 Deep Java Library (DJL)來實現深度學習應用部署。DJL 將充分釋放Spark強大的多線程處理性能,輕鬆提速2-5倍*現有的推理任務。DJL 是一個爲 Spark 量身定製的 Java 深度學習庫。它不受限於引擎,用戶能夠輕鬆的將 PyTorch, TensorFlow 以及MXNet的模型部署在 Spark 上。在本 blog 中,咱們經過使用 DJL 來完成一個圖片分類模型的部署任務,你也能夠在這裏參閱完整的代碼。git
咱們將使用 Resnet50的預訓練圖像分類模型來部署一個推理任務。爲了簡化配置流程,咱們只會在本地設置單一 cluster 與多個虛擬 worker node 的形式來進行推理。這是大體的工做流程:github
Spark 會產生多個 Executor 來開啓每一個 JVM 進程,而後每個處理任務(task) 都會發送給 Executor 執行。每個 Excutor 擁有獨立分配的內核以及內存。具體任務執行將會徹底使用多線程來執行。在大數據處理中,這種架構能夠幫助每一個 worker 分配到合理的數據量。apache
經過使用 sbt,咱們能夠輕鬆構建 Scala 項目。想了解更多關於 sbt 的介紹,請參考這裏。能夠經過下面的模版輕鬆設定:後端
name := "sparkExample" version := "0.1" // DJL要求JVM 1.8及以上 scalaVersion := "2.11.12" scalacOptions += "-target:jvm-1.8" resolvers += Resolver.mavenLocal libraryDependencies += "org.apache.spark" %% "spark-core" % "2.3.0" libraryDependencies += "ai.djl" % "api" % "0.5.0" libraryDependencies += "ai.djl" % "repository" % "0.5.0" // 使用MXNet引擎 libraryDependencies += "ai.djl.mxnet" % "mxnet-model-zoo" % "0.5.0" libraryDependencies += "ai.djl.mxnet" % "mxnet-native-auto" % "1.6.0"
項目使用 MXNet 做爲默認引擎。你能夠經過修改下面兩行來更換使用 PyTorch:api
// 使用PyTorch引擎 libraryDependencies += "ai.djl.pytorch" % "pytorch-model-zoo" % "0.5.0" libraryDependencies += "ai.djl.pytorch" % "pytorch-native-auto" % "1.5.0"
咱們使用下面的配置在本地運行 Spark:多線程
// Spark 設置 val conf = new SparkConf() .setAppName("圖片分類任務") .setMaster("local[*]") .setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine") val sc = new SparkContext(conf)
MXNet 多線程須要設置額外的 NaiveEngine 環境變量。若是使用 PyTorch 或者 TensorFlow,這一行能夠刪除:架構
.setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine")
輸入數據是一個內含多張圖片的文件夾。Spark 會把這些圖片讀入而後分紅不一樣的 partition。每一個 partition 會被分發給不一樣的 Executor。那麼咱們配置一下圖片分發的過程:
val partitions = sc.binaryFiles("images/*")
在這一步,咱們將建立一個 Spark 計算圖用於進行模型讀取以及推理。因爲每一張圖片推理都會在多線程下完成,咱們須要在進行推理前設置一下 Executor:
// 開始分發任務到 worker 節點 val result = partitions.mapPartitions( partition => { // 準備深度學習模型:創建一個篩選器 val criteria = Criteria.builder // 圖片分類模型 .optApplication(Application.CV.IMAGE_CLASSIFICATION) .setTypes(classOf[BufferedImage], classOf[Classifications]) .optFilter("dataset", "imagenet") // resnet50設置 .optFilter("layers", "50") .optProgress(new ProgressBar) .build val model = ModelZoo.loadModel(criteria) // 創建predictor val predictor = model.newPredictor() // 多線程推理 partition.map(streamData => { val img = ImageIO.read(streamData._2.open()) predictor.predict(img).toString }) })
DJL 引入了一個叫作ModelZoo的概念,經過 Criteria 來設置讀取的模型。而後在 partition 內建立 Predictor。在圖片分類的過程當中,咱們從 RDD 中讀取圖片而後進行推理。此次使用的 Resnet50 模型是通過ImageNet數據集預訓練的模型。
當咱們完成了 Map 數據的過程,咱們須要讓 Master 主節點收集數據:
// 打印推理的結果 result.collect().foreach(print) // 存儲到output文件夾 result.saveAsTextFile("output")
運行上述兩行代碼會驅動 Spark 開啓任務,輸出的文件會保存在 output 文件夾. 請參閱 Scala example 來運行完整的代碼。
若是你運行了示例代碼,這個是輸出的結果:
[ class: "n02085936 Maltese dog, Maltese terrier, Maltese", probability: 0.81445 class: "n02096437 Dandie Dinmont, Dandie Dinmont terrier", probability: 0.08678 class: "n02098286 West Highland white terrier", probability: 0.03561 class: "n02113624 toy poodle", probability: 0.01261 class: "n02113712 miniature poodle", probability: 0.01200 ][ class: "n02123045 tabby, tabby cat", probability: 0.52391 class: "n02123394 Persian cat", probability: 0.24143 class: "n02123159 tiger cat", probability: 0.05892 class: "n02124075 Egyptian cat", probability: 0.04563 class: "n03942813 ping-pong ball", probability: 0.01164 ][ class: "n03770679 minivan", probability: 0.95839 class: "n02814533 beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon", probability: 0.01674 class: "n03769881 minibus", probability: 0.00610 class: "n03594945 jeep, landrover", probability: 0.00448 class: "n03977966 police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria", probability: 0.00278 ]
在這個例子裏,咱們用了 RDD 來進行任務分配,這個只是爲了方便展現。若是考慮到性能因素,建議使用 DataFrame 來做爲數據的載體。從 Spark 3.0開始,Apache Spark 爲 DataFrame 提供了 binary 文件讀取功能。這樣在將來圖片讀取存儲將會易如反掌。
Amazon Retail System (ARS) 經過使用 DJL 在 Spark 上運行了數以百萬的大規模數據流推理任務。這些推理的結果用於推斷用戶對於不一樣操做的傾向,好比是否會購買這個商品,或者是否會添加商品到購物車等等。數以千計的用戶傾向類別能夠幫助 Amazon 更好的推送相關的廣告到用戶的客戶端與主頁。ARS 的深度學習模型使用了數以千計的特徵應用在幾億用戶上,輸入的數據的總量達到了1000億。在龐大的數據集下,因爲使用了基於 Scala 的 Spark 處理平臺,他們曾經一直在爲沒有好的解決方案而困擾。在使用了 DJL 以後,他們的深度學習任務輕鬆的集成在了 Spark 上。推理時間從過去的不少天變成了只需幾小時。咱們在以後將推出另外一篇文章來深度解析 ARS 使用的深度學習模型,以及 DJL 在其中的應用。
DJL 是亞馬遜雲服務在2019年 re:Invent 大會推出的專爲 Java 開發者量身定製的深度學習框架,現已運行在亞馬遜數以百萬的推理任務中。若是要總結 DJL 的主要特點,那麼就是以下三點:
想了解更多,請參見下面幾個連接:
https://github.com/awslabs/djl
也歡迎加入 DJL 的 slack 論壇。
[
](https://s3.cn-north-1.amazona...
*2-5倍基於 PySpark 在 PyTorch Python CPU 與 Spark 在 DJL PyTorch Scala CPU 上性能測試的結果。