home

Multilayer Perceptron

Creator: Petar Veličković (original)

A feedforward multilayer perceptron with one hidden neuron expanded into its affine transform and nonlinear activation.


Multilayer Perceptron

Download

PNG PDF SVG

Code

multilayer-perceptron.typ (123 lines)

#import "@preview/cetz:0.5.2": canvas, draw
#import draw: bezier, circle, content, line

// Outline weight of network units.
#let node-stroke = 0.8pt

#set page(width: auto, height: auto, margin: 8pt, fill: none)
#set text(size: 12pt)

#canvas({
  let arrow = (mark: (end: "stealth", fill: black, scale: .55), stroke: .8pt)
  let perceptron-radius = .48
  let small(name, pos, label: none, radius: .28) = {
    circle(pos, radius: radius, fill: rgb("#cdd3da"), stroke: node-stroke, name: name)
    if label != none { content(name, label) }
  }
  let right-rim(pos, radius: .28) = (pos.at(0) + radius, pos.at(1))
  let rim-from(pos) = {
    let length = calc.sqrt(pos.at(0) * pos.at(0) + pos.at(1) * pos.at(1))
    (
      pos.at(0) / length * perceptron-radius,
      pos.at(1) / length * perceptron-radius,
    )
  }

  circle(
    (0, 0),
    radius: perceptron-radius,
    fill: rgb("#cdd3da"),
    stroke: node-stroke,
    name: "perceptron",
  )
  content("perceptron", $Sigma sigma$)
  for (name, y, label, weight, ctrl-y) in (
    ("x0", 1.5, $+1$, $w_0$, 1.05),
    ("x1", .75, $x_1$, $w_1$, .72),
    ("x2", 0, $x_2$, $w_2$, 0),
    ("x3", -.75, $x_3$, $w_3$, -.72),
    ("xn", -1.75, $x_n$, $w_n$, -1.05),
  ) {
    let pos = (-2, y)
    small(name, pos, label: text(size: 12pt)[#label])
    bezier(
      right-rim(pos),
      rim-from((-.85, ctrl-y)),
      (-1.45, y),
      (-.85, ctrl-y),
      ..arrow,
    )
    content(
      (-1.25, (y + ctrl-y) / 2 + .12),
      text(size: 12pt)[#weight],
      anchor: "south",
    )
  }
  content((rel: (0, .53), to: "xn"), $dots.v$)
  line(
    (rel: (0, -.43), to: "perceptron"),
    (rel: (0, .43), to: "perceptron"),
    stroke: (dash: "dashed", paint: black, thickness: .5pt),
  )
  line(
    "perceptron.east",
    (rel: (3.5, 0), to: "perceptron.east"),
    ..arrow,
    name: "perceptron-out",
  )
  content(
    (rel: (0, .42), to: "perceptron-out.mid"),
    $sigma(w_0 + sum_(i=1)^n w_i x_i)$,
    fill: rgb("#cdd3da"),
    padding: 1pt,
  )

  let inputs = (
    ("i1", (6, .75), $I_1$),
    ("i2", (6, 0), $I_2$),
    ("i3", (6, -.75), $I_3$),
  )
  let hidden = (
    ("h1", (8, 1.5)),
    ("h2", (8, .75)),
    ("h3", (8, 0)),
    ("h4", (8, -.75)),
    ("h5", (8, -1.5)),
  )
  let outputs = (("o1", (10, .75), $O_1$), ("o2", (10, -.75), $O_2$))
  for (name, pos, label) in inputs {
    small(name, pos, radius: .16)
    let west = name + ".west"
    let edge = name + "-in"
    line((rel: (-.85, 0), to: west), west, ..arrow, name: edge)
    content((rel: (0, .28), to: edge + ".mid"), label)
  }
  for (name, pos) in hidden { small(name, pos, radius: .16) }
  for (name, pos, label) in outputs { small(name, pos, radius: .16) }
  for (input-name, ..) in inputs {
    for (hidden-name, ..) in hidden { line(input-name, hidden-name, ..arrow) }
  }
  for (hidden-name, ..) in hidden {
    for (output-name, ..) in outputs { line(hidden-name, output-name, ..arrow) }
  }
  for (name, pos, label) in outputs {
    let east = name + ".east"
    let edge = name + "-out"
    line(east, (rel: (.85, 0), to: east), ..arrow, name: edge)
    content((rel: (0, .28), to: edge + ".mid"), label)
  }
  content((rel: (0, 1.55), to: "i1.north"), align(center)[Input\ layer])
  content((rel: (0, .8), to: "h1.north"), align(center)[Hidden\ layer])
  content((rel: (0, 1.55), to: "o1.north"), align(center)[Output\ layer])
  line(
    (rel: (.12, .43), to: "perceptron"),
    (rel: (0, .16), to: "h5"),
    stroke: (paint: luma(45%), dash: "dashed", thickness: 1pt),
  )
  line(
    (rel: (.12, -.43), to: "perceptron"),
    (rel: (0, -.16), to: "h5"),
    stroke: (paint: luma(45%), dash: "dashed", thickness: 1pt),
  )
})