Đào tạo dựa trên dân số trong Ray Tune
Điều chỉnh siêu tham số là một bước quan trọng trong việc lựa chọn mô hình. Hyperparameters cũng giống như các thiết lập, nếu bạn xử lý chúng không hợp lý có thể gây ảnh hưởng xấu đến kết quả của mô hình. Điều chỉnh có thể được thực hiện thủ công hoặc tự động. Trong thế giới ngày nay, do khả năng tính toán, số lượng siêu đường kính lớn, nhiều thuật toán và thư viện trợ giúp như Ray, cách ưa thích là tự động điều chỉnh siêu đường kính.
Trong bài viết này, chúng ta sẽ nói về Đào tạo dựa trên dân số, khám phá Ray Tune và xem một ví dụ về điều chỉnh siêu tham số. Kho lưu trữ GitHub:https://github.com/lukakap/pbt-tune.git
PBT có nghĩa là gì
Như chúng tôi đã đề cập, hiệu suất tốt của mô hình có liên quan đến việc lựa chọn chính xác các siêu đường kính. Đào tạo dựa trên dân số là một trong những cách hấp dẫn để lựa chọn siêu tham số. Nó bao gồm hai phần: tìm kiếm ngẫu nhiên và lựa chọn thông minh. Trong bước tìm kiếm ngẫu nhiên, thuật toán chọn một số tổ hợp siêu đường kính một cách ngẫu nhiên. Có khả năng cao là hầu hết các kết hợp sẽ có điểm hiệu suất thấp và ngược lại, một phần nhỏ các kết hợp sẽ có điểm hiệu suất tốt hơn/tốt hơn. Ở đây có sự lựa chọn thông minh. Bước thứ hai là trong một chu kỳ cho đến khi chúng tôi đạt được kết quả mong muốn hoặc chúng tôi không sử dụng hết số lần lặp lại. Bước lựa chọn thông minh bao gồm hai phương pháp chính: khai thác và khám phá . Khai thác— thay thế tổ hợp siêu tham số bằng tổ hợp hứa hẹn hơn, dựa trên chỉ số hiệu suất. Khám phá - làm xáo trộn ngẫu nhiên các siêu tham số (trong hầu hết các trường hợp, nó được nhân với một số yếu tố) để thêm nhiễu.
Đào tạo dựa trên dân số cho phép thực hiện hai việc có ý nghĩa cùng nhau: đào tạo song song các kết hợp siêu tham số, nghiên cứu từ phần còn lại của dân số và nhanh chóng nhận được kết quả đầy hứa hẹn.
Nói về Ray Tune
Ray Tune là một thư viện python dựa trên Ray để điều chỉnh siêu tham số với các thuật toán mới nhất như PBT. Chúng tôi sẽ làm việc trên Ray phiên bản 2.1.0. Những thay đổi có thể được nhìn thấy trong ghi chú phát hành bên dưới. Chúng tôi cũng sẽ đề cập đến những thay đổi quan trọng trong cách thức.
Trước khi chuyển sang các ví dụ thực tế, chúng ta hãy xem qua một số khái niệm cơ bản. Có thể huấn luyện — là mục tiêu giúp thuật toán đánh giá cấu hình. Nó có thể có API lớp hoặc API chức năng, nhưng theo tài liệu điều chỉnh tia, nên sử dụng API chức năng. Không gian tìm kiếm - phạm vi giá trị cho siêu tham số. Bản dùng thử — Tuner sẽ tạo một số cấu hình và chạy các quy trình trên chúng, do đó, quy trình chạy trên một cấu hình được gọi là Bản dùng thử. Thuật toán tìm kiếm — đề xuất các cấu hình siêu tham số, theo mặc định, Tune sử dụng tìm kiếm ngẫu nhiên làm thuật toán tìm kiếm. Bộ lập lịch — Dựa trên kết quả được báo cáo trong quá trình đào tạo, bộ lập lịch quyết định dừng hay tiếp tục. Khái niệm có ý nghĩa tiếp theo làđiểm kiểm tra . Điểm kiểm tra có nghĩa là lưu kết quả trung gian, cần thiết để tiếp tục và sau đó tiếp tục đào tạo.
Trong hầu hết các trường hợp, Thuật toán tìm kiếm và Trình lập lịch biểu có thể được sử dụng cùng nhau trong quá trình điều chỉnh, nhưng vẫn có ngoại lệ. Một trong những trường hợp khi chúng không được sử dụng cùng nhau là Đào tạo dựa trên dân số. Trong tài liệu Ray Tune, PBT nằm trong phần lập lịch trình, nhưng nó có cả hai cùng một lúc. Nó là một công cụ lập lịch vì nó dừng các thử nghiệm dựa trên kết quả và là một công cụ tìm kiếm vì nó có logic để tạo một cấu hình mới.
Chúng tôi sử dụng Bộ dữ liệu Nhà ở Boston nổi tiếng (https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html). Chúng tôi có thể nhập tập dữ liệu này từ sklearn.
Một thay đổi có ý nghĩa trong Ray Tune là API thực thi. tuner.run() đã thay đổi thành Tuner().fit. Trước khi cập nhật, chúng tôi đã chuyển các tham số một cách riêng biệt, nhưng trong phiên bản mới, các lớp cấu hình đã được giới thiệu giúp đơn giản hóa rất nhiều thứ. Trước hết, nhóm các tham số liên quan lại với nhau giúp mã thực thi dễ đọc và dễ hiểu hơn. Và thứ hai, khi bạn sử dụng Ray Tune trong một dự án, một số cấu hình giống nhau đối với một số thuật toán, vì vậy bạn có thể tạo một đối tượng lớp cấu hình chung và di chuyển xung quanh các thuật toán, giúp cuộc sống dễ dàng hơn.
nhập khẩu
# 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
Hãy bắt đầu với đào tạo. Như chúng tôi đã đề cập, có hai API có thể đào tạo: dựa trên chức năng và dựa trên lớp. Chúng tôi sẽ viết có thể đào tạo với API lớp.
class TrainableForPBT(tune.Trainable):
def setup(self, config):
pass
def step(self):
pass
Trong phần thiết lập , chúng ta cần có x_train và y_train để ước tính hiệu quả của mô hình thử nghiệm trong các bước sau. Tất nhiên, thiết lập là hàm (tune.Trainable) của lớp cha, nhưng nó cho chúng ta khả năng thêm các đối số bổ sung. Ngoài ra, chúng ta cần khởi tạo biến hồi quy/mô hình lgbm trong phần thiết lập . Chúng tôi sẽ đào tạo lại mô hình của mình trên mỗi lần lặp lại, nhưng ở lần đầu tiên, chúng tôi chỉ muốn mô hình vừa với mô hình, do đó cần tính xem chúng tôi đang ở bước lặp nào. Không có gì hơn vào thời điểm này.
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
Bắt đầu với save_checkpoint . Chúng tôi sẽ sử dụng thư viện joblib để lưu và khôi phục mô hình. Chúng ta cần tiết kiệm những gì? Trước hết - mô hình, vì chúng ta luôn cần mô hình lặp trước đó (init_model) cho lần lặp tiếp theo, chúng ta cũng có thể lưu số lần lặp hiện tại và điểm số bước hiện tại.
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)
Trong quá trình đào tạo, chúng tôi có nhiều Trainables như mẫu cấu hình. Mỗi Trainable cần vài giây để bắt đầu. Với tính năng tái sử dụng_actor, chúng tôi có thể sử dụng lại Có thể huấn luyện đã bắt đầu cho nhiều cấu hình/siêu tham số mới. Vì vậy, chúng tôi sẽ cần ít khả năng đào tạo hơn và thời gian khởi động cũng sẽ ít hơn.
Hãy triển khai reset_config , cung cấp siêu tham số mới. Trong reset_config, mọi biến cần được điều chỉnh theo siêu tham số mới, nó giống như thiết lập mới . Có một câu hỏi khó, mỗi khi các cấu hình khác nhau hoán đổi cùng một Có thể huấn luyện, chúng có bắt đầu quá trình lại từ đầu không, do thực tế là trong reset_config chúng tôi viết nó giống như phần đầu? Trên thực tế, không, bởi vì sau reset_config , điểm kiểm tra tải cuộc gọi Trainable nếu tồn tại, do đó, quá trình đào tạo sẽ tiếp tục từ điểm dừng/điểm kiểm tra cuối cùng.
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
Bây giờ chúng ta có thể tạo một số cấu hình và chạy thử nghiệm Tune. Tuner có 4 tham số: có thể huấn luyện , param_space , tune_config và run_config . Có thể huấn luyện đã được triển khai. Hãy xác định param_space.
Param_space giống như không gian tìm kiếm đã đề cập. Trước tiên, chúng ta cần xác định danh sách các tham số mà chúng ta sẽ điều chỉnh. Để đơn giản bạn chọn 3 thông số: learning_rate, num_leaves, max_depth.
Tune có API không gian tìm kiếm riêng, vì vậy chúng ta nên sử dụng chúng khi xác định không gian tìm kiếm. Tên của không gian tìm kiếm là trực quan, vì vậy hãy xem kết quả mà không cần phải chờ đợi thêm.
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)
},
}
Tham số đầu tiên của lịch trình đào tạo cơ sở dân số là time_attr. Đó là thuộc tính kết quả đào tạo để so sánh, đáng lẽ phải là thứ tăng lên một cách đơn điệu. Chúng tôi chọn trainig_iteration làm thuộc tính thời gian, vì vậy khi chúng tôi đề cập đến time_attr ở bất kỳ đâu, điều đó có nghĩa là training_iteration. perturbation_interval - tần suất xảy ra nhiễu loạn. Nếu chúng ta thường xuyên nhiễu loạn, thì chúng ta cũng cần lưu các điểm kiểm tra thường xuyên. Do đó, hãy chọn perturbation_interval là 4. burn_in_period — nhiễu loạn sẽ không xảy ra trước khi số khoảng thời gian (lặp) này trôi qua. Sẽ không đúng nếu chúng ta sao chép trạng thái của những người hoạt động tốt nhất sang những mô hình hoạt động kém ngay từ đầu, vì điểm số hoạt động không ổn định ở giai đoạn đầu. Vì vậy, hãy lặp lại 10 lần thử nghiệm burn_in_period và sau đó bắt đầu nhiễu loạn. hyperparam_mutations là một lệnh của siêu tham số và không gian tìm kiếm của chúng, có thể bị nhiễu loạn. Chúng tôi muốn làm nhiễu loạn tất cả các siêu đường kính từparam_space dict, vì vậy hyperparam_mutations sẽ giống như param_space [“params”]. Chúng tôi sẽ không chuyển đối số chế độ và số liệu trong PeopleBasedTraining, như chúng tôi xác định chúng trong 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()
Bây giờ, chúng ta có thể tương tác với các kết quả bằng cách sử dụng đối tượng ResultGrid (phân tích). Sử dụng get_best_result, chúng tôi có thể nhận được kết quả tốt nhất từ tất cả các thử nghiệm. Tôi cũng sẽ cho bạn thấy một số kết quả hữu ích từ 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}")