Ray Tune의 인구 기반 훈련
하이퍼파라미터 튜닝은 모델 선택의 핵심 단계입니다. 하이퍼파라미터는 설정과 같아서 적절하게 다루지 않으면 모델 결과에 나쁜 영향을 미칠 수 있습니다. 조정은 수동 또는 자동으로 수행할 수 있습니다. 오늘날의 세계에서는 컴퓨팅 기능, 많은 수의 하이퍼파라미터, 매우 다양한 알고리즘 및 Ray와 같은 도우미 라이브러리로 인해 하이퍼파라미터를 자동으로 조정하는 것이 선호되는 방법입니다.
이 기사에서는 Population Based Training에 대해 이야기하고 Ray Tune을 살펴보고 하이퍼파라미터 튜닝의 예를 살펴보겠습니다. GitHub 저장소:https://github.com/lukakap/pbt-tune.git
PBT의 의미
이미 언급했듯이 모델의 좋은 성능은 하이퍼파라미터의 올바른 선택과 관련이 있습니다. Population Based Training은 매력적인 하이퍼파라미터 선택 방법 중 하나입니다. 무작위 검색과 영리한 선택의 두 부분으로 구성됩니다. 무작위 검색 단계에서 알고리즘은 하이퍼파라미터의 여러 조합을 무작위로 선택합니다. 대부분의 조합이 낮은 성과 점수를 가질 가능성이 높고 반대로 일부 조합은 더 좋은/좋은 성과 점수를 가질 가능성이 높습니다. 여기에 영리한 선택이 온다. 두 번째 단계는 원하는 결과를 얻거나 반복 횟수를 소진하지 않을 때까지 주기입니다. 영리한 선택 단계에는 exploit 및 explore 라는 두 가지 주요 방법이 포함 됩니다. 악용하다— 하이퍼파라미터의 조합을 성능 메트릭을 기반으로 더 유망한 것으로 바꿉니다. 탐색 — 노이즈를 추가하기 위해 하이퍼파라미터를 무작위로 교란합니다(대부분의 경우 몇 가지 요소를 곱함).
Population Based Training을 사용하면 두 가지 의미 있는 작업을 함께 수행할 수 있습니다. 즉, 하이퍼 매개변수 조합의 병렬 학습, 나머지 모집단에서 연구 및 유망한 결과를 즉시 얻을 수 있습니다.
Ray Tune에 대해 이야기하기
Ray Tune은 PBT와 같은 최신 알고리즘으로 하이퍼파라미터 튜닝을 위한 Ray 기반 Python 라이브러리입니다. 우리는 Ray 버전 2.1.0에서 작업할 것입니다. 변경 사항은 아래 릴리스 정보에서 확인할 수 있습니다. 우리는 또한 중요한 변화를 언급할 것입니다.
실제 예제로 이동하기 전에 몇 가지 기본 개념을 살펴보겠습니다. Trainable — 알고리즘이 구성을 평가하는 데 도움이 되는 목표입니다. Class API 또는 Function API를 가질 수 있지만 Ray Tune 문서에 따르면 Function API를 권장합니다. 검색 공간 — 하이퍼파라미터의 값 범위. 평가판 - Tuner는 여러 구성을 생성하고 구성에서 프로세스를 실행하므로 구성에서 실행되는 프로세스를 평가판이라고 합니다. 검색 알고리즘 — 기본적으로 Tune은 검색 알고리즘으로 임의 검색을 사용하는 하이퍼파라미터 구성을 제안합니다. 스케줄러 — 교육 프로세스 중에 보고된 결과에 따라 스케줄러는 중지 또는 계속 여부를 결정합니다. 다음 의미있는 개념은체크포인트 . 체크포인트는 훈련을 재개한 다음 계속하는 데 필요한 중간 결과를 저장하는 것을 의미합니다.
대부분의 경우 튜닝 과정에서 검색 알고리즘과 스케줄러를 함께 사용할 수 있지만 예외가 있습니다. 함께 사용되지 않는 경우 중 하나는 인구 기반 교육입니다. Ray Tune 문서에서 PBT는 스케줄러 부분에 있지만 둘 다 동시에 있습니다. 결과에 따라 시도를 중지하기 때문에 스케줄러이고 새 구성을 만드는 논리가 있으므로 검색기입니다.
우리는 잘 알려진 The Boston Housing Dataset(https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html). sklearn에서 이 데이터 세트를 가져올 수 있습니다.
Ray Tune의 의미 있는 변경 사항 중 하나는 실행 API였습니다. tune.run()이 Tuner().fit으로 변경되었습니다. 업데이트 전에는 매개변수를 별도로 전달했지만 새 버전에서는 구성 클래스가 도입되어 많은 것을 단순화했습니다. 우선 실행 코드를 더 쉽게 읽고 이해할 수 있도록 관련 매개변수를 그룹화합니다. 둘째, 프로젝트에서 Ray Tune을 사용할 때 일부 구성은 일부 알고리즘에 대해 동일하므로 하나의 공통 구성 클래스 개체를 만들고 알고리즘을 이동할 수 있으므로 삶이 더 쉬워집니다.
수입품
# 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
훈련 가능한 것부터 시작합시다. 이미 언급했듯이 훈련 가능한 API에는 함수 기반과 클래스 기반의 두 가지가 있습니다. 우리는 Class API로 trainable을 작성할 것입니다.
class TrainableForPBT(tune.Trainable):
def setup(self, config):
pass
def step(self):
pass
설정 에서 향후 단계에서 시험 모델의 효율성을 추정하기 위해 x_train 및 y_train이 있어야 합니다. 물론 setup 은 부모 클래스의 (tune.Trainable) 함수이지만 추가 인수를 추가할 수 있는 가능성을 제공합니다. 또한 설정에서 lgbm 회귀자/모델을 초기화해야 합니다 . 우리는 모든 반복에서 모델을 재교육할 것이지만, 첫 번째 반복에서는 모델을 적합시키기를 원하므로 우리가 어떤 반복에 있는지 계산해야 합니다. 이 시점에서 더 이상 아무것도 없습니다.
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
save_checkpoint 로 시작하십시오 . 우리는 모델을 저장하고 복원하기 위해 joblib 라이브러리를 사용할 것입니다. 무엇을 구해야 합니까? 우선 — 모델, 다음 반복을 위해 항상 이전 반복 모델(init_model)이 필요하므로 현재 반복 횟수와 현재 단계 점수도 저장할 수 있습니다.
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)
학습 프로세스에는 구성 샘플만큼 많은 Trainable이 있습니다. 각 Trainable을 시작하려면 몇 초가 걸립니다. 재사용_액터 기능을 사용하면 새로운 여러 구성/초매개변수에 대해 이미 시작된 Trainable을 재사용할 수 있습니다. 따라서 Trainable이 덜 필요하고 시작에 소요되는 시간도 줄어들 것입니다.
새 하이퍼 매개변수를 제공하는 reset_config 를 구현해 보겠습니다 . reset_config 에서 모든 변수는 새로운 하이퍼파라미터로 조정되어야 하며 이는 new setup 과 같습니다 . 하나의 까다로운 질문이 있습니다. 다른 구성이 동일한 Trainable을 교체할 때마다 reset_config 에서 시작처럼 작성 한다는 사실 때문에 처음부터 프로세스를 시작합니까? 실제로 그렇지 않습니다. 왜냐하면 reset_config 이후 에 Trainable은 체크포인트가 있는 경우 로드 체크포인트를 호출하므로 교육은 마지막 중지/체크포인트에서 계속됩니다.
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
이제 몇 가지 구성을 만들고 Tune 실험을 실행할 수 있습니다. 튜너에는 trainable , param_space , tune_config 및 run_config 의 4가지 매개변수가 있습니다. Trainable은 이미 구현되어 있습니다. param_space를 정의합시다.
Param_space 는 이미 언급한 검색 공간과 동일합니다. 먼저 조정할 매개변수 목록을 정의해야 합니다. 단순화하려면 learning_rate, num_leaves, max_depth의 3개 매개변수를 선택합니다.
Tune에는 자체 검색 공간 API가 있으므로 검색 공간을 정의할 때 이를 사용해야 합니다. 검색 공간의 이름은 직관적이므로 더 이상 고민하지 않고 결과를 보자.
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)
},
}
Population Base Training 일정의 첫 번째 매개변수는 time_attr입니다. 비교를 위한 훈련 결과 속성이며 단조롭게 증가해야 합니다. 우리는 trainig_iteration을 시간 속성으로 선택하므로 어디에서나 time_attr을 언급하면 training_iteration을 의미합니다. perturbation_interval — 섭동이 얼마나 자주 발생해야 하는지. 섭동을 자주 수행하면 체크포인트도 자주 저장해야 합니다. 따라서 perturbation_interval을 4로 선택하겠습니다. burn_in_period — 이 간격(반복) 횟수가 지나기 전에는 perturbation이 발생하지 않습니다. 성능 점수가 초기 단계에서 불안정하기 때문에 처음부터 성능이 좋지 않은 모델에 최고 성능의 상태를 복제하는 것은 사실이 아닙니다. 따라서 burn_in_period 시행을 10번 반복한 다음 섭동을 시작합니다. hyperparam_mutations는 교란될 수 있는 하이퍼파라미터 및 해당 검색 공간의 사전입니다. 우리는 모든 하이퍼파라미터를 교란시키고 싶습니다.param_space dict이므로 hyperparam_mutations는 param_space [“params”]와 동일합니다. TuneConfig에서 정의한 대로 PopulationBasedTraining에서 모드 및 메트릭 인수를 전달하지 않습니다.
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()
이제 ResultGrid 개체(분석)를 사용하여 결과와 상호 작용할 수 있습니다. get_best_result 를 사용 하면 모든 시도에서 최상의 결과를 얻을 수 있습니다. 또한 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}")