pg.hyper.CustomHyper

Accessible via pg.hyper.CustomHyper.

class CustomHyper(name=None, hints=None)[source]

Bases: pg.hyper.HyperPrimitive

User-defined hyper primitive.

User-defined hyper primitive is useful when users want to have full control on the semantics and genome encoding of the search space. For example, the decision points are of variable length, which is not yet supported by built-in hyper primitives.

To use user-defined hyper primitive is simple, the user should:

  1. Subclass CustomHyper and implements the pg.hyper.CustomHyper method. It’s optional to implement the pg.hyper.CustomHyper method, which is only necessary when the user want to encoder a material object into a DNA.

  2. Introduce a DNAGenerator that can generate DNA for the pg.geno.CustomDecisionPoint.

For example, the following code tries to find an optimal sub-sequence of an integer sequence by their sums:

import random

class IntSequence(pg.hyper.CustomHyper):

  def custom_decode(self, dna):
    return [int(v) for v in dna.value.split(',') if v != '']

class SubSequence(pg.evolution.Mutator):

  def mutate(self, dna):
    genome = dna.value
    items = genome.split(',')
    start = random.randint(0, len(items))
    end = random.randint(start, len(items))
    new_genome = ','.join(items[start:end])
    return pg.DNA(new_genome, spec=dna.spec)

@pg.geno.dna_generator
def initial_population():
  yield pg.DNA('12,-34,56,-2,100,98', spec=dna_spec)

algo = pg.evolution.Evolution(
    (pg.evolution.selectors.Random(10)
     >> pg.evolution.selectors.Top(1)
     >> SubSequence()),
    population_init=initial_population(),
    population_update=pg.evolution.selectors.Last(20))

best_reward, best_example = None, None
for int_seq, feedback in pg.sample(IntSequence(), algo, num_examples=100):
  reward = sum(int_seq)
  if best_reward is None or best_reward < reward:
    best_reward, best_example = reward, int_seq
  feedback(reward)

print(best_reward, best_example)

Please note that user-defined hyper value can be used together with PyGlove’s built-in hyper primitives, for example:

pg.oneof([IntSequence(), None])

Therefore it’s also a mechanism to extend PyGlove’s search space definitions.

Methods:

custom_apply(path, value_spec, allow_partial)

Validate candidates during value_spec binding time.

custom_decode(dna)

Decode a DNA whose value is a string of user-defined genome.

custom_encode(value)

Encode value to user defined genome.

dna_spec([location])

Always returns CustomDecisionPoint for CustomHyper.

encode(value)

Encode value into DNA with user-defined genome.

first_dna()

Returns the first DNA of current sub-space.

next_dna([dna])

Subclasses should override this method to support pg.Sweeping.

random_dna([random_generator, previous_dna])

Subclasses should override this method to support pg.random_dna.

custom_apply(path, value_spec, allow_partial, child_transform=None)[source]

Validate candidates during value_spec binding time.

Return type:

Tuple[bool, pg.hyper.CustomHyper]

abstract custom_decode(dna)[source]

Decode a DNA whose value is a string of user-defined genome.

Return type:

Any

custom_encode(value)[source]

Encode value to user defined genome.

Return type:

pg.DNA

dna_spec(location=None)[source]

Always returns CustomDecisionPoint for CustomHyper.

Return type:

pg.DNASpec

encode(value)[source]

Encode value into DNA with user-defined genome.

Return type:

pg.DNA

first_dna()[source]

Returns the first DNA of current sub-space.

Return type:

pg.DNA

Returns:

A string-valued DNA.

next_dna(dna=None)[source]

Subclasses should override this method to support pg.Sweeping.

Return type:

Optional[pg.DNA]

random_dna(random_generator=None, previous_dna=None)[source]

Subclasses should override this method to support pg.random_dna.

Return type:

pg.DNA