自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面向機(jī)器智能的TensorFlow實(shí)踐:產(chǎn)品環(huán)境中模型的部署

人工智能 機(jī)器學(xué)習(xí)
本文將創(chuàng)建一個(gè)簡單的Web App,使用戶能夠上傳一幅圖像,并對其運(yùn)行Inception模型,實(shí)現(xiàn)圖像的自動(dòng)分類。

面向機(jī)器智能的TensorFlow實(shí)踐:產(chǎn)品環(huán)境中模型的部署

在了解如何利用TesnsorFlow構(gòu)建和訓(xùn)練各種模型——從基本的機(jī)器學(xué)習(xí)模型到復(fù)雜的深度學(xué)習(xí)網(wǎng)絡(luò)后,我們就要考慮如何將訓(xùn)練好的模型投入于產(chǎn)品,以使其能夠?yàn)槠渌麘?yīng)用所用,本文對此將進(jìn)行詳細(xì)介紹。文章節(jié)選自《面向機(jī)器智能的TensorFlow實(shí)踐》第7章。

本文將創(chuàng)建一個(gè)簡單的Web App,使用戶能夠上傳一幅圖像,并對其運(yùn)行Inception模型,實(shí)現(xiàn)圖像的自動(dòng)分類。

搭建TensorFlow服務(wù)開發(fā)環(huán)境

Docker鏡像

TensorFlow服務(wù)是用于構(gòu)建允許用戶在產(chǎn)品中使用我們提供的模型的服務(wù)器的工具。在開發(fā)過程中,使用該工具的方法有兩種:手工安裝所有的依賴項(xiàng)和工具,并從源碼開始構(gòu)建;或利用Docker鏡像。這里準(zhǔn)備使用后者,因?yàn)樗菀住⒏蓛?,同時(shí)允許在其他不同于Linux的環(huán)境中進(jìn)行開發(fā)。

如果不了解Docker鏡像,不妨將其想象為一個(gè)輕量級的虛擬機(jī)鏡像,但它在運(yùn)行時(shí)不需要以在其中運(yùn)行完整的操作系統(tǒng)為代價(jià)。如果尚未安裝Docker,請?jiān)陂_發(fā)機(jī)中安裝它,點(diǎn)擊查看具體安裝步驟(https://docs.docker.com/engine/installation/)。

為了使用Docker鏡像,還可利用筆者提供的文件(https://github.com/tensorflow/serving/blob/master/tensorflow_serving/tools/docker/Dockerfile.devel),它是一個(gè)用于在本地創(chuàng)建鏡像的配置文件。要使用該文件,可使用下列命令:

  1. docker build --pull -t $USER/tensorflow-serving-devel 
  2.  
  3. https://raw.githubusercontent.com/tensorflow/serving/master/ 
  4.  
  5. tensorflow_serving/tools/docker/Dockerfile.devel  

請注意,執(zhí)行上述命令后,下載所有的依賴項(xiàng)可能需要一段較長的時(shí)間。

上述命令執(zhí)行完畢后,為了使用該鏡像運(yùn)行容器,可輸入下列命令:

  1. docker run -v $HOME:/mnt/home -p 9999:9999 -it $USER
  2.  
  3. tensorflow-serving-devel  

該命令執(zhí)行后會(huì)將你的home目錄加載到容器的/mnt/home路徑中,并允許在其中的一個(gè)終端下工作。這是非常有用的,因?yàn)槟憧墒褂米约浩玫腎DE或編輯器直接編輯代碼,同時(shí)在運(yùn)行構(gòu)建工具時(shí)僅使用該容器。它還會(huì)開放端口9999,使你可從自己的主機(jī)中訪問它,并供以后將要構(gòu)建的服務(wù)器使用。

鍵入exit命令可退出該容器終端,使其停止運(yùn)行,也可利用上述命令在需要的時(shí)候啟動(dòng)它。

Bazel工作區(qū)

由于TensorFlow服務(wù)程序是用C++編寫的,因此在構(gòu)建時(shí)應(yīng)使用Google的Bazel構(gòu)建工具。我們將從最近創(chuàng)建的容器內(nèi)部運(yùn)行Bazel。

Bazel在代碼級管理著第三方依賴項(xiàng),而且只要它們也需要用Bazel構(gòu)建,Bazel便會(huì)自動(dòng)下載和構(gòu)建它們。為了定義我們的項(xiàng)目將支持哪些第三方依賴項(xiàng),必須在項(xiàng)目庫的根目錄下定義一個(gè)WORKSPACE文件。

我們需要的依賴項(xiàng)是TensorFlow服務(wù)庫。在我們的例子中,TensorFlow模型庫包含了Inception模型的代碼。

不幸的是,在撰寫本書時(shí),TensorFlow服務(wù)尚不支持作為Git庫通過Bazel直接引用,因此必須在項(xiàng)目中將它作為一個(gè)Git的子模塊包含進(jìn)去:

  1. # 在本地機(jī)器上 
  2.  
  3. mkdir ~/serving_example 
  4.  
  5. cd ~/serving_example 
  6.  
  7. git init 
  8.  
  9. git submodule add https://github.com/tensorflow/serving.git 
  10.  
  11. tf_serving 
  12.  
  13. git.submodule update - -init - -recursive  

下面利用WORKSPACE文件中的local_repository規(guī)則將第三方依賴項(xiàng)定義為在本地存儲(chǔ)的文件。此外,還需利用從項(xiàng)目中導(dǎo)入的tf_workspace規(guī)則對TensorFlow的依賴項(xiàng)初始化:

  1. # Bazel WORKSPACE文件 
  2.  
  3. workspace(name = "serving"
  4.  
  5. local_repository( 
  6.  
  7. name = "tf_serving"
  8.  
  9. path = _workspace_dir__ + "/tf_serving", 
  10.  
  11. local_repository( 
  12.  
  13. name = "org_tensorflow"
  14.  
  15. path = _workspace_dir__ + "/tf_serving/tensorflow"
  16.  
  17.  
  18. load('//tf_serving/tensorflow/tensorflow:workspace.bzl'
  19.  
  20. 'tf_workspace'
  21.  
  22. tf_workspace("tf_serving/tensorflow/""@org_tensorflow"
  23.  
  24. bind( 
  25.  
  26. name = "libssl"
  27.  
  28. actual = "@boringssl_git//:ssl"
  29.  
  30.  
  31. bind( 
  32.  
  33. name = "zlib"
  34.  
  35. actual = "@zlib_archive//:zlib" 
  36.  
  37. ) 
  38.  
  39. # 僅當(dāng)導(dǎo)入inception 模型時(shí)需要 
  40.  
  41. local_repository( 
  42.  
  43. name = "inception_model"
  44.  
  45. path = __workspace_dir__ + "/tf_serving/tf_models/ 
  46.  
  47. inception”, 
  48.  
  49. ) 
  50.  
  51. 最后,需要從容器內(nèi)為Tensorflow運(yùn)行./configure: 
  52.  
  53. # 在Docker容器中 
  54.  
  55. cd /mnt/home/serving_example/tf_serving/tensorflow 
  56.  
  57. ./configure  

導(dǎo)出訓(xùn)練好的模型

一旦模型訓(xùn)練完畢并準(zhǔn)備進(jìn)行評估,便需要將數(shù)據(jù)流圖及其變量值導(dǎo)出,以使其可為產(chǎn)品所用。

模型的數(shù)據(jù)流圖應(yīng)當(dāng)與其訓(xùn)練版本有所區(qū)分,因?yàn)樗仨殢恼嘉环邮蛰斎?,并對其進(jìn)行單步推斷以計(jì)算輸出。對于Inception模型這個(gè)例子,以及對于任意一般圖像識(shí)別模型,我們希望輸入是一個(gè)表示了JPEG編碼的圖像字符串,這樣就可輕易地將它傳送到消費(fèi)App中。這與從TFRecord文件讀取訓(xùn)練輸入頗為不同。

定義輸入的一般形式如下:

  1. def convert_external_inputs (external_x): 
  2.  
  3.  #將外部輸入變換為推斷所需的輸入格式 
  4.  
  5. def inference(x): 
  6.  
  7.  #從原始模型中…… 
  8.  
  9. external_x = tf.placeholder(tf.string) 
  10.  
  11. x = convert_external_inputs(external_x) 
  12.  
  13. y = inference(x)  

在上述代碼中,為輸入定義了占位符,并調(diào)用了一個(gè)函數(shù)將用占位符表示的外部輸入轉(zhuǎn)換為原始推斷模型所需的輸入格式。例如,我們需要將JPEG字符串轉(zhuǎn)換為Inception模型所需的圖像格式。最后,調(diào)用原始模型推斷方法,依據(jù)轉(zhuǎn)換后的輸入得到推斷結(jié)果。

例如,對于Inception模型,應(yīng)當(dāng)有下列方法:

  1. import tensorflow as tf 
  2.  
  3. from tensorflow_serving.session_bundle import exporter 
  4.  
  5. from inception import inception_model 
  6.  
  7. def convert_external_inputs (external_x) 
  8.  
  9. # 將外部輸入變換為推斷所需的輸入格式 
  10.  
  11. # 將圖像字符串轉(zhuǎn)換為一個(gè)各分量位于[0,1]內(nèi)的像素張量 
  12.  
  13. image = 
  14.  
  15. tf.image.convert_image_dtype(tf.image.decode_jpeg(external_x, 
  16.  
  17. channels=3), tf.float32) 
  18.  
  19. # 對圖像尺寸進(jìn)行縮放,使其符合模型期望的寬度和高度 
  20.  
  21. images = tf.image.resize_bilinear(tf.expand_dims(image, 
  22.  
  23. 0),[299,299]) 
  24.  
  25. # 將像素值變換到模型所要求的區(qū)間[-1,1]內(nèi) 
  26.  
  27. images =tf.mul(tf.sub(image,0.5),2) 
  28.  
  29. return images 
  30.  
  31.  
  32. def inference(images): 
  33.  
  34.   logits, _ = inception_model.inference(images, 1001) 
  35.  
  36.   return logits   

這個(gè)推斷方法要求各參數(shù)都被賦值。我們將從一個(gè)訓(xùn)練檢查點(diǎn)恢復(fù)這些參數(shù)值。你可能還記得,在前面的章節(jié)中,我們周期性地保存模型的訓(xùn)練檢查點(diǎn)文件。那些文件中包含了當(dāng)時(shí)學(xué)習(xí)到的參數(shù),因此當(dāng)出現(xiàn)異常時(shí),訓(xùn)練進(jìn)展不會(huì)受到影響。

訓(xùn)練結(jié)束時(shí),最后一次保存的訓(xùn)練檢查點(diǎn)文件中將包含最后更新的模型參數(shù),這正是我們希望在產(chǎn)品中使用的版本。

要恢復(fù)檢查點(diǎn)文件,可使用下列代碼:

  1. saver = tf.train.Saver() 
  2.  
  3. with tf.Session() as sess: 
  4.  
  5.    # 從訓(xùn)練檢查點(diǎn)文件恢復(fù)各交量 
  6.  
  7. ckpt = tf.train.get_checkpoint_state(sys.argv[1]) 
  8.  
  9. if ckpt and ckpt.model_checkpoint_path: 
  10.  
  11.      saver.restore(sess, sys.argv[1])+”/”+ 
  12.  
  13. ckpt.model_checkpoint_path) 
  14.  
  15. else
  16.  
  17.       print(“Checkpoint file not found”) 
  18.  
  19.       raise SystemExit  

對于Inception模型,可從下列鏈接下載一個(gè)預(yù)訓(xùn)練的檢查點(diǎn)文件:http://download.tensorflow.org/models/image/imagenet/inception-v3-2016-03-01.tar.gz。

  1. # 在docker容器中 
  2.  
  3. cd/tmp 
  4.  
  5. curl -O http://download.tensorflow.org/models/image/imagenet/ 
  6.  
  7. inception-v3-2016-03-01.tar.gz 
  8.  
  9. tar –xzf inception-v3-2016-03-01.tar.gz  

最后,利用tensorflow_serving.session_bundle.exporter.Exporter類將模型導(dǎo)出。我們通過傳入一個(gè)保存器實(shí)例創(chuàng)建了一個(gè)它的實(shí)例。然后,需要利用exporter.classification_signature方法創(chuàng)建該模型的簽名。該簽名指定了什么是input_tensor以及哪些是輸出張量。輸出由classes_tensor構(gòu)成,它包含了輸出類名稱列表以及模型分配給各類別的分值(或概率)的socres_tensor。通常,在一個(gè)包含的類別數(shù)相當(dāng)多的模型中,應(yīng)當(dāng)通過配置指定僅返回tf.nn.top_k所選擇的那些類別,即按模型分配的分?jǐn)?shù)按降序排列后的前K個(gè)類別。

最后一步是應(yīng)用這個(gè)調(diào)用了exporter.Exporter.init方法的簽名,并通過export方法導(dǎo)出模型,該方法接收一個(gè)輸出路徑、一個(gè)模型的版本號(hào)和會(huì)話對象。

  1. Scores, class_ids=tf.nn.top_k(y,NUM_CLASS_TO_RETURN) 
  2.  
  3. #為了簡便起見,我們將僅返回類別ID,應(yīng)當(dāng)另外對它們命名 
  4.  
  5. classes = 
  6.  
  7. tf.contrib.lookup.index_to_string(tf.to_int64(class_ids) 
  8.  
  9. mapping=tf.constant([str(i) for i in range(1001)])) 
  10.  
  11.  
  12. model_exporter = exporter.Exporter(saver) 
  13.  
  14. signature = exporter.classification_signature( 
  15.  
  16.    input_tensor=external_x, classes_tensor=classes, 
  17.  
  18. scores_tensor=scores) 
  19.  
  20. model_exporter.init(default_graph_signature=signature, 
  21.  
  22. init_op=tf.initialize_all_tables()) 
  23.  
  24.    model_exporter.export(sys.argv[1]+ "/export" 
  25.  
  26. tf.constant(time.time()), sess)  

由于對Exporter類代碼中自動(dòng)生成的代碼存在依賴,所以需要在Docker容器內(nèi)部使用bazel運(yùn)行我們的導(dǎo)出器。

為此,需要將代碼保存到之前啟動(dòng)的bazel工作區(qū)內(nèi)的exporter.py中。此外,還需要一個(gè)帶有構(gòu)建規(guī)則的BUILD文件,類似于下列內(nèi)容:

  1. # BUILD文件 
  2.  
  3. py_binary( 
  4.  
  5.    name = "export", 
  6.  
  7. srcs =[ 
  8.  
  9.   “export.py”, 
  10.  
  11. ], 
  12.  
  13. deps = [ 
  14.  
  15. “//tensorflow_serving/session_bundle:exporter”, 
  16.  
  17. “@org_tensorflow//tensorflow:tensorflow_py”, 
  18.  
  19. #僅在導(dǎo)出 inception模型時(shí)需 
  20.  
  21. “@inception_model//inception”, 
  22.  
  23. ], 
  24.  
  25.  

然后,可在容器中通過下列命令運(yùn)行導(dǎo)出器:

  1. # 在Docker容器中 
  2.  
  3. cd /mnt/home/serving_example  

它將依據(jù)可從/tmp/inception-v3中提取到的檢查點(diǎn)文件在/tmp/inception-v3/{current_timestamp}/ 中創(chuàng)建導(dǎo)出器。

注意,首次運(yùn)行它時(shí)需要花費(fèi)一些時(shí)間,因?yàn)樗仨氁獙ensorFlow進(jìn)行編譯。

定義服務(wù)器接口

接下來需要為導(dǎo)出的模型創(chuàng)建一個(gè)服務(wù)器。

TensorFlow服務(wù)使用gRPC協(xié)議(gRPC是一種基于HTTP/2的二進(jìn)制協(xié)議)。它支持用于創(chuàng)建服務(wù)器和自動(dòng)生成客戶端存根的各種語言。由于TensorFlow是基于C++的,所以需要在其中定義自己的服務(wù)器。幸運(yùn)的是,服務(wù)器端代碼比較簡短。

為了使用gRPS,必須在一個(gè)protocol buffer中定義服務(wù)契約,它是用于gRPC的IDL(接口定義語言)和二進(jìn)制編碼。下面來定義我們的服務(wù)。前面的導(dǎo)出一節(jié)曾提到,我們希望服務(wù)有一個(gè)能夠接收一個(gè)JPEG編碼的待分類的圖像字符串作為輸入,并可返回一個(gè)依據(jù)分?jǐn)?shù)排列的由推斷得到的類別列表。

這樣的服務(wù)應(yīng)定義在一個(gè)classification_service.proto文件中,類似于:

  1. syntax = "proto3"; 
  2.  
  3. message ClassificationRequest { 
  4.  
  5. // JPEG 編碼的圖像字符串 
  6.  
  7. bytes input = 1; 
  8.  
  9. }; 
  10.  
  11. message ClassificationResponse{ 
  12.  
  13.     repeated ClassificationClass classes = 1; 
  14.  
  15. }; 
  16.  
  17. message ClassificationClass { 
  18.  
  19. string name = 1; 
  20.  
  21. float score = 2; 
  22.  
  23.  

可對能夠接收一幅圖像,或一個(gè)音頻片段或一段文字的任意類型的服務(wù)使用同一個(gè)接口。

為了使用像數(shù)據(jù)庫記錄這樣的結(jié)構(gòu)化輸入,需要修改ClassificationRequest消息。例如,如果試圖為Iris數(shù)據(jù)集構(gòu)建分類服務(wù),則需要如下編碼:

  1. message ClassificationRequest { 
  2.  
  3. float petalWidth = 1; 
  4.  
  5. float petaHeight = 2; 
  6.  
  7. float petalWidth = 3; 
  8.  
  9. float petaHeight = 4; 
  10.  
  11.  

這個(gè)proto文件將由proto編譯器轉(zhuǎn)換為客戶端和服務(wù)器相應(yīng)的類定義。為了使用protobuf編譯器,必須為BUILD文件添加一條新的規(guī)則,類似于:

  1. load("@protobuf//:protobuf.bzl""cc_proto_library"
  2.  
  3. cc_proto_library( 
  4.  
  5. name="classification_service_proto"
  6.  
  7. srcs=["classification_service.proto"], 
  8.  
  9. cc_libs = ["@protobuf//:protobuf"], 
  10.  
  11. protoc="@protobuf//:protoc"
  12.  
  13. default_runtime="@protobuf//:protobuf"
  14.  
  15. use_grpc_plugin=1 
  16.  
  17.  

請注意位于上述代碼片段中最上方的load。它從外部導(dǎo)入的protobuf庫中導(dǎo)入了cc_proto_library規(guī)則定義。然后,利用它為proto文件定義了一個(gè)構(gòu)建規(guī)則。利用bazel build :classification_service_proto可運(yùn)行該構(gòu)建,并通過bazel-genfiles/classification_service.grpc.pb.h檢查結(jié)果:

  1. … 
  2.  
  3. class ClassificationService { 
  4.  
  5. ... 
  6.  
  7. class Service : public ::grpc::Service { 
  8.  
  9. public
  10.  
  11. Service(); 
  12.  
  13. virtual ~Service(); 
  14.  
  15. virtual ::grpc::Status classify(::grpc::ServerContext* 
  16.  
  17. context, const ::ClassificationRequest* 
  18.  
  19. request, ::ClassificationResponse* response); 
  20.  
  21. };  

按照推斷邏輯,ClassificationService::Service是必須要實(shí)現(xiàn)的接口。我們也可通過檢查bazel-genfiles/classification_service.pb.h查看request和response消息的定義:

  1. … 
  2.  
  3. class ClassificationRequest : 
  4.  
  5. public ::google::protobuf::Message { 
  6.  
  7. ... 
  8.  
  9. const ::std::string& input() const; 
  10.  
  11. void set_input(const ::std::string& value); 
  12.  
  13. ... 
  14.  
  15.  
  16. class ClassificationResponse : 
  17.  
  18. public ::google::protobuf::Message { 
  19.  
  20. ... 
  21.  
  22. const ::ClassificationClass& classes() const; 
  23.  
  24. void set_allocated_classes(::ClassificationClass* 
  25.  
  26. classes); 
  27.  
  28. ... 
  29.  
  30.  
  31. class ClassificationClass : 
  32.  
  33. public ::google::protobuf::Message { 
  34.  
  35. ... 
  36.  
  37. const ::std::string& name() const; 
  38.  
  39. void set_name(const ::std::string& value); 
  40.  
  41. float score() const; 
  42.  
  43. void set_score(float value); 
  44.  
  45. ... 
  46.  
  47.  

可以看到,proto定義現(xiàn)在變成了每種類型的C++類接口。它們的實(shí)現(xiàn)也是自動(dòng)生成的,這樣便可直接使用它們。

實(shí)現(xiàn)推斷服務(wù)器

為實(shí)現(xiàn)ClassificationService::Service,需要加載導(dǎo)出模型并對其調(diào)用推斷方法。這可通過一個(gè)SessionBundle對象來實(shí)現(xiàn),該對象是從導(dǎo)出的模型創(chuàng)建的,它包含了一個(gè)帶有完全加載的數(shù)據(jù)流圖的TF會(huì)話對象,以及帶有定義在導(dǎo)出工具上的分類簽名的元數(shù)據(jù)。

為了從導(dǎo)出的文件路徑創(chuàng)建SessionBundle對象,可定義一個(gè)便捷函數(shù),以處理這個(gè)樣板文件:

  1. #include <iostream> 
  2.  
  3. #include <memory> 
  4.  
  5. #include <string> 
  6.  
  7.  
  8. #include <grpc++/grpc++.h> 
  9.  
  10. #include "classification_service.grpc.pb.h" 
  11.  
  12.  
  13. #include "tensorflow_serving/servables/tensorflow/ 
  14.  
  15. session_bundle_factory.h" 
  16.  
  17.  
  18. using namespace std; 
  19.  
  20. using namespace tensorflow::serving; 
  21.  
  22. using namespace grpc; 
  23.  
  24.  
  25. unique_ptr<SessionBundle> createSessionBundle(const string& 
  26.  
  27. pathToExportFiles) { 
  28.  
  29. SessionBundleConfig session_bundle_config = 
  30.  
  31. SessionBundleConfig(); 
  32.  
  33. unique_ptr<SessionBundleFactory> bundle_factory; 
  34.  
  35. SessionBundleFactory::Create(session_bundle_config, 
  36.  
  37. &bundle_factory); 
  38.  
  39.  
  40.         unique_ptr<SessionBundle> sessionBundle; 
  41.  
  42. bundle_factory- 
  43.  
  44. >CreateSessionBundle(pathToExportFiles, &sessionBundle); 
  45.  
  46.  
  47.        return sessionBundle; 
  48.  
  49.  

在這段代碼中,我們利用了一個(gè)SessionBundleFactory類創(chuàng)建了SessionBundle對象,并將其配置為從pathToExportFiles指定的路徑中加載導(dǎo)出的模型。最后返回一個(gè)指向所創(chuàng)建的SessionBundle實(shí)例的unique指針。

接下來需要定義服務(wù)的實(shí)現(xiàn)—ClassificationServiceImpl,該類將接收SessionBundle實(shí)例作為參數(shù),以在推斷中使用:

  1. class ClassificationServiceImpl final : public 
  2.  
  3. ClassificationService::Service { 
  4.  
  5. private: 
  6.  
  7. unique_ptr<SessionBundle> sessionBundle; 
  8.  
  9. public
  10.  
  11. ClassificationServiceImpl(unique_ptr<SessionBundle> 
  12.  
  13. sessionBundle) : 
  14.  
  15. sificationServiceImpl(unique_ptr<Sessi 
  16.  
  17. Status classify(ServerContext* context, const 
  18.  
  19. ClassificationRequest* request, 
  20.  
  21. ClassificationResponse* response) 
  22.  
  23. override { 
  24.  
  25. // 加載分類簽名 
  26.  
  27. ClassificationSignature signature; 
  28.  
  29. const tensorflow::Status signatureStatus = 
  30.  
  31. GetClassificationSignature(sessionBundle- 
  32.  
  33. >meta_graph_def, &signature); 
  34.  
  35. if (!signatureStatus.ok()) { 
  36.  
  37. return Status(StatusCode::INTERNAL, 
  38.  
  39. signatureStatus.error_message()); 
  40.  
  41.  
  42. // 將 protobuf 輸入變換為推斷輸入張量 
  43.  
  44. tensorflow::Tensor 
  45.  
  46. input(tensorflow::DT_STRING, tensorflow::TensorShape()); 
  47.  
  48. input.scalar<string>()() = request->input(); 
  49.  
  50. vector<tensorflow::Tensor> outputs; 
  51.  
  52. //運(yùn)行推斷 
  53.  
  54. const tensorflow::Status inferenceStatus = 
  55.  
  56. sessionBundle->session->Run( 
  57.  
  58. {{signature.input().tensor_name(), 
  59.  
  60. input}}, 
  61.  
  62. {signature.classes().tensor_name(), 
  63.  
  64. signature.scores().tensor_name()}, 
  65.  
  66. {}, 
  67.  
  68. &outputs); 
  69.  
  70. if (!inferenceStatus.ok()) { 
  71.  
  72. return Status(StatusCode::INTERNAL, 
  73.  
  74. inferenceStatus.error_message()); 
  75.  
  76.  
  77. //將推斷輸出張量變換為protobuf輸出 
  78.  
  79. for (int i = 0; i < 
  80.  
  81. outputs[0].vec<string>().size(); ++i) { 
  82.  
  83. ClassificationClass 
  84.  
  85. *classificationClass = response->add_classes(); 
  86.  
  87. classificationClass- 
  88.  
  89. >set_name(outputs[0].flat<string>()(i)); 
  90.  
  91. classificationClass- 
  92.  
  93. >set_score(outputs[1].flat<float>()(i)); 
  94.  
  95.  
  96. return Status::OK; 
  97.  
  98.  
  99. };  

classify方法的實(shí)現(xiàn)包含了4個(gè)步驟:

  • 利用GetClassificationSignature函數(shù)加載存儲(chǔ)在模型導(dǎo)出元數(shù)據(jù)中的Classification-Signature。這個(gè)簽名指定了輸入張量的(邏輯)名稱到所接收的圖像的真實(shí)名稱以及數(shù)據(jù)流圖中輸出張量的(邏輯)名稱到對其獲得推斷結(jié)果的映射。
  • 將JPEG編碼的圖像字符串從request參數(shù)復(fù)制到將被進(jìn)行推斷的張量。
  • 運(yùn)行推斷。它從sessionBundle獲得TF會(huì)話對象,并運(yùn)行一次,同時(shí)傳入輸入和輸出張量的推斷。
  • 從輸出張量將結(jié)果復(fù)制到由ClassificationResponse消息指定的形狀中的response輸出參數(shù)并格式化。

最后一段代碼是設(shè)置gRPC服務(wù)器并創(chuàng)建ClassificationServiceImpl實(shí)例(用Session-Bundle對象進(jìn)行配置)的樣板代碼。

  1. int main(int argc, char** argv) { 
  2.  
  3. if (argc < 3) { 
  4.  
  5.     cerr << "Usage: server <port> /path/to/export/files" << 
  6.  
  7. endl; 
  8.  
  9.             return 1; 
  10.  
  11.  
  12.     const string serverAddress(string("0.0.0.0:") + 
  13.  
  14. argv[1]); 
  15.  
  16.     const string pathToExportFile (argv[2]) ; 
  17.  
  18.  
  19.     unique_ptr<SessionBundle> sessionBundle = 
  20.  
  21. createSessionBundle(pathToExportFiles); 
  22.  
  23.  
  24.     const string serverAddres 
  25.  
  26. classificationServiceImpl(move(sessionBundle)); 
  27.  
  28.  
  29. ServerBuilder builder; 
  30.  
  31. builder. AddListeningPort(serverAddress, 
  32.  
  33. grpc::InsecureServerCredentials()); 
  34.  
  35.     builder.RegisterService(&classificationServiceImpl); 
  36.  
  37.  
  38.     unique_ptr<Server> server = builder.BuildAndStart(); 
  39.  
  40. cout << "Server listening on " << serverAddress << endl; 
  41.  
  42.  
  43.     server->Wait(); 
  44.  
  45.     return 0; 
  46.  
  47.  

為了編譯這段代碼,需要在BUILD文件中為其定義一條規(guī)則:

  1. cc_binary( 
  2.  
  3. name = "server"
  4.  
  5. srcs = [ 
  6.  
  7. "server.cc"
  8.  
  9. ], 
  10.  
  11. deps = [ 
  12.  
  13. ":classification_service_proto"
  14.  
  15. "@tf_serving//tensorflow_serving/servables/ 
  16.  
  17. tensorflow:session_bundle_factory", 
  18.  
  19.       "@grpc//:grpc++"
  20.  
  21. ], 
  22.  
  23. )  

借助這段代碼,便可通過命令bazel run :server 9999 /tmp/inception-v3/export/{timestamp}從容器中運(yùn)行推斷服務(wù)器。

客戶端應(yīng)用

由于gRPC是基于HTTP/2的,將來可能會(huì)直接從瀏覽器調(diào)用基于gRPC的服務(wù),但除非主流的瀏覽器支持所需的HTTP/2特性,且谷歌發(fā)布瀏覽器端的JavaScript gRPC客戶端程序,從webapp訪問推斷服務(wù)都應(yīng)當(dāng)通過服務(wù)器端的組件進(jìn)行。

接下來將基于BaseHTTPServer搭建一個(gè)簡單的Python Web服務(wù)器,BaseHTTPServer將處理上載的圖像文件,并將其發(fā)送給推斷服務(wù)進(jìn)行處理,再將推斷結(jié)果以純文本形式返回。

為了將圖像發(fā)送到推斷服務(wù)器進(jìn)行分類,服務(wù)器將以一個(gè)簡單的表單對GET請求做出響應(yīng)。所使用的代碼如下:

  1. From BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler 
  2.  
  3. import cgi 
  4.  
  5. import classification_service_pb2 
  6.  
  7. From grpc.beta import implementations 
  8.  
  9.  
  10. class ClientApp (BaseHTTPRequestHandler); 
  11.  
  12.    def do_GET(self): 
  13.  
  14. self.respond_form() 
  15.  
  16.  
  17.    def respond_form(self, response=""): 
  18.  
  19.  
  20.       form = ""
  21.  
  22. <html><body> 
  23.  
  24. <h1>Image classification service</h1> 
  25.  
  26. <form enctype="multipart/form-data" method="post"
  27.  
  28. <div>Image: <input type="file" name="file" 
  29.  
  30. accept="image/jpeg"></div> 
  31.  
  32.       <div><input type="submit" value="Upload"></div> 
  33.  
  34. </form> 
  35.  
  36. %s 
  37.  
  38. </body></html> 
  39.  
  40. ""
  41.  
  42.  
  43. response = form % response 
  44.  
  45.  
  46. self.send_response(200) 
  47.  
  48. self.send_header("Content-type""text/html"
  49.  
  50. self.send_header("Content-length", len(response)) 
  51.  
  52. self.end_headers() 
  53.  
  54. self.wfile.write(response)  

為了從Web App服務(wù)器調(diào)用推斷功能,需要ClassificationService相應(yīng)的Python protocol buffer客戶端。為了生成它,需要運(yùn)行Python的protocol buffer編譯器:

  1. pip install grpcio cython grpcio-tools 
  2.  
  3. python -m grpc.tools.protoc -I. --python_out=. -- 
  4.  
  5. grpc_python_out=. classification_service.proto  

它將生成包含了用于調(diào)用服務(wù)的stub的classification_service_pb2.py文件。

服務(wù)器接收到POST請求后,將對發(fā)送的表單進(jìn)行解析,并用它創(chuàng)建一個(gè)Classification-Request對象。然后為這個(gè)分類服務(wù)器設(shè)置一個(gè)channel,并將請求提交給它。最后,它會(huì)將分類響應(yīng)渲染為HTML,并送回給用戶。

  1. def do_POST(self): 
  2.  
  3.    form = cgi.FieldStorage( 
  4.  
  5. fp=self.rfile, 
  6.  
  7. headers=self.headers, 
  8.  
  9. environ={ 
  10.  
  11. 'REQUEST_METHOD''POST'
  12.  
  13. 'CONTENT_TYPE': self.headers['Content-Type'], 
  14.  
  15. }) 
  16.  
  17.    request = 
  18.  
  19. classification_service_pb2.ClassificationRequest() 
  20.  
  21. request.input = form['file'].file.read() 
  22.  
  23.  
  24. channel = 
  25.  
  26. implementations.insecure_channel("127.0.0.1", 9999) 
  27.  
  28. stub = 
  29.  
  30. classification_service_pb2.beta_create_ClassificationService_stub(channel) 
  31.  
  32. response = stub.classify(request, 10) # 10 secs 
  33.  
  34. timeout 
  35.  
  36. self.respond_form("<div>Response: %s</div>" % 
  37.  
  38. response)  

為了運(yùn)行該服務(wù)器,可從該容器外部使用命令python client.py。然后,用瀏覽器導(dǎo)航到http://localhost:8080來訪問其UI。請上傳一幅圖像并查看推斷結(jié)果如何。

產(chǎn)品準(zhǔn)備

在結(jié)束本文內(nèi)容之前,我們還將學(xué)習(xí)如何將分類服務(wù)器應(yīng)用于產(chǎn)品中。

首先,將編譯后的服務(wù)器文件復(fù)制到一個(gè)容器內(nèi)的永久位置,并清理所有的臨時(shí)構(gòu)建文件:

  1. #在容器內(nèi)部 
  2.  
  3. mkdir /opt/classification_server 
  4.  
  5. cd /mnt/home/serving_example 
  6.  
  7. cp -R bazel-bin/. /opt/classification_server 
  8.  
  9. bazel clean  

現(xiàn)在,在容器外部,我們必須將其狀態(tài)提交給一個(gè)新的Docker鏡像,基本含義是創(chuàng)建一個(gè)記錄其虛擬文件系統(tǒng)變化的快照。

  1. #在容器外部 
  2.  
  3. docker ps 
  4.  
  5. #獲取容器ID 
  6.  
  7. docker commit <container id>  

這樣,便可將圖像推送到自己偏好的docker服務(wù)云中,并對其進(jìn)行服務(wù)。

本文小結(jié)

在本文中,我們學(xué)習(xí)了如何將訓(xùn)練好的模型用于服務(wù)、如何將它們導(dǎo)出,以及如何構(gòu)建可運(yùn)行這些模型的快速、輕量級服務(wù)器;還學(xué)習(xí)了當(dāng)給定了從其他App使用TensorFlow模型的完整工具集后,如何創(chuàng)建使用這些模型的簡單Web App。 

責(zé)任編輯:龐桂玉 來源: CSDN大數(shù)據(jù)
相關(guān)推薦

2014-09-01 09:57:11

Go產(chǎn)品環(huán)境最佳語言

2018-01-08 09:09:46

機(jī)器學(xué)習(xí)模型NET

2024-02-20 15:17:35

機(jī)器學(xué)習(xí)模型部署

2024-02-21 19:00:12

2020-07-10 10:39:04

Python開發(fā)工具

2022-12-21 19:06:55

機(jī)器學(xué)習(xí)人工智能

2016-02-18 10:32:39

谷歌TensorFlow 機(jī)器學(xué)習(xí)

2017-11-28 08:47:19

TensorFlow區(qū)塊鏈大數(shù)據(jù)分析

2025-02-17 08:00:00

機(jī)器學(xué)習(xí)開發(fā)Docker

2021-11-02 09:40:50

TensorFlow機(jī)器學(xué)習(xí)人工智能

2018-12-28 09:00:00

人工智能機(jī)器學(xué)習(xí)開源框架

2021-01-25 09:00:00

機(jī)器學(xué)習(xí)人工智能算法

2019-08-08 08:00:00

深度學(xué)習(xí)機(jī)器學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)

2020-05-19 14:29:50

機(jī)器學(xué)習(xí)TensorFlow

2022-01-22 19:21:38

人工智能AI機(jī)器視覺

2023-07-13 11:03:12

2023-02-24 13:29:11

2021-12-13 09:14:06

清單管理數(shù)據(jù)集

2020-07-15 13:51:48

TensorFlow數(shù)據(jù)機(jī)器學(xué)習(xí)

2017-04-07 06:55:11

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)