treeq is a small educational experiment for learning how neural networks can learn structured relationships.
It follows a key paper:
- Back-propagating Errors Rumelhart, Hinton & Williams .1986
It uses two small, isomorphic family trees — one English and one Italian — and trains a network to answer questions such as:
John → father → David
David → sister → Sarah
James → brother → Robert
Everything here is written from scratch on top of numpy: the forward pass, back-propagation, and the training loop. There is no autograd and no deep learning framework.
The experiment is a squeeze. Each person enters the network through an encoder of N units, and the network is trained twice: once with N = 24, one unit per person and room to just memorise, and once with N = 6, four people per unit so something has to be shared. The question is what the narrow one gives up — and what it works out in exchange.
The family trees live in data/families.csv and are defined using only three primitives:
parentsspousesex
Every other relationship is derived from those. Six sex-neutral relations (parents_of, children_of, siblings_of, spouse_of, piblings_of, niblings_of) are filtered by sex to give the twelve the network is trained on:
father mother husband wife son daughter
brother sister uncle aunt nephew niece
Running all twelve over 24 people gives 132 training rows — every (person, relation) pair that has at least one answer. A row can have more than one answer: David → son is both James and Robert, so the target is multi-hot.
uv sync
uv run treeq132 rows 24 people 12 relations
training 6 models...
person_dim runs train test loss
-----------------------------------------
24 3 0.987 0.417 0.1738
6 3 0.997 0.583 0.0451
Useful flags:
uv run treeq --show-codes # print the learned person representation
uv run treeq --ask David son # train on every row, then ask a question
uv run treeq --dims 24 12 6 --seeds 0 1 2 3 4
uv run treeq --epochs 2000 --learning-rate 0.5 --n-test 4
uv run treeq --helpdata/families.csv 24 people, two isomorphic trees
src/treeq/kinship.py primitives and the twelve derived relations
src/treeq/dataset.py relations → training rows, and the held-out split
src/treeq/network.py sigmoid, init, forward, backward, train, scoring
src/treeq/experiment.py running and summarising the dimension comparison
src/treeq/cli.py the treeq command
docs/ results and design notes
uv run pytestBack-propagation is checked against finite differences — every analytic gradient is compared with the loss actually moving when the weight is nudged, which is the only real proof the derivation is right.
- docs/results.md — what the two representations actually scored, what the bottleneck learned about the two families, and why the numbers are thinner evidence than they look
- docs/design.md — the architecture, the in-law decision behind uncle/aunt/nephew/niece, how scoring and splitting work, and where this parts company with the paper
This project really gives a proper understanding of relationships 😋
