The knowledge bottleneck — what you cannot write down
You read ten handwritten digits in a second. Then try to write down the rule you used. The best hand-written rules score 24% where you scored 100%, and the reason is not a lack of effort.
The arms race you lose showed hand-written knowledge running out of value, and then being overtaken. Both of those are arguments about effort. They say the job is bigger than you can afford.
This lesson is about a harder problem. Start by doing something easy.
Read these ten digits
Ten out of ten, in about a second, with no effort. You have just outperformed every system in this path so far, on a task you did not know you were being tested on.
Now the only question that matters. How did you do that?
Write down the rule you just used
Not a vague answer. A rule of the kind The machine that reasoned ran on, that a program could execute. Here is what people actually propose.
- how much ink there is altogether
- how much ink is in the top, the middle, the bottom
- how much is on the left against the right, and whether it is symmetric
- how wide it is, how tall it is
- whether it has a hole in it, which is surely how anyone knows a 0 from a 1
Every one of those is measurable on 64 pixels, and every one is what an expert would offer.
10 features, all sensible. Now build the best possible rules from them. For each digit, find the conditions that separate it most cleanly, tuned as hard as tuning goes.
Random guessing gets 10%%. The best tuned rules get 24.2%%. You got 100%.
| Accuracy | |
|---|---|
| one condition per rule | 2.9%% |
| five conditions, tuned as far as they go | 24.2%% |
| guessing at random | 10%% |
| you, a moment ago | 100% |
one condition per rule
- Accuracy
- 2.9%%
five conditions, tuned as far as they go
- Accuracy
- 24.2%%
guessing at random
- Accuracy
- 10%%
you, a moment ago
- Accuracy
- 100%
This is not a hard problem badly solved. It is a problem you solved perfectly, without trying, using knowledge you cannot get at. The written-down version of that knowledge, tuned as far as it goes, reaches 24.2%%.
Rules fall apart in two ways at once, and the fixes pull against each other
Look at what the ruleset does with each digit, rather than at the score.
- 36%% get a clean answer. Exactly one rule fires.
- 21%% fire no rule at all. The system has nothing to say about them.
- 43%% fire several rules that disagree. It is a 3 and a 5 and an 8 at once.
Widen the rules to cover the gaps and you create more contradictions. Tighten them to resolve contradictions and you open more gaps. Every large rule base develops both.
The best feature on the easiest pair still overlaps 81% of the time
Take the two digits a rule-writer would most confidently separate, a 1 and a 7. Find the single best feature for telling them apart out of all 10. It turns out to be ink at the bottom.
81%% of them fall in the range where the two overlap. Wherever you put the threshold, you are wrong about a large fraction of both.
Meanwhile you told a 1 from a 7 instantly. Asked how, you would say something like the 7 has a bar across the top. That is true, and none of the sixty-four numbers say it, because a bar across the top is not a quantity.
We can know more than we can tell
The philosopher Michael Polanyi put it in one line in 1966. He was not writing about computers. He was pointing out that most human expertise is like riding a bicycle: completely reliable, and impossible to hand over in words.
That is the knowledge bottleneck, and it is fatal to the whole approach. An expert system needs its expert to dictate. The expert cannot dictate, because what makes them expert is the part they cannot put into words.
The things we find hard, machines find easy
The machine that looked ahead built a machine that cannot lose at a game, and noted that it cannot pick up one of the pieces. Now you can see the shape of that.
Chess grandmasters were beaten decades before anyone could read a handwritten envelope reliably. This is Moravec's paradox, put plainly in 1988.
There is a reason for it. Playing chess is recent, deliberate and conscious, which is exactly why we can describe it and therefore program it. Recognising a shape is ancient and automatic.
So do not write the rules. Show the machine examples.
Classical AI ends here, and not from lack of effort or hardware. It ends because its central assumption failed. Knowledge could be extracted from experts and written down. For the problems that matter most, there is nothing to extract.
The knowledge plainly exists. You used it a moment ago. So there is one possibility left. Show the machine thousands of examples and let it work out the rule itself.
content/notebooks/introduction-to-ai/what-you-cannot-write-down.ipynb
Add your own feature and see whether it helps. Worth trying: pick the two digits you think are easiest to separate and find the best single feature for them.
Show the code5 cells
D = load_digits()
IMG, Y = D.images, D.target
record('n_digits', f'{len(Y):,}')
record('img_size', f'{IMG.shape[1]}x{IMG.shape[2]}')
record('n_pixels', IMG.shape[1] * IMG.shape[2])
record('n_classes', len(set(Y)))from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
picks = [int(np.where(Y == d)[0][1]) for d in range(10)]
def plot(ax):
# Rectangles, not imshow: imshow embeds a base64 raster and the page's
# SVG guard refuses <image>. Alpha carries the ink so it reads on either theme.
rects, alphas = [], []
for k, idx in enumerate(picks):
ox = k * 9
for r in range(8):
for c in range(8):
v = IMG[idx][r, c] / 16.0
if v > 0.02:
rects.append(Rectangle((ox + c, r), 1, 1)); alphas.append(v)
pc = PatchCollection(rects, facecolor=(0.91, 0.41, 0.25), edgecolor='none')
pc.set_alpha(None); pc.set_facecolors([(0.91, 0.41, 0.25, a) for a in alphas])
ax.add_collection(pc)
ax.set_xlim(0, 90); ax.set_ylim(8.6, -0.6); ax.set_aspect('equal')
ax.set_xticks([]); ax.set_yticks([])
for s in ax.spines.values(): s.set_visible(False)
save_fig('ten-digits', plot, figsize=(7, 1.5))def holes(img):
"""Enclosed background regions — the thing that makes 0 and 8 obvious."""
bg = img < 4; H, W = bg.shape
seen = np.zeros_like(bg, bool); n = 0
for sr in range(H):
for sc in range(W):
if not bg[sr, sc] or seen[sr, sc]: continue
stack, cells, touches = [(sr, sc)], [], False
seen[sr, sc] = True
while stack:
r, c = stack.pop(); cells.append((r, c))
if r in (0, H-1) or c in (0, W-1): touches = True
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
nr, nc = r+dr, c+dc
if 0 <= nr < H and 0 <= nc < W and bg[nr, nc] and not seen[nr, nc]:
seen[nr, nc] = True; stack.append((nr, nc))
if not touches: n += 1
return n
def features(img):
ink = img.sum()
return {
'total ink': ink,
'ink in the top': img[:3].sum(),
'ink in the middle':img[3:5].sum(),
'ink at the bottom':img[5:].sum(),
'ink on the left': img[:, :4].sum(),
'ink on the right': img[:, 4:].sum(),
'left-right symmetry': -abs(img[:, :4].sum() - img[:, 4:][:, ::-1].sum()),
'how wide it is': np.count_nonzero(img.sum(axis=0) > 2),
'how tall it is': np.count_nonzero(img.sum(axis=1) > 2),
'holes in it': holes(img),
}
NAMES = list(features(IMG[0]))
X = np.array([[features(im)[n] for n in NAMES] for im in IMG], float)
record('n_features', len(NAMES))
record('feature_list', ', '.join(NAMES[:4]) + ', …')def best_rule(target, k):
"""Best k-condition rule for one digit: pick the k features whose bands
separate it best, and take the tightest band that keeps 80% of them."""
is_t = Y == target
scored = []
for j in range(X.shape[1]):
lo, hi = np.percentile(X[is_t, j], [10, 90])
fires = (X[:, j] >= lo) & (X[:, j] <= hi)
prec = (fires & is_t).sum() / max(fires.sum(), 1)
scored.append((prec, j, lo, hi))
scored.sort(reverse=True)
return [(j, lo, hi) for _, j, lo, hi in scored[:k]]
def accuracy(k, tally=None):
rules = {d: best_rule(d, k) for d in range(10)}
right = 0
for i in range(len(Y)):
votes = [d for d, conds in rules.items()
if all(lo <= X[i, j] <= hi for j, lo, hi in conds)]
if tally is not None:
tally['none' if not votes else 'many' if len(votes) > 1 else 'one'] += 1
if len(votes) == 1 and votes[0] == Y[i]: right += 1
return right / len(Y)
# The two failures an expert system actually suffers, counted (OUTLINE L7):
# digits NO rule covers, and digits where rules CONTRADICT each other.
tally = {'none': 0, 'one': 0, 'many': 0}
accuracy(3, tally)
record('no_rule_fires', f"{tally['none']/len(Y):.0%}")
record('rules_contradict', f"{tally['many']/len(Y):.0%}")
record('exactly_one_rule', f"{tally['one']/len(Y):.0%}")
curve = [accuracy(k) for k in range(1, 6)]
for k, a in zip(range(1, 6), curve):
record(f'acc_{k}cond', f'{a:.1%}')
record('best_rule_acc', f'{max(curve):.1%}')
record('guessing', f'{1/10:.0%}')
def plot(ax):
ax.plot(range(1, 6), [100*c for c in curve], marker='o', lw=2.2)
ax.axhline(10, ls=':', lw=1.2)
ax.text(5, 12, 'guessing', ha='right', fontsize=9)
ax.axhline(97, ls='--', lw=1.2)
ax.text(1.05, 92, 'you, just now, in one second', fontsize=9)
ax.set_xticks(range(1, 6))
ax.set_xlabel('conditions per rule')
ax.set_ylabel('digits identified correctly (%)')
ax.set_ylim(0, 105)
save_fig('rules-vs-you', plot, figsize=(7, 3.8))def overlap(a, b):
best = None
for j in range(X.shape[1]):
ma, mb = X[Y == a, j], X[Y == b, j]
lo = max(ma.min(), mb.min()); hi = min(ma.max(), mb.max())
share = (((ma >= lo) & (ma <= hi)).mean() + ((mb >= lo) & (mb <= hi)).mean()) / 2
if best is None or share < best[0]: best = (share, j)
return best
share, j = overlap(1, 7)
record('best_feature_1v7', NAMES[j])
record('overlap_1v7', f'{share:.0%}')
def plot(ax):
bins = np.linspace(X[:, j].min(), X[:, j].max(), 26)
ax.hist(X[Y == 1, j], bins=bins, alpha=.75, label='the digit 1')
ax.hist(X[Y == 7, j], bins=bins, alpha=.75, label='the digit 7')
ax.set_xlabel(NAMES[j])
ax.set_ylabel('how many digits')
ax.legend(frameon=False)
save_fig('the-overlap', plot, figsize=(7, 3.4))Related lessons
Base rates — what a piece of evidence is actually worth
A face-recognition system that is 99.9% accurate and almost entirely wrong, and a number that sent an innocent woman to prison. Both are the same arithmetic, and it is the arithmetic that decides what any piece of evidence is worth.
ReadConfirmation and survivorship — what you never looked for
Two questions about evidence you did not go looking for. One is a rule you have to discover, and one is a pattern in five famous people — and in both, the thing that would have told you the truth is the thing nobody checks.
ReadLoss aversion, sunk cost and regression — what it costs you
Four questions you answer about yourself rather than about a scenario, and your own answers are the finding. Then the pattern that makes praise look useless and criticism look like it works, whatever you actually do.
Read
