<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Zeta-Puppis.com &#187; Algorithms</title>
	<atom:link href="http://zeta-puppis.com/category/algorithms/feed/" rel="self" type="application/rss+xml" />
	<link>http://zeta-puppis.com</link>
	<description>my very own personal corner</description>
	<lastBuildDate>Wed, 07 Apr 2010 15:36:25 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Simulated Annealing</title>
		<link>http://zeta-puppis.com/2010/02/22/simulated-annealing/</link>
		<comments>http://zeta-puppis.com/2010/02/22/simulated-annealing/#comments</comments>
		<pubDate>Mon, 22 Feb 2010 13:17:59 +0000</pubDate>
		<dc:creator>kratorius</dc:creator>
				<category><![CDATA[Algorithms]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[algorithm]]></category>
		<category><![CDATA[combinatorial problem]]></category>
		<category><![CDATA[knapsack problem]]></category>
		<category><![CDATA[local search]]></category>
		<category><![CDATA[np-complete]]></category>
		<category><![CDATA[simulated annealing]]></category>
		<category><![CDATA[stochastic]]></category>

		<guid isPermaLink="false">http://zeta-puppis.com/?p=261</guid>
		<description><![CDATA[For a problem I&#8217;m working on I got stuck onto the classical situation of local maximum. After trying to work around the problem in several more or less creative ways, I thought of the simulated annealing algorithm. Considering it&#8217;s been a while since I last saw it I tried to search for it on the [...]]]></description>
			<content:encoded><![CDATA[<p>For a problem I&#8217;m working on I got stuck onto the classical situation of local maximum. After trying to work around the problem in several more or less creative ways, I thought of the <a href="http://en.wikipedia.org/wiki/Simulated_annealing">simulated annealing</a> algorithm. Considering it&#8217;s been a while since I last saw it I tried to search for it on the web and surprisingly there is not much stuff about it, and the few bits I found are often contraddictory. After quite a lot of digging I decided to write about it here. As a warning I should probably say that there will be digging into some basic statistic and complexity analysis, as well as a quick formal introduction to the problem of the knapsack. You should be able to follow even if you don&#8217;t know nothing about those topics, but having some foundations in these areas would be of great help.<br />
<span id="more-261"></span><br />
Let begin with the knapsack problem. This is a classic combinatorial computer science problem known to be <a href="http://en.wikipedia.org/wiki/NP-complete">NP-complete</a>, meaning that the exact optimal solution cannot be found in polynomial time. This often means that most of the times we are happy of a good solution, assuming it&#8217;s not so far from the optimal one. In the simpliest possible terms you are a thief and you&#8217;re in a room with a set of objects that are worth something but you have only one knapsack, and that knapsack can carry at most a certain weight, so you have to choose carefully what objects to steal in order to maximize the earnings. For example consider the following situation: you can carry at most 5kg, and there is one laptop and a 4kg safe with pure diamonds within. You can&#8217;t carry both of them so you have to choose what&#8217;s better to carry on, the diamonds or the laptop. A smart thief would choose the diamonds since their value is considerably higher than the&nbsp;laptop.</p>
<p>There are few variations of the same problem but most common one is named &#8220;0-1&#8221;: you can&#8217;t split the weight over two or more carriers or bags but either you take the whole weight or you leave the object where it is. Mathematically talking, consider a set of <img src='http://s.wordpress.com/latex.php?latex=n&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n' title='n' class='latex' /> objects, each item <img src='http://s.wordpress.com/latex.php?latex=x_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='x_j' title='x_j' class='latex' /> is worth <img src='http://s.wordpress.com/latex.php?latex=p_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='p_j' title='p_j' class='latex' /> and weights <img src='http://s.wordpress.com/latex.php?latex=w_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='w_j' title='w_j' class='latex' /> with <img src='http://s.wordpress.com/latex.php?latex=1%20%5Cleq%20j%20%5Cleq%20n&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='1 \leq j \leq n' title='1 \leq j \leq n' class='latex' />. Then the goal is to maximize the following&nbsp;function:</p>
<img src='http://s.wordpress.com/latex.php?latex=q%28%5C%7Bx_1%2C%20x_2%2C%20%5Cldots%2C%20x_n%5C%7D%29%20%3D%20%5Csum_%7Bj%3D0%7D%5E%7Bn%7Dp_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='q(\{x_1, x_2, \ldots, x_n\}) = \sum_{j=0}^{n}p_j' title='q(\{x_1, x_2, \ldots, x_n\}) = \sum_{j=0}^{n}p_j' class='latex' />
<p>But keeping the following constraint (being <img src='http://s.wordpress.com/latex.php?latex=W&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='W' title='W' class='latex' /> the maximum weight we can&nbsp;carry):</p>
<img src='http://s.wordpress.com/latex.php?latex=%5Csum_%7Bj%3D0%7D%5E%7Bn%7Dw_j%20%5Cleq%20W&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\sum_{j=0}^{n}w_j \leq W' title='\sum_{j=0}^{n}w_j \leq W' class='latex' />
<p>The first problem we have to face then is how we should generate the items and how the value of a solution should be calculated. The following example, as the other that will follow, it&#8217;s written in Python but it&#8217;s quite easy to understand so porting to another language wouldn&#8217;t be that hard. I chosen to generate 50 objects with values that range from 1 to 99$ (both ends included) using a <a href="http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)">uniform distribution</a> (if you don&#8217;t know much about statistic, it means that all the values are equally distributed among the objects). The same with the weights except they range from 1 to 20 (the choice of the weight&#8217;s unit measure is left to&nbsp;you).</p>
<pre><code>def generate_items(n_items=100):
    "Generate a list of items that could be stealed"
    items = []
    for n in range(0, n_items):
        # use a uniform distribution both for values and for weights
        cost = random.randint(1, 100)
        weight = random.uniform(1, 20)
        items.append((cost, weight))
    return items</code></pre>
<p>So the items are nothing other than a list of pairs in the format <img src='http://s.wordpress.com/latex.php?latex=%28value_i%2C%20weight_i%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(value_i, weight_i)' title='(value_i, weight_i)' class='latex' /> for every object <img src='http://s.wordpress.com/latex.php?latex=x_i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='x_i' title='x_i' class='latex' />. Probably a better and more realistic dataset would have used a <a href="http://en.wikipedia.org/wiki/Normal_distribution">normal distribution</a> for the weights but it&#8217;s trivial to change the generation to function to work in that way. For our purposes the uniform distribution does its&nbsp;job.</p>
<p>As we said above the problem is NP-complete so we usually need to visit the whole search space to get the optimal solution which can be quite big when as the number of objects grows. Here comes the simulated annealing. We won&#8217;t visit the search space extensively, but we&#8217;d rather <em>generate</em> solutions. Indeed, it is a stochastic heuristic search algorithm. An heuristic is a function that measures how much something is good or bad, and stochastic means that we move more or less in a random way into the search space. In practical terms it&#8217;s not greedy as that it doesn&#8217;t always follow what the heuristic says but rather randomly search where the heuristic function points to. For example consider the needle in the haystack situation: an exaustive search method would take every straw piece, check that it&#8217;s not a needle, put it apart and repeat those moves until you don&#8217;t find the needle. You don&#8217;t want to proceed in this way, you&#8217;re more likely to end in less time if you look randomly in the haystack and if somethings stings you while you&#8217;re holding straw then search into that straw piece, because there may be the needle in there. In the knapsack problem we do have the heuristic, and it&#8217;s the <img src='http://s.wordpress.com/latex.php?latex=q&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='q' title='q' class='latex' /> function above that, for each solution (admissible or not), says how much it&#8217;s worth. In this case we define an admissible solution as one that it&#8217;s not too&nbsp;heavy.</p>
<p>An useful (and classic as well) example is the one of the blind hill climbing. You&#8217;re blind and stuck on a hill and you need to reach the top. A good principle that could lead you to the top is to touch the terrain and always follow the rising path. It <i>could</i>, because if you&#8217;re on a rock than you surely haven&#8217;t reached the top but the principle above doesn&#8217;t apply: you reached a local maximum (invert the things and you get the same thing for a local minimum). Simulated annealing avoids these problems by trying worsening moves from time to time: even if this may not sound like a good move it helps avoiding the problems we described&nbsp;above.</p>
<p><img src="http://zeta-puppis.com/wp-content/uploads/2010/02/plotsurf.gif" class="align-center" /></p>
<p>In the function above once in the middle we could choose to take the left maximum (which is a local maximum). Using hill climbing we&#8217;d be stuck on that because we wouldn&#8217;t try other&nbsp;paths.</p>
<p>Simulated annealing takes its name from the same process that metals go through when cooling from a melting point. Indeed, the cooling process consists of several particles that changes energy states (this statement may not be accurate or be inexact at all, but please forgive me as I never studied those things and I all know in this field comes from simulated annealing algorithm itself), in particular we can calculate the transiction probability from one state to another. Considering two energy states <img src='http://s.wordpress.com/latex.php?latex=e_i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='e_i' title='e_i' class='latex' /> and <img src='http://s.wordpress.com/latex.php?latex=e_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='e_j' title='e_j' class='latex' /> and a temperature <img src='http://s.wordpress.com/latex.php?latex=T&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T' title='T' class='latex' />, switching from <img src='http://s.wordpress.com/latex.php?latex=e_i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='e_i' title='e_i' class='latex' /> to <img src='http://s.wordpress.com/latex.php?latex=e_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='e_j' title='e_j' class='latex' /> has&nbsp;probability:</p>
<img src='http://s.wordpress.com/latex.php?latex=P%28e_i%2C%20e_j%20%7C%20T%29%20%3D%20e%5E%7B%28e_i%20-%20e_j%29%20%2F%20%28k_BT%29%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='P(e_i, e_j | T) = e^{(e_i - e_j) / (k_BT)}' title='P(e_i, e_j | T) = e^{(e_i - e_j) / (k_BT)}' class='latex' />
<p>Where <img src='http://s.wordpress.com/latex.php?latex=k_B&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='k_B' title='k_B' class='latex' /> is a constant called <a href="http://en.wikipedia.org/wiki/Boltzmann_constant">Boltzmann&#8217;s constant</a>. But then, how do we apply those statements to our problem (or a combinatorial search problem, in general)? The most important concept to grasp is the energy switching one. As a particle change state, a solution might change. Indeed for the knapsack problem there are many admissible solution, each one with an associated earning. Of course we&#8217;d prefer the one with the higher earnings (and simulated annealing will help us find that) but still it&#8217;s perfectly acceptable to go from a solution to another as long as the other solution continues to be&nbsp;admissible.</p>
<p>So here it is what the simulated annealing does: if you find a better item go on an take that path (under this circumnstance, behaves just like the hill climbing), otherwise change state with probability <img src='http://s.wordpress.com/latex.php?latex=P%28e_i%2C%20e_j%20%7C%20T%29%20%3D%20e%5E%7B%28e_i%20-%20e_j%29%20%2F%20T%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='P(e_i, e_j | T) = e^{(e_i - e_j) / T}' title='P(e_i, e_j | T) = e^{(e_i - e_j) / T}' class='latex' /> (you may notice that the Boltzmann&#8217;s constant is missing, indeed that constant applies mostly to thermodynamic when dealing with different metals). The effect that the temperature scaling has is that at higher temperatures it&#8217;ll try worsening moves quite often while on lower temperatures the probability to do worsening moves is lower so when temperature tends towards 0 it behaves quite like the hill climbing&nbsp;algorithm.</p>
<pre><code>def simulated_annealing(solution, items, max_weight):
    "Apply the simulated annealing for solving the knapsack problem"
    best = solution
    best_value = compute_cost(solution, items)[0]
    current_sol = solution
    temperature = 1.0

    while True:
        current_value = compute_cost(best, items)[0]

        for i in range(0, COOLING_STEPS):
            moves = generate_moves(current_sol, items, max_weight)
            idx = random.randint(0, len(moves) - 1)
            random_move = moves[idx]

            delta = compute_cost(random_move, items)[0] - compute_cost(best, items)[0]

            if delta &gt; 0:
                best = random_move
                best_value = compute_cost(best, items)[0]
                current_sol = random_move
            else:
                if math.exp(delta / float(temperature)) &gt; random.random():
                    current_sol = random_move

        temperature = TEMP_ALPHA * temperature
        if current_value &gt;= best_value or temperature &lt;= 0:
            break</code></pre>
<p>And finally, that is the simulated annealing. You start from a temperature of 1.0 then you have a certain number of cooling steps, in every one of them you extract a random item from the neighbours and, if the item is better than the current best item then it becomes the new best item (and the new local solution). If the new item&#8217;s value is worst than the current best then update the current local solution with the probability expressed above. After the cooling steps the temperature is decreased with an <a href="http://en.wikipedia.org/wiki/Exponential_decay">exponential decay</a> (usually it is&nbsp;<img src='http://s.wordpress.com/latex.php?latex=t%20%3D%20%5Calpha%20t%2C%200.8%20%3C%20%5Calpha%20%3C%200.9&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='t = \alpha t, 0.8 &lt; \alpha &lt; 0.9' title='t = \alpha t, 0.8 &lt; \alpha &lt; 0.9' class='latex' />).</p>
<p>In the example above you don&#8217;t wait for the temperature to be 0 but you leave the loop if after all the cooling steps there hasn&#8217;t been any improvement. How big must be the number of cooling steps and the <img src='http://s.wordpress.com/latex.php?latex=%5Calpha&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\alpha' title='\alpha' class='latex' /> value it&#8217;s a fine tuning problem. A different approach that allow to get rid of the cooling steps is to make the temperature get cold slowly (<img src='http://s.wordpress.com/latex.php?latex=t%20%3D%20%20t%20%2F%20%281%20%2B%20%28%5Cbeta%20t%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='t =  t / (1 + (\beta t))' title='t =  t / (1 + (\beta t))' class='latex' /> and <img src='http://s.wordpress.com/latex.php?latex=%5Cbeta&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\beta' title='\beta' class='latex' /> is a very small value like 0.01). Besides, a common improvement is to cache the moves within the cooling steps unless you found a new best value or changed&nbsp;state.</p>
<p>You can see that the most critical points are the neighbour generation and the cost computation. While the neighbour generation could be cached like I said above, the cost computation could be replaced with a probability estimate in order to reduce the time per cooling&nbsp;step.</p>
<p>But how do you apply the algorithm? If an empty solution is acceptable then you can just start with that and let the neighbour&#8217;s generator to create a solution, but usually you start with a greedy solution (found through the hill climbing, for example) or from a random&nbsp;one.</p>
<p>Here follows the complete code. I used 1000 cooling steps and a <img src='http://s.wordpress.com/latex.php?latex=%5Calpha&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\alpha' title='\alpha' class='latex' /> value of 0.8. I start from a random solution whose behaviour is not that bad considering how much time it takes to compute the solution. Indeed often random algorithms perform really well on combinatorial algorithms, see <a href="http://www.cs.ubc.ca/labs/beta/Courses/CPSC532D-02/tutorial-slides.pdf">stochastic search</a> for some more&nbsp;informations.</p>
<pre><code>#!/usr/bin/env python
import math
import operator
import pprint
import random
import sys

COOLING_STEPS = 1000
TEMP_ALPHA = 0.8

random.seed()

def generate_items(n_items=100):
    "Generate a list of items that could be stealed"
    items = []
    for n in range(0, n_items):
        # use a uniform distribution both for values and for weights
        cost = random.randint(1, 100)
        weight = random.uniform(1, 20)
        items.append((cost, weight))
    return items

def main(args):
    items = generate_items()
    pprint.pprint(items)

    start_sol = generate_random_solution(items, max_weight=40)
    print "Random solution: %s" % start_sol
    print "value: (cost: %d, weight: %f)" % compute_cost(start_sol, items)

    solution = simulated_annealing(start_sol, items, max_weight=40)
    print "Final solution: %s" % solution
    print "value: (cost: %d, weight: %f)" % compute_cost(solution, items)

    return False

def generate_random_solution(items, max_weight):
    "Generate a starting random solution"

    # generate a random solution by adding a random item
    # until we don't get over the weight
    solution = []
    while compute_cost(solution, items)[1] &lt;= max_weight:
        idx = random.randint(0, len(items) - 1)
        # skip duplicates
        if idx not in solution:
            solution.append(idx)
    # last item makes us get over the weight so simply remove it
    # we'll look for better results after
    solution = solution[:-1]
    return solution

def simulated_annealing(solution, items, max_weight):
    "Apply the simulated annealing for solving the knapsack problem"
    best = solution
    best_value = compute_cost(solution, items)[0]
    current_sol = solution
    temperature = 1.0

    while True:
        current_value = compute_cost(best, items)[0]

        for i in range(0, COOLING_STEPS):
            moves = generate_moves(current_sol, items, max_weight)
            idx = random.randint(0, len(moves) - 1)
            random_move = moves[idx]

            delta = compute_cost(random_move, items)[0] - \
                    compute_cost(best, items)[0]

            if delta &gt; 0:
                best = random_move
                best_value = compute_cost(best, items)[0]
                current_sol = random_move
            else:
                if math.exp(delta / float(temperature)) &gt; random.random():
                    current_sol = random_move

        temperature = TEMP_ALPHA * temperature
        if current_value &gt;= best_value or temperature &lt;= 0:
            break
    return best

def generate_moves(solution, items, max_weight):
    """
    Generate all the ammissible moves starting from the input
    solution
    """
    moves = []
    # try to add another item and save as a possible move
    for idx, item in enumerate(items):
        if idx not in solution:
            move = solution[::]
            move.append(idx)

            if compute_cost(move, items)[1] &lt;= max_weight:
                moves.append(move)

    # try to remove one item
    for idx, item in enumerate(solution):
        move = solution[::]
        del move[idx]
        if move not in moves:
            moves.append(move)

    return moves

def compute_cost(solution, items):
    """
    Return a tuple in the format (id_item1, id_item2, ...)
    for the input solution
    """
    cost, weight = 0, 0
    for item in solution:
        cost += items[item][0]
        weight += items[item][1]
    return (cost, weight)

if __name__ == '__main__':
    sys.exit(main(sys.argv))</code></pre>
<p>The results are suprising. For three different set of 100 items, each one with its own value and weight, here are the&nbsp;results:</p>
<pre><code>$ python sa.py
Random solution: [98, 71, 95]
value: (cost: 44, weight: 27.001685)
Final solution: [71, 95, 67, 9, 41, 33, 27]
value: (cost: 229, weight: 39.791386)

$ python sa.py
Random solution: [38, 16, 62, 31]
value: (cost: 124, weight: 36.863846)
Final solution: [38, 16, 62, 31, 5]
value: (cost: 194, weight: 38.970745)

Random solution: [61, 44, 48, 38]
value: (cost: 293, weight: 30.357135)
Final solution: [61, 44, 48, 38, 37, 5, 2]
value: (cost: 421, weight: 39.331549)</code></pre>
<p>We usually don&#8217;t leave much free weight and the quality of the solutions found is quite good considering that the time required to compute that is&nbsp;~0.5s.</p>
]]></content:encoded>
			<wfw:commentRss>http://zeta-puppis.com/2010/02/22/simulated-annealing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
