<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Julia Community 🟣: Patrick Altmeyer</title>
    <description>The latest articles on Julia Community 🟣 by Patrick Altmeyer (@patalt).</description>
    <link>https://forem.julialang.org/patalt</link>
    <image>
      <url>https://forem.julialang.org/images/eas5H_56FnCwUu9Ia0wPEtoYrLL-4ybWdS9L5Vo5-bU/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9mb3Jl/bS5qdWxpYWxhbmcu/b3JnL3JlbW90ZWlt/YWdlcy91cGxvYWRz/L3VzZXIvcHJvZmls/ZV9pbWFnZS81NjMv/MjQ0MjZmNzItZDBl/NS00YzA3LTg0ZTkt/MzczYTdmNDVkNDE5/LmpwZWc</url>
      <title>Julia Community 🟣: Patrick Altmeyer</title>
      <link>https://forem.julialang.org/patalt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.julialang.org/feed/patalt"/>
    <language>en</language>
    <item>
      <title>Conformal Prediction Intervals for any Regression Model</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Wed, 14 Dec 2022 00:00:14 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/prediction-intervals-for-any-regression-model-16f5</link>
      <guid>https://forem.julialang.org/patalt/prediction-intervals-for-any-regression-model-16f5</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;&lt;img src="https://forem.julialang.org/images/KiSZjVEps85mQfq4eFzJvJu0qs1hv2eOWvW3A6nFG40/w:880/mb:500000/ar:1/aHR0cHM6Ly9mb3Jl/bS5qdWxpYWxhbmcu/b3JnL3JlbW90ZWlt/YWdlcy91cGxvYWRz/L2FydGljbGVzL3cx/c3RwOHR1bDRneDg5/aTkyazd2LnBuZw" alt="" width="880" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Conformal Prediction in Julia — Part 3
&lt;/h4&gt;

&lt;p&gt;This is the third (and for now final) part of a series of posts that introduce Conformal Prediction in Julia using &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;. The first &lt;a href="https://forem.julialang.org/patalt/conformal-prediction-in-julia-h9n"&gt;post&lt;/a&gt; introduced Conformal Prediction for supervised classification tasks: we learned that conformal classifiers produce set-valued predictions that are guaranteed to include the true label of a new sample with a certain probability. In the second &lt;a href="https://medium.com/towards-data-science/how-to-conformalize-a-deep-image-classifier-14ead4e1a5a0"&gt;post&lt;/a&gt; we applied these ideas to a more hands-on example: we saw how easy it is to use &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt; to conformalize a Deep Learning image classifier.&lt;/p&gt;

&lt;p&gt;In this post, we will look at regression problems instead, that is supervised learning tasks involving a continuous outcome variable. Regression tasks are as ubiquitous as classification tasks. For example, we might be interested in using a machine learning model to predict house prices or the inflation rate of the Euro or the parameter size of the next large language model. In fact, many readers may be more familiar with regression models than classification, in which case it may also be easier for you to understand Conformal Prediction (CP) in this context.&lt;/p&gt;

&lt;h3&gt;
  
  
  📖 Background
&lt;/h3&gt;

&lt;p&gt;Before we start, let’s briefly recap what CP is all about. Don’t worry, we’re not about to deep-dive into methodology. But just to give you a high-level description upfront:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Conformal prediction (a.k.a. conformal inference) is a user-friendly paradigm for creating statistically rigorous uncertainty sets/intervals for the predictions of such models. Critically, the sets are valid in a distribution-free sense: they possess explicit, non-asymptotic guarantees even without distributional assumptions or model assumptions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angelopoulos and Bates (2021) (&lt;a href="https://arxiv.org/pdf/2107.07511.pdf"&gt;arXiv&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Intuitively, CP works under the premise of turning heuristic notions of uncertainty into rigorous uncertainty estimates through repeated sampling or the use of dedicated calibration data.&lt;/p&gt;

&lt;p&gt;In what follows we will explore what CP can do by going through a standard machine learning workflow using &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ.jl&lt;/a&gt; and &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;. There will be less focus on how exactly CP works, but references will point you to additional resources.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡💡💡 &lt;strong&gt;Interactive Version&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is also available as a fully interactive &lt;a href="https://github.com/fonsp/Pluto.jl"&gt;Pluto.jl&lt;/a&gt; 🎈 notebook: click &lt;a href="https://binder.plutojl.org/v0.19.12/open?url=https%253A%252F%252Fraw.githubusercontent.com%252Fpat-alt%252FConformalPrediction.jl%252Fmain%252Fdocs%252Fpluto%252Fintro.jl"&gt;here&lt;/a&gt;. In my own experience, this may take some time to load, certainly long enough to get yourself a hot beverage ☕ and first read on here. But I promise you that the wait is worth it!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  📈 Data
&lt;/h3&gt;

&lt;p&gt;Most machine learning workflows start with data. For illustrative purposes we will work with synthetic data. The helper function below can be used to generate some regression data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; get_data&lt;/span&gt;&lt;span class="x"&gt;(;&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xmax&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;# Inputs:&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distributions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uniform&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xmax&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xmax&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MLJBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;# Outputs:&lt;/span&gt;
    &lt;span class="n"&gt;ε&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@.&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ε&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Figure 1 illustrates our observations (dots) along with the ground-truth mapping from inputs to outputs (line). We have defined that mapping f: 𝒳 ↦ 𝒴 as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://forem.julialang.org/images/gIBPh1LVphEyN5u5mvC19aaDUBf8pYiM37WVNsF-myk/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjAwLzEqWS1STUQ2/V205NXdQb1lCckxH/WERVUS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/gIBPh1LVphEyN5u5mvC19aaDUBf8pYiM37WVNsF-myk/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjAwLzEqWS1STUQ2/V205NXdQb1lCckxH/WERVUS5wbmc" alt="" width="600" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Some synthetic regression data. Observations are shown as dots. The ground-truth mapping from inputs to outputs is shown as a dashed line. Image by author.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🏋️ Model Training using &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.paltmeyer.com/blog/posts/conformal-regression/(https://github.com/pat-alt/ConformalPrediction.jl)"&gt;ConformalPrediction.jl&lt;/a&gt; is interfaced to &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ.jl&lt;/a&gt; (Blaom et al. 2020): a comprehensive Machine Learning Framework for Julia. MLJ.jl provides a large and growing suite of popular machine learning models that can be used for supervised and unsupervised tasks. Conformal Prediction is a model-agnostic approach to uncertainty quantification, so it can be applied to any common supervised machine learning model.&lt;/p&gt;

&lt;p&gt;The interface to MLJ.jl therefore seems natural: any (supervised) MLJ.jl model can now be conformalized using ConformalPrediction.jl. By leveraging existing MLJ.jl functionality for common tasks like training, prediction and model evaluation, this package is light-weight and scalable. Now let's see how all of that works ...&lt;/p&gt;

&lt;p&gt;To start with, let’s split our data into a training and test set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eachindex&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s define a model for our regression task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@load&lt;/span&gt; &lt;span class="n"&gt;KNNRegressor&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NearestNeighborModels&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="x"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡💡💡 &lt;strong&gt;Have it your way!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think this dataset is too simple? Wondering why on earth I’m not using XGBoost for this task? In the interactive &lt;a href="https://binder.plutojl.org/v0.19.12/open?url=https%253A%252F%252Fraw.githubusercontent.com%252Fpat-alt%252FConformalPrediction.jl%252Fmain%252Fdocs%252Fpluto%252Fintro.jl"&gt;version&lt;/a&gt; of this post you have full control over the data and the model. Try it out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using standard MLJ.jl workflows let us now first train the unconformalized model. We first wrap our model in data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;mach_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we fit the machine to the training data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;MLJBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach_raw&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbosity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Figure 2 below shows the resulting point predictions for the test data set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/yZ8XZzamShbQhWNv6z8HeSo-eeSFtcuY-KFKI9ktVA0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NTk5LzEqZHNOV0J1/WlVleU9Lc3VTUndv/ZE5vUS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/yZ8XZzamShbQhWNv6z8HeSo-eeSFtcuY-KFKI9ktVA0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NTk5LzEqZHNOV0J1/WlVleU9Lc3VTUndv/ZE5vUS5wbmc" alt="" width="599" height="399"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Point predictions for our machine learning model. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How is our model doing? It’s never quite right, of course, since predictions are estimates and therefore uncertain. Let’s see how we can use Conformal Prediction to express that uncertainty.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔥 Conformalizing the Model
&lt;/h3&gt;

&lt;p&gt;We can turn our model into a conformalized model in just one line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default &lt;code&gt;conformal_model&lt;/code&gt; creates an Inductive Conformal Regressor (more on this below) when called on a &lt;code&gt;&amp;lt;:Deterministic&lt;/code&gt; model. This behaviour can be changed by using the optional method key argument.&lt;/p&gt;

&lt;p&gt;To train our conformal model we can once again rely on standard MLJ.jl workflows. We first wrap our model in data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we fit the machine to the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;MLJBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbosity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let us look at the predictions for our test data again. The chart below shows the results for our conformalized model. Predictions from conformal regressors are range-valued: for each new sample the model returns an interval (yₗ, yᵤ) ∈ 𝒴 that covers the test sample with a user-specified probability (1-α), where α is the expected error rate. This is known as the &lt;strong&gt;marginal coverage guarantee&lt;/strong&gt; and it is proven to hold under the assumption that training and test data are exchangeable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/sN90d498BX42TIzo3YlIMxXcc7BWi5Cs7KMGuYCYpwI/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NTk5LzEqaXIxdUlH/MXM5aFFDLU9CTHpM/eG5YZy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/sN90d498BX42TIzo3YlIMxXcc7BWi5Cs7KMGuYCYpwI/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NTk5LzEqaXIxdUlH/MXM5aFFDLU9CTHpM/eG5YZy5wbmc" alt="" width="599" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Prediction intervals for our conformalized machine learning model. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Intuitively, a higher coverage rate leads to larger prediction intervals: since a larger interval covers a larger subspace of 𝒴, it is more likely to cover the true value.&lt;/p&gt;

&lt;p&gt;I don’t expect you to believe me that the marginal coverage property really holds. In fact, I couldn’t believe it myself when I first learned about it. If you like mathematical proofs, you can find one in this &lt;a href="https://arxiv.org/pdf/2107.07511.pdf"&gt;tutorial&lt;/a&gt;, for example. If you like convincing yourself through empirical observations, read on below …&lt;/p&gt;
&lt;h3&gt;
  
  
  🧐 Evaluation
&lt;/h3&gt;

&lt;p&gt;To verify the marginal coverage property empirically we can look at the empirical coverage rate of our conformal predictor (see Section 3 of the &lt;a href="https://arxiv.org/pdf/2107.07511.pdf"&gt;tutorial&lt;/a&gt; for details). To this end our package provides a custom performance measure &lt;code&gt;emp_coverage&lt;/code&gt; that is compatible with MLJ.jl model evaluation workflows. In particular, we will call &lt;code&gt;evaluate!&lt;/code&gt; on our conformal model using &lt;code&gt;emp_coverage&lt;/code&gt; as our performance metric. The resulting empirical coverage rate should then be close to the desired level of coverage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;model_evaluation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;evaluate!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MLJBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;emp_coverage&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbosity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empirical coverage: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round(model_evaluation.measurement[1], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Coverage per fold: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round.(model_evaluation.per_fold[1], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;Empirical&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.902&lt;/span&gt; 
&lt;span class="n"&gt;Coverage&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.94&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.904&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.874&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.874&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.898&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.922&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;✅ ✅ ✅ Great! We got an empirical coverage rate that is slightly higher than desired 😁 … but why isn’t it exactly the same?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In most cases it will be slightly higher than desired, since (1-α) is a lower bound. But note that it can also be slightly lower than desired. That is because the coverage property is “marginal” in the sense that the probability is averaged over the randomness in the data. For most purposes a large enough calibration set size (n&amp;gt;1000) mitigates that randomness enough. Depending on your choices above, the calibration set may be quite small (set to 500), which can lead to &lt;strong&gt;coverage slack&lt;/strong&gt; (see Section 3 in the &lt;a href="https://arxiv.org/pdf/2107.07511.pdf"&gt;tutorial&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  So what’s happening under the hood?
&lt;/h3&gt;

&lt;p&gt;Inductive Conformal Prediction (also referred to as Split Conformal Prediction) broadly speaking works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Partition the training into a proper training set and a separate calibration set&lt;/li&gt;
&lt;li&gt;Train the machine learning model on the proper training set.&lt;/li&gt;
&lt;li&gt;Using some heuristic notion of uncertainty (e.g., absolute error in the regression case), compute nonconformity scores using the calibration data and the fitted model.&lt;/li&gt;
&lt;li&gt;For the given coverage ratio compute the corresponding quantile &lt;em&gt;q&lt;/em&gt; of the empirical distribution of nonconformity scores.&lt;/li&gt;
&lt;li&gt;For the given quantile and test sample &lt;em&gt;X&lt;/em&gt;, form the corresponding conformal prediction set like so: &lt;em&gt;C&lt;/em&gt;(&lt;em&gt;X&lt;/em&gt;) &lt;em&gt;=&lt;/em&gt; {&lt;em&gt;y:&lt;/em&gt; s(&lt;em&gt;X,y&lt;/em&gt;) &lt;em&gt;≤ q&lt;/em&gt;}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  🔃 Recap
&lt;/h3&gt;

&lt;p&gt;This has been a super quick tour of &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;. We have seen how the package naturally integrates with &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ.jl&lt;/a&gt;, allowing users to generate rigorous predictive uncertainty estimates for any supervised machine learning model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Are we done?&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Quite cool, right? Using a single API call we are able to generate rigorous prediction intervals for all kinds of different regression models. Have we just solved predictive uncertainty quantification once and for all? Do we even need to bother with anything else? Conformal Prediction is a very useful tool, but like so many other things, it is not the final answer to all our problems. In fact, let’s see if we can take CP to its limits.&lt;/p&gt;

&lt;p&gt;The helper function to generate data from above takes an optional argument xmax. By increasing that value, we effectively expand the domain of our input. Let's do that and see how our conformal model does on this new out-of-domain data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/Pq214uYIgNEo2DAVyU3m-dvICH2WS9T9wwV-c0sUwes/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjAwLzEqd3JHajZr/bHdIUkVoR3pTSjZp/MDZZUS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/Pq214uYIgNEo2DAVyU3m-dvICH2WS9T9wwV-c0sUwes/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjAwLzEqd3JHajZr/bHdIUkVoR3pTSjZp/MDZZUS5wbmc" alt="" width="600" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: Prediction intervals for our conformalized machine learning model applied to out-of-domain data. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whooooops 🤕 … looks like we’re in trouble: in Figure 4 the prediction intervals do not cover out-of-domain test samples well. What happened here?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By expanding the domain of out inputs, we have violated the exchangeability assumption. When that assumption is violated, the marginal coverage property does not hold. But do not despair! There are ways to deal with this.&lt;/p&gt;

&lt;h3&gt;
  
  
  📚 Read on
&lt;/h3&gt;

&lt;p&gt;If you are curious to find out more, be sure to read on in the &lt;a href="https://www.paltmeyer.com/ConformalPrediction.jl/stable/"&gt;docs&lt;/a&gt;. There are also a number of useful resources to learn more about Conformal Prediction, a few of which I have listed below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification&lt;/em&gt; by Angelopoulos and Bates (&lt;a href="https://arxiv.org/pdf/2107.07511.pdf"&gt;2022&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Awesome Conformal Prediction&lt;/em&gt; repository by Manokhin (&lt;a href="https://github.com/valeman/awesome-conformal-prediction"&gt;2022&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAPIE&lt;/strong&gt; : a comprehensive Python &lt;a href="https://mapie.readthedocs.io/en/latest/index.html"&gt;library&lt;/a&gt; for conformal prediction.&lt;/li&gt;
&lt;li&gt;My previous two blog posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Angelopoulos, Anastasios N., and Stephen Bates. 2021. “A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification.” &lt;a href="https://arxiv.org/abs/2107.07511"&gt;https://arxiv.org/abs/2107.07511&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Blaom, Anthony D., Franz Kiraly, Thibaut Lienart, Yiannis Simillides, Diego Arenas, and Sebastian J. Vollmer. 2020. “MLJ: A Julia Package for Composable Machine Learning.” &lt;em&gt;Journal of Open Source Software&lt;/em&gt; 5 (55): 2704. &lt;a href="https://doi.org/10.21105/joss.02704"&gt;https://doi.org/10.21105/joss.02704&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/conformal-regression/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 12, 2022.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>machinelearning</category>
      <category>conformalprediction</category>
      <category>julia</category>
      <category>regression</category>
    </item>
    <item>
      <title>How to Conformalize a Deep Image Classifier</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Mon, 05 Dec 2022 00:00:27 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/how-to-conformalize-a-deep-image-classifier-50p2</link>
      <guid>https://forem.julialang.org/patalt/how-to-conformalize-a-deep-image-classifier-50p2</guid>
      <description>&lt;h4&gt;
  
  
  Conformal Prediction in Julia — Part 2
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/hrEgl4ybyi1ReR3Ub85CzByBRGWChXQPj8RlKzTy3Lk/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/OTAwLzEqcGtGMkxk/dmIwTUdhbkpMMzhy/UEdZZy5naWY" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/hrEgl4ybyi1ReR3Ub85CzByBRGWChXQPj8RlKzTy3Lk/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/OTAwLzEqcGtGMkxk/dmIwTUdhbkpMMzhy/UEdZZy5naWY" alt="" width="880" height="293"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Conformal Predictions sets with varying degrees of uncertainty. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deep Learning is popular and — for some tasks like image classification — remarkably powerful. But it is also well-known that Deep Neural Networks (DNN) can be unstable (Goodfellow, Shlens, and Szegedy 2014) and poorly calibrated. Conformal Prediction can be used to mitigate these pitfalls.&lt;/p&gt;

&lt;p&gt;In the first &lt;a href="https://forem.julialang.org/patalt/conformal-prediction-in-julia-h9n"&gt;part&lt;/a&gt; of this series of posts on Conformal Prediction, we looked at the basic underlying methodology and how CP can be implemented in Julia using &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;. This second part of the series is a more goal-oriented how-to guide: it demonstrates how you can conformalize a deep learning image classifier built in Flux.jl in just a few lines of code.&lt;/p&gt;
&lt;h3&gt;
  
  
  🎯 The Task at Hand
&lt;/h3&gt;

&lt;p&gt;The task at hand is to predict the labels of handwritten images of digits using the famous MNIST dataset (LeCun 1998). Importing this popular machine learning dataset in Julia is made remarkably easy through MLDatasets.jl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;MLDatasets&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="n"&gt;Xraw&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yraw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MNIST&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Xraw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Xraw&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;yraw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yraw&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚧 Building the Network
&lt;/h3&gt;

&lt;p&gt;To model the mapping from image inputs to labels will rely on a simple Multi-Layer Perceptron (MLP). A great Julia library for Deep Learning is Flux.jl. But wait ... doesn't ConformalPrediction.jl work with models trained in MLJ.jl? That's right, but fortunately there exists a Flux.jl interface to MLJ.jl, namely MLJFlux.jl. The interface is still in its early stages, but already very powerful and easily accessible for anyone (like myself) who is used to building Neural Networks in Flux.jl.&lt;/p&gt;

&lt;p&gt;In Flux.jl, you could build an MLP for this task as follows,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;

&lt;span class="n"&gt;mlp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prod&lt;/span&gt;&lt;span class="x"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="x"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where (28,28) is just the input dimension (28x28 pixel images). Since we have ten digits, our output dimension is 10. For a full tutorial on how to build an MNIST image classifier relying solely on Flux.jl, check out this &lt;a href="https://fluxml.ai/Flux.jl/stable/tutorials/2021-01-26-mlp/"&gt;tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can do the exact same thing in MLJFlux.jl as follows,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;MLJFlux&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MLJFlux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;@builder&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prod&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_in&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_out&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where here we rely on the @builder macro to make the transition from Flux.jl to MLJ.jl as seamless as possible. Finally, MLJFlux.jl already comes with a number of helper functions to define plain-vanilla networks. In this case, we will use the ImageClassifier with our custom builder and cross-entropy loss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;ImageClassifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@load&lt;/span&gt; &lt;span class="n"&gt;ImageClassifier&lt;/span&gt;
&lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageClassifier&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crossentropy&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated instance clf is a model (in the MLJ.jl sense) so from this point on we can rely on standard MLJ.jl workflows. For example, we can wrap our model in data to create a machine and then evaluate it on a holdout set as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;evaluate!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resampling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Holdout&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fraction_train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;predict_mode&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;accuracy&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The accuracy of our very simple model is not amazing, but good enough for the purpose of this tutorial. For each image, our MLP returns a softmax output for each possible digit: 0,1,2,3,…,9. Since each individual softmax output is valued between zero and one, yₖ ∈ (0,1), this is commonly interpreted as a probability: yₖ ≔ p(y=k|X). Edge cases — that is values close to either zero or one — indicate high predictive certainty. But this is only a heuristic notion of predictive uncertainty (Angelopoulos and Bates 2021). Next, we will turn this heuristic notion of uncertainty into a rigorous one using Conformal Prediction.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔥 Conformalizing the Network
&lt;/h3&gt;

&lt;p&gt;Since clf is a model, it is also compatible with our package: ConformalPrediction.jl. To conformalize our MLP, we therefore only need to call conformal_model(clf). Since the generated instance conf_model is also just a model, we can still rely on standard MLJ.jl workflows. Below we first wrap it in data and then fit it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;ConformalPrediction&lt;/span&gt;
&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;simple_inductive&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;=.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aaaand … we’re done! Let’s look at the results in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Results
&lt;/h3&gt;

&lt;p&gt;Figure 2 below presents the results. Figure 2 (a) displays highly certain predictions, now defined in the rigorous sense of Conformal Prediction: in each case, the conformal set (just beneath the image) includes only one label.&lt;/p&gt;

&lt;p&gt;Figure 2 (b) and Figure 2 (c) display increasingly uncertain predictions of set size two and three, respectively. They demonstrate that CP is well equipped to deal with samples characterized by high aleatoric uncertainty: digits four (4), seven (7) and nine (9) share certain similarities. So do digits five (5) and six (6) as well as three (3) and eight (8). These may be hard to distinguish from each other even after seeing many examples (and even for a human). It is therefore unsurprising to see that these digits often end up together in conformal sets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/xp9tJ6gf5Dod7Rmelyoer1jjyWrnWVlAer_YxX8iOiw/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk4LzEqNGE0NGtC/Z3YzVVdKX1BlUXVY/dGo5Zy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/xp9tJ6gf5Dod7Rmelyoer1jjyWrnWVlAer_YxX8iOiw/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk4LzEqNGE0NGtC/Z3YzVVdKX1BlUXVY/dGo5Zy5wbmc" alt="" width="798" height="267"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2 (a): Randomly selected prediction sets of size |C|=1. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/OPtanH0gwLwDmBrdSKVe53j84nciXtvmuj0tagd6eYs/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk2LzEqbGE2ODhu/ZjI2TVptbjRlcHFC/YkRaZy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/OPtanH0gwLwDmBrdSKVe53j84nciXtvmuj0tagd6eYs/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk2LzEqbGE2ODhu/ZjI2TVptbjRlcHFC/YkRaZy5wbmc" alt="" width="796" height="265"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2 (b): Randomly selected prediction sets of size |C|=2. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/29AU7sepS3GfR-uAAWxSZK80YE-ZVvAF4oKWhpR6GVc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk3LzEqQkQ4ZXpn/ejRVODJlWm96MWE2/VlJPUS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/29AU7sepS3GfR-uAAWxSZK80YE-ZVvAF4oKWhpR6GVc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/Nzk3LzEqQkQ4ZXpn/ejRVODJlWm96MWE2/VlJPUS5wbmc" alt="" width="797" height="266"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2 (c): Randomly selected prediction sets of size |C|=3. Image by author.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🧐 Evaluation
&lt;/h3&gt;

&lt;p&gt;To evaluate the performance of conformal models, specific performance measures can be used to assess if the model is correctly specified and well-calibrated (Angelopoulos and Bates 2021). We will look at this in some more detail in another post in the future. For now, just be aware that these measures are already available in ConformalPrediction.jl and we will briefly showcase them here.&lt;/p&gt;

&lt;p&gt;As for many other things, ConformalPrediction.jl taps into the existing functionality of MLJ.jl for model evaluation. In particular, we will see below how we can use the generic evaluate! method on our machine. To assess the correctness of our conformal predictor, we can compute the empirical coverage rate using the custom performance measure emp_coverage. With respect to model calibration we will look at the model's conditional coverage. For adaptive, well-calibrated conformal models, conditional coverage is high. One general go-to measure for assessing conditional coverage is size-stratified coverage. The custom measure for this purpose is just called size_stratified_coverage, aliased by ssc.&lt;/p&gt;

&lt;p&gt;The code below implements the model evaluation using cross-validation. The Simple Inductive Classifier that we used above is not adaptive and hence the attained conditional coverage is low compared to the overall empirical coverage, which is close to 0.95, so in line with the desired coverage rate specified above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;_eval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evaluate!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resampling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CV&lt;/span&gt;&lt;span class="x"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp_coverage&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empirical coverage: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round(_eval.measurement[1], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSC: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round(_eval.measurement[2], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Empirical&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.957&lt;/span&gt;
&lt;span class="n"&gt;SSC&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.556&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can attain higher adaptivity (SSC) when using adaptive prediction sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;adaptive_inductive&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;=.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_eval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evaluate!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resampling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CV&lt;/span&gt;&lt;span class="x"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp_coverage&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empirical coverage: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round(_eval.measurement[1], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSC: &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;(round(_eval.measurement[2], digits=3))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Empirical&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;
&lt;span class="n"&gt;SSC&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.942&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also have a look at the resulting set size for both approaches using a custom Plots.jl recipe (Figure 3). In line with the above, the spread is wider for the adaptive approach, which reflects that “the procedure is effectively distinguishing between easy and hard inputs” (A. N. Angelopoulos and Bates 2021).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;plt_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="x"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_mod&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
    &lt;span class="n"&gt;push!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plt_list&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fitresult&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_mod&lt;/span&gt;&lt;span class="x"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plt_list&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt;&lt;span class="n"&gt;bg_colour&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;transparent&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://forem.julialang.org/images/Ycw-9jRhjJKW5mbxSWsh6c0MlKiMLUDDilo4Bxu-uSg/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjkzLzEqYlV3N0hL/YXFXUkFyYkdoazNO/ZU1fZy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/Ycw-9jRhjJKW5mbxSWsh6c0MlKiMLUDDilo4Bxu-uSg/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjkzLzEqYlV3N0hL/YXFXUkFyYkdoazNO/ZU1fZy5wbmc" alt="" width="693" height="262"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Distribution of set sizes for both approaches. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔁 Recap
&lt;/h3&gt;

&lt;p&gt;In this short guide we have seen how easy it is to conformalize a deep learning image classifier in Julia using ConformalPrediction.jl. Almost any deep neural network trained in Flux.jl is compatible with MLJ.jl and can therefore be conformalized in just a few lines of code. This makes it remarkably easy to move uncertainty heuristics to rigorous predictive uncertainty estimates. We have also seen a sneak peek at performance evaluation of conformal predictors. Stay tuned for more!&lt;/p&gt;

&lt;h3&gt;
  
  
  🎓 References
&lt;/h3&gt;

&lt;p&gt;Angelopoulos, Anastasios N., and Stephen Bates. 2021. “A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification.” &lt;a href="https://arxiv.org/abs/2107.07511"&gt;https://arxiv.org/abs/2107.07511&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Goodfellow, Ian J, Jonathon Shlens, and Christian Szegedy. 2014. “Explaining and Harnessing Adversarial Examples.” &lt;a href="https://arxiv.org/abs/1412.6572"&gt;https://arxiv.org/abs/1412.6572&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;LeCun, Yann. 1998. “The MNIST Database of Handwritten Digits.”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/conformal-image-classifier/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 5, 2022.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>machinelearning</category>
      <category>julia</category>
      <category>conformalprediction</category>
      <category>artificialintelligen</category>
    </item>
    <item>
      <title>A year of using Quarto with Julia</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Mon, 21 Nov 2022 00:00:09 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/a-year-of-using-quarto-with-julia-3jbi</link>
      <guid>https://forem.julialang.org/patalt/a-year-of-using-quarto-with-julia-3jbi</guid>
      <description>&lt;p&gt;&lt;a href="https://forem.julialang.org/images/R_diVg4vgJS0t1-p9mAb_5wSLxSDXKhRSvw9a9BVgGA/w:880/mb:500000/ar:1/aHR0cHM6Ly9mb3Jl/bS5qdWxpYWxhbmcu/b3JnL3JlbW90ZWlt/YWdlcy91cGxvYWRz/L2FydGljbGVzL2Rz/anFqOTVqajhqMTAy/cW5reTFmLmdpZg" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/R_diVg4vgJS0t1-p9mAb_5wSLxSDXKhRSvw9a9BVgGA/w:880/mb:500000/ar:1/aHR0cHM6Ly9mb3Jl/bS5qdWxpYWxhbmcu/b3JnL3JlbW90ZWlt/YWdlcy91cGxvYWRz/L2FydGljbGVzL2Rz/anFqOTVqajhqMTAy/cW5reTFmLmdpZg" alt="" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Earlier this year in July, I gave a short Experience Talk at &lt;a href="https://pretalx.com/juliacon-2022/speaker/8DGYCX/"&gt;JuliaCon&lt;/a&gt;. In a related blog &lt;a href="https://www.paltmeyer.com/blog/posts/julia-and-quarto-a-match-made-in-heaven/index.html"&gt;post&lt;/a&gt; I explained how the introduction of Quarto made my transition from R to Julia painless: I would be able to start learning Julia without having to give up on all the benefits associated with R Markdown.&lt;/p&gt;

&lt;p&gt;In November, 2022, I am presenting on this topic again at the &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:6995656928957718528?updateEntityUrn=urn%3Ali%3Afs_feedUpdate%3A%28V2%2Curn%3Ali%3Aactivity%3A6995656928957718528%29"&gt;2nd JuliaLang Eindhoven meetup&lt;/a&gt;. In addition to the &lt;a href="https://www.paltmeyer.com/content/talks/posts/2022-julia-eindhoven/index.html"&gt;slides&lt;/a&gt;, I thought I’d share a small companion blog post that highlights some useful tips and tricks for anyone interested in using Quarto with Julia.&lt;/p&gt;

&lt;h3&gt;
  
  
  General things
&lt;/h3&gt;

&lt;p&gt;We will start in this section with a few general recommendations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;I continue to recommend using VSCode for any work with Quarto and Julia. The Quarto &lt;a href="https://quarto.org/docs/computations/julia.html#vs-code"&gt;docs&lt;/a&gt; explain how to get started by installing the necessary Quarto and IJulia extensions. Since most Julia users will regularly want to update their Julia version, I would additionally recommend to add IJulia.jl to your ~/.julia/config/startup.jl file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Setup OhMyREPL, Revise and Term&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pkg&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;
    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Revise"&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"OhMyREPL"&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Term"&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"IJulia"&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_package&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;nothing&lt;/span&gt;
            &lt;span class="n"&gt;Pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, you only need to remember that …&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;… if you install a new Julia binary […], you must update the IJulia installation […] by running&lt;/em&gt; &lt;em&gt;Pkg.build("IJulia")&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source:&lt;/em&gt; &lt;a href="https://julialang.github.io/IJulia.jl/stable/manual/installation/#Updating-Julia-and-IJulia"&gt;&lt;em&gt;IJulia docs&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I guess this step can also be automated in ~/.julia/config/startup.jl, but haven't tried that yet.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using .ipynb vs .qmd
&lt;/h4&gt;

&lt;p&gt;I also continue to recommend working with Quarto notebooks as opposed to Jupyter notebooks (files ending in .qmd and .ipynb, respectively). This is partially just based on preference (from R Markdown I'm used to working with .Rmd files), but there is also a good reason to consider using .qmd, even if you're used to working with Jupyter: the code chunks in your Quarto notebook automatically link to the Julia REPL in VSCode. In other words, you can run code chunks in your notebook and then access any variable that you may have created in the REPL. I find this quite useful, cause it allows me to quickly test code. Perhaps there's a good way to do this with Jupyter notebooks as well, but when I last used them I would always have to insert new code cells to test stuff.&lt;/p&gt;

&lt;p&gt;Either way switching between Jupyter and Quarto notebooks is straight-forward: quarto convert notebook.qmd will convert any Quarto notebook into a Jupyter notebook and vice versa. One potential benefit of Jupyter notebooks is their connection to Google Colab: it is possible to store Jupyter notebooks on Github and make them available on Colab, allowing users to quickly interact with your code without the need to clone anything. If this is important to you, you can still work with .qmd documents and simply specify keep-ipynb: true in the YAML header.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dynamic Content
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The world and the data that describes it is not static 📈. Why should scientific outputs be?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the things I have always really loved about R Markdown was the ability to use inline code: the Knitr engine allows you to call and render any object x that you have created in preceding R chunks like this: r x. This is very powerful, because it enables us to bridge the gap between computations and output. In other words, it allows us to easily produce reproducible and dynamic content.&lt;/p&gt;

&lt;p&gt;Until recently I had not been aware that this is also possible for Julia. Consider the following example. The code below depends on remote data that is continuously updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;MarketData&lt;/span&gt;
&lt;span class="n"&gt;snp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yahoo&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"^GSPC"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Dates&lt;/span&gt;
&lt;span class="n"&gt;last_trade_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snp&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="x"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;p_close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snp&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="x"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;last_trade_day_formatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_trade_day&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"U d, yyyy"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It loads the most recent publicly available data on equity prices from Yahoo finance. In an ideal world, we’d like any updates to these inputs to be reflected in our output. That way you can just re-render the Quarto notebook to get an updated report. To render Julia code inline, we use Markdown.jl like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Markdown&lt;/span&gt;
&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""
When the S&amp;amp;P 500 last traded, on &lt;/span&gt;&lt;span class="si"&gt;$(last_trade_day_formatted)&lt;/span&gt;&lt;span class="s"&gt;, it closed at &lt;/span&gt;&lt;span class="si"&gt;$(p_close)&lt;/span&gt;&lt;span class="s"&gt;. 
"""&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the S&amp;amp;P 500 last traded, on November 18, 2022, it closed at 3965.340088.&lt;/p&gt;

&lt;p&gt;In practice, one would of course set #| echo: false in this case. Whatever content you publish, this approach will keep it up-to-date. This practice of simply re-rendering the source notebook also ensures that any other output remains up-to-date (e.g. &lt;a href="https://www.paltmeyer.com/blog/posts/tips-and-tricks-for-using-quarto-with-julia/#fig-snp"&gt;Figure 1&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/WNreIiom4DOf7t9gnAyR2730EfmF_Hmz4bFEOS4iLp0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjczLzEqLTBPTHRf/Q3VyRUdKaFhmWkI0/bVdPQS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/WNreIiom4DOf7t9gnAyR2730EfmF_Hmz4bFEOS4iLp0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NjczLzEqLTBPTHRf/Q3VyRUdKaFhmWkI0/bVdPQS5wbmc" alt="Figure 1: Price history of the S&amp;amp;P 500. Image by author." width="673" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Code Execution
&lt;/h4&gt;

&lt;p&gt;Related to the previous point, I typically define the following execution options in my _quarto.yml or _metadata.yml. The freeze: auto option ensures that documents are only rerendered if the source changes. In cases where code should always be re-executed you whould want to set freeze: false, instead. I set output: false because typically I have a lot of code chunks that don't generate any output that is of immediate interest to readers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reproducibility
&lt;/h4&gt;

&lt;p&gt;To ensure that your content can be reproduced easily, it may additionally be helpful to explicitly specify the Julia version you used ( jupyter: julia-1.8) and set up a global or local Julia environments. Inserting the following at the beginning of your Quarto notebook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Pkg&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activate&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;path&amp;gt;"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ensures that the desired environment that lives in  is actually activated and used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package Documentation
&lt;/h3&gt;

&lt;p&gt;I have also continued to use Quarto in combination with Documenter.jl to document my Julia packages. This essentially boils down to writing up documentation using interactive .qmd notebooks and then rendering those to .md files as inputs for Documenter.jl. There are a few good reasons for this approach, especially if you're used to working with Quarto anyway:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Re-rendering any docs with eval: true provides an additional layer of quality assurance: if any of the code chunks throws an error, you know that your documentation is outdated (perhaps due to an API change). It also offers a straight-forward way to test package functions that produce non-testable (e.g. stochastic) output. In such cases, the use of jldoctest is not always straight-forward (see &lt;a href="https://github.com/JuliaDocs/Documenter.jl/issues/452"&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;You get some stuff for free, e.g. citation management. Unfortunately, as far as I’m aware there is still no support for cross-referencing.&lt;/li&gt;
&lt;li&gt;You can use Quarto execution options like execute-dir: project and resources: www/ to globally specify the working directory and a directory for external resources like images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are also a few peculiarities to be aware of. To avoid any issues with Documenter.jl, I've found it useful to ensure that the rendered .md files do not contain any raw HTML and to preserve text wrapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;commonmark&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-raw_html&lt;/span&gt;
    &lt;span class="na"&gt;wrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;preserve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When working with .qmd files you also need to use a slightly different syntax for &lt;a href="https://documenter.juliadocs.org/stable/showcase/#Admonitions"&gt;admonitions&lt;/a&gt;. The following syntax inside the .qmd&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| !!! note \"An optional title\"
|     Here is something that you should pay attention to.   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will generate the desired output inside the rendered .md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!!! note "An optional title"
    Here is something that you should pay attention to.   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any of my package repos — &lt;a href="https://github.com/pat-alt/CounterfactualExplanations.jl"&gt;CounterfactualExplanations.jl&lt;/a&gt;, &lt;a href="https://github.com/pat-alt/LaplaceRedux.jl"&gt;LaplaceRedux.jl&lt;/a&gt;, &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt; — should provide additional colour on this topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quarto for Academic Journal Articles
&lt;/h3&gt;

&lt;p&gt;Quarto supports LaTeX templates/classes, which has helped me with paper submissions in the past (e.g. my pending JuliaCon Proceedings submissions). I’ve found that &lt;a href="https://pkgs.rstudio.com/rticles/articles/rticles.html"&gt;rticles&lt;/a&gt; still has an edge here, but the &lt;a href="https://quarto.org/docs/extensions/listing-journals.html"&gt;list&lt;/a&gt; of out-of-the-box templates for journal articles is growing. Should I find some time in the future, I will try to add a template for JuliaCon Proceedings. The beauty of this is that it should enable publishers to not only use traditional forms of publication (PDF), but also include more dynamic formats with ease (think &lt;a href="https://distill.pub/"&gt;distill&lt;/a&gt;, but more than that.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;This short post has provided a bit of an update on using Quarto with Julia. From my own experience so far, things have been getting easier and better (thanks to the amazing work of Quarto dev team). I’m exicted to see things improve even further and still think that Quarto is a revolutionary new tool for scientific publishing. Let’s hope publishers eventually recognise this value 👀.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/tips-and-tricks-for-using-quarto-with-julia/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on November 21, 2022.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quarto</category>
      <category>julia</category>
    </item>
    <item>
      <title>Conformal Prediction in Julia</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Tue, 25 Oct 2022 00:00:39 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/conformal-prediction-in-julia-h9n</link>
      <guid>https://forem.julialang.org/patalt/conformal-prediction-in-julia-h9n</guid>
      <description>&lt;h3&gt;
  
  
  Conformal Prediction in Julia
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Part 1 — Introduction
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/z-hxDgKjet59HJoatrtgho84TygTCFLYAVv4661G2Dc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8xKnNLMWRm/N1I5Rlp4MkhIUzVi/SVlEaGcuZ2lm" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/z-hxDgKjet59HJoatrtgho84TygTCFLYAVv4661G2Dc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8xKnNLMWRm/N1I5Rlp4MkhIUzVi/SVlEaGcuZ2lm" alt="" width="880" height="292"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Prediction sets for two different samples and changing coverage rates. As coverage grows, so does the size of the prediction sets. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A first crucial step towards building trustworthy AI systems is to be transparent about &lt;strong&gt;predictive uncertainty&lt;/strong&gt;. Model parameters are random variables and their values are estimated from noisy data. That inherent stochasticity feeds through to model predictions and should to be addressed, at the very least in order to avoid overconfidence in models.&lt;/p&gt;

&lt;p&gt;Beyond that obvious concern, it turns out that quantifying model uncertainty actually opens up a myriad of possibilities to improve up- and down-stream modelling tasks like active learning and robustness. In Bayesian Active Learning, for example, uncertainty estimates are used to guide the search for new input samples, which can make ground-truthing tasks more efficient (Houlsby et al. 2011). With respect to model performance in downstream tasks, uncertainty quantification can be used to improve model calibration and robustness (Lakshminarayanan, Pritzel, and Blundell 2016).&lt;/p&gt;

&lt;p&gt;In previous posts we have looked at how uncertainty can be quantified in the &lt;strong&gt;Bayesian&lt;/strong&gt; context (see &lt;a href="https://forem.julialang.org/patalt/bayesian-logistic-regression-2i31-temp-slug-4724412"&gt;here&lt;/a&gt; and &lt;a href="https://forem.julialang.org/patalt/go-deep-but-also-go-bayesian-2pma-temp-slug-9208263"&gt;here&lt;/a&gt;). Since in Bayesian modelling we are generally concerned with estimating posterior distributions, we get uncertainty estimates almost as a byproduct. This is great for all intends and purposes, but it hinges on assumptions about prior distributions. Personally, I have no quarrel with the idea of making prior distributional assumptions. On the contrary, I think the Bayesian framework formalises the idea of integrating prior information in models and therefore provides a powerful toolkit for conducting science. Still, in some cases this requirement may be seen as too restrictive or we may simply lack prior information.&lt;/p&gt;

&lt;p&gt;Enter: &lt;strong&gt;Conformal Prediction&lt;/strong&gt; (CP) — a scalable &lt;strong&gt;frequentist approach&lt;/strong&gt; to uncertainty quantification and coverage control. In this post we will go through the basic concepts underlying CP. A number of hands-on usage examples in Julia should hopefully help to convey some intuition and ideally attract people interested in contributing to a new and exciting open-source development.&lt;/p&gt;
&lt;h3&gt;
  
  
  📖 Background
&lt;/h3&gt;

&lt;p&gt;Conformal Prediction promises to be an easy-to-understand, distribution-free and model-agnostic way to generate statistically rigorous uncertainty estimates. That’s quite a mouthful, so let’s break it down: firstly, as I will hopefully manage to illustrate in this post, the underlying concepts truly are fairly straight-forward to understand; secondly, CP indeed relies on only minimal distributional assumptions; thirdly, common procedures to generate conformal predictions really do apply almost universally to all supervised models, therefore making the framework very intriguing to the ML community; and, finally, CP does in fact come with a frequentist coverage guarantee that ensures that conformal prediction sets contain the true value with a user-chosen probability. For a formal proof of this &lt;em&gt;marginal coverage&lt;/em&gt; property and a detailed introduction to the topic, I recommend the tutorial by Angelopoulos and Bates (2021).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In what follows we will loosely treat the tutorial by Angelopoulos and Bates (2021) and the general framework it sets as a reference. You are not expected to have read the paper, but I also won’t reiterate any details here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CP can be used to generate prediction intervals for regression models and prediction sets for classification models (more on this later). There is also some recent work on conformal predictive distributions and probabilistic predictions. Interestingly, it can even be used to complement Bayesian methods. Angelopoulos and Bates (2021), for example, point out that prior information should be incorporated into prediction sets and demonstrate how Bayesian predictive distributions can be conformalised in order to comply with the frequentist notion of coverage. Relatedly, Hoff (2021) proposes a Bayes-optimal prediction procedure. And finally, Stanton, Maddox, and Wilson (2022) very recently proposed a way to introduce conformal prediction in Bayesian Optimisation. I find this type of work that combines different schools of thought very promising, but I’m drifting off a little … So, without further ado, let us look at some code.&lt;/p&gt;
&lt;h3&gt;
  
  
  📦 Conformal Prediction in Julia
&lt;/h3&gt;

&lt;p&gt;In this section of this first short post on CP we will look at how conformal prediction can be implemented in Julia. In particular, we will look at an approach that is compatible with any of the many supervised machine learning models available in &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ&lt;/a&gt;: a beautiful, comprehensive machine learning framework funded by the &lt;a href="https://www.turing.ac.uk/"&gt;Alan Turing Institute&lt;/a&gt; and the &lt;a href="https://www.mbie.govt.nz/science-and-technology/science-and-innovation/funding-information-and-opportunities/investment-funds/strategic-science-investment-fund/ssif-funded-programmes/university-of-auckland/"&gt;New Zealand Strategic Science Investment Fund&lt;/a&gt;. We will go through some basic usage examples employing a new Julia package that I have been working on: &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ConformalPrediction.jl is a package for uncertainty quantification through conformal prediction for machine learning models trained in &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/dev/"&gt;MLJ&lt;/a&gt;. At the time of writing it is still in its early stages of development, but already implements a range of different approaches to CP. Contributions are very much welcome:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.paltmeyer.com/ConformalPrediction.jl/stable/"&gt;Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.paltmeyer.com/ConformalPrediction.jl/stable/#Contribute"&gt;Contributor’s Guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Split Conformal Classification
&lt;/h3&gt;

&lt;p&gt;We consider a simple binary classification problem. Let (Xᵢ, Yᵢ), i=1,…,n denote our feature-label pairs and let μ: 𝒳 ↦ 𝒴 denote the mapping from features to labels. For illustration purposes we will use the moons dataset 🌙. Using &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/v0.18/"&gt;MLJ.jl&lt;/a&gt; we first generate the data and split into into a training and test set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;MLJ&lt;/span&gt; 
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;  

&lt;span class="c"&gt;# Data:&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_moons&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eachindex&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we will use a specific case of CP called &lt;em&gt;split conformal prediction&lt;/em&gt; which can then be summarised as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Partition the training into a proper training set and a separate calibration set: 𝒟ₙ=𝒟[train] ∪ 𝒟[cali].&lt;/li&gt;
&lt;li&gt;Train the machine learning model on the proper training set: μ(Xᵢ, Yᵢ) for i ∈ 𝒟[train].&lt;/li&gt;
&lt;li&gt;Compute nonconformity scores, 𝒮, using the calibration data 𝒟[cali] and the fitted model μ(Xᵢ, Yᵢ) for i ∈ 𝒟[train].&lt;/li&gt;
&lt;li&gt;For a user-specified desired coverage ratio (1-α) compute the corresponding quantile, q̂, of the empirical distribution of nonconformity scores, 𝒮.&lt;/li&gt;
&lt;li&gt;For the given quantile and test sample X[test], form the corresponding conformal prediction set: C(X[test])={y: s(X[test], y) ≤ q̂}&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the default procedure used for classification and regression in &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You may want to take a look at the source code for the classification case &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl/blob/67712e870dc3a438bf0846d376fa48480612f042/src/ConformalModels/inductive_classification.jl#L1"&gt;here&lt;/a&gt;. As a &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl/blob/67712e870dc3a438bf0846d376fa48480612f042/src/ConformalModels/inductive_classification.jl#L3"&gt;first&lt;/a&gt; important step, we begin by defining a concrete type SimpleInductiveClassifier that wraps a supervised model from &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/v0.18/"&gt;MLJ.jl&lt;/a&gt; and reserves additional fields for a few hyperparameters. As a &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl/blob/67712e870dc3a438bf0846d376fa48480612f042/src/ConformalModels/inductive_classification.jl#L26"&gt;second&lt;/a&gt; step, we define the training procedure, which includes the data-splitting and calibration step. Finally, as a &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl/blob/67712e870dc3a438bf0846d376fa48480612f042/src/ConformalModels/inductive_classification.jl#L56"&gt;third&lt;/a&gt; step we implement the procedure in the equation above (step 5) to compute the conformal prediction set.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The permalinks above take you to the version of the package that was up-to-date at the time of writing. Since the package is in its early stages of development, the code base and API can be expected to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let’s take this to our 🌙 data. To illustrate the package functionality we will demonstrate the envisioned workflow. We first define our atomic machine learning model following standard &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/v0.18/"&gt;MLJ.jl&lt;/a&gt; conventions. Using &lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt; we then wrap our atomic model in a conformal model using the standard API call conformal_model(model::Supervised; kwargs...). To train and predict from our conformal model we can then rely on the conventional &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/v0.18/"&gt;MLJ.jl&lt;/a&gt; procedure again. In particular, we wrap our conformal model in data (turning it into a machine) and then fit it on the training set. Finally, we use our machine to predict the label for a new test sample Xtest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Model:&lt;/span&gt;
&lt;span class="n"&gt;KNNClassifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@load&lt;/span&gt; &lt;span class="n"&gt;KNNClassifier&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;NearestNeighborModels&lt;/span&gt; 
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KNNClassifier&lt;/span&gt;&lt;span class="x"&gt;(;&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;   

&lt;span class="c"&gt;# Training:&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;ConformalPrediction&lt;/span&gt; 
&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;=.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;  

&lt;span class="c"&gt;# Conformal Prediction:&lt;/span&gt;
&lt;span class="n"&gt;Xtest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selectrows&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt; 
&lt;span class="n"&gt;ytest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="x"&gt;)]&lt;/span&gt; 
&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Xtest&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UnivariateFinite&lt;/span&gt;&lt;span class="x"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Multiclass&lt;/span&gt;&lt;span class="x"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;}}&lt;/span&gt;     
     &lt;span class="n"&gt;┌&lt;/span&gt; &lt;span class="n"&gt;┐&lt;/span&gt; 
   &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■&lt;/span&gt; &lt;span class="mf"&gt;0.94&lt;/span&gt;   
     &lt;span class="n"&gt;└&lt;/span&gt; &lt;span class="n"&gt;┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final predictions are set-valued. While the softmax output remains unchanged for the SimpleInductiveClassifier, the size of the prediction set depends on the chosen coverage rate, (1-α).&lt;/p&gt;

&lt;p&gt;When specifying a coverage rate very close to one, the prediction set will typically include many (in some cases all) of the possible labels. Below, for example, both classes are included in the prediction set when setting the coverage rate equal to (1-α)=1.0. This is intuitive, since high coverage quite literally requires that the true label is covered by the prediction set with high probability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;  

&lt;span class="c"&gt;# Conformal Prediction:&lt;/span&gt;
&lt;span class="n"&gt;Xtest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;],&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; 
&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Xtest&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UnivariateFinite&lt;/span&gt;&lt;span class="x"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Multiclass&lt;/span&gt;&lt;span class="x"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;}}&lt;/span&gt;    
     &lt;span class="n"&gt;┌&lt;/span&gt; &lt;span class="n"&gt;┐&lt;/span&gt; 
   &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;   
   &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;   
     &lt;span class="n"&gt;└&lt;/span&gt; &lt;span class="n"&gt;┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conversely, for low coverage rates, prediction sets can also be empty. For a choice of (1-α)=0.1, for example, the prediction set for our test sample is empty. This is a bit difficult to think about intuitively and I have not yet come across a satisfactory, intuitive interpretation (should you have one, please share!). When the prediction set is empty, the predict call currently returns missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conformal_model&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf_model&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;  

&lt;span class="c"&gt;# Conformal Prediction: &lt;/span&gt;
&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Xtest&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;missing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Figure 2 should provide some more intuition as to what exactly is happening here. It illustrates the effect of the chosen coverage rate on the predicted softmax output and the set size in the two-dimensional feature space. Contours are overlayed with the moon data points (including test data). The two samples highlighted in red, X₁ and X₂, have been manually added for illustration purposes. Let’s look at these one by one.&lt;/p&gt;

&lt;p&gt;Firstly, note that X₁ (red cross) falls into a region of the domain that is characterized by high predictive uncertainty. It sits right at the bottom-right corner of our class-zero moon 🌜 (orange), a region that is almost entirely enveloped by our class-one moon 🌛 (green). For low coverage rates the prediction set for X₁ is empty: on the left-hand side this is indicated by the missing contour for the softmax probability; on the right-hand side we can observe that the corresponding set size is indeed zero. For high coverage rates the prediction set includes both y=0 and y=1, indicative of the fact that the conformal classifier is uncertain about the true label.&lt;/p&gt;

&lt;p&gt;With respect to X₂, we observe that while also sitting on the fringe of our class-zero moon, this sample populates a region that is not fully enveloped by data points from the opposite class. In this region, the underlying atomic classifier can be expected to be more certain about its predictions, but still not highly confident. How is this reflected by our corresponding conformal prediction sets?&lt;/p&gt;

&lt;p&gt;Well, for low coverage rates (roughly &amp;lt;0.9) the conformal prediction set does not include y=0: the set size is zero (right panel). Only for higher coverage rates do we have C(X₂)={0}: the coverage rate is high enough to include y=0, but the corresponding softmax probability is still fairly low. For example, for (1-α)=0.9 we have p̂(y=0|X₂)=0.72.&lt;/p&gt;

&lt;p&gt;These two examples illustrate an interesting point: for regions characterised by high predictive uncertainty, conformal prediction sets are typically empty (for low coverage) or large (for high coverage). While set-valued predictions may be something to get used to, this notion is overall intuitive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/YThwJswcaYy6VpSNOtw5wRAzLr2s7prcqB7c2vTtGBQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NzY4LzEqWG1ocWlI/YnJpQUFkR0Z1TXJ2/WUtmdy5naWY" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/YThwJswcaYy6VpSNOtw5wRAzLr2s7prcqB7c2vTtGBQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/NzY4LzEqWG1ocWlI/YnJpQUFkR0Z1TXJ2/WUtmdy5naWY" alt="" width="768" height="288"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: The effect of the coverage rate on the conformal prediction set. Softmax probabilities are shown on the left. The size of the prediction set is shown on the right. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🏁 Conclusion
&lt;/h3&gt;

&lt;p&gt;This has really been a whistle-stop tour of Conformal Prediction: an active area of research that probably deserves much more attention. Hopefully, though, this post has helped to provide some color and, if anything, made you more curious about the topic. Let’s recap the most important points from above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conformal Prediction is an interesting frequentist approach to uncertainty quantification that can even be combined with Bayes.&lt;/li&gt;
&lt;li&gt;It is scalable and model-agnostic and therefore well applicable to machine learning.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pat-alt/ConformalPrediction.jl"&gt;ConformalPrediction.jl&lt;/a&gt; implements CP in pure Julia and can be used with any supervised model available from &lt;a href="https://alan-turing-institute.github.io/MLJ.jl/v0.18/"&gt;MLJ.jl&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Implementing CP directly on top of an existing, powerful machine learning toolkit demonstrates the potential usefulness of this framework to the ML community.&lt;/li&gt;
&lt;li&gt;Standard conformal classifiers produce set-valued predictions: for ambiguous samples these sets are typically large (for high coverage) or empty (for low coverage).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below I will leave you with some further resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  📚 Further Resources
&lt;/h3&gt;

&lt;p&gt;Chances are that you have already come across the Awesome Conformal Prediction &lt;a href="https://github.com/valeman/awesome-conformal-prediction"&gt;repo&lt;/a&gt;: Manokhin (n.d.) provides a comprehensive, up-to-date overview of resources related to the conformal prediction. Among the listed articles you will also find Angelopoulos and Bates (2021), which inspired much of this post. The repo also points to open-source implementations in other popular programming languages including Python and R.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Angelopoulos, Anastasios N., and Stephen Bates. 2021. “A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification.” &lt;a href="https://arxiv.org/abs/2107.07511"&gt;https://arxiv.org/abs/2107.07511&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hoff, Peter. 2021. “Bayes-Optimal Prediction with Frequentist Coverage Control.” &lt;a href="https://arxiv.org/abs/2105.14045"&gt;https://arxiv.org/abs/2105.14045&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Houlsby, Neil, Ferenc Huszár, Zoubin Ghahramani, and Máté Lengyel. 2011. “Bayesian Active Learning for Classification and Preference Learning.” &lt;a href="https://arxiv.org/abs/1112.5745"&gt;https://arxiv.org/abs/1112.5745&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lakshminarayanan, Balaji, Alexander Pritzel, and Charles Blundell. 2016. “Simple and Scalable Predictive Uncertainty Estimation Using Deep Ensembles.” &lt;a href="https://arxiv.org/abs/1612.01474"&gt;https://arxiv.org/abs/1612.01474&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Manokhin, Valery. n.d. “Awesome Conformal Prediction.”&lt;/p&gt;

&lt;p&gt;Stanton, Samuel, Wesley Maddox, and Andrew Gordon Wilson. 2022. “Bayesian Optimization with Conformal Coverage Guarantees.” &lt;a href="https://arxiv.org/abs/2210.12496"&gt;https://arxiv.org/abs/2210.12496&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For attribution, please cite this work as:&lt;/p&gt;

&lt;p&gt;Patrick Altmeyer, and Patrick Altmeyer. 2022. “Conformal Prediction in Julia 🟣🔴🟢.” October 25, 2022.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/conformal-prediction/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on October 25, 2022.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>conformalprediction</category>
      <category>julia</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>A new tool for explainable AI</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Wed, 20 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/a-new-tool-for-explainable-ai-2289</link>
      <guid>https://forem.julialang.org/patalt/a-new-tool-for-explainable-ai-2289</guid>
      <description>&lt;p&gt;&lt;a href="https://forem.julialang.org/images/FM5mg-ghtnUNWV1jG134uxnkUwVxplHvR4KR4f9I_-g/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MzAwLzAqLXozQnpQ/eEFYcU1hWGtwcS5n/aWY" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/FM5mg-ghtnUNWV1jG134uxnkUwVxplHvR4KR4f9I_-g/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MzAwLzAqLXozQnpQ/eEFYcU1hWGtwcS5n/aWY" alt="Turning a 9 (nine) into a 4 (four). Image by author." width="300" height="300"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Turning a 9 (nine) into a 4 (four). Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Counterfactual explanations, which I introduced in one of my previous &lt;a href="https://forem.julialang.org/patalt/individual-recourse-for-black-box-models-1cak-temp-slug-6649736"&gt;posts&lt;/a&gt;, offer a simple and intuitive way to explain black-box models without opening them. Still, as of today there exists only one open-source library that provides a unifying approach to generate and benchmark counterfactual explanations for models built and trained in Python (Pawelczyk et al. 2021). This is great, but of limited use to users of other programming languages 🥲.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://www.paltmeyer.com/CounterfactualExplanations.jl/stable/"&gt;CounterfactualExplanations.jl&lt;/a&gt;: a Julia package that can be used to explain machine learning algorithms developed and trained in Julia, Python and R. Counterfactual explanations fall into the broader category of explainable artificial intelligence (XAI).&lt;/p&gt;

&lt;p&gt;Explainable AI typically involves models that are not inherently interpretable but require additional tools to be explainable to humans. Examples of the latter include ensembles, support vector machines and deep neural networks. This is not to be confused with interpretable AI, which involves models that are inherently interpretable and transparent such as general additive models (GAM), decision trees and rule-based models.&lt;/p&gt;

&lt;p&gt;Some would argue that we best avoid explaining black-box models altogether (Rudin 2019) and instead focus solely on interpretable AI. While I agree that initial efforts should always be geared towards interpretable models, stopping there would entail missed opportunities and anyway is probably not very realistic in times of &lt;a href="https://openai.com/blog/dall-e/"&gt;DALL-E&lt;/a&gt; and Co.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Even though […] interpretability is of great importance and should be pursued, explanations can, in principle, be offered without opening the “black box.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wachter, Mittelstadt, and Russell (&lt;a href="https://www.paltmeyer.com/blog/posts/a-new-tool-for-explainable-ai/#ref-wachter2017counterfactual"&gt;2017&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post introduces the main functionality of the new Julia package. Following a motivating example using a model trained in Julia, we will see how easy the package can be adapted to work with models trained in Python and R. Since the motivation for this post is also to hopefully attract contributors, the final section outlines some of the exciting developments we have planned.&lt;/p&gt;
&lt;h3&gt;
  
  
  Counterfactuals for image data 🖼
&lt;/h3&gt;

&lt;p&gt;To introduce counterfactual explanations I used a simple binary classification problem in my previous &lt;a href="https://forem.julialang.org/patalt/individual-recourse-for-black-box-models-1cak-temp-slug-6649736"&gt;post&lt;/a&gt;. It involved a linear classifier and a linearly separable, synthetic data set with just two features. This time we are going to step it up a notch: we will generate counterfactual explanations MNIST data. The MNIST dataset contains 60,000 training samples of handwritten digits in the form of 28x28 pixel grey-scale images (LeCun 1998). Each image is associated with a label indicating the digit (0–9) that the image represents.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.paltmeyer.com/CounterfactualExplanations.jl/stable/"&gt;CounterfactualExplanations.jl&lt;/a&gt; package ships with two black-box models that were trained to predict labels for this data: firstly, a simple multi-layer perceptron (MLP) and, secondly, a corresponding deep ensemble. Originally proposed by Lakshminarayanan, Pritzel, and Blundell (2016), deep ensembles are really just ensembles of deep neural networks. They are still among the most popular approaches to Bayesian deep learning. For more information on Bayesian deep learning see my previous post: [&lt;a href="https://forem.julialang.org/patalt/go-deep-but-also-go-bayesian-2pma-temp-slug-9208263"&gt;TDS&lt;/a&gt;], [&lt;a href="https://www.paltmeyer.com/blog/posts/effortsless-bayesian-dl/"&gt;blog&lt;/a&gt;].&lt;/p&gt;
&lt;h3&gt;
  
  
  Black-box models
&lt;/h3&gt;

&lt;p&gt;While the package can currently handle a few simple classification models natively, it is designed to be easily extensible through users and contributors. Extending the package to deal with custom models typically involves only two simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Subtyping&lt;/strong&gt; : the custom model needs to be declared as a subtype of the package-internal type AbstractFittedModel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple dispatch&lt;/strong&gt; : the package-internal functions logits and probs need to be extended through custom methods for the new model type.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code that implements these two steps can be found in the corresponding &lt;a href="https://www.paltmeyer.com/blog/posts/a-new-tool-for-explainable-ai/#black-box-models"&gt;post&lt;/a&gt; on my own blog.&lt;/p&gt;
&lt;h3&gt;
  
  
  Counterfactual generators
&lt;/h3&gt;

&lt;p&gt;Next, we need to specify the counterfactual generators we want to use. The package currently ships with two default generators that both need gradient access: firstly, the generic generator introduced by Wachter, Mittelstadt, and Russell (2017) and, secondly, a greedy generator introduced by Schut et al. (2021).&lt;/p&gt;

&lt;p&gt;The greedy generator is designed to be used with models that incorporate uncertainty in their predictions such as the deep ensemble introduced above. It works for probabilistic (Bayesian) models, because they only produce high-confidence predictions in regions of the feature domain that are populated by training samples. As long as the model is expressive enough and well-specified, counterfactuals in these regions will always be realistic and unambiguous since by construction they should look very similar to training samples. Other popular approaches to counterfactual explanations like REVISE (Joshi et al. 2019) and CLUE (Antorán et al. 2020) also play with this simple idea.&lt;/p&gt;

&lt;p&gt;The following two lines of code instantiate the two generators for the problem at hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;generic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GenericGenerator&lt;/span&gt;&lt;span class="x"&gt;(;&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;logitcrossentropy&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;greedy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GreedyGenerator&lt;/span&gt;&lt;span class="x"&gt;(;&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;logitcrossentropy&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanations
&lt;/h3&gt;

&lt;p&gt;Once the model and counterfactual generator are specified, running counterfactual search is very easy using the package. For a given factual (x), target class (target) and data set (counterfactual_data), simply running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="n"&gt;generate_counterfactual&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counterfactual_data&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generic&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will generate the results, in this case using the generic generator (generic) for the MLP (M). Since we have specified two different black-box models and two different counterfactual generators, we have four combinations of a model and a generator in total. For each of these combinations I have used the generate_counterfactual function to produce the results in Figure 1.&lt;/p&gt;

&lt;p&gt;In every case the desired label switch is in fact achieved, but arguably from a human perspective only the counterfactuals for the deep ensemble look like a four. The generic generator produces mild perturbations in regions that seem irrelevant from a human perspective, but nonetheless yields a counterfactual that can pass as a four. The greedy approach clearly targets pixels at the top of the handwritten nine and yields the best result overall. For the non-Bayesian MLP, both the generic and the greedy approach generate counterfactuals that look much like adversarial examples: they perturb pixels in seemingly random regions on the image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/U5xFydtwhvszSfTNSvWEK9ZzDeeDCGrIhPko_bCXlvM/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKnBqcG94/OHpldGxNTXRqSTEu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/U5xFydtwhvszSfTNSvWEK9ZzDeeDCGrIhPko_bCXlvM/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKnBqcG94/OHpldGxNTXRqSTEu/cG5n" alt="Counterfactual explanations for MNIST: turning a nine (9) into a four (4). Image by author." width="880" height="220"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Counterfactual explanations for MNIST: turning a nine (9) into a four (4). Image by author.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Language interoperability 👥
&lt;/h3&gt;

&lt;p&gt;The Julia language offers unique support for programming language interoperability. For example, calling R or Python is made remarkably easy through RCall.jl and PyCall.jl, respectively. This functionality can be leveraged to use CounterfactualExplanations.jl to generate explanations for models that were developed in other programming languages. At this time there is no native support for foreign programming languages, but the following example involving a torch neural network trained in R demonstrates how versatile the package is. The corresponding example involving PyTorch is analogous and therefore omitted, but available &lt;a href="https://www.paltmeyer.com/CounterfactualExplanations.jl/dev/tutorials/interop/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Explaining a model trained in R
&lt;/h3&gt;

&lt;p&gt;We will consider a simple MLP trained for a binary classification task. As before we first need to adapt this custom model for use with our package. The code below the two necessary steps — sub-typing and method extension. Logits are returned by the torch model and copied from the R environment into the Julia scope. Probabilities are then computed inside the Julia scope by passing the logits through the sigmoid function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;CounterfactualExplanations&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CounterfactualExplanations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Models&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CounterfactualExplanations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="c"&gt;# import functions in order to extend&lt;/span&gt;

&lt;span class="c"&gt;# Step 1)&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="nc"&gt; TorchNetwork&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;:&lt;/span&gt; &lt;span class="n"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbstractFittedModel&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Step 2)&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; logits&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TorchNetwork&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;AbstractArray&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;nn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;
  &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rcopy&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="s"&gt;"as_array(&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;nn(torch_tensor(t(&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;X))))"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isa&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AbstractArray&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; probs&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TorchNetwork&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;AbstractArray&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;σ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TorchNetwork&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared to models trained in Julia, we need to do a little more work at this point. Since our counterfactual generators need gradient access, we essentially need to allow our package to communicate with the R torch library. While this may sound daunting, it turns out to be quite manageable: all we have to do is respecify the function that computes the gradient with respect to the counterfactual loss function so that it can deal with the TorchNetwork type we defined above. The code below implements this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CounterfactualExplanations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Generators&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;∂ℓ&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;LinearAlgebra&lt;/span&gt;

&lt;span class="c"&gt;# Countefactual loss:&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; ∂ℓ&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AbstractGradientBasedGenerator&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;counterfactual_state&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CounterfactualState&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
  &lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counterfactual_state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;
  &lt;span class="n"&gt;nn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;
  &lt;span class="n"&gt;x′&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counterfactual_state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x′&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counterfactual_state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_encoded&lt;/span&gt;
  &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="s"&gt;"""
  x &amp;lt;- torch_tensor(&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;x′, requires_grad=TRUE)
  output &amp;lt;- &lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;nn(x)
  loss_fun &amp;lt;- nnf_binary_cross_entropy_with_logits
  obj_loss &amp;lt;- loss_fun(output,&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;t)
  obj_loss&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;backward()
  """&lt;/span&gt;
  &lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rcopy&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="s"&gt;"as_array(x&lt;/span&gt;&lt;span class="si"&gt;$&lt;/span&gt;&lt;span class="s"&gt;grad)"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;grad&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all the adjustment needed to use CounterfactualExplanations.jl for our custom R model. Figure 2 shows a counterfactual path for a randomly chosen sample with respect to the MLP trained in R.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/7FyBjeK6pH8Hre2iGrbS2F54J3XUUk62P6Z64hQiHIc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/ODAwLzAqWlhCWFVE/OUxxUXRFSkE5ZC5n/aWY" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/7FyBjeK6pH8Hre2iGrbS2F54J3XUUk62P6Z64hQiHIc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/ODAwLzAqWlhCWFVE/OUxxUXRFSkE5ZC5n/aWY" alt="Counterfactual path using the generic counterfactual generator for a model trained in R. Image by author." width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Counterfactual path using the generic counterfactual generator for a model trained in R. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  We need you! 🫵
&lt;/h3&gt;

&lt;p&gt;The ambition for CounterfactualExplanations.jl is to provide a go-to place for counterfactual explanations to the Julia community and beyond. This is a grand ambition, especially for a package that has so far been built by a single developer who has little prior experience with Julia. We would therefore very much like to invite community contributions. If you have an interest in trustworthy AI, the open-source community and Julia, please do get involved! This package is still in its early stages of development, so any kind of contribution is welcome: advice on the core package architecture, pull requests, issues, discussions and even just comments below would be much appreciated.&lt;/p&gt;

&lt;p&gt;To give you a flavour of what type of future developments we envision, here is a non-exhaustive list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Native support for additional counterfactual generators and predictive models including those built and trained in Python or R.&lt;/li&gt;
&lt;li&gt;Additional datasets for testing, evaluation and benchmarking.&lt;/li&gt;
&lt;li&gt;Improved preprocessing including native support for categorical features.&lt;/li&gt;
&lt;li&gt;Support for regression models.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, if you like this project but don’t have much time, then simply sharing this article or starring the &lt;a href="https://github.com/pat-alt/CounterfactualExplanations.jl"&gt;repo&lt;/a&gt; on GitHub would also go a long way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading 📚
&lt;/h3&gt;

&lt;p&gt;If you’re interested in learning more about this development, feel free to check out the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package docs: &lt;a href="https://pat-alt.github.io/CounterfactualExplanations.jl/stable"&gt;[stable]&lt;/a&gt;, &lt;a href="https://pat-alt.github.io/CounterfactualExplanations.jl/dev"&gt;[dev]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.paltmeyer.com/CounterfactualExplanations.jl/stable/contributing/"&gt;Contributor’s guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pat-alt/CounterfactualExplanations.jl"&gt;GitHub repo&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Thanks 💐
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://twitter.com/miouantoinette?lang=en"&gt;Lisa Schut&lt;/a&gt; and &lt;a href="https://oatml.cs.ox.ac.uk/members/oscar_key/"&gt;Oscar Key&lt;/a&gt; — corresponding authors of Schut (2021) — have been tremendously helpful in providing feedback on this post and answering a number of questions I had about their paper. Thank you!&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Antorán, Javier, Umang Bhatt, Tameem Adel, Adrian Weller, and José Miguel Hernández-Lobato. 2020. “Getting a Clue: A Method for Explaining Uncertainty Estimates.” &lt;em&gt;arXiv Preprint arXiv:2006.06848&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Joshi, Shalmali, Oluwasanmi Koyejo, Warut Vijitbenjaronk, Been Kim, and Joydeep Ghosh. 2019. “Towards Realistic Individual Recourse and Actionable Explanations in Black-Box Decision Making Systems.” &lt;em&gt;arXiv Preprint arXiv:1907.09615&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Lakshminarayanan, Balaji, Alexander Pritzel, and Charles Blundell. 2016. “Simple and Scalable Predictive Uncertainty Estimation Using Deep Ensembles.” &lt;em&gt;arXiv Preprint arXiv:1612.01474&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;LeCun, Yann. 1998. “The MNIST Database of Handwritten Digits.” &lt;a href="http://Http://Yann."&gt;&lt;em&gt;Http://Yann.&lt;/em&gt;&lt;/a&gt; &lt;em&gt;Lecun. Com/Exdb/Mnist/&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pawelczyk, Martin, Sascha Bielawski, Johannes van den Heuvel, Tobias Richter, and Gjergji Kasneci. 2021. “Carla: A Python Library to Benchmark Algorithmic Recourse and Counterfactual Explanation Algorithms.” &lt;em&gt;arXiv Preprint arXiv:2108.00783&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Rudin, Cynthia. 2019. “Stop Explaining Black Box Machine Learning Models for High Stakes Decisions and Use Interpretable Models Instead.” &lt;em&gt;Nature Machine Intelligence&lt;/em&gt; 1 (5): 206–15.&lt;/p&gt;

&lt;p&gt;Schut, Lisa, Oscar Key, Rory Mc Grath, Luca Costabello, Bogdan Sacaleanu, Yarin Gal, et al. 2021. “Generating Interpretable Counterfactual Explanations by Implicit Minimisation of Epistemic and Aleatoric Uncertainties.” In &lt;em&gt;International Conference on Artificial Intelligence and Statistics&lt;/em&gt;, 1756–64. PMLR.&lt;/p&gt;

&lt;p&gt;Wachter, Sandra, Brent Mittelstadt, and Chris Russell. 2017. “Counterfactual Explanations Without Opening the Black Box: Automated Decisions and the GDPR.” &lt;em&gt;Harv. JL &amp;amp; Tech.&lt;/em&gt; 31: 841.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/a-new-tool-for-explainable-ai/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on April 20, 2022.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>probabilisiticai</category>
      <category>explainableai</category>
      <category>julialang</category>
    </item>
    <item>
      <title>Go deep, but also … go Bayesian!</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Fri, 18 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/go-deep-but-also-go-bayesian-1471</link>
      <guid>https://forem.julialang.org/patalt/go-deep-but-also-go-bayesian-1471</guid>
      <description>&lt;h4&gt;
  
  
  Truly effortless Bayesian Deep Learning in Julia
&lt;/h4&gt;

&lt;p&gt;Deep learning has dominated AI research in recent years — but how much promise does it really hold? That is very much an ongoing and increasingly polarising debate that you can follow live on &lt;a href="https://twitter.com/ilyasut/status/1491554478243258368"&gt;Twitter&lt;/a&gt;. On one side you have optimists like Ilya Sutskever, chief scientist of OpenAI, who believes that large deep neural networks may already be slightly conscious — that’s “may” and “slightly” and only if you just go deep enough? On the other side you have prominent skeptics like Judea Pearl who has long since argued that deep learning still boils down to curve fitting — purely associational and not even remotely intelligent (Pearl and Mackenzie 2018).&lt;/p&gt;

&lt;h3&gt;
  
  
  The case for Bayesian Deep Learning
&lt;/h3&gt;

&lt;p&gt;Whatever side of this entertaining twitter dispute you find yourself on, the reality is that deep-learning systems have already been deployed at large scale both in academia and industry. More pressing debates therefore revolve around the trustworthiness of these existing systems. How robust are they and in what way exactly do they arrive at decisions that affect each and every one of us? Robustifying deep neural networks generally involves some form of adversarial training, which is costly, can hurt generalization (Raghunathan et al. 2019) and does ultimately not guarantee stability (Bastounis, Hansen, and Vlačić 2021). With respect to interpretability, surrogate explainers like LIME and SHAP are among the most popular tools, but they too have been shown to lack robustness (Slack et al. 2020).&lt;/p&gt;

&lt;p&gt;Exactly why are deep neural networks unstable and in-transparent? The first thing to note is that the number of free parameters is typically huge (if you ask Mr Sutskever it really probably cannot be huge enough!). That alone makes it very hard to monitor and interpret the inner workings of deep-learning algorithms. Perhaps more importantly though, the number of parameters &lt;em&gt;relative&lt;/em&gt; to the size of the data is generally huge:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;[…] deep neural networks are typically very underspecified by the available data, and […] parameters [therefore] correspond to a diverse variety of compelling explanations for the data. (Wilson 2020)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, training a single deep neural network may (and usually does) lead to one random parameter specification that fits the underlying data very well. But in all likelihood there are many other specifications that also fit the data very well. This is both a strength and vulnerability of deep learning: it is a strength because it typically allows us to find one such “compelling explanation” for the data with ease through stochastic optimization; it is a vulnerability because one has to wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How compelling is an explanation really if it competes with many other equally compelling, but potentially very different explanations?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A scenario like this very much calls for treating predictions from deep learning models probabilistically (Wilson 2020). Formally, we are interested in estimating the posterior predictive distribution as the following Bayesian model average (BMA):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/JQWRNE8YslewTk-ecSw40ntCrM_znFru2_sN8YS8V7w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKnNjSTNS/UVdTa2p4ZHpjM1cu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/JQWRNE8YslewTk-ecSw40ntCrM_znFru2_sN8YS8V7w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKnNjSTNS/UVdTa2p4ZHpjM1cu/cG5n" alt="" width="880" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The integral implies that we essentially need many predictions from many different specifications of the model. Unfortunately, this means more work for us or rather our computers. Fortunately though, researchers have proposed many ingenious ways to approximate the equation above in recent years: Gal and Ghahramani (2016) propose using dropout at test time while Lakshminarayanan et al. (2016) show that averaging over an ensemble of just five models seems to do the trick. Still, despite their simplicity and usefulness these approaches involve additional computational costs compared to training just a single network. As we shall see now though, another promising approach has recently entered the limelight: &lt;strong&gt;Laplace approximation&lt;/strong&gt; (LA).&lt;/p&gt;

&lt;p&gt;If you have read my &lt;a href="https://forem.julialang.org/patalt/bayesian-logistic-regression-2i31-temp-slug-4724412"&gt;previous post&lt;/a&gt; on Bayesian Logistic Regression, then the term Laplace should already sound familiar to you. As a matter of fact, we will see that all concepts covered in that previous post can be naturally extended to deep learning. While some of these concepts will be revisited below, I strongly recommend you check out the previous post before reading on here. Without further ado let us now see how LA can be used for truly effortless deep learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laplace Approximation
&lt;/h3&gt;

&lt;p&gt;While LA was first proposed in the 18th century, it has so far not attracted serious attention from the deep learning community largely because it involves a possibly large Hessian computation. Daxberger et al. (2021) are on a mission to change the perception that LA has no use in DL: in their &lt;a href="https://arxiv.org/pdf/2106.14806.pdf"&gt;NeurIPS 2021 paper&lt;/a&gt; they demonstrate empirically that LA can be used to produce Bayesian model averages that are at least at par with existing approaches in terms of uncertainty quantification and out-of-distribution detection and significantly cheaper to compute. They show that recent advancements in autodifferentation can be leveraged to produce fast and accurate approximations of the Hessian and even provide a fully-fledged &lt;a href="https://aleximmer.github.io/Laplace/"&gt;Python library&lt;/a&gt; that can be used with any pretrained Torch model. For this post, I have built a much less comprehensive, pure-play equivalent of their package in Julia — &lt;a href="https://www.paltmeyer.com/BayesLaplace.jl/dev/"&gt;BayesLaplace.jl&lt;/a&gt; can be used with deep learning models built in &lt;a href="https://fluxml.ai/"&gt;Flux.jl&lt;/a&gt;, which is Julia’s main DL library. As in the previous post on Bayesian logistic regression I will rely on Julia code snippits instead of equations to convey the underlying maths. If you’re curious about the maths, the &lt;a href="https://arxiv.org/pdf/2106.14806.pdf"&gt;NeurIPS 2021 paper&lt;/a&gt; provides all the detail you need. You will also find a slightly more detailed version of this article on my &lt;a href="https://www.paltmeyer.com/blog/posts/effortsless-bayesian-dl/"&gt;blog&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Bayesian Logistic Regression …
&lt;/h3&gt;

&lt;p&gt;Let’s recap: in the case of logistic regression we had assumed a zero-mean Gaussian prior for the weights that are used to compute logits, which in turn are fed to a sigmoid function to produce probabilities. We saw that under this assumption solving the logistic regression problem corresponds to minimizing the following differentiable loss function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/o2DzMg4779BQCzpUqoTGQ9MfEL8CD5oDXc2JYouBVVc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKjhtNUpy/UTZWcGdJYk85encu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/o2DzMg4779BQCzpUqoTGQ9MfEL8CD5oDXc2JYouBVVc/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKjhtNUpy/UTZWcGdJYk85encu/cG5n" alt="" width="880" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As our first step towards Bayesian deep learning, we observe the following: the loss function above corresponds to the objective faced by a single-layer artificial neural network with sigmoid activation and weight decay. In other words, regularized logistic regression is equivalent to a very simple neural network architecture and hence it is not surprising that underlying concepts can in theory be applied in much the same way.&lt;/p&gt;

&lt;p&gt;So let’s quickly recap the next core concept: LA relies on the fact that the second-order Taylor expansion of our loss function evaluated at the &lt;strong&gt;maximum a posteriori&lt;/strong&gt; (MAP) estimate amounts to a multi-variate Gaussian distribution. In particular, that Gaussian is centered around the MAP estimate with covariance equal to the inverse Hessian evaluated at the mode (Murphy 2022).&lt;/p&gt;

&lt;p&gt;That is basically all there is to the story: if we have a good estimate of the Hessian we have an analytical expression for an (approximate) posterior over parameters. So let’s go ahead and implement this approach in Julia using &lt;a href="https://www.paltmeyer.com/BayesLaplace.jl/dev/"&gt;BayesLaplace.jl&lt;/a&gt;. The code below generates some toy data, builds and trains a single-layer neural network and finally fits a post-hoc Laplace approximation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Import libraries.&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Plots&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PlotThemes&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Statistics&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BayesLaplace&lt;/span&gt;
&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;wong&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Toy data:&lt;/span&gt;
&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toy_data_linear&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hcat&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;);&lt;/span&gt; &lt;span class="c"&gt;# bring into tabular format&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Neural network:&lt;/span&gt;
&lt;span class="n"&gt;nn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;λ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="n"&gt;sqnorm&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs2&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;weight_regularization&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqnorm&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Losses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logitbinarycrossentropy&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;weight_regularization&lt;/span&gt;&lt;span class="x"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;# Training:&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Optimise&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ADAM&lt;/span&gt;
&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ADAM&lt;/span&gt;&lt;span class="x"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;epochs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="n"&gt;gs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Laplace approximation:&lt;/span&gt;
&lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;laplace&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;p_plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot_contour&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Plugin"&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="x"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;p_laplace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot_contour&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Laplace"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Plot the posterior distribution with a contour plot.&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_plugin&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_laplace&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting plot below visualizes the posterior predictive distribution in the 2D feature space. For comparison I have added the corresponding plugin estimate as well. Note how for the Laplace approximation the predicted probabilities fan out indicating that confidence decreases in regions scarce of data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/OJrcCsOXOdOGPmbkY0T9HAcNx-7yDO-QX-J8D8a3f0U/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKlhpXzUt/UmJOaU5CV2NJM1cu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/OJrcCsOXOdOGPmbkY0T9HAcNx-7yDO-QX-J8D8a3f0U/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKlhpXzUt/UmJOaU5CV2NJM1cu/cG5n" alt="" width="880" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Posterior predictive distribution of Logistic regression in the 2D feature space using plugin estimator (left) and Laplace approximation (right). Image by author.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  … to Bayesian Neural Networks
&lt;/h3&gt;

&lt;p&gt;Now let’s step it up a notch: we will repeat the exercise from above, but this time for data that is not linearly separable using a simple MLP instead of the single-layer neural network we used above. The code below is almost the same as above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Import libraries.&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Plots&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PlotThemes&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Statistics&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BayesLaplace&lt;/span&gt;
&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;wong&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Toy data:&lt;/span&gt;
&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toy_data_linear&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hcat&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;);&lt;/span&gt; &lt;span class="c"&gt;# bring into tabular format&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Build MLP:&lt;/span&gt;
&lt;span class="n"&gt;n_hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;nn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;σ&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Dense&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="x"&gt;)&lt;/span&gt;  
&lt;span class="n"&gt;λ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;
&lt;span class="n"&gt;sqnorm&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs2&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;weight_regularization&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqnorm&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Losses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logitbinarycrossentropy&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;weight_regularization&lt;/span&gt;&lt;span class="x"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;# Training:&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Optimise&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ADAM&lt;/span&gt;
&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ADAM&lt;/span&gt;&lt;span class="x"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;epochs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="n"&gt;gs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Laplace approximation:&lt;/span&gt;
&lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;laplace&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;λ&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fit!&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;p_plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot_contour&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Plugin"&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=:&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="x"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;p_laplace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot_contour&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Laplace"&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Plot the posterior distribution with a contour plot.&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_plugin&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_laplace&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;),&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="x"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Figure 2 demonstrates that once again the Laplace approximation yields a posterior predictive distribution that is more conservative than the over-confident plugin estimate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/vY_XqjmcQFRj4E2r8vob1xYMz_xs4uuvDdS-xiT_kps/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKmlMQmot/NERlTGsxUGlCTC0u/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/vY_XqjmcQFRj4E2r8vob1xYMz_xs4uuvDdS-xiT_kps/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKmlMQmot/NERlTGsxUGlCTC0u/cG5n" alt="" width="880" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Posterior predictive distribution of MLP in the 2D feature space using plugin estimator (left) and Laplace approximation (right). Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To see why this is a desirable outcome consider the zoomed out version of Figure 2 below: the plugin estimator classifies with full confidence in regions completely scarce of any data. Arguably Laplace approximation produces a much more reasonable picture, even though it too could likely be improved by fine-tuning our prior and the neural network architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/COeE9fjSPBnOeVeA9FwE4cNhE4OqzMok6OCcufQmv3o/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKkk3d3gw/ZjJzT2xUa0JEVC0u/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/COeE9fjSPBnOeVeA9FwE4cNhE4OqzMok6OCcufQmv3o/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAwMC8wKkk3d3gw/ZjJzT2xUa0JEVC0u/cG5n" alt="" width="880" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Posterior predictive distribution of MLP in the 2D feature space using plugin estimator (left) and Laplace approximation (right). Zoomed out. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;Recent state-of-the-art research on neural information processing suggests that Bayesian deep learning can be effortless: Laplace approximation for deep neural networks appears to work very well and it does so at minimal computational cost (Daxberger et al. 2021). This is great news, because the case for turning Bayesian is strong: society increasingly relies on complex automated decision-making systems that need to be trustworthy. More and more of these systems involve deep learning, which in and of itself is not trustworthy. We have seen that typically there exist various viable parameterizations of deep neural networks each with their own distinct and compelling explanation for the data at hand. When faced with many viable options, don’t put all of your eggs in one basket. In other words, go Bayesian!&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;To get started with Bayesian deep learning I have found many useful and free resources online, some of which are listed below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://turing.ml/dev/tutorials/03-bayesian-neural-network/"&gt;Turing.jl tutorial&lt;/a&gt; on Bayesian deep learning in Julia.&lt;/li&gt;
&lt;li&gt;Various RStudio AI blog posts including &lt;a href="https://blogs.rstudio.com/ai/posts/2018-11-12-uncertainty_estimates_dropout/"&gt;this one&lt;/a&gt; and &lt;a href="https://blogs.rstudio.com/ai/posts/2019-06-05-uncertainty-estimates-tfprobability/"&gt;this one&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/tensorflow/regression-with-probabilistic-layers-in-tensorflow-probability-e46ff5d37baf"&gt;TensorFlow blog post&lt;/a&gt; on regression with probabilistic layers.&lt;/li&gt;
&lt;li&gt;Kevin Murphy’s &lt;a href="https://probml.github.io/pml-book/book1.html"&gt;draft text book&lt;/a&gt;, now also available as print.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Bastounis, Alexander, Anders C Hansen, and Verner Vlačić. 2021. “The Mathematics of Adversarial Attacks in AI-Why Deep Learning Is Unstable Despite the Existence of Stable Neural Networks.” &lt;em&gt;arXiv Preprint arXiv:2109.06098&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Daxberger, Erik, Agustinus Kristiadi, Alexander Immer, Runa Eschenhagen, Matthias Bauer, and Philipp Hennig. 2021. “Laplace Redux-Effortless Bayesian Deep Learning.” &lt;em&gt;Advances in Neural Information Processing Systems&lt;/em&gt; 34.&lt;/p&gt;

&lt;p&gt;Gal, Yarin, and Zoubin Ghahramani. 2016. “Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning.” In &lt;em&gt;International Conference on Machine Learning&lt;/em&gt;, 1050–59. PMLR.&lt;/p&gt;

&lt;p&gt;Lakshminarayanan, Balaji, Alexander Pritzel, and Charles Blundell. 2016. “Simple and Scalable Predictive Uncertainty Estimation Using Deep Ensembles.” &lt;em&gt;arXiv Preprint arXiv:1612.01474&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Murphy, Kevin P. 2022. &lt;em&gt;Probabilistic Machine Learning: An Introduction&lt;/em&gt;. MIT Press.&lt;/p&gt;

&lt;p&gt;Pearl, Judea, and Dana Mackenzie. 2018. &lt;em&gt;The Book of Why: The New Science of Cause and Effect&lt;/em&gt;. Basic books.&lt;/p&gt;

&lt;p&gt;Raghunathan, Aditi, Sang Michael Xie, Fanny Yang, John C Duchi, and Percy Liang. 2019. “Adversarial Training Can Hurt Generalization.” &lt;em&gt;arXiv Preprint arXiv:1906.06032&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Slack, Dylan, Sophie Hilgard, Emily Jia, Sameer Singh, and Himabindu Lakkaraju. 2020. “Fooling Lime and Shap: Adversarial Attacks on Post Hoc Explanation Methods.” In &lt;em&gt;Proceedings of the AAAI/ACM Conference on AI, Ethics, and Society&lt;/em&gt;, 180–86.&lt;/p&gt;

&lt;p&gt;Wilson, Andrew Gordon. 2020. “The Case for Bayesian Deep Learning.” &lt;em&gt;arXiv Preprint arXiv:2001.10995&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/blog/posts/effortsless-bayesian-dl/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on February 18, 2022.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>deeplearning</category>
      <category>neuralnetworks</category>
      <category>julia</category>
    </item>
    <item>
      <title>Bayesian Logistic Regression</title>
      <dc:creator>Patrick Altmeyer</dc:creator>
      <pubDate>Mon, 15 Nov 2021 00:00:00 +0000</pubDate>
      <link>https://forem.julialang.org/patalt/bayesian-logistic-regression-3l65</link>
      <guid>https://forem.julialang.org/patalt/bayesian-logistic-regression-3l65</guid>
      <description>&lt;p&gt;If you’ve ever searched for evaluation metrics to assess model accuracy, chances are that you found many different options to choose from. Accuracy is in some sense the holy grail of prediction so it’s not at all surprising that the machine learning community spends a lot time thinking about it. In a world where more and more high-stake decisions are being automated, model accuracy is in fact a very valid concern.&lt;/p&gt;

&lt;p&gt;But does this recipe for model evaluation seem like a sound and complete approach to automated decision-making? Haven’t we forgot anything? Some would argue that we need to pay more attention to &lt;strong&gt;model uncertainty&lt;/strong&gt;. No matter how many times you have cross-validated your model, the loss metric that it is being optimized against as well as its parameters and predictions remain inherently random variables. Focusing merely on prediction accuracy and ignoring uncertainty altogether can install a false level of confidence in automated decision-making systems. Any &lt;strong&gt;trustworthy&lt;/strong&gt; approach to learning from data should therefore at the very least be transparent about its own uncertainty.&lt;/p&gt;

&lt;p&gt;How can we estimate uncertainty around model parameters and predictions? &lt;strong&gt;Frequentist&lt;/strong&gt; methods for uncertainty quantification generally involve either closed-form solutions based on asymptotic assumptions or bootstrapping (see for example &lt;a href="https://web.stanford.edu/class/archive/stats/stats200/stats200.1172/Lecture26.pdf"&gt;here&lt;/a&gt; for the case of logistic regression). In Bayesian statistics and machine learning we are instead concerned with modelling the &lt;strong&gt;posterior distribution&lt;/strong&gt; over model parameters. This approach to uncertainty quantification is known as &lt;strong&gt;Bayesian Inference&lt;/strong&gt; because we treat model parameters in a Bayesian way: we make assumptions about their distribution based on &lt;strong&gt;prior&lt;/strong&gt; knowledge or beliefs and update these beliefs in light of new evidence. The frequentist approach avoids the need for being explicit about prior beliefs, which in the past has sometimes been considered as _un_scientific. Still, frequentist methods come with their own assumptions and pitfalls (see for example Murphy (2012)) for a discussion). Without diving further into this argument, let us now see how &lt;strong&gt;Bayesian Logistic Regression&lt;/strong&gt; can be implemented from the bottom up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ground truth
&lt;/h3&gt;

&lt;p&gt;In this post we will work with a synthetic toy data set composed of binary labels and corresponding feature vectors. Working with synthetic data has the benefit that we have control over the &lt;strong&gt;ground truth&lt;/strong&gt; that generates our data. In particular, we will assume that the binary labels are indeed generated by a logistic regression model. Features are generated from a mixed Gaussian model.&lt;/p&gt;

&lt;p&gt;To add a little bit of life to our example we will assume that the binary labels classify samples into cats and dogs, based on their height and tail length. The figure below shows the synthetic data in the two-dimensional feature domain. Following an introduction to Bayesian Logistic Regression in the next section we will use this data to estimate our model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/R_9A3J0nExQW2cgN0cXOGc6HF0k0RTIR-2TnO4ORt3w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/OTYwLzAqN2ZuVElp/WDVJcHVaVkVteS5w/bmc" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/R_9A3J0nExQW2cgN0cXOGc6HF0k0RTIR-2TnO4ORt3w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/OTYwLzAqN2ZuVElp/WDVJcHVaVkVteS5w/bmc" alt="Ground truth labels. Image by author." width="880" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ground truth labels. Image by author.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The maths
&lt;/h3&gt;

&lt;p&gt;Adding maths to articles on Medium remains a bit of a hassle, so here we will rely entirely on intuition and avoid formulas altogether. One of the perks of using Julia language is that it allows the use of Unicode characters, so the code we will see below looks almost like maths anyway. If you want to see the full mathematical treatment for a complete understanding of all the details, feel free to check out the extended version of this article on my &lt;a href="https://www.paltmeyer.com/post/bayesian-logistic-regression/"&gt;website&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Problem setup
&lt;/h4&gt;

&lt;p&gt;The starting point for Bayesian Logistic Regression is &lt;strong&gt;Bayes’ Theorem,&lt;/strong&gt; which formally states that the posterior distribution of parameters is proportional to the product of two quantities: the likelihood of observing the data given the parameters and the prior density of parameters. Applied to our context this can intuitively be understood as follows: our posterior beliefs around the logistic regression coefficients are formed by both our prior beliefs and the evidence we observe (i.e. the data).&lt;/p&gt;

&lt;p&gt;Under the assumption that individual label-feature pairs are &lt;strong&gt;independently&lt;/strong&gt; and &lt;strong&gt;identically&lt;/strong&gt; distributed, their joint likelihood is simply the product over their individual densities (Bernoulli). The prior beliefs around parameters are at our discretion. In practice they may be derived from previous experiments. Here we will use a zero-mean spherical Gaussian prior for reasons explained further below.&lt;/p&gt;

&lt;p&gt;Unlike with linear regression there are no closed-form analytical solutions to estimating or maximising the posterior likelihood, but fortunately accurate approximations do exist (Murphy 2022). One of the simplest approaches called &lt;strong&gt;Laplace Approximation&lt;/strong&gt; is straight-forward to implement and computationally very efficient. It relies on the observation that under the assumption of a Gaussian prior, the posterior of logistic regression is also approximately Gaussian: in particular, it this Gaussian distribution is centered around the &lt;strong&gt;maximum a posteriori&lt;/strong&gt; (MAP) estimate with a covariance matrix equal to the inverse Hessian evaluated at the mode. Below we will see how the MAP and corresponding Hessian can be estimated.&lt;/p&gt;
&lt;h4&gt;
  
  
  Solving the problem
&lt;/h4&gt;

&lt;p&gt;In practice we do not maximize the posterior directly. Instead we minimize the negative log likelihood, which is equivalent and easier to implement. The Julia code below shows the implementation of this loss function and its derivatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt; ❗️&lt;em&gt;I should mention that (at the time of writing in 2021) this was the first time I program in Julia, so for any Julia pros out there: please bear with me! More than happy to hear your suggestions in the comments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the negative log likelihood is equal to the sum over two terms: firstly, a sum over (log) Bernoulli distributions — corresponding to the likelihood of the data — and, secondly, a (log) Gaussian distribution — corresponding to our prior beliefs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Loss:&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; 𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;H_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;μ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Δw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;w_0&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;∑&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;μ&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;μ&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Δw&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;H_0&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Δw&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Gradient:&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; ∇𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;H_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;μ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Δw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;w_0&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;∑&lt;/span&gt;&lt;span class="x"&gt;((&lt;/span&gt;&lt;span class="n"&gt;μ&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;H_0&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Δw&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Hessian:&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; ∇∇𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;H_0&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;μ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;∑&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;μ&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;μ&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="x"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="x"&gt;]&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;H_0&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since minimizing this loss function is a convex optimization problem we have many efficient algorithms to choose from in order to solve this problem. With the Hessian also at hand it seems natural to use a second-order method, because incorporating information about the curvature of the loss function generally leads to faster convergence. Here we will implement &lt;strong&gt;Newton’s method&lt;/strong&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight julia"&gt;&lt;code&gt;&lt;span class="c"&gt;# Newton's Method&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; arminjo&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ρ&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;θ_t&lt;/span&gt; &lt;span class="o"&gt;.+&lt;/span&gt; &lt;span class="n"&gt;ρ&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="n"&gt;d_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;.+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="n"&gt;ρ&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="n"&gt;d_t&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;g_t&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nf"&gt; newton&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;θ&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;∇&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;∇∇&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;τ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-5&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;# Intialize:&lt;/span&gt;
    &lt;span class="n"&gt;converged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="c"&gt;# termination state&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c"&gt;# iteration count&lt;/span&gt;
    &lt;span class="n"&gt;θ_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;θ&lt;/span&gt; &lt;span class="c"&gt;# initial parameters&lt;/span&gt;
    &lt;span class="c"&gt;# Descent:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;converged&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt; 
        &lt;span class="kd"&gt;global&lt;/span&gt; &lt;span class="n"&gt;g_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;∇&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# gradient&lt;/span&gt;
        &lt;span class="kd"&gt;global&lt;/span&gt; &lt;span class="n"&gt;H_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;∇∇&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# hessian&lt;/span&gt;
        &lt;span class="n"&gt;converged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_t&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;.&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;τ&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isposdef&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H_t&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# check first-order condition&lt;/span&gt;
        &lt;span class="c"&gt;# If not converged, descend:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;converged&lt;/span&gt;
            &lt;span class="n"&gt;d_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H_t&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;g_t&lt;/span&gt; &lt;span class="c"&gt;# descent direction&lt;/span&gt;
            &lt;span class="c"&gt;# Line search:&lt;/span&gt;
            &lt;span class="n"&gt;ρ_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="c"&gt;# initialize at 1.0&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;arminjo&lt;/span&gt;&lt;span class="x"&gt;(&lt;/span&gt;&lt;span class="err"&gt;𝓁&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ρ_t&lt;/span&gt;&lt;span class="x"&gt;)&lt;/span&gt; 
                &lt;span class="n"&gt;ρ_t&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
            &lt;span class="n"&gt;θ_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;θ_t&lt;/span&gt; &lt;span class="o"&gt;.+&lt;/span&gt; &lt;span class="n"&gt;ρ_t&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="n"&gt;d_t&lt;/span&gt; &lt;span class="c"&gt;# update parameters&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c"&gt;# Output:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;θ_t&lt;/span&gt;&lt;span class="x"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H_t&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose now that we have trained the Bayesian Logistic Regression model as our binary classifier using our training data and a new unlabelled sample arrives. As with any binary classifier we can predict the missing label by simply plugging the new sample into our fitted model. If at training phase we have found the model to achieve good accuracy, we may expect good out-of-sample performance. But since we are still dealing with an expected value of a random variable, we would generally like to have an idea of how noisy this prediction is.&lt;/p&gt;

&lt;p&gt;Formally, we are interested in the &lt;strong&gt;posterior predictive&lt;/strong&gt; distribution, which without any further assumption is a mathematically intractable integral. It can be numerically estimated through Monte Carlo — by simply repeatedly sampling parameters from the posterior distribution — or by using what is called a &lt;strong&gt;probit approximation.&lt;/strong&gt; The latter uses the finding that the sigmoid function can be well approximated by a rescaled standard Gaussian cdf (see the figure below). Approximating the sigmoid function in this way allows us to derive an analytical solution for the posterior predictive. This approach was used to generate the results in the following section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/iY4ci0fCeDFjOmaQ2vv1b8_tYjdZ49BzXnNGjbrkWTU/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKjNOSFNo/dnBjdFlvVnQzRmUu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/iY4ci0fCeDFjOmaQ2vv1b8_tYjdZ49BzXnNGjbrkWTU/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKjNOSFNo/dnBjdFlvVnQzRmUu/cG5n" alt="Demonstration of the probit approximation. Image by author." width="880" height="628"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Demonstration of the probit approximation. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The estimates
&lt;/h3&gt;

&lt;p&gt;The first figure below shows the resulting posterior distribution for the coefficients on height and tail length at varying degrees of prior uncertainty. The red dot indicates the unconstrained maximum likelihood estimate (MLE). Note that as the prior uncertainty tends towards zero the posterior approaches the prior. This is intuitive since we have imposed that we have no uncertainty around our prior beliefs and hence no amount of new evidence can move us in any direction. Conversely, for very large levels of prior uncertainty the posterior distribution is centered around the unconstrained MLE: prior knowledge is very uncertain and hence the posterior is dominated by the likelihood of the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/j81Emq4VlxHt_z216dBaWYYkRqYvbaY25PcYrVjRYGQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKlM5ZlNx/SVNCVG1vZzU4TDUu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/j81Emq4VlxHt_z216dBaWYYkRqYvbaY25PcYrVjRYGQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKlM5ZlNx/SVNCVG1vZzU4TDUu/cG5n" alt="Posterior distribution at varying degrees of prior uncertainty σ. Image by author." width="880" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Posterior distribution at varying degrees of prior uncertainty σ. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What about the posterior predictive? The story is similar: since for very low levels of prior uncertainty the posterior is completely dominated by the zero-mean prior all samples are classified as 0.5 (top left panel in the figure below). As we gradually increase uncertainty around our prior the predictive posterior depends more and more on the data: uncertainty around predicted labels is high only in regions that are not populated by samples. Not surprisingly, this effect is strongest for the MLE where we see some evidence of overfitting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forem.julialang.org/images/waWBNtEfFWofhj8r3dOMNAjbbyo1vEhwR597JU3kQHQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKnFydHMx/cWQzNS1WYTZTelMu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://forem.julialang.org/images/waWBNtEfFWofhj8r3dOMNAjbbyo1vEhwR597JU3kQHQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jZG4t/aW1hZ2VzLTEubWVk/aXVtLmNvbS9tYXgv/MTAyNC8wKnFydHMx/cWQzNS1WYTZTelMu/cG5n" alt="Predictive posterior distribution at varying degrees of prior uncertainty σ. Image by author." width="880" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Predictive posterior distribution at varying degrees of prior uncertainty σ. Image by author.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;In this post we have seen how Bayesian Logistic Regression can be implemented from scratch in Julia language. The estimated posterior distribution over model parameters can be used to quantify uncertainty around coefficients and model predictions. I have argued that it is important to be transparent about model uncertainty to avoid being overly confident in estimates.&lt;/p&gt;

&lt;p&gt;There are many more benefits associated with Bayesian (probabilistic) machine learning. Understanding where in the input domain our model exerts high uncertainty can for example be instrumental in labelling data: see for example Gal, Islam, and Ghahramani (2017) and follow-up works for an interesting application to &lt;strong&gt;active learning&lt;/strong&gt; for image data. Similarly, there is a recent work that uses estimates of the posterior predictive in the context of &lt;strong&gt;algorithmic recourse (&lt;/strong&gt;Schut et al. 2021). For a brief introduction to algorithmic recourse see my &lt;a href="https://forem.julialang.org/patalt/individual-recourse-for-black-box-models-1cak-temp-slug-6649736"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a great reference for further reading about probabilistic machine learning I can highly recommend Murphy (2022). An electronic version of the book is currently freely available as a draft. Finally, if you are curious to see the full source code in detail and want to try yourself at the code, you can check out this &lt;a href="https://colab.research.google.com/github/pat-alt/pat-alt.github.io/blob/main/content/post/2021-11-15-bayesian-logistic-regression/julia_implementation.ipynb"&gt;interactive notebook&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;Bishop, Christopher M. 2006. &lt;em&gt;Pattern Recognition and Machine Learning&lt;/em&gt;. springer.&lt;/p&gt;

&lt;p&gt;Gal, Yarin, Riashat Islam, and Zoubin Ghahramani. 2017. “Deep Bayesian Active Learning with Image Data.” In &lt;em&gt;International Conference on Machine Learning&lt;/em&gt;, 1183–92. PMLR.&lt;/p&gt;

&lt;p&gt;Murphy, Kevin P. 2012. &lt;em&gt;Machine Learning: A Probabilistic Perspective&lt;/em&gt;. MIT press.&lt;/p&gt;

&lt;p&gt;— -. 2022. &lt;em&gt;Probabilistic Machine Learning: An Introduction&lt;/em&gt;. MIT Press.&lt;/p&gt;

&lt;p&gt;Schut, Lisa, Oscar Key, Rory Mc Grath, Luca Costabello, Bogdan Sacaleanu, Yarin Gal, et al. 2021. “Generating Interpretable Counterfactual Explanations by Implicit Minimisation of Epistemic and Aleatoric Uncertainties.” In &lt;em&gt;International Conference on Artificial Intelligence and Statistics&lt;/em&gt;, 1756–64. PMLR.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full article published at&lt;/em&gt; &lt;a href="https://www.paltmeyer.com/post/bayesian-logistic-regression/"&gt;&lt;em&gt;https://www.paltmeyer.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on November 15, 2021.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>logisticregression</category>
      <category>julialang</category>
      <category>bayes</category>
    </item>
  </channel>
</rss>
