DVC-2: Model Versiyonlama

Önceki yazıda DVC ile veri versiyonlama işlemi gerçekleştirmiştir. Bu yazıda DVC ile model versiyonlama yapacağız.

İlk olarak bu linkten gerekli veri setini indirebilirsiniz. Gerekli olan kütüphaneleri kurmakla başlayalım;

pip install dvclive omegaconf colorama scikit-learn pandas joblib 

1 – ) ‘pipeline-test’ isminde bir klasör açıyorum ve içerisine bazı klasör ve dosyalar oluşturuyorum. Projemizdeki dosyaların dizin yapısı şu şekildedir;

  • data: Veri dosyalarını barındıran dizindir. ‘raw’ klasörüne işlenmemiş veri setini yükleyeceğiz, ‘processed’ klasörüne ise işlenmiş veri setini kaydedeceğiz.
  • models: Oluşan modelleri kaydedeceğimiz dizindir.
  • results: Model sonuçlarını kaydedeceğimiz dizindir.
  • prepare.py: Modeli eğitim ve test setlerine bölme kodları.
  • process.py: Veri setini ön işleme kodları.
  • train.py: Model eğitme kodları.
  • evaluate.py: Modeli sınama kodları.
  • params.yaml: Veri setini işlemeden modeli sınamaya kadar kullanılacak olan tüm parametrelerin tanımlandığı yaml dosyasıdır.
  • dvc.yaml: Model eğitim sürecini otomatikleştirerek modeli takip edebileceğimiz dvc üzerinde bir ‘experiment’ oluşturan yaml dosyasıdır.
alper@alper:/mnt/d/work2/data-versioning-dvc/cyberds/pipeline-test$ ls -R
.:
data  dvc.yaml  evaluate.py  models  params.yaml  prepare.py  process.py  results  train.py

./data:
processed  raw

./data/processed:

./data/raw:
winequality-red.csv

./models:

./results:

2 – ) DVC yi kullanabilmek için projemize git eklenmiş olması gerekiyordu. ‘git init’ yazarak projemize git ekliyoruz ve ‘dvc init’ yazarak da projemize DVC’yi ekliyoruz.

alper@alper:/mnt/d/work2/data-versioning-dvc/cyberds/pipeline-test$ git init
alper@alper:/mnt/d/work2/data-versioning-dvc/cyberds/pipeline-test$ dvc init

3 – ) Projemiz için gerekli dosyaları da ayarladığımıza göre artık kodlamaya geçebiliriz. İlk olarak params.yaml dosyasını hazırlayalım. Bu YAML dosyasında verilere erişip kod içinde kullanabilmek için ‘OmegaConf’ kütüphanesini kullanıyoruz. YAML dosyası içerisindeki en baştaki değerler ‘stage’ yani durumlardır. Bunlar yürütülecek olan process.py, train.py gibi çeşitli dosyalarda kullanılacak olan parametrelerin bölünmüş halidir. Her bir durum içindeki parametre o durumda kullanılacak olan parametrelerdir. Model için takip edilecek adımlar şunlardır;

  1. Kaynak veri setinin ‘quality’ sütunu bizim hedef değişkenimiz. Veri seti %70’i eğitim, %30’u sınama için ayrılacak ve ayrı ayrı ‘csv’ dosyalarında kaydedilecek.
  2. Daha sonra kaydedilen csv dosyalarına ölçeklendirme yapılacak ve bu ölçeklendirilmiş dosyalar kaydedilecek.
  3. Model eğitmek için ‘RandomForestClassifier’ ve birtakım hiperparametreler kullanılacak. Daha sonra eğitilen model kaydedilecek.
  4. Eğitilmiş model dosyasını kullanarak ölçeklendirilmiş sınama verisi üzerinde test işlemi yapılacak ve ‘accuracy’ ve ‘f1’ skorları bir yaml dosyasına kaydedilecek.
data:
  raw_data_path: ./data/raw/winequality-red.csv
  target: quality
  test_size: 0.3
  random_state: 42
  train_csv_path: ./data/raw/train.csv
  test_csv_path: ./data/raw/test.csv
  

features:
  train_features_path: ./data/processed/train.joblib
  test_features_path: ./data/processed/test.joblib

train:
  n_estimators:  100
  min_samples_split: 2
  max_features: sqrt
  model_path: ./models/model.joblib

evaluate:
  results_path: ./results/results.yaml

4 – ) prepare.py dosyasında veri setimizi %70’i eğitim ve %30’u sınama olacak şekilde bölüyoruz ve daha sonra bölünmüş veri setini csv dosyalarına kaydediyoruz.

import colorama
from colorama import Fore
colorama.init(autoreset=True)

import pandas as pd
from omegaconf import OmegaConf
from sklearn.model_selection import train_test_split

def prepare(config):
    print(f"{Fore.YELLOW}[+] Preparing data.")
    df = pd.read_csv(config.data.raw_data_path)

    target_map = {3: "Low", 4: "Low", 5: "Medium", 6: "Medium", 7: "High", 8: "High"}

    test_size = config.data.test_size
    train_df, test_df = train_test_split(df, test_size=test_size, random_state=config.data.random_state)
    
    train_df[config.data.target] = train_df[config.data.target].map(target_map)
    test_df[config.data.target] = test_df[config.data.target].map(target_map)
    train_df.to_csv(config.data.train_csv_path, index=False)
    test_df.to_csv(config.data.test_csv_path, index=False)

if __name__ == "__main__":
    config = OmegaConf.load("./params.yaml")
    prepare(config)

5 – ) process.py dosyasında verimizi ölçeklendireceğiz. Veri setimiz tamamen numerik değerler olduğu için ‘StandardScaler’ kullanarak tüm değerlerimizi ölçeklendiriyoruz. Daha sonra ölçeklendirilmiş veriyi bir ‘joblib’ dosyası olarak kaydediyoruz. Bu dosyaları modeli eğitirken ve sınarken kullanacağız.

import colorama
from colorama import Fore
colorama.init(autoreset=True)

import joblib
import pandas as pd
from omegaconf import OmegaConf
from sklearn.preprocessing import StandardScaler

def process(config):
    print(f"{Fore.BLUE}[+] Processing data.")
    train_df = pd.read_csv(config.data.train_csv_path)
    test_df = pd.read_csv(config.data.test_csv_path)

    sc = StandardScaler()
    train_scaled = sc.fit_transform(train_df.drop(config.data.target, axis=1))
    test_scaled = sc.fit_transform(test_df.drop(config.data.target, axis=1))

    joblib.dump(train_scaled, config.features.train_features_path)
    joblib.dump(test_scaled, config.features.test_features_path)

if __name__ == "__main__":
    config = OmegaConf.load('./params.yaml')
    process(config)

6 – ) train.py modelimizi eğiteceğiz. Daha önceden ölçeklendirdiğimiz eğitim verilerini kullanarak ve YAML dosyasında belirttiğimiz hiperparametreleri kullanarak modelimizi eğitiyoruz. Burdaki ‘Live’ modelimi eğitirken kullandığımız hiperparametreleri loglamamızı sağlıyor. Daha sonra bu loglanan değerleri ‘Visual Studio Code’ üzerinde inceleyeceğiz. Eğitim bittikten sonra eğitilen modeli kaydediyoruz.

import colorama
from colorama import Fore
colorama.init(autoreset=True)

import joblib
import pandas as pd
from omegaconf import OmegaConf
from sklearn.ensemble import RandomForestClassifier
from dvclive import Live

def process(config):
    print(f"{Fore.RED}[+] Training model.")
    train_features = joblib.load(config.features.train_features_path)
    train_labels = pd.read_csv(config.data.train_csv_path)[config.data.target].values

    n_estimators = config.train.n_estimators
    min_samples_split = config.train.min_samples_split
    max_features = config.train.max_features

    with Live() as live:
        live.log_param("n_estimators", n_estimators)
        live.log_param("min_samples_split", min_samples_split)
        live.log_param("max_features", max_features)

        model = RandomForestClassifier(n_estimators=n_estimators, min_samples_split=min_samples_split, max_features=max_features)
        model.fit(train_features, train_labels)

    joblib.dump(model, config.train.model_path)  

if __name__ == "__main__":
    config = OmegaConf.load('./params.yaml')
    process(config)

7 – ) evaluate.py eğittiğimiz modeli sınıyoruz. ‘Live’ ile eğitim aşamasındaki hiperparametrelerimizi loglamıştık. Bu sefer ‘Live’ kullanarak metrikleri logluyoruz ve karmaşıklık matrisi (confusion matrix) oluşturup kaydediyoruz.

import colorama
from colorama import Fore
colorama.init(autoreset=True)

import joblib
import pandas as pd
from omegaconf import OmegaConf
from sklearn.metrics import accuracy_score, f1_score
from dvclive import Live

def process(config):
    print(f"{Fore.GREEN}[+] Evaluating model.")
    test_inputs = joblib.load(config.features.test_features_path)
    test_labels = pd.read_csv(config.data.test_csv_path)[config.data.target].values
    
    model = joblib.load(config.train.model_path)

    with Live() as live:
        predicted = model.predict(test_inputs)
        
        accuracy = accuracy_score(test_labels, predicted)
        f1 = f1_score(test_labels, predicted, average="weighted")

        live.log_metric("test/accuracy", accuracy)
        live.log_metric("test/f1", f1)
        live.log_sklearn_plot("confusion_matrix", test_labels, predicted, name="test/confusion_matrix", title="Test Confusion Matrix")

        results_json = {"accuracy": accuracy, "f1": f1}
        OmegaConf.save(results_json, config.evaluate.results_path)

if __name__ == "__main__":
    config = OmegaConf.load('./params.yaml')
    process(config)

8 – ) Tüm aşamalar için gereken kodları tamamladık. Şimdi DVC ile bunları otomatik olarak çalıştıracak bir YAML dosyası hazırlayalım. ‘Stages’ ile her bir adımda çalıştırılacak olan işlemleri bölümlere ayırıyoruz. ‘cmd’ etiketi her bir adımda çalıştırılacak python dosyasını temsil ediyor. ‘deps’ o python dosyasını çalıştırmak için gereken bileşenleri ve dosyaları temsil ediyor. ‘params’ ise ‘params.yaml’ dosyasında kullanacağı bölüm adını temsil ediyor. ‘outs’ kod çalıştırıldıktan sonra üretilecek çıktıları temsil ediyor.

stages:
  prepare:
    cmd: python3 ./prepare.py
    deps:
    - ./prepare.py
    - ./data/raw/winequality-red.csv
    params:
    - data
    outs:
    - ./data/raw/train.csv
    - ./data/raw/test.csv
  process:
    cmd: python3 ./process.py
    deps:
    - ./process.py
    - ./data/raw/train.csv
    - ./data/raw/test.csv
    params:
    - features
    outs:
    - ./data/processed/train.joblib
    - ./data/processed/test.joblib
  train:
    cmd: python3 ./train.py
    deps:
    - ./train.py
    - ./data/raw/train.csv
    - ./data/processed/train.joblib
    params:
    - train
    outs:
    - ./models/model.joblib
  evaluate:
    cmd: python3 ./evaluate.py
    deps:
    - ./evaluate.py
    - ./data/raw/test.csv
    - ./data/processed/test.joblib
    - ./models/model.joblib
    params:
    - evaluate

9 – ) ‘dvc dag’ komutu ile aşamaları görselleştirelim. Bu görselde yukarıdan aşağıya doğru sırasıyla çalıştırılacak kodları gösteriliyor.

alper@alper:/mnt/d/work2/data-versioning-dvc/cyberds/pipeline$ dvc dag
                    +---------+
                    | prepare |
                  **+---------+****
              ****        *        ***
           ***            *           ***
         **               *              ****
+---------+               *                  **
| process |               *                   *
+---------+***            *                   *
      *       ****        *                   *
      *           ***     *                   *
      *              **   *                   *
      **             +-------+               **
        ***          | train |           ****
           ****      +-------+        ***
               ***        *        ***
                  ***     *    ****
                     **   *  **
                    +----------+
                    | evaluate |
                    +----------+

10 – ) ‘dvc run exp’ komutu ile ilk modelimi oluşturalım.

alper@alper:/mnt/d/work2/data-versioning-dvc/cyberds/pipeline-test$ dvc exp run
Running stage 'prepare':
> python3 ./prepare.py
[+] Preparing data.
Generating lock file 'dvc.lock'
Updating lock file 'dvc.lock'

Running stage 'process':
> python3 ./process.py
[+] Processing data.
Updating lock file 'dvc.lock'

Running stage 'train':
> python3 ./train.py
[+] Training model.
Updating lock file 'dvc.lock'

Running stage 'evaluate':
> python3 ./evaluate.py
[+] Evaluating model.

11 – ) İlk modelimizin n_estimators değeri 100 idi. YAML dosyasında bunu 300 yapıp tekrar ‘dvc run exp’ komutunu çalıştırarak bir model daha eğitiyorum. Kodun sadece model eğitme kısmında değişiklik yaptığım için ilk iki adım prepare ve process kısmı çalışmadı.

Stage 'prepare' didn't change, skipping
Stage 'process' didn't change, skipping
Running stage 'train':
> python3 ./train.py
[+] Training model.
Updating lock file 'dvc.lock'

Running stage 'evaluate':
> python3 ./evaluate.py
[+] Evaluating model.
Updating lock file 'dvc.lock'

12 – ) Şimdi eğittiğimiz modellerin sonuçlarını ‘Visual Studio Code’ üzerinden inceleyelim. Bunun için marketten ‘DVC’ eklentisini kurmanız gerek. Eklentiyi kurduktan sonra soldaki menüden DVC logosuna tıklayıp ‘show experiments’ butonuna basıyoruz. Bu kısımda modelimizdeki parametreler ve metrikler gözüküyor.

13 – ) ‘Show plots’ butonunda ise modeli eğitirken oluşturduğumuz karmaşıklık matrisleri görünüyor.