home

Random Forest

Diagram of the random forest (RF) algorithm (Breiman 2001). RFs are ensembles model consisting of binary decision trees that predicts the mode of individual tree predictions in classification or the mean in regression. Every node in a decision tree is a condition on a single feature, chosen to split the dataset into two so that similar samples end up in the same set. RFs are inspectable, invariant to scaling and other feature transformations, robust to inclusion of irrelevant features and can estimate feature importance via mean decrease in impurity (MDI).


Random Forest

Download

PNG PDF SVG

Code

random-forest.typ (181 lines)

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

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

#let arrow-style = (
  mark: (end: "stealth", fill: black, scale: 0.5),
  stroke: 0.5pt,
)
#let edge-style = (stroke: 0.5pt)
#let red-arrow-style = (
  mark: (end: "stealth", fill: red, scale: 0.65),
  stroke: red + 1.2pt,
)

#canvas({
  let node-radius = 0.22
  let blue-fill = rgb("#7a7aff")
  let red-fill = rgb("#ff4a4a")

  let mix(from, to, ratio) = {
    let (from-x, from-y) = from
    let (to-x, to-y) = to
    (from-x + (to-x - from-x) * ratio, from-y + (to-y - from-y) * ratio)
  }

  let tree-node(position, fill) = circle(position, radius: node-radius, fill: fill, stroke: none)

  let red-path-arrow(from, to) = line(
    (rel: (0.18, 0), to: mix(from, to, 0.3)),
    (rel: (0.18, 0), to: mix(from, to, 0.78)),
    ..red-arrow-style,
  )

  let tree-edge(from, to, arrow: false) = {
    line(from, to, ..edge-style)
    if arrow { red-path-arrow(from, to) }
  }
  let edge(from, to, arrow: false) = (from, to, arrow)

  let node-box(position, body, name) = content(
    position,
    body,
    frame: "rect",
    stroke: 0.1pt,
    fill: rgb("#cdd3da"),
    inset: 3pt,
    radius: 3pt,
    padding: (3pt, 5pt, 2pt),
    name: name,
  )

  // a tree is a list of (name, horizontal offset, row, color) plus edges joining those
  // names; edges flagged as arrows trace the path one sample takes down to its leaf
  let draw-tree(box-name, x, label-inset, label, nodes, edges) = {
    let at = (:)
    for (name, dx, y, _) in nodes { at.insert(name, (x + dx, y)) }
    group(name: box-name, padding: (0.45, 0.5, 0.35, 0.35), {
      content((x - label-inset, -1.32), text(size: 16pt, label), anchor: "west")
      for (from, to, arrow) in edges { tree-edge(at.at(from), at.at(to), arrow: arrow) }
      for (name, _, _, fill) in nodes { tree-node(at.at(name), fill) }
    })
    rect(box-name + ".north-west", box-name + ".south-east", stroke: 0.5pt, fill: none, radius: 3pt)
  }

  let y-root = -1.55
  let y-child = -2.9
  let y-grandchild = -4.4
  let y-leaf = -6.0

  draw-tree(
    "tree1",
    -6.8,
    2.85,
    [Tree 1],
    (
      ("root", 0, y-root, red-fill),
      ("left", -1.25, y-child, blue-fill),
      ("right", 1.25, y-child, red-fill),
      ("left-left", -1.9, y-grandchild, blue-fill),
      ("left-right", -0.75, y-grandchild, blue-fill),
      ("right-left", 0.35, y-grandchild, blue-fill),
      ("right-right", 2.1, y-grandchild, red-fill),
      ("mid-left", -0.35, y-leaf, blue-fill),
      ("mid-right", 0.75, y-leaf, blue-fill),
      ("red-leaf", 1.85, y-leaf, red-fill),
      ("blue-leaf", 2.95, y-leaf, blue-fill),
    ),
    (
      edge("root", "left"),
      edge("root", "right", arrow: true),
      edge("left", "left-left"),
      edge("left", "left-right"),
      edge("right", "right-left"),
      edge("right", "right-right", arrow: true),
      edge("right-left", "mid-left"),
      edge("right-left", "mid-right"),
      edge("right-right", "red-leaf", arrow: true),
      edge("right-right", "blue-leaf"),
    ),
  )

  draw-tree(
    "tree2",
    -0.3,
    2.35,
    [Tree 2],
    (
      ("root", 0, y-root, red-fill),
      ("left", -1.15, y-child, red-fill),
      ("right", 1.15, y-child, blue-fill),
      ("left-left", -1.55, y-grandchild, blue-fill),
      ("red-leaf", -0.45, y-grandchild, red-fill),
      ("right-left", 0.75, y-grandchild, blue-fill),
      ("right-right", 1.65, y-grandchild, blue-fill),
      ("left-leaf-a", -2.0, y-leaf, blue-fill),
      ("left-leaf-b", -1.1, y-leaf, blue-fill),
      ("right-leaf-a", 1.3, y-leaf, blue-fill),
      ("right-leaf-b", 2.2, y-leaf, blue-fill),
    ),
    (
      edge("root", "left", arrow: true),
      edge("root", "right"),
      edge("left", "left-left"),
      edge("left", "red-leaf", arrow: true),
      edge("left-left", "left-leaf-a"),
      edge("left-left", "left-leaf-b"),
      edge("right", "right-left"),
      edge("right", "right-right"),
      edge("right-right", "right-leaf-a"),
      edge("right-right", "right-leaf-b"),
    ),
  )

  draw-tree(
    "tree3",
    6.0,
    1.95,
    [Tree $n$],
    (
      ("root", 0, y-root, red-fill),
      ("left", -1.15, y-child, blue-fill),
      ("right", 1.15, y-child, red-fill),
      ("left-left", -1.55, y-grandchild, blue-fill),
      ("left-right", -0.55, y-grandchild, blue-fill),
      ("red-child", 0.55, y-grandchild, red-fill),
      ("right-leaf", 1.55, y-grandchild, blue-fill),
      ("blue-leaf", 0.05, y-leaf, blue-fill),
      ("red-leaf", 1.05, y-leaf, red-fill),
    ),
    (
      edge("root", "left"),
      edge("root", "right", arrow: true),
      edge("left", "left-left"),
      edge("left", "left-right"),
      edge("right", "red-child", arrow: true),
      edge("right", "right-leaf"),
      edge("red-child", "blue-leaf"),
      edge("red-child", "red-leaf", arrow: true),
    ),
  )

  node-box((0, 1.75), [Training Data], "training")
  node-box((0, 0.55), [sample and feature bagging], "bagging")
  content((3.2, -3.55), text(size: 1.8em)[$dots.c$])
  node-box(
    (0, -7.8),
    text(size: 14pt)[mean in regression or majority vote in classification],
    "mean",
  )
  node-box((0, -9.2), [prediction], "pred")

  line("training", "bagging", ..edge-style)
  for tree in ("tree1", "tree2", "tree3") {
    line("bagging", tree + ".north", ..arrow-style)
    line(tree + ".south", "mean", ..arrow-style)
  }
  line("mean", "pred", ..arrow-style)
})