Class: NES

Inherits:
Object
  • Object
show all
Defined in:
lib/neuroevo/nes.rb

Overview

Translated from Giuse's NES Mathematica library

Direct Known Subclasses

SNES, XNES

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ndims, obj_fn, opt_type, seed: nil) ⇒ NES

Returns a new instance of NES.


13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/neuroevo/nes.rb', line 13

def initialize ndims, obj_fn, opt_type, seed: nil
  # ndims: number of parameters to optimize
  # obj_fn: any object defining a #call method (Proc, lambda, custom class)
  # opt_type: :min or :max, for minimization / maximization of obj_fn
  # seed: allow for deterministic execution on seed provided
  raise "Hell!" unless [:min, :max].include? opt_type
  raise "Hell!" unless obj_fn.respond_to? :call
  @ndims, @opt_type, @obj_fn = ndims, opt_type, obj_fn
  @id = NMatrix.identity(ndims, dtype: :float64)
  @rand = Random.new seed || Random.new_seed
  initialize_distribution
end

Instance Attribute Details

#idObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def id
  @id
end

#muObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def mu
  @mu
end

#ndimsObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def ndims
  @ndims
end

#obj_fnObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def obj_fn
  @obj_fn
end

#opt_typeObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def opt_type
  @opt_type
end

#randObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def rand
  @rand
end

#sigmaObject (readonly)

Natural Evolution Strategies


11
12
13
# File 'lib/neuroevo/nes.rb', line 11

def sigma
  @sigma
end

Instance Method Details

#hansen_lrateObject


51
52
53
# File 'lib/neuroevo/nes.rb', line 51

def hansen_lrate
  (3+Math.log(ndims)) / (5*Math.sqrt(ndims))
end

#hansen_popsizeObject


55
56
57
# File 'lib/neuroevo/nes.rb', line 55

def hansen_popsize
  [5, 4 + (3*Math.log(ndims)).floor].max
end

#hansen_utilitiesObject


39
40
41
42
43
44
45
46
47
48
49
# File 'lib/neuroevo/nes.rb', line 39

def hansen_utilities
  # Algorithm equations are meant for fitness maximization
  # Match utilities with individuals sorted by INCREASING fitness
  log_range = (1..popsize).collect do |v|
    [0, Math.log(popsize.to_f/2 - 1) - Math.log(v)].max
  end
  total = log_range.reduce(:+)
  buf = 1.0/popsize
  vals = log_range.collect { |v| v / total - buf }.reverse
  NMatrix[vals, dtype: :float64]
end

#lrateObject


37
# File 'lib/neuroevo/nes.rb', line 37

def lrate;   @lrate     ||= hansen_lrate       end

#move_inds(inds) ⇒ Object


63
64
65
66
67
68
69
70
71
# File 'lib/neuroevo/nes.rb', line 63

def move_inds inds
  # TODO: can we get rid of double transpose?
  # sigma.dot(inds.transpose).map(&mu.method(:+)).transpose

  multi_mu = NMatrix[*inds.rows.times.collect {mu.to_a}, dtype: :float64].transpose
  (multi_mu + sigma.dot(inds.transpose)).transpose

  # sigma.dot(inds.transpose).transpose + inds.rows.times.collect {mu.to_a}.to_nm
end

#popsizeObject


36
# File 'lib/neuroevo/nes.rb', line 36

def popsize; @popsize   ||= hansen_popsize * 2 end

#run(ntrain: 10, printevery: 1) ⇒ Object


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/neuroevo/nes.rb', line 95

def run ntrain: 10, printevery: 1
  ## Set printevery to `false` to disable output printout
  if printevery # Pre-run print
    puts "\n\n    NES training -- #{ntrain} iterations\n"
  end
  # Actual run
  ntrain.times do |i|
    if printevery and i==0 || (i+1)%printevery==0
      puts "\n#{i+1}/#{ntrain}\n  mu:   #{mu}\n  conv: #{convergence}"
    end
    train   #   <== actual training
  end
  # End-of-run print
  if printevery
    puts "\n    Training complete"
    puts "    mu (avg): #{mu.reduce(:+)/ndims}"
    puts "    convergence: #{convergence}"
  end
end

#sorted_indsObject

TODO: refactor this, it's THIS open for easier debugging


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/neuroevo/nes.rb', line 74

def sorted_inds
  # Algorithm equations are meant for fitness maximization
  # Utilities need to be matched with individuals sorted by
  # INCREASING fitness -- reverse order for minimization
  samples = standard_normal_samples
  inds = move_inds(samples).to_a
  fits = obj_fn.(inds)
  # quick cure for fitness NaNs
  fits.map!{ |x| x.nan? ? (opt_type==:max ? -1 : 1) * Float::INFINITY : x }
  fits_by_sample = samples.to_a.zip(fits).to_h
  # http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-sort_by
  # refactor: compute the fitness directly in sort_by
  sorted_samples = samples.to_a.sort_by { |s| fits_by_sample[s] }
  sorted_samples.reverse! if opt_type==:min
  NMatrix[*sorted_samples, dtype: :float64]
end

#standard_normal_samplesObject


59
60
61
# File 'lib/neuroevo/nes.rb', line 59

def standard_normal_samples
  NMatrix.build([popsize,ndims], dtype: :float64) {unit_normal_sample}
end

#trainObject

Raises:

  • (NotImplementedError)

91
92
93
# File 'lib/neuroevo/nes.rb', line 91

def train
  raise NotImplementedError, "Implement in child class!"
end

#unit_normal_sampleObject

Box-Muller transform: generates unit normal distribution samples


27
28
29
30
31
32
# File 'lib/neuroevo/nes.rb', line 27

def unit_normal_sample
  rho = Math.sqrt(-2.0 * Math.log(rand.rand))
  theta = 2 * Math::PI * rand.rand
  tfn = rand.rand > 0.5 ? :cos : :sin
  rho * Math.send(tfn, theta)
end

#utilsObject

Doubling popsize and halving lrate often helps


35
# File 'lib/neuroevo/nes.rb', line 35

def utils;   @utilities ||= hansen_utilities   end