如何打造 Production-Ready 的 ML Libraries

Cinnamon AI Taiwan
15 min readMay 4, 2022

--

相信許多的讀者對建構 AI 模型並不陌生,然而如何將我們訓練好的模型打包給終端用戶(End user),其中所需在意的細節與實用的工具倒是鮮少被提及。本篇文章將會藉由CinnamonAI內部開發人員的經歷,以Python語言為例,揭露如何打造符合 Production-Level 的程式碼及封包(Package)。

一般的 ML researcher(尤其是分工明確的公司) 不太會接觸到 Packaging & Deployment & Production system

Breakdown of job function by role [source]

Outline

— Part1

  • Introduction
  • Coding Style & Convention : flake8, black
  • Useful Libraries : mypy, isort, pre-commit, pipenv

— Part2

  • Testing & CI Platform
  • Code Quality
  • Build Python Package

Introduction

Research Code & Production Code

學生或是研究員為了特定作業、專案所建構的模型通常屬於 Research Code 的範疇,這類的程式碼是為了快速去驗證某些想法(如將RNN架構替換成Transformer)或跑過不同的實驗任務所撰寫,輸出預測結果或精度就能打完收工,然而為了能順利地將模型轉交給客戶或終端使用者, Production Code 會有更嚴謹的規範以降低日後出錯的機率。

Research v.s Production

Production Code 常見的規範包含 Testability、Maintainability、Reproducibility、Scalability、Performance,我們用下表簡單描述每個特性

Production code properties

由上表可知, Production Code 相較於 Research Code更為嚴謹,以 Performance為例,如果今天只是要跑個實驗結果,開發者不會太在意程式碼的運行速度、模型針對記憶體的使用量等細節,當系統報錯時,通常也是快速手動修改一下,並不會花太多時間思考日後遇到不同錯誤要如何解決,然而可以想像是的當我們今天已經把整套系統部署到客戶的環境上,事情將會變得複雜許多。

Coding Style & Convention

“你的程式碼看起來如何?” 取決於你的 Coding style,每個程式語言皆有人為其制定不同的準則與開發規範,如PEP8是 Python 語言下極為常見的 Coding style guideline,另外像是PEP257則是常見的 Docstring Conventions。

不同公司當然也可以制定屬於自己的標準,好處是當所有開發者都遵守相同的標準時,會大大提升程式碼的 Readbility(可讀性),進而提升開發效率與日後的 Maintainability。

常見的 coding style 規範包含縮排、空行、空格、命名規則(Naming Convention)、註釋格式(Docstring Conventions)等,在此我們就不多加贅述,有興趣的讀者可以參考 PEP 8 — Style Guide for Python Code

Linter & Formatter

Linter

然而要邊開發邊維持統一的Coding style,對某些開發者來說可能會降低其開發效率,此時Linter & Formatter就能派上用場。Linter 泛指能幫助開發者自動分析程式碼格式、邏輯錯誤與檢查語法正確性的工具,不同的程式語言有其各自的 Linter,單一程式語言也可能有多個 linting tools,其規範與嚴謹度或有些許差異,但目標皆是相同的。

常見的Python Linter 如 Pylint 、Flake8皆屬於靜態代碼檢查工具(static code analyzer tool)。

Static code analysis tools are any tools that analyze source code without the need to run it.

以Flake8為例,其主要是封裝以下三個工具:

  • PyFlakes : A simple program which checks Python source files for errors.
  • Pycodestyle : A tool to check your Python code against some of the style conventions in PEP 8.
  • Ned Batchelder’s McCabe script : Check cyclomatic complexity. (ex. 一段程式碼 If / else statement越多,可能路徑就越多,因此cyclomatic complexity就越高)

Cyclomatic complexity it is a software metric created by Thomas J. McCabe to measure the number of independent paths through the source code

而將 Linting tools 整合到開發流程的情境如下

  • 撰寫程式碼
  • 運行並修正錯誤回報
  • 完成後利用 Linter偵錯
  • 針對 Linter回報的錯誤進行分析與修正(可以忽略特定錯誤)
  • 重複上述兩步驟直到無任何錯誤
  • 最後再由其他開發者人工審查程式碼

下方我們會介紹 flake8 常見的用法

  • 基本指令
# 先安裝:python -m pip install flake8
# open an interactive shell and run
flake8 path/to/code/to/check.py
# or
flake8 path/to/code/
  • 修改Line長度限制:默認 為 79個字元,其實非常容易超過
flake8 --max-line-length 99 path/to/code/
  • 忽略特定錯誤 : 我們不一定要遵照所有準則或默認設置,如開發者認為長度限制以及偵測未使用導入模塊的功能是多餘的,可以運用 ignore 參數來調整,關於更多 Error / Violation Codes 可以參考連結:Reference
flake8 --ignore=E501,F401 path/to/code/
  • 忽略特定檔案或資料夾
# 忽略所有.pyc結尾的檔案
flake8 --exclude=*.pyc path/to/code/
# 如果不想動到默認設置,可以使用 --extend-exclude
# 下方指令會使flake8忽略所有src 資料夾底下的檔案
flake8 --extend-exclude=src/ path/to/code/

更多options指令可以參考:Full Listing of Options and Their Descriptions

Formatter

Linter 只能自動檢查與報錯,錯誤的部分還是得靠開發者手動來修正, formatter則是能直接幫助開發者修正一些排版上的錯誤,常見的 Python formatter 包括 autopep8, yapf, black

這裡我們以 black 為例,black 之所以盛行的主要原因就是其使用起來非常簡單,並且只有極少數 Options 會影響到最終的風格(確保 Coding Style一致)。

# 先安裝 : pip install black
# run
black {source_file_or_directory}
# run through all files
black *

下方程式碼展示了格式化前後的差異

Useful Libraries

除了上述的 Linter & formatter,下方將介紹幾個實用的工具,能進一步提升開發代碼的品質。

mypy

mypy 是Python底下的靜態型別檢查器(static type checker),其主要原理是比較Type Hint 與 輸入參數型態,例如下方 type hint 明確指示了input type 為 “int”,然而輸入卻是 “str”,此時mypy就會報錯。

mypy也會檢查source libraries的 type hint,如下方我們定義number type為 “str” 並且也輸入了 “str”參數,然而 還是報錯,其主要原因是 mypy 同時檢查了 math.floor method 底下的 type hint並偵測到出入

import math
def floor(number: str = "3") -> int:
return math.floor(number)

使用mypy的好處是不需要實際跑過代碼就拿做 type checking,以減少實際運行時出現型態類別錯誤的可能,然而值得注意的是mypy無法真正解決型別誤用的情況,因其主要還是透過開發者輸入的Type Hint來判斷。有關於 type hint 的更多細節,可參考官方資料 : PEP484

isort

isort 主要用途是按字母順序對導入(imports)進行排序,並自動按照類型劃分。以官方文檔為範例:

首先可以觀察到 isort 後 packages, modules 照字母順序排列(a,b,c…),再者重複的imports被整合(import sys),除此之外原本個別的 import (ex.from my_lib import Object, from my_lib import Object3) 也被打包到在一起,整體比較起來代碼在處理後更為乾淨,可讀性也較高。

# Before isort
from my_lib import Object
import osfrom my_lib import Object3import sysfrom third_party import lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14import sys from __future__ import absolute_importfrom third_party import lib3#--------------------------------------
# After isort
from __future__ import absolute_import
import os
import sys
from my_lib import Object, Object3
from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14, lib15)

isort 的安裝與使用也相當簡單

# shell
# 安裝
pip install isort
# 運行在特定檔案上
isort mypythonfile.py mypythonfile2.py
# 在recursively 跑過所有當前路徑下的檔案
isort .
# 忽略存在 syntax errors 的檔案
isort --atomic .

pre-commit

上方我們介紹了不少實用的工具來提昇程式碼的品質,然而手動跑過所有工具想必不是一個聰明的作法,當然,我們可以準備一個 shell script 來執行,但更常見且實用的方法是創建 pre-commit hook。

Pre-commit hook 是 Git hook 的一種,通常有 “hook” 就是代表在執行某個動作或任務之前,會去觸發另一項動作。

pre-commit hook 顧名思義就是在 下達 git commit指令時去執行設定任務 ,而 pre-commit 就是一套去管理與維護 pre-commit hook的框架。

下方我們提供Github Repo 範例來實際執行pre-commit : Link

  • 首先先將Repo clone下來:
git clone git@github.com:jeff52415/Tips-for-improving-your-neural-network-pytorch.git
  • 安裝 pre-commit library : pip install pre-commit
  • 創建一個 pre-commit configuration : .pre-commit-cofig.yaml,在這個Repo下,已經有預先準備的pre-commit configuration,示範如何整合 formatter, linter 還有上述介紹到的工具 : reference
id : the id of the hook — used in pre-commit-config.yaml.entry : the entry point - the executable to run. entry can also contain arguments that will not be overridden such as entry: autopep8 -i.args : list of additional parameters to pass to the hook, 如更改flake8 --max-line-length 長度。

要注意的是有時候 linter <-> linter, linters <-> Formatter 之間會有衝突, ex. black, isort 會互相修改彼此結果 : reference

  • Run pre-commit install: 除了第一次執行外,每次clone Repo都要執行,其主要目的是為了啟動pre-commit hook : .git/hooks/pre-commit
  • 完成上述步驟,之後每次執行 git commit 時都會觸發 pre-commit hook去執行 configuration 內的任務。
  • 如果想要繞過 pre-commit hook,可以使用 -no-verify option
git commit --no-verify ...
  • 另外我們也能手動觸發 pre-commit hook
* 手動跑所有檔案: pre-commit run --all-files 
* 手動跑個別檔案: pre-commit run <hook_id> #hook_id : ex. black

Pipenv

在這章節的最後想簡短介紹一下pipenv,一個相當方便的python 套件管理工具,適當的使用能確保開發時環境的整潔度並提升開發效率。

如果只是想要大致了解 pipenv 功能,我們可以將 pipenv 看成 pip (package installer for Python) + virtualenv(tool to create isolated Python environments) + pyenv(switch between multiple versions of Python) 的綜合體。

軟體開發時最怕的就是搞砸環境,尤其對ML專案而言,Reproducibility相當重要,相信很多讀者開發時都是透過 virtualenv 創立獨立環境,之後 source進入環境後再用 pip 安裝所需套件,如果需要切換專案再手動進入另一個獨立環境,當然這樣的做法並沒有什麼問題,不過要是我們能一步就完成上述步驟,何樂而不為?

pipenv 的使用方式:

  • 安裝pipenv
pip install pipenv
  • 建立專案
pipenv --python [PYTHON VERSION] # ex. pipenv --python 3.7
pipenv --three # Python 3
pipenv --two # Python 2
  • 安裝/卸載/更新套件
pipenv install package_name# 安裝特定版號
pipenv install numpy==1.19
# 卸載套件
pipenv uninstall numpy
# 更新套件
pipenv update package_name
# 更新所有需要更新的套件
pipenv update --outdated
  • 第一次執行 pipenv 後會在目錄下產生 Pipfile 裡面會記載安裝過的套件,同時也會在安裝好後新增 Pipfile.lock 作為 Hash 的安全檢查用(source)
  • 進入虛擬環境
pipenv shell
  • 不進入虛擬環境,單純將個別指令運行在虛擬環境
pipenv run task# ex. run pre-commit
pipenv run pre-commit run --all-files
  • 輸出 requirements.txt
pipenv lock --requirements > requirements.txt
  • 列出目前虛擬環境內套件
pipenv graph

以上就是 pipenv 常見指令,這裡也推薦讀者們延伸閱讀

總結

本篇章我們先介紹了 Production Code的基本觀念、Coding Style 統一的重要性以及一些提升程式碼品質的實用套件,下一章我們會繼續深入更多主題,並實際打包一個專案。

Reference

【2022 Global Student Bootcamp — AI 產品實作營】即日起開始報名!

以「開發使用者為導向的產品」為核心概念設計的 Cinnamon AI Bootcamp 是 AI Junior 人才的精神時光屋,為你奠定進入 AI 產業界的基石,彌平產學落差!
今年更是台灣首次與越南辦公室聯合舉行 2022 AI Bootcamp,讓你有許多與國際學生們交流、切磋的機會。加快自我成長速度、邁向 AI 職涯的第一步,就從加入 2022 Global Student Bootcamp 開始吧!

--

--