Skip to content

Instantly share code, notes, and snippets.

@yohm
Last active December 26, 2016 06:14
Show Gist options
  • Save yohm/d192676f5fce8d60eb5e9b2c773df5b4 to your computer and use it in GitHub Desktop.
Save yohm/d192676f5fce8d60eb5e9b2c773df5b4 to your computer and use it in GitHub Desktop.
A sample of parameter optimization using OACIS and Differential Evolution algorithm.

A sample of optimzation of parameters

This is a sample of optimizing parameters using OACIS watcher. This program iteratively search for parameters which minimizes the results of the simulations. For the optimization, we adopted a differential evolutiion algorithm.

Prerequisites

Register simulator as follows.

  • Name: "de_optimize_test"
  • Parameter Definitions:
    • "p1", Float, 0.0
    • "p2", Float, 0.0
  • Command:
    • ruby -r json -e 'j=JSON.load(File.read("_input.json")); f=(j["p1"]-1.0)**2+(j["p2"]-2.0)**2; puts({"f"=>f}.to_json)' > _output.json
  • Input type: JSON
  • Executable_on : localhost

The following command will register this simulator in your OACIS.

oacis_ruby prepare_simulator.rb

What does this sample code do?

Search a pair of ("p1","p2") which minimizes the result of the simulations.

"de_optimizer.rb" is an optimization engine implementing a differential evolution algorithm. This is a generic routine independent of OACIS APIs.

"optimize_with_oacis.rb" combines OACIS and "de_optimizer.rb". It iteratively finds optimal parameters using the optimizer as a subroutine.

How to run

Specify the parameters for Differential Evolution algorithm as command line arguments.

oacis_ruby optimize_with_oacis.rb <num_iterations> <population size> <f> <cr> <seed>

For example, run the following.

oacis_ruby optimize_with_oacis.rb 10 20 0.8 0.9 1234

A sample output

A scatter plot of the sampled parameters is in scatter_plot.svg. Color scale indicates the simulation outputs. As you see in the figure, region close to the optimal point is more intensively sampled.

require 'pp'
class DE_Optimizer
class Domain
attr_reader :min, :max, :eps
def initialize(h)
@min, @max, @eps = h[:min], h[:max], h[:eps].to_r
raise "invalid range : [#{@min}, #{@max}]" if @min > @max
end
def round(x)
rounded = ( @eps * (x / @eps).round ).to_f
rounded = (rounded > @max) ? @max : rounded
rounded = (rounded < @min) ? @min : rounded
end
def scale(r) # give [0,1] value and return a value scaled in [min,max]
round( r * (@max - @min) + @min )
end
end
attr_reader :best_point, :best_f, :t, :population
attr_accessor :map_func
def initialize( map_func, domains, n: nil, f: 0.8, cr: 0.9, rand_seed: nil )
@n, @f, @cr = (n || domains.size*10), f, cr
@rng = Random.new( rand_seed || Random.new_seed )
@domains = domains.map {|h| Domain.new(h) }
@map_func = map_func
@t = 0
@best_point = nil
@best_f = Float::INFINITY
generate_initial_points
end
def generate_initial_points
@population = Array.new(@n) {|i| @domains.map {|d| d.scale( @rng.rand ) } }
@current_fs = @map_func.call( @population )
end
def average_f
@current_fs.inject(:+) / @current_fs.size
end
def proceed
new_positions = []
@n.times do |i|
new_pos = generate_candidate(i)
new_positions << new_pos
end
new_fs = @map_func.call( new_positions )
# selection
@n.times do |i|
if new_fs[i] < @current_fs[i]
@population[i] = new_positions[i]
@current_fs[i] = new_fs[i]
if new_fs[i] < @best_f
@best_point = new_positions[i]
@best_f = new_fs[i]
end
end
end
@t += 1
end
private
# generate a candidate for @population[i]
# based on DE/rand/1/binom algorithm
def generate_candidate(i)
# randomly pick a,b,c
begin
a = @rng.rand( @n )
end while ( a == i )
begin
b = @rng.rand( @n )
end while ( b == i || b == a )
begin
c = @rng.rand( @n )
end while ( c == i || c == a || c == b )
# compute the new position
new_pos = @population[i].dup
# pick a random index r
dim = @domains.size
r = @rng.rand( dim )
dim.times do |d|
if( d == r || @rng.rand < @cr )
new_pos[d] = @domains[d].round( @population[a][d] + @f * (@population[b][d] - @population[c][d]) )
end
end
new_pos
end
end
if $0 == __FILE__
domains = [
{min: -10.0, max: 10.0, eps: Rational(1,10)},
{min: -10.0, max: 10.0, eps: Rational(1,10)},
{min: -10.0, max: 10.0, eps: Rational(1,10)}
]
f = lambda {|x| (x[0]-1.0)**2+(x[1]-2.0)**2+(x[2]-3.0)**2 }
map_agents = lambda {|points| points.map(&f) }
=begin
domains = [
{min: -5.0, max: 5.0, eps: Rational(1,10)},
{min: -5.0, max: 5.0, eps: Rational(1,10)}
]
f = lambda{|x|
arg1 = -0.2 * Math.sqrt(0.5 * (x[0] ** 2 + x[1] ** 2))
arg2 = 0.5 * ( Math.cos(2. * Math::PI * x[0]) + Math.cos(2. * Math::PI * x[1]))
-20.0 * Math.exp(arg1) - Math.exp(arg2) + 20.0 + Math::E
}
=end
opt = DE_Optimizer.new(map_agents, domains, n: 30, f: 0.8, cr: 0.9, rand_seed: 1234)
20.times do |t|
opt.proceed
puts "#{opt.t} #{opt.best_point} #{opt.best_f} #{opt.average_f}"
end
end
require_relative "de_optimizer"
# parsing inputs
unless ARGV.size == 5
$stderr.puts "Usage: ruby #{__FILE__} <num_iterations> <population size> <f> <cr> <seed>"
raise "invalid arguments"
end
num_iteration = ARGV[0].to_i
n = ARGV[1].to_i
f = ARGV[2].to_f
cr = ARGV[3].to_f
seed = ARGV[4].to_i
logger = Logger.new($stderr)
domains = [
{min: -10.0, max: 10.0, eps: Rational(1,10)},
{min: -10.0, max: 10.0, eps: Rational(1,10)}
]
sim = Simulator.where(name: "de_optimize_test").first
host = Host.where(name:"localhost").first
map_agents = lambda {|agents|
parameter_sets = agents.map do |x|
ps = sim.find_or_create_parameter_set( p1:x[0], p2:x[1] )
ps.find_or_create_runs_upto(1, submitted_to: host, host_param: host.default_host_parameters)
logger.info "Created a new PS: #{ps.v}"
ps
end
OacisWatcher::start( logger: logger ) {|w| w.watch_all_ps( parameter_sets ) {} }
parameter_sets.map {|ps| ps.runs.first.result["f"] }
}
opt = DE_Optimizer.new(map_agents, domains, n: n, f: f, cr: cr, rand_seed: 1234)
num_iteration.times do |t|
opt.proceed
puts "#{opt.t} #{opt.best_point} #{opt.best_f} #{opt.average_f}"
end
if sim = Simulator.where(name: "de_optimize_test").first
$stderr.puts "already Simulator '#{sim.name}' exists. Deleting this."
sim.discard
end
command = <<EOS.chomp
ruby -r json -e 'j=JSON.load(File.read("_input.json")); f=(j["p1"]-1.0)**2+(j["p2"]-2.0)**2; puts({"f"=>f}.to_json)' > _output.json
EOS
sim = Simulator.create!(
name: "de_optimize_test",
parameter_definitions: [
ParameterDefinition.new(key: "p1", type: "Float", default: 0.0),
ParameterDefinition.new(key: "p2", type: "Float", default: 0.0)
],
command: command,
executable_on: [Host.where(name: "localhost").first]
)
$stderr.puts "A new simulator #{sim.id} is created."
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment