精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

日韩电影免费看| 天天爽夜夜爽夜夜爽精品| 最新日韩一区| 国产日韩欧美精品电影三级在线| 国产精品 欧美在线| 无码人妻精品一区二区中文| 国模视频一区| 亚洲视频免费看| 国产精品久久精品国产| 欧美日韩乱国产| 欧美a级片视频| 精品国产免费一区二区三区香蕉| 亚洲自偷自拍熟女另类| www 日韩| 国产成人在线视频网站| 97在线免费视频| 欧美色图12p| 99视频网站| 亚洲av中文无码乱人伦在线视色| 日韩欧美字幕| 欧美一区二区三区免费在线看| aaa免费在线观看| 亚洲成人中文字幕在线| 久久婷婷丁香| 欧美大片欧美激情性色a∨久久| 亚洲の无码国产の无码步美| 亚洲青青久久| 色网综合在线观看| 免费视频爱爱太爽了| 91在线观看| 91网站视频在线观看| 91深夜福利视频| 久草视频一区二区| 亚洲免费黄色| 欧美精品亚州精品| 日韩精品久久久久久久的张开腿让 | 一区二区不卡| 亚洲人成免费电影| 亚洲麻豆一区二区三区| 国产成人免费av一区二区午夜| 欧美性猛交xxxx免费看漫画| 国产盗摄视频在线观看| 成人免费在线电影| 26uuu亚洲| 国产一区二区免费电影| 伊人网中文字幕| 亚洲欧美日韩专区| 国模精品系列视频| 精品99久久久久成人网站免费| 欧美日韩精品一区二区视频| 亚洲精品一区在线观看香蕉| av在线播放网址| 91成人午夜| 日韩午夜电影av| 中文字幕 欧美日韩| 黄页免费欧美| 欧美怡红院视频| 欧美极品欧美精品欧美图片| 绿色成人影院| 五月天中文字幕一区二区| 免费网站在线观看视频| 三级福利片在线观看| 亚洲人亚洲人成电影网站色| 一本久久a久久精品vr综合| 阿v免费在线观看| 国产午夜一区二区三区| 日韩精品一线二线三线| 风间由美一区| 日本一区二区久久| 中文字幕乱码一区二区三区| 午夜伦全在线观看| 日韩一区在线免费观看| 视频一区二区视频| 中中文字幕av在线| 亚洲一二三专区| 草b视频在线观看| 麻豆视频在线看| 一本到不卡精品视频在线观看| 波多野结衣作品集| 国外成人福利视频| 日韩三级在线观看| 国产精品麻豆入口| 国内精品久久久久久久久电影网| 一区三区二区视频| 国精品人伦一区二区三区蜜桃| 91综合久久| 欧美激情乱人伦一区| 亚洲国产成人精品激情在线| 蜜桃视频一区| 91精品久久久久久久久久| 99免费在线视频| aaa欧美日韩| 日本欧洲国产一区二区| 欧美一级二级三级区| 亚洲激情第一区| 黄色网页免费在线观看| 国产亚洲精彩久久| 精品人在线二区三区| 中文人妻一区二区三区| 日韩在线观看一区| 欧美激情一区二区三区久久久| 好吊操这里只有精品| 日韩专区欧美专区| 亚洲最大av网| 精品无人乱码| 一区二区免费在线播放| 国产熟女高潮视频| 国产亚洲观看| 亚洲美女中文字幕| 九九热最新地址| 国产色综合网| 91最新在线免费观看| 五月激情婷婷网| 综合久久久久久久| 久久国产成人精品国产成人亚洲| 亚洲精品tv| 亚洲精品视频免费在线观看| caoporn91| 三级亚洲高清视频| 国产精品一区二区三区在线| 日本电影在线观看网站| 欧美日韩亚洲系列| 国产xxx在线观看| 日韩精品欧美| 欧美综合激情网| 亚洲精品无amm毛片| 国产女人18毛片水真多成人如厕| 精品丰满人妻无套内射| 天天综合91| 亚洲性生活视频在线观看| 久久精品这里有| 国产一区二区三区免费观看| 日韩欧美一区二区在线观看| 精精国产xxxx视频在线野外| 欧美一二三在线| 日韩一卡二卡在线观看| 日韩不卡在线观看日韩不卡视频| 国模精品一区二区三区| 性网站在线观看| 欧美福利一区二区| 成人性生交大片免费看无遮挡aⅴ| 99视频一区| www.成人av| 成人直播在线| 欧美日本免费一区二区三区| 成人精品999| 国产一区二区三区的电影| 大波视频国产精品久久| а√天堂官网中文在线| 在线不卡中文字幕播放| 亚洲欧美另类日本| 久久精品免费观看| 亚洲人成网站在线观看播放| 78精品国产综合久久香蕉| 国产亚洲欧美日韩精品| 激情网站在线观看| 国产喷白浆一区二区三区| 国产无套粉嫩白浆内谢的出处| 亚洲自拍都市欧美小说| 青青草精品毛片| 精品欧美不卡一区二区在线观看| 欧美日韩在线视频一区| 日本黄色特级片| 久久这里只有| 天堂社区 天堂综合网 天堂资源最新版 | 国产理论电影在线 | 黄片毛片在线看| 亚洲国产精品影院| yy1111111| 久久亚洲欧美| 欧美一区免费视频| 狠狠久久伊人中文字幕| 久久综合伊人77777蜜臀| 国产黄a三级三级三级| 亚洲资源中文字幕| 国产网站无遮挡| 日韩影院精彩在线| 国产精品久久成人免费观看| 日韩区一区二| 久久男人的天堂| 深夜福利视频一区| 欧美性欧美巨大黑白大战| 肉色超薄丝袜脚交69xx图片| 国产精品77777竹菊影视小说| 免费看欧美黑人毛片| 视频福利一区| 国产日韩在线视频| 美女网站视频在线| 亚洲欧美激情另类校园| 一级片一区二区三区| 亚洲一区二区三区国产| 亚洲精品乱码久久久久久久久久久久| 久久丁香综合五月国产三级网站| www.激情网| 国产不卡av一区二区| 91久久久久久久久| 免费成人在线电影| 久久精品电影一区二区| 黄色一级a毛片| 欧洲人成人精品| 久草免费在线观看视频| 久久午夜色播影院免费高清| 天天影视色综合| 999亚洲国产精| 亚洲日本理论电影| 理论片一区二区在线| 国产精品男女猛烈高潮激情| 欧美xxxx少妇| 中文字幕亚洲无线码在线一区| www五月婷婷| 欧美午夜精品电影| 国产福利拍拍拍| 18涩涩午夜精品.www| 国产中文字幕一区二区| 国产中文字幕一区| 国产精品免费观看久久| 国精品一区二区| 亚洲在线播放电影| 亚洲欧洲美洲国产香蕉| 999视频在线观看| 午夜av成人| 国产91精品久久久久久久| 91一区二区三区在线| 中日韩午夜理伦电影免费 | 日本一区二区高清视频| 超碰地址久久| 91热精品视频| 久久免费影院| 国产福利精品视频| 密臀av在线播放| 久久久久久国产精品久久| 国产在线观看a| 最近更新的2019中文字幕| 日本v片在线免费观看| 亚洲爱爱爱爱爱| av手机免费看| 6080日韩午夜伦伦午夜伦| 超碰在线97观看| 色综合久久综合中文综合网| 一区二区三区视频免费看| 亚洲午夜精品久久久久久久久| 成人高潮免费视频| 国产精品福利影院| 国产精品理论在线| 国产亚洲一区二区在线观看| 9.1成人看片| 99久久精品国产毛片| 亚洲精品国产成人av在线| 国产成人久久精品77777最新版本| 久国产精品视频| 美女精品自拍一二三四| 在线视频日韩一区| 免费的国产精品| 污污网站免费观看| 美日韩一区二区三区| 鲁一鲁一鲁一鲁一av| 免费在线一区观看| 中文字幕中文在线| 国产在线精品一区二区三区不卡 | 久久精品欧美一区二区| 亚洲一区自拍偷拍| 国产午夜福利一区二区| 午夜精品福利在线| 免费av网站在线| 日本高清视频一区二区| 欧美一级黄视频| 欧美人与禽zozo性伦| 国产农村妇女毛片精品| 欧美tickling网站挠脚心| 欧美一区二区三区黄片| 精品一区二区三区四区在线| 国产精品二线| 日韩视频免费在线观看| 亚洲精品白浆| 91av视频导航| 日韩制服诱惑| 96sao精品视频在线观看| 大奶在线精品| 欧美xxxx黑人又粗又长精品| 欧美少妇xxxx| 大桥未久一区二区| 亚洲一区二区三区免费在线观看 | 国产福利在线播放麻豆| 久久久久久久久91| 欧美大胆成人| 成人精品久久av网站| xvideos.蜜桃一区二区| 日本在线观看一区| 在线看片不卡| 日韩人妻精品无码一区二区三区| 蜜臀av在线播放一区二区三区| 精产国品一二三区| 久久久国产精品午夜一区ai换脸| 永久av免费网站| 亚洲不卡一区二区三区| 中文字幕一区二区久久人妻| 欧美tickling挠脚心丨vk| se在线电影| 91精品国产乱码久久久久久蜜臀| 欧美日韩在线精品一区二区三区激情综合| 91亚洲国产成人精品性色| 任你躁在线精品免费| 日韩第一页在线观看| 国产一区成人| 天天操精品视频| 久久综合99re88久久爱| 外国一级黄色片| 色婷婷综合久色| 亚洲成人黄色片| 伊人一区二区三区久久精品| 久久香蕉av| 国产欧美一区二区三区四区| 丝袜美腿综合| 国产女主播av| 久久精品久久99精品久久| 亚洲色图14p| 亚洲黄色小视频| 中文字幕在线网址| 亚洲欧美视频在线| 97蜜桃久久| 91久久国产自产拍夜夜嗨| 欧洲三级视频| 欧美a在线视频| 成人久久久精品乱码一区二区三区| 69视频在线观看免费| 五月激情综合网| 亚洲精品视频专区| 精品中文字幕视频| 欧美成人高清视频在线观看| 欧美主播一区二区三区美女 久久精品人 | 黄色av网站在线| 久久久久一本一区二区青青蜜月| 香蕉久久一区| 亚洲春色在线视频| 老**午夜毛片一区二区三区| 欧美成人三级伦在线观看| 亚洲图片自拍偷拍| 午夜精品久久久久久久91蜜桃| 日韩视频中文字幕| 成人黄色免费网站| 五码日韩精品一区二区三区视频| 亚洲综合国产| 日本免费福利视频| 精品人伦一区二区三区蜜桃免费| 黄片毛片在线看| 91精品国产乱码久久久久久蜜臀| 国产精品久久久久久久久久白浆| 97超碰国产精品| 国产精品12区| 免费人成视频在线| 精品国产sm最大网站| 男女在线观看视频| 国产日韩精品推荐| 日韩亚洲精品在线| 少妇按摩一区二区三区| 色综合久久综合网97色综合| 久色视频在线| 国产精品电影网站| 日韩精品91| 午夜免费福利网站| 亚洲激情自拍视频| 欧美一级一区二区三区| 97国产精品视频人人做人人爱| 美日韩黄色大片| 日本一本二本在线观看| 亚洲国产高清不卡| 国产精品欧美久久久久天天影视| 久久亚洲成人精品| 999久久精品| 国产美女无遮挡网站| 国产欧美1区2区3区| 国产精品久久久久精| 久久成人人人人精品欧| 风间由美性色一区二区三区四区 | 日韩精品每日更新| 免费看黄色三级| 制服丝袜中文字幕亚洲| 国内在线视频| 欧美日韩在线精品| 精品影院一区二区久久久| 久久久久无码精品国产| 亚洲精品视频在线播放 | 国产精品xxxx| 亚洲欧美日韩在线观看a三区| 91香蕉国产视频| 精品少妇一区二区三区| av综合电影网站| 综合国产精品久久久| 成人黄色av网站在线| 波多野结衣理论片| 免费av一区二区| 亚洲欧洲av| 无套内谢丰满少妇中文字幕 | 欧美激情一区二区三区免费观看| 蜜月aⅴ免费一区二区三区| 欧美变态网站| 亚洲18在线看污www麻豆| 精品久久在线播放| 黄色在线观看网站| 久久久一本精品99久久精品66|