การฝึกอบรมตามประชากรใน Ray Tune
การปรับไฮเปอร์พารามิเตอร์เป็นขั้นตอนสำคัญในการเลือกโมเดล ไฮเปอร์พารามิเตอร์ก็เหมือนกับการตั้งค่า หากคุณไม่จัดการอย่างเหมาะสม อาจส่งผลเสียต่อผลลัพธ์ของโมเดลได้ การปรับแต่งสามารถทำได้ด้วยตนเองหรือโดยอัตโนมัติ ในโลกปัจจุบัน เนื่องจากความสามารถในการคำนวณ ไฮเปอร์พารามิเตอร์จำนวนมาก อัลกอริทึมที่หลากหลาย และไลบรารีตัวช่วยอย่าง Ray วิธีที่ต้องการคือการปรับไฮเปอร์พารามิเตอร์โดยอัตโนมัติ
ในบทความนี้ เราจะพูดถึงการฝึกอบรมตามประชากร สำรวจ Ray Tune และดูตัวอย่างการปรับไฮเปอร์พารามิเตอร์ GitHub Repo:https://github.com/lukakap/pbt-tune.git
PBT หมายถึงอะไร
ดังที่เราได้กล่าวไปแล้ว ประสิทธิภาพที่ดีของโมเดลเกี่ยวข้องกับการเลือกไฮเปอร์พารามิเตอร์ที่ถูกต้อง การฝึกอบรมตามประชากรเป็นหนึ่งในวิธีที่มีเสน่ห์ในการเลือกไฮเปอร์พารามิเตอร์ ประกอบด้วยสองส่วน: การค้นหาแบบสุ่มและการเลือกอย่างชาญฉลาด ในขั้นตอนการค้นหาแบบสุ่ม อัลกอริทึมจะเลือกไฮเปอร์พารามิเตอร์หลายชุดแบบสุ่ม มีโอกาสสูงที่ชุดค่าผสมส่วนใหญ่จะมีคะแนนประสิทธิภาพต่ำ และชุดค่าผสมส่วนน้อยในทางตรงกันข้ามจะมีคะแนนประสิทธิภาพที่ดีกว่า/ดี มาถึงการเลือกที่ชาญฉลาด ขั้นตอนที่สองอยู่ในวงจรจนกว่าเราจะได้ผลลัพธ์ที่ต้องการหรือจำนวนการวนซ้ำไม่หมด ขั้นตอนการเลือกที่ชาญฉลาดประกอบด้วยสองวิธีหลัก: หาประโยชน์และสำรวจ เอาเปรียบ— แทนที่ชุดค่าผสมของไฮเปอร์พารามิเตอร์ด้วยค่าที่มีแนวโน้มมากกว่า โดยอิงจากเมตริกประสิทธิภาพ สำรวจ — รบกวน hyperparameters แบบสุ่ม (ในกรณีส่วนใหญ่จะคูณด้วยปัจจัยบางอย่าง) เพื่อเพิ่มสัญญาณรบกวน
การฝึกอบรมตามประชากรช่วยให้ทำสองสิ่งที่มีความหมายร่วมกันได้: จัดการฝึกอบรมชุดค่าผสมไฮเปอร์พารามิเตอร์แบบคู่ขนาน ศึกษาจากประชากรที่เหลือ และได้รับผลลัพธ์ที่คาดหวังในทันที
พูดคุยเกี่ยวกับ Ray Tune
Ray Tune เป็นไลบรารี Python ที่ใช้ Ray สำหรับการปรับไฮเปอร์พารามิเตอร์ด้วยอัลกอริธึมล่าสุด เช่น PBT เราจะทำงานกับ Ray เวอร์ชัน 2.1.0 การเปลี่ยนแปลงสามารถดูได้ในบันทึกประจำรุ่นด้านล่าง เรายังจะกล่าวถึงการเปลี่ยนแปลงที่สำคัญในทาง
ก่อนที่จะไปยังตัวอย่างที่ใช้งานได้จริง เรามาพูดถึงแนวคิดพื้นฐานบางประการกันก่อน Trainable — เป็นวัตถุประสงค์ที่ช่วยให้อัลกอริทึมสามารถประเมินการกำหนดค่าได้ สามารถมี Class API หรือ Function API ได้ แต่ตามเอกสารประกอบของ ray tune แนะนำให้ใช้ Function API Search Spaces — ช่วงค่าสำหรับไฮเปอร์พารามิเตอร์ การทดลองใช้งาน — Tuner จะสร้างการกำหนดค่าหลายรายการและเรียกใช้กระบวนการเหล่านั้น ดังนั้นกระบวนการที่เรียกใช้ในการกำหนดค่าจึงเรียกว่าการทดลองใช้งาน อัลกอริทึมการค้นหา — แนะนำการกำหนดค่าไฮเปอร์พารามิเตอร์ โดยค่าเริ่มต้น Tune จะใช้การค้นหาแบบสุ่มเป็นอัลกอริทึมการค้นหา ตัว กำหนดตารางเวลา — ขึ้นอยู่กับผลลัพธ์ที่รายงานระหว่างกระบวนการฝึกอบรม ตัวกำหนดตารางเวลาจะตัดสินใจว่าจะหยุดหรือดำเนินการต่อ แนวคิดที่มีความหมายต่อไปคือด่าน . ด่านหมายถึงการบันทึกผลลัพธ์ระหว่างกลางซึ่งจำเป็นในการดำเนินการต่อและดำเนินการฝึกอบรมต่อไป
ในกรณีส่วนใหญ่ Search Algorithm และ Scheduler สามารถใช้ร่วมกันในกระบวนการปรับแต่งได้ แต่มีข้อยกเว้น หนึ่งในกรณีที่ไม่ได้ใช้ร่วมกันคือการฝึกอบรมตามประชากร ใน Ray Tune docs 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
class TrainableForPBT(tune.Trainable):
def setup(self, config):
pass
def step(self):
pass
ในการตั้งค่าเราจำเป็นต้องมี x_train และ y_train เพื่อประเมินประสิทธิภาพของโมเดลทดลองในขั้นตอนต่อๆ ไป แน่นอนการตั้งค่าเป็นฟังก์ชัน (tune.Trainable) ของคลาสพาเรนต์ แต่ช่วยให้เราสามารถเพิ่มอาร์กิวเมนต์เพิ่มเติมได้ นอกจากนี้ เราจำเป็นต้องเริ่มต้น lgbm regressor/model ในการตั้งค่า เราจะฝึกโมเดลของเราใหม่ในทุกๆ การวนซ้ำ แต่ในอันแรกเราต้องการให้พอดีกับโมเดล ดังนั้นจำเป็นต้องนับว่าเราอยู่ในการวนซ้ำแบบใด ไม่มีอะไรมากไปกว่านี้แล้ว
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)
ในกระบวนการฝึกอบรม เรามี Trainables มากพอๆ กับตัวอย่างการกำหนดค่า Trainable แต่ละอันต้องใช้เวลาหลายวินาทีในการเริ่มต้น ด้วยคุณสมบัติ reuse_actor เราสามารถนำ 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 Tuner มี 4 พารามิเตอร์: ฝึกได้, param_space , tune_configและrun_config Trainable ถูกนำมาใช้แล้ว ลองกำหนด param_space
Param_spaceเหมือนกับพื้นที่ค้นหาที่กล่าวถึงแล้ว ขั้นแรก เราต้องกำหนดรายการพารามิเตอร์ที่เราจะปรับแต่ง เพื่อลดความซับซ้อน ให้เลือกพารามิเตอร์ 3 ตัว: learning_rate, num_leaves, max_ความลึก
Tune มี Search Space 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)
},
}
พารามิเตอร์แรกของตารางการฝึกอบรมฐานประชากรคือ time_attr เป็นแอตทริบิวต์ผลการฝึกอบรมสำหรับการเปรียบเทียบ ซึ่งควรเป็นสิ่งที่เพิ่มขึ้นอย่างซ้ำซากจำเจ เราเลือกtrainig_iterationเป็นแอตทริบิวต์ของเวลา ดังนั้นเมื่อเรากล่าวถึงtime_attrที่ใดก็ตาม นั่นหมายถึงtraining_iteration perturbation_interval — การก่อกวนควรเกิดขึ้นบ่อยแค่ไหน ถ้าเราก่อกวนบ่อยก็ต้องเซฟด่านบ่อยเช่นกัน ดังนั้น เรามาเลือก perturbation_interval เป็น 4 กันเถอะ burn_in_period — การก่อกวนจะไม่เกิดขึ้นก่อนที่จำนวนช่วงเวลา (การวนซ้ำ) จะผ่านไป จะไม่เป็นความจริงหากเราโคลนสถานะของประสิทธิภาพสูงสุดเป็นโมเดลที่มีประสิทธิภาพต่ำตั้งแต่เริ่มต้น เนื่องจากคะแนนประสิทธิภาพจะไม่เสถียรในระยะแรก ดังนั้นให้ทำการทดลองซ้ำ 10 รอบของ Burn_in_period จากนั้นจึงเริ่มก่อกวน hyperparam_mutations เป็นคำสั่งของไฮเปอร์พารามิเตอร์และพื้นที่การค้นหา ซึ่งสามารถก่อกวนได้ เราต้องการรบกวนไฮเปอร์พารามิเตอร์ทั้งหมดจากparam_space dict ดังนั้น hyperparam_mutations จะเหมือนกับparam_space [“params”] เราจะไม่ผ่านอาร์กิวเมนต์โหมดและเมตริกใน PopulationBasedTraining เนื่องจากเรากำหนดไว้ใน 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()
ตอนนี้ เราสามารถโต้ตอบกับผลลัพธ์โดยใช้วัตถุ 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}")