Flask搭建服务(二):实践从服务搭建到服务部署

1.服务搭建

功能要求:提供模型预测服务。预加载模型,

主要包含三个脚本 :

  1. index.py——提供服务,flask app所在。
  2. predict_server.py——实现模型预测功能
  3. client.py——用来发请求,测试服务是否可用

一 提供服务

index.py脚本:

# -*- encoding:utf-8 -*-
from flask import Flask, request, Response
from importlib import import_module
import sys, time, os, json, re
from collections import defaultdict
import logging
from logging.handlers import TimedRotatingFileHandler
import numpy as np
'''
第一部分:
初始化:所有的Flask都必须创建程序实例,
web服务器使用wsgi协议,把客户端所有的请求都转发给这个程序实例
程序实例是Flask的对象,一般情况下用如下方法实例化
Flask类只有一个必须指定的参数,即程序主模块或者包的名字,__name__是系统变量,该变量指的是本py文件的文件名
'''
app = Flask(__name__)
sys.path.append(os.getcwd())

'''
 第二部分,路由和视图函数:
 客户端发送url给web服务器,web服务器将url转发给flask程序实例,程序实例
 需要知道对于每一个url请求启动那一部分代码,所以保存了一个url和python函数的映射关系。
 处理url和函数之间关系的程序,称为路由
 在flask中,定义路由最简便的方式,是使用程序实例的app.route装饰器,把装饰的函数注册为路由
'''
@app.route('/multi_class', methods=['POST']) # multi_class可以叫任意的名字,功能的唯一标志;methods=['POST']这个表示发送请求需要post格式
def predict_multi_class():
    # 输出定义、输入判断
    res = defaultdict()
    res['score'] = 0.0
    res['label'] = '0'
    if request.json:
        jsonstr = request.json
    else:
        logger.error("please check the data format of request.json and the parameters in data")
        return res
    # 模型
    try:
        res = model_predict(jsonstr)
    except Exception as e:
        logger.exception(e)
    return Response(json.dumps(res))
#模型初始化,将其赋值给一个静态变量,这样只有在第一次加载时,耗时较多,其它请求,则不会再预加载模型了
def app_name_process_init(app_name):
    p = import_module('predict_server')
    if app_name == 'two_class':
        model_path = './saved_dict/20201126_193727.ckpt'
        predict_server = p.Predict_server(model_path, 21)
        description = {'0': '负', '1': '正'}
    return predict_server, description

def get_top_n(lists, n):
    sort_index = np.argsort(lists)
    n_index = sort_index[-n:]  # 因为是按照概率从大到小取n个
    indexs = list(reversed(n_index))
    return indexs

def model_predict(jsonstr):
    res = defaultdict()
    res['score'] = 0.0
    res['label'] = '0'
    try:
        pred_data = predict_server.predict_data_clean(jsonstr['text'])
        if len(jsonstr['text']) - len(pred_data) > 5:
            logger.info('len(jsonstr[text]) - len(pred_data) > 5')
        predict_score = predict_server.predict_torch(pred_data)
        out = predict_score.cpu().numpy()
        res['score'] = out
        logger.info("predict done")
    except OSError as err:
        logger.exception("OS error: ", err)
    except ValueError as err:
        logger.exception("input value error: ", err)
    except Exception as e:
        logger.exception(e)
    return res

def setup_log(log_name):
    #日志,将日志放在log文件夹下,日志按天存放,保留30天内的日志。
    logger = logging.getLogger(log_name)
    log_path = os.path.join('./log/', log_name)
    logger.setLevel(logging.INFO)
    file_handler = TimedRotatingFileHandler(
        filename=log_path, when="MIDNIGHT", interval=1, backupCount=30
    )
    file_handler.suffix = "%Y-%m-%d.log"
    file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}.log$")
    file_handler.setFormatter(
        logging.Formatter(
            "[%(asctime)s] [%(process)d] [%(levelname)s] - %(module)s.%(funcName)s (%(filename)s:%(lineno)d) - %(message)s"
        )
    )
    logger.addHandler(file_handler)
    return logger

'''
第三部分:程序实例用run方法启动flask集成的开发web服务器
__name__ == '__main__'是python常用的方法,表示只有直接启动本脚本时候,才用app.run方法
如果是其他脚本调用本脚本,程序假定父级脚本会启用不同的服务器,因此不用执行app.run()
服务器启动后,会启动轮询,等待并处理请求。轮询会一直请求,直到程序停止。
'''
if __name__ == '__main__':
    # log 格式
    logger = setup_log("pred_log") #  如果其他py文件想使用此配置日志,只需 logging.getLogger(日志的名字)  即可

    predict_server, description = app_name_process_init('multi_class')
    app.run(debug=True)

二 功能模块

predict_server.py脚本,两个功能:

  • 一个是初始化加载,给一个静态变量,这样下次调用时候,不会重新加载模型
  • 预测功能,predict_torch()函数的实现
# coding: UTF-8
import torch, time, logging
from transformers import BertForSequenceClassification, BertTokenizer

class Predict_server():
    def __init__(self, class_number=2):
        logger = logging.getLogger('pred_log')
        self.bert_name = 'bert-base-chinese'
        # 缺少判断: model_path是否存在
        self.model = BertForSequenceClassification.from_pretrained(self.bert_name, num_labels=class_number)
        logger.info('{0}---model init done'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        logger.info('{0}---tokenizer init done'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

    def predict_torch(self, pre_data):
        input_ids = self.tokenizer.encode(
            pre_data,
            add_special_tokens=True,  # 添加special tokens, 也就是CLS和SEP
            max_length=160,  # 设定最大文本长度
            pad_to_max_length=True,  # pad到最大的长度
            return_tensors='pt'  # 返回的类型为pytorch tensor
        )
        self.model.eval()
        with torch.no_grad():
            logits = self.model(input_ids, token_type_ids=None, attention_mask=(input_ids > 0))
            m = torch.nn.Sigmoid()
            out = m(logits[0])
        return out

3 请求测试

client.py脚本内容

# -*- encoding:utf-8 -*-
import requests,json
if __name__ == '__main__':
    headers = {'content-type': 'application/json'}
    pred_data = {'text': '说点什么?实在词穷', 'detail': 1}
    r = requests.post("http://127.0.0.1:5000/multi_class", data=json.dumps(pred_data), headers=headers)
    print(r.json())

遇到的bug:

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

原因:
代码中的业务逻辑,返回的是数值,list,其中元素是 float,不符合flask返回的形式,flask要求返回 字符串。

将return pred_score, pred_class

长这个 样子 :
----pred_score : [0.78471559 0.80732614 0.77324665 0.80003554 0.77968925 0.76409107
 0.8216269  0.79871303]
----pred_class : [1 1 1 1 1 1 1 1]

改为 :

return pred_score.tostring(), pred_class.tostring()

2.服务部署

“部署”,就是启动服务,让他能对外提供功能。

>python index.py  # 启动了服务

3.请求测试

> python client.py # 测试服务是否OK

app是flask的实例,功能就是接受来自web服务器的请求

  1. 浏览器将请求给web服务器,web服务器将请求给app ,
  2. app收到请求,通过路由找到对应的视图函数,然后将请求处理,得到一个响应response
  3. 然后app将响应返回给web服务器,
  4. web服务器返回给浏览器,
  5. 浏览器展示给用户观看,流程完毕。

参考:
1.https://blog.csdn.net/SelinaGu/article/details/103732131

2.https://blog.csdn.net/yelena_11/article/details/53404892

3.设置日志:https://www.cnblogs.com/xujunkai/p/12364619.html

4.request参数:https://www.cnblogs.com/android-it/p/9558751.htmlhttps://2.python-requests.org/zh_CN/latest/user/advanced.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值