# 基於容器的MLflow和模型版本控制
:::spoiler Table of Content
[TOC]
:::
<br>
<div class="text-center">
<img src="https://hackmd.io/_uploads/H1cH_0pds.png">
<figcaption> 你也為了整理各種版本的資料以及模型所困擾嗎?</figcaption>
</div>
## 簡介
在機器學習以及深度學習的訓練和部屬場景中,經常我們會被非常多不同版本的資料集、訓練紀錄以及模型版本所困擾。過去Tensorboard以及一些基本的紀錄工具可以協助我們在一定程度上進行模型的效能的紀錄以及模型本身的版本化,但仍缺少一個非常完善的框架。在[MLOps](https://en.wikipedia.org/wiki/MLOps)的概念中被提出的核心概念有兩項是關於資料以及模型的版本化和自動化管理,一個是資料版本控制(Data version control,DVC)以及模型版本控制(Model Version Control, MVC)。我們將在本文中介紹一個具有模型版本控制以及模型登錄(Model Registry)功能的工具——**MLflow**。
## MLflow

[MLflow](https://mlflow.org/)是一個開源、免費的機器學習生命週期管理工具。他提供的功能有:
* 紀錄訓練中所使用的各項超參數以及結果 ([MLflow Tracking](https://mlflow.org/docs/latest/tracking.html))
* 將訓練程式包裝成可重複、可重現的形式 ([MLflow Project](https://mlflow.org/docs/latest/projects.html#projects))
* 基於不同的平台以及不同的框架來進行模型的佈屬 ([MLflow Models](https://mlflow.org/docs/latest/models.html))
* 提供中心化的儲存/紀錄方案,包含模型登錄、模型版本控制以及模型標記等等功能 ([MLflow Model Registry](https://mlflow.org/docs/latest/model-registry.html#registry))
[MLflow](https://mlflow.org/)也提供在各種不同的程式語言和框架中使用的API,例如:
* 程式語言
* Python
* R
* Java
* REST
* 機器學習框架
* Scikit-Learn
* CatBoost
* fastai
* lightgbm
* tensorflow
* torch
* spark
* onnx
## 架設MLflow
### 前置規畫
在MLflow所提供的紀錄以及成品(artifacts)存放方案中,主要有以下幾種形式 :
* 紀錄以及成品均存放在本機資料系統
* 紀錄儲存於資料庫,成品存放於本機資料系統
* 紀錄儲存於本機資料系統,成品存放於遠端伺服器
* 紀錄以及成品均存放於遠端資料庫及伺服器
在此我們將採取最後一個選項,將參數以及結果紀錄於MySQL資料庫,並將成品以及模型紀錄於FTP伺服器。
:::info
詳細說明可以參考官網的[介紹:link:](https://mlflow.org/docs/latest/tracking.html#where-runs-are-recorded)。
:::
:::info
如果不熟悉資料庫操作的用戶,可以參考[附錄:link:](##附錄)中關於將記錄儲存在本機資料系統的介紹。
:::
### 資料庫架設(基於MySQL)
我們將利用MySQL官方所提供的Docker容器[映像檔](https://hub.docker.com/_/mysql)來建立一個MySQL資料庫。
將主機的`3306`埠與容器相連,並將資料庫的IP指定為`0.0.0.0`,再給定`root`的密碼,即可以快速地建立一個MySQL容器。
詳細設定如下方所示 :
{%gist c494c62981fa647e79a9784f612fb287 %}
:::info
額外掛載的兩個資料夾是為了將MySQL的資料儲存在實體的位置,避免容器關閉後遺失。資料夾內容將由MySQL資料庫自動寫入,使用者不需要預先放入任何內容。
:::
#### 資料庫設定
由於直接使用`root`帳號存取資料庫是相當危險的,所以必須要另外設定一個帳號,並給予這個帳號相對應的權限來進行資料庫操作。
#### 新增用戶
我們可以用下方的SQL語法來新增一個用戶`user`:
```sql
CREATE USER 'user'@'%' IDENTIFIED WITH mysql_native_password BY 'passwd';
```
#### 給予用戶權限
我們可以用下方的SQL語法來給予用戶`user`相應的權限:
```sql
GRANT ALL PRIVILEGES ON *.* TO 'user'@'%';
```
#### 建立資料庫
為了存放MLflow所傳送的資料,我們需要在MySQL資料庫中建立一個資料庫。以下的SQL語法將會建立一個名為`mllfow_artifacts`的資料庫:
```sql
CREATE DATABASE mlflow_artifacts;
```
### 成品存放伺服器架設(基於FTP)
基於Alpine是最輕量、最簡化的Docker映像檔,故我們選擇以Alpine為基礎搭配[vsftpd](https://security.appspot.com/vsftpd.html)來建立一個FTP伺服器。
詳細的`Dockerfile`如下方所示:
{%gist 2aa8a8914bfbe91cb5205f1de44e812a %}
在`Dockerfile`中有出現一個`entrypoint.sh`,這是一個包含了啟動`vsftpd`服務的腳本,詳細內容如下:
{%gist 9d373caa75a3bd250d6f0ff9cd35c143 %}
在上方的啟動腳本中,我們啟用了vsftpd的被動模式,並且宣告了相對應的被動連接埠;我們也開啟了寫入權限,讓來自MLflow的成品可以被正常的寫入到FTP伺服器之中。
:::info
關於`vsftpd`的詳細設定,可以參考鳥哥的[教學:link:](https://linux.vbird.org/linux_server/centos4/0410vsftpd-centos4.php#theory_pasv)。
:::
:::warning
在上方的`-oseccomp_sandbox=NO`的設定是為了避免出現`500 OOPS: child died.`錯誤。詳細請參考[這篇問答:link:](https://serverfault.com/questions/574722/vsftp-error-500-oops-child-died)。
:::
### MLflow伺服器架設
在前兩個章節,我們架設了MySQL資料庫以及FTP。接下來,我們將架設一個MLflow的伺服器。同樣的,我們將使用Docker容器來建立這個伺服器。
* Python環境要求: `python >= 3.8`
* 函式庫要求: 根據所使用的資料庫以及資料伺服器而定。(以MySQL為例,需要加裝`mysqlclient`函式庫)。
我們將使用Python的官方Docker容器映像檔來建構MLflow的伺服器。`Dockerfile`的設定如下:
{%gist 1dbc2e19c8b3a7ece00fd3a430f8dc96 %}
同樣的,我們也準備了相對應的`mlflow_entrypoint.sh`來啟動MLflow的服務。將`<ip>`以及`<port>`換成MySQL以及FTP相對應的IP以及連接埠即可。
{%gist 7e396737f0c1a2d3354f5e2b6b77a77a %}
其中,命令的末兩個參數`--backend-store-uri`以及`--default-artifact-root`分別帶有以下的作用:
* `--backend-store-uri`:宣告資料庫的連結位置。
* `--default-artifact-root`:宣告成品存放伺服器的位置。
兩者的宣告方法均為`<protocol>://<user>:<password>@<ip>:<port>/<path>`。
只要使用`docker run`指令利用上方的`Dockerfile`所建立的映像檔來產生一個容器,就可以建立一個MLflow的伺服器。使用者可以透過以下網址來連線到MLflow的網頁:
```
http://<ip>:3306/
```
## 使用Mlflow紀錄Scikit-learn訓練
接下來,我們可以使用一個簡單的範例來示範如何使用MLflow所提供的服務。
假設我們的MLflow服務架設在以下網址
```
http://192.168.0.10:9090
```
我們需要在Python程式碼中,導入`mlflow`模組,並透過`mlflow.set_tracking_uri()`來設定伺服器的位置,並利用`mlflow.set_experiment()`來設定這次實驗的名稱。在紀錄上,我們採用MLflow所提供的自動紀錄功能,`mlflow.sklearn.autolog()`會基於預設的行為進行紀錄,並將模型儲存至遠端資料伺服器。
{%gist 188d88e4cb94c52fdf5d2c3d87632c73 %}
利用以下的指令,執行上方的Python程式:
```
python mlflow_example_sklearn.py
```
將會得到以下結果:


可以發現`mlflow.sklearn.autolog()`自動幫我們紀錄了`MAE`, `MSE`, 以及`RMSE`等結果,並且將模型儲存到了遠端的FTP伺服器。
## 結語
在本文中,我們介紹了如何利用Docker容器來建立MLflow以及其所需要的各個必要元素,同時也示範了如何利用MLflow紀錄模型的訓練結果以及模型本身。
## 附錄 —— 將MLflow紀錄儲存於本機
在先前的[章節](###MLflow伺服器架設)中,我們於`mlflow_entrypoint.sh`的中宣告的參數有`--backend-store-uri`以及`--default-artifact-root`,分別對應到了資料庫以及成品存放伺服器的連結位置。
如果要將成品以及訓練所紀錄的參數以及結果存放在本機的資料夾,則不需要特別架設MLflow伺服器。只需要在Python程式中利用MLflow的記錄功能,並直接執行即可。MLlfow將會在執行Python程式的資料夾中,新建一個`mlruns`資料夾,並將所有記錄、結果以及模型紀錄在其中。使用者只需要執行以下指令,就可以在`http://localhost:5000/`的位置連接到MLflow的網頁。
```
mlflow ui
```
###### tags: `技術隨筆` `MLOps` `MLflow` `Model Version Control` `MySQL` `vsftpd`