Skip to content

Neps local and incumbent

NePSLocalPriorIncumbentSampler dataclass #

NePSLocalPriorIncumbentSampler(
    space: PipelineSpace,
    random_ratio: float = 0.0,
    local_prior: dict[str, Any] | None = None,
    inc_takeover_mode: Literal[0, 1, 2, 3] = 0,
)

Implement a sampler that samples from the incumbent.

inc_takeover_mode class-attribute instance-attribute #

inc_takeover_mode: Literal[0, 1, 2, 3] = 0

The incumbent takeover mode. 0: Always mutate the first config. 1: Use the global incumbent. 2: Crossover between global incumbent and first config. 3: Choose randomly between 0, 1, and 2.

local_prior class-attribute instance-attribute #

local_prior: dict[str, Any] | None = None

The local prior configuration.

random_ratio class-attribute instance-attribute #

random_ratio: float = 0.0

The ratio of random sampling vs incumbent sampling.

space instance-attribute #

The pipeline space to optimize over.

sample_config #

sample_config(table: DataFrame) -> dict[str, Any]

Sample a configuration based on the PriorBand algorithm.

PARAMETER DESCRIPTION
table

The table containing the configurations and their performance.

TYPE: DataFrame

RETURNS DESCRIPTION
dict[str, Any]

dict[str, Any]: A sampled configuration.

Source code in neps/optimizers/neps_local_and_incumbent.py
def sample_config(self, table: pd.DataFrame) -> dict[str, Any]:  # noqa: C901
    """Sample a configuration based on the PriorBand algorithm.

    Args:
        table (pd.DataFrame): The table containing the configurations and their
            performance.

    Returns:
        dict[str, Any]: A sampled configuration.
    """

    completed: pd.DataFrame = table[table["perf"].notna()]  # type: ignore
    if completed.empty:
        logging.warning("No local prior found. Sampling randomly from the space.")
        return (
            self.local_prior
            if self.local_prior is not None
            else self._sample_random()
        )

    # If no local prior is given, save the first config as the local prior
    if self.local_prior is None:
        first_config = completed.iloc[0]["config"]
        assert isinstance(first_config, dict)
        self.local_prior = first_config

    # Get the incumbent configuration
    inc_config = completed.loc[completed["perf"].idxmin()]["config"]
    first_config = self.local_prior
    assert isinstance(inc_config, dict)

    # Decide whether to sample randomly or from the incumbent
    if random.random() < self.random_ratio:
        return self._sample_random()

    match self.inc_takeover_mode:
        case 0:
            # Always mutate the first config.
            new_config = self._mutate_inc(inc_config=first_config)
        case 1:
            # Use the global incumbent.
            new_config = self._mutate_inc(inc_config=inc_config)
        case 2:
            # Crossover between global incumbent and first config.
            new_config = self._crossover_incs(
                inc_config=inc_config,
                first_config=first_config,
            )
        case 3:
            # Choose randomly between 0, 1, and 2.
            match random.randint(0, 2):
                case 0:
                    new_config = self._mutate_inc(inc_config=first_config)
                case 1:
                    new_config = self._mutate_inc(inc_config=inc_config)
                case 2:
                    new_config = self._crossover_incs(
                        inc_config=inc_config,
                        first_config=first_config,
                    )
                case _:
                    raise ValueError(
                        "This should never happen. Only for type checking."
                    )
        case _:
            raise ValueError(f"Invalid inc_takeover_mode: {self.inc_takeover_mode}")
    return new_config