创建必要的目录和文件
比如在 work
目录下创建 food_predict
文件夹,并在该目录下分别创建 module.py
__init__.py
,其中 module.py
作为 Module 的入口,用来实现逻辑预测功能。
tree work/
module.py中的代码如下:
import paddle
import numpy as np
import argparse
import os
from PIL import Image
import base64
from io import BytesIO
import json
import paddle.nn.functional as F
import paddlehub as hub
from paddlehub.module.module import runnable, moduleinfo, serving
# 读取一张本地的样例图片,转变成模型输入的格式
def load_image(img_path):
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img = img.resize((224, 224), Image.ANTIALIAS)
img = np.array(img).astype("float32")
img = img.transpose(2,0,1)
img = np.expand_dims(img, axis=0) # 增加一个维度
img = img / 255.0 # 将图像数据归一化
return img
#定义模型
class My_vgg(paddle.nn.Layer):
def __init__(self, num_classes=11):
super().__init__()
self.num_classes = num_classes
net = []
# block 1
net.append(paddle.nn.Conv2D(3, 64, (3, 3),padding=1, stride=1))
net.append(paddle.nn.ReLU())
# net.append(paddle.nn.Conv2D(64, 64, (3, 3),padding=1, stride=1))
# net.append(paddle.nn.ReLU())
net.append(paddle.nn.MaxPool2D(kernel_size=2, stride=2))
# block 2
net.append(paddle.nn.Conv2D(64, 128, (3, 3), stride=1, padding=1))
net.append(paddle.nn.ReLU())
# net.append(paddle.nn.Conv2D(128, 128, (3, 3), stride=1, padding=1))
# net.append(paddle.nn.ReLU())
net.append(paddle.nn.MaxPool2D(kernel_size=2, stride=2))
# block 3
net.append(paddle.nn.Conv2D(128, 256, (3, 3), stride=1,padding=1))
net.append(paddle.nn.ReLU())
net.append(paddle.nn.Conv2D(256, 256, (3, 3), stride=1,padding=1))
net.append(paddle.nn.ReLU())
# net.append(paddle.nn.Conv2D(256, 256, (3, 3), stride=1,padding=1))
# net.append(paddle.nn.ReLU())
net.append(paddle.nn.MaxPool2D(kernel_size=2, stride=2))
# block 4
net.append(paddle.nn.Conv2D(256, 512, (3, 3), stride=1, padding=1))
net.append(paddle.nn.ReLU())
net.append(paddle.nn.Conv2D(512, 512, (3, 3), stride=1, padding=1))
net.append(paddle.nn.ReLU())
# net.append(paddle.nn.Conv2D( 512, 512, (3, 3), stride=1, padding=1))
# net.append(paddle.nn.ReLU())
net.append(paddle.nn.MaxPool2D(kernel_size=2, stride=2))
# block 5
net.append(paddle.nn.Conv2D( 512, 512, (3, 3), stride=1, padding=1))
net.append(paddle.nn.ReLU())
net.append(paddle.nn.Conv2D( 512, 512, (3, 3), stride=1, padding=1))
net.append(paddle.nn.ReLU())
# net.append(paddle.nn.Conv2D( 512, 512, (3, 3), stride=1, padding=1))
# net.append(paddle.nn.ReLU())
#net.append(paddle.nn.MaxPool2D(kernel_size=2, stride=2))
net.append(paddle.nn.AdaptiveAvgPool2D(output_size=1))
# 组网
#顺序容器。子Layer将按构造函数参数的顺序添加到此容器中。
self.conv = paddle.nn.Sequential(*net)
classifier = []
# classifier.append(paddle.nn.Linear( 512*7*7, 4096))
# classifier.append(paddle.nn.ReLU())
# classifier.append(paddle.nn.Dropout(p=0.5))
# classifier.append(paddle.nn.Linear( 4096, 4096))
# classifier.append(paddle.nn.ReLU())
# classifier.append(paddle.nn.Dropout(p=0.5))
# classifier.append(paddle.nn.Linear( 4096, self.num_classes))
classifier.append(paddle.nn.Dropout(p=0.5))
classifier.append(paddle.nn.Linear( 512, self.num_classes))
# 组网
#顺序容器。子Layer将按构造函数参数的顺序添加到此容器中。
self.classifier = paddle.nn.Sequential(*classifier)
def forward(self, x):
features = self.conv(x)
features = paddle.reshape(features, [features.shape[0], -1])
classify_result = self.classifier(features)
return classify_result
@moduleinfo(
name="food_predict",
version="1.0.0",
summary="This is a PaddleHub Module. Just for test.",
author="hyq",
author_email="",
type="cv/my_food",
)
class FoodPredict:
def __init__(self):
self.parser = argparse.ArgumentParser(
description="Run the food_predict module.",
prog='hub run food_predict',
usage='%(prog)s',
add_help=True)
self.parser.add_argument(
'--input_img', type=str, default=None, help="img to predict")
def food_predict(self, img_path):
print('forward')
model.eval()
tensor_img = load_image(img_path)
paddle.disable_static()
result = model(paddle.to_tensor(tensor_img))
return result.numpy().astype('int32')
@runnable
def runnable(self, argvs):
print('runnable')
args = self.parser.parse_args(argvs)
return self.mnist_predict(args.input_img)
@serving
def serving(self, img_b64):
print('serving')
model.eval()
img_b = base64.b64decode(img_b64)
tensor_img = load_image(BytesIO(img_b))
paddle.disable_static()
result = model(paddle.to_tensor(tensor_img))
# 应该返回JSON格式数据
# 从numpy读出的数据格式是 numpy.int32
# res = { 'res': int(np.argmax(result.numpy()[0])) }
res = int(np.argmax(result.numpy()[0]))
return json.dumps(res)
# 代码应该放到全局
model = My_vgg()
params_file_path = 'work/My_vgg.pdparams'
param_dict = paddle.load(params_file_path)
model.load_dict(param_dict)
my_mnist = FoodPredict()
编写 module.py
的第一步就是导入依赖库。这里有一些库不太常见,比如 BytesIO、argparse 和 paddlehub.module.module 里的内容。
- BytesIO 用于把二进制图片转换成
Image.open
可以读取的格式 - argparse 用来解析命令行参数
- paddlehub 中的库用于装饰器,可以轻松添加命令行预测、服务部署功能
训练好的模型保存在 work/My_vgg.pdparams
当中。接下来我们编写一个 PaddleHub Module,并定义一个 新的类 。这个类用来实现Hub的预测功能,包括Python调用、命令行调用和服务部署。
如果希望Module可以支持命令行调用,则需要提供一个经过runnable修饰的接口,接口负责解析传入数据并进行预测,将结果返回。如果不需要提供命令行预测功能,则可以不实现该接口,PaddleHub在用命令行执行时,会自动发现该Module不支持命令行方式,并给出提示。
如果希望Module可以支持PaddleHub Serving部署预测服务,则需要提供一个经过serving修饰的接口,接口负责解析传入数据并进行预测,将结果返回。如果不需要提供PaddleHub Serving部署预测服务,则可以不需要加上serving修饰。
安装模型
在 module.py
中编写好代码后,就可以通过 hub install xxx
的方式来安装模型了。(终端运行)
hub install work/food_predict
安装成功:
预测测试:
import paddlehub as hub
my_food_predector = hub.Module(name="food_predict")
my_food_predector.food_predict("work/0.jpg")
在终端通过服务部署的话,部署和推理都需要在终端实现,因为外网没法访问到notebook。
部署方法
- 在
终端-1
运行命令hub serving start -m mnist_predict
。如果它出现下面的提示说明部署成功
W0723 21:44:42.436631 3845 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W0723 21:44:42.441895 3845 device_context.cc:422] device: 0, cuDNN Version: 7.6.
* Serving Flask app "paddlehub.serving.app_compat" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:8866/ (Press CTRL+C to quit)
- 通过POST请求实现预测
import PIL.Image as Image
import matplotlib.pyplot as plt
img = Image.open("work/0.jpg")
plt.imshow(img)
import requests
import json
import cv2
import base64
def cv2_to_base64(image):
data = cv2.imencode('.jpg', image)[1]
return base64.b64encode(data.tobytes()).decode('utf-8')
# 发送HTTP请求
data = {'img_b64': cv2_to_base64(cv2.imread("work/0.jpg"))}
headers = {"Content-type": "application/json"}
url = "http://0.0.0.0:8866/predict/food_predict"
r = requests.post(url=url, headers=headers, data=json.dumps(data))
# 打印预测结果
print(r)
res = r.json()
print(res)
pre_index = res['results']
# 打印预测结果
print('预测结果:'+pre_index+" "+ food_class[int(pre_index)])