home

MADE

TikZ-reproduction of fig. 1 from the paper MADE: Masked Autoencoder for Distribution Estimation (arxiv:1502.03509).


MADE

Download

PNG PDF SVG

Code

made.typ (184 lines)

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

// 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-style = (
    mark: (end: "stealth", fill: black, scale: 0.5, offset: 1pt),
    stroke: .5pt,
  )
  let node-style = (stroke: node-stroke)
  let spacing = (layer: 2, horizontal: 1.3)

  let draw-layer(y, nodes, prefix: "", masks: none, x-offset: 0) = {
    for i in range(nodes) {
      let x = (
        (nodes - 1) * spacing.horizontal / 2 - i * spacing.horizontal + x-offset
      )
      circle((x, y), radius: 0.3, name: prefix + str(i), ..node-style)
      if masks != none {
        content((x, y), str(masks.at(i)))
      }
    }
  }

  let fcnn-x = -5
  let mask-x = 0
  let made-x = 5

  // === Autoencoder (left): fully-connected layers + weight labels ===
  for (idx, (y, nodes)) in (
    (0, 3),
    (spacing.layer, 4),
    (2 * spacing.layer, 4),
    (3 * spacing.layer, 3),
  ).enumerate() {
    draw-layer(y, nodes, prefix: "fcnn" + str(idx) + "-", x-offset: fcnn-x)
  }
  for (from-idx, to-idx, layer-label) in (
    (0, 1, $W_1$),
    (1, 2, $W_2$),
    (2, 3, $V$),
  ) {
    let from-nodes = if from-idx == 0 { 3 } else { 4 }
    let to-nodes = if to-idx == 3 { 3 } else { 4 }
    // Connect each pair of successive layers using their node-name prefixes.
    for from-node in range(from-nodes) {
      for to-node in range(to-nodes) {
        line(
          "fcnn" + str(from-idx) + "-" + str(from-node),
          "fcnn" + str(to-idx) + "-" + str(to-node),
          ..arrow-style,
        )
      }
    }
    let mid-y = (from-idx + 0.5) * spacing.layer
    content(
      (fcnn-x + 2.1 + if layer-label == $W_2$ { 0.3 } else { 0 }, mid-y),
      layer-label,
    )
  }

  // === Mask matrices (middle) ===
  let mask-base-size = 1.25
  let mask-sep = 2.5

  // Bounding box, label, and mask grid share one set of dimensions.
  let mask-box(x, y, rows, cols, filled-cells, label) = {
    let width = mask-base-size * cols / 3
    let height = mask-base-size * rows / 3
    rect((x - width / 2, y), (x + width / 2, y + height))
    content((x - width / 2 - 0.8, y + height / 2), label)
    let cell-width = width / cols
    let cell-height = height / rows
    for i in range(cols + 1) {
      line(
        (x - width / 2 + i * cell-width, y),
        (x - width / 2 + i * cell-width, y + height),
        stroke: .2pt,
      )
    }
    for i in range(rows + 1) {
      line(
        (x - width / 2, y + i * cell-height),
        (x + width / 2, y + i * cell-height),
        stroke: .2pt,
      )
    }
    for (row, col) in filled-cells {
      rect(
        (x - width / 2 + col * cell-width, y + (rows - row - 1) * cell-height),
        (
          x - width / 2 + (col + 1) * cell-width,
          y + (rows - row) * cell-height,
        ),
        fill: black,
      )
    }
  }

  mask-box(
    mask-x,
    2 * mask-sep,
    2,
    4,
    ((0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (1, 3)),
    $M_V =$,
  )
  mask-box(
    mask-x,
    mask-sep,
    4,
    4,
    ((0, 0), (0, 2), (0, 3), (3, 0), (3, 2), (3, 3)),
    $M_(W_2) =$,
  )
  mask-box(
    mask-x,
    0,
    4,
    3,
    ((0, 0), (1, 0), (2, 0), (3, 0), (2, 2)),
    $M_(W_1) =$,
  )

  // === MADE (right): masked autoregressive connections ===
  for (idx, (y, nodes, masks)) in (
    (0, 3, (3, 1, 2)),
    (spacing.layer, 4, (2, 1, 2, 2)),
    (2 * spacing.layer, 4, (1, 2, 2, 1)),
    (3 * spacing.layer, 3, (3, 1, 2)),
  ).enumerate() {
    draw-layer(
      y,
      nodes,
      prefix: "made" + str(idx) + "-",
      masks: masks,
      x-offset: made-x,
    )
  }

  for (layer_idx, connections) in (
    ((), (0, 1, 2, 3), (0, 2, 3)),
    ((1, 2), (0, 1, 2, 3), (1, 2), (1, 2)),
    ((0, 2), (0,), (0,), (0, 2)),
  ).enumerate() {
    for (from_idx, destinations) in connections.enumerate() {
      for to_idx in destinations {
        line(
          "made" + str(layer_idx) + "-" + str(from_idx),
          "made" + str(layer_idx + 1) + "-" + str(to_idx),
          ..arrow-style,
        )
      }
    }
  }

  // === Labels ===
  for i in range(3) {
    content((rel: (0, -0.6), to: "fcnn0-" + str(i)), $x_#i$)
    content((rel: (0, 0.6), to: "fcnn3-" + str(i)), $hat(x)_#i$)
    content((rel: (0, -0.6), to: "made0-" + str(i)), $x_#i$)
  }
  content((rel: (0, 0.6), to: "made3-0"), $p(x_3|x_2)$)
  content((rel: (0, 0.6), to: "made3-1"), $p(x_2)$)
  content((rel: (-.2, 0.6), to: "made3-2"), $p(x_1|x_2,x_3)$)

  let label-size = 1.5em
  let bottom-y = -1.5
  for (center_x, label) in (
    (fcnn-x, [autoencoder]),
    (mask-x - 2, [$times$]),
    (mask-x, [masks]),
    (mask-x + 2, [$arrow.r$]),
    (made-x, [MADE]),
  ) {
    content((center_x, bottom-y), text(weight: "bold", size: label-size, label))
  }
})