【機器學習從零到一】 Day4: Data science 的版控 → DVC 的建立與使用

PJ Wang
21 min readAug 4, 2019

從學生時期開始接觸機器學習,當初以為的 Machine learning 就只要會處理資料、訓練模型、得到比較高的準確率,然後就此結束,但出社會後發現,從 Jupyter 到 Production 的路還有段距離,那該怎麼辦呢?

因此,接下來將會分幾個章節來介紹:

  1. 建立 GCP 機器 — VM 執行個體
  2. 建立機器學習環境 (GPU environment)
  3. Pytorch 介紹與範例 (image classification)
  4. Data science 的版控 → DVC 的建立與使用
  5. 利用 Flask 部署機器學習套件
  6. 利用Docker 建立機器學習容器

Data Version Control ( DVC ) 介紹

當 Data scientist 利用機器學習、深度學習來建立模型時,會需要去讀取資料、處理資料、建立模型、調整參數、訓練模型、評估模型 … 等,因此這邊會有大量的參數會需要去嘗試,如果沒有一個好的版本控制,常常會全部混淆在一起,更常看到的命名法是 model_1 model_2 … ,這樣完全沒辦法分辨哪個模型是幹嘛的。

因此 DVC 的概念如下圖所示,每一個模型的產生應該要同時考慮資料集、參數、程式碼。在用 DVC 控制之後,之後就可以針對比較好的參數、資料集輕易地做 reproduce 。

那為什麼 git 不行取代呢?是因為 git 推到 github 時,就會有檔案上傳的限制,而且更多的是,你可能想要看每個版本的 precision, recall, f1 分數的比較,這邊 DVC 都有辦法幫你一次搞定,更好的是,它會建立起一個 Pipeline ,具體來說就是,調整完參數後,輸入 dvc repro ,就能幫你從「資料的切割 → 資料的處理 → 訓練模型 → 模型評估」做一條龍式的處理,最後用 dvc metrics show 就能看到最後評估完的結果。

DVC 真正的運作邏輯是在原本受 git 版控的環境,多加了一個 資料版控進去,因此 DVC 會和 git 兩個相輔相成的配合。而 DVC 是利用輸入與輸出來將資料做 pipeline 的串接,所以在執行一段程式時,都要告訴他輸入是哪些檔案,輸出是哪些檔案。更多詳細的步驟我們會一步一步的走過,若還想更多,可以參考官網: https://dvc.org/

專案挑戰:化妝品分類

題目:是否能分辨一張圖片有出現哪一個種類的化妝品,而化妝品的種類粗類為 → 口紅、眼線筆、指甲油、粉底液、粉餅、刷具、眼影盤。

進入正題

我們這次選擇的框架是 Pytorch ,如果想知道更多 Pytorch 如何實作,可以參考我上一篇文章 【機器學習從零到一】 Day3: Pytorch 介紹與範例 (cosmetics classification) ,而我們這一篇會從上一篇 cosmetics-classification.ipynb ,拆成四段程式 config.py evaluate.py split_train_test.py train.py。

程式碼:Github

資料集:下載連結 → 下載好後執行 tar zxvf dataset.tar.gz

這次會使用到的資料如下,以及資料夾的編制如下(粗體字表示由程式產生,並非一開始就在資料夾內):

./ 
├── script
│ ├─ split_data.py
│ ├─ train.py
│ ├─ evaluate.py
│ └─ config.py
├── dataset
│ ├── cosmetics-category
│ ├── cosmetics-all
│ │ ├─ _PRrlNrSLhHGaUTfduYgQ6eOAdI.jpg
│ │ ├─ -b2cugUzKg6IIHIJjfXGcDnKsZA.jpg
│ │ ├─ -FK0wRGzJZ5NirgLw0CbvUf-7Og.jpg
│ │ └─ ...
│ ├── train.csv
│ ├── test.csv
│ └── annotation.csv
├── model
│ └── model.pth
└── log
├── train-output.txt
├── test-output.txt
└── eval.txt

0. 事前準備

將 repository clone 下來、和資料集解壓縮後,資料夾編制如下:

./ 
├── script
│ ├─ split_data.py
│ ├─ train.py
│ ├─ evaluate.py
│ └─ config.py
├── dataset
│ ├── cosmetics-category
│ ├── cosmetics-all
│ │ ├─ _PRrlNrSLhHGaUTfduYgQ6eOAdI.jpg
│ │ ├─ -b2cugUzKg6IIHIJjfXGcDnKsZA.jpg
│ │ ├─ -FK0wRGzJZ5NirgLw0CbvUf-7Og.jpg
│ │ └─ ...
│ ├── train.csv
│ ├── test.csv
│ └── annotation.csv
├── dataset.tar.gz
├── cosmetics-classification.ipynb
└── split-train-test.ipynb

可以先將這次不會用到的檔案刪除

$ rm dataset.tar.gz
$ rm cosmetics-classification.ipynb
$ rm split-train-test.ipynb

刪除好後,加入到 git 當中

$ git add .
$ git commit -m "remove useless file"

DVC 套件安裝

$ pip install dvc

1. DVC 託管專案

利用指令 dvc init ,並且會產生 .dvc/.gitignore.dvc/cache/ . dvc/config 三份檔案,其中最重要的是 .dvc/cache/, DVC 會在這邊建立檔案的連結,也是最後會 push 到雲端的檔案。

$ dvc init

2. 切割檔案

DVC 在建立 pipeline 的方式是透過輸入、輸出,所以在執行程式碼的過程,會需要告訴 DVC 會需要吃進哪些檔案、輸出哪些檔案。DVC 執行程式的方法如下:

$ dvc run -d 要執行的程式或要輸入的檔案 -o 要輸出的檔案 python 要執行的程式

以我們要切割資料集來看的話,會執行下面這段程式

$ dvc run -d script/split_train_test.py -d script/config.py -d dataset/annotation.csv -o dataset/train.csv -o dataset/test.csv python script/split_train_test.py

執行完後,可以看一下 DVC 當中做了什麼事情

$ git status -s
?? train.csv.dvc
$ cat train.csv.dvc
md5: c53dd6d76f7cdc25aaf2146db6223bf0
cmd: python script/split_train_test.py
wdir: .
deps:
- md5: 826ea439f28fb04923de739af8c26b5d
path: script/split_train_test.py
- md5: a933ce5d996b1687817a60b3453e18ed
path: script/config.py
- md5: 87c46f0402b54b960b294ef7791f7cf8
path: dataset/annotation.csv
outs:
- md5: 896389741ff20a2055acfe5c65893bf1
path: dataset/train.csv
cache: true
metric: false
persist: false
- md5: 1ea600a9a7b720cd28fe99ff6d1c3e70
path: dataset/test.csv
cache: true
metric: false
persist: false

會發現幾件事:

  1. 他會以第一個 -o 的檔案去加上 .dvc 成為新的檔案
  2. 承1,但我有輸出兩個檔案 test.csvtrain.csv ,因此他會都記錄在 train.csv.dvc 這分檔案當中。
  3. md5 會紀錄的是檔案在 .dvc/config 當中的位置,而每一份檔案對於 DVC 來說是建立一個 link 去連到原本的檔案位置,而並非去創建新的檔案。
$ du -sh .dvc/cache/89/* .dvc/cache/1e/*
60K .dvc/cache/89/6389741ff20a2055acfe5c65893bf1
16K .dvc/cache/1e/a600a9a7b720cd28fe99ff6d1c3e70
$ du -sh dataset/train.csv dataset/test.csv
60K dataset/train.csv
16K dataset/test.csv

最後我們可以在用 git 來管理我們的檔案

$ git add .
$ git commit -m "split data"

2. 訓練模型

由於我們會建立 log 檔以及 model,因此我們先建立起兩個資料夾:

$ mkdir log
$ mkdir model

而利用 DVC 執行 train.py 的方法和步驟一雷同,將會用到的檔案用 -d 去加入,而輸出的檔案用 -o 去建立。

$ dvc run -d script/train.py -d script/config.py -d dataset/train.csv -d dataset/test.csv -o model/model.pth -o log/train-output.txt -o log/test-output.txt python script/train.py

執行完後,會建立兩份 log 檔 log/train-output.txtlog/test-output.txt 以及模型 model/model.pth ,我們可以把建立出來的檔案用 git 來管理。

$ git add .
$ git commit -m "train model"

3. 評估模型

這裡比較特別要注意的是,輸出的 eval.txt 檔案,是要用 -M 而並非 -o ,原因是 DVC 後續會去追蹤這份檔案,讓我們能夠快速地去做成效的比較。而因為這樣的話沒有輸出檔案,所以可以用 -f 的方式來指定輸出的檔案。

$ dvc run -d script/evaluate.py -d script/config.py -d dataset/test.csv -d model/model.pth -M log/eval.txt -f Dvcfile python script/evaluate.py

執行完後,會建立 log/eval.txt以及 Dvcfile ,會取名為Dvcfile是因為 稍後在做 dvc repro 時若未指定檔案,則會用讀取 default 的檔案,而default 檔案的檔名就為 Dvcfile,而Dvcfile 和上面步驟的 .dvc 檔案是一樣的,記載 md5cache 等資訊。

而由於我們剛剛是利用 -M 的方式去指定檔案路徑,因此利用 DVC 內建的語法 dvc metrics show 就能夠快速地看到模型表現。

$ dvc metrics show
log/eval.txt:
input size: 224
classes number: 7
use pretrained: True
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8626760563380281
$ cat log/eval.txt
input size: 224
classes number: 7
use pretrained: True
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8626760563380281

最後,我們可以把建立出來的檔案用 git 來管理。

$ git add .
$ git commit -m "evaluation"

4. Reproduce

如果我今天想要修改 epochs 從,21 修改為 51時,那我會這樣做

$ git checkout -b epochs51
$ vi script/config.py # 將epochs = 21 改為 epochs = 51
$ dvc repro

由於我們一開始從資料分割到最後評估模型的過程,都有手動的將輸入輸出去做設定,因此 DVC 就可以很快地做到自動化,一條龍式的完成。

$ dvc metrics show -a
epochs51:
log/eval.txt:
input size: 224
classes number: 7
use pretrained: True
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8661971830985915
master:
log/eval.txt:
input size: 224
classes number: 7
use pretrained: True
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8661971830985915

5. Merge the model to master

為了能夠練習 merge ,因此我多訓練了一組沒有使用 pretrained model 的模型(先切回 master 再 checkout -b)

$ git checkout master
$ git checkout -b wo_pretrained
$ vi script/config.py # 將usePretrained = True 改為 False
$ dvc repro
$ git add .
$ git commit -m "without pretrained"
$ dvc metrics show -a
epochs51:
log/eval.txt:
input size: 224
classes number: 7
use pretrained: True
epochs: 51
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8626760563380281
master:
log/eval.txt:
input size: 224
classes number: 7
use pretrained: True
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.8661971830985915
wo_pretrained:
log/eval.txt:
input size: 224
classes number: 7
use pretrained: False
epochs: 21
batch size: 32
learning rate: 0.001
momentum: 0.9
accuracy: 0.30633802816901406

若我們要將 wo_pretrained 以及 epochs51 merge 在一起的話,需要做一些處理

$ git merge epochs51
Auto-merging train.csv.dvc
CONFLICT (content): Merge conflict in train.csv.dvc
Auto-merging script/config.py
CONFLICT (content): Merge conflict in script/config.py
Auto-merging model.pth.dvc
CONFLICT (content): Merge conflict in model.pth.dvc
Auto-merging log/eval.txt
CONFLICT (content): Merge conflict in log/eval.txt
Auto-merging Dvcfile
CONFLICT (content): Merge conflict in Dvcfile
Automatic merge failed; fix conflicts and then commit the result.

會發現有許多地方都有 conflict ,因此會需要手動的調整,並且保留你認為比較好的模型的參數,以 train.csv.dvc 為例:

<<<<<<< HEAD
md5: 8d2bb1a5dd7542df80a684cf04179444
=======
md5: 0a72f2ce076e4d8f5ba6cd476d1cb464
>>>>>>> epochs51
cmd: python script/split_train_test.py
wdir: .
deps:
- md5: 826ea439f28fb04923de739af8c26b5d
path: script/split_train_test.py
<<<<<<< HEAD
- md5: acb7b60a7d2f31a2ea5c527731f3b5f7
=======
- md5: 1ffb468a9e98a8248f5071286cfd0111
>>>>>>> epochs51
path: script/config.py
- md5: 87c46f0402b54b960b294ef7791f7cf8
path: dataset/annotation.csv
outs:
- md5: 896389741ff20a2055acfe5c65893bf1
path: dataset/train.csv
cache: true
metric: false
persist: false
- md5: 1ea600a9a7b720cd28fe99ff6d1c3e70
path: dataset/test.csv
cache: true
metric: false
persist: false

會把 HEAD 的部分刪除,保留 epochs51 ,如下所示:


md5: 0a72f2ce076e4d8f5ba6cd476d1cb464
cmd: python script/split_train_test.py
wdir: .
deps:
- md5: 826ea439f28fb04923de739af8c26b5d
path: script/split_train_test.py
- md5: 1ffb468a9e98a8248f5071286cfd0111
path: script/config.py
- md5: 87c46f0402b54b960b294ef7791f7cf8
path: dataset/annotation.csv
outs:
- md5: 896389741ff20a2055acfe5c65893bf1
path: dataset/train.csv
cache: true
metric: false
persist: false
- md5: 1ea600a9a7b720cd28fe99ff6d1c3e70
path: dataset/test.csv
cache: true
metric: false
persist: false

照著這樣的步驟,就可以一一的把 conflict 都解完,修改完後可以輸入 dvc checkout,目的是為了將 DVC 控制的 pipeline 切回剛剛設定的,並且重新 reproduce 。

$ dvc checkout
$ dvc repro
$ git add .
$ git commit -m "merge wo_pretrained and epochs51"

最後,可以再切回 master 去 merge 原本的 branch ,就完成了。

$ git checkout master
$ git merge wo_pretrained
Fast-forward
Dvcfile | 8 ++++----
log/eval.txt | 4 ++--
model.pth.dvc | 12 ++++++------
script/config.py | 2 +-
script/train.py | 1 -
train.csv.dvc | 4 ++--
6 files changed, 15 insertions(+), 16 deletions(-)

6. Pushing data to the cloud

我們這邊以上傳到 gcp 為範例,因此會需要先安裝套件

$ pip install dvc[gs]

並且需要先在 GCP 上去做設定,取得憑證資料,才能夠順利上傳,方法如下:

  1. 進入 API和服務 資訊主頁

2. 點擊 啟用 API 和服務

3. 在搜尋欄位輸入 Cloud Storage

4. 點擊連結

5. 點擊 管理

6. 選擇左方的憑證Create Credentials服務帳戶金鑰

7–1. 點選 請選取 … 就會跳出 新增服務帳戶

7–2. 輸入帳戶名稱,以及選擇角色「Storage物件建立者」以及「Storage管理員」

8. 建立後,會輸出一份 .json 檔案,可以將這份檔案加入專案當中。

9. 在搜尋欄位輸入 Storage 並且選擇 Storage

10. 點選 建立 Bucket

11–1. 輸入資料,最重要的為上面兩項 ,下面兩項則可以用 Default值即可。

12. 建立好後,就可以去查看詳細的資料

完成設定 GCP 設定後,即可回到我們的專案來,並且把剛剛的憑證 gcp-test.json 加入 .dvc/config 當中。

$ dvc remote add -d upstream gs://cosmetics-bucket/
$ dvc remote modify upstream credentialpath gcp-test.json
$ dvc push
Preparing to upload data to 'gs://cosmetics-bucket/'
Preparing to collect status from gs://cosmetics-bucket/
Collecting information from local cache...
[##############################] 100%
Collecting information from remote cache...
[##############################] 100
[##############################] 100% Analysing status
[##############################] 100% log/test-output.txt
[##############################] 100% dataset/test.csv
[##############################] 100% log/train-output.txt
[##############################] 100% dataset/train.csv
[##############################] 100% model/model.pth

DVC 會將 cache 裡面的資料全部都上傳上去,如下圖所示:

7. Pulling data from the cloud

pull 的話很簡單,直接利用 git 的指令即可

$ git clone https://github.com/dmpetrov/new_tag_classifier.git
$ dvc pull

Next…

下一章,會開始進入 Flask 套件的使用,讓你在最後完成的模型,能夠架成一個服務,提供大家使用。

Day 5: Flask 套件 → 架成 Server

如果喜歡我的文章內容,請幫我多多鼓掌

1 個鼓掌:喜歡這篇的內容

10個鼓掌:期待這一系列的課程

30個鼓掌:希望未來能有更多相關文章

--

--

PJ Wang

台大資工所碩畢 / 設計思考教練 / 系統思考顧問 / 資料科學家 / 新創 / 科技 + 商業 + 使用者