Pelatihan Berbasis Populasi di Ray Tune

Nov 28 2022
Penyesuaian hyperparameter adalah langkah kunci dalam pemilihan model. Hyperparameter seperti pengaturan, jika Anda tidak menanganinya dengan tepat, dapat berdampak buruk pada hasil model.

Penyesuaian hyperparameter adalah langkah kunci dalam pemilihan model. Hyperparameter seperti pengaturan, jika Anda tidak menanganinya dengan tepat, dapat berdampak buruk pada hasil model. Penyetelan dapat dilakukan secara manual atau otomatis. Di dunia sekarang ini, karena kemampuan komputasi, jumlah hyperparameter yang tinggi, berbagai macam algoritme, dan pustaka pembantu seperti Ray, cara yang lebih disukai adalah menyetel hyperparameter secara otomatis.

Pada artikel ini, kita akan berbicara tentang Pelatihan Berbasis Populasi, menjelajahi Ray Tune, dan melihat contoh penyetelan hyperparameter. Repo GitHub:https://github.com/lukakap/pbt-tune.git

Foto oleh Gary Bendig di Unsplash

Apa yang dimaksud dengan PBT

Seperti yang telah kami sebutkan, kinerja model yang baik terkait dengan pemilihan hyperparameter yang benar. Pelatihan Berbasis Populasi adalah salah satu cara menawan untuk memilih hyperparameter. Ini terdiri dari dua bagian: pencarian acak dan pemilihan cerdas. Pada langkah pencarian acak, algoritma memilih beberapa kombinasi hyperparameter secara acak. Kemungkinan besar sebagian besar kombinasi akan memiliki skor kinerja rendah dan sebagian kecil kombinasi sebaliknya akan memiliki skor kinerja yang lebih baik/baik. Inilah pemilihan yang cerdas. Langkah kedua adalah dalam sebuah siklus sampai kita mencapai hasil yang diinginkan atau kita tidak menghabiskan jumlah iterasi. Langkah pemilihan yang cerdas berisi dua metode utama: exploit dan explore . Mengeksploitasi— ganti kombinasi hyperparameter dengan yang lebih menjanjikan, berdasarkan metrik kinerja. Jelajahi — ganggu hyperparameter secara acak (dalam banyak kasus dikalikan dengan beberapa faktor) untuk menambah kebisingan.

Pelatihan Berbasis Populasi memungkinkan melakukan dua hal yang berarti bersama-sama: memparalelkan pelatihan kombinasi hyperparameter, mempelajari dari populasi lainnya, dan segera mendapatkan hasil yang menjanjikan.

Bicara tentang Ray Tune

Ray Tune adalah pustaka python berbasis Ray untuk penyetelan hyperparameter dengan algoritme terbaru seperti PBT. Kami akan mengerjakan Ray versi 2.1.0. Perubahan dapat dilihat pada catatan rilis di bawah ini. Kami juga akan menyebutkan perubahan penting di jalan.

Sebelum beralih ke contoh praktis, mari kita membahas beberapa konsep dasar. Dapat dilatih - adalah tujuan yang membantu algoritme untuk mengevaluasi konfigurasi. Itu dapat memiliki Class API atau Function API, tetapi menurut dokumentasi ray tune, Function API direkomendasikan. Ruang Pencarian — rentang nilai untuk hyperparameter. Trials — Tuner akan menghasilkan beberapa konfigurasi dan menjalankan proses pada konfigurasi tersebut, sehingga proses yang berjalan pada konfigurasi disebut Trial. Algoritma Pencarian — menyarankan konfigurasi hyperparameter, secara default Tune menggunakan pencarian acak sebagai algoritma pencarian. Penjadwal — Berdasarkan hasil yang dilaporkan selama proses pelatihan, penjadwal memutuskan apakah berhenti atau melanjutkan. Konsep bermakna selanjutnya adalahpos pemeriksaan . Checkpointing berarti menyimpan hasil antara, yang diperlukan untuk melanjutkan dan kemudian melanjutkan pelatihan.

Dalam kebanyakan kasus Algoritma Pencarian dan Penjadwal dapat digunakan bersama dalam proses penyetelan, tetapi ada pengecualian. Salah satu kasus, ketika mereka tidak digunakan bersama adalah Pelatihan Berbasis Populasi. Dalam dokumen Ray Tune, PBT ada di bagian penjadwal, tetapi keduanya sekaligus. Ini adalah penjadwal karena menghentikan uji coba berdasarkan hasil dan merupakan pencari karena memiliki logika untuk membuat konfigurasi baru.

Kami menggunakan Kumpulan Data Perumahan Boston yang terkenal (https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html). Kita dapat mengimpor dataset ini dari sklearn.

Satu perubahan berarti dalam Ray Tune adalah API eksekusi. tune.run() telah berubah menjadi Tuner().fit. Sebelum pembaruan, kami melewati parameter secara terpisah, tetapi dalam versi baru, kelas konfigurasi diperkenalkan yang menyederhanakan banyak hal. Pertama-tama, kelompokkan parameter terkait yang membuat kode eksekusi lebih mudah dibaca dan dipahami. Dan kedua, saat Anda menggunakan Ray Tune dalam sebuah proyek, beberapa konfigurasi sama untuk beberapa algoritme, sehingga Anda dapat membuat satu objek kelas konfigurasi umum dan berpindah-pindah algoritme, yang membuat hidup lebih mudah.

impor

# imports
import json
import os

from joblib import dump, load
from lightgbm import LGBMRegressor
from ray import tune
from ray.air.config import CheckpointConfig
from ray.air.config import RunConfig
from ray.tune.schedulers import PopulationBasedTraining
from ray.tune.tune_config import TuneConfig
from ray.tune.tuner import Tuner
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split

Mari kita mulai dengan yang bisa dilatih. Seperti yang telah kami sebutkan, ada dua API yang dapat dilatih: berbasis fungsi dan berbasis kelas. Kami akan menulis yang dapat dilatih dengan API Kelas.

class TrainableForPBT(tune.Trainable):
    def setup(self, config):
        pass
    
    def step(self):
        pass

Dalam penyiapan , kita perlu memiliki x_train dan y_train untuk memperkirakan efisiensi model uji coba di langkah selanjutnya. Tentu saja, setup adalah fungsi (tune.Trainable) kelas induk, tetapi ini memberi kita kemungkinan untuk menambahkan argumen tambahan. Juga, kita perlu menginisialisasi lgbm regressor/model di setup . Kami akan melatih ulang model kami pada setiap iterasi, tetapi pada yang pertama kami hanya ingin menyesuaikan model, oleh karena itu perlu mengandalkan iterasi mana yang kami ikuti. Tidak lebih pada saat ini.

def setup(self, config, x_train, y_train):
    self.current_config = config
    self.x_train = x_train
    self.y_train = y_train
    # need to add about model
    self.model = LGBMRegressor(**self.current_config)
    # count on which iteration are we in
    self.current_iteration = 0
    self.current_step_score = None

def step(self):
    self.current_iteration += 1
    if self.current_iteration == 1:
        self.model.fit(self.x_train, self.y_train)
    else:
        self.model.fit(self.x_train, self.y_train, init_model=self.model)

    self.current_step_score = cross_val_score(estimator=self.model, X=self.x_train, y=self.y_train,
                                              scoring='r2', cv=5).mean()
    results_dict = {"r2_score": self.current_step_score}
    return results_dict

Mulailah dengan save_checkpoint . Kami akan menggunakan pustaka joblib untuk menyimpan dan memulihkan model. Apa yang perlu kita selamatkan? Pertama-tama — model, karena kita selalu membutuhkan model iterasi sebelumnya (init_model) untuk iterasi berikutnya, kita juga dapat menyimpan nomor iterasi saat ini dan skor langkah saat ini.

def save_checkpoint(self, tmp_checkpoint_dir):
    path = os.path.join(tmp_checkpoint_dir, "checkpoint")
    with open(path, "w") as f:
        f.write(json.dumps(
            {"current_score": self.current_step_score, "current_step": self.current_iteration}))

    path_for_model = os.path.join(tmp_checkpoint_dir, 'model.joblib')
    dump(self.model, path_for_model)

    return tmp_checkpoint_dir

def load_checkpoint(self, tmp_checkpoint_dir):
    with open(os.path.join(tmp_checkpoint_dir, "checkpoint")) as f:
        state = json.loads(f.read())
        self.current_step_score = state["current_score"]
        self.current_iteration = state["current_step"]

    path_for_model = os.path.join(tmp_checkpoint_dir, 'model.joblib')
    self.model = load(path_for_model)

Dalam proses pelatihan, kami memiliki banyak Pelatihan sebagai sampel konfigurasi. Setiap Trainable membutuhkan beberapa detik untuk memulai. Dengan fitur reuse_actor, kita dapat menggunakan kembali Trainable yang sudah dimulai untuk beberapa konfigurasi/hyperparameter baru. Jadi kita akan membutuhkan lebih sedikit Trainable dan waktu yang dihabiskan untuk start-up juga akan lebih sedikit.

Mari terapkan reset_config , yang memberikan hyperparameter baru. Di reset_config setiap variabel perlu disesuaikan dengan hyperparameter baru, seperti new setup . Ada satu pertanyaan rumit, setiap kali konfigurasi berbeda menukar Trainable yang sama, apakah mereka memulai proses dari awal, karena di reset_config kita menulisnya seperti awal? Sebenarnya tidak, karena setelah reset_config , Trainable memanggil load checkpoint jika ada, maka pelatihan akan dilanjutkan dari pemberhentian/checkpoint terakhir.

def reset_config(self, new_config):
    self.current_config = new_config
    self.current_iteration = 0
    self.current_step_score = None
    self.model = LGBMRegressor(**self.current_config)
    return True

class TrainableForPBT(tune.Trainable):
    def setup(self, config, x_train, y_train):
        self.current_config = config
        self.x_train = x_train
        self.y_train = y_train
        # need to add about model
        self.model = LGBMRegressor(**self.current_config)
        # count on which iteration are we in
        self.current_iteration = 0
        self.current_step_score = None

    def step(self):
        self.current_iteration += 1
        if self.current_iteration == 1:
            self.model.fit(self.x_train, self.y_train)
        else:
            self.model.fit(self.x_train, self.y_train, init_model=self.model)

        self.current_step_score = cross_val_score(estimator=self.model, X=self.x_train, y=self.y_train,
                                                  scoring='r2', cv=5).mean()
        results_dict = {"r2_score": self.current_step_score}
        return results_dict

    def save_checkpoint(self, tmp_checkpoint_dir):
        path = os.path.join(tmp_checkpoint_dir, "checkpoint")
        with open(path, "w") as f:
            f.write(json.dumps(
                {"current_score": self.current_step_score, "current_step": self.current_iteration}))

        path_for_model = os.path.join(tmp_checkpoint_dir, 'model.joblib')
        dump(self.model, path_for_model)

        return tmp_checkpoint_dir

    def load_checkpoint(self, tmp_checkpoint_dir):
        with open(os.path.join(tmp_checkpoint_dir, "checkpoint")) as f:
            state = json.loads(f.read())
            self.current_step_score = state["current_score"]
            self.current_iteration = state["current_step"]

        path_for_model = os.path.join(tmp_checkpoint_dir, 'model.joblib')
        self.model = load(path_for_model)

    def reset_config(self, new_config):
        self.current_config = new_config
        self.current_iteration = 0
        self.current_step_score = None
        self.model = LGBMRegressor(**self.current_config)
        return True

Sekarang kita dapat membuat beberapa konfigurasi dan menjalankan eksperimen Tune. Tuner memiliki 4 parameter: trainable , param_space , tune_config dan run_config . Dapat dilatih sudah diterapkan. Mari kita definisikan param_space.

Param_space sama dengan ruang pencarian yang telah disebutkan. Pertama, kita perlu menentukan daftar parameter yang akan kita sesuaikan. Untuk mempermudah, pilih 3 parameter: learning_rate, num_leaves, max_depth.

Tune memiliki Search Space API sendiri, jadi kita harus menggunakannya saat menentukan ruang pencarian. Nama ruang pencarian bersifat intuitif, jadi mari kita lihat hasilnya tanpa basa-basi lagi.

param_space = {
    "params": {
        "learning_rate": tune.loguniform(1e-5, 1e-1), #between 0.00001 and 0.1
        "num_leaves":  tune.randint(5, 100), #between 5 and 100(exclusive)
        "max_depth": tune.randint(1, 9), #between 1 and 9(exclusive)
    },
}

Parameter pertama jadwal Pelatihan Basis Populasi adalah time_attr. Ini adalah atribut hasil pelatihan untuk perbandingan, yang seharusnya menjadi sesuatu yang meningkat secara monoton. Kami memilih iterasi_latihan sebagai atribut waktu, jadi ketika kami menyebutkan time_attr di mana saja, itu berarti iterasi_latihan. perturbation_interval — seberapa sering gangguan harus terjadi. Jika kita sering melakukan perturbasi, maka kita juga perlu sering menyimpan checkpoint. Dengan ini, mari kita pilih perturbation_interval menjadi 4. burn_in_period — gangguan tidak akan terjadi sebelum jumlah interval (iterasi) ini terlewati. Tidaklah benar jika kita mengkloning keadaan model berkinerja terbaik ke model yang berkinerja buruk sejak awal, karena skor kinerja tidak stabil pada tahap awal. Jadi berikan 10 iterasi percobaan burn_in_period dan kemudian mulai gangguan. hyperparam_mutations adalah dikt dari hyperparameter dan ruang pencariannya, yang dapat terganggu. Kami ingin mengganggu semua hyperparameter dariparam_space dict, jadi hyperparam_mutations akan sama dengan param_space [“params”]. Kami tidak akan meneruskan argumen mode dan metrik di PopulationBasedTraining, seperti yang kami definisikan di TuneConfig.

pbt = PopulationBasedTraining(
            time_attr="training_iteration",
            perturbation_interval=4,
            burn_in_period=10,
            hyperparam_mutations=param_space["params"],
        )

tune_config = TuneConfig(metric="r2_score", mode="max", search_alg=None, scheduler=pbt, num_samples=15, reuse_actors=True)

checkpoint_config = CheckpointConfig(checkpoint_score_attribute="r2_score", 
                                     checkpoint_score_order="max", 
                                     checkpoint_frequency=4, 
                                     checkpoint_at_end=True)

run_config = RunConfig(name="pbt_experiment", 
                       local_dir='/Users/admin/Desktop/Dressler/Publications',
                       stop={"training_iteration": 30},
                       checkpoint_config=checkpoint_config)

X, y = load_boston(return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

trainable_with_parameters = tune.with_parameters(TrainableForPBT, x_train=x_train, y_train=y_train)

tuner = Tuner(trainable_with_parameters, param_space=param_space["params"], tune_config=tune_config, run_config=run_config)
analysis = tuner.fit()

Sekarang, kita dapat berinteraksi dengan hasil menggunakan objek ResultGrid (analisis). Dengan menggunakan get_best_result kita bisa mendapatkan hasil terbaik dari semua percobaan. Juga saya akan menunjukkan kepada Anda beberapa hasil yang berguna dari ResultGrid.

best_trial_id = analysis._experiment_analysis.best_trial.trial_id
best_result = analysis.get_best_result()
best_result_score = best_result.metrics['r2_score']
best_config = best_result.config
best_checkpoint = best_result.checkpoint
best_checkpoint_dir = best_result.checkpoint.as_directory()
print(f"BEST TRIAL ID: {best_trial_id}")
print(f"BEST RESULT SCORE: {best_result_score}")
print(f"BEST CHECKPOINT DIRECTORY: {best_checkpoint}")