Files
NexQuant/rdagent/scenarios/data_science/proposal/exp_gen/ckp_select.py
T
xuangu-fang e71d8f6c3c feat: advanced checkpoint selectors (#790)
* rebase selection code

* bug-free run: checkpoint selection and dynamic EDA loading

* add prototypes of various selectors, to imp. and test later

* fix EDA write bug

* imp SOTA-Jump policy

* fix small bug

* allow to set different selector by .env

* add always-win selector

* add init length for AlwaysWinCKPSelector

* add back_jump selector

* auto lint

* add sota_exp_to_submit attribute; change the name of ckp_selector and sota-selector

* fix bug

* auto lint

* working on auto sota selector

* add subtrace counter

* fix bug, remove unuse selector

* add auto sota selector

* auto lint

* fix bug

* fix small logic bug

* add logging

* add inject_diverse feat

* auto lint

* capable to None-select

* feat: add hypothesis_gen config and ExpGen2TraceAndMerge functionality

* refactor: use dynamic import for experiment generator instantiation

* feat: add BestValidSelector for improved SOTA experiment selection

* runnable twin-trace version

* fix logic error of trace-merge

* auto lint

* use import_class to set selector,

* auto-lint

---------

Co-authored-by: Young <afe.young@gmail.com>
2025-05-09 15:38:25 +08:00

150 lines
6.2 KiB
Python

import random
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.core.proposal import CheckpointSelector, Trace
from rdagent.log import rdagent_logger as logger
# # TODO: more advanced selector
# # TODO/Discussion: load selector function here or define selector class in `proposal.py`?
class LatestCKPSelector(CheckpointSelector):
"""
-`(-1, )` represents starting from the latest trial in the trace
"""
def __init__(
self,
):
logger.info(f"Using latest selector by default")
def get_selection(self, trace: Trace) -> tuple[int, ...]:
return (-1,)
class SOTAJumpCKPSelector(CheckpointSelector):
"""
SOTA jump policy:
if the cumulative SOTA in a window is below a threshold, jump to a new trial
otherwise, continue the current latest trial
"""
def __init__(
self,
) -> None:
self.SOTA_COUNT_WINDOW = DS_RD_SETTING.sota_count_window
self.SOTA_COUNT_THRESHOLD = DS_RD_SETTING.sota_count_threshold
logger.info(
f"Using SOTA-jump selector with window {self.SOTA_COUNT_WINDOW} and threshold {self.SOTA_COUNT_THRESHOLD}"
)
def get_selection(self, trace: Trace) -> tuple[int, ...]:
current_trace = trace.retrieve_search_list(search_type="ancestors")
if len(trace.hist) > 0 and len(current_trace) > self.SOTA_COUNT_WINDOW:
all_exp_list = trace.experiment_and_feedback_list_after_init(return_type="all", search_type="ancestors")
# sota_exp_list = trace.experiment_and_feedback_list_after_init(return_type="sota", search_type="ancestors")
exp_list_in_window = all_exp_list[-self.SOTA_COUNT_WINDOW :]
# compute the cumulative SOTA ratio in the window
sota_count = 0
for exp, fb in exp_list_in_window:
if fb.decision:
sota_count += 1
if sota_count < self.SOTA_COUNT_THRESHOLD:
trace.sub_trace_count += 1
logger.info(
f"SOTA count {sota_count} is below threshold {self.SOTA_COUNT_THRESHOLD}, jump to a new sub-trace"
)
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return ()
else:
logger.info(
f"SOTA count {sota_count} is above threshold {self.SOTA_COUNT_THRESHOLD}, continue the current latest trial"
)
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return (-1,)
else:
logger.info(f"Not enough history to make a decision, continue the current latest trial")
return (-1,)
class BackJumpCKPSelector(CheckpointSelector):
"""
back-jump policy:
if the cumulative SOTA in a window is below a threshold,
with 50% probability, reboot a new sub-trace
with 50% probability, jump back to the "last second" SOTA trial (we assume the lastest SOTA trial is not good enough selection)
"""
def __init__(
self,
) -> None:
self.SOTA_COUNT_WINDOW = DS_RD_SETTING.sota_count_window
self.SOTA_COUNT_THRESHOLD = DS_RD_SETTING.sota_count_threshold
logger.info(
f"Using back-jump selector with window {self.SOTA_COUNT_WINDOW} and threshold {self.SOTA_COUNT_THRESHOLD}"
)
def get_selection(self, trace: Trace) -> tuple[int, ...]:
current_trace = trace.retrieve_search_list(search_type="ancestors")
if len(trace.hist) > 0 and len(current_trace) > self.SOTA_COUNT_WINDOW:
all_exp_list = trace.experiment_and_feedback_list_after_init(return_type="all", search_type="ancestors")
# sota_exp_list = trace.experiment_and_feedback_list_after_init(return_type="sota", search_type="ancestors")
exp_list_in_window = all_exp_list[-self.SOTA_COUNT_WINDOW :]
# compute the cumulative SOTA ratio in the window
sota_count = 0
for exp, fb in exp_list_in_window:
if fb.decision:
sota_count += 1
if sota_count < self.SOTA_COUNT_THRESHOLD:
random_choice = random.random()
if random_choice < 0.5:
trace.sub_trace_count += 1
logger.info(
f"SOTA count {sota_count} is below threshold {self.SOTA_COUNT_THRESHOLD}, jump a new sub-trace"
)
return () # reboot a new sub-trace
else:
logger.info(
f"SOTA count {sota_count} is below threshold {self.SOTA_COUNT_THRESHOLD}, jump back to the last second SOTA in hist (may not in current sub-trace)"
)
sota_exp_list = trace.experiment_and_feedback_list_after_init(return_type="sota", search_type="all")
if len(sota_exp_list) > 1:
last_second_sota_idx = trace.hist.index(sota_exp_list[-2])
logger.info(
f"jump back to the last second SOTA in hist (may not in current sub-trace), index: {last_second_sota_idx}"
)
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return (last_second_sota_idx,)
else:
trace.sub_trace_count += 1
logger.info(
f"SOTA count {sota_count} is below threshold {self.SOTA_COUNT_THRESHOLD}, jump a new sub-trace"
)
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return () # reboot a new sub-trace
else:
logger.info(
f"SOTA count {sota_count} is above threshold {self.SOTA_COUNT_THRESHOLD}, continue the current latest trial"
)
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return (-1,)
else:
logger.info(f"Not enough history to make a decision, continue the current latest trial")
logger.info(f"current sub-trace count: {trace.sub_trace_count}")
return (-1,)
# TODO: implement these selectors and more