<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;AkIMQHwyeCp7ImA9WxFWEEk.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534</id><updated>2010-05-28T22:09:41.290+10:00</updated><title>Inspired Algorithms</title><subtitle type="html">This project aims to be the largest set of explained and executable computational intelligence algorithms on the planet.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.inspiredalgorithms.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.inspiredalgorithms.com/" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>8</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/InspiredAlgorithms" /><feedburner:info uri="inspiredalgorithms" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CkEGQncyeCp7ImA9WxVQFUo.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-7285090809930558506</id><published>2009-02-02T21:30:00.002+11:00</published><updated>2009-02-02T21:30:23.990+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-02T21:30:23.990+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="traveling salesman problem" /><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="TSP" /><category scheme="http://www.blogger.com/atom/ns#" term="GRASP" /><category scheme="http://www.blogger.com/atom/ns#" term="greedy randomized adaptive search procedure" /><category scheme="http://www.blogger.com/atom/ns#" term="combinatorial optimization" /><title>Greedy randomized adaptive search procedure: Step-wise construction with local search</title><content type="html">The &lt;a href="http://www.inspiredalgorithms.com/2009/01/iterated-local-search-cycling-global.html"&gt;Iterated Local Search&lt;/a&gt; algorithm introduced the combination of a biased global search algorithm followed by a local search to refine the results. This general problem solving pattern is also the basis of the &lt;span style="font-weight: bold;"&gt;Greedy Randomized Adaptive Search Procedure&lt;/span&gt; (GRASP).&lt;br /&gt;&lt;br /&gt;The GRASP involves alternating a greedy adaptive solution construction procedure with a local search algorithm to refine the coarser search result. The approach was designed for combinatorial problems where the  individual components of a candidate solution can be discriminated in the context of the goal (such as cost). This greedy step-wise construction involves the preparation of Restricted Candidate List (RCL) of components from which to select from, and the random selection of a component from the RCL to add to the growing solution. This process continues until a viable candidate solution is constructed and can be passed onto a local search procedure for evaluation.&lt;br /&gt;&lt;br /&gt;The power of GRASP is in the preparation of the RCL. Typically the value of individual components to a final solution is used to restrict entry to the list (a threshold). Alternative approaches involve a fixed RCL size (of the top components), and an adaptive threshold or size over time. Management of the RCL requires careful attention, ensuring sufficient flexibility in the selection of components for a solution, whilst biasing the search towards a global optimum supplemented with a local search technique. It is interesting to note that the use of a strict RCL that selects the best component during each step of construction will result in a greedy search.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Greedy randomized adaptive search procedure&lt;/span&gt;&lt;br /&gt;This guide provide a demonstration of the GRASP applied to a standard instance of the Traveling Salesman Problem. This classical combinatorial optimization provides a good example for the step-wise construction technique used by the GRASP.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;Berlin52TSP&lt;/span&gt; class provides an implementation of a small sized TSP with 52 cities. The problem data is stored as constants within the class. The &lt;span style="font-family: courier new;"&gt;COORDINATES&lt;/span&gt; constant provides a 2D array of city coordinates and the &lt;span style="font-family: courier new;"&gt;OPTIMAL_TOUR&lt;/span&gt; constant stores a representation of the optimal tour (permutation) of cities that used in the algorithms termination criterion. The &lt;span style="font-family: courier new;"&gt;initialize()&lt;/span&gt; constructor in turn calls the &lt;span style="font-family: courier new;"&gt;build_distance_matrix&lt;/span&gt; method to pre-calculate a matrix of the Euclidean distance between all combinations of cities. The city-to-city distance calculation represents the most expensive aspect of candidate solution evaluations which is eased via the pre-calculation requiring only a lookup of city ID's to determine the intervening distance. The &lt;span style="font-family: courier new;"&gt;evaluate(permutation)&lt;/span&gt; makes use of this pre-calculation via the &lt;span style="font-family: courier new;"&gt;dist(c1, c2)&lt;/span&gt; method, evaluating candidate solutions in the form a 51-city permutation where each city ID is specified between 1 and 52 inclusively. &lt;pre class="prettyprint"&gt;class Berlin52TSP&lt;br /&gt;  OPTIMAL_TOUR = [1,49,32,45,19,41,8,9,10,43,33,51,11,52,14,13,47,26,&lt;br /&gt;    27,28,12,25,4,6,15,5,24,48,38,37,40,39,36,35,34,44,46,16,29,50,20,&lt;br /&gt;    23,30,2,7,42,21,17,3,18,31,22]&lt;br /&gt;        &lt;br /&gt;  COORDINATES = [[565, 575],[25, 185],[345, 750],[945, 685],[845, 655],&lt;br /&gt;  [880, 660],[25, 230],[525, 1000],[580, 1175],[650, 1130],[1605, 620], &lt;br /&gt;  [1220, 580],[1465, 200],[1530, 5],[845, 680],[725, 370],[145, 665],&lt;br /&gt;  [415, 635],[510, 875], [560, 365],[300, 465],[520, 585],[480, 415],&lt;br /&gt;  [835, 625],[975, 580],[1215, 245],[1320, 315],[1250, 400],[660, 180],&lt;br /&gt;  [410, 250],[420, 555],[575, 665],[1150, 1160],[700, 580],[685, 595],&lt;br /&gt;  [685, 610],[770, 610],[795, 645],[720, 635],[760, 650],[475, 960],&lt;br /&gt;  [95, 260],[875, 920],[700, 500],[555, 815],[830, 485],[1170, 65],&lt;br /&gt;  [830, 610],[605, 625],[595, 360],[1340, 725],[1740, 245]]&lt;br /&gt;  &lt;br /&gt;  attr_reader :num_cities&lt;br /&gt;&lt;br /&gt;  def initialize()&lt;br /&gt;    @num_cities = COORDINATES.length        &lt;br /&gt;    @distance_matrix = Array.new(@num_cities) {Array.new(@num_cities)}&lt;br /&gt;    build_distance_matrix&lt;br /&gt;    @optimal_tour_length = evaluate(OPTIMAL_TOUR) # calculate&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def build_distance_matrix&lt;br /&gt;    # symmetrical matrix along the diag&lt;br /&gt;    puts "pre-calculating the TSP distance matrix"&lt;br /&gt;    @distance_matrix.each_with_index do |row, i|&lt;br /&gt;      row.each_index do |j|&lt;br /&gt;        row[j] = euc_2d(COORDINATES[i], COORDINATES[j])&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate(permutation)&lt;br /&gt;    dist = 0    &lt;br /&gt;    permutation.each_with_index do |c1, i|&lt;br /&gt;      c2 = (i==@num_cities-1) ? permutation[0] : permutation[i+1] &lt;br /&gt;      dist += dist(c1,c2)&lt;br /&gt;    end&lt;br /&gt;    return dist&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # expects city numbers [1,52]&lt;br /&gt;  def dist(c1, c2)&lt;br /&gt;     @distance_matrix[c1-1][c2-1]&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def euc_2d(c1, c2)&lt;br /&gt;    # As defined in TSPLIB'95 (EUC_2D)&lt;br /&gt;    Math::sqrt((c1[0] - c2[0])**2 + (c1[1] - c2[1])**2).round&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    @optimal_tour_length&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 is better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;Solution&lt;/span&gt; class provides a generic container for candidate solutions. Problem specific solution data (in this case permutations) are stored in the immutable &lt;span style="font-family: courier new;"&gt;@data&lt;/span&gt; instance variable, and a mutable solution costing is stored in the &lt;span style="font-family: courier new;"&gt;@score&lt;/span&gt; instance variable. &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;GRASP&lt;/span&gt; class provides an implementation of the Greedy Randomized Adaptive Search Procedure. The &lt;span style="font-family: courier new;"&gt;initialize(max_iterations)&lt;/span&gt; constructor for the algorithm sets up the maximum number of iterations for the algorithms termination criteria, as well as the algorithm specific &lt;span style="font-family: courier new;"&gt;@max_rcl_size&lt;/span&gt; that defines the maximum size of the Restricted Candidate List each step during construction, defaulted to 5 components. The &lt;span style="font-family: courier new;"&gt;search(problem)&lt;/span&gt; method provides the entry point to the search that executes the main loop until the termination criterion &lt;span style="font-family: courier new;"&gt;should_stop?(curr_it, problem)&lt;/span&gt; returns true. The main loop involves first a call to  &lt;span style="font-family: courier new;"&gt;greedy_randomized_solution(current, problem)&lt;/span&gt; to construct a candidate solution, followed by a call to &lt;span style="font-family: courier new;"&gt;local_search_solution(candidate, problem)&lt;/span&gt; to refine the result.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;greedy_randomized_solution(solution, problem)&lt;/span&gt; provides a simplistic realization of the GRASP with a fixed size RCL. The construction starts with a random city and proceeds by first preparing a set of viable (unused) cities to which to connect. The candidates are ordered by their distance from the '&lt;span style="font-style: italic;"&gt;current&lt;/span&gt;' city, the list is trimmed to be no longer than the &lt;span style="font-family: courier new;"&gt;@max_rcl_size&lt;/span&gt; instance variable, and finally a random component from the list is selected and appended to the permutation.&lt;br /&gt;&lt;br /&gt;The local search algorithm defined in the &lt;span style="font-family: courier new;"&gt;local_search_solution(solution, problem)&lt;/span&gt; method involves the application of the classical TSP local search called 2-opt (the &lt;span style="font-family: courier new;"&gt;two_opt_solution(solution)&lt;/span&gt; method) where two cities are selected and reconnected. This procedure is repeated 15 times as an approximation of locating the local optimum for the provided candidate solution. &lt;pre class="prettyprint"&gt;class GRASP&lt;br /&gt;  attr_accessor :max_iterations, :max_rcl_size&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations  &lt;br /&gt;    @max_rcl_size = 5 # default  &lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute the algorithm&lt;br /&gt;  def search(problem)&lt;br /&gt;    # initialize the search&lt;br /&gt;    @best_solution = nil&lt;br /&gt;    current = generate_initial_solution(problem)&lt;br /&gt;    evaluate_candidate_solution(current, problem)&lt;br /&gt;    curr_it = 0&lt;br /&gt;    begin&lt;br /&gt;      # greedy randomized solution&lt;br /&gt;      candidate = greedy_randomized_solution(current, problem)  &lt;br /&gt;      evaluate_candidate_solution(candidate, problem) # eval    &lt;br /&gt;      # local search&lt;br /&gt;      candidate = local_search_solution(candidate, problem)&lt;br /&gt;      # greedy acceptance&lt;br /&gt;      current = candidate if problem.is_better?(candidate.score, current.score)&lt;br /&gt;      curr_it += 1&lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_initial_solution(problem)&lt;br /&gt;    all = Array.new(problem.num_cities) {|i| (i+1)}&lt;br /&gt;    permutation = Array.new(all.length) {|i| all.delete_at(rand(all.length))}&lt;br /&gt;    return Solution.new(permutation)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def greedy_randomized_solution(solution, problem)    &lt;br /&gt;    # construct a valid perm&lt;br /&gt;    perm = []&lt;br /&gt;    perm &amp;lt;&amp;lt; rand(problem.num_cities) + 1 # random starting point&lt;br /&gt;    last = perm[0]&lt;br /&gt;    while perm.length &amp;lt; problem.num_cities&lt;br /&gt;      # create restricted candidate list for components&lt;br /&gt;      all = Array.new(problem.num_cities) {|i| (i+1)}&lt;br /&gt;      # ensure we can only add unused components&lt;br /&gt;      rcl = all - perm &lt;br /&gt;      # order by distance, asc (best have smallest distance)&lt;br /&gt;      rcl.sort! {|i,j| problem.dist(last, i) &amp;lt;=&gt; problem.dist(last, j)}&lt;br /&gt;      # trim to fixed size&lt;br /&gt;      rcl.pop until rcl.length &amp;lt;= @max_rcl_size&lt;br /&gt;      # select random&lt;br /&gt;      last = rcl[rand(rcl.length)]&lt;br /&gt;      perm &amp;lt;&amp;lt; last&lt;br /&gt;    end    &lt;br /&gt;    return Solution.new(perm)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def local_search_solution(solution, problem)&lt;br /&gt;    # greedy iterated 2-opt&lt;br /&gt;    15.times do&lt;br /&gt;      candidate = two_opt_solution(solution)&lt;br /&gt;      evaluate_candidate_solution(candidate, problem)&lt;br /&gt;      if problem.is_better?(candidate.score, solution.score)&lt;br /&gt;        solution = candidate &lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    return solution&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def two_opt_solution(solution)&lt;br /&gt;    perm = Array.new(solution.data) # copy&lt;br /&gt;    # select a sub-sequence&lt;br /&gt;    c1, c2 = rand(perm.length), rand(perm.length)&lt;br /&gt;    c2 = rand(perm.length) while c1 == c2&lt;br /&gt;    # ensure c1 is low and c2 is high&lt;br /&gt;    c1, c2 = c2, c1 if c2 &amp;lt; c1&lt;br /&gt;    # reverse sub-sequence&lt;br /&gt;    perm[c1...c2] = perm[c1...c2].reverse&lt;br /&gt;    return Solution.new(perm)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate_candidate_solution(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if @best_solution.nil? or&lt;br /&gt;      problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution&lt;br /&gt;      puts "&gt; new best: #{solution.score}"               &lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The algorithm can be executed by first creating an instance of the problem, the algorithm, and passing the problem to the algorithms &lt;span style="font-family: courier new;"&gt;search&lt;/span&gt; method. The algorithm will displaying its progress during the search and return the best result found. The global random number generated is seeded with a fixed number to ensure a consistent sequence of random numbers are used which can prove useful during testing. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = GRASP.new(500) # limit to 500 iterations &lt;br /&gt;problem = Berlin52TSP.new # create a problem&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; The demonstrated GRASP provides much opportunity for extension. One may modify the implementation of the TSP problem instance to provide a class that can load problems form definition files provided by the TSPLIB. The size of the RCL list may be tuned to vary the greediness of solution construction. Further, the preparation of the RCL may be modified to make use of the current working solution passed into the function, perhaps using existing selected components to bias the selection of those components during construction. Finally, adaptive RCL management procedures may be adopted as well as alternative local search procedures for the TSP.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/GRASP.rb"&gt;GRASP.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;T. A. Feo and M. G. C. Resende. &lt;a href="http://www.springerlink.com/content/m57h74q0805g1l43/"&gt;&lt;span style="font-style: italic;"&gt;Greedy randomized adaptive search procedures&lt;/span&gt;&lt;/a&gt;. (&lt;a href="http://www.research.att.com/%7Emgcr/doc/gtut.pdf"&gt;pre-print&lt;/a&gt;) Journal of Global Optimization, 6, 109-133, 1995.&lt;/li&gt;&lt;li&gt;M. G. C. Resende and C. C. Ribeiro &lt;a href="http://www.springerlink.com/content/v37150648423p4lm/"&gt;&lt;span style="font-style: italic;"&gt;Greedy randomized adaptive search procedures&lt;/span&gt;&lt;/a&gt; (&lt;a href="http://www.research.att.com/%7Emgcr/doc/sgrasp-hmetah.pdf"&gt;pre-print&lt;/a&gt;). In F. Glover and G. Kochenberger, editors, Handbook of Metaheuristics, Springer, pages. 219-249, 2003.&lt;/li&gt;&lt;li&gt;L. Pitsoulis and M. G. C. Resende &lt;span style="font-style: italic;"&gt;Greedy randomized adaptive search procedures&lt;/span&gt; (&lt;a href="http://www.research.att.com/%7Emgcr/doc/grasp-hao.pdf"&gt;pre-print&lt;/a&gt;). In P. M. Pardalos and M. G. C. Resende, editors, Handbook of Applied Optimization, Oxford University Press, pages. 168-181, 2002&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Greedy_randomized_adaptive_search_procedure"&gt;&lt;span style="font-style: italic;"&gt;Greedy randomized adaptive search procedure&lt;/span&gt;&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/"&gt;&lt;span style="font-style: italic;"&gt;TSPLIB&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have you found a bug? Know of a great reference? Got an idea for a guide? Please send an email to &lt;a href="mailto:jasonb@InspiredAlgorithms.com"&gt;jasonb@InspiredAlgorithms.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Need Help? Want to discuss this post? Please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-7285090809930558506?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/7285090809930558506?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/7285090809930558506?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/KaaeEflAsDE/greedy-randomized-adaptive-search.html" title="Greedy randomized adaptive search procedure: Step-wise construction with local search" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/02/greedy-randomized-adaptive-search.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUUEQ30zcCp7ImA9WxVRE08.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-4936979319021763313</id><published>2009-01-19T11:00:00.053+11:00</published><updated>2009-01-19T11:00:02.388+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-19T11:00:02.388+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="traveling salesman problem" /><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="TSP" /><category scheme="http://www.blogger.com/atom/ns#" term="taboo" /><category scheme="http://www.blogger.com/atom/ns#" term="tabu search" /><title>Tabu Search: Using explicit memory</title><content type="html">A problem with local search techniques is that after locating a local optima, they will continue to re-sample the same optima and neighboring solutions in the search space over and over again until a stopping condition is triggered. &lt;span style="font-weight: bold;"&gt;Tabu Search&lt;/span&gt; is an algorithm to address this problem by adding memory to a search algorithm, preventing or penalizing the algorithm for re-sampling (cycling) the same solutions and in turn encouraging further exploration of the search space.&lt;br /&gt;&lt;br /&gt;The core search algorithm embedded within a tabu search generates candidate solutions in the neighborhood of a current working solution. A &lt;span style="font-style: italic;"&gt;tabu list&lt;/span&gt; is maintained that holds a set of recently visited neighboring solutions (or components thereof) that may not be visited again (they are taboo). Moves are held in the tabu list for a defined &lt;span style="font-style: italic;"&gt;tabu tenure&lt;/span&gt; that is typically fixed, proportional to the problem size, or generated randomly within a pre-defined range.&lt;br /&gt;&lt;br /&gt;The tabu list is referred to as the &lt;span style="font-style: italic;"&gt;short term memory&lt;/span&gt; of a search algorithm. A searches &lt;span style="font-style: italic;"&gt;intermediate term memory&lt;/span&gt; refer to the factors that direct the process to promising areas (expected payoff) of the search space inferred from past experience, referred to as intensification. Examples of such intermediate memory processes include the inclusion of common components from recently visited good quality solutions, adapting the tabu tenures when locally optimal solutions are located, and restarting the search (clearing the tabu list) with a locally optimal solution. &lt;span style="font-style: italic;"&gt;Long term memory&lt;/span&gt; processes are used to counter the intensification of intermediate memory by encouraging exploration in the search space, referred to as diversification. Examples include the use of penalties per component that are increased with the frequency of inclusion, and changing the acceptance criteria to allow non-improving moves.&lt;br /&gt;&lt;br /&gt;Finally, so-called aspiration criteria may be used to permit neighbourhood moves on the tabu list (overcome the tabu). A standard aspiration criteria is to accept a tabu move if is better than a the current working position or better than the best solution found by the search to date.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Tabu Search for the Traveling Salesman Problem&lt;/span&gt;&lt;br /&gt;This guide applies the tabu search to a classical combinatorial optimization problem called the Traveling Salesman Problem (TSP). The general class of problem involves finding an order to visit a set of cities to minimize the overall distance traveled.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;Berlin52TSP&lt;/span&gt; class provides a definition of a TSP instance called Berlin 52 (with 52 nodes or cities). The class is large given that it has the city coordinates hard coded in the &lt;span style="font-family: courier new;"&gt;COORDINATES&lt;/span&gt; class constant, as well as the optimal permutation in the &lt;span style="font-family: courier new;"&gt;OPTIMAL_TOUR&lt;/span&gt; constant. The &lt;span style="font-family: courier new;"&gt;evaluate(permutation)&lt;/span&gt; expects a valid permutation of 52 non-repeating cities to visit as an array of integers (between 1 and 52 inclusively) and is evaluated as the rounded Euclidean distance of the cycle. &lt;pre class="prettyprint"&gt;class Berlin52TSP&lt;br /&gt;  OPTIMAL_TOUR = [1,49,32,45,19,41,8,9,10,43,33,51,11,52,14,13,47,26,&lt;br /&gt;    27,28,12,25,4,6,15,5,24,48,38,37,40,39,36,35,34,44,46,16,29,50,20,&lt;br /&gt;    23,30,2,7,42,21,17,3,18,31,22]&lt;br /&gt;        &lt;br /&gt;  COORDINATES = [[565, 575],[25, 185],[345, 750],[945, 685],[845, 655],&lt;br /&gt;  [880, 660],[25, 230],[525, 1000],[580, 1175],[650, 1130],[1605, 620], &lt;br /&gt;  [1220, 580],[1465, 200],[1530, 5],[845, 680],[725, 370],[145, 665],&lt;br /&gt;  [415, 635],[510, 875], [560, 365],[300, 465],[520, 585],[480, 415],&lt;br /&gt;  [835, 625],[975, 580],[1215, 245],[1320, 315],[1250, 400],[660, 180],&lt;br /&gt;  [410, 250],[420, 555],[575, 665],[1150, 1160],[700, 580],[685, 595],&lt;br /&gt;  [685, 610],[770, 610],[795, 645],[720, 635],[760, 650],[475, 960],&lt;br /&gt;  [95, 260],[875, 920],[700, 500],[555, 815],[830, 485],[1170, 65],&lt;br /&gt;  [830, 610],[605, 625],[595, 360],[1340, 725],[1740, 245]]&lt;br /&gt;  &lt;br /&gt;  attr_reader :num_cities&lt;br /&gt;&lt;br /&gt;  def initialize()&lt;br /&gt;    @num_cities = COORDINATES.length        &lt;br /&gt;    @optimal_tour_length = evaluate(OPTIMAL_TOUR) # calculate&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def evaluate(permutation)&lt;br /&gt;    dist = 0    &lt;br /&gt;    permutation.each_with_index do |c1, i|&lt;br /&gt;      c2 = (i==@num_cities-1) ? permutation[0] : permutation[i+1] &lt;br /&gt;      dist += euc_2d(COORDINATES[c1-1], COORDINATES[c2-1])&lt;br /&gt;    end&lt;br /&gt;    return dist&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def euc_2d(c1, c2)&lt;br /&gt;    # As defined in TSPLIB'95 (EUC_2D)&lt;br /&gt;    Math::sqrt((c1[0] - c2[0])**2 + (c1[1] - c2[1])**2).round&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    @optimal_tour_length&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 is better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt;The &lt;span style="font-family: courier new;"&gt;Solution&lt;/span&gt; class provides a container for candidate solutions, with an immutable &lt;span style="font-family: courier new;"&gt;@data&lt;/span&gt; instance variable for problem specific solution representation and a mutable &lt;span style="font-family: courier new;"&gt;@score&lt;/span&gt; instance variable to hold its costing. The overridden &lt;span style="font-family: courier new;"&gt;to_s&lt;/span&gt; method provides a pretty string representation of the solution for display on the command line. Important to this example is the overridden &lt;span style="font-family: courier new;"&gt;==(other)&lt;/span&gt; method that is used to test equality between solution objects. Here, the method is specialized assuming the &lt;span style="font-family: courier new;"&gt;@data&lt;/span&gt; holds an array, and compares two &lt;span style="font-family: courier new;"&gt;Solution&lt;/span&gt; objects based on the equality of the permutations they hold. Permutation comparison is not dependent on the order the permutation is recorded, so equality is checked in both directly and with one of the permutations reversed. This equality method is used to assess whether a solution has already been added to the tabu list (later on). Finally, a mutable &lt;span style="font-family: courier new;"&gt;@count&lt;/span&gt; instance variable is maintained to use in the frequency a tabu solution is desired (later). &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score, :count&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;    @count = 0&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def ==(other)&lt;br /&gt;    # permutation is direction independant&lt;br /&gt;    @data == other.data or @data.reverse == other.data&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;TabuSearchAlgorithm&lt;/span&gt; class offers an implementation of a iterative 2-opt search algorithm with a tabu list and aspiration criteria for a given TSP instance. The &lt;span style="font-family: courier new;"&gt;initialize(max_iterations)&lt;/span&gt; constructor records the maximum number of iterations the algorithm will execute and sets additional tabu search specific configuration. The &lt;span style="font-family: courier new;"&gt;@tabu_tenure&lt;/span&gt; instance variable defines the the size of the tabu list under the assumption that one candidate is added to the list each iteration. The &lt;span style="font-family: courier new;"&gt;@aspiration_frequency&lt;/span&gt; instance variable defines the maximum number of requests that may be made for a tabu solution on the tabu list before it may be reused (prematurely liberated from the tabu list).&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;search(problem)&lt;/span&gt; provides the entry point for the search for a given TSP instance. A random starting point is used to initialize the search provided by the &lt;span style="font-family: courier new;"&gt;generate_initial_solution(problem)&lt;/span&gt; method. The algorithm loop generally involves generating neighbouring candidate solutions one at a time, evaluating a solution, and using it as the current working position greedily if it has an improved problem specific scoring. Neighboring candidate solutions are generated by calling the &lt;span style="font-family: courier new;"&gt;two_opt_solution(solution) &lt;/span&gt;method for a given current working point. This standard local search for the TSP was chosen for its speed, effectiveness, and simplicity and involves selecting two break points in a permutation and reversing the sub-sequence between the points.&lt;br /&gt;&lt;br /&gt;The generation of neighboring candidate solutions loops until a candidate solution is generated that is not already on the tabu list. Importantly, this loop increments the number of times each tabu solution is requested, liberating a specific candidate solution if it's request frequency exceeds &lt;span style="font-family: courier new;"&gt;@aspiration_frequency&lt;/span&gt;, providing a basic aspiration criteria. The tabu list is maintained at the bottom of the main algorithm loop, first adding the current candidate solution regardless of whether it is accepted or not, and then whittling down the &lt;span style="font-family: courier new;"&gt;tabu_list&lt;/span&gt; to &lt;span style="font-family: courier new;"&gt;@tabu_tenure&lt;/span&gt; when required (treating the &lt;span style="font-family: courier new;"&gt;tabu_list&lt;/span&gt; array like a first-in-last-out FILO queue). &lt;pre class="prettyprint"&gt;class TabuSearchAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations&lt;br /&gt;    @tabu_tenure = 100 # maximum number of solutions on the list&lt;br /&gt;    @aspiration_frequency = 5 # max tries to visit a taboo before allowing&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a search on the provided problem&lt;br /&gt;  def search(problem)&lt;br /&gt;    tabu_list = [] # prep the tabu list&lt;br /&gt;    # random starting point&lt;br /&gt;    current = generate_initial_solution(problem)&lt;br /&gt;    tabu_list &amp;lt;&amp;lt; current # make candidate taboo&lt;br /&gt;    evaluate_candidate(current, problem)&lt;br /&gt;    curr_it = 0       &lt;br /&gt;    begin&lt;br /&gt;      # generate new neighbour that is not tabo&lt;br /&gt;      selection_ok = true&lt;br /&gt;      begin&lt;br /&gt;        selection_ok = true # optimism&lt;br /&gt;        candidate = two_opt_solution(current) # generate&lt;br /&gt;        # check tabu&lt;br /&gt;        if tabu_list.include?(candidate)&lt;br /&gt;          other = tabu_list.find {|t| t==candidate}&lt;br /&gt;          if (other.count+=1) &gt;= @aspiration_frequency&lt;br /&gt;            puts "&gt; we have an aspiration!!!"&lt;br /&gt;            tabu_list.delete_if {|t| t==candidate} # ensure no duplicates&lt;br /&gt;          else&lt;br /&gt;            puts "&gt; rejected tabu"&lt;br /&gt;            selection_ok = false &lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;      end until selection_ok&lt;br /&gt;      evaluate_candidate(candidate, problem) # evaluate&lt;br /&gt;      # manage short term memory&lt;br /&gt;      tabu_list &amp;lt;&amp;lt; candidate # make candidate taboo&lt;br /&gt;      tabu_list.delete_at(0) while tabu_list.length &gt; @tabu_tenure&lt;br /&gt;      # greedy acceptance&lt;br /&gt;      current = candidate if problem.is_better?(candidate.score, current.score)&lt;br /&gt;      curr_it += 1&lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_initial_solution(problem)&lt;br /&gt;    all = Array.new(problem.num_cities) {|i| (i+1)}&lt;br /&gt;    permutation = Array.new(all.length) {|i| all.delete_at(rand(all.length))}&lt;br /&gt;    return Solution.new(permutation)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def two_opt_solution(solution)&lt;br /&gt;    perm = Array.new(solution.data) # copy&lt;br /&gt;    # select a sub-sequence    &lt;br /&gt;    c1, c2 = rand(perm.length), rand(perm.length)&lt;br /&gt;    c2 = rand(perm.length) while c1 == c2&lt;br /&gt;    # ensure c1 is low and c2 is high&lt;br /&gt;    c1, c2 = c2, c1 if c2 &amp;lt; c1&lt;br /&gt;    # reverse sub-sequence&lt;br /&gt;    perm[c1...c2] = perm[c1...c2].reverse&lt;br /&gt;    return Solution.new(perm)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate_candidate(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if @best_solution.nil? or &lt;br /&gt;      problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution&lt;br /&gt;      puts " &gt; new best: #{solution.score}"               &lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The algorithm is demonstrated by first initializing the global random number generator to 1 to ensure the same sequence of random numbers on each execution (useful for testing). The problem is created and algorithm is initialized with a maximum of 2000 iterations. The search is executed by passing the problem instance to the algorithms search function, and after the execution of the run the best solution is displayed. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = TabuSearchAlgorithm.new(2000) # limit to 2000 iterations &lt;br /&gt;problem = Berlin52TSP.new # create a problem&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; During the execution of the algorithm, the scoring of the best solution is displayed each time a new best is located. The loop of the main algorithm displays a message each time a tabu solutions aspiration criteria is triggered or when a generated candidate solution is denied based on its existence in the tabu list. You will observe that as the search gets closer to the global optimum, the number of candidates denied increases and a number of liberation events occur as the aspiration criteria are triggered.&lt;br /&gt;&lt;br /&gt;There is plenty of room for extension on this example. One may change the configuration of the tabu search, increasing or decreasing both the tabu tenure as well as the aspiration frequency. Also experimentation of the search algorithm with and without a tabu list may be interesting to see if and when the short term memory of this tabu search example are useful. Alternative representations may be used for the tabu list such as the selection of cities to swap in the local search. Alternatively, the local search may be adjusted to construct tours in a step-wise (per-city) manner and maintain tabu information per-edge in an adjacency matrix. This matrix would then discourage or prevent the use of specific edges during solution construction. Finally, additional memory processes may be introduced such as search-restarts with the best found solution for intermediate memory, and the solution acceptance criteria may be made less greedy to allow non-improving moves to be taken.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/TabuSearch.rb"&gt;TabuSearch.rb&lt;br /&gt;&lt;/a&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers&lt;/span&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Fred Glover and Manuel Laguna, &lt;a href="http://www.amazon.com/gp/product/0792381874?ie=UTF8&amp;amp;tag=inspiredalgor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0792381874"&gt;&lt;span style="font-style: italic;"&gt;Tabu Search&lt;/span&gt;&lt;/a&gt;. Springer, 1997&lt;/li&gt;&lt;li&gt;F. Glover, &lt;a style="font-style: italic;" href="http://joc.highwire.org/cgi/content/abstract/1/3/190"&gt;Tabu Search: Part I&lt;/a&gt;. (&lt;a href="http://www.cds.caltech.edu/%7Eshiling/Tabu%20Search%20Part%20I.pdf"&gt;pre-print&lt;/a&gt;) INFORMS Journal on Computing 1(3), pages 190-206, 1989&lt;/li&gt;&lt;li&gt;F. Glover, &lt;a style="font-style: italic;" href="http://joc.journal.informs.org/cgi/content/abstract/2/1/4"&gt;Tabu Search: Part II&lt;/a&gt;. (&lt;a href="http://leeds-faculty.colorado.edu/glover/TS%20-%20Part%20II-ORSA.pdf"&gt;pre-print&lt;/a&gt;) INFORMS Journal on Computing 2(1), pages 4-32, 1990&lt;/li&gt;&lt;li&gt;Fred Glover and Eric Taillard, &lt;a style="font-style: italic;" href="http://www.springerlink.com/content/k607q47112622n45/"&gt;A user's guide to tabu search&lt;/a&gt;. Annals of Operations Research 41(1), pages 1-28, 1993&lt;/li&gt;&lt;li&gt;F. Glover, &lt;span style="font-style: italic;"&gt;Tabu Search: A Tutorial&lt;/span&gt;. (&lt;a href="http://leeds-faculty.colorado.edu/glover/TS%20-%20Interfaces.pdf"&gt;pre-print&lt;/a&gt;) INTERFACES 20(4), pages 74-94, 1990&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.springerlink.com/content/n09250115p14611x/"&gt;&lt;span style="font-style: italic;"&gt;Tabu Search Applied to TSP&lt;/span&gt;&lt;/a&gt;. In Johannes Josef Schneider and Scott Kirkpatrick, &lt;a href="http://www.amazon.com/gp/product/3540345590?ie=UTF8&amp;amp;tag=inspiredalgor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=3540345590"&gt;&lt;span style="font-style: italic;"&gt;Stochastic Optimization&lt;/span&gt;&lt;/a&gt;, Springer, pages 441-447. 2006&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites&lt;/span&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Tabu_search"&gt;&lt;span style="font-style: italic;"&gt;Tabu Search&lt;/span&gt;&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;li&gt;&lt;a href="http://mathworld.wolfram.com/TabuSearch.html"&gt;&lt;span style="font-style: italic;"&gt;Tabu Search&lt;/span&gt;&lt;/a&gt;. Wolfram MathWorld&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/"&gt;&lt;span style="font-style: italic;"&gt;TSPLIB&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have you found a bug? Know of a great reference? Got an idea for a guide? Please send an email to &lt;a href="mailto:jasonb@InspiredAlgorithms.com"&gt;jasonb@InspiredAlgorithms.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Need Help? Want to discuss this post? Please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-4936979319021763313?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/4936979319021763313?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/4936979319021763313?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/qVeOw8ZNymM/tabu-search-using-explicit-memory.html" title="Tabu Search: Using explicit memory" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/tabu-search-using-explicit-memory.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkQHSHY7eyp7ImA9WxVREUs.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-6895703737936459211</id><published>2009-01-17T11:00:00.040+11:00</published><updated>2009-01-17T14:52:19.803+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T14:52:19.803+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="function optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="random restart" /><category scheme="http://www.blogger.com/atom/ns#" term="random restart hill climber" /><category scheme="http://www.blogger.com/atom/ns#" term="multiple restart" /><category scheme="http://www.blogger.com/atom/ns#" term="random search" /><title>Multiple Restart Search: Multiple runs to address convergence</title><content type="html">While searching for the global optimum of a given problem domain the may question arise as to whether it is worth continuing with the search or starting the search again. This strategy of repeatedly executing a search on a problem domain with new initial conditions is called &lt;span style="font-style: italic;"&gt;Random&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;Early&lt;/span&gt;, or &lt;span style="font-weight: bold;"&gt;Multiple Restart Search&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;The Multiple Restart Search algorithm involves the application of another search technique until a predefined condition is met, after which the embedded search technique is executed again with different starting conditions. The conditions that govern when a restart will occur are refereed to as the restart schedule and examples include: a fixed number of function evaluations, a fixed time, or the convergence of the search process (no improvements for a fixed number of iterations or function evaluations).&lt;br /&gt;&lt;br /&gt;Restarting or multiple executions of a technique is a common practice with stochastic optimization algorithms as they are influenced by their initial starting conditions and convergent behavior that may lead to local rather than the desired global optimum of a problem's search space. Restart schedules are also used with local search and hill climbing searching algorithms as they allow potentially multiple local optima to be explored improving the algorithms approximation of the global optima. Such algorithms are commonly referred to a &lt;span style="font-style: italic;"&gt;Random-Restart Hill Climbing&lt;/span&gt; algorithms.&lt;br /&gt;&lt;br /&gt;The multiple executions of an embedded search algorithm (the restarts) are traditionally executed sequentially as the name suggests. Alternatively, the algorithm may exploit parallel or distributed hardware and execute each algorithm run in parallel, offering a linear (and in some cases a super-linear) speedup of locating or approximating a globally optimal solution.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Multiple Restart Search for Function Optimization&lt;/span&gt;&lt;br /&gt;This guide provides an example of a the multiple restart search algorithm applied to a continuous function optimization problem. The embedded search algorithm that is repeatedly restarted is a hill climbing algorithm, and as such the generalized approach may be referred to as a Random-Restart Hill Climber.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;SquaringFunction&lt;/span&gt; class defines an instance of a function optimization problem. The problem has a configurable number of parameters (&lt;span style="font-family: courier new;"&gt;@num_dimensions&lt;/span&gt;) the squares of which are summed together (hence the name). The &lt;span style="font-family: courier new;"&gt;evaluate(vector)&lt;/span&gt; method expects a vector of real valued numbers, the &lt;span style="font-family: courier new;"&gt;in_bounds?(vector)&lt;/span&gt; convenience method determines whether or not a given vector is valid within the bounds of the problems search space [-5.12, 5.12], and the &lt;span style="font-family: courier new;"&gt;is_optimal?(scoring)&lt;/span&gt; method determines whether or not the given scoring is optimal (equal to zero). &lt;pre class="prettyprint"&gt;class SquaringFunction&lt;br /&gt;  attr_reader :dimensions, :min, :max&lt;br /&gt;&lt;br /&gt;  def initialize(dimensions=2)&lt;br /&gt;    @dimensions = dimensions&lt;br /&gt;    @min, @max = -5.12, +5.12&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate(vector)&lt;br /&gt;    vector.inject(0) {|sum, x| sum + (x ** 2.0)}&lt;br /&gt;  end  &lt;br /&gt;  &lt;br /&gt;  def in_bounds?(vector)&lt;br /&gt;    vector.each {|x| return false if x&gt;@max or x&amp;lt;@min}&lt;br /&gt;    return true    &lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    0.0&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 has a better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;Solution&lt;/span&gt; class provides a container for candidate solutions. The &lt;span style="font-family: courier new;"&gt;@data&lt;/span&gt; instance variable maintains an immutable solution representation (in this case real valued vectors), whereas the &lt;span style="font-family: courier new;"&gt;@score&lt;/span&gt; instance variable maintains a mutable problem specific scoring for the maintained data. &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;MultipleRestartSearchAlgorithm&lt;/span&gt; provides an implementation of the random-restart hill climbing algorithm. The &lt;span style="font-family: courier new;"&gt;initialize(max_restarts)&lt;/span&gt; constructor stores some algorithm configuration parameters in instance variables, specifically the maximum number of restarts provided as an argument, and the number of non-improving steps taken per run of the embedded hill climbing algorithm (set to 100). The &lt;span style="font-family: courier new;"&gt;search(problem)&lt;/span&gt; method manages the restart schedule generating a random starting point which is used to seed a run of the embedded hill climbing algorithm on each restart.&lt;br /&gt;&lt;br /&gt;The problem independent &lt;span style="font-family: courier new;"&gt;hill_climb(problem, current)&lt;/span&gt; method does the hard work for the search, performing a fixed step hill climbing procedure on a provided starting point. The step size is set to 10% of the possible search space, and is unchanged throughout the run. The main loop of this procedure involves the generation of two steps (a positive and negative application of the step size) for a single problem component (function parameter). If one of the two steps results in an improvement the candidate solution is taken as the current position otherwise the number of non-improving iterations is incremented. This counter is rest each time an improving move is made, and is used to measure the convergence of a run of the hill climbing algorithm. The procedure loops through all components of the problem (one per iteration) seeking improvements. A run completes on convergence, which is occurs when the &lt;span style="font-family: courier new;"&gt;no_improve_count&lt;/span&gt; variable exceeds the &lt;span style="font-family: courier new;"&gt;@max_no_improvements&lt;/span&gt; parameter. &lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;take_step(problem, current, step_size, index, add)&lt;/span&gt; method creates a single new candidate solution from a given solution by mutating a specific component of the solution in a specific direction. Interestingly, this is the only problem-specific part of the algorithm, that in turn calls the &lt;span style="font-family: courier new;"&gt;next_bfloat(min, max)&lt;/span&gt; utility method to generates random values that are used to perturb a given vector. &lt;pre class="prettyprint"&gt;class MultipleRestartSearchAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_restarts)&lt;br /&gt;    @max_restarts = max_restarts&lt;br /&gt;    @max_no_improvements = 100 # max steps for hill climber&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a random search on the provided problem&lt;br /&gt;  def search(problem)    &lt;br /&gt;    curr_it = 0&lt;br /&gt;    begin&lt;br /&gt;      # generate a random solution &lt;br /&gt;      current = generate_random_solution(problem)&lt;br /&gt;      evaluate_candidate_solution(current, problem)&lt;br /&gt;      # hill climb until convergence&lt;br /&gt;      run_best = hill_climb(problem, current)&lt;br /&gt;      curr_it += 1      &lt;br /&gt;      puts "&gt; finished run #{curr_it} with: #{run_best.score}"   &lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_restarts) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_random_solution(problem)&lt;br /&gt;    real_vector = Array.new(problem.dimensions) do&lt;br /&gt;      next_bfloat(problem.min, problem.max)&lt;br /&gt;    end&lt;br /&gt;    return Solution.new(real_vector)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def hill_climb(problem, current)&lt;br /&gt;    step_size = (problem.max-problem.min)*0.10&lt;br /&gt;    no_improve_count = 0&lt;br /&gt;    index = 0&lt;br /&gt;    begin&lt;br /&gt;      # take a step&lt;br /&gt;      step1 = take_step(problem, current, step_size, index, true)&lt;br /&gt;      evaluate_candidate_solution(step1, problem)&lt;br /&gt;      step2 = take_step(problem, current, step_size, index, false)&lt;br /&gt;      evaluate_candidate_solution(step2, problem)      &lt;br /&gt;      # check for improvement&lt;br /&gt;      if problem.is_better?(step1.score, current.score) or &lt;br /&gt;        problem.is_better?(step2.score, current.score)&lt;br /&gt;        # store the best&lt;br /&gt;        if problem.is_better?(step1.score, current.score)&lt;br /&gt;          current = step1&lt;br /&gt;        else&lt;br /&gt;          current = step2&lt;br /&gt;        end&lt;br /&gt;        no_improve_count = 0 # reset&lt;br /&gt;      else&lt;br /&gt;        no_improve_count += 1 # count consecutative no improvements&lt;br /&gt;      end&lt;br /&gt;      index = (index==problem.dimensions-1) ? 0 : index+1&lt;br /&gt;    end until no_improve_count &gt;= @max_no_improvements&lt;br /&gt;    return current&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def take_step(problem, current, step_size, index, add)&lt;br /&gt;    vector = nil&lt;br /&gt;    begin # keep stepping until a valid point is generated&lt;br /&gt;      offset = next_bfloat(0, step_size)&lt;br /&gt;      vector = Array.new(current.data)&lt;br /&gt;      vector[index] += (add) ? offset : -offset&lt;br /&gt;    end until problem.in_bounds?(vector)&lt;br /&gt;    return Solution.new(vector)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def next_bfloat(min, max)&lt;br /&gt;    min + ((max - min) * rand)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def evaluate_candidate_solution(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if @best_solution.nil? or&lt;br /&gt;      problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution                  &lt;br /&gt;    end&lt;br /&gt;  end  &lt;br /&gt;end&lt;/pre&gt; The algorithm is demonstrated by first seeding the global random number generator to ensure a consistent sequence of random numbers (useful for testing) and creating instance of the problem and algorithm. The search is executed by passing the problem instance to the algorithm's &lt;span style="font-style: italic;"&gt;search&lt;/span&gt; method that displays the status of the algorithms as the best result found after each restart. The demonstration ends by displaying an approximation of the problem's global optima (which is 0.0 in each dimension) as the best solution encountered by the algorithm. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = MultipleRestartSearchAlgorithm.new(10) # limit to 10 restarts&lt;br /&gt;problem = SquaringFunction.new(5) # create a problem with 5 dimensions&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; This general procedure is called a meta-search algorithm as it can generally be applied to any other direct search technique. The demonstration may be extended by elaborating upon the hill climbing algorithm such as using an adaptive rather than a fixed step size. Further, the embedded hill climbing (local search) algorithm may be replaced with a population-based global stochastic search algorithm. Additionally, restarted algorithm runs may exploit the results found by previous runs, and alternate restart schedules may be used such as a maximum number of function evaluations or relative improvement over previous runs.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/MultipleRestartSearch.rb"&gt;MultipleRestartSearch.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers&lt;/span&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;X. Hu, R. Shonkwiler, and M. Spruill. &lt;a href="http://www.mat.univie.ac.at/%7Eneum/glopt/mss/ShoS94.ps.gz"&gt;&lt;span style="font-style: italic;"&gt;Random restart in global optimization&lt;/span&gt;&lt;/a&gt;. Technical Report 110592-015, Georgia Tech School of Mathematics, 1994.&lt;/li&gt;&lt;li&gt;M. Magdon-Ismail and A. Atiya. &lt;a href="http://www.mitpressjournals.org/doi/abs/10.1162/089976600300015376"&gt;&lt;span style="font-style: italic;"&gt;The early restart algorithm&lt;/span&gt;&lt;/a&gt;. Neural Computation, 12(6), pages 1303–1312, 2000.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;A. Fukunaga. &lt;a href="http://www.springerlink.com/content/2523l26uxj22l076/"&gt;&lt;span style="font-style: italic;"&gt;Restart scheduling for genetic algorithms&lt;/span&gt;&lt;/a&gt;. (&lt;a href="http://alexf04.maclisp.org/ppsn98.pdf"&gt;pre-print&lt;/a&gt;) In Parallel Problem Solving from Nature - PPSN V, pages 357-366. 1998. &lt;/li&gt;&lt;li&gt;Marco Muselli, &lt;a href="http://www.springerlink.com/content/r50682w7503w70u3/"&gt;&lt;span style="font-style: italic;"&gt;A Theoretical Approach to Restart in Global Optimization&lt;/span&gt;&lt;/a&gt;. (&lt;a href="http://www.ieiit.cnr.it/%7Emuselli/papers/jglob96.pdf"&gt;pre-print&lt;/a&gt;) In Journal of Global Optimization, 10(1), pages 1-16. 1997&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites&lt;/span&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Hill_climbing"&gt;&lt;span style="font-style: italic;"&gt;Hill climbing&lt;/span&gt;&lt;/a&gt;. Wikipedia&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have you found a bug? Know of a great reference? Got an idea for a guide? Please send an email to &lt;a href="mailto:jasonb@InspiredAlgorithms.com"&gt;jasonb@InspiredAlgorithms.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Need Help? Want to discuss this post? Please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-6895703737936459211?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/6895703737936459211?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/6895703737936459211?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/tDvnaip0PMg/multiple-restart-search-multiple.html" title="Multiple Restart Search: Multiple runs to address convergence" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/multiple-restart-search-multiple.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcCSX45fip7ImA9WxVSGUo.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-5342835797416963320</id><published>2009-01-15T11:00:00.003+11:00</published><updated>2009-01-15T11:07:48.026+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-15T11:07:48.026+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="function optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="nonlinear programming" /><category scheme="http://www.blogger.com/atom/ns#" term="stochastic seach" /><category scheme="http://www.blogger.com/atom/ns#" term="adaptive random search" /><title>Adaptive Random Search: Varying step size based on performance</title><content type="html">The &lt;a href="http://www.inspiredalgorithms.com/2009/01/random-search-baseline-technique.html"&gt;Random Search&lt;/a&gt; algorithm involves the blind generation and evaluation of candidate solutions to a problem, where as the &lt;a href="http://www.inspiredalgorithms.com/2009/01/localized-random-search-sampling-around.html"&gt;Localized Random Search&lt;/a&gt; algorithm makes the simple addition of successively varying the best solution found by the search so far. The &lt;span style="font-weight: bold;"&gt;Adaptive Random Search&lt;/span&gt; algorithm, like&lt;span style="font-style: italic;"&gt; Localized Random Search&lt;/span&gt; is concerned with searching the neighborhood of the best solution found by the search so far, although unlike that algorithm the definition of neighborhood is not fixed. Instead it is changed dynamically over the course of the search.&lt;br /&gt;&lt;br /&gt;When exploring the neighborhood of a candidate solution, the size of the steps taken within the neighborhood may be fixed in what is referred to as Fixed Step Size Random Search. The Adaptive Random Search algorithm involves varying the step size during the search in response to the relative improvements or non-improving steps as defined by the objective function evaluation. A small number of samples are taken each iteration with varied small and large step sizes in an attempt to approximate an optimal step size to converge the search to the local optimum. The step size that generate an improved candidate solution is adopted as the current step size for the search, and improved solutions are taken as the current working position. When a given step size stops producing improvements, its size is reduced to encourage the search algorithm to more carefully explore the smaller localized neighborhood. Larger step sizes are frequently explored in an effort to potentially escape local optima, and very large step sizes are sampled with a low frequency in an effort to explore distant regions of the search space.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Adaptive Random Search for Continuous Function Optimization&lt;/span&gt;&lt;br /&gt;This guide provides an example of the Adaptive Random Search algorithm applied to an instance of the continuous function optimization problem.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family:courier new;"&gt;ExponentFunction&lt;/span&gt; class defines an instance of a function optimization problem with a variable number of function parameters or dimensions. The &lt;span style="font-family:courier new;"&gt;evaluate(vector)&lt;/span&gt; method expects a vector of real values in the problem bounds and evaluates it as the sum of the fourth power of each function parameter. The global optimum of this minimization function is located at 0.0 which is accessible via the &lt;span style="font-family:courier new;"&gt;optimal_score&lt;/span&gt; method and is used by &lt;span style="font-family:courier new;"&gt;is_optimal?(scoring)&lt;/span&gt; to test whether the algorithm has located to the global optimum. Finally, the problem provides a &lt;span style="font-family:courier new;"&gt;in_bounds?(vector)&lt;/span&gt; method for assessing whether a given vector falls within the hypercube of the valid problem space, useful for algorithms like Adaptive Random Search that generate new samples from existing samples and may stray out of the problem's bounds. &lt;pre class="prettyprint"&gt;class ExponentFunction&lt;br /&gt;  attr_reader :dimensions, :min, :max&lt;br /&gt;&lt;br /&gt;  def initialize(dimensions=2)&lt;br /&gt;    @dimensions = dimensions&lt;br /&gt;    @min, @max = -5.12, +5.12&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate(vector)&lt;br /&gt;    vector.inject(0) {|sum, x| sum + (x ** 4.0)}&lt;br /&gt;  end  &lt;br /&gt;  &lt;br /&gt;  def in_bounds?(vector)&lt;br /&gt;    vector.each {|x| return false if x&gt;@max or x&amp;lt;@min}&lt;br /&gt;    return true    &lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    0.0&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 has a better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family:courier new;"&gt;Solution&lt;/span&gt; class provides a convenience container for holding immutable real-valued vectors in the &lt;span style="font-family:courier new;"&gt;@data&lt;/span&gt; instance variable, and a mutable &lt;span style="font-family:courier new;"&gt;@score&lt;/span&gt; instance variable for holing the costing of the vector assigned by a problems objective function. &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family:courier new;"&gt;AdaptiveRandomSearchAlgorithm&lt;/span&gt; class provides a reasonably generic implementation of the algorithm. The &lt;span style="font-family: courier new;"&gt;initialize(max_iterations)&lt;/span&gt; constructor expects a maximum number of algorithm iterations as an argument and goes on to define a set of algorithm configuration parameters as class instance variables. The &lt;span style="font-family: courier new;"&gt;search(problem)&lt;/span&gt; method is large and contains all the problem agnostic conditional logic of the algorithm. The search starts by generating a random starting point for the search (&lt;span style="font-family: courier new;"&gt;current&lt;/span&gt;) and setting an initial step size of 10% of the size of the search domain. The main loop of the algorithm is concerned with first generating a new sample using the &lt;span style="font-family: courier new;"&gt;step_size&lt;/span&gt; and a second sample using a larger step size. The larger step size is fixed at &lt;span style="font-family: courier new;"&gt;@small_factor * step_size&lt;/span&gt; although periodically (once every &lt;span style="font-family: courier new;"&gt;@large_factor_multiple&lt;/span&gt; iterations) a much larger factor (&lt;span style="font-family: courier new;"&gt;@large_factor&lt;/span&gt;) of the current step size is used.&lt;br /&gt;&lt;br /&gt;If one of the generated steps results in a candidate solution better than the current solution it is replaced. If the larger step size resulted in the improvement, it is used the current step size. If neither generated step sizes resulted in an improved candidate solution, a count of non-improving steps is incremented. If this count exceeds &lt;span style="font-family: courier new;"&gt;@maximum_no_improvements&lt;/span&gt; then the current step size is reduced by a constant factor (&lt;span style="font-family: courier new;"&gt;step_size / @small_factor&lt;/span&gt;).&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;generate_random_solution(problem)&lt;/span&gt; method used for the starting point of the search generates a random vector in the bounds of the problem space. The &lt;span style="font-family: courier new;"&gt;take_step(problem, current, step_size)&lt;/span&gt; is responsible for stochastically generating a new solution from a provided existing solution within a neighborhood defined by the provided &lt;span style="font-family: courier new;"&gt;step_size&lt;/span&gt;. This works by generating a random vector between +/- &lt;span style="font-family: courier new;"&gt;step_size&lt;/span&gt; which is then added to the current sample. This means the neighbourhood is a &lt;span style="font-family: courier new;"&gt;step_size&lt;/span&gt; sized hypercube around the origin. Steps are taken by this method until a valid (within the problem bounds) candidate solution is generated. &lt;pre class="prettyprint"&gt;class AdaptiveRandomSearchAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations&lt;br /&gt;    @small_factor = 1.3 # normal step size incrase(*) and decrease(/) rate&lt;br /&gt;    @large_factor = 10 # step size factor increase for larger jump&lt;br /&gt;    @large_factor_multiple = 100 # max steps before larger jump is tried&lt;br /&gt;    @maximum_no_improvements = 50 # max non-improvement steps before size decreased&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a random search on the provided problem&lt;br /&gt;  def search(problem)    &lt;br /&gt;    # random starting point&lt;br /&gt;    current = generate_random_solution(problem)&lt;br /&gt;    evaluate_candidate_solution(current, problem)&lt;br /&gt;    curr_it = 0&lt;br /&gt;    step_size = (problem.max-problem.min)*0.1 # 10% of the domain&lt;br /&gt;    no_change_cnt = 0&lt;br /&gt;    begin&lt;br /&gt;      # current step&lt;br /&gt;      step = take_step(problem, current, step_size)&lt;br /&gt;      evaluate_candidate_solution(step, problem)&lt;br /&gt;      # take second larger step&lt;br /&gt;      factor = @small_factor&lt;br /&gt;      factor = @large_factor if curr_it.modulo(@large_factor_multiple) == 0&lt;br /&gt;      larger_step_size = step_size * factor&lt;br /&gt;      larger_step = take_step(problem, current, larger_step_size)&lt;br /&gt;      evaluate_candidate_solution(larger_step, problem)&lt;br /&gt;      # check for improvement&lt;br /&gt;      if problem.is_better?(step.score, current.score) or &lt;br /&gt;        problem.is_better?(larger_step.score, current.score)        &lt;br /&gt;        # select new step size if larger step is better&lt;br /&gt;        if problem.is_better?(larger_step.score, step.score)&lt;br /&gt;          step_size = larger_step_size # increase step size&lt;br /&gt;          current = larger_step # new best solution&lt;br /&gt;        else&lt;br /&gt;          current = step # new best solution&lt;br /&gt;        end&lt;br /&gt;        no_change_cnt = 0 # reset counter&lt;br /&gt;      # check for step size decrease&lt;br /&gt;      elsif (no_change_cnt+=1) &gt;= @maximum_no_improvements&lt;br /&gt;        no_change_cnt = 0 # reset counter&lt;br /&gt;        step_size /= @small_factor # decrease step size&lt;br /&gt;      end&lt;br /&gt;      curr_it += 1      &lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_random_solution(problem)&lt;br /&gt;    real_vector = Array.new(problem.dimensions) do&lt;br /&gt;      next_bfloat(problem.min, problem.max)&lt;br /&gt;    end&lt;br /&gt;    return Solution.new(real_vector)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def take_step(problem, current, step_size)&lt;br /&gt;    vector = nil&lt;br /&gt;    begin # keep stepping until a valid point is generated&lt;br /&gt;      step = Array.new(problem.dimensions) do&lt;br /&gt;        v = next_bfloat(-step_size, +step_size) &lt;br /&gt;      end&lt;br /&gt;      data = current.data&lt;br /&gt;      vector = Array.new(data.length) {|i| data[i] + step[i]}&lt;br /&gt;    end while !problem.in_bounds?(vector)&lt;br /&gt;    return Solution.new(vector)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def next_bfloat(min, max)&lt;br /&gt;    min + ((max - min) * rand)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def evaluate_candidate_solution(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if @best_solution.nil? or&lt;br /&gt;      problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution&lt;br /&gt;      puts "&gt; new best: #{solution.score}"               &lt;br /&gt;    end&lt;br /&gt;  end  &lt;br /&gt;end&lt;/pre&gt; The algorithm is demonstrated by first seeding the global random number number generator to 1 to ensure the same sequence of random numbers is ued for each run. A new instance of the algorithm is created allowing a maximum of 5000 iterations, and an instance of the &lt;span style="font-family: courier new;"&gt;ExponentFunction&lt;/span&gt; problem is created in 5 dimensions. The search is executed by passing the problem to the algorithm, and the best result achieved by the algorithm is printed once the search is completed. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = AdaptiveRandomSearchAlgorithm.new(5000) # limit to 5000 iterations &lt;br /&gt;problem = ExponentFunction.new(5) # create a problem with 5 dimensions&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; Once the general approach is understood, one may begin playing with the parameters defined in the algorithms constructor that govern the dynamic adaptation of the step size. For example, smaller or larger factors may be used, and the frequency of large or small step size increase and decreases may be varied. The acceptance criteria for when the current working solution is replaced may be made probabilistic rather than deterministically greedy. Finally, one may experiment with hyper-spherical sampling neighbourhoods in the &lt;span style="font-family: courier new;"&gt;next_step&lt;/span&gt; function and with sampling distributions other than uniform such as Gaussian with the origin as the mean and the &lt;span style="font-family: courier new;"&gt;step_size&lt;/span&gt; as the standard deviation.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/AdaptiveRandomSearch.rb"&gt;AdaptiveRandomSearch.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers:&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;M. Schumer and Steiglitz, &lt;a style="font-style: italic;" href="http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=1098903"&gt;Adaptive step size random search&lt;/a&gt;. IEEE Transactions on Automatic Control 13(3), pages 270- 276. 1968&lt;/li&gt;&lt;li&gt;S. F. Masri and G. Bekey, &lt;span style="font-style: italic;"&gt;Global Optimization Algorithm Using Adaptive Random Search&lt;/span&gt;. Applied Mathematics and Computation 7, pages 353-375. 1980&lt;/li&gt;&lt;li&gt;Rajeeva Kumar, Pierre T. Kabamba, David C. Hyland, &lt;a href="http://linkinghub.elsevier.com/retrieve/pii/S0378475404002617"&gt;&lt;span style="font-style: italic;"&gt;Analysis and parameter selection for an adaptive random search algorithm&lt;/span&gt;&lt;/a&gt;. Mathematics and Computers in Simulation 68(2), pages 95-103. 2005&lt;/li&gt;&lt;li&gt;Andrei A. Prudius, &lt;a href="http://smartech.gatech.edu/handle/1853/16318"&gt;&lt;span style="font-style: italic;"&gt;Adaptive Random Search Methods for Simulation Optimization&lt;/span&gt;&lt;/a&gt;. Ph.D. Thesis, Georgia Institute of Technology. 2007&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites:&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Random_optimization"&gt;&lt;span style="font-style: italic;"&gt;Random optimization&lt;/span&gt;&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;li&gt;&lt;a style="font-style: italic;" href="http://en.wikipedia.org/wiki/Stochastic_optimization"&gt;Stochastic optimization&lt;/a&gt;. Wikipedia&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have you found a bug? Know of a great reference? Got an idea for a guide? Please send an email to &lt;a href="mailto:jasonb@InspiredAlgorithms.com"&gt;jasonb@InspiredAlgorithms.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Need Help? Want to discuss this post? Please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-5342835797416963320?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5342835797416963320?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5342835797416963320?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/ZrWJIK9_si4/adaptive-random-search-varying-step.html" title="Adaptive Random Search: Varying step size based on performance" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/adaptive-random-search-varying-step.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08AQH0zeCp7ImA9WxVREEs.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-2012215077640866544</id><published>2009-01-13T11:08:00.022+11:00</published><updated>2009-01-16T11:30:41.380+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-16T11:30:41.380+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="iterated local search" /><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="TSP" /><category scheme="http://www.blogger.com/atom/ns#" term="metaheuristic" /><category scheme="http://www.blogger.com/atom/ns#" term="random search" /><title>Iterated Local Search: Cycling global and local search</title><content type="html">A Hill Climbing algorithm such as the one demonstrated in &lt;a href="http://www.inspiredalgorithms.com/2009/01/localized-random-search-sampling-around.html"&gt;Localized Random Search&lt;/a&gt; is an example of a Local Search Algorithm. A local search involves moving through a search space from one neighboring candidate solution to the next, such as from vertex to vertex along the edges of a graph. The &lt;span style="font-weight: bold;"&gt;Iterated Local Search&lt;/span&gt; algorithm involves the repeated application of a local search algorithm applied to the candidate solutions found by a broader search process that involves a biased random walk through the search space.&lt;br /&gt;&lt;br /&gt;The algorithm works by first selecting a starting point for the search either randomly or via a domain specific construction heuristic. The starting position is then optimized to an approximation of the local optimum using a local search strategy. The algorithm loop involves three steps: a perturbation of the current position, the application of the local search to the perturbation, and an acceptance decision of whether or not the locally optimizing candidate solution should replace the current working position for the search.&lt;br /&gt;&lt;br /&gt;A good starting point is important for shorter algorithm runs, whereas this importance degrades as the length of the run is increased because the algorithm is given more opportunity to improve. The Iterative Local Search algorithm resembles random restart of a local search technique, although out-performs this technique given the dependence between each new starting position of the search. The perturbation of the current working position provides long jumps in the search space that are further refined by the local search. If the jumps are too large, performance degrades as the behaviors resembles random restart. If the jumps are too small in the search space, the broader search technique resembles the local search algorithm.&lt;br /&gt;&lt;br /&gt;The goal is to find a balance between the perturbation and local search mechanisms within the algorithm such that the local search technique cannot easily undo the longer jumps made by the perturbations. There is a relationship between the perturbations and the acceptance criteria for replacing the current working position. Always accepting improved points may result in a greedy algorithm that only accepts small steps and cannot explore distant regions of the search space. Longer jumps made by the perturbation mechanism may require a more flexible (less greedy) acceptance criteria for the search to progress.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Iterated Local Search of the Traveling Salesman Problem&lt;/span&gt;&lt;br /&gt;The Iterated Local Search algorithm is suited to problem domains where the good solutions in the search space are clustered. The approach is more commonly applied to combinatorial optimization problems like the Traveling Salesman Problem (TSP) where this clustering of good solutions is manifest as the sharing of common edges.  This guide demonstrates the application of the Iterated Local Search algorithm applied to a standardized instance of the TSP from the TSP library (TSPLIB).&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family: courier new;"&gt;Berlin52TSP&lt;/span&gt; class contains a the city coordinate data and optimal tour data as &lt;span style="font-family: courier new;"&gt;COORDINATES&lt;/span&gt; and &lt;span style="font-family: courier new;"&gt;OPTIMAL_TOUR&lt;/span&gt; constants respectively, taken from the TSPLIB definition files for the Berlin52 problem instance. The &lt;span style="font-family: courier new;"&gt;initialize()&lt;/span&gt; constructor sets the accessible &lt;span style="font-family: courier new;"&gt;@num_cities&lt;/span&gt; instance variable and calculates the &lt;span style="font-family: courier new;"&gt;@optimal_tour_length&lt;/span&gt; used by the &lt;span style="font-family: courier new;"&gt;is_optimal?(scoring)&lt;/span&gt; method for assessing scores to see if an optimal tour has been discovered. The &lt;span style="font-family: courier new;"&gt;evaluate(permutation)&lt;/span&gt;method calculates the distance of a given tour permutation which is an array of non-repeating integers that specify cities between 1 and 52 inclusively. Evaluation involves calculating the Euclidean distance for each of the city pairs using the problem definition coordinates. The &lt;span style="font-family: courier new;"&gt;euc_2d(c1, c2)&lt;/span&gt; method matches the Euclidean calculation specified by the TSPLIB documentation, most notably involving the rounding of calculated distances to integers for this symmetrical TSP instance. &lt;pre class="prettyprint"&gt;class Berlin52TSP&lt;br /&gt;  OPTIMAL_TOUR = [1,49,32,45,19,41,8,9,10,43,33,51,11,52,14,13,47,26,&lt;br /&gt;    27,28,12,25,4,6,15,5,24,48,38,37,40,39,36,35,34,44,46,16,29,50,20,&lt;br /&gt;    23,30,2,7,42,21,17,3,18,31,22]&lt;br /&gt;        &lt;br /&gt;  COORDINATES = [[565, 575],[25, 185],[345, 750],[945, 685],[845, 655],&lt;br /&gt;  [880, 660],[25, 230],[525, 1000],[580, 1175],[650, 1130],[1605, 620], &lt;br /&gt;  [1220, 580],[1465, 200],[1530, 5],[845, 680],[725, 370],[145, 665],&lt;br /&gt;  [415, 635],[510, 875], [560, 365],[300, 465],[520, 585],[480, 415],&lt;br /&gt;  [835, 625],[975, 580],[1215, 245],[1320, 315],[1250, 400],[660, 180],&lt;br /&gt;  [410, 250],[420, 555],[575, 665],[1150, 1160],[700, 580],[685, 595],&lt;br /&gt;  [685, 610],[770, 610],[795, 645],[720, 635],[760, 650],[475, 960],&lt;br /&gt;  [95, 260],[875, 920],[700, 500],[555, 815],[830, 485],[1170, 65],&lt;br /&gt;  [830, 610],[605, 625],[595, 360],[1340, 725],[1740, 245]]&lt;br /&gt;  &lt;br /&gt;  attr_reader :num_cities&lt;br /&gt;&lt;br /&gt;  def initialize()&lt;br /&gt;    @num_cities = COORDINATES.length        &lt;br /&gt;    @optimal_tour_length = evaluate(OPTIMAL_TOUR) # calculate&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def evaluate(permutation)&lt;br /&gt;    dist = 0    &lt;br /&gt;    permutation.each_with_index do |c1, i|&lt;br /&gt;      c2 = (i==@num_cities-1) ? permutation[0] : permutation[i+1] &lt;br /&gt;      dist += euc_2d(COORDINATES[c1-1], COORDINATES[c2-1])&lt;br /&gt;    end&lt;br /&gt;    return dist&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def euc_2d(c1, c2)&lt;br /&gt;    # As defined in TSPLIB'95 (EUC_2D)&lt;br /&gt;    Math::sqrt((c1[0] - c2[0])**2 + (c1[1] - c2[1])**2).round&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    @optimal_tour_length&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 is better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;Solution&lt;/span&gt; class is a classical container for candidate solutions, holding a TSP permutation in the immutable &lt;span style="font-family: courier new;"&gt;@data&lt;/span&gt; instance variable and managing a mutable permutation scoring in the &lt;span style="font-family: courier new;"&gt;@score&lt;/span&gt; instance variable. &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family: courier new;"&gt;IteratedLocalAlgorithm&lt;/span&gt; class is quite large. The search(problem) method is generalized and executes the iterated local search algorithm on the provided problem definition. It starts by calling &lt;span style="font-family: courier new;"&gt;generate_initial_solution(problem)&lt;/span&gt; to prepare a starting point for the search and refining it with a call to &lt;span style="font-family: courier new;"&gt;local_search_solution(current, problem)&lt;/span&gt;. The algorithms main loop is a text book implementation involving repeated rounds of generating a perturbed versions of the current working solution, refining it with a local search procedure, and in this case using a greedy (candidate must be better) acceptance criteria.&lt;br /&gt;&lt;br /&gt;The meat of the algorithm are specific to TSP permutations. The &lt;span style="font-family: courier new;"&gt;generate_initial_solution(problem)&lt;/span&gt; generates a random permutation as the starting point of the search. The &lt;span style="font-family: courier new;"&gt;perturb_solution(solution)&lt;/span&gt; method generates a 4-opt variation of a given permutation, also known as a double-bridge move. Basically the permutation is split into 4 pieces and reordered. This function may be called repeatedly if more drastic perturbations are desired, perhaps on large problem instances.  The &lt;span style="font-family: courier new;"&gt;local_search_solution(solution, problem)&lt;/span&gt; method is an iterative application of the 2-opt procedure, that evaluates generated solutions and greedily maintains the best candidate solution discovered. The 2-opt procedure involves breaking the tour permutation into two parts (two break points) and reconnecting the tour back together by switching the end points (reversing the sequence between the break points). This is a classical and fast local search procedure for TSP and here it is repeated 30 times. &lt;pre class="prettyprint"&gt;class IteratedLocalAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a iterated local search on the provided problem&lt;br /&gt;  def search(problem)&lt;br /&gt;    # random starting point&lt;br /&gt;    current = generate_initial_solution(problem)&lt;br /&gt;    @best_solution = current&lt;br /&gt;    evaluate_candidate_solution(current, problem)&lt;br /&gt;    # local search&lt;br /&gt;    local_best = local_search_solution(current, problem)&lt;br /&gt;    current = local_best if problem.is_better?(local_best.score, current.score)&lt;br /&gt;    curr_it = 0&lt;br /&gt;    begin&lt;br /&gt;      # generate perturbation&lt;br /&gt;      pert_solution = perturb_solution(current)&lt;br /&gt;      evaluate_candidate_solution(pert_solution, problem)&lt;br /&gt;      # local search&lt;br /&gt;      local_best = local_search_solution(pert_solution, problem)&lt;br /&gt;      # greedy acceptance&lt;br /&gt;      current = local_best if problem.is_better?(local_best.score, current.score)&lt;br /&gt;      curr_it += 1&lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_initial_solution(problem)&lt;br /&gt;    all = Array.new(problem.num_cities) {|i| (i+1)}&lt;br /&gt;    permutation = Array.new(all.length) {|i| all.delete_at(rand(all.length))}&lt;br /&gt;    return Solution.new(permutation)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def perturb_solution(solution)    &lt;br /&gt;    data =  solution.data&lt;br /&gt;    length = data.length&lt;br /&gt;    # double-bridge move (4-opt), break into 4 parts (a,b,c,d)&lt;br /&gt;    pos1 = 1 + rand(length / 4)&lt;br /&gt;    pos2 = pos1 + 1 + rand(length / 4)&lt;br /&gt;    pos3 = pos2 + 1 + rand(length / 4)&lt;br /&gt;    # put it back together (a,d,c,b)&lt;br /&gt;    perm = data[0...pos1] + data[pos3...length] + &lt;br /&gt;      data[pos2...pos3] + data[pos1...pos2]&lt;br /&gt;    return Solution.new(perm)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def local_search_solution(solution, problem)&lt;br /&gt;    # greedy iterated 2-opt&lt;br /&gt;    30.times do&lt;br /&gt;      candidate = two_opt_solution(solution)&lt;br /&gt;      evaluate_candidate_solution(candidate, problem)&lt;br /&gt;      if problem.is_better?(candidate.score, solution.score)&lt;br /&gt;        solution = candidate &lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    return solution&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def two_opt_solution(solution)&lt;br /&gt;    perm = Array.new(solution.data) # copy&lt;br /&gt;    # select a sub-sequence&lt;br /&gt;    c1, c2 = rand(perm.length), rand(perm.length)&lt;br /&gt;    c2 = rand(perm.length) while c1 == c2&lt;br /&gt;    # ensure c1 is low and c2 is high&lt;br /&gt;    c1, c2 = c2, c1 if c2 &amp;lt; c1&lt;br /&gt;    # reverse sub-sequence&lt;br /&gt;    perm[c1...c2] = perm[c1...c2].reverse&lt;br /&gt;    return Solution.new(perm)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate_candidate_solution(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution&lt;br /&gt;      puts " &gt; new best: #{solution.score}"               &lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The demonstration of the algorithm involves first seeding the global random number generator to one, and constructing both an instance of the problem and the algorithm. The algorithm is executed on the problem instance and the algorithm stop condition is triggered if the optimal solution is discovered or a maximum of 2000 iterations of the main algorithm loop are completed. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = IteratedLocalAlgorithm.new(1000) # limit to 1000 iterations &lt;br /&gt;problem = Berlin52TSP.new # create a problem&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; There is plenty of room for extension of this interesting guide. The problem definition may be made generic such that it loads problem instance descriptions and optimal tours from TSPLIB files. The problem class may be made computationally more efficient by pre-calculating the city distance matrix on construction and looking up these distance evaluations in the evaluate method. The use of a randomly generated starting point for the search suggests that the algorithm may need to be executed for a long time to approximate the global optima (longer than 2000 iterations). The algorithm may be adjusted to use a deterministically generated nearest-neighbor solution as the starting point of the search that expected to be a much better quality solution. Finally, more advanced local search procedures exist for the TSP (such as Lin-Kernighan), which may be integrated into the algorithm.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/IteratedLocalSearch.rb"&gt;IteratedLocalSearch.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Helena Ramalhinho-Lourenço, Olivier C. Martin, Thomas Stützle, &lt;a href="http://www.springerlink.com/content/qt0875w6h6726n13/"&gt;&lt;span style="font-style: italic;"&gt;Iterated Local Search&lt;/span&gt;&lt;/a&gt;. (&lt;a href="http://arxiv.org/pdf/math/0102188"&gt;pre-print&lt;/a&gt;) &lt;a href="http://www.amazon.com/gp/product/1402072635?ie=UTF8&amp;amp;tag=inspiredalgor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=1402072635"&gt;Handbook of Metaheuristics&lt;/a&gt;, pages 320-353. Springer. 2003&lt;/li&gt;&lt;li&gt;Thomas Stützle, &lt;a href="http://linkinghub.elsevier.com/retrieve/pii/S0377221705002651"&gt;&lt;span style="font-style: italic;"&gt;Iterated local search for the quadratic assignment problem&lt;/span&gt;&lt;/a&gt;. (&lt;a href="http://www.intellektik.informatik.tu-darmstadt.de/TR/1999/99-03.ps.gz"&gt;pre-print&lt;/a&gt;) European Journal of Operational Research. 174(3), pages 1519-1539. 2006&lt;/li&gt;&lt;li&gt;David S. Johnson, &lt;a href="http://www.springerlink.com/content/d217268nr6qu3656/"&gt;&lt;span style="font-style: italic;"&gt;Local optimization and the Traveling Salesman Problem&lt;/span&gt;&lt;/a&gt;. Automata, Languages and Programming, pages 446-461. 1990&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a style="font-style: italic;" href="http://en.wikipedia.org/wiki/Local_search_%28optimization%29"&gt;Local search (optimization)&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Hill_climbing"&gt;&lt;span style="font-style: italic;"&gt;Hill climbing&lt;/span&gt;&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/"&gt;TSPLIB&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have you found a bug? Know of a great reference? Got an idea for a guide? Please send an email to &lt;a href="mailto:jasonb@InspiredAlgorithms.com"&gt;jasonb@InspiredAlgorithms.com&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Need Help? Want to discuss this post? Please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-2012215077640866544?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/2012215077640866544?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/2012215077640866544?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/hjo4CB7rC_M/iterated-local-search-cycling-global.html" title="Iterated Local Search: Cycling global and local search" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/iterated-local-search-cycling-global.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU4DSHs8fSp7ImA9WxVSGU0.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-5190405282741596229</id><published>2009-01-11T10:00:00.005+11:00</published><updated>2009-01-14T15:39:39.575+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-14T15:39:39.575+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="one max" /><category scheme="http://www.blogger.com/atom/ns#" term="mutation hill climbing" /><category scheme="http://www.blogger.com/atom/ns#" term="localized random search" /><category scheme="http://www.blogger.com/atom/ns#" term="binary function optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="random search" /><title>Localized Random Search: Sampling around known solutions</title><content type="html">This guide extends &lt;a href="http://inspiredalgorithms.com/2009/01/random-search-baseline-technique.html"&gt;Random Search&lt;/a&gt; by generating new candidate solutions as variations of the the best known solution. This approach is generally referred to as &lt;span&gt;&lt;span style="font-weight: bold;"&gt;Localized Random Search&lt;/span&gt;&lt;/span&gt; or as &lt;span&gt;&lt;span style="font-style: italic;"&gt;Mutation&lt;/span&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt; Hill Climbing&lt;/span&gt; by the evolutionary computation community. This approach works by firstly selecting a starting point for the search by generating a random solution or selecting the best solution from a set of randomly generated solutions. The algorithm then proceeds to generate new candidate solutions as variations of the current best, replacing the current best if and only if the new mutant candidate solution is better or has the same objective scoring.&lt;br /&gt;&lt;br /&gt;The search is localized to a known 'good' area of the problem space, and can rapidly locate the locally optimal solution. Naturally, the starting point for the localized search is critically important as once a starting point for the search is selected, the algorithm can at best return the locally optimal solution.  As such, the approach is commonly used to refine the results of another more broadly focused search algorithm. Additionally, Localized Random Search can operate upon a set of candidate starting points in parallel or be restarted after it has converged to a local optimum, both strategies of which may increase the chances better approximating the global optimal solution.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Mutation Hill Climbing Algorithm on the OneMax Problem&lt;/span&gt;&lt;br /&gt;The evolutionary computation community have produced some interesting work on Localized Random Search. Specifically regarding the binary string optimization under difficult and deceptive cost functions, referring to the algorithm as a stochastic hill climber or mutation hill climbing algorithm. This guide demonstrates a mutation hill climbing algorithm on a binary string optimization problem called OneMax.&lt;br /&gt;&lt;br /&gt;The OneMax problem involves the optimization of a string of binary digits (1's and 0's) of an arbitrary fixed length where the cost assigned to a given string reflects the number of 1's in the string. The optimal scoring in this maximization problem is equal to the length of the string, sometimes referred to as unity. It is an interesting black box function because although gradient information is provided (more or less 1's), the algorithm is not informed which positions in the string are right or wrong.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family:courier new;"&gt;OneMaxFunction&lt;/span&gt; class encapsulates the problem definition. It expects a string of characters comprised of 1's and 0's which are assessed via the &lt;span style="font-family:courier new;"&gt;evaluate(bitstring)&lt;/span&gt; method. The complexity of the problem is managed by the &lt;span style="font-family:courier new;"&gt;@length&lt;/span&gt; instance variable, which defaults to 16 bits. &lt;pre class="prettyprint"&gt;class OneMaxFunction&lt;br /&gt;  attr_reader :length&lt;br /&gt;&lt;br /&gt;  def initialize(length=16)&lt;br /&gt;    @length = length&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate(bitstring)&lt;br /&gt;    bitstring.inject(0) {|sum, x| sum + ((x=='1') ? 1 : 0)}&lt;br /&gt;  end  &lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    @length&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 has the same or better score than s2&lt;br /&gt;  def is_same_or_better?(s1, s2)&lt;br /&gt;    s1 &gt;= s2 # maximizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family:courier new;"&gt;Solution&lt;/span&gt; class is a simple convenience class, providing an immutable (problem non-specific) representation of candidate solutions with the &lt;span style="font-family:courier new;"&gt;data&lt;/span&gt; instance variable, and a mutable &lt;span style="font-family:courier new;"&gt;score&lt;/span&gt; instance variable for assigned costing. The &lt;span style="font-family:courier new;"&gt;to_s&lt;/span&gt; method enumerates &lt;span style="font-family:courier new;"&gt;data&lt;/span&gt; to ensure that the binary strings used in this example print nicely (for example: &lt;span style="font-family:courier new;"&gt;101100101&lt;/span&gt;). &lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.collect{|x|x}}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family:courier new;"&gt;LocalizedRandomSearchAlgorithm&lt;/span&gt; class is initialized with a maximum number of iterations to complete. The important method of this class is &lt;span style="font-family:courier new;"&gt;search(problem)&lt;/span&gt; which executes a search on the provided problem definition. The method calls &lt;span style="font-family:courier new;"&gt;generate_random_solution(problem)&lt;/span&gt; to create a random candidate solution as a starting point for the search. The algorithm then loops until the stop condition is triggered &lt;span style="font-family:courier new;"&gt;should_stop?(curr_it, problem)&lt;/span&gt; creating mutated variations of the current best known solution (&lt;span style="font-family:courier new;"&gt;@best_solution&lt;/span&gt; instance variable), and replacing the current best known solution of the mutated candidate has the same or equal scoring. This acceptance criteria (of not just accepting improved scores) is important as it allows the search to continue across flat regions of the search space, potentially escaping local optima.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family:courier new;"&gt;generate_mutate_solution(solution)&lt;/span&gt; method creates a new candidate solution from a provided solution. It works by enumerating the provided solutions data one bit (character) at a time, probabilistically mutating the string by inverting a give bit position ('0'=&gt;'1' or '1'=&gt;'0'). This is achieved by calling the &lt;span style="font-family:courier new;"&gt;should_mutate?(length)&lt;/span&gt; method for each bit in the string which permits a bit-flip mutation with the probability of 1/L, where L is the length of the bitstring being optimized. This has be demonstrated to be an optimal mutation rate for the OneMax function and related linear binary optimization problems (see references), can provide a good heuristic for problems with unknown response surfaces.&lt;pre class="prettyprint"&gt;class LocalizedRandomSearchAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a random search on the provided problem&lt;br /&gt;  def search(problem)&lt;br /&gt;    @best_solution = generate_random_solution(problem) # starting point&lt;br /&gt;    @best_solution.score = problem.evaluate(@best_solution.data)&lt;br /&gt;    curr_it = 0&lt;br /&gt;    begin&lt;br /&gt;      # generate mutant&lt;br /&gt;      candidate = generate_mutate_solution(@best_solution)&lt;br /&gt;      candidate.score = problem.evaluate(candidate.data)&lt;br /&gt;      # compare to current best&lt;br /&gt;      if problem.is_same_or_better?(candidate.score, @best_solution.score)&lt;br /&gt;        @best_solution = candidate&lt;br /&gt;        puts " &gt; new best: #{@best_solution.score}"    &lt;br /&gt;      end&lt;br /&gt;      curr_it += 1&lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_random_solution(problem)&lt;br /&gt;    bitstring = Array.new(problem.length) {|i| (rand&amp;lt;0.5) ? "1" : "0"}&lt;br /&gt;    return Solution.new(bitstring)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def generate_mutate_solution(solution)&lt;br /&gt;    data = solution.data&lt;br /&gt;    bitstring = Array.new(data.length) do |i| &lt;br /&gt;      if should_mutate?(data.length)&lt;br /&gt;         (data[i]=='0') ? "1" : "0" # invert&lt;br /&gt;      else&lt;br /&gt;        data[i]&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    return Solution.new(bitstring)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_mutate?(length)&lt;br /&gt;    rand &amp;lt; 1.0/length&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt;The algorithm is demonstrated by first seeding the global random number generator to 1, creating an instance of the problem initialized with 32 bits, and an instance of the algorithm with a maximum of 1000 algorithm iterations. The algorithm is executed by calling and passed the problem instance. The algorithms current progress is displayed as improved or equally best scored solutions are found and the best solution is displayed at the end of the run. &lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = LocalizedRandomSearchAlgorithm.new(1000) # limit to 1000 iterations&lt;br /&gt;problem = OneMaxFunction.new(32) # create a problem with 32 bits&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;/pre&gt; This implementation can be extended upon by modifying the &lt;span style="font-family:courier new;"&gt;LocalizedRandomSearchAlgorithm&lt;/span&gt; class to manage a population of 'best' solutions in parallel, or to generate a small set of mutate candidate solutions from the current best solution each iteration. The algorithm implementation is generalized and can be applied to other problems that align to the &lt;span style="font-family:courier new;"&gt;OneMaxFunction&lt;/span&gt; interface by specializing the &lt;span style="font-family:courier new;"&gt;generate_random_solution(problem)&lt;/span&gt; and &lt;span style="font-family:courier new;"&gt;generate_mutate_solution(solution)&lt;/span&gt; methods.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/LocalizedRandomSearch.rb"&gt;LocalizedRandomSearch.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Papers&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Thomas Bäck, "&lt;a href="http://portal.acm.org/citation.cfm?id=657408"&gt;Optimal Mutation Rates in Genetic Search&lt;/a&gt;". Proceedings of the Fifth International Conference on Genetic Algorithms, pages 2-9, 1993.&lt;/li&gt;&lt;li&gt;Heinz Mühlenbein, "&lt;a href="http://www.iais.fraunhofer.de/fileadmin/images/pics/Abteilungen/INA/muehlenbein/PDFs/technical/how-genetic-algorithms.pdf"&gt;How Genetic Algorithms Really Work: I. Mutation and Hillclimbing&lt;/a&gt;". Parallel Problem Solving from Nature 2, PPSN-II, Belgium, pp. 15-26, 1992.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;James C. Spall, &lt;a href="http://math.u-bourgogne.fr/monge/bibliotheque/ebooks/csa/htmlbook/node49.html"&gt;&lt;span style="font-style: italic;"&gt;Stochastic Optimization&lt;/span&gt;&lt;/a&gt;. In James E. Gentle, Wolfgang Härdle, Yuichi Mori, &lt;a href="http://math.u-bourgogne.fr/monge/bibliotheque/ebooks/csa/start.html"&gt;Handbook of Computational Statistics&lt;/a&gt;. 2004&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Sites&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Hill_climbing"&gt;Hill Climbing&lt;/a&gt;. Wikipedia&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;To get help or discuss this post, please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-5190405282741596229?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5190405282741596229?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5190405282741596229?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/7lNth2r4DPc/localized-random-search-sampling-around.html" title="Localized Random Search: Sampling around known solutions" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/localized-random-search-sampling-around.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYGQn04fSp7ImA9WxVSGU0.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-4805784480668325569</id><published>2009-01-10T10:00:00.009+11:00</published><updated>2009-01-14T15:42:03.335+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-14T15:42:03.335+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="stochastic optimization" /><category scheme="http://www.blogger.com/atom/ns#" term="blind search" /><category scheme="http://www.blogger.com/atom/ns#" term="generate and test" /><category scheme="http://www.blogger.com/atom/ns#" term="stochastic seach" /><category scheme="http://www.blogger.com/atom/ns#" term="random search" /><title>Random Search: A baseline technique</title><content type="html">The classical baseline for stochastic optimization is the &lt;span style="font-weight: bold;"&gt;random search algorithm&lt;/span&gt;, also referred to as &lt;span style="font-style: italic;"&gt;blind search&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;stochastic generate and test&lt;/span&gt;. The strategy of this algorithm involves randomly generating viable solutions from the problems search space and evaluating them. It is used as a baseline for comparison because it is general enough to be applied to any problem for which candidate solutions can be generated and tested.&lt;br /&gt;&lt;br /&gt;Each new randomly generated candidate solution is independent of the generated candidate solutions that came before it. Importantly, the algorithm keeps track of the candidate solution found so far using a simple ratcheting function (current best is only replaced by something as good as or better). Given that the random search algorithm samples the problem search space in an unbiased (uniform) manner it may discover areas of interest not considered by conventional wisdom.&lt;br /&gt;&lt;br /&gt;The penalty of the algorithms simplicity and lack of memory is its inefficiency. The algorithm does not make use of any problem specific information such as gradient of the response surface. It may perform worse than a complete enumeration of the search space given that there are no mechanisms to bias or prevent the procedure from re-investigating already visited regions of the search space.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-weight: bold;"&gt;Random Search for Continuous Function Optimization&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;This guide demonstrates the random search algorithm applied to a simple summed squaring function. We start by defining the problem as a class with methods for assessing and comparing candidate solutions. The function is defined as the sum of the squared parameters, where the number of function parameters defines the difficulty or number of dimensions of the problem. Each function parameter is constrained between -5.12 and +5.12.&lt;br /&gt;&lt;br /&gt;Candidate solutions are assessed via the &lt;span style="font-family:courier new;"&gt;evaluate&lt;/span&gt; method and are expected as arrays of floating point values. The global solution to the problem is known at 0.0 in each dimension which evaluates to the optimal score of 0.0. In one-dimension the function's response surface can be graphed which looks like the letter 'u' with the optima in the middle of the function. In two-dimensions the response surface looks like a basin. The known optimal scoring is made available in the &lt;span style="font-family:courier new;"&gt;is_optimal?(scoring)&lt;/span&gt; function, useful for an algorithm for accessing whether the optimal solution has been achieved. The problem also defines a solution score comparison, enforcing the minimization constrain of the problem.&lt;br /&gt;&lt;pre class="prettyprint"&gt;class SquaringFunction&lt;br /&gt;  attr_reader :dimensions, :min, :max&lt;br /&gt;&lt;br /&gt;  def initialize(dimensions=2)&lt;br /&gt;    @dimensions = dimensions&lt;br /&gt;    @min, @max = -5.12, +5.12&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def evaluate(vector)&lt;br /&gt;    vector.inject(0) {|sum, x| sum +  (x ** 2.0)}&lt;br /&gt;  end  &lt;br /&gt;&lt;br /&gt;  def is_optimal?(scoring)&lt;br /&gt;    scoring == optimal_score&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def optimal_score&lt;br /&gt;    0.0&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # true if s1 has a better score than s2&lt;br /&gt;  def is_better?(s1, s2)&lt;br /&gt;    s1 &amp;lt; s2 # minimizing&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt; In order to address this problem using the random search algorithm, a candidate solution class and algorithm class are defined. The &lt;span style="font-family:courier new;"&gt;Solution&lt;/span&gt; class is not problem specific, proving a immutable container for solution &lt;span style="font-family:courier new;"&gt;data&lt;/span&gt; in an instance variable which must be set on construction, as well as a &lt;span style="font-family:courier new;"&gt;score&lt;/span&gt; which can be assigned at any time. A default score of NaN (not a number) is assigned defensively to ensure that an error occurs if the solution is not evaluated and is compared to another solution that is evaluated.&lt;br /&gt;&lt;pre class="prettyprint"&gt;class Solution&lt;br /&gt;  attr_reader :data&lt;br /&gt;  attr_accessor :score&lt;br /&gt;  &lt;br /&gt;  def initialize(data)&lt;br /&gt;    @data = data&lt;br /&gt;    @score = 0.0/0.0 # NaN&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def to_s&lt;br /&gt;    "[#{@data.inspect}] (#{@score})"&lt;br /&gt;  end    &lt;br /&gt;end&lt;/pre&gt; The &lt;span style="font-family:courier new;"&gt;RandomSearchAlgorithm&lt;/span&gt; class requires a maximum number of iterations as parameters on construction which is stored as an instance variable. The core method of the class is &lt;span style="font-family:courier new;"&gt;search(problem)&lt;/span&gt; that executes a random search on the provided problem definition. A single iteration of the algorithm involves the generation of a random candidate solution and its evaluation. The algorithm loops until the stop condition &lt;span style="font-family:courier new;"&gt;should_stop?(curr_it, problem)&lt;/span&gt; is triggered which occurs if the maximum number of iterations have elapsed or if the optimal solution is discovered. The generation of random solutions in the &lt;span style="font-family:courier new;"&gt;generate_random_solution(problem)&lt;/span&gt; method is the only problem-specific logic in the algorithm, creating a random floating point vector within there bounds of the problem space. Finally, the &lt;span style="font-family:courier new;"&gt;evaluate_candidate_solution(solution, problem)&lt;/span&gt; method evaluates a given candidate solution against the problem definition and keeps track of the best solution found.&lt;br /&gt;&lt;pre class="prettyprint"&gt;class RandomSearchAlgorithm&lt;br /&gt;  attr_accessor :max_iterations&lt;br /&gt;  attr_reader :best_solution&lt;br /&gt;  &lt;br /&gt;  def initialize(max_iterations)&lt;br /&gt;    @max_iterations = max_iterations&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # execute a random search on the provided problem&lt;br /&gt;  def search(problem)    &lt;br /&gt;    @best_solution = nil&lt;br /&gt;    curr_it = 0&lt;br /&gt;    begin&lt;br /&gt;      candidate = generate_random_solution(problem)&lt;br /&gt;      evaluate_candidate_solution(candidate, problem)&lt;br /&gt;      curr_it += 1&lt;br /&gt;    end until should_stop?(curr_it, problem)&lt;br /&gt;    return @best_solution&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def should_stop?(curr_it, problem)&lt;br /&gt;    (curr_it &gt;= @max_iterations) or problem.is_optimal?(best_solution.score)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_random_solution(problem)&lt;br /&gt;    real_vector = Array.new(problem.dimensions) do&lt;br /&gt;      next_bfloat(problem.min, problem.max)&lt;br /&gt;    end&lt;br /&gt;    return Solution.new(real_vector)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def next_bfloat(min, max)&lt;br /&gt;    min + ((max - min) * rand)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def evaluate_candidate_solution(solution, problem)&lt;br /&gt;    solution.score = problem.evaluate(solution.data)&lt;br /&gt;    # keep track of the best solution found&lt;br /&gt;    if @best_solution.nil? or&lt;br /&gt;      problem.is_better?(solution.score, @best_solution.score)&lt;br /&gt;      @best_solution = solution&lt;br /&gt;      puts " &gt; new best: #{solution.score}"               &lt;br /&gt;    end&lt;br /&gt;  end  &lt;br /&gt;end&lt;/pre&gt; Now we can execute the algorithm on the problem. This involves initializing the global random number generator to 1 and initializing the algorithm with the maximum number of 1000 iterations to execute. The problem instance is created with 5 decision variables (dimensions) and the algorithm is executed on the instance.&lt;br /&gt;&lt;pre class="prettyprint"&gt;srand(1) # set the random number seed to 1&lt;br /&gt;algorithm = RandomSearchAlgorithm.new(1000) # limit to 1000 iterations&lt;br /&gt;problem = SquaringFunction.new(5) # create a problem with 5 dimensions&lt;br /&gt;best = algorithm.search(problem) # execute the search&lt;br /&gt;puts "Best Solution: #{best}" # display the best solution&lt;br /&gt;&lt;/pre&gt;This example provides a demonstration of a standard separation of &lt;span style="font-style: italic;"&gt;problem&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;algorithm&lt;/span&gt;, and &lt;span style="font-style: italic;"&gt;solution&lt;/span&gt;, as well as modularized responsibility within each class. For example, the &lt;span style="font-family:courier new;"&gt;RandomSearchAlgorithm&lt;/span&gt; class can be directly applied to any function optimization problem, or with a replacement of the &lt;span style="font-family:courier new;"&gt;generate_random_solution(problem)&lt;/span&gt; method it can be applied to any problem definition that aligns to the &lt;span style="font-family:courier new;"&gt;SquaringFunction&lt;/span&gt; classes simple interface.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-weight: bold;"&gt;Source Code&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Download: &lt;a href="http://inspired-algorithms.googlecode.com/svn/trunk/stochastic/RandomSearch.rb"&gt;RandomSearch.rb&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-weight: bold;"&gt;Further Reading&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Given the simplicity of the approach there are few resources dedicated to describing it. Instead the following provide some general resources for the broader field of stochastic optimization.&lt;br /&gt;&lt;br /&gt;Books&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="addmd"&gt;James C. Spall, &lt;/span&gt;&lt;a href="http://www.amazon.com/gp/product/0471330523?ie=UTF8&amp;amp;tag=inspiredalgor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0471330523"&gt;&lt;span style="font-style: italic;"&gt;Introduction to Stochastic Search and Optimization&lt;/span&gt;&lt;/a&gt;. Wiley-IEEE, 2005&lt;/li&gt;&lt;li&gt;Anatoly Zhigljavsky and Antanas Žilinskas, &lt;a href="http://www.amazon.com/gp/product/0387740228?ie=UTF8&amp;amp;tag=inspiredalgor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0387740228"&gt;&lt;span style="font-style: italic;"&gt;Stochastic Global Optimization (Springer Optimization and Its Applications)&lt;/span&gt;&lt;/a&gt;. Springer, 2008&lt;/li&gt;&lt;li&gt;James C. Spall, &lt;a href="http://math.u-bourgogne.fr/monge/bibliotheque/ebooks/csa/htmlbook/node49.html"&gt;&lt;span style="font-style: italic;"&gt;Stochastic Optimization&lt;/span&gt;&lt;/a&gt;. In James E. Gentle, Wolfgang Härdle, Yuichi Mori, &lt;a href="http://math.u-bourgogne.fr/monge/bibliotheque/ebooks/csa/start.html"&gt;Handbook of Computational Statistics&lt;/a&gt;. 2004&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Sites&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a style="font-style: italic;" href="http://en.wikipedia.org/wiki/Stochastic_optimization"&gt;Stochastic Optimization&lt;/a&gt;. Wikipedia&lt;/li&gt;&lt;/ul&gt;To get help with or discuss this post, please visit the &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-4805784480668325569?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/4805784480668325569?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/4805784480668325569?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/HLjC1z8kqzY/random-search-baseline-technique.html" title="Random Search: A baseline technique" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/random-search-baseline-technique.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0AERHw_cCp7ImA9WxVSFkk.&quot;"><id>tag:blogger.com,1999:blog-8843207661864398534.post-5909777246521240623</id><published>2009-01-10T08:50:00.002+11:00</published><updated>2009-01-11T14:48:25.248+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-11T14:48:25.248+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="introduction" /><category scheme="http://www.blogger.com/atom/ns#" term="vision" /><category scheme="http://www.blogger.com/atom/ns#" term="overview" /><category scheme="http://www.blogger.com/atom/ns#" term="goal" /><category scheme="http://www.blogger.com/atom/ns#" term="welcome" /><category scheme="http://www.blogger.com/atom/ns#" term="inspired algorithms" /><title>Welcome to Inspired Algorithms</title><content type="html">Hi, my name is Jason Brownlee and I am really excited to present &lt;a href="http://inspiredalgorithms.com/"&gt;Inspired Algorithms&lt;/a&gt;. The goal of this project is to &lt;span style="font-weight: bold;"&gt;write and offer the largest set of explained and executable computational intelligence algorithms on the planet&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Toward this goal I (at this stage, alone) will endeavor to write and publish a significant number of independent guides  that provide a functional introduction and working program code for a wide array of computational intelligence algorithms. The best of these guides will be collected and re-released as a compendium in the form of (1) a free e-book, and (2) a self-published printed book offered at a low cost.&lt;br /&gt;&lt;br /&gt;The phrase "&lt;span style="font-style: italic;"&gt;inspired algorithms&lt;/span&gt;" is used in this project as an umbrella term to refer to machine learning algorithms drawn from across many fields of artificial intelligence not limited to computational intelligence, biologically inspired computing, metaheuristics, natural computation, and biomemetics.&lt;br /&gt;&lt;br /&gt;An intention of this project is openness. The guides will be readable for a general audience with some programming, computer science, or related background. The programs will be understandable for the average programmer. A diverse set of popular and interesting algorithms will be covered, and a useful set of books, papers, and websites will be listed with each guide for further reading and extension.&lt;br /&gt;&lt;br /&gt;An &lt;a href="http://code.google.com/p/inspired-algorithms/"&gt;Inspired Algorithms Open Source Project&lt;/a&gt; has been created to maintain all source code presented in the guides. Source code is presented in the Ruby Programming Language and is released under the &lt;a href="http://www.gnu.org/licenses/gpl.html"&gt;GNU General Public License v3&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;An &lt;a href="http://groups.google.com/group/inspired-algorithms"&gt;Inspired Algorithms User Group&lt;/a&gt; has been created where we can discuss algorithms, guides and help each other out with what can sometimes be a complex but always rewarding field. There are no constraints on who can participate, and I encourage interested amateurs, professional developers, and career academics alike to ask and answer questions.  I hope together we can refine this set of guides and algorithm implementation to make them both accurate, complete, and easy to understand.&lt;br /&gt;&lt;br /&gt;You may subscribe to the guides on this site and read them as they are released by pointing your news reader to the &lt;a href="http://feeds.feedburner.com/InspiredAlgorithms"&gt;Inspired Algorithms Feed&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you're interested in the author, you can find out more about me on my &lt;a href="http://www.linkedin.com/in/jasonbrownlee"&gt;LinkedIn profile&lt;/a&gt; as well as &lt;a href="http://www.neverreadpassively.com/"&gt;my personal blog&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I look forward to this adventure of learning and discovery, and hope something here sparks your interest or curiosity in what is a truly exciting and powerful frontier of computation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8843207661864398534-5909777246521240623?l=www.inspiredalgorithms.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5909777246521240623?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8843207661864398534/posts/default/5909777246521240623?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/InspiredAlgorithms/~3/gcVmngBQjHw/welcome-to-inspired-algorithms.html" title="Welcome to Inspired Algorithms" /><author><name>Jason</name><uri>http://www.blogger.com/profile/11283603094324243247</uri><email>jason.brownlee05@gmail.com</email><gd:extendedProperty name="OpenSocialUserId" value="11867812671395170949" /></author><feedburner:origLink>http://www.inspiredalgorithms.com/2009/01/welcome-to-inspired-algorithms.html</feedburner:origLink></entry></feed>
