• 用pyinstaller打包LGBM模型为ELF/EXE可执行文件


    1. 引入

    写好的python代码和模型,如果需要做到离线部署、运行,就必须要将代码和模型打包为可独立运行的可执行文件
    使用pyinstaller就能做到这个,相同的代码,在windows上运行就能打包为exe,在linux上运行就能打包为elf。

    打包的过程是怎么样?有哪些不同的打包方式?各有什么优缺点呢?

    2. 打包过程:生成多个文件

    假设我们的项目有3个文件组成:

    • main.py : 主入口程序
    • utils.py: 各种工具函数
    • model_rf.jl: 模型文件

    打包过程分为如下步骤,在windows和linux都一样:

    1. 安装pyinstaller
    pip install pyinstaller
    
    • 1
    1. 生成.spec文件
    pyi-makespec -w main.py
    
    • 1
    1. 修改.spec文件

    注意几点:
    (1)主入口程序写在: Analysis第一个参数
    (2)其他依赖程序写在:Analysis第一个参数的列表中
    (3)模型文件写在: binaries中,注意要写为tuple

    修改后好的.spec文件如下所示:

    # -*- mode: python ; coding: utf-8 -*-
    
    
    block_cipher = None
    
    
    a = Analysis(
        ['main.py',
    	'utils.py'],
        pathex=[],
        binaries=[('model_rf.jl','.')],
        datas=[],
        hiddenimports=['scipy.special.cython_special'],
        hookspath=[],
        hooksconfig={},
        runtime_hooks=[],
        excludes=[],
        win_no_prefer_redirects=False,
        win_private_assemblies=False,
        cipher=block_cipher,
        noarchive=False,
    )
    pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
    
    exe = EXE(
        pyz,
        a.scripts,
        [],
        exclude_binaries=True,
        name='main',
        debug=False,
        bootloader_ignore_signals=False,
        strip=False,
        upx=True,
        console=False,
        disable_windowed_traceback=False,
        argv_emulation=False,
        target_arch=None,
        codesign_identity=None,
        entitlements_file=None,
    )
    coll = COLLECT(
        exe,
        a.binaries,
        a.zipfiles,
        a.datas,
        strip=False,
        upx=True,
        upx_exclude=[],
        name='main',
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    至于为什么要加入hiddenimports=['scipy.special.cython_special'],,是因为笔者在python3.8下运行,打包正常后,运行可执行文件依然报错如下:

    (xxx) [aaa@bbb main]$ ./main
    Traceback (most recent call last):
      File "main.py", line 1, in <module>
      File "PyInstaller/loader/pyimod02_importers.py", line 385, in exec_module
      File "sklearn/ensemble/__init__.py", line 5, in <module>
      File "PyInstaller/loader/pyimod02_importers.py", line 385, in exec_module
      File "sklearn/ensemble/_base.py", line 18, in <module>
      File "PyInstaller/loader/pyimod02_importers.py", line 385, in exec_module
      File "sklearn/tree/__init__.py", line 6, in <module>
      File "PyInstaller/loader/pyimod02_importers.py", line 385, in exec_module
      File "sklearn/tree/_classes.py", line 41, in <module>
      File "sklearn/tree/_criterion.pyx", line 1, in init sklearn.tree._criterion
    ModuleNotFoundError: No module named 'scipy.special.cython_special'
    [45300] Failed to execute script 'main' due to unhandled exception!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    根据参考4,加入后就能修正该错误,因为pyinstaller没有加入这个必须的依赖。

    1. 运行命令进行打包
    pyinstaller main.spec
    
    • 1

    这种打包方式,会生成一个可执行文件(位于dist文件夹中),也会生成很多个运行该可执行文件所需的依赖库(dll, so),所以部署时,需要将整个文件夹拷贝到目标机。

    那么,能不能只生成一个可执行文件,不生成额外的依赖文件呢?

    3. 打包过程:生成单个文件

    如果只有一个py文件,那么,使用一条命令就能实现生成独立的可执行文件:

    pyinstaller -F main.py
    
    • 1

    但是我们这个例子中,是有多个文件的,这就必须用下面的命令来打包:

    pyinstaller -F  -w main.py -p utils.py -p model_rf.jl --hidden-import scipy.special.cython_special
    
    • 1

    这样就能在dist文件夹中生成一个较大的可执行文件,部署时只需要部署这一个文件就可以。

    4. 两种打包方式的区别

    上面讲解了生成多个文件生成单个文件两种pyinstaller的打包方式。看上去生成单个文件方式更方便。

    但是,实际运行打包后的可执行文件,就能发现:
    (1)生成单个文件,最终只生成一个可执行文件,比较简单,但是运行很慢
    (2)生成多个文件,最终生成一堆文件,但是其中的可执行文件运行会快很多;笔者实测这种方式比单个文件快5倍

    为什么生成单个文件会更慢呢?从参考3可知

    “one file” mode – this mode means that it has to unpack all of the libraries to a temporary directory before the app can start

    因为这个很大的单个文件,在运行主函数前,会将所有依赖都释放到临时文件中,再加载运行。这个释放文件的操作,需要占用I/O,而且每次启动程序都释放文件,自然就拖慢了运行速度。

    5. 总结

    pyinstaller能实现将多个.py文件,和其他模型文件,打包为可离线运行,不安装配置环境就能运行的可执行文件EXE或者ELF
    打包时,建议按照生成多个文件的方式来打包,这样程序运行起来会更快。 本文用到的所有代码和相关文件,都放到这个repo了,在linux下是正确运行的:https://github.com/ybdesire/machinelearning/tree/master/pyinstaller_model_package。

    参考

    1. https://blog.csdn.net/weixin_42112050/article/details/129555170
    2. https://blog.csdn.net/LIUWENCAIJIAYOU/article/details/121470028
    3. 为什么打包后的程序运行慢: https://stackoverflow.com/questions/9469932/app-created-with-pyinstaller-has-a-slow-startup
    4. https://stackoverflow.com/questions/62581504/why-do-i-have-modulenotfounderror-no-module-named-scipy-special-cython-specia
    5. 本文所用代码。https://github.com/ybdesire/machinelearning/tree/master/pyinstaller_model_package
  • 相关阅读:
    39个 Python Datetime 小例子,拯救因时间抓狂的你
    springboot+Mybatis项目初始化
    IDEA使用问题-02 无法引入POM依赖
    B轮融资背后:未势能源在万亿“长坡”上,铺出三重“厚雪”
    Docker_实用篇_Docker-Compose_微服务部署
    20220729NOI模拟赛--考后总结
    接口和接口测试
    React Native 项目配置 Flow (windows环境)
    conda pack迁移环境
    如何在华为 Ascend 设备上运行模型
  • 原文地址:https://blog.csdn.net/ybdesire/article/details/133529238