Note
Go to the end to download the full example code.
A robot’s belief that depends on context (COBRA)#
A person reaches toward a robot. The same hand cue means “hand over” after a look at the robot and “retrieve” after a look away. A classical Bayes filter adds the same evidence either way; COBRA lets the previous frame rotate the basis in which the next frame is read.
The data are simulated and built to contain this context effect. The script prints the Bayes filter and COBRA side by side, the shift each gives to the same hand cue after opposite looks, and the decision cost.
Bayes filter accuracy 0.844 expected cost 0.230 ask rate 0.13
COBRA accuracy 0.863 expected cost 0.222 ask rate 0.14
Bayes filter hand-cue shift after a look at the robot -0.12, after a look away -0.12
COBRA hand-cue shift after a look at the robot +4.44, after a look away -3.79
online belief after one hand-over episode: {'handover': np.float64(0.88), 'retrieve': np.float64(0.08), 'withdraw': np.float64(0.04)}
import numpy as np
from quantum_mind.belief import COBRA, GaussianBayesFilter, min_cost_action
MU_A, MU_F, KAPPA = np.array([0.7, -0.7, 0.0]), np.array([0.0, 0.0, 0.9]), np.array([1.0, -1.0, 0.0])
def simulate(n, rng, frames=4):
z = rng.integers(0, 3, n)
a = MU_A[z][:, None] + rng.standard_normal((n, frames))
f = MU_F[z][:, None] + rng.standard_normal((n, frames))
prev = np.concatenate([np.zeros((n, 1)), a[:, :-1]], axis=1)
b = KAPPA[z][:, None] * np.sign(prev) + rng.standard_normal((n, frames))
return np.stack([a, b, f], axis=2), z
rng = np.random.default_rng(11)
x_train, z_train = simulate(500, rng)
x_test, z_test = simulate(2000, rng)
bayes = GaussianBayesFilter(3).fit(x_train, z_train)
cobra = COBRA(3, 3, steps=60, batch=100, restarts=4, seed=0).fit(x_train, z_train)
cost = np.array([[0, 6, 10], [1.5, 0, 0], [0.8, 0.8, 0.8]]) # hand over, wait, ask
for name, model in (('Bayes filter', bayes), ('COBRA', cobra)):
p = model.predict_proba(x_test)
act = min_cost_action(p, cost)
print('%-13s accuracy %.3f expected cost %.3f ask rate %.2f'
% (name, (p.argmax(1) == z_test).mean(), cost[act, z_test].mean(), (act == 2).mean()))
def shift(model, sign):
"""Log-odds shift (handover vs retrieve) caused by one hand cue b = +1.5 after a gaze cue of +-1.5."""
before = np.zeros((1, 1, 3)); before[0, 0, 0] = sign * 1.5
after = np.zeros((1, 2, 3)); after[0, 0, 0] = sign * 1.5; after[0, 1, 1] = 1.5
odds = lambda p: float(np.log(p[0, 0] / p[0, 1]))
return odds(model.predict_proba(after)) - odds(model.predict_proba(before))
for name, model in (('Bayes filter', bayes), ('COBRA', cobra)):
print('%-13s hand-cue shift after a look at the robot %+.2f, after a look away %+.2f'
% (name, shift(model, +1), shift(model, -1)))
belief = cobra.start() # online, one frame at a time, as on a robot
for frame in x_test[np.flatnonzero(z_test == 0)[0]]:
p = belief.update(frame)
print('online belief after one hand-over episode:', dict(zip(['handover', 'retrieve', 'withdraw'], p.round(2))))
Total running time of the script: (0 minutes 27.986 seconds)