日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

gRPC python封装深度学习算法教程

發(fā)布時間:2023/12/10 python 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 gRPC python封装深度学习算法教程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

最近需要提供一個包含多個神經(jīng)網(wǎng)絡(luò)推理的python代碼供gRPC調(diào)用,即我需要在這個主程序的基礎(chǔ)上封裝一個支持gRPC的服務(wù)端(server)。本教程的目的在于通過簡單的代碼,來幫助有需求的朋友使用python來構(gòu)建屬于自己的gRPC服務(wù)端/客戶端。

0. 前言

最近需要用grpc調(diào)用我們的算法模塊, 對于我來講,就是需要提供一個grpc的server,供它們的go或者c++的client進(jìn)行消費(fèi)。那么, 在python里面如何定義一個完整的server–client,并且使其跑的非常好是個很重要的任務(wù)。

1. gRPC的官方介紹

中文官網(wǎng)的python接口例子直接放在grpc的github中,可能需要我們進(jìn)一步的挖掘,這里,為了避免繁瑣,我將通過一個簡單的例子來說明如何將我們的任務(wù)封裝為gRPC的服務(wù)端(server),并開啟客戶端(client)對其進(jìn)行調(diào)用。

在此之前,先簡單介紹一下什么是gRPC:

1.1 什么是gRPC
  • gRPC 是一個高性能、開源和通用的 RPC(遠(yuǎn)程過程調(diào)用) 框架,面向移動和 HTTP/2 設(shè)計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
    gRPC 基于 HTTP/2 標(biāo)準(zhǔn)設(shè)計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多復(fù)用請求等特。這些特性使得其在移動設(shè)備上表現(xiàn)更好,更省電和節(jié)省空間占用。

  • 在 gRPC 里客戶端(client)應(yīng)用可以像調(diào)用本地對象一樣直接調(diào)用另一臺不同的機(jī)器上服務(wù)端(server)應(yīng)用的方法,使得您能夠更容易地創(chuàng)建分布式應(yīng)用和服務(wù)。與許多 RPC 系統(tǒng)類似,gRPC 也是基于以下理念:① 定義一個服務(wù)② 指定其能夠被遠(yuǎn)程調(diào)用的方法(包含參數(shù)和返回類型)③ 在服務(wù)端實現(xiàn)這個接口,并運(yùn)行一個 gRPC 服務(wù)器來處理客戶端調(diào)用。 在客戶端擁有一個存根(stub)能夠像服務(wù)端一樣的方法。

  • gRPC 客戶端和服務(wù)端可以在多種環(huán)境中運(yùn)行和交互 - 從 google 內(nèi)部的服務(wù)器到你自己的筆記本,并且可以用任何 gRPC 支持的語言來編寫。所以,你可以很容易地用 Java 創(chuàng)建一個 gRPC 服務(wù)端,用 Go、Python、Ruby 來創(chuàng)建客戶端。此外,Google 最新 API 將有 gRPC 版本的接口,使你很容易地將 Google 的功能集成到你的應(yīng)用里。
1.2 使用 protocol buffers

gRPC 默認(rèn)使用 protocol buffers,這是 Google 開源的一套成熟的結(jié)構(gòu)數(shù)據(jù)序列化機(jī)制(當(dāng)然也可以使用其他數(shù)據(jù)格式如 JSON)。正如你將在下方例子里所看到的,你用 proto files 創(chuàng)建 gRPC 服務(wù),用 protocol buffers 消息類型來定義方法參數(shù)和返回類型。你可以在 Protocol Buffers 文檔找到更多關(guān)于 Protocol Buffers 的資料。
Protocol buffers 版本
盡管 protocol buffers 對于開源用戶來說已經(jīng)存在了一段時間,例子內(nèi)使用的卻一種名叫 proto3 的新風(fēng)格的 protocol buffers,它擁有輕量簡化的語法、一些有用的新功能,并且支持更多新語言。當(dāng)前針對 Java 和 C++ 發(fā)布了 beta 版本,針對 JavaNano(即 Android Java)發(fā)布 alpha 版本,在protocol buffers Github 源碼庫里有 Ruby 支持, 在golang/protobuf Github 源碼庫里還有針對 Go 語言的生成器, 對更多語言的支持正在開發(fā)中。 你可以在 proto3 語言指南里找到更多內(nèi)容, 在與當(dāng)前默認(rèn)版本的發(fā)布說明比較,看到兩者的主要不同點(diǎn)。更多關(guān)于 proto3 的文檔很快就會出現(xiàn)。雖然你可以使用 proto2 (當(dāng)前默認(rèn)的 protocol buffers 版本), 我們通常建議你在 gRPC 里使用 proto3,因為這樣你可以使用 gRPC 支持全部范圍的的語言,并且能避免 proto2 客戶端與 proto3 服務(wù)端交互時出現(xiàn)的兼容性問題,反之亦然。

ps: 我這里使用的都是protobuf作為gRPC約定的中間數(shù)據(jù)傳輸格式定義。雖然可以用json,但是我沒看到這方面的教程。

2. 基本步驟

因為官方教程有比較全面的grpc的各語言接口的安裝教程,我這里以python為例,來說明對深度學(xué)習(xí)應(yīng)用,我們應(yīng)該如何搭建一個基于grpc的server–client。

第1步:定義服務(wù)(實現(xiàn)自己的hellogdh.proto)

一個 RPC 服務(wù)通過參數(shù)和返回類型來指定可以遠(yuǎn)程調(diào)用的方法,gRPC 通過 protocol buffers 來實現(xiàn)。使用 protocol buffers 接口定義語言來定義服務(wù)方法,用 protocol buffer 來定義參數(shù)和返回類型。客戶端和服務(wù)端均使用服務(wù)定義生成的接口代碼。

本文的hellogdh.proto定義如下[2]:

// Copyright 2015 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License.// 參考資料:python版gRPC快速入門一 // https://blog.csdn.net/Eric_lmy/article/details/81355322 syntax = "proto3";option java_multiple_files = true; option java_package = "io.grpc.gdh.proto"; option java_outer_classname = "GdhProto"; option objc_class_prefix = "HLW";package hellogdh;// 定義服務(wù). service Greeter {// ① 簡單rpc.rpc SayHello (HelloRequest) returns (HelloReply) {}// ② 應(yīng)答式流rpc.rpc LstSayHello (HelloRequest) returns (stream HelloReply) {} }// 客戶端傳的消息: HelloRequest. message HelloRequest {string name = 1; } // 服務(wù)端發(fā)送的消息: HelloReply. // 不能使用int, 只能使用int32這種. // 1, 2, 3表示順序. message HelloReply {int32 num_people = 1; // repeated 定義列表對應(yīng)的結(jié)構(gòu).repeated int32 point = 2; }

其中,syntax = "proto3"表示使用proto3版本。option相關(guān)的東西我都沒咋動;service Greeter的意思是定義了一個叫做Greeter的服務(wù)。這個服務(wù)下面有兩種,關(guān)于gRPC可以定義的服務(wù)有4種,下面會詳細(xì)說明。

定義完畢之后,生成client和server的代碼(Note:我之前以為client和server的代碼是自己寫的,實際實踐后才知道,是根據(jù)xxx.proto生成的!!根本不需要我們自己寫!

執(zhí)行這一步需要安裝好一些工具如下:

sudo apt-get install protobuf-compiler-grpc sudo apt-get install protobuf-compiler

對我的環(huán)境(ubuntu18.04 python3.6) 執(zhí)行:

protoc -I ./grpc --python_out=./grpc --grpc_out=./grpc --plugin=protoc-gen-grpc=`which grpc_python_plugin` hellogdh.proto

在對應(yīng)的目錄下回生成兩個文件hellogdh_pb2_grpc.pyhellogdh_pb2.py

其中, hellogdh_pb2.py包括:

  • 定義在hellogdh.proto中的消息類(Message)
  • 定義在hellogdh.proto中的服務(wù)的抽象類:
    BetaHellogdhServicer, 定義了Hellogdh 服務(wù)實現(xiàn)的接口
    BetaHellogdhStub, 定義了可以被客戶端用來激活的Hellogdh RPC的存根
  • 應(yīng)用到的函數(shù):
    beta_create_Hellogdh_server: 根據(jù)BetaHellogdhServicer對象創(chuàng)建一個gRPC服務(wù)器(server專用)。
    beta_create_Hellogdh_stub: 客戶端用于創(chuàng)建存根stub(client專用)。
第2步:實現(xiàn)server部分代碼.

本部分分別以簡單調(diào)用(單項RPC)服務(wù)端流RPC為例進(jìn)行說明,實際上,gRPC允許4種類型服務(wù)方法(如果想完整的學(xué)習(xí),還是建議看官方文檔的例子[1]):



對我而言,因為我需要把多進(jìn)程的python程序的最后輸出隊列封裝給gRPC的server進(jìn)程,所以我首先需要把待處理的隊列(Queue)傳入gRPC server的進(jìn)程,再在這個進(jìn)程中定義好overwrite一些helloworld_pb2.py的方法。

最后,在主進(jìn)程中啟動所有的神經(jīng)網(wǎng)絡(luò)任務(wù)進(jìn)程和gRPC進(jìn)程,并阻塞(join(),join的作用是保證當(dāng)前進(jìn)程正常結(jié)束, 即不會因為主進(jìn)程先退出而把未執(zhí)行完的子進(jìn)程kill掉。)。

代碼如下,參考自github grpc/examples/python下的route_guide

import sys sys.path.append("..")import grpc import hellogdh_pb2 import hellogdh_pb2_grpc from concurrent import futuresimport cv2 import time import numpy as np from utils.config import * import logging from capture import queue_put, queue_get, queue_img_put from module.Worker import PoseWorker import multiprocessing as mp from multiprocessing import Pool, Queue, Lock# 0.0 grpc. def grpc_server(queue):class gdhServicer(hellogdh_pb2.BetaGreeterServicer):def SayHello(self, request, context):# Note: 傳參的時候必須要顯式指定參數(shù)名稱, 而不能lynxi_pb2.HelloReply(1, [5, 10])if request.name == 'gdh':return hellogdh_pb2.HelloReply(num_people=1, point=[1, 1])else:return hellogdh_pb2.HelloReply(num_people=55, point=[1, 1])def LstSayHello(self, request, context):while request.name == 'gdh':data = queue.get()# 因為是服務(wù)端流式模式,所以用yield。yield hellogdh_pb2.HelloReply(num_people=data.num, point=[data.point[0], data.point[1]])# 1. 之前啟動server的方式.# server = helloworld_gdh_pb2.beta_create_Greeter_server(gdhServicer())# 2. 在route_guide里面學(xué)到的啟動server的方式.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))lynxi_pb2_grpc.add_GreeterServicer_to_server(gdhServicer(), server)server.add_insecure_port('[::]:50051')# 因為 start() 不會阻塞,如果運(yùn)行時你的代碼沒有其它的事情可做,你可能需要循環(huán)等待。server.start()try:while True:# time.sleep(_ONE_DAY_IN_SECONDS)time.sleep(5)except KeyboardInterrupt:server.stop()# 2.1 對每個任務(wù)建立一個隊列. pose_queue_raw = Queue() monitor_queue_raw = Queue() pose_out_queue = Queue()# key: 名稱, val[0]: 隊列, val[1]: 加載好的模型. queues = {'pose': pose_queue_raw, 'monitor': monitor_queue_raw}# pose # / # / # 3. 生產(chǎn)者-消費(fèi)者 --- detect # \ # \ # face_detectprocesses = [] for key, val in queues.items():processes.append(mp.Process(target=queue_put, args=(val,)))if key == 'pose':processes.append(PoseWorker(val, pose_out_queue))else:processes.append(mp.Process(target=queue_get, args=(val, )))processes.append(mp.Process(target=grpc_server, args=(pose_out_queue, ))) [process.start() for process in processes] [process.join() for process in processes]

這段代碼的意思是將PoseWorker處理得到的隊列pose_out_queue喂給gRPC server進(jìn)程,并設(shè)置好根據(jù)client發(fā)來的請求來發(fā)送處理好的數(shù)據(jù)。queue_put和queue_get是將視頻的幀封裝后放入隊列A和從隊列A中讀取并顯示的函數(shù)。

import cv2 from multiprocessing import Queue, Process from PIL import Image, ImageFont, ImageDraw import cv2def queue_put(q, video_name="/home/samuel/gaodaiheng/handup.mp4"):cap = cv2.VideoCapture(video_name)while True:is_opened, frame = cap.read()q.put(frame) if is_opened else Nonedef queue_get(q, window_name='image'):cv2.namedWindow(window_name, flags=cv2.WINDOW_NORMAL)while True:frame = q.get()cv2.imshow(window_name, frame)cv2.waitKey(1)

需要額外注意的是,PoseWorker是繼承multiprocessing.Process類的進(jìn)程,其大體定義如下:

from multiprocessing import Queue, Processclass PoseWorker(Process):"""Pose estimation姿態(tài)估計."""def __init__(self, queue, out_queue):Process.__init__(self, name='PoseProcessor')# 輸入隊列和輸出隊列.self.in_queue = queueself.out_queue = out_queuedef run(self):#set enviornmentos.environ["CUDA_VISIBLE_DEVICES"] = "0"#load modelsimport tensorflow as tf...model = load_model(xxx)...while True:# 從入的隊列中消費(fèi)數(shù)據(jù).frame = self.in_queue.get()# 喂入模型推理得到結(jié)果.result = model.inference(frame)# 將結(jié)果放回到生產(chǎn)者中.self.out_queue.put(result)
第3步:實現(xiàn)server部分代碼.

和第2步類似,代碼如下,參考自github grpc/examples/python[3]下的route_guide

# coding: UTF-8 """@author: samuel ko """ import os import grpc import hellogdh_pb2 as helloworld_gdh_pb2 import hellogdh_pb2_grpc as helloworld_gdh_pb2_grpc import time_ONE_DAY_IN_SECONDS = 60*60*24# 1. 為了能調(diào)用服務(wù)的方法,我們得先創(chuàng)建一個 存根。 # 我們使用 .proto 中生成的 route_guide_pb2 模塊的函數(shù)beta_create_RouteGuide_stub。def run():with grpc.insecure_channel('localhost:50051') as channel:# 1) 存根方式1.stub = helloworld_gdh_pb2.GreeterStub(channel)# 2) 存根方式2.# stub = helloworld_gdh_pb2_grpc.GreeterStub(channel)print("-------------- ① 簡單RPC --------------")# response = stub.SayHello(helloworld_gdh_pb2.HelloRequest(name='gdh'))features = stub.SayHello(helloworld_gdh_pb2.HelloRequest(name='gdh'))print(features)print("-------------- ② 服務(wù)端流式RPC --------------")features = stub.LstSayHello(helloworld_gdh_pb2.HelloRequest(name='gdh'))for feature in features:print("哈哈哈 %s at %s, %s" % (feature.num_people, feature.point[0], feature.point[1]))if __name__ == "__main__":run()

最后,就會打印出符合我們服務(wù)端設(shè)定的數(shù)據(jù)結(jié)構(gòu)…

補(bǔ)充知識:protobuf 支持的python數(shù)據(jù)結(jié)構(gòu).

在proto的Message定義中, 我們支持python的string等類型, 在proto中需要顯式標(biāo)明, 我推測是由于gRPC是支持多種語言接口的,有些語言是強(qiáng)類型的(C/C++, Go),所以務(wù)必需要顯式標(biāo)明數(shù)據(jù)類型, 避免帶來不必要的麻煩:

message HelloRequest {string name = 1; }

其中, 1, 2, 3表示的是參數(shù)的順序. 我們支持的數(shù)據(jù)類型如下:

  • string
  • float
  • int32/uint32 (不支持int16和int8)
  • bool
  • repeated int以及我們自定義的Message.
    這里需要特別強(qiáng)調(diào), repeated 表示不定長的數(shù)組, 里面可以放built-in的類型,或者自己額外封裝的message. 很靈活. 對應(yīng)python的list.
message BoxInfos {message BoxInfo {uint32 x0 = 1;uint32 y0 = 2;uint32 x1 = 3;uint32 y1 = 4;}repeated BoxInfo boxInfos = 1; }
  • bytes 字節(jié)流, 可以用于傳遞圖片. 不過一般性在gRPC中, 每條消息的大小都不大(1MB左右?) 所以一般性都是傳圖片的絕對路徑?
  • map<string, int> 字典,對應(yīng)python的dict, 不過需要顯式指定key和value的類型.

總結(jié)

截至目前,一個封裝多進(jìn)程神經(jīng)網(wǎng)絡(luò)算法的python版 gRPC server-client就已經(jīng)圓滿完成, 因為我也是剛接觸,可能有理解上的偏差,懇請各位指正, 非常感謝~

參考資料

[1] gRPC–python中文官網(wǎng)
[2] python版gRPC快速入門一
[3] grpc/examples/python

總結(jié)

以上是生活随笔為你收集整理的gRPC python封装深度学习算法教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。