« home

Regular vs Bayes NN

machine learningneural networksBayesianstatisticsdeep learningprobabilitycetztikz

Comparison between regular neural networks and Bayesian neural networks. The diagram illustrates how Bayesian networks incorporate uncertainty in their predictions by treating weights as probability distributions rather than point values, leading to more robust predictions with uncertainty estimates.


Regular vs Bayes NN

  Download

PNGPDFSVG

  Code

  LaTeX

regular-vs-bayes-nn.tex (62 lines)

\documentclass[tikz]{standalone}

\usetikzlibrary{calc}

\def\layersep{3cm}
\newcommand\nn[1]{
    % Input layer
    \foreach \y in {1,...,2}
        \node[neuron, fill=green!40] (i\y-#1) at (0,\y+1) {$i\y$};

    % Hidden layer
    \foreach \y in {1,...,4}
        \path node[neuron, fill=blue!40] (h\y-#1) at (\layersep,\y) {$h\y$};

    % Output node
    \node[neuron, fill=red!40] (o-#1) at (2*\layersep,2.5) {$o$};

    % Connect every node in the input layer with every node in the hidden layer.
    \foreach \source in {1,...,2}
        \foreach \dest in {1,...,4}
            \path (i\source-#1) edge (h\dest-#1);

    % Connect every node in the hidden layer with the output layer
    \foreach \source in {1,...,4}
        \path (h\source-#1) edge (o-#1);
}

\begin{document}
\begin{tikzpicture}[
    scale=1.2,
    shorten >=1pt,->,draw=black!70, node distance=\layersep,
    neuron/.style={circle,fill=black!25,minimum size=20,inner sep=0},
    edge/.style 2 args={pos={(mod(#1+#2,2)+1)*0.33}, font=\tiny},
    distro/.style 2 args={
        edge={#1}{#2}, node contents={}, minimum size=0.6cm, path picture={\draw[double=orange,white,thick,double distance=1pt,shorten >=0pt] plot[variable=\t,domain=-1:1,samples=51] ({\t},{0.2*exp(-100*(\t-0.05*(#1-1))^2 - 3*\t*#2))});}
      },
    weight/.style 2 args={
        edge={#1}{#2}, node contents={\pgfmathparse{0.35*#1-#2*0.15}\pgfmathprintnumber[fixed]{\pgfmathresult}}, fill=white, inner sep=2pt
      }
  ]
  \nn{regular}

  \begin{scope}[xshift=8cm]
    \nn{bayes}
  \end{scope}

  % Draw weights for all regular edges.
  \foreach \i in {1,...,2}
  \foreach \j in {1,...,4}
  \path (i\i-regular) -- (h\j-regular) node[weight={\i}{\j}];
  \foreach \i in {1,...,4}
  \path (h\i-regular) -- (o-regular) node[weight={\i}{1}];

  % Draw distros for all Bayesian edges.
  \foreach \i in {1,...,2}
  \foreach \j in {1,...,4}
  \path (i\i-bayes) -- (h\j-bayes) node[distro={\i}{\j}];
  \foreach \i in {1,...,4}
  \path (h\i-bayes) -- (o-bayes) node[distro={\i}{1}];
\end{tikzpicture}
\end{document}

  Typst

regular-vs-bayes-nn.typ (216 lines)

#import "@preview/cetz:0.3.4": canvas, draw
#import "@preview/cetz-plot:0.1.1": plot
#import draw: line, circle, content, group, bezier, translate

#set page(width: auto, height: auto, margin: 8pt)

#canvas({
  let layer-sep = 3.5 // Horizontal separation between layers
  let node-sep = 1.5 // Vertical separation between nodes
  let arrow-style = (mark: (end: "stealth", scale: 0.7), stroke: gray + 0.7pt, fill: gray)

  // Helper function to draw a neuron
  let neuron(pos, fill: white, label: none, name: none) = {
    content(
      pos,
      if label != none { $#label$ },
      frame: "circle",
      fill: fill,
      stroke: none,
      radius: 0.4,
      padding: 3pt,
      name: name,
    )
  }

  // Helper function to calculate line angle and shift along it
  let line-shift(start, end, dist) = {
    let dx = end.at(0) - start.at(0)
    let dy = end.at(1) - start.at(1)
    let len = calc.sqrt(dx * dx + dy * dy)
    // Return shift vector components
    return (
      x: dist * dx / len,
      y: dist * dy / len,
    )
  }

  // Helper function to draw a weight label
  let weight-label(start, end, ii, jj, offset: 0) = {
    let mid-x = (start.at(0) + end.at(0)) / 2
    let mid-y = (start.at(1) + end.at(1)) / 2

    // Calculate shift along the line
    let shift = if offset != 0 {
      let s = line-shift(start, end, offset * 0.4)
      (s.x, s.y)
    } else { (0, 0) }

    content(
      (mid-x + shift.at(0), mid-y + shift.at(1)),
      [#calc.round(0.35 * ii - jj * 0.15, digits: 2)],
      frame: "rect",
      fill: white,
      stroke: none,
      padding: 1.5pt,
    )
  }

  // Helper function to draw a Gaussian distribution
  let gaussian(start, end, offset: 0, shift: 0) = {
    let width = 0.6
    let height = 0.25
    let x-mid = (start.at(0) + end.at(0)) / 2
    let y-mid = (start.at(1) + end.at(1)) / 2
    let mu = offset * 0.15

    // Calculate shift along the line
    let s = if shift != 0 {
      line-shift(start, end, shift * 0.4)
    } else { (x: 0, y: 0) }

    group({
      translate((x-mid - width / 2 + s.x, y-mid - height / 2 + s.y))
      plot.plot(
        size: (width, height),
        axis-style: none,
        {
          plot.add(
            style: (stroke: orange + 1pt, fill: orange.lighten(80%)),
            domain: (-1, 1),
            samples: 50,
            x => {
              let variance = 0.3 + calc.abs(offset) * 0.1
              let peak = 0.8 + calc.rem(calc.abs(offset), 0.4)
              peak * calc.exp(-5 * calc.pow(x - mu, 2) / variance)
            },
          )
        },
      )
    })
  }

  // Draw regular network
  group(
    name: "regular",
    {
      // Input layer
      for ii in range(2) {
        neuron(
          (0, (ii + 1) * node-sep + 1),
          fill: rgb("#90EE90"),
          label: "ii" + str(ii + 1),
          name: "ii" + str(ii + 1),
        )
      }

      // Hidden layer
      for ii in range(4) {
        neuron(
          (layer-sep, (ii + 1) * node-sep),
          fill: rgb("#ADD8E6"),
          label: "h" + str(ii + 1),
          name: "h" + str(ii + 1),
        )
      }

      // Output layer
      neuron(
        (2 * layer-sep, 2.5 * node-sep),
        fill: rgb("#FFB6C6"),
        label: "o",
        name: "o",
      )

      // Connect layers with weights
      for ii in range(2) {
        for jj in range(4) {
          let start = ("ii" + str(ii + 1))
          let end = ("h" + str(jj + 1))
          line(start, end, ..arrow-style)
          weight-label(
            (0, (ii + 1) * node-sep + 1),
            (layer-sep, (jj + 1) * node-sep),
            ii + 1,
            jj + 1,
            offset: if ii == 0 { 1.5 } else { -1 },
          )
        }
      }

      for ii in range(4) {
        let start = ("h" + str(ii + 1))
        line(start, "o", ..arrow-style)
        weight-label(
          (layer-sep, (ii + 1) * node-sep),
          (2 * layer-sep, 2.5 * node-sep),
          ii + 1,
          1,
        )
      }
    },
  )

  // Draw Bayesian network
  group(
    name: "bayes",
    {
      // Shift everything right
      let x-offset = 3 * layer-sep

      // Input layer
      for ii in range(2) {
        neuron(
          (x-offset, (ii + 1) * node-sep + 1),
          fill: rgb("#90EE90"),
          label: "ii" + str(ii + 1),
          name: "ii" + str(ii + 1),
        )
      }

      // Hidden layer
      for ii in range(4) {
        neuron(
          (x-offset + layer-sep, (ii + 1) * node-sep),
          fill: rgb("#ADD8E6"),
          label: "h" + str(ii + 1),
          name: "h" + str(ii + 1),
        )
      }

      // Output layer
      neuron(
        (x-offset + 2 * layer-sep, 2.5 * node-sep),
        fill: rgb("#FFB6C6"),
        label: "o",
        name: "o",
      )

      // Connect layers with distributions
      for ii in range(2) {
        for jj in range(4) {
          let start = ("ii" + str(ii + 1))
          let end = ("h" + str(jj + 1))
          line(start, end, ..arrow-style)
          gaussian(
            (x-offset, (ii + 1) * node-sep + 1),
            (x-offset + layer-sep, (jj + 1) * node-sep),
            offset: ii - jj,
            shift: if ii == 0 { 1.5 } else { -1 },
          )
        }
      }

      for ii in range(4) {
        let start = ("h" + str(ii + 1))
        line(start, "o", ..arrow-style)
        gaussian(
          (x-offset + layer-sep, (ii + 1) * node-sep),
          (x-offset + 2 * layer-sep, 2.5 * node-sep),
          offset: ii,
        )
      }
    },
  )
})