<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://aitmloukaddi.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aitmloukaddi.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-10-27T23:19:01+01:00</updated><id>https://aitmloukaddi.com/blog/feed.xml</id><title type="html">Addi Ait-Mlouk | Blog</title><subtitle>Addi Ait-Mlouk, Associate Professor (Docent) of AI and Data Science at the University of Skövde, working on federated and decentralized machine learning.</subtitle><author><name>Addi Ait-Mlouk</name></author><entry><title type="html">Does your ranking survive? Multi-criteria decisions with mcdakit</title><link href="https://aitmloukaddi.com/blog/mcdakit-ranking-that-holds.html" rel="alternate" type="text/html" title="Does your ranking survive? Multi-criteria decisions with mcdakit" /><published>2026-10-27T10:00:00+01:00</published><updated>2026-10-27T10:00:00+01:00</updated><id>https://aitmloukaddi.com/blog/mcdakit-ranking-that-holds</id><content type="html" xml:base="https://aitmloukaddi.com/blog/mcdakit-ranking-that-holds.html"><![CDATA[<p>Choosing between options that differ on several criteria at once is one of the most common decisions there is: suppliers, laptops, job offers, research proposals. Multi-criteria decision analysis (MCDA) gives that choice a method. You score every option on every criterion, weigh the criteria, and let an algorithm such as TOPSIS rank the options.</p>

<p>The ranking comes out looking exact, down to four decimals. This tutorial uses <a href="https://github.com/aitmlouk/mcdakit">mcdakit</a>, my open-source Python library, to ask two questions that most tools never ask:</p>

<ul>
  <li>Would the ranking change if a bad option were removed?</li>
  <li>How far would the weights have to move before a different option won?</li>
</ul>

<h2 id="install">Install</h2>

<p>mcdakit needs Python 3.9 or later, and numpy is its only dependency.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>git+https://github.com/aitmlouk/mcdakit
</code></pre></div></div>

<h2 id="the-decision">The decision</h2>

<p>We are choosing between four suppliers on four criteria. Price and lead time are costs, so lower is better. Quality and support are benefits, so higher is better. Supplier D is the cheapest, but it is poor on everything else.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">mcdakit</span> <span class="kn">import</span> <span class="n">Criterion</span><span class="p">,</span> <span class="n">rank</span><span class="p">,</span> <span class="n">reversal_check</span><span class="p">,</span> <span class="n">sensitivity</span>

<span class="n">criteria</span> <span class="o">=</span> <span class="p">[</span>
    <span class="n">Criterion</span><span class="p">(</span><span class="s">"Price"</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="mf">0.40</span><span class="p">,</span> <span class="n">direction</span><span class="o">=</span><span class="s">"cost"</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="p">(</span><span class="mf">2.00</span><span class="p">,</span> <span class="mf">4.00</span><span class="p">)),</span>
    <span class="n">Criterion</span><span class="p">(</span><span class="s">"Quality"</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="mf">0.25</span><span class="p">,</span> <span class="n">direction</span><span class="o">=</span><span class="s">"benefit"</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)),</span>
    <span class="n">Criterion</span><span class="p">(</span><span class="s">"Lead time"</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="mf">0.20</span><span class="p">,</span> <span class="n">direction</span><span class="o">=</span><span class="s">"cost"</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">35</span><span class="p">)),</span>
    <span class="n">Criterion</span><span class="p">(</span><span class="s">"Support"</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="mf">0.15</span><span class="p">,</span> <span class="n">direction</span><span class="o">=</span><span class="s">"benefit"</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)),</span>
<span class="p">]</span>
<span class="n">matrix</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">[</span><span class="mf">2.75</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mf">8.0</span><span class="p">],</span>  <span class="c1"># Supplier A
</span>    <span class="p">[</span><span class="mf">2.90</span><span class="p">,</span> <span class="mf">8.5</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mf">8.0</span><span class="p">],</span>  <span class="c1"># Supplier B
</span>    <span class="p">[</span><span class="mf">3.40</span><span class="p">,</span> <span class="mf">9.0</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">],</span>  <span class="c1"># Supplier C
</span>    <span class="p">[</span><span class="mf">2.20</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">],</span>  <span class="c1"># Supplier D: cheapest, but poor on everything else
</span><span class="p">]</span>
<span class="n">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s">"Supplier A"</span><span class="p">,</span> <span class="s">"Supplier B"</span><span class="p">,</span> <span class="s">"Supplier C"</span><span class="p">,</span> <span class="s">"Supplier D"</span><span class="p">]</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">bounds</code> gives each criterion’s plausible range, fixed in advance. We will see in a moment why that matters.</p>

<h2 id="step-1-rank-with-topsis">Step 1: rank with TOPSIS</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="n">rank</span><span class="p">(</span><span class="n">matrix</span><span class="p">,</span> <span class="n">criteria</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s">"topsis"</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">labels</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>topsis ranking:
  1. Supplier B   0.7159
  2. Supplier A   0.7044
  3. Supplier C   0.6238
  4. Supplier D   0.3666
</code></pre></div></div>

<p>Supplier B wins narrowly. Nobody would choose Supplier D.</p>

<h2 id="step-2-check-for-rank-reversal">Step 2: check for rank reversal</h2>

<p>Suppose Supplier D drops out. It was never going to win, so the order of the others should not change. Let’s check:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">check</span> <span class="o">=</span> <span class="n">reversal_check</span><span class="p">(</span><span class="n">matrix</span><span class="p">,</span> <span class="n">criteria</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s">"topsis"</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">labels</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">check</span><span class="p">[</span><span class="s">"reversed"</span><span class="p">])</span>           <span class="c1"># True
</span><span class="k">print</span><span class="p">(</span><span class="n">check</span><span class="p">[</span><span class="s">"cases"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"after"</span><span class="p">])</span>  <span class="c1"># ['Supplier A', 'Supplier C', 'Supplier B']
</span></code></pre></div></div>

<p>Removing the last-placed supplier sends the winner, Supplier B, from first to <strong>last</strong>. This is called <strong>rank reversal</strong>. It is not a bug in mcdakit or in your data. TOPSIS measures each option against the best and worst values found among the options themselves, so changing the set of options moves those reference points. Supplier D had the lowest price and the worst quality, lead time and support, so it set most of the reference points, and they were propping up Supplier B.</p>

<h2 id="step-3-rank-against-fixed-bounds-with-spotis">Step 3: rank against fixed bounds with SPOTIS</h2>

<p>SPOTIS measures every option against a fixed ideal point built from the <code class="language-plaintext highlighter-rouge">bounds</code>, never against the other options. Adding or removing an option cannot move anyone else’s score.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="o">=</span> <span class="n">rank</span><span class="p">(</span><span class="n">matrix</span><span class="p">,</span> <span class="n">criteria</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s">"spotis"</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">labels</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">winner</span><span class="p">)</span>          <span class="c1"># Supplier A
</span><span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">reversal_free</span><span class="p">)</span>   <span class="c1"># True
</span><span class="n">check</span> <span class="o">=</span> <span class="n">reversal_check</span><span class="p">(</span><span class="n">matrix</span><span class="p">,</span> <span class="n">criteria</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s">"spotis"</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">labels</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">check</span><span class="p">[</span><span class="s">"reversed"</span><span class="p">])</span>      <span class="c1"># False
</span></code></pre></div></div>

<p>If you leave out <code class="language-plaintext highlighter-rouge">bounds</code>, mcdakit falls back to the observed minimum and maximum, warns you, and sets <code class="language-plaintext highlighter-rouge">reversal_free</code> to <code class="language-plaintext highlighter-rouge">False</code>. It will not quietly hand back a ranking that only looks safe.</p>

<h2 id="step-4-how-fragile-is-the-winner">Step 4: how fragile is the winner?</h2>

<p>The weights (0.40, 0.25, 0.20, 0.15) were somebody’s judgement. A colleague might reasonably pick different ones. <code class="language-plaintext highlighter-rouge">sensitivity</code> finds, for each criterion, the smallest change to its weight that makes a different option win.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">report</span> <span class="o">=</span> <span class="n">sensitivity</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">report</span><span class="p">[</span><span class="s">"level"</span><span class="p">],</span> <span class="n">report</span><span class="p">[</span><span class="s">"weakest"</span><span class="p">])</span>  <span class="c1"># fragile Quality
</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">report</span><span class="p">[</span><span class="s">"rows"</span><span class="p">]:</span>
    <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="s">"tolerance"</span><span class="p">]</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="si">:</span><span class="o">&lt;</span><span class="mi">10</span><span class="si">}</span><span class="s"> never flips"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="si">:</span><span class="o">&lt;</span><span class="mi">10</span><span class="si">}</span><span class="s"> flips if it moves </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s">'tolerance'</span><span class="p">]</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="o">%</span><span class="si">}</span><span class="s"> "</span>
              <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s">'direction'</span><span class="p">]</span><span class="si">}</span><span class="s"> -&gt; </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s">'flips_to'</span><span class="p">]</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Price      flips if it moves 7.8% down -&gt; Supplier B
Quality    flips if it moves 3.9% up -&gt; Supplier B
Lead time  flips if it moves 8.8% down -&gt; Supplier B
Support    never flips
</code></pre></div></div>

<p>Each percentage is a share of the total weight. Raising the weight on quality from 0.25 to about 0.29 is enough to hand the win to Supplier B. mcdakit labels this decision <strong>fragile</strong>, its term for any winner that flips with a change of less than 10%.</p>

<p>That is the real finding. “A and B are practically tied, and the choice comes down to how much you value quality” is more honest, and more useful to a decision maker, than “A scored −0.3150”.</p>

<h2 id="what-to-take-away">What to take away</h2>

<ul>
  <li>Always check a ranking for reversal before you trust it. With mcdakit, that is one call to <code class="language-plaintext highlighter-rouge">reversal_check</code>.</li>
  <li>If the set of options can change, use a reversal-free method such as SPOTIS and give it real bounds.</li>
  <li>Report how robust the winner is, not just who won. A narrow win is still a result, as long as you say it is narrow.</li>
</ul>

<p>mcdakit has 17 ranking methods, including TOPSIS, VIKOR, PROMETHEE, ELECTRE and AHP, as well as weighting methods and group decisions. The <a href="https://aitmlouk.github.io/mcdakit">documentation</a> covers them all, and the <a href="https://github.com/aitmlouk/mcdakit">source is on GitHub</a>.</p>]]></content><author><name>Addi Ait-Mlouk</name></author><category term="Tutorial" /><summary type="html"><![CDATA[A hands-on Python tutorial: rank suppliers on four criteria, catch rank reversal and measure how fragile the winner is, with mcdakit.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aitmloukaddi.com/assets/img/og.png" /><media:content medium="image" url="https://aitmloukaddi.com/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Federated learning, explained without equations</title><link href="https://aitmloukaddi.com/blog/federated-learning-explained.html" rel="alternate" type="text/html" title="Federated learning, explained without equations" /><published>2026-10-27T09:00:00+01:00</published><updated>2026-10-27T09:00:00+01:00</updated><id>https://aitmloukaddi.com/blog/federated-learning-explained</id><content type="html" xml:base="https://aitmloukaddi.com/blog/federated-learning-explained.html"><![CDATA[<p>Most machine learning starts with the same step: collect all the data in one place, then train a model on it. For a lot of the data that matters most, that first step is impossible. Hospitals cannot send patient images to a shared server. Factories will not hand their process data to a competitor. Phones should not upload everything their owners type.</p>

<p><strong>Federated learning</strong> turns the usual approach around. Instead of bringing the data to the model, it sends the model to the data.</p>

<h2 id="how-it-works">How it works</h2>

<p>Picture five hospitals that each hold their own chest X-rays and want a better model for detecting pneumonia.</p>

<ol>
  <li>A coordinating server sends the same starting model to every hospital.</li>
  <li>Each hospital trains that model for a short while on its own images, behind its own firewall.</li>
  <li>Each hospital sends back only the <strong>model update</strong>: the changes to the model’s numbers, not the images.</li>
  <li>The server combines the updates into one improved model. The most common way, called FedAvg, is a weighted average.</li>
  <li>The improved model goes back out, and the cycle repeats until it stops getting better.</li>
</ol>

<p>No image ever leaves a hospital. The final model has still learned from the X-rays of all five, which is usually far more than any single hospital could train on alone.</p>

<h2 id="where-it-is-used">Where it is used</h2>

<ul>
  <li><strong>Healthcare.</strong> In one of our studies, a federated model told acute lymphoblastic leukemia apart from normal cells with 95.6% accuracy, close to a model trained on pooled data, while every image stayed with the client that owned it (<a href="/publications.html#lhasnaoui2024bridging">paper</a>).</li>
  <li><strong>Language.</strong> <a href="/publications.html#aitmlouk2022fedqas">FedQAS</a> trains question-answering models across organisations whose documents are private.</li>
  <li><strong>Industry.</strong> In the EU project <a href="/projects.html">ProcTwin</a>, we use federated learning so that steel plants can improve shared digital-twin models without exposing their process data.</li>
  <li><strong>Phones.</strong> Keyboard suggestions and voice features on many phones are trained this way, across millions of devices.</li>
</ul>

<h2 id="the-hard-parts">The hard parts</h2>

<p>Federated learning removes the need to share data. It does not make every problem go away.</p>

<p><strong>The data is uneven.</strong> One hospital sees mostly older patients, another mostly children. Models trained on such different slices pull in different directions, and a plain average can end up worse than any of them. Much current research, including ours on clients that join training late (<a href="/publications.html#aitmlouk2026fedrecall">FedRecall</a>), is about combining updates more cleverly.</p>

<p><strong>Devices are small and networks are slow.</strong> Sending a full deep-learning model back and forth every round is expensive at the edge. We showed that training only a random subset of layers on each device cut data transfer by around 75% when a quarter of the layers were trained, without hurting the final model (<a href="/publications.html#alawadi2024toward">paper</a>).</p>

<p><strong>Updates can leak.</strong> A model update is not the data, but it is not nothing either. With enough effort, attackers can sometimes recover details of the training data from it. Real deployments therefore add protections such as secure aggregation, where the server only ever sees the combined update, and differential privacy, which adds carefully measured noise.</p>

<p><strong>Some clients cheat.</strong> A client can send poisoned updates, for example by training on deliberately wrong labels. We proposed an aggregation rule that weighs each update by how well it performs on a small clean validation set, which held off both label flipping and hidden backdoors in our experiments (<a href="/publications.html#lhasnaoui2025adaptive">paper</a>).</p>

<h2 id="from-one-server-to-many">From one server to many</h2>

<p>The classic set-up has one central server, which becomes a bottleneck and a single point of failure as the number of clients grows. <strong>Hierarchical</strong> federated learning adds a middle layer. Clients report to a regional coordinator, the regions report to a global one, and each level aggregates what it receives.</p>

<p>This is the idea behind <a href="/software.html">Decentrai</a>, my upcoming open-source framework: clients train locally, regional coordinators aggregate their updates, and a global orchestrator builds the next model. It also includes 18 aggregation algorithms, differential privacy and LoRA fine-tuning of language models.</p>

<h2 id="when-to-reach-for-it">When to reach for it</h2>

<p>Federated learning is worth the extra engineering when three things are true:</p>

<ul>
  <li>the data cannot or should not be moved;</li>
  <li>it is spread across several owners or devices;</li>
  <li>each owner alone has too little data for a good model.</li>
</ul>

<p>If you can legally and practically put the data in one place, centralised training is simpler and usually a little more accurate. If you cannot, federated learning is often the only way to learn from it at all.</p>

<p>Want to go further? Start with the <a href="/publications.html?topic=fl">publications</a> filtered to federated learning, or look at the <a href="/join.html">open thesis topics</a> if you would like to work on these problems with us.</p>]]></content><author><name>Addi Ait-Mlouk</name></author><category term="Explainer" /><summary type="html"><![CDATA[What federated learning is, why hospitals and factories use it, where it falls short, and how our research tackles it. A plain-language introduction.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aitmloukaddi.com/assets/img/og.png" /><media:content medium="image" url="https://aitmloukaddi.com/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>