Class: MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies::BDNES
- Inherits:
-
Base
- Object
- Base
- MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies::BDNES
- Defined in:
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb
Overview
Block-Diagonal Natural Evolution Strategies
Constant Summary collapse
- MAX_RSEED =
block random seeds to be on the same range as ‘Random.new_seed`
10**Random.new_seed.size
Instance Attribute Summary collapse
-
#blocks ⇒ Object
readonly
Returns the value of attribute blocks.
-
#ndims_lst ⇒ Object
readonly
Returns the value of attribute ndims_lst.
-
#parallel_update ⇒ Object
readonly
Returns the value of attribute parallel_update.
-
#popsize ⇒ Object
readonly
Returns the value of attribute popsize.
Attributes inherited from Base
#best, #eye, #last_fits, #ndims, #obj_fn, #opt_type, #parallel_fit, #rescale_lrate, #rescale_popsize, #rng
Instance Method Summary collapse
- #convergence ⇒ Object
-
#initialize(ndims_lst, obj_fn, opt_type, parallel_fit: false, rseed: nil, parallel_update: false, **init_opts) ⇒ BDNES
constructor
Initialize a list of XNES, one for each block see class ‘Base` for the description of the rest of the arguments.
- #load(data) ⇒ Object
- #mu ⇒ Object
- #save ⇒ Object
- #sigma ⇒ Object
- #sorted_inds_lst ⇒ Object
-
#train(picks: sorted_inds_lst) ⇒ Object
TODO: refactor DRY.
Methods inherited from Base
#cmaes_lrate, #cmaes_popsize, #cmaes_utilities, #interface_methods, #lrate, #move_inds, #sorted_inds, #standard_normal_sample, #standard_normal_samples, #utils
Constructor Details
#initialize(ndims_lst, obj_fn, opt_type, parallel_fit: false, rseed: nil, parallel_update: false, **init_opts) ⇒ BDNES
Initialize a list of XNES, one for each block see class ‘Base` for the description of the rest of the arguments.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 19 def initialize ndims_lst, obj_fn, opt_type, parallel_fit: false, rseed: nil, parallel_update: false, **init_opts # mu_init: 0, sigma_init: 1 # init_opts = {rseed: rseed, mu_init: mu_init, sigma_init: sigma_init} # TODO: accept list of `mu_init`s and `sigma_init`s @ndims_lst, @obj_fn, @opt_type, @parallel_fit = ndims_lst, obj_fn, opt_type, parallel_fit block_fit = -> (*args) { raise "Should never be called" } # the BD-NES seed should ensure deterministic reproducibility # but each block should have a different seed # puts "BD-NES rseed: #{s}" # currently disabled @rng = Random.new rseed || Random.new_seed @blocks = ndims_lst.map do |ndims| b_rseed = rng.rand MAX_RSEED XNES.new ndims, block_fit, opt_type, rseed: b_rseed, **init_opts end # Need `popsize` to be the same for all blocks, to make complete individuals @popsize = blocks.map(&:popsize).max blocks.each { |xnes| xnes.instance_variable_set :@popsize, popsize } @best = [(opt_type==:max ? -1 : 1) * Float::INFINITY, nil] @last_fits = [] @parallel_update = parallel_update require 'parallel' if parallel_update end |
Instance Attribute Details
#blocks ⇒ Object (readonly)
Returns the value of attribute blocks.
9 10 11 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 9 def blocks @blocks end |
#ndims_lst ⇒ Object (readonly)
Returns the value of attribute ndims_lst.
9 10 11 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 9 def ndims_lst @ndims_lst end |
#parallel_update ⇒ Object (readonly)
Returns the value of attribute parallel_update.
9 10 11 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 9 def parallel_update @parallel_update end |
#popsize ⇒ Object (readonly)
Returns the value of attribute popsize.
9 10 11 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 9 def popsize @popsize end |
Instance Method Details
#convergence ⇒ Object
116 117 118 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 116 def convergence blocks.map(&:convergence).reduce(:+) end |
#load(data) ⇒ Object
124 125 126 127 128 129 130 131 132 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 124 def load data fit = -> (*args) { raise "Should never be called" } @blocks = data.map do |block_data| ndims = block_data.first.size XNES.new(ndims, fit, opt_type).tap do |nes| nes.load block_data end end end |
#mu ⇒ Object
108 109 110 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 108 def mu blocks.map(&:mu).reduce { |mem, var| mem.concatenate var, axis: 1 } end |
#save ⇒ Object
120 121 122 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 120 def save blocks.map &:save end |
#sigma ⇒ Object
112 113 114 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 112 def sigma raise NotImplementedError, "need to write a concatenation like for mu here" end |
#sorted_inds_lst ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 43 def sorted_inds_lst # Build samples and inds from the list of blocks samples_lst, inds_lst = blocks.map do |xnes| samples = xnes.standard_normal_samples inds = xnes.move_inds(samples) [samples.to_a, inds] end.transpose # Join the individuals for evaluation full_inds = inds_lst.reduce { |mem, var| mem.concatenate var, axis: 1 } # Need to fix sample dimensions for sorting # - current dims: nblocks x ninds x [block sizes] # - for sorting: ninds x nblocks x [block sizes] full_samples = samples_lst.transpose # Evaluate fitness of complete individuals fits = parallel_fit ? obj_fn.call(full_inds) : full_inds.map(&obj_fn) # Quick cure for NaN fitnesses fits.map { |x| x.nan? ? (opt_type==:max ? -1 : 1) * Float::INFINITY : x } @last_fits = fits # allows checking for stagnation # Sort inds based on fit and opt_type, save best # sorted = [fits, full_inds, full_samples].transpose.sort_by(&:first) # sorted.reverse! if opt_type==:min # this_best = sorted.last.take(2) # opt_cmp_fn = opt_type==:min ? :< : :> # @best = this_best if this_best.first.send(opt_cmp_fn, best.first) # sorted_samples = sorted.map(&:last) sort_idxs = fits.sort_index sort_idxs = sort_idxs.reverse if opt_type == :min this_best = [fits[sort_idxs[-1]], full_inds[sort_idxs[-1], true]] opt_cmp_fn = opt_type==:min ? :< : :> @best = this_best if this_best.first.send(opt_cmp_fn, best.first) sorted_samples = full_samples.values_at *sort_idxs # Need to bring back sample dimensions for each block # - current dims: ninds x nblocks x [block sizes] # - target blocks list: nblocks x ninds x [block sizes] block_samples = sorted_samples.transpose # then back to NArray for usage in training block_samples.map &:to_na end |
#train(picks: sorted_inds_lst) ⇒ Object
TODO: refactor DRY
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb', line 90 def train picks: sorted_inds_lst if parallel_update # Parallel.each(blocks.zip(picks)) do |xnes, s_inds| # xnes.train picks: s_inds # end # Actually it's not this simple. # Forks do not act on the parent, so I need to send back updated mu and sigma # Luckily we have `NES#save` and `NES#load` at the ready # Next: need to implement `#marshal_dump` and `#marshal_load` in `Base` # Actually using `Cumo` rather than `Parallel` may avoid marshaling altogether raise NotImplementedError, "Should dump and load each instance" else blocks.zip(picks).each do |xnes, s_inds| xnes.train picks: s_inds end end end |