提交 13fc90ba 编写于 作者: W wuzewu

Merge branch 'release/v1.6' into develop

......@@ -2,26 +2,24 @@
<img src="./docs/imgs/paddlehub_logo.jpg" align="middle"
</p>
[![Build Status](https://travis-ci.org/PaddlePaddle/PaddleHub.svg?branch=release/v1.3)](https://travis-ci.org/PaddlePaddle/PaddleHub)
[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE)
[![Build Status](https://travis-ci.org/PaddlePaddle/PaddleHub.svg?branch=release/v1.6)](https://travis-ci.org/PaddlePaddle/PaddleHub)
[![License](https://img.shields.io/badge/license-Apache%202-red.svg)](LICENSE)
[![Version](https://img.shields.io/github/release/PaddlePaddle/PaddleHub.svg)](https://github.com/PaddlePaddle/PaddleHub/releases)
![python version](https://img.shields.io/badge/python-3.6+-orange.svg)
![support os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-yellow.svg)
PaddleHub是飞桨预训练模型管理和迁移学习工具,通过PaddleHub开发者可以使用高质量的预训练模型结合Fine-tune API快速完成迁移学习到应用部署的全流程工作。其提供了飞桨生态下的高质量预训练模型,涵盖了图像分类、目标检测、词法分析、语义模型、情感分析、视频分类、图像生成、图像分割、文本审核、关键点检测等主流模型。更多模型详情请查看官网:https://www.paddlepaddle.org.cn/hub
PaddleHub是飞桨生态的预训练模型应用工具,开发者可以便捷地使用高质量的预训练模型结合Fine-tune API快速完成模型迁移到部署的全流程工作。PaddleHub提供的预训练模型涵盖了图像分类、目标检测、词法分析、语义模型、情感分析、视频分类、图像生成、图像分割、文本审核、关键点检测等主流模型。更多详情可查看官网:https://www.paddlepaddle.org.cn/hu
基于预训练模型,PaddleHub支持以下功能
PaddleHub以预训练模型应用为核心具备以下特点
* **[模型即软件](#模型即软件)**,通过Python API或命令行实现快速预测,更方便地使用PaddlePaddle模型库
* **[模型即软件](#模型即软件)**,通过Python API或命令行实现模型调用,可快速体验或集成飞桨特色预训练模型
* **[迁移学习](#迁移学习)**,用户通过Fine-tune API,只需要少量代码即可完成自然语言处理和计算机视觉场景的深度迁移学习
* **[易用的迁移学习](#迁移学习)**,通过Fine-tune API,内置多种优化策略,只需少量代码即可完成预训练模型的Fine-tuning
* **[服务化部署](#服务化部署paddlehub-serving)**,简单一行命令即可搭建属于自己的模型的API服务。
* **[超参优化](#超参优化autodl-finetuner)**,自动搜索最优超参,得到更好的模型效果。
**PaddleHub发布最新版本1.5.0**
* **[一键模型转服务](#服务化部署paddlehub-serving)**,简单一行命令即可搭建属于自己的深度学习模型API服务完成部署。
* **[自动超参优化](#超参优化autodl-finetuner)**,内置AutoDL Finetuner能力,一键启动自动化超参搜索。
<p align="center">
......@@ -45,43 +43,37 @@ PaddleHub是飞桨预训练模型管理和迁移学习工具,通过PaddleHub
## 安装
### 环境依赖
* Python==2.7 or Python>=3.5 for Linux or Mac
**Python>=3.6 for Windows**
* Python >= 3.6
* PaddlePaddle >= 1.7.0
* 操作系统: Windows/Mac/Linux
* PaddlePaddle>=1.5
### 安装命令
除上述依赖外,PaddleHub的预训练模型和预置数据集需要连接服务端进行下载,请确保机器可以正常访问网络。若本地已存在相关的数据集和预训练模型,则可以离线运行PaddleHub。
**NOTE:**
1. 若是出现离线运行PaddleHub错误,请更新PaddleHub 1.1.1版本之上。
pip安装方式如下:
PaddlePaddle框架的安装请查阅[飞桨快速安装](https://www.paddlepaddle.org.cn/install/quick)
```shell
$ pip install paddlehub
pip install paddlehub
```
2. 使用PaddleHub下载数据集、预训练模型等,要求机器可以访问外网。可以使用`server_check()`可以检查本地与远端PaddleHub-Server的连接状态,使用方法如下:
```python
import paddlehub
paddlehub.server_check()
# 如果可以连接远端PaddleHub-Server,则显示Request Hub-Server successfully。
# 如果无法连接远端PaddleHub-Server,则显示Request Hub-Server unsuccessfully。
```
除上述依赖外,预训练模型和数据集的下载需要网络连接,请确保机器可以**正常访问网络**。若本地已存在相关预训练模型目录,则可以离线使用PaddleHub。
## 特性
### 模型即软件
PaddleHub提出 **模型即软件** 的理念,通过Python API或命令行实现快速预测,更方便地使用PaddlePaddle模型库。
安装PaddleHub成功后,执行命令[hub run](./docs/tutorial/cmdintro.md),可以快速体验PaddleHub无需代码、一键预测的命令行功能,如下三个示例:
PaddleHub采用模型即软件的设计理念,所有的预训练模型与Python软件包类似,具备版本的概念,通过`hub install/uninstall` 可以便捷完成模型的升级和卸载。还可以通过Python的API或命令行实现快速预测的软件集成,更方便地应用和集成深度学习模型。
安装PaddleHub后,执行命令[hub run](./docs/tutorial/cmdintro.md),即可快速体验无需代码、一键预测的功能:
* 使用[目标检测](http://www.paddlepaddle.org.cn/hub?filter=category&value=ObjectDetection)模型pyramidbox_lite_mobile_mask对图片进行口罩检测
```shell
$ wget https://paddlehub.bj.bcebos.com/resources/test_mask_detection.jpg
$ hub run pyramidbox_lite_mobile_mask --input_path test_mask_detection.jpg
```
![人脸识别结果](docs/imgs/test_mask_detection_result.jpg)
<p align="center">
<img src="./docs/imgs/test_mask_detection_result.jpg" align="middle"
</p>
* 使用[词法分析](http://www.paddlepaddle.org.cn/hub?filter=category&value=LexicalAnalysis)模型LAC进行分词
```shell
......@@ -100,87 +92,63 @@ $ hub run senta_bilstm --input_text "今天天气真好"
$ wget https://paddlehub.bj.bcebos.com/resources/test_image.jpg
$ hub run ultra_light_fast_generic_face_detector_1mb_640 --input_path test_image.jpg
```
![人脸识别结果](docs/imgs/face_detection_result.jpeg)
<p align="center">
<img src="./docs/imgs/face_detection_result.jpeg" align="middle"
</p>
* 使用[图像分割](https://www.paddlepaddle.org.cn/hublist?filter=en_category&value=ImageSegmentation)模型进行人像扣图和人体部件识别
* 使用[图像分割](https://www.paddlepaddle.org.cn/hub?filter=en_category&value=ImageSegmentation)模型ace2p对图片进行语义分割
```shell
$ wget https://paddlehub.bj.bcebos.com/resources/test_image.jpg
$ hub run ace2p --input_path test_image.jpg
$ hub run deeplabv3p_xception65_humanseg --input_path test_image.jpg
```
![图像分割结果](docs/imgs/img_seg_result.jpeg)
除了上述三类模型外,PaddleHub还发布了图像分类、语义模型、视频分类、图像生成、图像分割、文本审核、关键点检测等业界主流模型,更多PaddleHub已经发布的模型,请前往 https://www.paddlepaddle.org.cn/hub 查看
### 迁移学习
<p align="center">
<img src="./docs/imgs/img_seg_result.jpeg" width="35%" />
<img src="./docs/imgs/humanseg_test_res.png" width="35%" />
</p>
迁移学习(Transfer Learning)通俗来讲,就是运用已有的知识来学习新的知识,核心是找到已有知识和新知识之间的相似性。PaddleHub提供了Fine-tune API,只需要少量代码即可完成深度学习模型在自然语言处理和计算机视觉场景下的迁移学习。
* 示例合集
PaddleHub提供了使用Finetune-API和预训练模型完成[文本分类](./demo/text_classification)[序列标注](./demo/sequence_labeling)[多标签分类](./demo/multi_label_classification)[图像分类](./demo/image_classification)[检索式问答任务](./demo/qa_classification)[回归任务](./demo/regression)[句子语义相似度计算](./demo/sentence_similarity)[阅读理解任务](./demo/reading_comprehension)等迁移任务的使用示例,详细参见[demo](./demo)
* 场景化使用
PaddleHub在AI Studio上提供了IPython NoteBook形式的demo。用户可以直接在平台上在线体验,链接如下:
|预训练模型|任务类型|数据集|AIStudio链接|备注|
|-|-|-|-|-|
|pyramidbox_lite_mobile_mask|口罩检测|N/A|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/267322)|
|ResNet|图像分类|猫狗数据集DogCat|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/147010)||
|ERNIE|文本分类|中文情感分类数据集ChnSentiCorp|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/147006)||
|ERNIE|文本分类|中文新闻分类数据集THUNEWS|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/221999)|本教程讲述了如何将自定义数据集加载,并利用Fine-tune API完成文本分类迁移学习。|
|ERNIE|序列标注|中文序列标注数据集MSRA_NER|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/147009)||
|ERNIE|序列标注|中文快递单数据集Express|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/184200)|本教程讲述了如何将自定义数据集加载,并利用Fine-tune API完成序列标注迁移学习。|
|ERNIE Tiny|文本分类|中文情感分类数据集ChnSentiCorp|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/186443)||
|Senta|文本分类|中文情感分类数据集ChnSentiCorp|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/216846)|本教程讲述了任何利用Senta和Fine-tune API完成情感分类迁移学习。|
|Senta|情感分析预测|N/A|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/215814)||
|LAC|词法分析|N/A|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/215711)||
|Ultra-Light-Fast-Generic-Face-Detector-1MB|人脸检测|N/A|[点击体验](https://aistudio.baidu.com/aistudio/projectdetail/215962)||
**NOTE:** [`飞桨PaddleHub`](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/79927)是PaddleHub的官方账号。
关于PaddleHub快捷完成迁移学习,更多信息参考:
<p align='center'>
&#8194;&#8194;&#8194&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;ace2p分割结果展示&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;&#8194;
humanseg分割结果展示&#8194;&#8194;&#8194;
</p>
[Fine-tune API](./docs/reference)
PaddleHub还提供图像分类、语义模型、视频分类、图像生成、图像分割、文本审核、关键点检测等主流模型,更多模型介绍,请前往 [https://www.paddlepaddle.org.cn/hub](https://www.paddlepaddle.org.cn/hub) 查看
[自定义数据集如何Fine-tune](./docs/tutorial/how_to_load_data.md)
### 易用的迁移学习
[实现自定义迁移任务](./docs/tutorial/how_to_define_task.md)
通过Fine-tune API,只需要少量代码即可完成深度学习模型在自然语言处理和计算机视觉场景下的迁移学习。
[ULMFiT优化策略](./docs/tutorial/strategy_exp.md)
* [Demo示例](./demo)提供丰富的Fine-tune API的使用代码,包括[文本分类](./demo/text_classification)[序列标注](./demo/sequence_labeling)[多标签分类](./demo/multi_label_classification)[图像分类](./demo/image_classification)[检索式问答任务](./demo/qa_classification)[回归任务](./demo/regression)[句子语义相似度计算](./demo/sentence_similarity)[阅读理解任务](./demo/reading_comprehension)等场景的模型迁移示例。
### 服务化部署PaddleHub Serving
* 如需在线快速体验,请点击[PaddleHub教程合集](https://aistudio.baidu.com/aistudio/projectdetail/231146),可使用AI Studio平台提供的GPU算力进行快速尝试。
PaddleHub提供便捷的服务化部署能力,简单一行命令即可实现模型部署上线以对外提供服务。
更多Fine-tune API的使用教程可参考:
**PaddleHub 1.5.0版本增加文本Embedding服务[Bert Service](./docs/tutorial/bert_service.md), 轻松获取文本embedding**
* [Fine-tune API](./docs/reference)
PaddleHub Serving启动方式有两种:
* [如何对自定义数据集进行Fine-tuning](./docs/tutorial/how_to_load_data.md)
* 命令行方式:
* [如何自定义迁移任务](./docs/tutorial/how_to_define_task.md)
```shell
$ hub serving start --modules [Module1==Version1, Module2==Version2, ...]
```
* [ULMFiT优化策略](./docs/tutorial/strategy_exp.md)
其中选项参数`--modules/-m`表示待部署模型。
### 一键模型转服务
* 配置文件方式
PaddleHub提供便捷的模型转服务的能力,只需简单一行命令即可完成模型的HTTP服务部署。通过以下命令即可快速启动LAC词法分析服务
```shell
$ hub serving start --config config.json
$ hub serving start --modules lac
```
config.json文件包含待部署模型信息等,
关于PaddleHub Serving详细信息参见[PaddleHub Serving一键服务化部署](./docs/tutorial/serving.md)
更多关于模型服务化使用说明参见[PaddleHub模型一键能服务化部署](./docs/tutorial/serving.md)
### 超参优化AutoDL Finetuner
**PaddleHub 1.5.0版本增加文本Embedding服务[Bert Service](./docs/tutorial/bert_service.md), 高性能地获取文本Embedding**
深度学习模型往往包含许多的超参数,而这些超参数的取值对模型性能起着至关重要的作用。因为模型参数空间大,目前超参调整都是通过手动,依赖人工经验或者不断尝试,且不同模型、样本数据和场景下不尽相同,所以需要大量尝试,时间成本和资源成本非常浪费。PaddleHub AutoDL Finetuner可以实现自动调整超参数,使得模型性能达到最优水平。它通过多种调优的算法来搜索最优超参。
AutoDL Finetuner详细信息参见[PaddleHub超参优化](./docs/tutorial/autofinetune.md)
### 自动超参优化
PaddleHub内置AutoDL Finetuner能力,提供多种优化策略策略实现自动化超参搜索,使得模型在验证集上得到更好的结果,用户只需要一行命令`hub autofinetune`即可启动。更多详细使用说明请参见[PaddleHub超参优化](./docs/tutorial/autofinetune.md)
## FAQ
......@@ -200,31 +168,27 @@ paddlehub.server_check()
# 如果无法连接远端PaddleHub-Server,则显示Request Hub-Server unsuccessfully。
```
**Q:** 利用PaddleHub ernie/bert进行Fine-tune时,运行出错并提示`paddle.fluid.core_avx.EnforceNotMet: Input ShapeTensor cannot be found in Op reshape2`等信息。
**Q:** 利用PaddleHub ERNIE/BERT进行Fine-tune时,运行出错并提示`paddle.fluid.core_avx.EnforceNotMet: Input ShapeTensor cannot be found in Op reshape2`等信息。
**A:** 因为ernie/bert module的创建时和此时运行环境中PaddlePaddle版本不对应。可以将PaddlePaddle和PaddleHub升级至最新版本,同时将ernie卸载。
**A:** 预训练模型版本与PaddlePaddle版本不匹配。可尝试将PaddlePaddle和PaddleHub升级至最新版本,并将原ERNIE模型卸载。
```shell
$ pip install --upgrade paddlehub
$ hub uninstall ernie
```
**NOTE**: PaddleHub 1.1.1版本已支持离线运行Module
**更多问题**
**FAQ**
当安装或者使用遇到问题时,可以通过[FAQ](https://github.com/PaddlePaddle/PaddleHub/wiki/PaddleHub-FAQ)查找解决方案。
如果在FAQ中没有找到解决方案,欢迎您将问题和bug报告[Github Issues](https://github.com/PaddlePaddle/PaddleHub/issues)的形式提交给我们,我们会第一时间进行跟进。
如果在FAQ中没有找到解决方案,欢迎您将问题以[Github Issues](https://github.com/PaddlePaddle/PaddleHub/issues)的形式提交给我们,我们会第一时间进行跟进。
## 用户交流群
* 飞桨PaddlePaddle 交流群:796771754(QQ群)
* 飞桨 ERNIE交流群:760439550(QQ群)
* 飞桨ERNIE交流群:760439550(QQ群)
## 更新历史
PaddleHub v1.5.0已发布!
PaddleHub v1.6.0已发布!
详情参考[更新历史](./RELEASE.md)
# `v1.6.0`
* NLP Module全面升级,提升应用性和灵活性
* lac、senta系列(bow、cnn、bilstm、gru、lstm)、simnet_bow、porn_detection系列(cnn、gru、lstm)升级高性能预测,性能提升高达50%
* ERNIE、BERT、RoBERTa等Transformer类语义模型新增获取预训练embedding接口get_embedding,方便接入下游任务,提升应用性
* 新增RoBERTa通过模型结构压缩得到的3层Transformer模型[rbt3](https://www.paddlepaddle.org.cn/hubdetail?name=rbt3&en_category=SemanticModel)[rbtl3](https://www.paddlepaddle.org.cn/hubdetail?name=rbtl3&en_category=SemanticModel)
* Task predict接口增加高性能预测模式accelerate_mode,性能提升高达90%
* PaddleHub Module创建流程开放,支持Fine-tune模型转化,全面提升应用性和灵活性
* [预训练模型转化为PaddleHub Module教程](./docs/contribution/contri_pretrained_model.md)
* [Fine-tune模型转化为PaddleHub Module教程](./docs/tutorial/finetuned_model_to_module.md)
* [PaddleHub Serving](/docs/tutorial/serving.md)优化启动方式,支持更加灵活的参数配置
# `v1.5.4`
* 修复Fine-tune中断,checkpoint文件恢复训练失败的问题
# `v1.5.3`
* 优化口罩模型输出结果,提供更加灵活的部署及调用方式
# `v1.5.2`
* 优化pyramidbox_lite_server_mask、pyramidbox_lite_mobile_mask模型的服务化部署性能
# `v1.5.1`
* 修复加载module缺少cache目录的问题
......
......@@ -2,7 +2,7 @@
目前PaddleHub有以下任务示例:
* [图像分类](./mask_detection)
* [口罩检测](./mask_detection)
提供了基于完整的口罩人脸检测及分类的模型搭建的完整的视频级别Demo,同时提供基于飞桨高性能预测库的C++和Python部署方案。
* [图像分类](./image_classification)
......@@ -22,6 +22,7 @@
* [文本分类](./text_classification)
该样例展示了PaddleHub如何将ERNIE/BERT等Transformer类模型作为预训练模型在GLUE、ChnSentiCorp等数据集上完成文本分类的FineTune和预测。
**同时,该样例还展示了如何将一个Fine-tune保存的模型转化成PaddleHub Module。** 请确认转化时,使用的PaddleHub为1.6.0以上版本。
* [多标签分类](./multi_label_classification)
该样例展示了PaddleHub如何将BERT作为预训练模型在Toxic数据集上完成多标签分类的FineTune和预测。
......@@ -44,6 +45,10 @@
* [服务化部署Hub Serving使用](./serving)
该样例文件夹下展示了服务化部署Hub Serving如何使用,将PaddleHub支持的可预测Module如何服务化部署。
* [预训练模型转化成PaddleHub Module](./senta_module_sample)
该样例展示了如何将一个预训练模型转化成PaddleHub Module形式,使得可以通过`hub.Module(name="module_name")`实现一键加载。
请确认转化时,使用的PaddleHub为1.6.0以上版本。
**NOTE:**
以上任务示例均是利用PaddleHub提供的数据集,若您想在自定义数据集上完成相应任务,请查看[PaddleHub适配自定义数据完成Fine-tune](https://github.com/PaddlePaddle/PaddleHub/wiki/PaddleHub%E9%80%82%E9%85%8D%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E5%AE%8C%E6%88%90FineTune)
......
......@@ -146,7 +146,7 @@ body {
autoplay: true,//是否自动播放
drag: 'start', //拖动的属性
video: [
['video/result/222.mp4', 'video/mp4', '中文标清', 0],
['video/result/1-mask_detection.mp4', 'video/mp4', '中文标清', 0],
]
};
var player = new ckplayer(videoObject);
......
# 如何编写一个PaddleHub Module
## 模型基本信息
我们准备编写一个PaddleHub Module,Module的基本信息如下:
```yaml
name: senta_test
version: 1.0.0
summary: This is a PaddleHub Module. Just for test.
author: anonymous
author_email:
type: nlp/sentiment_analysis
```
Module存在一个接口sentiment_classify,用于接收传入文本,并给出文本的情感倾向(正面/负面),支持python接口调用和命令行调用。
```python
import paddlehub as hub
senta_test = hub.Module(name="senta_test")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
```cmd
hub run senta_test --input_text 这部电影太差劲了
```
<br/>
## 策略
为了示例代码简单起见,我们使用一个非常简单的情感判断策略,当输入文本中带有词表中指定单词时,则判断文本倾向为负向,否则为正向
<br/>
## Module创建
### step 1. 创建必要的目录与文件
创建一个senta_test的目录,并在senta_test目录下分别创建__init__.py、module.py、processor.py、vocab.list,其中
|文件名|用途|
|-|-|
|\_\_init\_\_.py|空文件|
|module.py|主模块,提供Module的实现代码|
|processor.py|辅助模块,提供词表加载的方法|
|vocab.list|存放词表|
```cmd
➜ tree senta_test
senta_test/
├── vocab.list
├── __init__.py
├── module.py
└── processor.py
```
### step 2. 实现辅助模块processor
在processor.py中实现一个load_vocab接口用于读取词表
```python
def load_vocab(vocab_path):
with open(vocab_path) as file:
return file.read().split()
```
### step 3. 编写Module处理代码
module.py文件为Module的入口代码所在,我们需要在其中实现预测逻辑。
#### step 3_1. 引入必要的头文件
```python
import argparse
import os
import paddlehub as hub
from paddlehub.module.module import runnable, moduleinfo
from senta_test.processor import load_vocab
```
**NOTE:** 当引用Module中模块时,需要输入全路径,如senta_test.processor
#### step 3_2. 定义SentaTest类
module.py中需要有一个继承了hub.Module的类存在,该类负责实现预测逻辑,并使用moduleinfo填写基本信息。当使用hub.Module(name="senta_test")加载Module时,PaddleHub会自动创建SentaTest的对象并返回。
```python
@moduleinfo(
name="senta_test",
version="1.0.0",
summary="This is a PaddleHub Module. Just for test.",
author="anonymous",
author_email="",
type="nlp/sentiment_analysis",
)
class SentaTest(hub.Module):
...
```
#### step 3_3. 执行必要的初始化
```python
def _initialize(self):
# add arg parser
self.parser = argparse.ArgumentParser(
description="Run the senta_test module.",
prog='hub run senta_test',
usage='%(prog)s',
add_help=True)
self.parser.add_argument(
'--input_text', type=str, default=None, help="text to predict")
# load word dict
vocab_path = os.path.join(self.directory, "vocab.list")
self.vocab = load_vocab(vocab_path)
```
`注意`:执行类的初始化不能使用默认的__init__接口,而是应该重载实现_initialize接口。对象默认内置了directory属性,可以直接获取到Module所在路径
#### step 3_4. 完善预测逻辑
```python
def sentiment_classify(self, texts):
results = []
for text in texts:
sentiment = "positive"
for word in self.vocab:
if word in text:
sentiment = "negative"
break
results.append({"text":text, "sentiment":sentiment})
return results
```
#### step 3_5. 支持命令行调用
如果希望Module可以支持命令行调用,则需要提供一个经过runnable修饰的接口,接口负责解析传入数据并进行预测,将结果返回。
如果不需要提供命令行预测功能,则可以不实现该接口,PaddleHub在用命令行执行时,会自动发现该Module不支持命令行方式,并给出提示。
```python
@runnable
def run_cmd(self, argvs):
args = self.parser.parse_args(argvs)
texts = [args.input_text]
return self.sentiment_classify(texts)
```
#### step 3_6. 支持serving调用
TODO
### 完整代码
* [module.py](./senta_test/module.py)
* [processor.py](./senta_test/module.py)
<br/>
## 测试步骤
完成Module编写后,我们可以通过以下方式测试该Module
### 调用方法1
将Module安装到本机中,再通过Hub.Module(name=...)加载
```shell
hub install senta_test
```
```python
import paddlehub as hub
senta_test = hub.Module(name="senta_test")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
### 调用方法2
直接通过Hub.Module(directory=...)加载
```python
import paddlehub as hub
senta_test = hub.Module(directory="senta_test/")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
### 调用方法3
将senta_test作为路径加到环境变量中,直接加载SentaTest对象
```shell
export PYTHONPATH=senta_test:$PYTHONPATH
```
```python
from senta_test.module import SentaTest
SentaTest.sentiment_classify(texts=["这部电影太差劲了"])
```
### 调用方法4
将Module安装到本机中,再通过hub run运行
```shell
hub install senta_test
hub run senta_test --input_text "这部电影太差劲了"
```
import argparse
import os
import paddlehub as hub
from paddlehub.module.module import runnable, moduleinfo
from senta_test.processor import load_vocab
@moduleinfo(
name="senta_test",
version="1.0.0",
summary="This is a PaddleHub Module. Just for test.",
author="anonymous",
author_email="",
type="nlp/sentiment_analysis",
)
class SentaTest(hub.Module):
def _initialize(self):
# add arg parser
self.parser = argparse.ArgumentParser(
description="Run the senta_test module.",
prog='hub run senta_test',
usage='%(prog)s',
add_help=True)
self.parser.add_argument(
'--input_text', type=str, default=None, help="text to predict")
# load word dict
vocab_path = os.path.join(self.directory, "vocab.list")
self.vocab = load_vocab(vocab_path)
def sentiment_classify(self, texts):
results = []
for text in texts:
sentiment = "positive"
for word in self.vocab:
if word in text:
sentiment = "negative"
break
results.append({"text": text, "sentiment": sentiment})
return results
@runnable
def run_cmd(self, argvs):
args = self.parser.parse_args(argvs)
texts = [args.input_text]
return self.sentiment_classify(texts)
def load_vocab(vocab_path):
with open(vocab_path) as file:
return file.read().split()
......@@ -18,6 +18,14 @@ Loading stgan_celeba successful.
这样就完成了一个图像生成服务化API的部署,默认端口号为8866。
## Step2:测试图像生成在线API
首先指定编码格式及引入需要的包:
```python
# coding: utf8
import requests
import json
import base64
import os
```
我们用来测试的样例图片为:
<p align="center">
......@@ -36,7 +44,6 @@ info为图像描述,根据示例图像信息,info应为"Male,Black_Hair,Eyeg
image为要生成的图像风格,我们选取"Bald"(秃顶的)作为生成图像的风格。
代码如下:
```python
>>> # 指定要使用的图片文件并生成列表[("image", img_1), ("image", img_2), ... ]
>>> file_list = ["../img/man.png"]
......@@ -52,7 +59,14 @@ image为要生成的图像风格,我们选取"Bald"(秃顶的)作为生成图
>>> url = "http://127.0.0.1:8866/predict/image/stgan_celeba"
>>> r = requests.post(url=url, data=data, files=files)
```
stgan_celeba返回的结果包括生成图像的base64编码格式,经过转换可以得到生成图像,代码如下:
stgan_celeba返回的结果包括生成图像的base64编码格式,经过转换可以得到生成图像。
我们建立一个指定的文件夹用于存放结果图片:
```python
>>> if not os.path.exists("stgan_output"):
os.mkdir("stgan_output")
```
然后将图片进行保存,代码如下:
```python
>>> for item in results:
... with open(output_path, "wb") as fp:
......
......@@ -13,12 +13,11 @@ if __name__ == "__main__":
# 指定图片生成方法为stgan_celeba并发送post请求
url = "http://127.0.0.1:8866/predict/image/stgan_celeba"
r = requests.post(url=url, data=data, files=files)
print(r.text)
results = eval(r.json()["results"])
# 保存生成的图片到output文件夹,打印模型输出结果
if not os.path.exists("stgan_output"):
os.mkdir("stgan_output")
results = eval(r.json()["results"])
for item in results:
output_path = os.path.join("stgan_output", item["path"].split("/")[-1])
with open(output_path, "wb") as fp:
......
......@@ -42,7 +42,7 @@ PaddleHub Serving是基于PaddleHub的一键模型服务部署工具,能够通
&emsp;&emsp;该示例展示了利用deeplabv3p_xception65_humanseg完成图像分割服务化部署和在线预测,获取识别结果和分割后的图像。
* [中文情感分析-基于simnet_bow](../module_serving/semantic_model_simnet_bow)
* [中文情感分析-基于senta_lstm](../module_serving/sentiment_analysis_senta_lstm)
&emsp;&emsp;该示例展示了利用senta_lstm完成中文文本情感分析服务化部署和在线预测,获取文本的情感分析结果。
......
......@@ -18,6 +18,12 @@ Loading vgg11_imagenet successful.
这样就完成了一个图像分类服务化API的部署,默认端口号为8866。
## Step2:测试图像分类在线API
首先引入需要的包:
```python
>>> import requests
>>> import json
```
我们用来测试的样例图片为:
<p align="center">
......@@ -49,11 +55,20 @@ files = [("image", file_1), ("image", file_2)]
```
vgg11_imagenent返回的结果为图像分类结果及其对应的概率,我们尝试打印接口返回结果:
```python
>>> print(json.dumps(r.json(), indent=4, ensure_ascii=False))
{
"results": "[[{'Egyptian cat': 0.540287435054779}], [{'daisy': 0.9976677298545837}]]"
}
>>> results = eval(r.json()["results"])
>>> print(json.dumps(results, indent=4, ensure_ascii=False))
[
[
{
"Egyptian cat": 0.540287435054779
}
],
[
{
"daisy": 0.9976677298545837
}
]
]
```
这样我们就完成了对图像分类预测服务化部署和测试。
......
......@@ -12,5 +12,7 @@ if __name__ == "__main__":
url = "http://127.0.0.1:8866/predict/image/vgg11_imagenet"
r = requests.post(url=url, files=files)
results = eval(r.json()["results"])
# 打印预测结果
print(json.dumps(r.json(), indent=4, ensure_ascii=False))
print(json.dumps(results, indent=4, ensure_ascii=False))
......@@ -20,7 +20,12 @@ Loading lac successful.
## Step2:测试语言模型在线API
### 不使用自定义词典
在服务部署好之后,我们可以进行测试,用来测试的文本为`今天是个好日子``天气预报说今天要下雨`
首先指定编码格式及引入需要的包:
```python
>>> # coding: utf8
>>> import requests
>>> import json
```
准备的数据格式为:
```python
{"text": [text_1, text_2, ...]}
......@@ -46,38 +51,26 @@ Loading lac successful.
# 打印预测结果
>>> print(json.dumps(r.json(), indent=4, ensure_ascii=False))
{
"msg": "",
"results": [
{
"tag": [
"TIME",
"v",
"q",
"n"
"TIME", "v", "q", "n"
],
"word": [
"今天",
"是",
"个",
"好日子"
"今天", "是", "个", "好日子"
]
},
{
"tag": [
"n",
"v",
"TIME",
"v",
"v"
"n", "v", "TIME", "v", "v"
],
"word": [
"天气预报",
"说",
"今天",
"要",
"下雨"
"天气预报", "说", "今天", "要", "下雨"
]
}
]
],
"status": "0"
}
```
这样我们就完成了对词法分析的预测服务化部署和测试。
......@@ -99,3 +92,58 @@ Loading lac successful.
```
完整的测试代码见[lac_with_dict_serving_demo.py](lac_with_dict_serving_demo.py)
### 客户端请求新版模型的方式
对某些新版模型,客户端请求方式有所变化,更接近本地预测的请求方式,以降低学习成本。
以lac(2.1.0)为例,使用上述方法进行请求将提示:
```python
{
"Warnning": "This usage is out of date, please use 'application/json' as content-type to post to /predict/lac. See 'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details."
}
```
对于lac(2.1.0),请求的方式如下:
```python
# coding: utf8
import requests
import json
if __name__ == "__main__":
# 指定用于预测的文本并生成字典[text_1, text_2, ... ]
text = ["今天是个好日子", "天气预报说今天要下雨"]
# 以key的方式指定text传入预测方法的时的参数,此例中为"texts"
# 对应本地部署,则为lac.analysis_lexical(text=[text1, text2])
data = {"texts": text, "batch_size": 1}
# 指定预测方法为lac并发送post请求
url = "http://127.0.0.1:8866/predict/lac"
# 指定post请求的headers为application/json方式
headers = {"Content-Type": "application/json"}
r = requests.post(url=url, headers=headers, data=json.dumps(data))
# 打印预测结果
print(json.dumps(r.json(), indent=4, ensure_ascii=False))
```
对结果的解析等与前种方式一致,显示如下:
```python
{
"results": [
{
"tag": [
"TIME", "v", "q", "n"
],
"word": [
"今天", "是", "个", "好日子"
]
},
{
"tag": [
"n", "v", "TIME", "v", "v"
],
"word": [
"天气预报", "说", "今天", "要", "下雨"
]
}
]
}
```
此Demo的具体信息和代码请参见[LAC Serving_2.1.0](lac_2.1.0_serving_demo.py)
# coding: utf8
import requests
import json
if __name__ == "__main__":
# 指定用于预测的文本并生成字典{"text": [text_1, text_2, ... ]}
text = ["今天是个好日子", "天气预报说今天要下雨"]
# 以key的方式指定text传入预测方法的时的参数,此例中为"data"
# 对应本地部署,则为lac.analysis_lexical(data=text, batch_size=1)
data = {"texts": text, "batch_size": 1}
# 指定预测方法为lac并发送post请求,content-type类型应指定json方式
url = "http://127.0.0.1:8866/predict/lac"
# 指定post请求的headers为application/json方式
headers = {"Content-Type": "application/json"}
r = requests.post(url=url, headers=headers, data=json.dumps(data))
# 打印预测结果
print(json.dumps(r.json(), indent=4, ensure_ascii=False))
......@@ -24,6 +24,13 @@ Loading pyramidbox_lite_server_mask successful.
这样就完成了一个口罩检测服务化API的部署,默认端口号为8866。
## Step2:测试图像生成在线API
首先指定编码格式及引入需要的包:
```python
>>> import requests
>>> import json
>>> import base64
>>> import os
```
我们用来测试的样例图片为:
<p align="center">
......@@ -56,7 +63,7 @@ files = [("image", file_1), ("image", file_2)]
```python
>>> # 指定检测方法为pyramidbox_lite_server_mask并发送post请求
>>> url = "http://127.0.0.1:8866/predict/image/pyramidbox_lite_server_mask"
>>> r = requests.post(url=url, files=files)
>>> r = requests.post(url=url, files=files, data={"visual_result": "True"})
```
我们可以打印接口返回结果:
```python
......@@ -67,63 +74,79 @@ files = [("image", file_1), ("image", file_2)]
"data": [
{
"label": "MASK",
"left": 455.5180733203888,
"right": 658.8289226293564,
"top": 186.38022020459175,
"bottom": 442.67284870147705,
"confidence": 0.92117363
"left": 938.8167103528976,
"right": 1126.8890985250473,
"top": 335.8177453279495,
"bottom": 586.0342741012573,
"confidence": 0.9775171
},
{
"label": "MASK",
"left": 938.9076416492462,
"right": 1121.0804233551025,
"top": 326.9856423139572,
"bottom": 586.0468536615372,
"confidence": 0.997152
"label": "NO MASK",
"left": 1166.563014626503,
"right": 1331.2186390161514,
"top": 298.1251895427704,
"bottom": 496.373051404953,
"confidence": 0.6512484
},
{
"label": "NO MASK",
"left": 1166.189564704895,
"right": 1325.6211009025574,
"top": 295.55220007896423,
"bottom": 496.9406336545944,
"confidence": 0.9346678
"label": "MASK",
"left": 458.2292696237564,
"right": 664.9880893230438,
"top": 179.45007160305977,
"bottom": 446.70506715774536,
"confidence": 0.98069304
}
],
"path": "",
"path": "family_mask.jpg",
"id": 1
},
{
"data": [
{
"label": "MASK",
"left": 1346.7342281341553,
"right": 1593.7974529266357,
"top": 239.36296990513802,
"bottom": 574.6375751495361,
"confidence": 0.95378655
"left": 1340.4194090366364,
"right": 1595.8429119586945,
"top": 251.97067219018936,
"bottom": 584.6931987404823,
"confidence": 0.9681898
},
{
"label": "MASK",
"left": 840.5126552581787,
"right": 1083.8391423225403,
"top": 417.5169044137001,
"bottom": 733.8856244087219,
"confidence": 0.85434145
"left": 839.8990581035614,
"right": 1084.293223142624,
"top": 446.8751857280731,
"bottom": 758.4936121702194,
"confidence": 0.9673422
},
{
"label": "NO MASK",
"left": 1145.4194769859314,
"right": 1253.0083780288696,
"top": 128.66552621126175,
"bottom": 283.0486469864845,
"confidence": 0.97426504
}
],
"path": "",
"path": "woman_mask.jpg",
"id": 2
}
]
```
根据结果可以看出准确识别了请求图片中的人脸位置及戴口罩确信度。
pyramidbox_lite_server_mask返回的结果还包括标注检测框的图像的base64编码格式,经过转换可以得到生成图像,代码如下:
pyramidbox_lite_server_mask返回的结果还包括标注检测框的图像的base64编码格式,经过转换可以得到生成图像。
我们建立一个用于保存结果图片的文件夹:
```python
>>> if not os.path.exists("output"):
>>> os.mkdir("output")
```
然后将图片数据进行解码并保存,代码如下:
```python
>>> results = eval(r.json()["results"])
>>> for item in results:
... with open(output_path, "wb") as fp:
... fp.write(base64.b64decode(item["base64"].split(',')[-1]))
>>> with open(output_path, "wb") as fp:
>>> fp.write(base64.b64decode(item["base64"].split(',')[-1]))
```
查看指定输出文件夹,就能看到生成图像了,如图:
......
......@@ -13,12 +13,13 @@ if __name__ == "__main__":
# 指定检测方法为pyramidbox_lite_server_mask并发送post请求
url = "http://127.0.0.1:8866/predict/image/pyramidbox_lite_server_mask"
r = requests.post(url=url, files=files)
results = eval(r.json()["results"])
r = requests.post(url=url, files=files, data={"visual_result": "True"})
# 创建图片保存文件夹
if not os.path.exists("output"):
os.mkdir("output")
results = eval(r.json()["results"])
for item in results:
with open(os.path.join("output", item["path"]), "wb") as fp:
fp.write(base64.b64decode(item["base64"].split(',')[-1]))
......
......@@ -18,6 +18,13 @@ Loading yolov3_darknet53_coco2017 successful.
这样就完成了一个图像生成服务化API的部署,默认端口号为8866。
## Step2:测试图像生成在线API
首先引入需要的包:
```python
>>> import requests
>>> import json
>>> import base64
>>> import os
```
我们用来测试的样例图片为:
<p align="center">
......@@ -95,8 +102,15 @@ files = [("image", file_1), ("image", file_2)]
```
根据结果可以看出准确识别了请求的图片。
yolov3_darknet53_coco2017返回的结果还包括标注检测框的图像的base64编码格式,经过转换可以得到生成图像,代码如下:
yolov3_darknet53_coco2017返回的结果还包括标注检测框的图像的base64编码格式,经过转换可以得到生成图像。
我们创建一个保存结果图片的文件夹:
```python
>>> if not os.path.exists("output"):
>>> os.mkdir("output")
```
然后将图片数据进行解码并保存,代码如下:
```python
>>> results = eval(r.json()["results"])
>>> for item in results:
... with open(output_path, "wb") as fp:
... fp.write(base64.b64decode(item["base64"].split(',')[-1]))
......
......@@ -14,11 +14,11 @@ if __name__ == "__main__":
url = "http://127.0.0.1:8866/predict/image/yolov3_darknet53_coco2017"
r = requests.post(url=url, files=files)
results = eval(r.json()["results"])
# 保存检测生成的图片到output文件夹,打印模型输出结果
if not os.path.exists("output"):
os.mkdir("output")
results = eval(r.json()["results"])
for item in results:
with open(os.path.join("output", item["path"]), "wb") as fp:
fp.write(base64.b64decode(item["base64"].split(',')[-1]))
......
......@@ -20,6 +20,12 @@ Loading lac successful.
## Step2:测试语义模型在线API
在服务部署好之后,我们可以进行测试,用来测试的文本对分别为`[这道题太难了:这道题是上一年的考题], [这道题太难了:这道题不简单], [这道题太难了:这道题很有意思]`
首先指定编码格式及引入需要的包:
```python
>>> # coding: utf8
>>> import requests
>>> import json
```
准备的数据格式为:
```python
{"text_1": [text_a1, text_a2, ... ], "text_2": [text_b1, text_b2, ... ]}
......@@ -48,6 +54,7 @@ Loading lac successful.
# 打印预测结果
>>> print(json.dumps(r.json(), indent=4, ensure_ascii=False))
{
"msg": "",
"results": [
{
"similarity": 0.8445,
......@@ -64,7 +71,8 @@ Loading lac successful.
"text_1": "这道题太难了",
"text_2": "这道题很有意思"
}
]
],
"status": "0"
}
```
这样我们就完成了对语义模型simnet_bow的预测服务化部署和测试。
......
......@@ -18,6 +18,14 @@ Loading deeplabv3p_xception65_humanseg successful.
这样就完成了一个图像分割服务化API的部署,默认端口号为8866。
## Step2:测试图像分割在线API
首先指定编码格式及引入需要的包:
```python
>>> # coding: utf8
>>> import requests
>>> import json
>>> import base64
>>> import os
```
我们用来测试的样例图片为:
<p align="center">
......@@ -51,15 +59,21 @@ files = [("image", file_1), ("image", file_2)]
>>> results = eval(r.json()["results"])
>>> print(json.dumps(results, indent=4, ensure_ascii=False))
[
{
"origin": "girl.jpg",
"processed": "humanseg_output/girl.png"
}
{
"origin": "girl.jpg",
"processed": "humanseg_output/girl.png"
}
]
```
deeplabv3p_xception65_humanseg返回的结果还包括人像分割后的图像的base64编码格式,经过转换可以得到生成图像,代码如下
deeplabv3p_xception65_humanseg返回的结果还包括人像分割后的图像的base64编码格式,经过转换可以得到生成图像。
我们建立一个文件夹用于存放结果图片
```python
>>> if not os.path.exists("output"):
>>> os.mkdir("output")
```
然后将图片数据解码并保存,代码如下:
```python
>>> results = eval(r.json()["results"])
>>> for item in results:
... with open(output_path, "wb") as fp:
... fp.write(base64.b64decode(item["base64"].split(',')[-1]))
......
......@@ -12,11 +12,11 @@ if __name__ == "__main__":
url = "http://127.0.0.1:8866/predict/image/deeplabv3p_xception65_humanseg"
r = requests.post(url=url, files=files)
results = eval(r.json()["results"])
# 保存分割后的图片到output文件夹,打印模型输出结果
if not os.path.exists("output"):
os.mkdir("output")
results = eval(r.json()["results"])
for item in results:
with open(
os.path.join("output", item["processed"].split("/")[-1]),
......
......@@ -19,9 +19,16 @@ Loading senta_lstm successful.
这样就完成了一个词法分析服务化API的部署,默认端口号为8866。
## Step2:测试词法分析在线API
在服务部署好之后,我们可以进行测试,用来测试的文本为`我不爱吃甜食``我喜欢躺在床上看电影`
在服务部署好之后,我们可以进行测试。
准备的数据格式为:
首先指定编码格式及引入需要的包:
```python
>>> # coding: utf8
>>> import requests
>>> import json
```
用来测试的文本为`我不爱吃甜食``我喜欢躺在床上看电影`,准备的数据格式为:
```python
{"text": [text_1, text_2, ...]}
```
......@@ -37,47 +44,33 @@ Loading senta_lstm successful.
## Step3:获取并验证结果
接下来发送请求到词法分析API,并得到结果,代码如下:
```python
# 指定预测方法为lac并发送post请求
# 指定预测方法为senta_lstm并发送post请求
>>> url = "http://127.0.0.1:8866/predict/text/senta_lstm"
>>> r = requests.post(url=url, data=text)
```
`LAC`模型返回的结果为每个文本分词后的结果,我们尝试打印接口返回结果:
我们尝试打印接口返回结果:
```python
# 打印预测结果
>>> print(json.dumps(r.json(), indent=4, ensure_ascii=False))
{
"msg": "",
"results": [
{
"tag": [
"TIME",
"v",
"q",
"n"
],
"word": [
"今天",
"是",
"个",
"好日子"
]
"negative_probs": 0.7079,
"positive_probs": 0.2921,
"sentiment_key": "negative",
"sentiment_label": 0,
"text": "我不爱吃甜食"
},
{
"tag": [
"n",
"v",
"TIME",
"v",
"v"
],
"word": [
"天气预报",
"说",
"今天",
"要",
"下雨"
]
"negative_probs": 0.0149,
"positive_probs": 0.9851,
"sentiment_key": "positive",
"sentiment_label": 1,
"text": "我喜欢躺在床上看电影"
}
]
],
"status": "0"
}
```
这样我们就完成了对词法分析的预测服务化部署和测试。
......
......@@ -19,9 +19,16 @@ Loading porn_detection_lstm successful.
这样就完成了一个文本审核服务化API的部署,默认端口号为8866。
## Step2:测试文本审核在线API
在服务部署好之后,我们可以进行测试,用来测试的文本为`黄片下载``中国黄页`
在服务部署好之后,我们可以进行测试。
准备的数据格式为:
首先指定编码格式及引入需要的包:
```python
>>> # coding: utf8
>>> import requests
>>> import json
```
用来测试的文本为`黄片下载``中国黄页`,准备的数据格式为:
```python
{"text": [text_1, text_2, ...]}
```
......@@ -45,6 +52,7 @@ Loading porn_detection_lstm successful.
# 打印预测结果
>>> print(json.dumps(r.json(), indent=4, ensure_ascii=False))
{
"msg": "",
"results": [
{
"not_porn_probs": 0.0121,
......@@ -60,7 +68,8 @@ Loading porn_detection_lstm successful.
"porn_probs": 0.0046,
"text": "中国黄页"
}
]
],
"status": "0"
}
```
可以看出正确得到了两个文本的预测结果。
......
......@@ -218,3 +218,9 @@ python predict.py --checkpoint_dir $CKPT_DIR --max_seq_len 128
## 超参优化AutoDL Finetuner
PaddleHub还提供了超参优化(Hyperparameter Tuning)功能, 自动搜索最优模型超参得到更好的模型效果。详细信息参见[AutoDL Finetuner超参优化功能教程](../../docs/tutorial/autofinetune.md)
## Fine-tune之后保存的模型转化为PaddleHub Module
代码详见[finetuned_model_to_module](./finetuned_model_to_module)文件夹下
Fine-tune之后保存的模型转化为PaddleHub Module[教程](../../docs/tutorial/finetuned_model_to_module.md)
# -*- coding:utf-8 -*-
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# 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.
"""Finetuning on classification task """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import numpy as np
from paddlehub.common.logger import logger
from paddlehub.module.module import moduleinfo, serving
import paddlehub as hub
@moduleinfo(
name="ernie_tiny_finetuned",
version="1.0.0",
summary="ERNIE tiny which was fine-tuned on the chnsenticorp dataset.",
author="anonymous",
author_email="",
type="nlp/semantic_model")
class ERNIETinyFinetuned(hub.Module):
def _initialize(self,
ckpt_dir="ckpt_chnsenticorp",
num_class=2,
max_seq_len=128,
use_gpu=False,
batch_size=1):
self.ckpt_dir = os.path.join(self.directory, ckpt_dir)
self.num_class = num_class
self.MAX_SEQ_LEN = max_seq_len
# Load Paddlehub ERNIE Tiny pretrained model
self.module = hub.Module(name="ernie_tiny")
inputs, outputs, program = self.module.context(
trainable=True, max_seq_len=max_seq_len)
self.vocab_path = self.module.get_vocab_path()
# Download dataset and use accuracy as metrics
# Choose dataset: GLUE/XNLI/ChinesesGLUE/NLPCC-DBQA/LCQMC
# metric should be acc, f1 or matthews
metrics_choices = ["acc"]
# For ernie_tiny, it use sub-word to tokenize chinese sentence
# If not ernie tiny, sp_model_path and word_dict_path should be set None
reader = hub.reader.ClassifyReader(
vocab_path=self.module.get_vocab_path(),
max_seq_len=max_seq_len,
sp_model_path=self.module.get_spm_path(),
word_dict_path=self.module.get_word_dict_path())
# Construct transfer learning network
# Use "pooled_output" for classification tasks on an entire sentence.
# Use "sequence_output" for token-level output.
pooled_output = outputs["pooled_output"]
# Setup feed list for data feeder
# Must feed all the tensor of module need
feed_list = [
inputs["input_ids"].name,
inputs["position_ids"].name,
inputs["segment_ids"].name,
inputs["input_mask"].name,
]
# Setup runing config for PaddleHub Finetune API
config = hub.RunConfig(
use_data_parallel=False,
use_cuda=use_gpu,
batch_size=batch_size,
checkpoint_dir=self.ckpt_dir,
strategy=hub.AdamWeightDecayStrategy())
# Define a classfication finetune task by PaddleHub's API
self.cls_task = hub.TextClassifierTask(
data_reader=reader,
feature=pooled_output,
feed_list=feed_list,
num_classes=self.num_class,
config=config,
metrics_choices=metrics_choices)
def predict(self, data, return_result=False, accelerate_mode=True):
"""
Get prediction results
"""
run_states = self.cls_task.predict(
data=data,
return_result=return_result,
accelerate_mode=accelerate_mode)
return run_states
if __name__ == "__main__":
ernie_tiny = ERNIETinyFinetuned(
ckpt_dir="../ckpt_chnsenticorp", num_class=2)
# Data to be prdicted
data = [["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"],
["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]]
index = 0
run_states = ernie_tiny.predict(data=data)
results = [run_state.run_results for run_state in run_states]
for batch_result in results:
# get predict index
batch_result = np.argmax(batch_result, axis=2)[0]
for result in batch_result:
print("%s\tpredict=%s" % (data[index][0], result))
index += 1
......@@ -16,7 +16,6 @@
import argparse
import ast
import paddle.fluid as fluid
import paddlehub as hub
# yapf: disable
......
# 贡献预训练模型
# 如何编写一个PaddleHub Module
我们非常欢迎开发者贡献预训练模型到PaddleHub中,如果你想要贡献预训练模型,请提供以下资源:
## 模型基本信息
## 模型
我们准备编写一个PaddleHub Module,Module的基本信息如下:
```yaml
name: senta_test
version: 1.0.0
summary: This is a PaddleHub Module. Just for test.
author: anonymous
author_email:
type: nlp/sentiment_analysis
```
请提供相应的网络结构和参数文件,除了PaddlePaddle的模型外,我们也支持将其他主流框架的模型转换到PaddleHub中,包括:
* tensorflow
* pytorch
* mxnet
* caffe
* onnx
**本示例代码可以参考[senta_module_sample](../../demo/senta_module_sample/senta_test)**
您可以直接使用 [**x2paddle**](https://github.com/PaddlePaddle/X2Paddle) 进行转换,也可以将相应模型提供给我们,由我们进行转换
Module存在一个接口sentiment_classify,用于接收传入文本,并给出文本的情感倾向(正面/负面),支持python接口调用和命令行调用。
```python
import paddlehub as hub
## 相关代码
senta_test = hub.Module(name="senta_test")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
```cmd
hub run senta_test --input_text 这部电影太差劲了
```
* 支持预测的模型,请提供相应的预测脚本以及测试样例
* 支持finetune的模型,请提供相应的finetune demo
<br/>
## 相应的介绍资料
## 策略
|资料|是否必选|
为了示例代码简单起见,我们使用一个非常简单的情感判断策略,当输入文本中带有词表中指定单词时,则判断文本倾向为负向,否则为正向
<br/>
## Module创建
### step 1. 创建必要的目录与文件
创建一个senta_test的目录,并在senta_test目录下分别创建__init__.py、module.py、processor.py、vocab.list,其中
|文件名|用途|
|-|-|
|模型结构|√|
|预训练的数据集|√|
|模型介绍文案|√|
|源代码链接||
|模型结构图||
|第三方库依赖||
|\_\_init\_\_.py|空文件|
|module.py|主模块,提供Module的实现代码|
|processor.py|辅助模块,提供词表加载的方法|
|vocab.list|存放词表|
```cmd
➜ tree senta_test
senta_test/
├── vocab.list
├── __init__.py
├── module.py
└── processor.py
```
### step 2. 实现辅助模块processor
在processor.py中实现一个load_vocab接口用于读取词表
```python
def load_vocab(vocab_path):
with open(vocab_path) as file:
return file.read().split()
```
### step 3. 编写Module处理代码
module.py文件为Module的入口代码所在,我们需要在其中实现预测逻辑。
#### step 3_1. 引入必要的头文件
```python
import argparse
import os
import paddlehub as hub
from paddlehub.module.module import runnable, moduleinfo
from senta_test.processor import load_vocab
```
**NOTE:** 当引用Module中模块时,需要输入全路径,如senta_test.processor
#### step 3_2. 定义SentaTest类
module.py中需要有一个继承了hub.Module的类存在,该类负责实现预测逻辑,并使用moduleinfo填写基本信息。当使用hub.Module(name="senta_test")加载Module时,PaddleHub会自动创建SentaTest的对象并返回。
```python
@moduleinfo(
name="senta_test",
version="1.0.0",
summary="This is a PaddleHub Module. Just for test.",
author="anonymous",
author_email="",
type="nlp/sentiment_analysis",
)
class SentaTest(hub.Module):
...
```
#### step 3_3. 执行必要的初始化
```python
def _initialize(self):
# add arg parser
self.parser = argparse.ArgumentParser(
description="Run the senta_test module.",
prog='hub run senta_test',
usage='%(prog)s',
add_help=True)
self.parser.add_argument(
'--input_text', type=str, default=None, help="text to predict")
# load word dict
vocab_path = os.path.join(self.directory, "vocab.list")
self.vocab = load_vocab(vocab_path)
```
`注意`:执行类的初始化不能使用默认的__init__接口,而是应该重载实现_initialize接口。对象默认内置了directory属性,可以直接获取到Module所在路径
#### step 3_4. 完善预测逻辑
```python
def sentiment_classify(self, texts):
results = []
for text in texts:
sentiment = "positive"
for word in self.vocab:
if word in text:
sentiment = "negative"
break
results.append({"text":text, "sentiment":sentiment})
return results
```
#### step 3_5. 支持命令行调用
如果希望Module可以支持命令行调用,则需要提供一个经过runnable修饰的接口,接口负责解析传入数据并进行预测,将结果返回。
如果不需要提供命令行预测功能,则可以不实现该接口,PaddleHub在用命令行执行时,会自动发现该Module不支持命令行方式,并给出提示。
```python
@runnable
def run_cmd(self, argvs):
args = self.parser.parse_args(argvs)
texts = [args.input_text]
return self.sentiment_classify(texts)
```
#### step 3_6. 支持serving调用
如果希望Module可以支持PaddleHub Serving部署预测服务,则需要提供一个经过serving修饰的接口,接口负责解析传入数据并进行预测,将结果返回。
如果不需要提供PaddleHub Serving部署预测服务,则可以不需要加上serving修饰。
```python
@serving
def sentiment_classify(self, texts):
results = []
for text in texts:
sentiment = "positive"
for word in self.vocab:
if word in text:
sentiment = "negative"
break
results.append({"text":text, "sentiment":sentiment})
return results
```
### 完整代码
* [module.py](../../demo/senta_module_sample/senta_test/module.py)
* [processor.py](../../demo/senta_module_sample/senta_test/processor.py)
<br/>
## 测试步骤
完成Module编写后,我们可以通过以下方式测试该Module
### 调用方法1
将Module安装到本机中,再通过Hub.Module(name=...)加载
```shell
hub install senta_test
```
```python
import paddlehub as hub
senta_test = hub.Module(name="senta_test")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
### 调用方法2
直接通过Hub.Module(directory=...)加载
```python
import paddlehub as hub
senta_test = hub.Module(directory="senta_test/")
senta_test.sentiment_classify(texts=["这部电影太差劲了"])
```
### 调用方法3
将senta_test作为路径加到环境变量中,直接加载SentaTest对象
```shell
export PYTHONPATH=senta_test:$PYTHONPATH
```
```python
from senta_test.module import SentaTest
SentaTest.sentiment_classify(texts=["这部电影太差劲了"])
```
**NOTE:**
### 调用方法4
将Module安装到本机中,再通过hub run运行
* 为了保证使用体验,请确保模型在python 2.7/3.x下均可正常运行
```shell
hub install senta_test
hub run senta_test --input_text "这部电影太差劲了"
```
......@@ -2,7 +2,7 @@
## 一、简介
目前深度学习模型参数可分两类:*模型参数 (Model Parameters)**超参数 (Hyper Parameters)*,前者是模型通过大量的样本数据进行训练学习得到的参数数据;后者则需要通过人工经验或者不断尝试找到最佳设置(如学习率、dropout_rate、batch_size等),以提高模型训练的效果。如果想得到一个效果好的深度学习神经网络模型,超参的设置非常关键。因为模型参数空间大,目前超参调整都是通过手动,依赖人工经验或者不断尝试,且不同模型、样本数据和场景下不尽相同,所以需要大量尝试,时间成本和资源成本非常浪费。PaddleHub AutoDL Finetuner可以实现自动调整超参数。
目前深度学习模型参数可分两类:*模型参数 (Model Parameters)**超参数 (Hyper Parameters)*,前者是模型通过大量的样本数据进行训练学习得到的参数数据;后者则需要通过人工经验或者不断尝试找到最佳设置(如学习率、dropout_rate、batch_size等),以提高模型训练的效果。如果想得到一个效果好的深度学习神经网络模型,超参的设置非常关键。因为模型参数空间大,目前超参调整都是通过手动,依赖人工经验或者不断尝试,且不同模型、样本数据和场景下不尽相同,所以需要大量尝试,时间成本和资源成本非常浪费。PaddleHub AutoDL Finetuner可以实现自动调整超参数。
PaddleHub AutoDL Finetuner提供两种超参优化算法:
......
......@@ -27,7 +27,7 @@ PaddleHub支持修改预训练模型存放路径:
`选项`
* `--output_path`:用于指定存放下载文件的目录,默认为当前目录
* `--uncompress`:是否对下载的压缩包进行解压,默认不解压
* `--uncompress`:是否对下载的压缩包进行解压,填写true或者false,默认false(不解压)
* `--type`:指定下载的资源类型,当指定Model时,download只会下载Model的资源。默认为All,此时会优先搜索Module资源,如果没有相关的Module资源,则搜索Model
......@@ -79,14 +79,14 @@ PaddleHub在使用过程中会产生一些缓存数据,这部分数据默认
## `config`
用于查看和设置paddlehub相关设置,包括对server地址、日志级别的设置
用于查看和设置paddlehub相关设置,包括对server地址、日志级别的设置
`示例`
* `hub config`: 显示当前paddlehub的设置
* `hub config reset`: 恢复当前paddlehub的设置为默认设置
* `hub config server==[address]`: 设置当前server地址为[address]
* `hub config server==[address]`: 设置当前paddlehub-server地址为[address],paddlehub客户端从此地址获取模型信息
* `hub config log==[level]`: 设置当前日志级别为[level], 可选值为critical, error, warning, info, debug, nolog, 从左到右优先级从高到低,nolog表示不显示日志信息
......
# Fine-tune保存的模型如何转化为一个PaddleHub Module
## 模型基本信息
本示例以模型ERNIE Tiny在数据集ChnSentiCorp上完成情感分类Fine-tune任务后保存的模型转化为一个PaddleHub Module,Module的基本信息如下:
```yaml
name: ernie_tiny_finetuned
version: 1.0.0
summary: ERNIE tiny which was fine-tuned on the chnsenticorp dataset.
author: anonymous
author_email:
type: nlp/semantic_model
```
**本示例代码可以参考[finetuned_model_to_module](../../demo/text_classification/finetuned_model_to_module/)**
Module存在一个接口predict,用于接收带预测,并给出文本的情感倾向(正面/负面),支持python接口调用和命令行调用。
```python
import paddlehub as hub
ernie_tiny_finetuned = hub.Module(name="ernie_tiny_finetuned")
ernie_tiny_finetuned.predcit(data=[["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"],
["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]])
```
## Module创建
### step 1. 创建必要的目录与文件
创建一个finetuned_model_to_module的目录,并在finetuned_model_to_module目录下分别创建__init__.py、module.py,其中
|文件名|用途|
|-|-|
|\_\_init\_\_.py|空文件|
|module.py|主模块,提供Module的实现代码|
|ckpt文件|利用PaddleHub Fine-tune得到的ckpt文件夹,其中必须包含best_model文件|
```cmd
➜ tree finetuned_model_to_module
finetuned_model_to_module/
├── __init__.py
├── ckpt_chnsenticorp
│   ├── ***
│   ├── best_model
│   │   ├── ***
└── module.py
```
### step 2. 编写Module处理代码
module.py文件为Module的入口代码所在,我们需要在其中实现预测逻辑。
#### step 2_1. 引入必要的头文件
```python
import os
import numpy as np
from paddlehub.common.logger import logger
from paddlehub.module.module import moduleinfo, serving
import paddlehub as hub
```
#### step 2_2. 定义ERNIE_Tiny_Finetuned类
module.py中需要有一个继承了hub.Module的类存在,该类负责实现预测逻辑,并使用moduleinfo填写基本信息。当使用hub.Module(name="ernie_tiny_finetuned")加载Module时,PaddleHub会自动创建ERNIE_Tiny_Finetuned的对象并返回。
```python
@moduleinfo(
name="ernie_tiny_finetuned",
version="1.0.0",
summary="ERNIE tiny which was fine-tuned on the chnsenticorp dataset.",
author="anonymous",
author_email="",
type="nlp/semantic_model")
class ERNIETinyFinetuned(hub.Module):
...
```
#### step 2_3. 执行必要的初始化
```python
def _initialize(self,
ckpt_dir="ckpt_chnsenticorp",
num_class=2,
max_seq_len=128,
use_gpu=False,
batch_size=1):
self.ckpt_dir = os.path.join(self.directory, ckpt_dir)
self.num_class = num_class
self.MAX_SEQ_LEN = max_seq_len
self.params_path = os.path.join(self.ckpt_dir, 'best_model')
if not os.path.exists(self.params_path):
logger.error(
"%s doesn't contain the best_model file which saves the best parameters as fietuning."
)
exit()
# Load Paddlehub ERNIE Tiny pretrained model
self.module = hub.Module(name="ernie_tiny")
inputs, outputs, program = self.module.context(
trainable=True, max_seq_len=max_seq_len)
self.vocab_path = self.module.get_vocab_path()
# Download dataset and use accuracy as metrics
# Choose dataset: GLUE/XNLI/ChinesesGLUE/NLPCC-DBQA/LCQMC
# metric should be acc, f1 or matthews
metrics_choices = ["acc"]
# For ernie_tiny, it use sub-word to tokenize chinese sentence
# If not ernie tiny, sp_model_path and word_dict_path should be set None
reader = hub.reader.ClassifyReader(
vocab_path=self.module.get_vocab_path(),
max_seq_len=max_seq_len,
sp_model_path=self.module.get_spm_path(),
word_dict_path=self.module.get_word_dict_path())
# Construct transfer learning network
# Use "pooled_output" for classification tasks on an entire sentence.
# Use "sequence_output" for token-level output.
pooled_output = outputs["pooled_output"]
# Setup feed list for data feeder
# Must feed all the tensor of module need
feed_list = [
inputs["input_ids"].name,
inputs["position_ids"].name,
inputs["segment_ids"].name,
inputs["input_mask"].name,
]
# Setup runing config for PaddleHub Finetune API
config = hub.RunConfig(
use_data_parallel=False,
use_cuda=use_gpu,
batch_size=batch_size,
checkpoint_dir=self.ckpt_dir,
strategy=hub.AdamWeightDecayStrategy())
# Define a classfication finetune task by PaddleHub's API
self.cls_task = hub.TextClassifierTask(
data_reader=reader,
feature=pooled_output,
feed_list=feed_list,
num_classes=self.num_class,
config=config,
metrics_choices=metrics_choices)
```
初始化过程即为Fine-tune时创建Task的过程。
**NOTE:** 执行类的初始化不能使用默认的__init__接口,而是应该重载实现_initialize接口。对象默认内置了directory属性,可以直接获取到Module所在路径
#### step 3_4. 完善预测逻辑
```python
def predict(self, data, return_result=False, accelerate_mode=True):
"""
Get prediction results
"""
run_states = self.cls_task.predict(
data=data,
return_result=return_result,
accelerate_mode=accelerate_mode)
return run_states
```
#### step 3_5. 支持serving调用
如果希望Module可以支持PaddleHub Serving部署预测服务,则需要将预测接口predcit加上serving修饰(`@serving`),接口负责解析传入数据并进行预测,将结果返回。
如果不需要提供PaddleHub Serving部署预测服务,则可以不需要加上serving修饰。
```python
@serving
def predict(self, data, return_result=False, accelerate_mode=True):
"""
Get prediction results
"""
run_states = self.cls_task.predict(
data=data,
return_result=return_result,
accelerate_mode=accelerate_mode)
return run_states
```
### 完整代码
* [module.py](../../demo/text_classification/finetuned_model_to_module/module.py)
* [__init__.py](../../demo/text_classification/finetuned_model_to_module/__init__.py)
**NOTE:** `__init__.py`是空文件
## 测试步骤
完成Module编写后,我们可以通过以下方式测试该Module
### 调用方法1
将Module安装到本机中,再通过Hub.Module(name=...)加载
```shell
hub install finetuned_model_to_module
```
安装成功会显示**Successfully installed ernie_tiny_finetuned**
```python
import paddlehub as hub
import numpy as np
ernie_tiny = hub.Module(name="ernie_tiny_finetuned")
# Data to be prdicted
data = [["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"],
["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]]
index = 0
run_states = ernie_tiny.predict(data=data)
results = [run_state.run_results for run_state in run_states]
for batch_result in results:
# get predict index
batch_result = np.argmax(batch_result, axis=2)[0]
for result in batch_result:
print("%s\tpredict=%s" % (data[index][0], result))
index += 1
```
### 调用方法2
直接通过Hub.Module(directory=...)加载
```python
import paddlehub as hub
import numpy as np
ernie_tiny_finetuned = hub.Module(directory="finetuned_model_to_module/")
# Data to be prdicted
data = [["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"],
["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]]
index = 0
run_states = ernie_tiny.predict(data=data)
results = [run_state.run_results for run_state in run_states]
for batch_result in results:
# get predict index
batch_result = np.argmax(batch_result, axis=2)[0]
for result in batch_result:
print("%s\tpredict=%s" % (data[index][0], result))
index += 1
```
### 调用方法3
将finetuned_model_to_module作为路径加到环境变量中,直接加载ERNIETinyFinetuned对象
```shell
export PYTHONPATH=finetuned_model_to_module:$PYTHONPATH
```
```python
from finetuned_model_to_module.module import ERNIETinyFinetuned
import numpy as np
# Data to be prdicted
data = [["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"],
["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]]
run_states = ERNIETinyFinetuned.predict(data=data)
index = 0
results = [run_state.run_results for run_state in run_states]
for batch_result in results:
# get predict index
batch_result = np.argmax(batch_result, axis=2)[0]
for result in batch_result:
print("%s\tpredict=%s" % (data[index][0], result))
index += 1
```
......@@ -18,7 +18,8 @@ PaddleHub Serving有两种启动方式,分别是使用命令行启动,以及
$ hub serving start --modules [Module1==Version1, Module2==Version2, ...] \
--port XXXX \
--use_gpu \
--use_multiprocess
--use_multiprocess \
--workers \
```
**参数**
......@@ -28,8 +29,9 @@ $ hub serving start --modules [Module1==Version1, Module2==Version2, ...] \
|--modules/-m|PaddleHub Serving预安装模型,以多个Module==Version键值对的形式列出<br>*`当不指定Version时,默认选择最新版本`*|
|--port/-p|服务端口,默认为8866|
|--use_gpu|使用GPU进行预测,必须安装paddlepaddle-gpu|
|--use_multiprocess|是否启用并发方式,默认为单进程方式,推荐多核CPU机器使用此方式<br>*`Windows操作系统只支持单进程方式`*|
|--use_multiprocess|是否启用并发方式,默认为单进程方式,推荐多核CPU机器使用此方式<br>*`Windows操作系统只支持单进程方式`*|
|--workers|在并发方式下指定的并发任务数,默认为`2*cpu_count-1`,其中`cpu_count`为CPU核数|
**NOTE:** --use_gpu不可与--use_multiprocess共用。
#### 配置文件启动
启动命令
```shell
......@@ -38,44 +40,55 @@ $ hub serving start --config config.json
`config.json`格式如下:
```json
{
"modules_info": [
{
"module": "MODULE_NAME_1",
"version": "MODULE_VERSION_1",
"batch_size": "BATCH_SIZE_1"
},
{
"module": "MODULE_NAME_2",
"version": "MODULE_VERSION_2",
"batch_size": "BATCH_SIZE_2"
{
"modules_info": {
"yolov3_darknet53_coco2017": {
"init_args": {
"version": "1.0.0"
},
"predict_args": {
"batch_size": 1,
"use_gpu": false
}
},
"lac": {
"init_args": {
"version": "1.1.0"
},
"predict_args": {
"batch_size": 1,
"use_gpu": false
}
}
],
},
"port": 8866,
"use_gpu": false,
"use_multiprocess": false
"use_multiprocess": false,
"workers": 2
}
```
**参数**
|参数|用途|
|-|-|
|modules_info|PaddleHub Serving预安装模型,以字典列表形式列出,其中:<br>`module`为预测服务使用的模型名<br>`version`为预测模型的版本<br>`batch_size`为预测批次大小
|modules_info|PaddleHub Serving预安装模型,以字典列表形式列出,key为模型名称。其中:<br>`init_args`为模型加载时输入的参数,等同于`paddlehub.Module(**init_args)`<br>`predict_args`为模型预测时输入的参数,以`lac`为例,等同于`lac.analysis_lexical(**predict_args)`
|port|服务端口,默认为8866|
|use_gpu|使用GPU进行预测,必须安装paddlepaddle-gpu|
|use_multiprocess|是否启用并发方式,默认为单进程方式,推荐多核CPU机器使用此方式<br>*`Windows操作系统只支持单进程方式`*|
|use_multiprocess|是否启用并发方式,默认为单进程方式,推荐多核CPU机器使用此方式<br>*`Windows操作系统只支持单进程方式`*|
|workers|启动的并发任务数,在并发模式下才生效,默认为`2*cpu_count-1`,其中`cpu_count`代表CPU的核数|
### Step2:访问服务端
在使用PaddleHub Serving部署服务端的模型预测服务后,就可以在客户端访问预测接口以获取结果了,接口url格式为:
http://0.0.0.0:8866/predict/<CATEGORY\>/\<MODULE>
http://127.0.0.1:8866/predict/<CATEGORY\>/\<MODULE>
其中,\<CATEGORY>为text或image,与模型种类对应,\<MODULE>为模型名。
通过发送一个POST请求,即可获取预测结果,下面我们将展示一个具体的demo,以说明使用PaddleHub Serving部署和使用流程。
### Step3:利用PaddleHub Serving进行个性化开发
使用PaddleHub Serving进行模型服务部署后,可以利用得到的接口进行开发,如对外提供web服务,或接入到应用程序中,以降低客户端预测压力,提高性能,下面展示了一个web页面demo:
......@@ -85,6 +98,17 @@ http://0.0.0.0:8866/predict/<CATEGORY\>/\<MODULE>
</p>
### Step4:关闭serving
使用关闭命令即可关闭启动的serving,
```shell
$ hub serving stop --port XXXX
```
**参数**
|参数|用途|
|-|-|
|--port/-p|指定要关闭的服务端口,默认为8866|
## Demo——部署一个在线lac分词服务
### Step1:部署lac在线服务
......@@ -101,16 +125,20 @@ $ hub serving start -c serving_config.json
其中`serving_config.json`的内容如下:
```json
{
"modules_info": [
{
"module": "lac",
"version": "1.0.0",
"batch_size": 1
}
],
"use_gpu": false,
"modules_info": {
"lac": {
"init_args": {
"version": "1.1.0"
},
"predict_args": {
"batch_size": 1,
"use_gpu": false
}
}
},
"port": 8866,
"use_multiprocess": false
"use_multiprocess": false,
"workers": 2
}
```
启动成功界面如图:
......@@ -121,7 +149,7 @@ $ hub serving start -c serving_config.json
</p>
这样我们就在8866端口部署了lac的在线分词服务。
这样我们就在8866端口成功部署了lac的在线分词服务。
*此处warning为Flask提示,不影响使用*
### Step2:访问lac预测接口
......@@ -139,7 +167,7 @@ if __name__ == "__main__":
text_list = ["今天是个好日子", "天气预报说今天要下雨"]
text = {"text": text_list}
# 指定预测方法为lac并发送post请求
url = "http://0.0.0.0:8866/predict/text/lac"
url = "http://127.0.0.1:8866/predict/text/lac"
r = requests.post(url=url, data=text)
# 打印预测结果
......@@ -171,6 +199,22 @@ if __name__ == "__main__":
}
```
### Step3:停止serving服务
由于启动时我们使用了默认的服务端口8866,则对应的关闭命令为:
```shell
$ hub serving stop --port 8866
```
或不指定关闭端口,则默认为8866。
```shell
$ hub serving stop
```
等待serving清理服务后,提示:
```shell
$ PaddleHub Serving will stop.
```
则serving服务已经停止。
此Demo的具体信息和代码请参见[LAC Serving](../../demo/serving/module_serving/lexical_analysis_lac)。另外,下面展示了一些其他的一键服务部署Demo。
## Demo——其他模型的一键部署服务
......@@ -205,9 +249,42 @@ if __name__ == "__main__":
&emsp;&emsp;该示例展示了利用deeplabv3p_xception65_humanseg完成图像分割服务化部署和在线预测,获取识别结果和分割后的图像。
* [中文情感分析-基于simnet_bow](../../demo/serving/module_serving/semantic_model_simnet_bow)
* [中文情感分析-基于senta_lstm](../../demo/serving/module_serving/sentiment_analysis_senta_lstm)
&emsp;&emsp;该示例展示了利用senta_lstm完成中文文本情感分析服务化部署和在线预测,获取文本的情感分析结果。
## 客户端请求新版模型的方式
对某些新版模型,客户端请求方式有所变化,更接近本地预测的请求方式,以降低学习成本。
以lac(2.1.0)为例,使用上述方法进行请求将提示:
```python
{
"Warnning": "This usage is out of date, please use 'application/json' as content-type to post to /predict/lac. See 'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details."
}
```
对于lac(2.1.0),请求的方式如下:
```python
# coding: utf8
import requests
import json
if __name__ == "__main__":
# 指定用于预测的文本并生成字典{"text": [text_1, text_2, ... ]}
text = ["今天是个好日子", "天气预报说今天要下雨"]
# 以key的方式指定text传入预测方法的时的参数,此例中为"data"
# 对应本地部署,则为lac.analysis_lexical(texts=[text1, text2])
data = {"texts": text, "batch_size": 2}
# 指定预测方法为lac并发送post请求
url = "http://127.0.0.1:8866/predict/lac"
# 指定post请求的headers为application/json方式
headers = {"Content-Type": "application/json"}
r = requests.post(url=url, headers=headers, data=json.dumps(data))
# 打印预测结果
print(json.dumps(r.json(), indent=4, ensure_ascii=False))
```
此Demo的具体信息和代码请参见[LAC Serving_2.1.0](../../demo/serving/module_serving/lexical_analysis_lac/lac_2.1.0_serving_demo.py)
## Bert Service
除了预训练模型一键服务部署功能之外,PaddleHub Serving还具有`Bert Service`功能,支持ernie_tiny、bert等模型快速部署,对外提供可靠的在线embedding服务,具体信息请参见[Bert Service](./bert_service.md)
......@@ -11,10 +11,11 @@
命令行工具<cmdintro>
自定义数据<how_to_load_data>
Fine-tune模型转化为PaddleHub Module<finetuned_model_to_module.md>
自定义任务<how_to_define_task>
服务化部署<serving>
文本Embedding服务<bert_service>
语义相似度计算<sentence_sim>
ULMFit优化策略<strategy_exp>
超参优化<autofinetune>
Hook机制<hook>
\ No newline at end of file
Hook机制<hook>
......@@ -63,3 +63,5 @@ from .finetune.strategy import ULMFiTStrategy
from .finetune.strategy import CombinedStrategy
from .autofinetune.evaluator import report_final_result
from .module.nlp_module import NLPPredictionModule, TransformerModule
......@@ -202,7 +202,7 @@ class ServingCommand(BaseCommand):
module=key,
version=init_args.get("version", "0.0.0")).start()
if "dir" not in init_args:
if "directory" not in init_args:
init_args.update({"name": key})
m = hub.Module(**init_args)
method_name = m.serving_func_name
......@@ -222,6 +222,7 @@ class ServingCommand(BaseCommand):
def start_app_with_file(self):
port = self.args.config.get("port", 8866)
self.args.port = port
if ServingCommand.is_port_occupied("127.0.0.1", port) is True:
print("Port %s is occupied, please change it." % port)
return False
......@@ -240,6 +241,7 @@ class ServingCommand(BaseCommand):
def start_single_app_with_file(self):
port = self.args.config.get("port", 8866)
self.args.port = port
if ServingCommand.is_port_occupied("127.0.0.1", port) is True:
print("Port %s is occupied, please change it." % port)
return False
......@@ -313,6 +315,11 @@ class ServingCommand(BaseCommand):
with open(self.args.config, "r") as fp:
self.args.config = json.load(fp)
self.modules_info = self.args.config["modules_info"]
if isinstance(self.modules_info, list):
raise RuntimeError(
"This configuration method is outdated, see 'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details."
)
exit(1)
else:
raise RuntimeError("{} not exists.".format(self.args.config))
exit(1)
......@@ -329,9 +336,7 @@ class ServingCommand(BaseCommand):
"init_args": {
"version": version
},
"predict_args": {
"use_gpu": self.args.use_gpu
}
"predict_args": {}
}
})
......@@ -365,7 +370,7 @@ class ServingCommand(BaseCommand):
if self.args.use_multiprocess is True:
self.start_app_with_args(self.args.workers)
else:
self.start_app_with_args(1)
self.start_single_app_with_args()
@staticmethod
def show_help():
......@@ -413,11 +418,11 @@ class ServingCommand(BaseCommand):
except:
ServingCommand.show_help()
return False
self.link_module_info()
if self.args.sub_command == "start":
if self.args.bert_service == "bert_service":
ServingCommand.start_bert_serving(self.args)
elif self.args.bert_service is None:
self.link_module_info()
self.start_serving()
else:
ServingCommand.show_help()
......
......@@ -127,6 +127,8 @@ class ShowCommand(BaseCommand):
cwd = os.getcwd()
module_dir = default_module_manager.search_module(module_name)
module_dir = (os.path.join(cwd, module_name),
None) if not module_dir else module_dir
if not module_dir or not os.path.exists(module_dir[0]):
print("%s is not existed!" % module_name)
return True
......
......@@ -18,6 +18,9 @@ from __future__ import division
from __future__ import print_function
from paddlehub.common.utils import is_windows
from paddlehub.common.utils import sort_version_key
from paddlehub.common.utils import strflist_version
from functools import cmp_to_key
linux_color_dict = {
"white": "\033[1;37m%s\033[0m",
......@@ -146,3 +149,27 @@ class TablePrinter(object):
def get_text(self):
self.add_horizontal_line()
return self.text
def paint_modules_info(module_versions_info):
if is_windows():
placeholders = [20, 8, 14, 14]
else:
placeholders = [30, 8, 16, 16]
tp = TablePrinter(
titles=["ResourceName", "Version", "PaddlePaddle", "PaddleHub"],
placeholders=placeholders)
module_versions_info.sort(key=cmp_to_key(sort_version_key))
for resource_name, resource_version, paddle_version, \
hub_version in module_versions_info:
colors = ["yellow", None, None, None]
tp.add_line(
contents=[
resource_name, resource_version,
strflist_version(paddle_version),
strflist_version(hub_version)
],
colors=colors)
return tp.get_text()
......@@ -8,8 +8,8 @@ class WinLock(object):
pass
def __init__(self):
self.LOCK_EX = ""
self.LOCK_UN = ""
self.LOCK_EX = "WIN_LOCK_EX"
self.LOCK_UN = "WIN_LOCK_UN"
class Lock(object):
......
......@@ -279,3 +279,34 @@ def clone_program(origin_program, for_test=False):
).vars[name].stop_gradient = var.stop_gradient
return dest_program
def rename_var(block, old_name, new_name):
for op in block.ops:
for input_name in op.input_arg_names:
if input_name == old_name:
op._rename_input(old_name, new_name)
for output_name in op.output_arg_names:
if output_name == old_name:
op._rename_output(old_name, new_name)
block._rename_var(old_name, new_name)
def add_vars_prefix(program, prefix, vars=None, excludes=None):
block = program.global_block()
vars = list(vars) if vars else list(block.vars.keys())
vars = [var for var in vars if var not in excludes] if excludes else vars
for var in vars:
rename_var(block, var, prefix + var)
def remove_vars_prefix(program, prefix, vars=None, excludes=None):
block = program.global_block()
vars = [var for var in vars if var.startswith(prefix)] if vars else [
var for var in block.vars.keys() if var.startswith(prefix)
]
vars = [var for var in vars if var not in excludes] if excludes else vars
for var in vars:
rename_var(block, var, var.replace(prefix, "", 1))
......@@ -56,7 +56,8 @@ def version_compare(version1, version2):
def base64s_to_cvmats(base64s):
for index, value in enumerate(base64s):
value = bytes(value, encoding="utf8")
if isinstance(value, str):
value = bytes(value, encoding="utf8")
value = base64.b64decode(value)
value = np.fromstring(value, np.uint8)
value = cv2.imdecode(value, 1)
......@@ -65,6 +66,16 @@ def base64s_to_cvmats(base64s):
return base64s
def cvmats_to_base64s(cvmats):
for index, value in enumerate(cvmats):
retval, buffer = cv2.imencode('.jpg', value)
pic_str = base64.b64encode(buffer)
value = pic_str.decode()
cvmats[index] = value
return cvmats
def handle_mask_results(results, data_len):
result = []
if len(results) <= 0 and data_len != 0:
......
......@@ -120,9 +120,7 @@ def get_depth_parameter(main_program):
return updated_depth_params_dict
def set_gradual_unfreeze(main_program, unfreeze_depths):
depth_params_dict = get_depth_parameter(main_program)
def set_gradual_unfreeze(depth_params_dict, unfreeze_depths):
for depth in unfreeze_depths:
for index, param in enumerate(depth_params_dict[depth]):
depth_params_dict[depth][index].stop_gradient = False
......@@ -509,7 +507,7 @@ class CombinedStrategy(DefaultStrategy):
if self.max_depth > 0 and self.epoch <= self.scheduler[
"gradual_unfreeze"]["blocks"]:
set_gradual_unfreeze(
self.main_program,
depth_params_dict=self.depth_params_dict,
unfreeze_depths=self.
sorted_depth[:self.max_depth * self.epoch //
self.scheduler["gradual_unfreeze"]["blocks"]])
......
......@@ -30,12 +30,13 @@ if six.PY2:
else:
from inspect import getfullargspec as get_args
import numpy as np
import paddle
import paddle.fluid as fluid
from tb_paddle import SummaryWriter
import paddlehub as hub
from paddlehub.common.paddle_helper import dtype_map, clone_program
from paddlehub.common.utils import mkdir
from paddlehub.common.utils import mkdir, version_compare
from paddlehub.common.dir import tmp_dir
from paddlehub.common.logger import logger
from paddlehub.finetune.checkpoint import load_checkpoint, save_checkpoint
......@@ -807,10 +808,16 @@ class BaseTask(object):
# NOTE: current saved checkpoint machanism is not completed,
# it can't restore dataset training status
def save_checkpoint(self):
"""
save the program of the last step in training
"""
model_saved_dir = os.path.join(self.config.checkpoint_dir,
"step_%d" % self.current_step)
logger.info("Saving model checkpoint to {}".format(model_saved_dir))
self.save_inference_model(dirname=model_saved_dir)
# to resume traning by loading ckpt, it must be save program (save_persistables)
fluid.io.save_persistables(
self.exe, dirname=model_saved_dir, main_program=self.main_program)
save_checkpoint(
checkpoint_dir=self.config.checkpoint_dir,
current_epoch=self.current_epoch,
......@@ -926,6 +933,7 @@ class BaseTask(object):
with tmp_dir() as _dir:
self.save_inference_model(dirname=_dir)
predictor_config = fluid.core.AnalysisConfig(_dir)
predictor_config.disable_glog_info()
if self.config.use_cuda:
predictor_config.enable_use_gpu(100, 0)
......@@ -976,7 +984,7 @@ class BaseTask(object):
data,
load_best_model=True,
return_result=False,
accelerate_mode=False):
accelerate_mode=True):
"""
make prediction for the input data.
......@@ -989,6 +997,11 @@ class BaseTask(object):
Returns:
RunState: the running result of predict phase
"""
if not version_compare(paddle.__version__, "1.6.2") and accelerate_mode:
logger.warning(
"Fail to open predict accelerate mode as it does not support paddle < 1.6.2. Please update PaddlePaddle."
)
accelerate_mode = False
self.accelerate_mode = accelerate_mode
with self.phase_guard(phase="predict"):
......
......@@ -30,10 +30,12 @@ import paddlehub as hub
from paddlehub.common import utils
from paddlehub.common.downloader import default_downloader
from paddlehub.common.dir import MODULE_HOME
from paddlehub.common.cml_utils import TablePrinter
from paddlehub.common.cml_utils import paint_modules_info
from paddlehub.common.logger import logger
from paddlehub.common import tmp_dir
from paddlehub.module import module_desc_pb2
from paddlehub.version import hub_version as sys_hub_verion
from paddle import __version__ as sys_paddle_version
class LocalModuleManager(object):
......@@ -55,9 +57,12 @@ class LocalModuleManager(object):
desc.ParseFromString(fp.read())
info['version'] = desc.attr.map.data["module_info"].map.data[
"version"].s
info['name'] = desc.attr.map.data["module_info"].map.data[
"name"].s
return True, info
else:
module_file = os.path.join(module_path, 'module.py')
module_file = os.path.realpath(
os.path.join(module_path, 'module.py'))
if os.path.exists(module_file):
basename = os.path.split(module_path)[-1]
dirname = os.path.join(
......@@ -68,11 +73,15 @@ class LocalModuleManager(object):
for _item, _cls in inspect.getmembers(
_module, inspect.isclass):
_item = _module.__dict__[_item]
if issubclass(_item, hub.Module):
_file = os.path.realpath(
sys.modules[_item.__module__].__file__)
if issubclass(
_item,
hub.Module) and _file.startswith(module_file):
version = _item._version
break
sys.path.pop(0)
return True, {'version': version}
return True, {'version': version, 'name': _item._name}
logger.warning(
"%s does not exist, the module will be reinstalled" %
desc_pb_path)
......@@ -87,9 +96,13 @@ class LocalModuleManager(object):
for sub_dir_name in os.listdir(self.local_modules_dir):
sub_dir_path = os.path.join(self.local_modules_dir, sub_dir_name)
if os.path.isdir(sub_dir_path):
if "-" in sub_dir_path:
new_sub_dir_path = sub_dir_path.replace("-", "_")
shutil.move(sub_dir_path, new_sub_dir_path)
sub_dir_path = new_sub_dir_path
valid, info = self.check_module_valid(sub_dir_path)
if valid:
module_name = sub_dir_name
module_name = info['name']
self.modules_dict[module_name] = (sub_dir_path,
info['version'])
return self.modules_dict
......@@ -135,40 +148,20 @@ class LocalModuleManager(object):
return False, tips, None
module_versions_info = hub.HubServer().search_module_info(
module_name)
if module_versions_info is not None and len(
module_versions_info) > 0:
if utils.is_windows():
placeholders = [20, 8, 14, 14]
else:
placeholders = [30, 8, 16, 16]
tp = TablePrinter(
titles=[
"ResourceName", "Version", "PaddlePaddle",
"PaddleHub"
],
placeholders=placeholders)
module_versions_info.sort(
key=cmp_to_key(utils.sort_version_key))
for resource_name, resource_version, paddle_version, \
hub_version in module_versions_info:
colors = ["yellow", None, None, None]
tp.add_line(
contents=[
resource_name, resource_version,
utils.strflist_version(paddle_version),
utils.strflist_version(hub_version)
],
colors=colors)
tips = "The version of PaddlePaddle or PaddleHub " \
"can not match module, please upgrade your " \
"PaddlePaddle or PaddleHub according to the form " \
"below." + tp.get_text()
if module_versions_info is None:
tips = "Can't find module %s, please check your spelling." \
% (module_name)
elif module_version is not None and module_version not in [
item[1] for item in module_versions_info
]:
tips = "Can't find module %s with version %s, all versions are listed below." \
% (module_name, module_version)
tips += paint_modules_info(module_versions_info)
else:
tips = "Can't find module %s" % module_name
if module_version:
tips += " with version %s" % module_version
tips = "The version of PaddlePaddle(%s) or PaddleHub(%s) can not match module, please upgrade your PaddlePaddle or PaddleHub according to the form below." \
% (sys_paddle_version, sys_hub_verion)
tips += paint_modules_info(module_versions_info)
return False, tips, None
result, tips, module_zip_file = default_downloader.download_file(
......@@ -179,7 +172,7 @@ class LocalModuleManager(object):
print_progress=True)
result, tips, module_dir = default_downloader.uncompress(
file=module_zip_file,
dirname=MODULE_HOME,
dirname=os.path.join(_dir, "tmp_module"),
delete_file=True,
print_progress=True)
......@@ -190,19 +183,25 @@ class LocalModuleManager(object):
module_dir = os.path.join(_dir, file_names[0])
for index, file_name in enumerate(file_names):
tar.extract(file_name, _dir)
if module_dir:
if not module_name:
if "-" in module_dir:
new_module_dir = module_dir.replace("-", "_")
shutil.move(module_dir, new_module_dir)
module_dir = new_module_dir
module_name = hub.Module(directory=module_dir).name
if from_user_dir:
module_name = hub.Module(directory=module_dir).name
module_version = hub.Module(directory=module_dir).version
self.all_modules(update=False)
module_info = self.modules_dict.get(module_name, None)
if module_info:
module_dir = self.modules_dict[module_name][0]
module_tag = module_name if not module_version else '%s-%s' % (
module_name, module_version)
tips = "Module %s already installed in %s" % (module_tag,
module_dir)
return True, tips, self.modules_dict[module_name]
if module_version == module_info[1]:
module_dir = self.modules_dict[module_name][0]
module_tag = module_name if not module_version else '%s-%s' % (
module_name, module_version)
tips = "Module %s already installed in %s" % (
module_tag, module_dir)
return True, tips, self.modules_dict[module_name]
if module_dir:
if md5_value:
......@@ -211,7 +210,8 @@ class LocalModuleManager(object):
"w") as fp:
fp.write(md5_value)
save_path = os.path.join(MODULE_HOME, module_name)
save_path = os.path.join(MODULE_HOME,
module_name.replace("-", "_"))
if save_path != module_dir:
if os.path.exists(save_path):
shutil.rmtree(save_path)
......
#coding:utf-8
# coding:utf-8
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
......@@ -23,8 +23,6 @@ import sys
import functools
import inspect
import importlib
import tarfile
import six
import shutil
import paddle
......@@ -36,15 +34,10 @@ from paddlehub.common.dir import CACHE_HOME
from paddlehub.common.lock import lock
from paddlehub.common.logger import logger
from paddlehub.common.hub_server import CacheUpdater
from paddlehub.common import tmp_dir
from paddlehub.common.downloader import progress
from paddlehub.module import module_desc_pb2
from paddlehub.module.manager import default_module_manager
from paddlehub.module.checker import ModuleChecker
from paddlehub.module.signature import Signature, create_signature
from paddlehub.module.base_processor import BaseProcessor
from paddlehub.io.parser import yaml_parser
from paddlehub import version
# PaddleHub module dir name
ASSETS_DIRNAME = "assets"
......@@ -142,19 +135,28 @@ class Module(object):
if "_is_initialize" in self.__dict__ and self._is_initialize:
return
mod = self.__class__.__module__ + "." + self.__class__.__name__
if mod in _module_runnable_func:
_run_func_name = _module_runnable_func[mod]
self._run_func = getattr(self, _run_func_name)
else:
self._run_func = None
self._serving_func_name = _module_serving_func.get(mod, None)
self._code_version = "v2"
_run_func_name = self._get_func_name(self.__class__,
_module_runnable_func)
self._run_func = getattr(self,
_run_func_name) if _run_func_name else None
self._serving_func_name = self._get_func_name(self.__class__,
_module_serving_func)
self._directory = directory
self._initialize(**kwargs)
self._is_initialize = True
self._code_version = "v2"
def _get_func_name(self, current_cls, module_func_dict):
mod = current_cls.__module__ + "." + current_cls.__name__
if mod in module_func_dict:
_func_name = module_func_dict[mod]
return _func_name
elif current_cls.__bases__:
for base_class in current_cls.__bases__:
return self._get_func_name(base_class, module_func_dict)
else:
return None
@classmethod
def init_with_name(cls, name, version=None, **kwargs):
fp_lock = open(os.path.join(CACHE_HOME, name), "a")
......@@ -190,7 +192,10 @@ class Module(object):
_module = importlib.import_module("{}.module".format(basename))
for _item, _cls in inspect.getmembers(_module, inspect.isclass):
_item = _module.__dict__[_item]
if issubclass(_item, Module):
_file = os.path.realpath(sys.modules[_item.__module__].__file__)
_module_path = os.path.realpath(
os.path.join(directory, "module.py"))
if issubclass(_item, Module) and _file.startswith(_module_path):
user_module = _item(directory=directory, **kwargs)
break
sys.path.pop(0)
......
# coding:utf-8
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# 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.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import ast
import json
import os
import re
import six
import numpy as np
import paddle.fluid as fluid
from paddlehub.common import paddle_helper
from paddle.fluid.core import PaddleTensor, AnalysisConfig, create_paddle_predictor
import paddlehub as hub
from paddlehub.common.logger import logger
from paddlehub.common.utils import sys_stdin_encoding
from paddlehub.io.parser import txt_parser
from paddlehub.module.module import runnable
class DataFormatError(Exception):
def __init__(self, *args):
self.args = args
class NLPBaseModule(hub.Module):
def _initialize(self):
"""
initialize with the necessary elements
This method must be overrided.
"""
raise NotImplementedError()
def get_vocab_path(self):
"""
Get the path to the vocabulary whih was used to pretrain
Returns:
self.vocab_path(str): the path to vocabulary
"""
return self.vocab_path
class NLPPredictionModule(NLPBaseModule):
def _set_config(self):
"""
predictor config setting
"""
cpu_config = AnalysisConfig(self.pretrained_model_path)
cpu_config.disable_glog_info()
cpu_config.disable_gpu()
self.cpu_predictor = create_paddle_predictor(cpu_config)
try:
_places = os.environ["CUDA_VISIBLE_DEVICES"]
int(_places[0])
use_gpu = True
except:
use_gpu = False
if use_gpu:
gpu_config = AnalysisConfig(self.pretrained_model_path)
gpu_config.disable_glog_info()
gpu_config.enable_use_gpu(memory_pool_init_size_mb=500, device_id=0)
self.gpu_predictor = create_paddle_predictor(gpu_config)
def texts2tensor(self, texts):
"""
Tranform the texts(dict) to PaddleTensor
Args:
texts(list): each element is a dict that must have a named 'processed' key whose value is word_ids, such as
texts = [{'processed': [23, 89, 43, 906]}]
Returns:
tensor(PaddleTensor): tensor with texts data
"""
lod = [0]
data = []
for i, text in enumerate(texts):
data += text['processed']
lod.append(len(text['processed']) + lod[i])
tensor = PaddleTensor(np.array(data).astype('int64'))
tensor.name = "words"
tensor.lod = [lod]
tensor.shape = [lod[-1], 1]
return tensor
def to_unicode(self, texts):
"""
Convert each element's type(str) of texts(list) to unicode in python2.7
Args:
texts(list): each element's type is str in python2.7
Returns:
texts(list): each element's type is unicode in python2.7
"""
if six.PY2:
unicode_texts = []
for text in texts:
if isinstance(text, six.string_types):
unicode_texts.append(
text.decode(sys_stdin_encoding()).decode("utf8"))
else:
unicode_texts.append(text)
texts = unicode_texts
return texts
@runnable
def run_cmd(self, argvs):
"""
Run as a command
"""
self.parser = argparse.ArgumentParser(
description='Run the %s module.' % self.name,
prog='hub run %s' % self.name,
usage='%(prog)s',
add_help=True)
self.arg_input_group = self.parser.add_argument_group(
title="Input options", description="Input data. Required")
self.arg_config_group = self.parser.add_argument_group(
title="Config options",
description=
"Run configuration for controlling module behavior, not required.")
self.add_module_config_arg()
self.add_module_input_arg()
args = self.parser.parse_args(argvs)
try:
input_data = self.check_input_data(args)
except DataFormatError and RuntimeError:
self.parser.print_help()
return None
results = self.predict(
texts=input_data, use_gpu=args.use_gpu, batch_size=args.batch_size)
return results
def add_module_config_arg(self):
"""
Add the command config options
"""
self.arg_config_group.add_argument(
'--use_gpu',
type=ast.literal_eval,
default=False,
help="whether use GPU for prediction")
self.arg_config_group.add_argument(
'--batch_size',
type=int,
default=1,
help="batch size for prediction")
def add_module_input_arg(self):
"""
Add the command input options
"""
self.arg_input_group.add_argument(
'--input_file',
type=str,
default=None,
help="file contain input data")
self.arg_input_group.add_argument(
'--input_text', type=str, default=None, help="text to predict")
def check_input_data(self, args):
input_data = []
if args.input_file:
if not os.path.exists(args.input_file):
print("File %s is not exist." % args.input_file)
raise RuntimeError
else:
input_data = txt_parser.parse(args.input_file, use_strip=True)
elif args.input_text:
if args.input_text.strip() != '':
if six.PY2:
input_data = [
args.input_text.decode(
sys_stdin_encoding()).decode("utf8")
]
else:
input_data = [args.input_text]
else:
print(
"ERROR: The input data is inconsistent with expectations.")
if input_data == []:
print("ERROR: The input data is inconsistent with expectations.")
raise DataFormatError
return input_data
class _TransformerEmbeddingTask(hub.BaseTask):
def __init__(self,
pooled_feature,
seq_feature,
feed_list,
data_reader,
config=None):
main_program = pooled_feature.block.program
super(_TransformerEmbeddingTask, self).__init__(
main_program=main_program,
data_reader=data_reader,
feed_list=feed_list,
config=config,
metrics_choices=[])
self.pooled_feature = pooled_feature
self.seq_feature = seq_feature
def _build_net(self):
return [self.pooled_feature, self.seq_feature]
def _postprocessing(self, run_states):
results = []
for batch_state in run_states:
batch_result = batch_state.run_results
batch_pooled_features = batch_result[0]
batch_seq_features = batch_result[1]
for i in range(len(batch_pooled_features)):
results.append(
[batch_pooled_features[i], batch_seq_features[i]])
return results
class TransformerModule(NLPBaseModule):
"""
Tranformer Module base class can be used by BERT, ERNIE, RoBERTa and so on.
"""
def init_pretraining_params(self, exe, pretraining_params_path,
main_program):
assert os.path.exists(
pretraining_params_path
), "[%s] cann't be found." % pretraining_params_path
def existed_params(var):
if not isinstance(var, fluid.framework.Parameter):
return False
return os.path.exists(
os.path.join(pretraining_params_path, var.name))
fluid.io.load_vars(
exe,
pretraining_params_path,
main_program=main_program,
predicate=existed_params)
logger.info("Load pretraining parameters from {}.".format(
pretraining_params_path))
def param_prefix(self):
return "@HUB_%s@" % self.name
def context(
self,
max_seq_len=128,
trainable=True,
):
"""
get inputs, outputs and program from pre-trained module
Args:
max_seq_len (int): the max sequence length
trainable (bool): optimizing the pre-trained module params during training or not
Returns: inputs, outputs, program.
The inputs is a dict with keys named input_ids, position_ids, segment_ids, input_mask and task_ids
The outputs is a dict with two keys named pooled_output and sequence_output.
"""
assert max_seq_len <= self.MAX_SEQ_LEN and max_seq_len >= 1, "max_seq_len({}) should be in the range of [1, {}]".format(
max_seq_len, self.MAX_SEQ_LEN)
module_program = fluid.Program()
startup_program = fluid.Program()
with fluid.program_guard(module_program, startup_program):
with fluid.unique_name.guard("@HUB_%s@" % self.name):
input_ids = fluid.layers.data(
name='input_ids',
shape=[-1, max_seq_len, 1],
dtype='int64',
lod_level=0)
position_ids = fluid.layers.data(
name='position_ids',
shape=[-1, max_seq_len, 1],
dtype='int64',
lod_level=0)
segment_ids = fluid.layers.data(
name='segment_ids',
shape=[-1, max_seq_len, 1],
dtype='int64',
lod_level=0)
input_mask = fluid.layers.data(
name='input_mask',
shape=[-1, max_seq_len, 1],
dtype='float32',
lod_level=0)
pooled_output, sequence_output = self.net(
input_ids, position_ids, segment_ids, input_mask)
inputs = {
'input_ids': input_ids,
'position_ids': position_ids,
'segment_ids': segment_ids,
'input_mask': input_mask,
}
outputs = {
"pooled_output": pooled_output,
"sequence_output": sequence_output,
0: pooled_output,
1: sequence_output
}
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# To be compatible with the module v1
vars = filter(lambda var: "tmp" not in var,
list(module_program.global_block().vars.keys())[4:])
paddle_helper.add_vars_prefix(
program=module_program, prefix=self.param_prefix(), vars=vars)
self.init_pretraining_params(
exe, self.params_path, main_program=module_program)
self.params_layer = {}
for param in module_program.global_block().iter_parameters():
param.trainable = trainable
match = re.match(r'.*layer_(\d+).*', param.name)
if match:
# layer num begins from 0
layer = match.group(1)
self.params_layer[param.name] = int(layer)
return inputs, outputs, module_program
def get_embedding(self, texts, use_gpu=False, batch_size=1):
"""
get pooled_output and sequence_output for input texts.
Warnings: this method depends on Paddle Inference Library, it may not work properly in PaddlePaddle <= 1.6.2.
Args:
texts (list): each element is a text sample, each sample include text_a and text_b where text_b can be omitted.
for example: [[sample0_text_a, sample0_text_b], [sample1_text_a, sample1_text_b], ...]
use_gpu (bool): use gpu or not, default False.
batch_size (int): the data batch size, default 1.
Returns:
pooled_outputs(list): its element is a numpy array, the first feature of each text sample.
sequence_outputs(list): its element is a numpy array, the whole features of each text sample.
"""
if not hasattr(
self, "emb_job"
) or self.emb_job["batch_size"] != batch_size or self.emb_job[
"use_gpu"] != use_gpu:
inputs, outputs, program = self.context(
trainable=True, max_seq_len=self.MAX_SEQ_LEN)
reader = hub.reader.ClassifyReader(
dataset=None,
vocab_path=self.get_vocab_path(),
max_seq_len=self.MAX_SEQ_LEN,
sp_model_path=self.get_spm_path() if hasattr(
self, "get_spm_path") else None,
word_dict_path=self.get_word_dict_path() if hasattr(
self, "word_dict_path") else None)
feed_list = [
inputs["input_ids"].name,
inputs["position_ids"].name,
inputs["segment_ids"].name,
inputs["input_mask"].name,
]
pooled_feature, seq_feature = outputs["pooled_output"], outputs[
"sequence_output"]
config = hub.RunConfig(
use_data_parallel=False,
use_cuda=use_gpu,
batch_size=batch_size)
self.emb_job = {}
self.emb_job["task"] = _TransformerEmbeddingTask(
pooled_feature=pooled_feature,
seq_feature=seq_feature,
feed_list=feed_list,
data_reader=reader,
config=config,
)
self.emb_job["batch_size"] = batch_size
self.emb_job["use_gpu"] = use_gpu
return self.emb_job["task"].predict(
data=texts, return_result=True, accelerate_mode=True)
def get_spm_path(self):
if hasattr(self, "spm_path"):
return self.spm_path
else:
return None
def get_word_dict_path(self):
if hasattr(self, "word_dict_path"):
return self.word_dict_path
else:
return None
def get_params_layer(self):
if not hasattr(self, "params_layer"):
raise AttributeError(
"The module context has not been initialized. "
"Please call context() before using get_params_layer")
return self.params_layer
......@@ -23,24 +23,52 @@ import logging
import glob
def gen_result(status, msg, data):
return {"status": status, "msg": msg, "results": data}
def predict_v2(module_info, input):
serving_method_name = module_info["method_name"]
serving_method = getattr(module_info["module"], serving_method_name)
predict_args = module_info["predict_args"]
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": input})
for item in serving_method.__code__.co_varnames:
if item in module_info.keys():
predict_args.update({item: module_info[item]})
output = serving_method(**predict_args)
return {"results": output}
try:
output = serving_method(**predict_args)
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return gen_result("-1", "Please check data format!", "")
return gen_result("0", "", output)
def predict_v2_advanced(module_info, input):
serving_method_name = module_info["method_name"]
serving_method = getattr(module_info["module"], serving_method_name)
predict_args = module_info["predict_args"].copy()
predict_args.update(input)
for item in serving_method.__code__.co_varnames:
if item in module_info.keys():
predict_args.update({item: module_info[item]})
try:
output = serving_method(**predict_args)
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return gen_result("-1", "Please check data format!", "")
return gen_result("0", "", output)
def predict_nlp(module_info, input_text, req_id, extra=None):
method_name = module_info["method_name"]
predict_method = getattr(module_info["module"], method_name)
predict_args = {"data": input_text}
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": input_text})
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
predict_args.update({"sign_name": method_name})
......@@ -57,20 +85,22 @@ def predict_nlp(module_info, input_text, req_id, extra=None):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"results": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
user_dict = extra.get("user_dict", [])
for item in user_dict:
if os.path.exists(item):
os.remove(item)
return {"results": res}
return gen_result("0", "", res)
def predict_classification(module_info, input_img, id, extra={}):
method_name = module_info["method_name"]
module = module_info["module"]
predict_method = getattr(module, method_name)
predict_args = {"data": {"image": input_img}}
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": {"image": input_img}})
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
predict_args.update({"sign_name": method_name})
......@@ -82,19 +112,21 @@ def predict_classification(module_info, input_img, id, extra={}):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"result": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
for item in input_img:
if os.path.exists(item):
os.remove(item)
return results
return gen_result("0", "", str(results))
def predict_gan(module_info, input_img, id, extra={}):
method_name = module_info["method_name"]
module = module_info["module"]
predict_method = getattr(module, method_name)
predict_args = {"data": {"image": input_img}}
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": {"image": input_img}})
predict_args["data"].update(extra)
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
......@@ -108,7 +140,7 @@ def predict_gan(module_info, input_img, id, extra={}):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"result": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
base64_list = []
results_pack = []
......@@ -127,10 +159,10 @@ def predict_gan(module_info, input_img, id, extra={}):
results_pack.append(results[index])
os.remove(item)
os.remove(output_file)
return results_pack
return gen_result("0", "", str(results_pack))
def predict_mask(module_info, input_img, id, extra=None, r_img=True):
def predict_mask(module_info, input_img, id, extra=None, r_img=False):
output_folder = "detection_result"
method_name = module_info["method_name"]
module = module_info["module"]
......@@ -142,8 +174,10 @@ def predict_mask(module_info, input_img, id, extra=None, r_img=True):
data.update(input_img)
if extra is not None:
data.update(extra)
r_img = True if "r_img" in extra.keys() else False
predict_args = {"data": data}
r_img = True if "visual_result" in extra.keys() else False
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": data})
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
predict_args.update({"sign_name": method_name})
......@@ -156,7 +190,7 @@ def predict_mask(module_info, input_img, id, extra=None, r_img=True):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"result": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
base64_list = []
results_pack = []
......@@ -198,7 +232,7 @@ def predict_mask(module_info, input_img, id, extra=None, r_img=True):
else:
results_pack = results
return results_pack
return gen_result("0", "", str(results_pack))
def predict_object_detection(module_info, input_img, id, extra={}):
......@@ -206,7 +240,10 @@ def predict_object_detection(module_info, input_img, id, extra={}):
method_name = module_info["method_name"]
module = module_info["module"]
predict_method = getattr(module, method_name)
predict_args = {"data": {"image": input_img}}
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": {"image": input_img}})
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
predict_args.update({"sign_name": method_name})
......@@ -218,7 +255,7 @@ def predict_object_detection(module_info, input_img, id, extra={}):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"result": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
base64_list = []
results_pack = []
......@@ -236,14 +273,17 @@ def predict_object_detection(module_info, input_img, id, extra={}):
results_pack.append(results[index])
os.remove(item)
os.remove(os.path.join(output_folder, item))
return results_pack
return gen_result("0", "", str(results_pack))
def predict_semantic_segmentation(module_info, input_img, id, extra={}):
method_name = module_info["method_name"]
module = module_info["module"]
predict_method = getattr(module, method_name)
predict_args = {"data": {"image": input_img}}
predict_args = module_info["predict_args"].copy()
predict_args.update({"data": {"image": input_img}})
if isinstance(predict_method, functools.partial):
predict_method = predict_method.func
predict_args.update({"sign_name": method_name})
......@@ -255,7 +295,7 @@ def predict_semantic_segmentation(module_info, input_img, id, extra={}):
except Exception as err:
curr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print(curr, " - ", err)
return {"result": "Please check data format!"}
return gen_result("-1", "Please check data format!", "")
finally:
base64_list = []
results_pack = []
......@@ -277,7 +317,7 @@ def predict_semantic_segmentation(module_info, input_img, id, extra={}):
os.remove(item)
if output_file_path != "":
os.remove(output_file_path)
return results_pack
return gen_result("0", "", str(results_pack))
def create_app(init_flag=False, configs=None):
......@@ -318,6 +358,20 @@ def create_app(init_flag=False, configs=None):
def predict_image(module_name):
if request.path.split("/")[-1] not in cv_module_info.modules_info:
return {"error": "Module {} is not available.".format(module_name)}
module_info = cv_module_info.get_module_info(module_name)
if module_info["code_version"] == "v2":
results = {}
# results = predict_v2(module_info, inputs)
results.update({
"Warnning":
"This usage is out of date, please "
"use 'application/json' as "
"content-type to post to "
"/predict/%s. See "
"'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details."
% (module_name)
})
return gen_result("-1", results, "")
req_id = request.data.get("id")
img_base64 = request.form.getlist("image")
extra_info = {}
......@@ -336,7 +390,7 @@ def create_app(init_flag=False, configs=None):
for item in img_base64:
ext = item.split(";")[0].split("/")[-1]
if ext not in ["jpeg", "jpg", "png"]:
return {"result": "Unrecognized file type"}
return gen_result("-1", "Unrecognized file type", "")
filename = req_id + "_" \
+ utils.md5(str(time.time()) + item[0:20]) \
+ "." \
......@@ -351,10 +405,7 @@ def create_app(init_flag=False, configs=None):
file_name = req_id + "_" + item.filename
item.save(file_name)
file_name_list.append(file_name)
# module = default_module_manager.get_module(module_name)
# predict_func_name = cv_module_info.get_module_info(module_name)[
# "method_name"]
module_info = cv_module_info.get_module_info(module_name)
module = module_info["module"]
predict_func_name = cv_module_info.cv_module_method.get(module_name, "")
if predict_func_name != "":
......@@ -367,13 +418,18 @@ def create_app(init_flag=False, configs=None):
if extra_info == {}:
extra_info = None
results = predict_func(module_info, file_name_list, req_id, extra_info)
r = {"results": str(results)}
return r
return results
@app_instance.route("/predict/text/<module_name>", methods=["POST"])
def predict_text(module_name):
if request.path.split("/")[-1] not in nlp_module_info.nlp_modules:
return {"error": "Module {} is not available.".format(module_name)}
module_info = nlp_module_info.get_module_info(module_name)
if module_info["code_version"] == "v2":
results = "This usage is out of date, please use 'application/json' as content-type to post to /predict/%s. See 'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details." % (
module_name)
return gen_result("-1", results, "")
req_id = request.data.get("id")
inputs = {}
for item in list(request.form.keys()):
......@@ -385,16 +441,30 @@ def create_app(init_flag=False, configs=None):
file_name = req_id + "_" + file.filename
files[file_key].append(file_name)
file.save(file_name)
module_info = nlp_module_info.get_module_info(module_name)
if module_info["code_version"] == "v2":
results = predict_v2(module_info, inputs)
results = predict_nlp(
module_info=module_info,
input_text=inputs,
req_id=req_id,
extra=files)
return results
@app_instance.route("/predict/<module_name>", methods=["POST"])
def predict_modulev2(module_name):
if module_name in nlp_module_info.nlp_modules:
module_info = nlp_module_info.get_module_info(module_name)
elif module_name in cv_module_info.cv_modules:
module_info = cv_module_info.get_module_info(module_name)
else:
results = predict_nlp(
module_info=module_info,
input_text=inputs,
req_id=req_id,
extra=files)
msg = "Module {} is not available.".format(module_name)
return gen_result("-1", msg, "")
inputs = request.json
if inputs is None:
results = "This usage is out of date, please use 'application/json' as content-type to post to /predict/%s. See 'https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.6/docs/tutorial/serving.md' for more details." % (
module_name)
return gen_result("-1", results, "")
results = predict_v2_advanced(module_info, inputs)
return results
return app_instance
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册