# 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](https://github.com/PaddlePaddle/PaddleHub/tree/release/v1.7/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:** 1. 执行类的初始化不能使用默认的__init__接口,而是应该重载实现_initialize接口。对象默认内置了directory属性,可以直接获取到Module所在路径。 2. 使用Fine-tune保存的模型预测时,无需加载数据集Dataset,即Reader中的dataset参数可为None。 #### 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) results = [run_state.run_results for run_state in run_states] prediction = [] for batch_result in results: # get predict index batch_result = np.argmax(batch_result, axis=2)[0] batch_result = batch_result.tolist() prediction += batch_result return prediction ``` #### 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) results = [run_state.run_results for run_state in run_states] prediction = [] for batch_result in results: # get predict index batch_result = np.argmax(batch_result[0], axis=1) batch_result = batch_result.tolist() prediction += batch_result return prediction ``` ### 完整代码 * [module.py](https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.7/demo/text_classification/finetuned_model_to_module/module.py) * [__init__.py](https://github.com/PaddlePaddle/PaddleHub/blob/release/v1.7/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天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]] predictions = ernie_tiny.predict(data=data) for index, text in enumerate(data): print("%s\tpredict=%s" % (data[index][0], predictions[index])) ``` ### 调用方法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天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]] predictions = ernie_tiny.predict(data=data) for index, text in enumerate(data): print("%s\tpredict=%s" % (data[index][0], predictions[index])) ``` ### 调用方法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天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]] predictions = ERNIETinyFinetuned.predict(data=data) for index, text in enumerate(data): print("%s\tpredict=%s" % (data[index][0], predictions[index])) ``` ### PaddleHub Serving调用方法 **第一步:启动预测服务** ```shell hub serving start -m ernie_tiny_finetuned ``` **第二步:发送请求,获取预测结果** 通过如下脚本既可以发送请求: ```python # coding: utf8 import requests import json # 待预测文本 texts = [["这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般"], ["交通方便;环境很好;服务态度很好 房间较小"], ["19天硬盘就罢工了~~~算上运来的一周都没用上15天~~~可就是不能换了~~~唉~~~~你说这算什么事呀~~~"]] # key为'data', 对应着预测接口predict的参数data data = {'data': texts} # 指定模型为ernie_tiny_finetuned并发送post请求,且请求的headers为application/json方式 url = "http://127.0.0.1:8866/predict/ernie_tiny_finetuned" 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)) ``` 关与PaddleHub Serving更多信息参见[Hub Serving教程](./serving.md)以及[Demo](https://github.com/PaddlePaddle/PaddleHub/tree/release/v1.7/demo/serving)