LP love tensorflow & spark

昨天看到一篇文章激發起了我很多興趣點,文章的題目是 Spark Love Tensorflow,心想何止如此,LP love tensorflow & spark,之前謎之Love Tensorflow,花了三年的時候把它收入麾下,19年開始接觸spark,同樣激發出不少火花,同時學習了scala語言(人生太短,python吧),也想花二到三年的時間將spark拿下。

感興趣的可以看看我整理的repo:

tensorflow:https://github.com/MachineLP/Tensorflow-

spark:https://github.com/MachineLP/Spark-

              https://github.com/MachineLP/MachineLP-CodeFun/tree/master/04-machine_learning/02-sparkml_examples

如果想成爲data scientist可以看這裏:https://github.com/MachineLP/MachineLP-CodeFun

回到正題吧: 推薦一片文章《Spark Love TensorFlow》:https://mp.weixin.qq.com/s/Dexxj4VnDzVKSt-BYwMdOg

具體如下:

本篇文章介紹在 Spark 中調用訓練好的 TensorFlow 模型進行預測的方法。

本文內容的學習需要一定的 Spark 和 Scala 基礎。想要入門spark的同學,可以在公衆號後臺回覆關鍵字:spark,獲取spark入門獨家教程。

本篇文章我們通過 TensorFlow for Java 在 Spark 中調用訓練好的 TensorFlow 模型。利用 Spark 的分佈式計算能力,從而可以讓訓練好的 TensorFlow 模型在成百上千的機器上分佈式並行執行模型推斷。

Spark-Scala 調用 TensorFlow 模型概述

在 Spark(Scala) 中調用 TensorFlow 模型進行預測需要完成以下幾個步驟:

  1. 準備 protobuf 模型文件

  2. 創建 Spark-Scala 項目,在項目中添加 Java 版本的 TensorFlow 對應的 jar 包依賴

  3. 在 Spark-Scala 項目中 driver 端加載 TensorFlow 模型調試成功

  4. 在 Spark-Scala) 項目中通過 RDD 在 executor 上加載 TensorFlow 模型調試成功

  5. 在 Spark-Scala 項目中通過 DataFrame 在 executor 上加載 TensorFlow 模型調試成功

 

一 準備 protobuf 模型文件

我們使用 tf.keras 訓練一個簡單的線性迴歸模型,並保存成 protobuf 文件。

import tensorflow as tf
from tensorflow.keras import models,layers,optimizers

## 樣本數量
n = 800

## 生成測試用數據集
X = tf.random.uniform([n,2],minval=-10,maxval=10)
w0 = tf.constant([[2.0],[-1.0]])
b0 = tf.constant(3.0)

Y = X@w0 + b0 + tf.random.normal([n,1],mean = 0.0,stddev= 2.0)  # @表示矩陣乘法,增加正態擾動

## 建立模型
tf.keras.backend.clear_session()
inputs = layers.Input(shape = (2,),name ="inputs") #設置輸入名字爲inputs
outputs = layers.Dense(1, name = "outputs")(inputs) #設置輸出名字爲outputs
linear = models.Model(inputs = inputs,outputs = outputs)
linear.summary()

## 使用fit方法進行訓練
linear.compile(optimizer="rmsprop",loss="mse",metrics=["mae"])
linear.fit(X,Y,batch_size = 8,epochs = 100)

tf.print("w = ",linear.layers[1].kernel)
tf.print("b = ",linear.layers[1].bias)

## 將模型保存成pb格式文件
export_path = "./data/linear_model/"
version = "1"       #後續可以通過版本號進行模型版本迭代與管理
linear.save(export_path+version, save_format="tf")
!ls {export_path+version}


# 查看模型文件相關信息!saved_model_cli show --dir {export_path+str(version)} --all

 

模型文件信息中這些標紅的部分都是後面有可能會用到的:

 

 

二 添加 TensorFlow for java 項目依賴

如果使用 maven 管理項目,需要添加如下 jar 包依賴:

<!-- https://mvnrepository.com/artifact/org.tensorflow/tensorflow -->
<dependency>
    <groupId>org.tensorflow</groupId>
    <artifactId>tensorflow</artifactId>
    <version>1.15.0</version>
</dependency>

 

也可以從下面網址中直接下載 org.tensorflow.tensorflow 的 jar 包,以及其依賴的 org.tensorflow.libtensorflow 和 org.tensorflowlibtensorflow_jni 的 jar 包放到項目中。

  • https://mvnrepository.com/artifact/org.tensorflow/tensorflow/1.15.0

 

 

三 在 Driver 端加載 TensorFlow 模型

我們的示範代碼在 Jupyter Notebook 中進行演示,需要安裝 toree 以支持 Spark-Scala。

import scala.collection.mutable.WrappedArray
import org.{tensorflow=>tf}

//注:load函數的第二個參數一般都是“serve”,可以從模型文件相關信息中找到

val bundle = tf.SavedModelBundle
   .load("/Users/liangyun/CodeFiles/eat_tensorflow2_in_30_days/data/linear_model/1","serve")

//注:在java版本的tensorflow中還是類似tensorflow1.0中靜態計算圖的模式,需要建立Session, 指定feed的數據和fetch的結果, 然後 run.
//注:如果有多個數據需要喂入,可以連續用用多個feed方法
//注:輸入必須是float類型

val sess = bundle.session()
val x = tf.Tensor.create(Array(Array(1.0f,2.0f),Array(2.0f,3.0f)))
val y =  sess.runner().feed("serving_default_inputs:0", x)
         .fetch("StatefulPartitionedCall:0").run().get(0)

val result = Array.ofDim[Float](y.shape()(0).toInt,y.shape()(1).toInt)
y.copyTo(result)

if(x != null) x.close()
if(y != null) y.close()
if(sess != null) sess.close()
if(bundle != null) bundle.close()

result

輸出如下:

Array(Array(3.019596), Array(3.9878292))

 

四 通過 RDD 加載 TensorFlow 模型

下面我們通過廣播機制將 Driver 端加載的 TensorFlow 模型傳遞到各個 executor 上,並在 executor 上分佈式地調用模型進行推斷。

import org.apache.spark.sql.SparkSession
import scala.collection.mutable.WrappedArray
import org.{tensorflow=>tf}

val spark = SparkSession
    .builder()
    .appName("TfRDD")
    .enableHiveSupport()
    .getOrCreate()

val sc = spark.sparkContext

//在Driver端加載模型
val bundle = tf.SavedModelBundle 
   .load("/Users/liangyun/CodeFiles/master_tensorflow2_in_20_hours/data/linear_model/1","serve")

//利用廣播將模型發送到excutor上
val broads = sc.broadcast(bundle)

//構造數據集
val rdd_data = sc.makeRDD(List(Array(1.0f,2.0f),Array(3.0f,5.0f),Array(6.0f,7.0f),Array(8.0f,3.0f)))

//通過mapPartitions調用模型進行批量推斷
val rdd_result = rdd_data.mapPartitions(iter => {
    
    val arr = iter.toArray
    val model = broads.value
    val sess = model.session()
    val x = tf.Tensor.create(arr)
    val y =  sess.runner().feed("serving_default_inputs:0", x)
             .fetch("StatefulPartitionedCall:0").run().get(0)

    //將預測結果拷貝到相同shape的Float類型的Array中
    val result = Array.ofDim[Float](y.shape()(0).toInt,y.shape()(1).toInt)
    y.copyTo(result)
    result.iterator
    
})


rdd_result.take(5)
bundle.close

 

輸出如下:

Array(Array(3.019596), Array(3.9264367), Array(7.8607616), Array(15.974984))

 

五 通過 DataFrame 加載 TensorFlow 模型

除了可以在 Spark 的 RDD 數據上調用 TensorFlow 模型進行分佈式推斷,我們也可以在 DataFrame 數據上調用 TensorFlow 模型進行分佈式推斷。

主要思路是將推斷方法註冊成爲一個 SparkSQL 函數。

import org.apache.spark.sql.SparkSession
import scala.collection.mutable.WrappedArray
import org.{tensorflow=>tf}

object TfDataFrame extends Serializable{
    
    
    def main(args:Array[String]):Unit = {
        
        val spark = SparkSession
        .builder()
        .appName("TfDataFrame")
        .enableHiveSupport()
        .getOrCreate()
        val sc = spark.sparkContext
        
        
        import spark.implicits._

        val bundle = tf.SavedModelBundle 
           .load("/Users/liangyun/CodeFiles/master_tensorflow2_in_20_hours/data/linear_model/1","serve")

        val broads = sc.broadcast(bundle)
        
        //構造預測函數,並將其註冊成sparkSQL的udf
        val tfpredict = (features:WrappedArray[Float])  => {
            val bund = broads.value
            val sess = bund.session()
            val x = tf.Tensor.create(Array(features.toArray))
            val y =  sess.runner().feed("serving_default_inputs:0", x)
                     .fetch("StatefulPartitionedCall:0").run().get(0)
            val result = Array.ofDim[Float](y.shape()(0).toInt,y.shape()(1).toInt)
            y.copyTo(result)
            val y_pred = result(0)(0)
            y_pred
        }
        spark.udf.register("tfpredict",tfpredict)
        
        //構造DataFrame數據集,將features放到一列中
        val dfdata = sc.parallelize(List(Array(1.0f,2.0f),Array(3.0f,5.0f),Array(7.0f,8.0f))).toDF("features")
        dfdata.show
        
        //調用sparkSQL預測函數,增加一個新的列作爲y_preds
        val dfresult = dfdata.selectExpr("features","tfpredict(features) as y_preds")
        dfresult.show
        bundle.close
    }
}
TfDataFrame.main(Array())

輸出如下:

+----------+
|  features|
+----------+
|[1.0, 2.0]|
|[3.0, 5.0]|
|[7.0, 8.0]|
+----------+

+----------+---------+
|  features|  y_preds|
+----------+---------+
|[1.0, 2.0]| 3.019596|
|[3.0, 5.0]|3.9264367|
|[7.0, 8.0]| 8.828995|
+----------+---------+

 

以上我們分別在 Spark 的 RDD 數據結構和 DataFrame 數據結構上實現了調用一個 tf.keras 實現的線性迴歸模型進行分佈式模型推斷。

在本例基礎上稍作修改則可以用 Spark 調用訓練好的各種複雜的神經網絡模型進行分佈式模型推斷。但實際上 TensorFlow 並不僅僅適合實現神經網絡,其底層的計算圖語言可以表達各種數值計算過程。

利用其豐富的低階 API,我們可以在 TensorFlow 2.0 上實現任意機器學習模型,結合 tf.Module 提供的便捷的封裝功能,我們可以將訓練好的任意機器學習模型導出成模型文件並在 Spark 上分佈式調用執行。

這無疑爲我們的工程應用提供了巨大的想象空間。    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章