pyinstaller + flask + tensorflow(keras) 打包部署

文章目录

 

一 、环境准备

打包部署环境:CentOS7 + Anaconda3.5.X
下载源后上传至~/目录配置系统源:

#!/usr/bin/bash
echo -e '\033[32m======= start ==========\033[0m'

cp CentOS7-Base-163.repo /etc/yum.repos.d/
cd /etc/yum.repos.d/
mv CentOS-Base.repo CentOS-Base.repo.bak
mv CentOS7-Base-163.repo CentOS-Base.repo
echo
echo "cp CentOS7-Base-163.repo /etc/yum.repos.d/"
echo "cd /etc/yum.repos.d/"
echo "mv CentOS-Base.repo CentOS-Base.repo.bak"
echo "mv CentOS7-Base-163.repo CentOS-Base.repo"
echo
echo -e '\033[32m======= doing...==========\033[0m'

echo "yum clean al"
echo "yum makecache"
echo "yum update"

yum clean all
sleep 1
yum makecache
sleep 1
yum update
sleep 1

echo -e '\033[32m======= done! ==========\033[0m'

 

关于Anaconda环境安装

#!/usr/bin/bash
echo -e '\033[32m======= first ==========\033[0m'
echo
echo "yum -y install swig gcc gcc-c++ kernel-devel"
echo

yum -y install swig gcc gcc-c++ kernel-devel


echo
echo -e '\033[32m======= second ==========\033[0m'
echo
echo "pip install oscrypto"
echo "pip install paramiko"
echo "pip install pyinstaller"
echo "pip install flask"
echo "pip install flask_restful"
echo "pip install pandas"
echo "pip install endesive"
echo "pip install opencv-python"
echo "pip install pdfplumber"
echo "pip install gunicorn"
echo "pip install gevent"
echo "pip install PyMuPDF==1.17.0"
echo

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple oscrypto
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple paramiko
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask_restful
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple endesive
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pdfplumber
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gunicorn
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gevent
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyMuPDF==1.17.0



echo -e '\033[32m======= third ==========\033[0m'
echo "pip install wrapt"
echo "pip install tensorflow & keras"

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade setuptools
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wrapt --ignore-installed
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tensorflow==2.2.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple keras
echo
echo -e '\033[32m======== end ==========\033[0m'

 

二、pyinstaller打包基于tensorflow 2.2.0的程序

参考文章:【Flask作为Web App部署Keras模型】
准备完服务的代码执行 pyinstaller app.py 打包服务。执行服务:/dist/app/app
缺少依赖模块解决方法:
添加hook文件:hook-tensorflow.py

from PyInstaller.utils.hooks import collect_data_files, collect_submodules
hiddenimports = collect_submodules('tensorflow_core.core.framework')
hiddenimports += collect_submodules('tensorflow_core.core')
hiddenimports += collect_submodules('tensorflow.compiler.tf2tensorrt')
hiddenimports += collect_submodules('tensorflow_core')
hiddenimports += collect_submodules('tensorflow.python.keras.engine.base_layer_v1')
hiddenimports += collect_submodules('tensorflow_core.lite.experimental.microfrontend.python.ops')

可能需要添加hook文件:book-tensorflow.core.framework.py 导入:

hiddenimports=['pkg_resources.py2_warn',
                'tensorflow',
                'tensorflow_core.python',
                'tensorflow_core.python.platform',
                'tensorflow_core.python.platform.tf_logging',
                'tensorflow_core.python.platform.self_check',
                'tensorflow_core.python.platform.build_info',
                'tensorflow_core.python.pywrap_tensorflow',
                'tensorflow_core.python.pywrap_tensorflow_internal',
                'tensorflow_core.python.util',
                'tensorflow_core.python.util.deprecation',
                'tensorflow_core.python.util.tf_export',
                'tensorflow_core.python.util.tf_decorator',
                'tensorflow_core.python.util.tf_stack',
                'tensorflow_core.python.util.tf_inspect',
                'tensorflow_core.python.util.*',
                'tensorflow_core.python.util.decorator_utils',
                'tensorflow_core.python.util.ANY.*',
                'tensorflow_core.python.util.is_in_graph_mode',
                'tensorflow_core.python.util.tf_contextlib',
                'tensorflow_core.core.*',
                'tensorflow_core.core.framework.*']

若含有sklearn模块,则还需要添加如下依赖文件。

["cython", "sklearn", "sklearn.ensemble", "sklearn.neighbors.typedefs", "sklearn.neighbors.quad_tree", "sklearn.tree._utils","sklearn.utils._cython_blas"]
  • 1

缺少二进制文件模块解决方法:编辑app.spec文件,在binaries栏添加内容:

binaries = [("/home/`username`/anaconda3/lib/python3.6/site-packages/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so",\
"./tensorflow_core/lite/experimental/microfrontend/python/ops")],
  • 1
  • 2

打包完,执行程序时可能还会报错:
无法找到动态库 libpython3.6m.so
无法找到动态库tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so

(可能)需要执行:

mv /home/`username`/path/dist/ocr_server/libpython3.6m.so.1.0 /home/wangsp/ocr_server_files/dist/ocr_server/libpython3.6m.so

mkdir -p /home/`username`/path/dist/ocr_server/tensorflow/lite/experimental/microfrontend/python/ops/
cp /home/`username`/anaconda3/lib/python3.6/site-packages/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so /home/`username`/path/dist/ocr_server/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so

三、flask服务请求

flask 服务端与客户端示例代码:http://www.suoniao.com/article/23907
flask 服务端支持正则的路由:https://www.cnblogs.com/felixwang2/p/9262464.html

示例:传入json字符串 --server 端

#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
from flask import Flask
from flask import request
from flask import redirect
from flask import jsonify
app = Flask(__name__)

@app.route("/" , methods=["GET", "POST"])
def index():
    if request.method == "POST":
        a = request.get_data()
        dict1 = json.loads(a)
        return json.dumps(dict1["data"])
    else:
        return "<h1>Only post request</h1>"

@app.route("/user/<name>")
def user(name):
    return"<h1>hello, %s</h1>" % name

if __name__ =="__main__":
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)

—client端

#coding=utf-8
import requests
import json


data={ "opr": "add", "data": { "userName": "98997", "disc": "hudihiudhu", "ip": ["10.10.11.1", "10.10.11.2"]}}
data = json.dumps(data)
r = requests.post("http://127.0.0.1:5000/", data)

print(r.status_code)
print(r.headers["content-type"])
print(r.encoding)
print(r.text)

 

pyinstaller打包后,运行可执行文件报错:TemplateNotFound: index.html
解决方法:在可执行文件中添加

import sys
sys.path.append(os.getcwd())

 

app\__init__.py文件中添加如下内容:

import sys
import os
from flask import Flask


if getattr(sys, 'frozen', False):
    template_folder = os.path.join(sys._MEIPASS, 'templates')
    app = Flask(__name__, template_folder=template_folder)
else:
    app = Flask(__name__)

 

【参考文章】

四、flask 实现多线程

在服务实现多线程中找寻了许多方法,这里我主要测试了gunicorntornadogevent 这三种方式。

不同部署方式方法:https://www.jianshu.com/p/e8ee1eed2e50

  • gunicorn方式部署
  • tornado方式部署
  • bjoern部署方式
  • cherrypy部署方式
  • meinheld 方式
  • gevent 部署方式

性能测试:

组合成功率总耗时备注
cherrypy48%18s单进程
tornado76%9.5s单进程
tornado84%4.5s4进程
gevent84%6s单进程
meinheld84%3.7s单进程
bjoern84%3.7s单进程
gunicorn+gevent84%4.3s9进程
gunicorn+meinheld84%3.6s9进程

4.1 gunicorn + flask服务打包

【gunicorn 官网】
flask使用的是Werkzeug来作为它的WSGI server,但是性能很一般,生产环境一般会使用其他的WSGI server, 网上查到有以下WSGI server:

Gunicorn 独角兽,从Ruby的Unicorn移植过来的。
uWSGI 比较全能的一个WSGI server。
CherryPy CherryPy是Python的一个HTTP Framework,然后它也有WSGI server。
可能还有其他的一些WSGI server,对于gunicorn熟悉,那么要使用gunicorn,app.py需添加以下代码:
gunicorn + flask服务:https://www.jianshu.com/p/70a30944fade
使用该方法打包依赖处理,隐藏导入:

hiddenimports=[
    "tensorflow.python.keras.engine.base_layer_v1",\
    "gunicorn.glogging",\
    "gunicorn.workers.sync",\
    "gunicorn.workers.ggevent"],

 

重写Gunicorn基类服务 【Gunicorn文档】

import multiprocessing,os
import gunicorn.app.base
from flask import Flask, request

app = Flask(__name__)


def number_of_workers():
    return (multiprocessing.cpu_count() * 2) + 1

class StandaloneApplication(gunicorn.app.base.BaseApplication):
    def __init__(self, app, options=None):
        self.options = options or {}
        self.application = app
        super().__init__()

    def load_config(self):
        config = {key: value for key, value in self.options.items()
                  if key in self.cfg.settings and value is not None}
        for key, value in config.items():
            self.cfg.set(key.lower(), value)

    def load(self):
        return self.application


if __name__ == '__main__':
    gunicorn_config = {
    'bind': '%s:%s' % ('127.0.0.1', '8080'),
    "check_config": True,
    "worker_class": "gthread",
    "workers": number_of_workers(),
    "threads": 2,
    'timeout': 60,
    "loglevel": "info",
    "access_log_format": "gunicorn %(h)s - %(t)s - %(r)s - %(s)s - %(f)s",
    "backlog": 30,
    }
    StandaloneApplication(app, options=gunicorn_config).run()

 

4.2 tornado+ flask服务打包

【tornado官网文档】 【tornado中文文档】 \

在这里查看tornado详情:https://blog.csdn.net/ka_ka314/category_7820903.html
关于日志使用:https://www.cnblogs.com/shijingjing07/p/7670672.html

#!/usr/bin/python
# -*- coding:utf-8 -*-
import os,json
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="-1"

from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from flask import Flask
from flask import request

app = Flask(__name__)

@app.route("/" , methods=["GET", "POST"])
def index():
    if request.method == "POST":
        a = request.get_data()
        dict1 = json.loads(a)
        return json.dumps(dict1["data"])
    else:
        return "<h1>Only post request</h1>"

@app.route("/user/<name>")
def user(name):
    return"<h1>hello, %s</h1>" % name

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    http_server = HTTPServer(WSGIContainer(app))
    http_server.listen(port)
    IOLoop.instance().start()

4.2 gevent+ flask服务打包

使用协程的方式启动。【参考文章】
下载地址:pip install gevent https://pypi.org/project/gevent/

#!/usr/bin/python
# -*- coding:utf-8 -*-
import os,json
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="-1"
from gevent import monkey
monkey.patch_all()
from flask import Flask
from flask import request
from gevent.pywsgi import WSGIServer


app = Flask(__name__)


@app.route("/" , methods=["GET", "POST"])
def index():
    if request.method == "POST":
        a = request.get_data()
        dict1 = json.loads(a)
        return json.dumps(dict1["data"])
    else:
        return "<h1>Only post request</h1>"

@app.route("/user/<name>")
def user(name):
    return"<h1>hello, %s</h1>" % name

if __name__ =="__main__":
    port = int(os.environ.get('PORT', 5000))
    http_server = WSGIServer(('0.0.0.0', port), app)
    http_server.serve_forever()

 

五、设置日志输出

Python 中的 logging 模块可以让你跟踪代码运行时的事件,当程序崩溃时可以查看日志并且发现是什么引发了错误。Log 信息有内置的层级——调试(debugging)、信息(informational)、警告(warnings)、错误(error)和严重错误(critical)。你也可以在 logging 中包含 traceback 信息。不管是小项目还是大项目,都推荐在 Python 程序中使用 logging。

  1. 基本使用
    配置logging基本的设置,然后在控制台输出日志
import logging
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")

 

  1. 将日志写入到文件
    设置logging,创建一个FileHandler,并对输出消息的格式进行设置,将其添加到logger,然后将日志写入到指定的文件中,
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt") # 文件路径
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)


# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")

 

  1. 将日志同时输出到屏幕和日志文件
    logger中添加StreamHandler,可以将日志输出到屏幕上,
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)


# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")

 

综合

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import logging.handlers #日志滚动及删除使用

#1.设置log日志记录格式及记录级别
#level记录级别包括DEBUG/INFO/WARNING/ERROR/CRITICAL,级别依次上升,log只会输出保存设置的级别及以上的日志。如果设置level=logging.DEBUG,则所有级别日志都会输出保存、如果level=logging.CRITICAL,则只输出保存CRITICAL级别日志
#format输出格式levelname级别名、asctime 时间、filename所在文件名、message记录内容
#datefmt 时间格式
#filename 要保存的文件名
#a写入模式,a则每次启动脚本时在原有文件中继续添加;w则每次启动脚本会重置文件然后记录
logging.basicConfig(level=logging.INFO,
                format='%(levelname)s: %(asctime)s %(filename)s %(message)s',
                datefmt='%Y-%m-%d %A %H:%M:%S',
                filename='myapp.log',
                filemode='a')

#2.设置log日志的标准输出打印,如果不需要在终端输出结果可忽略
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)

#3.设置log日志文件按时间拆分记录,并保存几个历史文件,如果不需要拆分文件记录可忽略
#class logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
#例:设置每天保存一个log文件,以日期为后缀,保留7个旧文件。
myapp = logging.getLogger()
myapp.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
filehandler = logging.handlers.TimedRotatingFileHandler("myapp.log", when='d', interval=1, backupCount=7)#每 1(interval) 天(when) 重写1个文件,保留7(backupCount) 个旧文件;when还可以是Y/m/H/M/S
filehandler.suffix = "%Y-%m-%d_%H-%M-%S.log"#设置历史文件 后缀
filehandler.setFormatter(formatter)
myapp.addHandler(filehandler)

#4.设置log日志文件按文件大小拆分记录,并保存几个历史文件,如果不需要拆分文件记录可忽略
#class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
myapp = logging.getLogger()
myapp.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
filehandler = logging.handlers. RotatingFileHandler("myapp.log", mode='a', maxBytes=1024, backupCount=2)#每 1024Bytes重写一个文件,保留2(backupCount) 个旧文件
filehandler.setFormatter(formatter)
myapp.addHandler(filehandler)


#使用
logging.debug('debug message : %s , result: %s',info,result)  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message') 

 

https://www.v2ex.com/t/480247

多进程日志

由于python 中logging 并不支持多进程,所以会遇到不少麻烦。
不过python有第三方库pip install concurrent-log-handler 查看详情使用方法

from logging import getLogger, INFO
from concurrent_log_handler import ConcurrentRotatingFileHandler
import os

log = getLogger(__name__)
# Use an absolute path to prevent file rotation trouble.
logfile = os.path.abspath("mylogfile.log")
# Rotate log after reaching 512K, keep 5 old copies.
rotateHandler = ConcurrentRotatingFileHandler(logfile, "a", 512*1024, 5)
log.addHandler(rotateHandler)
log.setLevel(INFO)

log.info("Here is a very exciting log message, just for you")

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值