revac 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/revac.rb +207 -0
- data/test/.gitkeep +0 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d7c4ca351b69ea97d4e61ce1a0e2a615204c22b4
|
4
|
+
data.tar.gz: fa2f20c8a5f837c9ebe29db0e95fdffb0f9733c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e78e574cde74e8822b3dbf17b4766e208cb63c87ad6c3cd5b6e64cbe506c5bfc87fc4d8635a60431189770246526a26552280e23e7510f156762ae2438ddf26b
|
7
|
+
data.tar.gz: a05be6bc722d77ddf0006e18f75eac50d906a5be3d468f7b84ac4b96274a2d0fa834caefe04a823a9f64ad5b820c52b856eb788690db205eefd4eab1383a1137
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Chris Timperley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Load all dependencies.
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/testtask'
|
8
|
+
require 'rdoc/task'
|
9
|
+
require 'jeweler'
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:default, :development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
$stderr.puts e.message
|
15
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gem Specification.
|
20
|
+
Jeweler::Tasks.new do |gem|
|
21
|
+
gem.name = "revac"
|
22
|
+
gem.homepage = "http://github.com/ChrisTimperley/RubyREVAC"
|
23
|
+
gem.license = "MIT"
|
24
|
+
gem.summary = "Implementation of REVAC tuner for meta-heuristics (Relevance Estimation and Value Calibration
|
25
|
+
of Evolutionary Algorithm Parameters)."
|
26
|
+
gem.description = <<-EOF
|
27
|
+
An implementation of the REVAC tuner (Relevance Estimation and Value Calibration
|
28
|
+
of Evolutionary Algorithm Parameters) proposed by V. Nannen and A.E. Eiben, extended to work with arbitrary meta-heuristics
|
29
|
+
implemented on arbitrary platforms.
|
30
|
+
EOF
|
31
|
+
gem.email = "christimperley@gmail.com"
|
32
|
+
gem.author = "Chris Timperley"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gem Management.
|
36
|
+
Jeweler::RubygemsDotOrgTasks.new
|
37
|
+
|
38
|
+
# Unit Testing.
|
39
|
+
Rake::TestTask.new(:test) do |test|
|
40
|
+
test.libs << 'lib' << 'test'
|
41
|
+
test.pattern = 'test/**/test_*.rb'
|
42
|
+
test.verbose = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Documentation.
|
46
|
+
task :default => :test
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "revac #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/revac.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'CSV' unless defined?(CSV)
|
3
|
+
|
4
|
+
# Implements the REVAC tuning method (Relevance Estimation and Value
|
5
|
+
# Calibration of Evolutionary Algorithm Parameters), proposed by Nannen and
|
6
|
+
# Eiben, which provides a fast and rational approach to parameter tuning for
|
7
|
+
# meta-heuristics.
|
8
|
+
#
|
9
|
+
# Although originally designed for finding the optimal parameter setup for
|
10
|
+
# evolutionary algorithms, this REVAC implementation may also be used to
|
11
|
+
# tune other meta-heuristics, such as Ant Colony Optimisation or Particle
|
12
|
+
# Swarm Optimisation.
|
13
|
+
module REVAC
|
14
|
+
|
15
|
+
# Used to hold the name and range of legal values for a search parameter.
|
16
|
+
Parameter = Struct.new(:name, :range)
|
17
|
+
|
18
|
+
# Logs a parameter vector and its associated utility function value using
|
19
|
+
# a given CSV file.
|
20
|
+
#
|
21
|
+
# ==== Parameters
|
22
|
+
# [+path+] The path to the output CSV file.
|
23
|
+
# [+evaluation+] The current evaluation.
|
24
|
+
# [+vector+] The parameter vector table.
|
25
|
+
# [+utility+] The utility of the given vector.
|
26
|
+
def self.log(path, evaluation, vector, utility)
|
27
|
+
CSV.open(path, 'ab') do |f|
|
28
|
+
f << [evaluation] + vector + [utility]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Computes the utility (response) of a given parameter vector.
|
33
|
+
#
|
34
|
+
# ==== Parameters
|
35
|
+
# [+vector+] The parameter vector to evaluate.
|
36
|
+
# [+parameters+] The list of tunable parameters.
|
37
|
+
# [+algorithm+] A function which takes the a hash of parameter
|
38
|
+
# values as input, performs a given algorithm and
|
39
|
+
# returns the best fitness value found.
|
40
|
+
# [+runs+] The number of runs to use for each parameter
|
41
|
+
# vector.
|
42
|
+
def self.evaluate_vector(vector, parameters, algorithm, runs)
|
43
|
+
|
44
|
+
# Transform the vector into a hash of parameter names and
|
45
|
+
# settings and compute the mean best fitness using this
|
46
|
+
# parameter vector across a given number of runs.
|
47
|
+
vector = Hash[vector.to_a.each_with_index.map { |v, i|
|
48
|
+
[parameters[i].name, v]
|
49
|
+
}]
|
50
|
+
Array.new(runs) { algorithm[vector] }.inject(:+).to_f / runs
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
# Performs REVAC mutation on an individual at a given index within the vector
|
55
|
+
# table to produce a new mutated vector.
|
56
|
+
#
|
57
|
+
# ==== Parameters
|
58
|
+
# [+random+] The random number generator.
|
59
|
+
# [+table+] The vector table.
|
60
|
+
# [+index+] The index of the individual to mutate.
|
61
|
+
# [+h+] The radius of the partial marginal density function.
|
62
|
+
#
|
63
|
+
# ==== Returns
|
64
|
+
# The resulting vector.
|
65
|
+
def self.mutate(random, table, index, h)
|
66
|
+
|
67
|
+
# Sort the values of this parameter for all vectors into ascending order
|
68
|
+
# before calculating the upper and lower bounds of the mutation distribution
|
69
|
+
# and drawing a new parameter value at uniform random.
|
70
|
+
return Array.new(table[0].length) do |i|
|
71
|
+
window = (0...table.length).sort { |x, y| table[x][i] <=> table[y][i] }
|
72
|
+
position = window.find_index(index)
|
73
|
+
window = [
|
74
|
+
window[[position - h, 0].max],
|
75
|
+
window[[position + h, table.length - 1].min]
|
76
|
+
]
|
77
|
+
window = table[window[0]][i] .. table[window[1]][i]
|
78
|
+
random.rand(window)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# Performs a multi-parent crossover on a list of parent chromosomes to produce
|
84
|
+
# a new chromosome.
|
85
|
+
#
|
86
|
+
# ==== Parameters
|
87
|
+
# [+random+] The random number generator.
|
88
|
+
# [+parents+] The parents of the child chromosome.
|
89
|
+
#
|
90
|
+
# ==== Returns
|
91
|
+
# The proto-child vector of the provided parent vectors.
|
92
|
+
def self.crossover(random, parents)
|
93
|
+
Array.new(parents[0].length) { |i| parents[random.rand(parents.length)][i] }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Tunes a given algorithm using REVAC.
|
97
|
+
#
|
98
|
+
# ==== Parameters
|
99
|
+
# [+parameters+] A list of parameters to optimise. Each entry in the
|
100
|
+
# list contains the name of the parameter and the range
|
101
|
+
# of values that it can take.
|
102
|
+
# [+opts+] A hash of keyword options to this method.
|
103
|
+
# [+&algorithm+] A lambda function which takes a hash of named parameters
|
104
|
+
# as its input, uses them to perform a given algorithm,
|
105
|
+
# and returns the fitness of the best individual found.
|
106
|
+
#
|
107
|
+
# ==== Options
|
108
|
+
# [+evaluations+] The maximum number of evaluations to perform.
|
109
|
+
# [+parents+] The number of parents to use when creating each child.
|
110
|
+
# [+runs+] The number of runs to perform for each vector.
|
111
|
+
# [+vectors+] The number of parameter vectors in the population.
|
112
|
+
# [+h+] The radius of the partial marginal density function.
|
113
|
+
# [+log+] The path to the output CSV file.
|
114
|
+
# [+debug+] Used to enable / disable evaluation logging to the console.
|
115
|
+
#
|
116
|
+
# ==== Returns
|
117
|
+
# A hash containing the `optimal' parameter values for the given problem.
|
118
|
+
def self.tune(parameters, opts = {}, &algorithm)
|
119
|
+
|
120
|
+
# Load the default values for any omitted parameters.
|
121
|
+
opts[:vectors] ||= 80
|
122
|
+
opts[:parents] ||= 40
|
123
|
+
opts[:h] ||= 5
|
124
|
+
opts[:runs] ||= 5
|
125
|
+
opts[:evaluations] ||= 5000
|
126
|
+
opts[:debug] ||= false
|
127
|
+
|
128
|
+
# The RNG to use during optimisation.
|
129
|
+
random = Random.new
|
130
|
+
|
131
|
+
# Convert the list of parameter name/range pairs into a list of parameter
|
132
|
+
# objects.
|
133
|
+
parameters = parameters.map { |n, r| Parameter.new(n, r) }
|
134
|
+
|
135
|
+
# Initialise evolution statistics.
|
136
|
+
oldest = 0
|
137
|
+
iterations = 0
|
138
|
+
evaluations = 0
|
139
|
+
|
140
|
+
# Initialise the output CSV file (if one has been given).
|
141
|
+
CSV.open(opts[:log], 'wb') do |f|
|
142
|
+
f << ['Evaluation'] + parameters.map { |p| p.name } + ['Utility']
|
143
|
+
end if opts[:log]
|
144
|
+
|
145
|
+
# Draw an initial set of parameter vectors at uniform random from
|
146
|
+
# their initial distributions.
|
147
|
+
table = Array.new(opts[:vectors]) do
|
148
|
+
parameters.map { |p| random.rand(p.range) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Compute the utility of each parameter vector, before finding and recording
|
152
|
+
# the best parameter vector.
|
153
|
+
utility = table.map do |v|
|
154
|
+
u = evaluate_vector(v, parameters, algorithm, opts[:runs])
|
155
|
+
log(opts[:output], evaluations, v, u) if opts[:log]
|
156
|
+
puts "Evaluation #{evaluations}: #{u}" if opts[:debug]
|
157
|
+
evaluations += 1
|
158
|
+
u
|
159
|
+
end
|
160
|
+
best_utility, best_vector = utility.each_with_index.min
|
161
|
+
best_vector = table[best_vector]
|
162
|
+
|
163
|
+
# Keep optimising until the termination condition is met.
|
164
|
+
until evaluations == opts[:evaluations]
|
165
|
+
|
166
|
+
# Select the N-best vectors from the table as the parents
|
167
|
+
# of the next parameter vector.
|
168
|
+
parents = (0...opts[:vectors]).sort { |x, y|
|
169
|
+
utility[x] <=> utility[y]
|
170
|
+
}.take(opts[:parents]).map { |i|
|
171
|
+
table[i]
|
172
|
+
}
|
173
|
+
|
174
|
+
# Perform multi-parent crossover on the N parents to create
|
175
|
+
# a proto-child vector.
|
176
|
+
child = crossover(random, parents)
|
177
|
+
|
178
|
+
# Replace the oldest vector from the population with this
|
179
|
+
# proto-child vector, before mutating it and computing its
|
180
|
+
# utility.
|
181
|
+
table[oldest] = child
|
182
|
+
table[oldest] = mutate(random, table, oldest, opts[:h])
|
183
|
+
utility[oldest] = evaluate_vector(child, parameters, algorithm, opts[:runs])
|
184
|
+
|
185
|
+
# Update the best vector if the child vector is an improvement.
|
186
|
+
if utility[oldest] < best_utility
|
187
|
+
best_vector = table[oldest]
|
188
|
+
best_utility = utility[oldest]
|
189
|
+
end
|
190
|
+
|
191
|
+
# Update evolution statistics and perform logging.
|
192
|
+
iterations += 1
|
193
|
+
evaluations += 1
|
194
|
+
log(opts[:output], evaluations, child, utility[oldest]) if opts[:log]
|
195
|
+
puts "Evaluation #{evaluations}: #{utility[oldest]}" if opts[:debug]
|
196
|
+
oldest = (oldest + 1) % opts[:vectors]
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# Return the optimal parameter vector as a hash.
|
201
|
+
return Hash[parameters.zip(0...parameters.length).map { |param, index|
|
202
|
+
[param.name, best_vector[index]]
|
203
|
+
}]
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
data/test/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: revac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Timperley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jeweler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: |2
|
70
|
+
An implementation of the REVAC tuner (Relevance Estimation and Value Calibration
|
71
|
+
of Evolutionary Algorithm Parameters) proposed by V. Nannen and A.E. Eiben, extended to work with arbitrary meta-heuristics
|
72
|
+
implemented on arbitrary platforms.
|
73
|
+
email: christimperley@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files:
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
files:
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- VERSION
|
85
|
+
- lib/revac.rb
|
86
|
+
- test/.gitkeep
|
87
|
+
homepage: http://github.com/ChrisTimperley/RubyREVAC
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.0.14
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Implementation of REVAC tuner for meta-heuristics (Relevance Estimation and
|
111
|
+
Value Calibration of Evolutionary Algorithm Parameters).
|
112
|
+
test_files: []
|