<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Rishabh&#39;s notes</title>
<link>https://blog.therealrishabh.com/</link>
<atom:link href="https://blog.therealrishabh.com/index.xml" rel="self" type="application/rss+xml"/>
<description>Research notes, technical explorations, and executable notebooks.</description>
<generator>quarto-1.9.37</generator>
<lastBuildDate>Tue, 18 Aug 2026 04:00:00 GMT</lastBuildDate>
<item>
  <title>Hopfield Machine Learning</title>
  <link>https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/</link>
  <description><![CDATA[ 




<p><em>Hopfield nets</em> are a unique type of machine learning that encode memories as local energy minima, and recover those memories by allowing similar images to settle towards the defined minima.</p>
<p>The basic idea of a hopfield net is to create an energy landscape, and define minima in that energy landscape to store information or generalizations. A hopfield net has many unique <em>binary configurations</em>, defined as a vectorized memory of N bits. We encode that as the vector <img src="https://latex.codecogs.com/png.latex?S%20%5Cin%20%5C%7B-1,%201%5C%7D%5EN">. This is better visualized as a collection of nodes. Each node is connected to every other node by a <em>weight</em> and also corresponds to a unique <em>bias</em>. Given the weights (<img src="https://latex.codecogs.com/png.latex?W">) and biases (<img src="https://latex.codecogs.com/png.latex?B">) of a hopfield net, as well as its binary configruation, there is an associated <em>energy</em>.</p>
<p>Energy is defined by <img src="https://latex.codecogs.com/png.latex?E=-%5Csum%7BS_i%20B_i%7D-%5Csum_%7Bi%20%5Cneq%20j%7D%7BS_i%20S_j%20W_%7Bij%7D%7D">.</p>
<p>You can converge to a local energy minima through a simple procedure:</p>
<ol type="1">
<li>Pick a random node</li>
<li>Compute <img src="https://latex.codecogs.com/png.latex?%5CDelta%20E_i%20=%20B_i%20+%20%5Csum_%7Bi%20%5Cneq%20j%7D%7BS_j%20W_%7Bij%7D%7D"></li>
<li>If <img src="https://latex.codecogs.com/png.latex?%5CDelta%20E_i%20%3C%200"> then set the state of the node to -1</li>
<li>Otherwise set the state of the node to 1</li>
<li>Repeat this proceudure on random nodes until you pass through every node and none of them require updates</li>
</ol>
<div id="4644ff7a-b182-4d85-9fb9-6245b9422935" class="cell" data-execution_count="25">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> energy_minimize(S, W, B):</span>
<span id="cb1-2">    N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(S)</span>
<span id="cb1-3">    requires_updates <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb1-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> requires_updates:</span>
<span id="cb1-5">        requires_updates <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb1-6">        indexes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N))</span>
<span id="cb1-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> indexes:</span>
<span id="cb1-8">            i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> indexes.pop(np.random.randint(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(indexes)))</span>
<span id="cb1-9">            dE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> B[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> W[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S</span>
<span id="cb1-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> np.sign(dE) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> np.sign(S[i]):</span>
<span id="cb1-11">                requires_updates <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb1-12">                S[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>S[i]</span>
<span id="cb1-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> S</span></code></pre></div></div>
</div>
<p>The question now becomes, how do we find weights such that the energy minima are actually useful?</p>
<p>For example, lets use the MNIST dataset (a collection of black and white images of handwritten images). We can encode each image as a binary configuration into the hopfield net. If we can now find a set of weights and biases that cause all images of the same number to converge to a common energy minima, we have in essence built a handwritten image classifier. There are a few methods we can employ to achieve this.</p>
<section id="hebbian-learning-rule" class="level2">
<h2 class="anchored" data-anchor-id="hebbian-learning-rule">Hebbian Learning Rule</h2>
<p>The <em>Hebbian learning rule</em> is a simple way to encode a specific binary configuration as a local minima in the energy space, thereby storing it as a memory in the hopfield net. In other words, we want to choose weights <img src="https://latex.codecogs.com/png.latex?W"> and biases <img src="https://latex.codecogs.com/png.latex?B"> such that <img src="https://latex.codecogs.com/png.latex?E"> has a local minima at the binary configuration <img src="https://latex.codecogs.com/png.latex?S">.</p>
<p>Taking a look at the formula for energy, we can see that in order to achieve this, we must ensure that our binary configuration produces the maximum value for <img src="https://latex.codecogs.com/png.latex?%5Csum%7BS_i%20B_i%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Csum_%7Bi%20%5Cneq%20j%7D%7BS_i%20S_j%20W_%7Bij%7D%7D"> compared to all other possible binary configurations. For the first term, we can simply make it so this term is always zero by zeroing out all biases. For the second term, let us restrict <img src="https://latex.codecogs.com/png.latex?W%20%5Cin%20%5C%7B-1,%201%5C%7D">. By doing this, we ensure that <img src="https://latex.codecogs.com/png.latex?S_i%20S_j%20W_%7Bij%7D%20%5Cin%20%5C%7B-1,%201%5C%7D">. Now, if you want to ensure that this term is at its maximum for a specific binary configuration, we want as many combinations of <img src="https://latex.codecogs.com/png.latex?S_i%20S_j%20W_%7Bij%7D%20=%201"> as possible. To do this, simply set <img src="https://latex.codecogs.com/png.latex?W_%7Bij%7D%20=%20S_i%20S_j">, that way, the signs of <img src="https://latex.codecogs.com/png.latex?S_i%20S_j"> and <img src="https://latex.codecogs.com/png.latex?W_%7Bij%7D"> cancel each other out!</p>
<div id="5fe9354d-eff7-4992-a8f3-5756a779addb" class="cell" data-execution_count="26">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hebbian_weights(S):</span>
<span id="cb2-2">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.outer(S, S)</span>
<span id="cb2-3">    np.fill_diagonal(W, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> W, np.zeros(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(S))</span></code></pre></div></div>
</div>
<p>Once you have defined these weights, take any random binary configuration and apply the energy minimization algorithm, and you will be able to recover the original binary configration used to define those weights.</p>
<div id="2f4c25b0-9a7b-4c3f-8e58-c0e36755c074" class="cell" data-execution_count="27">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> PIL <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Image</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Helper method to show a binary configuration as an image</span></span>
<span id="cb3-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> display_binary_configuration(S, height, width):</span>
<span id="cb3-7">    display_S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S.copy()</span>
<span id="cb3-8">    display_S[display_S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb3-9">    display_S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> display_S.reshape((height, width))</span>
<span id="cb3-10">    plt.imshow(display_S, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gray"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-11">    plt.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>)</span>
<span id="cb3-12">    plt.show()</span>
<span id="cb3-13"></span>
<span id="cb3-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Format input image</span></span>
<span id="cb3-15">path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"images/sailboat-20x20.png"</span></span>
<span id="cb3-16">S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(Image.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(path).convert(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span>), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>).flatten()</span>
<span id="cb3-17">S[S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-18">N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(S)</span>
<span id="cb3-19"></span>
<span id="cb3-20">display_binary_configuration(S, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>)</span>
<span id="cb3-21"></span>
<span id="cb3-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Create weights/biases matricies</span></span>
<span id="cb3-23">W, B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hebbian_weights(S)</span>
<span id="cb3-24"></span>
<span id="cb3-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Apply energy minimzation algorithm to random matrix</span></span>
<span id="cb3-26">S_new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> energy_minimize(np.random.choice([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>N), W, B)</span>
<span id="cb3-27"></span>
<span id="cb3-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Should be the same as the original image</span></span>
<span id="cb3-29">display_binary_configuration(S_new, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-4-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-4-output-2.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>Notice how <code>S_new</code> is defined with zero references to the original image, but is still able to recover the original image through energy minimization with the weights. So in essence, we have trained the hopfield net to “remember” the image of the sailboat. The other more obvious and interestig thing to notice is that <code>S_new</code> actual turned out to be the <em>inverse</em> of <code>S</code>. This is because both the original image and the inverse are energy minima when using the Hebbian learning rule.</p>
<p>To explore this visually, next we will plot the overlap of a binary configuration with the memory versus the energy of the binary configuration. Define <img src="https://latex.codecogs.com/png.latex?q=%5Csum%7BS_i%20%5Cxi_i%7D"> where <img src="https://latex.codecogs.com/png.latex?%5Cxi"> is the memory encoded into the hopfield net, and <img src="https://latex.codecogs.com/png.latex?S"> is the particular binary configuration. If <img src="https://latex.codecogs.com/png.latex?S_i"> and <img src="https://latex.codecogs.com/png.latex?%5Cxi_i"> agree perfectly, then <img src="https://latex.codecogs.com/png.latex?q=N">, and if they are perfect inverses, then <img src="https://latex.codecogs.com/png.latex?q=-N">. Let’s plot (q, E) for binary configurations of various energies.</p>
<div id="fab2a68f-29e5-4ac7-bdf4-ba5d654e28b4" class="cell" data-execution_count="28">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Produces a random binary configuration of energy N - 2k</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> random_binary_configuration(S, k):</span>
<span id="cb4-3">    N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(S)</span>
<span id="cb4-4">    indexes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N))</span>
<span id="cb4-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(indexes) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> k:</span>
<span id="cb4-6">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> indexes.pop(np.random.randint(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(indexes)))</span>
<span id="cb4-7">        S[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>S[i]</span>
<span id="cb4-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> S</span>
<span id="cb4-9"></span>
<span id="cb4-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Compute the energy</span></span>
<span id="cb4-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_energy(S, W, B):</span>
<span id="cb4-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S</span>
<span id="cb4-13">    </span>
<span id="cb4-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Form the dataset</span></span>
<span id="cb4-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> one_memory_dataset(S, W, B): </span>
<span id="cb4-16">    N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(S)</span>
<span id="cb4-17">    dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S"</span>: [], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q"</span>: [], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>: []}</span>
<span id="cb4-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N):</span>
<span id="cb4-19">        dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S"</span>].append(random_binary_configuration(S.copy(), k))</span>
<span id="cb4-20">        dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q"</span>].append(N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k)</span>
<span id="cb4-21">        dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>].append(compute_energy(dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S"</span>][k], W, B))</span>
<span id="cb4-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> dataset</span>
<span id="cb4-23">    </span>
<span id="cb4-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#Plot (q, E)</span></span>
<span id="cb4-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> plot_energy_landscape(dataset):</span>
<span id="cb4-26">    plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb4-27">    plt.plot(dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q"</span>], dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>], linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>)</span>
<span id="cb4-28">    plt.xlabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">q</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>)</span>
<span id="cb4-29">    plt.ylabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">E</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>)</span>
<span id="cb4-30">    plt.title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hopfield Energy Landscape"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>)</span>
<span id="cb4-31">    plt.show()</span>
<span id="cb4-32"></span>
<span id="cb4-33">plot_energy_landscape(one_memory_dataset(S, W, B))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-5-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>This clearly demonstrates that the Hebbian learning rule actually forms two energy minima, one of which is at the inverse of the original memory. Now lets try some more advanced learning rules.</p>
</section>
<section id="spurious-minima" class="level2">
<h2 class="anchored" data-anchor-id="spurious-minima">Spurious Minima</h2>
<p>To start, we can extend the Hebbian learning rule to encode multiple memories at once. Rather than defining <img src="https://latex.codecogs.com/png.latex?W_%7Bij%7D%20=%20S_i%20S_j"> how we did for a single memory, you can simply sum the different <img src="https://latex.codecogs.com/png.latex?S_i%20S_j"> pairs together for the different memories, so <img src="https://latex.codecogs.com/png.latex?W_%7Bij%7D%20=%20S%5E%7B(1)%7D_i%20S%5E%7B(1)%7D_j%20+%20S%5E%7B(2)%7D_i%20S%5E%7B(2)%7D_j%20+%20..."></p>
<div id="985915d4-ef8e-49d4-a891-86a219d4a3c4" class="cell" data-execution_count="29">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hebbian_weights(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>memories):</span>
<span id="cb5-2">    M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(memories)</span>
<span id="cb5-3">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> M</span>
<span id="cb5-4">    np.fill_diagonal(W, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> W, np.zeros(M.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> two_memory_dataset(S1, S2, W, B):</span>
<span id="cb5-8">    agree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(S1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> S2)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-9">    disagree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(S1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> S2)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-10">    dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S"</span>: [], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q1"</span>: [], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q2"</span>: [], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>: []}</span>
<span id="cb5-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(agree) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb5-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(disagree) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb5-13">            S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>S1.copy()</span>
<span id="cb5-14">            S[agree[:a]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S1[agree[:a]]</span>
<span id="cb5-15">            S[disagree[:d]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S1[disagree[:d]]</span>
<span id="cb5-16">            dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S"</span>].append(S)</span>
<span id="cb5-17">            dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q1"</span>].append(S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S1)</span>
<span id="cb5-18">            dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q2"</span>].append(S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S2)</span>
<span id="cb5-19">            dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>].append(compute_energy(S, W, B))</span>
<span id="cb5-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> dataset</span>
<span id="cb5-21"></span>
<span id="cb5-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> plot_energy_landscape(dataset):</span>
<span id="cb5-23">    q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q1"</span>])</span>
<span id="cb5-24">    q2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q2"</span>])</span>
<span id="cb5-25">    E  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(dataset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>])</span>
<span id="cb5-26">    fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>))</span>
<span id="cb5-27">    heatmap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.tricontourf(q1, q2, E, levels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"viridis"</span>)</span>
<span id="cb5-28">    minima <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.isclose(E, E.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>())</span>
<span id="cb5-29">    ax.scatter(q1[minima], q2[minima], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>, c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"red"</span>, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>)</span>
<span id="cb5-30">    fig.colorbar(heatmap, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">E</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-31">    ax.set_xlabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">q_1</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>)</span>
<span id="cb5-32">    ax.set_ylabel(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">q_2</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>)</span>
<span id="cb5-33">    ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hopfield Energy Landscape"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>)</span>
<span id="cb5-34">    plt.show()</span>
<span id="cb5-35"></span>
<span id="cb5-36">S2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(Image.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"images/owl-20x20.png"</span>).convert(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span>), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>).flatten()</span>
<span id="cb5-37">S2[S2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-38">W, B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hebbian_weights(S, S2)</span>
<span id="cb5-39">dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_memory_dataset(S, S2, W, B)</span>
<span id="cb5-40">plot_energy_landscape(dataset)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-6-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>Here we can see that <img src="https://latex.codecogs.com/png.latex?q_1=400">, <img src="https://latex.codecogs.com/png.latex?q_1=-400">, <img src="https://latex.codecogs.com/png.latex?q_2=400">, and <img src="https://latex.codecogs.com/png.latex?q_2=-400"> are all minima (indicated by the red dots). These minima perfectly correspond to the two memories and the inverses of these memories. Now lets see what happens if you try and add even more memories.</p>
<div id="c70ad69f-afd8-42d4-95fb-8315ad2ae4cf" class="cell" data-execution_count="30">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">S3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(Image.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"images/rocket-20x20.png"</span>).convert(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span>), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>).flatten()</span>
<span id="cb6-2">S3[S3<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-3">W, B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hebbian_weights(S, S2, S3)</span>
<span id="cb6-4">dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_memory_dataset(S, S2, W, B)</span>
<span id="cb6-5">plot_energy_landscape(dataset)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-7-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>Here the results are slightly less promising. After adding a third memory to the weights, the second memory is no longer a global minima. It is still a local minima as far as we can see in this plotted region. Lets try plotting a few different regions.</p>
<div id="1dda5c03-8dbe-423e-8476-7af691361873" class="cell" data-execution_count="31">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_memory_dataset(S, S3, W, B)</span>
<span id="cb7-2">plot_energy_landscape(dataset)</span>
<span id="cb7-3"></span>
<span id="cb7-4">dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_memory_dataset(S2, S3, W, B)</span>
<span id="cb7-5">plot_energy_landscape(dataset)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-8-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-8-output-2.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>Based on these plots, it seems like after adding a third dimension, the memories are no longer strict global minima. Are there any other global minima in this new energy landscape? Consider <img src="https://latex.codecogs.com/png.latex?S_%7B%5Ctext%7Bspur%7D%7D%20=%20%5Ctext%7Bsign%7D(S_1%20+%20S_2%20+%20S_3)">. Lets try plotting this memory using the existing weights.</p>
<div id="e1a7b431-215c-403c-9e9d-014e244b064d" class="cell" data-execution_count="32">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">S_spur <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sign(S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> S2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> S3)</span>
<span id="cb8-2">dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> two_memory_dataset(S, S_spur, W, B)</span>
<span id="cb8-3">plot_energy_landscape(dataset)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/index_files/figure-html/cell-9-output-1.png" class="img-fluid figure-img"></p>
</figure>
</div>
</div>
</div>
<p>Now the issue is clear. <img src="https://latex.codecogs.com/png.latex?S_%7Bspur%7D"> has turned into a <em>spurious minima</em>, or an extra memory that got encoded into the weights. This memory seems to have a lower minima than <img src="https://latex.codecogs.com/png.latex?S_1">, so random images would converge toward this unwanted memory rather than towards <img src="https://latex.codecogs.com/png.latex?S_1">. This is a fundamental limitation of the Hebbian learning rule. <em>Boltzmann machines</em> are a much more powerful extension of Hopfield nets that can help deal with this issue.</p>


</section>

 ]]></description>
  <guid>https://blog.therealrishabh.com/posts/thermodynamic-computing-lab/hopfield/</guid>
  <pubDate>Tue, 18 Aug 2026 04:00:00 GMT</pubDate>
</item>
</channel>
</rss>
