精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面向機器智能的TensorFlow實踐:產品環境中模型的部署

人工智能 機器學習
本文將創建一個簡單的Web App,使用戶能夠上傳一幅圖像,并對其運行Inception模型,實現圖像的自動分類。

面向機器智能的TensorFlow實踐:產品環境中模型的部署

在了解如何利用TesnsorFlow構建和訓練各種模型——從基本的機器學習模型到復雜的深度學習網絡后,我們就要考慮如何將訓練好的模型投入于產品,以使其能夠為其他應用所用,本文對此將進行詳細介紹。文章節選自《面向機器智能的TensorFlow實踐》第7章。

本文將創建一個簡單的Web App,使用戶能夠上傳一幅圖像,并對其運行Inception模型,實現圖像的自動分類。

搭建TensorFlow服務開發環境

Docker鏡像

TensorFlow服務是用于構建允許用戶在產品中使用我們提供的模型的服務器的工具。在開發過程中,使用該工具的方法有兩種:手工安裝所有的依賴項和工具,并從源碼開始構建;或利用Docker鏡像。這里準備使用后者,因為它更容易、更干凈,同時允許在其他不同于Linux的環境中進行開發。

如果不了解Docker鏡像,不妨將其想象為一個輕量級的虛擬機鏡像,但它在運行時不需要以在其中運行完整的操作系統為代價。如果尚未安裝Docker,請在開發機中安裝它,點擊查看具體安裝步驟(https://docs.docker.com/engine/installation/)。

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

  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  

請注意,執行上述命令后,下載所有的依賴項可能需要一段較長的時間。

上述命令執行完畢后,為了使用該鏡像運行容器,可輸入下列命令:

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

該命令執行后會將你的home目錄加載到容器的/mnt/home路徑中,并允許在其中的一個終端下工作。這是非常有用的,因為你可使用自己偏好的IDE或編輯器直接編輯代碼,同時在運行構建工具時僅使用該容器。它還會開放端口9999,使你可從自己的主機中訪問它,并供以后將要構建的服務器使用。

鍵入exit命令可退出該容器終端,使其停止運行,也可利用上述命令在需要的時候啟動它。

Bazel工作區

由于TensorFlow服務程序是用C++編寫的,因此在構建時應使用Google的Bazel構建工具。我們將從最近創建的容器內部運行Bazel。

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

我們需要的依賴項是TensorFlow服務庫。在我們的例子中,TensorFlow模型庫包含了Inception模型的代碼。

不幸的是,在撰寫本書時,TensorFlow服務尚不支持作為Git庫通過Bazel直接引用,因此必須在項目中將它作為一個Git的子模塊包含進去:

  1. # 在本地機器上 
  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規則將第三方依賴項定義為在本地存儲的文件。此外,還需利用從項目中導入的tf_workspace規則對TensorFlow的依賴項初始化:

  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. # 僅當導入inception 模型時需要 
  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. 最后,需要從容器內為Tensorflow運行./configure: 
  52.  
  53. # 在Docker容器中 
  54.  
  55. cd /mnt/home/serving_example/tf_serving/tensorflow 
  56.  
  57. ./configure  

導出訓練好的模型

一旦模型訓練完畢并準備進行評估,便需要將數據流圖及其變量值導出,以使其可為產品所用。

模型的數據流圖應當與其訓練版本有所區分,因為它必須從占位符接收輸入,并對其進行單步推斷以計算輸出。對于Inception模型這個例子,以及對于任意一般圖像識別模型,我們希望輸入是一個表示了JPEG編碼的圖像字符串,這樣就可輕易地將它傳送到消費App中。這與從TFRecord文件讀取訓練輸入頗為不同。

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

  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)  

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

例如,對于Inception模型,應當有下列方法:

  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. # 將圖像字符串轉換為一個各分量位于[0,1]內的像素張量 
  12.  
  13. image = 
  14.  
  15. tf.image.convert_image_dtype(tf.image.decode_jpeg(external_x, 
  16.  
  17. channels=3), tf.float32) 
  18.  
  19. # 對圖像尺寸進行縮放,使其符合模型期望的寬度和高度 
  20.  
  21. images = tf.image.resize_bilinear(tf.expand_dims(image, 
  22.  
  23. 0),[299,299]) 
  24.  
  25. # 將像素值變換到模型所要求的區間[-1,1]內 
  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   

這個推斷方法要求各參數都被賦值。我們將從一個訓練檢查點恢復這些參數值。你可能還記得,在前面的章節中,我們周期性地保存模型的訓練檢查點文件。那些文件中包含了當時學習到的參數,因此當出現異常時,訓練進展不會受到影響。

訓練結束時,最后一次保存的訓練檢查點文件中將包含最后更新的模型參數,這正是我們希望在產品中使用的版本。

要恢復檢查點文件,可使用下列代碼:

  1. saver = tf.train.Saver() 
  2.  
  3. with tf.Session() as sess: 
  4.  
  5.    # 從訓練檢查點文件恢復各交量 
  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模型,可從下列鏈接下載一個預訓練的檢查點文件: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類將模型導出。我們通過傳入一個保存器實例創建了一個它的實例。然后,需要利用exporter.classification_signature方法創建該模型的簽名。該簽名指定了什么是input_tensor以及哪些是輸出張量。輸出由classes_tensor構成,它包含了輸出類名稱列表以及模型分配給各類別的分值(或概率)的socres_tensor。通常,在一個包含的類別數相當多的模型中,應當通過配置指定僅返回tf.nn.top_k所選擇的那些類別,即按模型分配的分數按降序排列后的前K個類別。

最后一步是應用這個調用了exporter.Exporter.init方法的簽名,并通過export方法導出模型,該方法接收一個輸出路徑、一個模型的版本號和會話對象。

  1. Scores, class_ids=tf.nn.top_k(y,NUM_CLASS_TO_RETURN) 
  2.  
  3. #為了簡便起見,我們將僅返回類別ID,應當另外對它們命名 
  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類代碼中自動生成的代碼存在依賴,所以需要在Docker容器內部使用bazel運行我們的導出器。

為此,需要將代碼保存到之前啟動的bazel工作區內的exporter.py中。此外,還需要一個帶有構建規則的BUILD文件,類似于下列內容:

  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. #僅在導出 inception模型時需 
  20.  
  21. “@inception_model//inception”, 
  22.  
  23. ], 
  24.  
  25.  

然后,可在容器中通過下列命令運行導出器:

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

它將依據可從/tmp/inception-v3中提取到的檢查點文件在/tmp/inception-v3/{current_timestamp}/ 中創建導出器。

注意,首次運行它時需要花費一些時間,因為它必須要對TensorFlow進行編譯。

定義服務器接口

接下來需要為導出的模型創建一個服務器。

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

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

這樣的服務應定義在一個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.  

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

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

  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.  

這個proto文件將由proto編譯器轉換為客戶端和服務器相應的類定義。為了使用protobuf編譯器,必須為BUILD文件添加一條新的規則,類似于:

  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。它從外部導入的protobuf庫中導入了cc_proto_library規則定義。然后,利用它為proto文件定義了一個構建規則。利用bazel build :classification_service_proto可運行該構建,并通過bazel-genfiles/classification_service.grpc.pb.h檢查結果:

  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是必須要實現的接口。我們也可通過檢查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定義現在變成了每種類型的C++類接口。它們的實現也是自動生成的,這樣便可直接使用它們。

實現推斷服務器

為實現ClassificationService::Service,需要加載導出模型并對其調用推斷方法。這可通過一個SessionBundle對象來實現,該對象是從導出的模型創建的,它包含了一個帶有完全加載的數據流圖的TF會話對象,以及帶有定義在導出工具上的分類簽名的元數據。

為了從導出的文件路徑創建SessionBundle對象,可定義一個便捷函數,以處理這個樣板文件:

  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.  

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

接下來需要定義服務的實現—ClassificationServiceImpl,該類將接收SessionBundle實例作為參數,以在推斷中使用:

  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. //運行推斷 
  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方法的實現包含了4個步驟:

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

最后一段代碼是設置gRPC服務器并創建ClassificationServiceImpl實例(用Session-Bundle對象進行配置)的樣板代碼。

  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文件中為其定義一條規則:

  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}從容器中運行推斷服務器。

客戶端應用

由于gRPC是基于HTTP/2的,將來可能會直接從瀏覽器調用基于gRPC的服務,但除非主流的瀏覽器支持所需的HTTP/2特性,且谷歌發布瀏覽器端的JavaScript gRPC客戶端程序,從webapp訪問推斷服務都應當通過服務器端的組件進行。

接下來將基于BaseHTTPServer搭建一個簡單的Python Web服務器,BaseHTTPServer將處理上載的圖像文件,并將其發送給推斷服務進行處理,再將推斷結果以純文本形式返回。

為了將圖像發送到推斷服務器進行分類,服務器將以一個簡單的表單對GET請求做出響應。所使用的代碼如下:

  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服務器調用推斷功能,需要ClassificationService相應的Python protocol buffer客戶端。為了生成它,需要運行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  

它將生成包含了用于調用服務的stub的classification_service_pb2.py文件。

服務器接收到POST請求后,將對發送的表單進行解析,并用它創建一個Classification-Request對象。然后為這個分類服務器設置一個channel,并將請求提交給它。最后,它會將分類響應渲染為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)  

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

產品準備

在結束本文內容之前,我們還將學習如何將分類服務器應用于產品中。

首先,將編譯后的服務器文件復制到一個容器內的永久位置,并清理所有的臨時構建文件:

  1. #在容器內部 
  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  

現在,在容器外部,我們必須將其狀態提交給一個新的Docker鏡像,基本含義是創建一個記錄其虛擬文件系統變化的快照。

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

這樣,便可將圖像推送到自己偏好的docker服務云中,并對其進行服務。

本文小結

在本文中,我們學習了如何將訓練好的模型用于服務、如何將它們導出,以及如何構建可運行這些模型的快速、輕量級服務器;還學習了當給定了從其他App使用TensorFlow模型的完整工具集后,如何創建使用這些模型的簡單Web App。 

責任編輯:龐桂玉 來源: CSDN大數據
相關推薦

2014-09-01 09:57:11

Go產品環境最佳語言

2018-01-08 09:09:46

機器學習模型NET

2024-02-20 15:17:35

機器學習模型部署

2025-07-07 08:10:24

2024-02-21 19:00:12

2020-07-10 10:39:04

Python開發工具

2022-12-21 19:06:55

機器學習人工智能

2016-02-18 10:32:39

谷歌TensorFlow 機器學習

2017-11-28 08:47:19

TensorFlow區塊鏈大數據分析

2025-02-17 08:00:00

機器學習開發Docker

2021-01-25 09:00:00

機器學習人工智能算法

2018-12-28 09:00:00

人工智能機器學習開源框架

2021-11-02 09:40:50

TensorFlow機器學習人工智能

2020-05-19 14:29:50

機器學習TensorFlow

2022-01-22 19:21:38

人工智能AI機器視覺

2019-08-08 08:00:00

深度學習機器學習神經網絡

2023-02-24 13:29:11

2023-07-13 11:03:12

2021-12-13 09:14:06

清單管理數據集

2020-07-15 13:51:48

TensorFlow數據機器學習
點贊
收藏

51CTO技術棧公眾號

黄色一级视频片| 91国产丝袜在线放| 国产精品高清无码在线观看| 成人在线中文| 一二三区精品视频| 欧美日韩一区在线观看视频| 91免费视频播放| 精品二区视频| 视频一区视频二区国产精品| 性生交大片免费看l| 日韩精品99| 亚洲精品国产高清久久伦理二区| 欧美另类一区| 亚洲精品久久久蜜桃动漫 | 久久亚洲AV无码| 亚洲精品蜜桃乱晃| 欧美一级二级在线观看| aa免费在线观看| 色呦呦久久久| 欧美国产国产综合| 国产日韩在线一区二区三区| 国产精品sm调教免费专区| 午夜激情一区| 最近2019年日本中文免费字幕| 日本少妇xxxx软件| 欧洲亚洲精品久久久久| 欧美日韩色婷婷| 在线观看污视频| 国产九色在线| 91香蕉视频mp4| 亚洲淫片在线视频| 在线免费观看av片| 免费日韩视频| 久久久久久久久亚洲| 国产又粗又猛又爽又黄的视频四季| 精品国产导航| 欧美sm极限捆绑bd| 亚洲黄色片免费看| 精品国产欧美日韩一区二区三区| 精品国产乱码久久久久久婷婷| 国产又粗又大又爽的视频| 日本在线视频站| 国产偷国产偷精品高清尤物| 激情久久av| 亚洲免费一级片| 国产精品一区不卡| 成人在线观看视频网站| 一本到在线视频| 美女久久久精品| 国产精品第3页| 中国精品一区二区| 日日欢夜夜爽一区| 国产精品成人一区二区| 欧美男人亚洲天堂| 久久午夜av| 国产91网红主播在线观看| 国产一区二区99| 中文欧美日韩| 96精品视频在线| 探花视频在线观看| 久久久成人网| 国产精品第七影院| 中文字幕男人天堂| 精品影视av免费| 91久久精品美女高潮| 国产农村老头老太视频| 国产精品一区二区视频| 成人午夜激情免费视频| 国产叼嘿视频在线观看| 国产91富婆露脸刺激对白| 91久久久久久| 性网爆门事件集合av| 成人美女视频在线观看| 久久综合给合久久狠狠色| 激情小视频在线| 国产精品美女久久久久高潮| 熟女视频一区二区三区| 男人天堂亚洲| 日韩欧美在线视频| 黄色永久免费网站| 视频二区欧美| 亚洲欧美日韩一区在线| 成年人看的免费视频| 中文字幕一区二区三区久久网站| 久久久久久久久久国产| 丁香六月婷婷综合| 精品亚洲成a人| 国产精品免费在线| 久久久久久青草| 成人免费在线视频观看| 91免费国产精品| 另类图片综合电影| 在线播放欧美女士性生活| 四虎永久免费观看| 国产不卡av一区二区| 久久影院在线观看| 欧美a视频在线观看| 精品一二三四区| 欧美国产二区| av片哪里在线观看| 欧美在线免费视屏| av在线天堂网| 久久福利影院| 国语自产精品视频在线看一大j8| 亚洲毛片一区二区三区| 国产风韵犹存在线视精品| 欧美lavv| 黄色成人在线网| 欧美日韩免费在线视频| 制服.丝袜.亚洲.中文.综合懂| 国产一区二区三区电影在线观看| 欧美丰满老妇厨房牲生活 | 香蕉网在线播放| 五月精品视频| 日韩av色综合| 懂色av一区二区三区四区| 国产精品区一区二区三区| 青青青免费在线| 国产午夜精品一区在线观看| 亚洲欧美国产另类| 国产在线拍揄自揄拍| 麻豆成人91精品二区三区| 精品综合在线| 激情图片在线观看高清国产| 欧美男男青年gay1069videost | 亚洲欧洲日本精品| 久久99视频| 欧美孕妇与黑人孕交| 亚洲黄色精品视频| 亚洲欧美日韩在线| 久久久久久久久久久久久久久国产| 色天下一区二区三区| 久久久久久欧美| 精品久久久无码中文字幕| 国产精品色眯眯| 无码人妻精品一区二区三区66| 麻豆一区二区| 国内自拍欧美激情| 后进极品白嫩翘臀在线视频| 亚洲另类在线一区| 伊人精品视频在线观看| 日韩国产在线| 国产日韩在线看片| 在线免费看av| 欧美理论片在线| 亚洲欧美另类日本| 精品一区二区三区视频在线观看 | 国内精品久久久久久久97牛牛| 亚洲xxxxx| av在线免费播放| 日韩一区二区电影网| 四虎免费在线视频| 国产成人精品免费一区二区| 欧美一区二区三区综合| youjizz亚洲| 久久久久久国产免费| 亚洲精品喷潮一区二区三区| 亚洲国产视频在线| 国产极品一区二区| 国产农村妇女毛片精品久久莱园子| 国产伦精品一区二区三区视频免费| 波多野结衣在线播放| 亚洲国产精品va在线看黑人动漫| 伊人国产在线观看| 99re这里都是精品| 久久久久久久少妇| 婷婷综合视频| julia一区二区中文久久94| 91九色在线播放| 国产偷国产偷亚洲清高网站| 日韩综合在线观看| 国产精品久久久久影院老司| 97人人模人人爽人人澡| 亚洲一级电影| 欧美中日韩一区二区三区| 123成人网| 蜜臀久久99精品久久久无需会员 | 久久久国产午夜精品| 久久这里只精品| 欧美在线观看天堂一区二区三区| 国产精品久久精品视| 自拍一区在线观看| 日韩在线观看高清| 欧美特级特黄aaaaaa在线看| 一本久久a久久精品亚洲 | 一区二区三区在线观看欧美| 国产真实乱人偷精品| 日本美女一区二区三区| www.一区二区.com| 最新亚洲精品| 91在线网站视频| 欧美粗大gay| 美女视频黄免费的亚洲男人天堂| 你懂的免费在线观看视频网站| 69p69国产精品| 午夜精品久久久久久久久久久久久蜜桃| 国产欧美日韩精品一区| 又黄又爽又色的视频| 久久久精品性| 欧美激情亚洲天堂| 禁果av一区二区三区| 国产精品视频一区二区三区经| 性感美女一区二区在线观看| 欧美床上激情在线观看| 国产女主播在线直播| 精品久久久久久无| 中文无码精品一区二区三区| 亚洲成人av资源| 午夜激情福利网| 欧美高清在线一区二区| 亚洲欧美日本一区| 国产精品一卡二| 九九九在线观看视频| 99视频精品| 天堂а√在线中文在线| 日韩精品诱惑一区?区三区| 精品视频高清无人区区二区三区| 国产精品成人**免费视频| 日韩美女视频在线观看| sqte在线播放| 久久在线免费视频| 色影院视频在线| 亚洲日本中文字幕| 污视频在线免费| 日韩欧美国产一区在线观看| 亚洲一区中文字幕在线| 91成人国产精品| 日韩av男人天堂| 亚洲成人资源在线| 久久久久久天堂| 亚洲精品中文在线影院| 永久免费看片直接| 国产精品久久久久久久久免费相片| 美女久久久久久久久久| 97成人超碰视| 欧美bbbbb性bbbbb视频| 成年人国产精品| 久久久国产精品久久久| 国产一区二区精品在线观看| 一区二区免费av| 美女免费视频一区| 手机看片一级片| 麻豆精品国产传媒mv男同 | 日日av拍夜夜添久久免费| 欧美做受高潮1| 亚洲黄色免费看| 日本一本a高清免费不卡| 亚洲三级欧美| 日本高清不卡的在线| 成人美女黄网站| 国产成人高清激情视频在线观看 | 日韩欧美一区二区三区| 国产一级一级国产| 在线影视一区二区三区| 一区精品在线观看| 欧美精品乱码久久久久久| 国产又黄又爽视频| 欧美一区二区观看视频| 精品久久久免费视频| 欧美tickling网站挠脚心| 天天操天天干天天插| 日韩精品中文字幕在线观看 | 亚洲一区二区三区四区电影| 成人动漫在线视频| 免费看久久久| 日韩精品欧美一区二区三区| 久久看人人摘| 国产日产欧美一区二区| 伊人久久亚洲热| 成人小视频在线看| 九一久久久久久| wwwww在线观看| 91视频国产资源| 日韩影视一区二区三区| 自拍偷拍亚洲欧美日韩| 激情五月婷婷小说| 色婷婷综合久色| 国产精品久久久国产盗摄| 精品国产精品一区二区夜夜嗨| 日韩大胆视频| 日韩中文字幕网址| bbw在线视频| 国产精品美女久久| 视频一区在线| 欧美日韩大片一区二区三区| 99精品在线免费在线观看| 日韩视频在线视频| 奇米一区二区三区| 黄色在线免费播放| 国产欧美一区二区精品性色超碰 | 久久性爱视频网站| 国产精品无人区| 国产性一乱一性一伧一色| 一本大道综合伊人精品热热| 国产情侣激情自拍| 国产一区二区三区在线| 欧美1234区| 国产精品扒开腿做爽爽爽男男| 911亚洲精品| 亚洲高清123| 亚洲伦伦在线| 天天久久综合网| 久久嫩草精品久久久精品| 内射一区二区三区| 在线精品国精品国产尤物884a| 亚洲第一第二区| 日韩小视频在线| 亚洲色图官网| 99视频国产精品免费观看| 欧美理论视频| 毛片在线视频播放| 国产高清精品网站| 在线观看亚洲大片短视频| 天天av天天翘天天综合网色鬼国产| 一级黄色片免费| 亚洲精品一区中文| 波多野结衣在线观看| 成人午夜在线视频一区| blacked蜜桃精品一区| 黄色一级片播放| 成人性生交大片| 一级黄色录像视频| 欧美日韩视频一区二区| 精品久久av| 欧美又大又粗又长| 日韩中出av| www.av中文字幕| 国产69精品久久久久777| 久久久久久视频| 欧美日韩国产大片| www.在线播放| 国产精品久久视频| 国产成人三级| 男人天堂999| 久久综合色鬼综合色| 国产精品自拍99| 日韩激情视频在线| 日韩理论视频| 精品日本一区二区| 国产精品日韩精品欧美精品| 亚洲高清无码久久| 午夜欧美视频在线观看| 欧洲av在线播放| 国外成人在线视频| 精品伊人久久久| 日韩免费一级视频| 99精品国产视频| 影音先锋在线国产| 亚洲人成在线观看网站高清| 粉嫩一区二区三区| 亚洲国产欧洲综合997久久| 全部av―极品视觉盛宴亚洲| 亚洲第一视频区| 欧美日韩国产一二三| 免费在线毛片网站| 91亚洲精品一区| 国产精品国码视频| 免费日本黄色网址| 日韩欧美精品中文字幕| www 日韩| 91青青草免费在线看| 国产精品v欧美精品v日本精品动漫| 无码国产精品久久一区免费| 午夜日韩在线电影| 二区在线视频| 成人信息集中地欧美| 伊人久久综合| 91成年人网站| 91精品国产综合久久精品性色| 伊人春色在线观看| 久久狠狠久久综合桃花| 日韩激情一区二区| 97在线观看视频免费| 精品久久久久久久久久久久久久久久久 | www精品久久| 国产午夜精品一区二区三区嫩草| 在线观看视频二区| 隔壁老王国产在线精品| 深夜福利久久| 丰满少妇一区二区三区专区| 午夜国产精品影院在线观看| 久久经典视频| 91麻豆精品秘密入口| 亚洲综合二区| 亚洲一区电影在线观看| 亚洲成人av在线播放| 欧美日韩女优| 国产69精品久久久久999小说| 国产婷婷一区二区| 精品人妻一区二区三区麻豆91| 26uuu久久噜噜噜噜| 亚洲国产成人精品女人| 538国产视频| 日韩视频一区二区三区 | 91在线视频免费| 亚洲欧美不卡| 久久久久久久久久久久久久久久久 | 欧美制服第一页| 国产精品magnet| 99在线视频免费|