definition example

A bicategory has objects , and for each pair of objects a hom-category whose objects are 1-cells and whose morphisms are 2-cells . There are composition functors and identity 1-cells , but composition of 1-cells is associative and unital only up to specified invertible 2-cells

natural in and satisfying the pentagon and triangle identities — exactly the coherence data of a Monoidal Category, one dimension up. A bicategory with one object is a monoidal category (1-cells are objects, horizontal composition is ), and a bicategory whose associators and unitors are identities is a strict 2-Category.

Sources: Bénabou (1967), Introduction to bicategories; Johnson & Yau, 2-Dimensional Categories (Oxford 2021); Cruttwell et al. arXiv:2103.01931 (notes) Remark 2.1 (the 2-categorical perspective on ); Capucci et al. arXiv:2105.06332 (notes) Definition 2; St Clere Smithe & Perin, AutoBayes arXiv:2503.18608 (notes) Remarks 5, 7, 24, 30.

Why bicategories show up in categorical machine learning

Whenever a morphism carries extra data that gets multiplied under composition, composition is associative only up to reassociating that data — and a bicategory appears.

  • (Capucci et al., Definition 2). A 1-cell is a pair ; composing and has parameter . Associativity holds only up to the associator of , and the 2-cells are the reparametrisations . Cruttwell et al. (Remark 2.1) quotient this away to get a category; optimisers, weight tying and LoRA are 2-cells, so the quotient loses information.
  • Open models (AutoBayes, Remark 5). A 1-cell is a kernel with a latent space; composites accumulate latent spaces , again associative up to isomorphism. The operation reveal, which moves a latent factor into the observed codomain, is a 2-cell.
  • Statistical games and Bayesian lenses (AutoBayes, Remark 24) inherit the bicategory structure of open models, and parameterized statistical games form a monoidal bicategory (Remark 30).
  • Spans and cospans. Spans in a category with pullbacks form the bicategory (composition by pullback, defined only up to isomorphism); dually cospans compose by pushout. This is the original example of Bénabou.
  • Profunctors compose by a coend, which is again only associative up to isomorphism.

Examples

  • is a (strict) 2-category: 1-cells are functors, 2-cells natural transformations.
  • as a locally posetal bicategory: hom-categories are the posets , a 2-cell exists iff .
  • A monoidal category is the one-object bicategory (“delooping”).
  • : a 1-cell is a neural network layer with its weight space; a 2-cell is a map of weight spaces compatible with the layers.

Coherence and strictification

Every bicategory is biequivalent to a 2-category (Mac Lane–Paré coherence), so for most purposes one can compute as if associativity held strictly — in practice, this is what implementations do: a parameter tree stored as a nested NamedTuple (Lux.jl, Lenticulum.jl) is a canonical representative of , and the associator is never materialised. Functors between bicategories come in strict, pseudo, lax and oplax flavours; see Lax Functor.

Lenticulum.jl

Lenticulum’s factors are 1-cells in the bicategory of parameterized statistical games; its parameter trees are the 2-cell data made concrete. See Factors are Parameterized Statistical Games.

Docs: Theories (Catlab): ThBicategoryRelations

# 1-cells of Para(Set): a parameter "space" (here: a type) and a map P × A → B.
struct Para1{P,F}; ptype::Type{P}; f::F; end
compose(g::Para1, f::Para1) = Para1(Tuple{g.ptype, f.ptype}, ((q, p), a) -> g.f(q, f.f(p, a)))
# the associator is a relabelling of nested parameter tuples, not an equality
assoc(((r, q), p)) = (r, (q, p))
f = Para1(Float64, (w, x) -> w * x)          # a scaling layer
g = Para1(Float64, (b, x) -> x + b)          # a bias layer
h = Para1(Float64, (s, x) -> tanh(s * x))
left  = compose(compose(h, g), f)            # parameter shape ((s, b), w)
right = compose(h, compose(g, f))            # parameter shape (s, (b, w))
x, s, b, w = 0.3, 2.0, 0.1, 1.5
left.f(((s, b), w), x) ≈ right.f(assoc(((s, b), w)), x)   # equal up to the associator: true
# a 2-cell (reparametrisation) r : Q → P, here weight tying P = (Float64, Float64) ← Q = Float64
tie(θ) = (θ, θ)
reparam(φ::Para1, r, Q) = Para1(Q, (q, a) -> φ.f(r(q), a))
tied = reparam(compose(f, f), tie, Float64)
tied.f(3.0, 1.0) == 9.0                       # both layers share one weight
import Mathlib
open CategoryTheory
#check Bicategory                       -- hom-categories, associator, unitors, pentagon, triangle
#check @Bicategory.associator
#check @Bicategory.leftUnitor
#check MonoidalSingleObj                -- a monoidal category as a one-object bicategory
{-# LANGUAGE GADTs #-}
-- Para over (Hask, (,)): a 1-cell a -> b with parameter p
newtype Para p a b = Para { runPara :: (p, a) -> b }
 
compose :: Para q b c -> Para p a b -> Para (q, p) a c
compose (Para g) (Para f) = Para (\((q, p), a) -> g (q, f (p, a)))
 
-- the associator: reassociate the parameter, a 2-cell rather than an equality
assoc :: Para ((r, q), p) a d -> Para (r, (q, p)) a d
assoc (Para k) = Para (\((r, (q, p)), a) -> k (((r, q), p), a))
 
-- a 2-cell / reparametrisation along r :: p' -> p
reparam :: (p' -> p) -> Para p a b -> Para p' a b
reparam r (Para f) = Para (\(p', a) -> f (r p', a))