1. 程式人生 > >tensorflow serving 服務部署與訪問(Python + Java)

tensorflow serving 服務部署與訪問(Python + Java)

我的目標是使用tensorflow serving 用docker部署模型後,將服務暴露出來,分別在Python和Java中對模型進行訪問,因為tensorflow serving的文件較少,grpc使用花了不少時間,不過總算是可以用了。
後續優化:這樣簡單地部署的Serving服務,,所以每次呼叫都需要花比較多的時間(感覺像是每次都需要載入模型,本地載入完模型後單預測只需要十幾毫秒),需要後續找時間看看有沒有辦法讓模型預載入,服務呼叫時使用預測方法。

Tensorflow Serving 服務部署

我直接用tensorflow serving docker部署的,直接按照官方的文件即可,唯一可能不同的是國內的網路問題,可以將下載和安裝的步驟從dockerfile裡面轉移到登陸docker container去手動做。
我的總體環境:
tensorflow 1.3.0
python 3.5
java 1.8

Tensorflow Serving 服務編寫

這裡我訓練了一個分類器,主要有三個分類,主要程式碼如下:

#設定匯出時的目錄特徵名
export_time = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
#為了接收平鋪開的圖片陣列(Java處理比較麻煩) 150528 = 224*224*3
x = tf.placeholder(tf.float32, [None, 150528])
x2 = tf.reshape(x, [-1, 224, 224, 3])
#我自己的網路預測
prob = net.network(x2)

sess = tf.Session()
#恢復模型引數
saver = tf.train.Saver() module_file = tf.train.latest_checkpoint(weights_path) saver.restore(sess, module_file) #獲取top 1預測 values, indices = tf.nn.top_k(prob, 1) #建立模型輸出builder builder = tf.saved_model.builder.SavedModelBuilder(exporter_path + export_time) #轉化tensor到模型支援的格式tensor_info,下面的reshape是因為只想輸出單個結果陣列,否則是二維的
tensor_info_x = tf.saved_model.utils.build_tensor_info(x) tensor_info_pro = tf.saved_model.utils.build_tensor_info(tf.reshape(values, [1])) tensor_info_classify = tf.saved_model.utils.build_tensor_info(tf.reshape(indices, [1])) #定義方法名和輸入輸出 signature_def_map = { "predict_image": tf.saved_model.signature_def_utils.build_signature_def( inputs={"image": tensor_info_x}, outputs={ "pro": tensor_info_pro, "classify": tensor_info_classify }, method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME )} builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING], signature_def_map=signature_def_map) builder.save()

可以使用python命令生成模型資料夾,裡面包含了saved_model.pb檔案和variables資料夾
接著在container中可以新建一個資料夾,如serving-models,在資料夾下新建該模型資料夾classify_data,用來存放的模型資料夾,使用docker拷貝的命令拷貝模型到模型資料夾中:

docker cp 本機模型資料夾 containerId:/serving-models/classify_data/模型版本號

啟動模型服務,監聽9000埠:

bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --port=9000 --model_name=classify_data --model_base_path=/serving-models/classify_data/

Python 客戶端

編寫Python訪問客戶端,可以執行看看之前儲存模型時,signature_def_map的輸入輸出:

inputs {
  key: "image"
  value {
    name: "Placeholder:0"
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: -1
      }
      dim {
        size: 224
      }
      dim {
        size: 224
      }
      dim {
        size: 3
      }
    }
  }
}
outputs {
  key: "classify"
  value {
    name: "ToFloat_1:0"
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: -1
      }
      dim {
        size: 1
      }
    }
  }
}
outputs {
  key: "pro"
  value {
    name: "TopKV2:0"
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: -1
      }
      dim {
        size: 1
      }
    }
  }
}

我們可以定義自己的proto檔案,並使用tenserflow/serving/api中的proto來生成程式碼,這裡我不打算如此做,而是用pip install tensorflow-serving-client安裝了一個第三方提供的庫來訪問tensorflow serving服務,python程式碼如下:

import sys
sys.path.insert(0, "./")
from tensorflow_serving_client.protos import predict_pb2, prediction_service_pb2
import cv2
from grpc.beta import implementations
import tensorflow as tf
from tensorflow.python.framework import dtypes
import time

#注意,如果在windows下測試,檔名可能需要寫成:im_name = r"測試檔案目錄\檔名"
im_name = "測試檔案目錄/檔名"
if __name__ == '__main__':
    #檔案讀取和處理
    im = cv2.imread(im_name)
    re_im = cv2.resize(im, (224, 224), interpolation=cv2.INTER_CUBIC)
    #記個時
    start_time = time.time()
    #建立連線
    channel = implementations.insecure_channel("你的ip", 9000)
    stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
    request = predict_pb2.PredictRequest()
    #這裡由儲存和執行時定義,第一個是執行時配置的模型名,第二個是儲存時輸入的方法名
    request.model_spec.name = "classify_data"
    #入參參照入參定義
    request.inputs["image"].ParseFromString(tf.contrib.util.make_tensor_proto(re_im, dtype=dtypes.float32, shape=[1, 224, 224, 3]).SerializeToString())
    #第二個引數是最大等待時間,因為這裡是block模式訪問的
    response = stub.Predict(request, 10.0)
    results = {}
    for key in response.outputs:
        tensor_proto = response.outputs[key]
        nd_array = tf.contrib.util.make_ndarray(tensor_proto)
        results[key] = nd_array
    print("cost %ss to predict: " % (time.time() - start_time))
    print(results["pro"])
    print(results["classify"])

最終輸出,例如:

cost 5.115269899368286s to predict: 
[ 1.]
[2]

Java 訪問

        <dependency>
            <groupId>com.yesup.oss</groupId>
            <artifactId>tensorflow-client</artifactId>
            <version>1.4-2</version>
        </dependency>
        <!-- 這個庫是做影象處理的 -->
        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>2.0.7.Final</version>
        </dependency>

Java程式碼如下:

String file = "檔案地址"
//讀取檔案,強制修改圖片大小,設定輸出檔案格式bmp(模型定義時輸入資料是無編碼的)
BufferedImage im = Thumbnails.of(file).forceSize(224, 224).outputFormat("bmp").asBufferedImage();
//轉換圖片到圖片陣列,匹配輸入資料型別為Float
Raster raster = im.getData();
List<Float> floatList = new ArrayList<>();
float [] temp = new float[raster.getWidth() * raster.getHeight() * raster.getNumBands()];
float [] pixels  = raster.getPixels(0,0,raster.getWidth(),raster.getHeight(),temp);
for(float pixel: pixels) {
    floatList.add(pixel);
}

#記個時
long t = System.currentTimeMillis();
#建立連線,注意usePlaintext設定為true表示用非SSL連線
ManagedChannel channel = ManagedChannelBuilder.forAddress("192.168.2.24", 9000).usePlaintext(true).build();
//這裡還是先用block模式
PredictionServiceGrpc.PredictionServiceBlockingStub stub = PredictionServiceGrpc.newBlockingStub(channel);
//建立請求
Predict.PredictRequest.Builder predictRequestBuilder = Predict.PredictRequest.newBuilder();
//模型名稱和模型方法名預設
Model.ModelSpec.Builder modelSpecBuilder = Model.ModelSpec.newBuilder();
modelSpecBuilder.setName("classify_data");
modelSpecBuilder.setSignatureName("predict_image");
predictRequestBuilder.setModelSpec(modelSpecBuilder);
//設定入參,訪問預設是最新版本,如果需要特定版本可以使用tensorProtoBuilder.setVersionNumber方法
TensorProto.Builder tensorProtoBuilder = TensorProto.newBuilder();
tensorProtoBuilder.setDtype(DataType.DT_FLOAT);
TensorShapeProto.Builder tensorShapeBuilder = TensorShapeProto.newBuilder();
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(1));
#150528 = 224 * 224 * 3
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(150528));
tensorProtoBuilder.setTensorShape(tensorShapeBuilder.build());
tensorProtoBuilder.addAllFloatVal(floatList);
predictRequestBuilder.putInputs("image", tensorProtoBuilder.build());
//訪問並獲取結果
Predict.PredictResponse predictResponse = stub.predict(predictRequestBuilder.build());
System.out.println("classify is: " + predictResponse.getOutputsOrThrow("classify").getIntVal(0));
System.out.println("prob is: " + predictResponse.getOutputsOrThrow("pro").getFloatVal(0));
System.out.println("cost time: " + (System.currentTimeMillis() - t));

結果列印如下:

classify is: 2
prob is: 1.0
cost time: 6911