只需10分鐘即可在Deep Java Library中使用Spark進行深度學習 - Qing Lan

banq發表於2020-06-12

Apache Spark是一種廣泛使用的資料處理技術,並且被機器學習使用者大量使用。Spark可用於對產品進行分類,預測需求並個性化建議。儘管Spark支援多種程式語言,但首選的Spark SDK是為Scala實現的,大多數深度學習框架都沒有很好地支援它。大多數機器學習框架都傾向於將Python與SDK結合使用,從而使Spark開發人員的選擇不盡人意:將其程式碼移植到Python或實現自定義Scala包裝器。這些選項影響開發人員的工作速度,並以易碎的程式碼威脅生產環境。在此部落格中,我們演示了使用者如何使用Deep Java庫直接從Scala執行深度學習工作負載(DJL)。DJL是一個框架無關的庫,旨在直接在使用Java開發的Spark作業中提供深度學習。在下面的教程中,儘管也支援PyTorch和TensorFlow,我們將逐步介紹使用MXNet進行影像分類的方案。

有關本文完整程式碼,請參見DJL Spark影像分類示例

示例:使用DJL和Spark進行影像分類

在本教程中,我們使用resnet50(一種預先訓練的模型)來執行推理。對於本教程,我們將使用具有三個工作程式節點的單個群集進行分類。

只需10分鐘即可在Deep Java Library中使用Spark進行深度學習 - Qing Lan

我們的示例將在流程中建立多個執行器,併為每個執行器分配任務。每個執行器包含一個或多個在不同執行緒中執行任務的核心。這為每個工作節點提供了均衡的工作負載以進行大資料處理。

步驟1.建立Spark專案

我們使用流行的開源工具sbt在Scala中構建此Spark專案。您可以在此處找到有關如何開始使用sbt的更多資源。我們使用以下程式碼塊在sbt中定義我們的專案:

name := "sparkExample"
version := "0.1"
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"
// Using MXNet Engine
libraryDependencies += "ai.djl.mxnet" % "mxnet-model-zoo" % "0.5.0"
libraryDependencies += "ai.djl.mxnet" % "mxnet-native-auto" % "1.6.0"

本教程使用MXNet作為其基礎引擎。可輕而易舉地切換到另一個框架,如下例所示:

// Using PyTorch Engine
libraryDependencies += "ai.djl.pytorch" % "pytorch-model-zoo" % "0.5.0"
libraryDependencies += "ai.djl.pytorch" % "pytorch-native-auto" % "1.5.0"

步驟2:配置Spark

在本教程中,我們在本地計算機上執行此示例。Spark應用程式將使用以下配置:

// Spark configuration
val conf = new SparkConf()
  .setAppName("Simple Image Classification")
  .setMaster("local<li>")
  .setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine")
val sc = new SparkContext(conf)

MXNet中的多執行緒推理需要NaiveEngine引數。如果使用PyTorch或TensorFlow,則可以刪除以下行:

.setExecutorEnv("MXNET_ENGINE_TYPE", "NaiveEngine")

步驟3:識別輸入資料

在本教程中,輸入資料表示為包含要分類影像的資料夾。Spark將載入這些二進位制檔案並將其分割槽到不同的分割槽。每個分割槽由一個執行程式執行。以下語句將在每個分割槽上均勻分佈資料夾中的所有影像。

val partitions = sc.binaryFiles("images/*")

步驟4:定義Spark作業

接下來,我們使用上一步中建立的分割槽為此作業建立執行圖。在Spark中,每個執行程式都以多執行緒方式執行任務。結果,我們需要在執行推理之前將每個模型載入到執行器中。我們使用以下程式碼進行設定:

// Start assign work for each worker node
val result = partitions.mapPartitions( partition => {
   // before classification
    val criteria = Criteria.builder
        .optApplication(Application.CV.IMAGE_CLASSIFICATION)
        .setTypes(classOf[BufferedImage], classOf[Classifications])
        .optFilter("dataset", "imagenet")
        .optFilter("layers", "50")
        .optProgress(new ProgressBar)
        .build
   val model = ModelZoo.loadModel(criteria)
    val predictor = model.newPredictor()
   // classification
   partition.map(streamData => {
        val img = ImageIO.read(streamData._2.open())
        predictor.predict(img).toString
    })
})

需要為每個分割槽指定ModelZoo的條件,以找到相應的模型並建立預測變數。在分類過程中,我們從RDD載入影像併為其建立推理。該模型使用ImageNet資料集進行了訓練,並儲存在DJL ModelZoo中。

步驟5:定義輸出位置

完成對映過程後,主節點將收集,彙總結果並將其儲存在檔案系統中。

result.collect().foreach(print)
result.saveAsTextFile("output")

執行此程式碼將產生前面列出的輸出類。輸出檔案將儲存到output不同分割槽的資料夾中。有關本教程的完整程式碼,請參見Scala示例。控制檯的預期輸出:

[
    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使用率趨勢並節省快取記憶體,生產使用者應考慮為這些影像建立一個架構並將其以DataFrame格式儲存。從Spark 3.0開始,Spark提供了一個二進位制檔案讀取器選項,使影像轉換為DataFrame更加方便。

案例分析

亞馬遜零售系統(ARS)使用DJL對通過Spark路由的大量資料流進行數百萬個預測。這些預測確定了客戶傾向於使用數千種客戶屬性跨多個類別採取行動,然後向客戶呈現相關廣告和橫幅的傾向。ARS使用成千上萬的功能和數億的客戶-超過10萬億個資料點。他們需要一個可以有效擴充套件的解決方案。為了解決這個關鍵問題,他們最初為他們的工作建立了一個Scala包裝器,但是包裝器存在記憶體問題並且執行緩慢。在採用DJL之後,他們的解決方案可與Spark完美配合,並且總推理時間從數天縮短至數小時。

相關文章