Adaptive questioning#

Adaptive questioning: which question a robot should ask next, and when to stop asking.

The robot is unsure which of several hypotheses holds (which object the person means, which plan they prefer). It can ask yes/no questions, each with a cost, or act on its best hypothesis, risking an error. After each answer it updates its belief and decides again.

The answer model matters. People’s answers depend on the questions asked before (order effects), so a planner that treats answers as independent readings of a fixed preference draws wrong conclusions from the second and later answers. Two answer models are provided:

  • ProjectiveAnswerModel: each hypothesis is a belief state and each question a projector; answers follow the Lüders rule in sequence, so they depend on what was asked before;

  • IndependentAnswerModel: a fixed table of yes-probabilities per hypothesis and question (the classical, order-free assumption).

QuestionPlanner works with either. At each step it computes the value of information of every remaining question, the expected reduction in the cost of acting wrongly,

\[\text{VOI}(q) = C_{\text{err}}\,\bigl(1 - \max_h p(h)\bigr) - \sum_a p(a \mid q)\, C_{\text{err}}\,\bigl(1 - \max_h p(h \mid a)\bigr),\]

asks the question with the largest value when it exceeds the cost of asking, and acts otherwise.

Examples

>>> import numpy as np
>>> from quantum_mind.applications.questioning import IndependentAnswerModel, QuestionPlanner
>>> m = IndependentAnswerModel(['red cup', 'blue cup'], ['red?'], [[0.95], [0.05]])
>>> planner = QuestionPlanner(m, ask_cost=0.2, error_cost=5.0)
>>> planner.decide([])[:2]                 # 50/50: worth asking
('ask', 'red?')
>>> planner.decide([('red?', 1)])[:2]      # after "yes": act on the red cup
('act', 'red cup')

Classes

ProjectiveAnswerModel

Order-dependent answers: Lüders rule on a belief state per hypothesis.

IndependentAnswerModel

Order-free answers: a fixed yes-probability per hypothesis and question.

QuestionPlanner

Value-of-information question planner.

class ProjectiveAnswerModel(states, projectors)[source]#

Bases: object

Order-dependent answers: Lüders rule on a belief state per hypothesis.

Parameters:
  • states (dict) – {hypothesis: state vector}: the person’s belief state when the hypothesis is true.

  • projectors (dict) – {question: projector} of the “yes” answer.

likelihood(h, history)[source]#

Probability of the whole answer history under one hypothesis.

Parameters:
  • h (hashable) – Hypothesis.

  • history (list of tuple) – [(question, answer), ...] with answer 1 (yes) or 0 (no), in the order asked.

Returns:

float

p_yes(h, history, q)[source]#

Probability of “yes” to the next question, given the history.

Parameters:
  • h (hashable)

  • history (list of tuple)

  • q (hashable) – Next question.

Returns:

float

order_free()[source]#

The order-blind approximation of this model.

Returns:

IndependentAnswerModel – Yes-probabilities of every question asked first, with answers treated as independent.

class IndependentAnswerModel(hypotheses, questions, table)[source]#

Bases: object

Order-free answers: a fixed yes-probability per hypothesis and question.

Parameters:
  • hypotheses (sequence) – Hypothesis names.

  • questions (sequence) – Question names.

  • table (array_like) – table[i][j] = P(yes to question j | hypothesis i).

likelihood(h, history)[source]#

Probability of the answer history under one hypothesis (answers independent).

Parameters:
Returns:

float

p_yes(h, history, q)[source]#

Probability of “yes” to a question (independent of the history).

Parameters:
  • h (hashable)

  • history (list of tuple)

  • q (hashable)

Returns:

float

class QuestionPlanner(answer_model, prior=None, ask_cost=1.0, error_cost=5.0, repeat=False)[source]#

Bases: object

Value-of-information question planner.

Parameters:
  • answer_model (ProjectiveAnswerModel or IndependentAnswerModel) – How the robot believes people answer.

  • prior (array_like, optional) – Prior over hypotheses (uniform by default).

  • ask_cost (float, optional) – Cost of asking one question (time, annoyance).

  • error_cost (float, optional) – Cost of acting on a wrong hypothesis.

  • repeat (bool, optional) – Allow asking the same question twice (default False).

posterior(history)[source]#

Belief over hypotheses after an answer history.

Parameters:

history (list of tuple) – [(question, answer), ...].

Returns:

numpy.ndarray

value_of_information(q, history)[source]#

Expected reduction of the error cost from asking one more question.

Parameters:
  • q (hashable) – Candidate question.

  • history (list of tuple)

Returns:

float

information_gain(q, history)[source]#

Expected entropy reduction (bits) of the belief from asking a question.

Parameters:
Returns:

float

decide(history)[source]#

Ask or act.

Parameters:

history (list of tuple) – Answers so far.

Returns:

tuple – ('ask', question, value_of_information) or ('act', hypothesis, probability).

run(respond, max_questions=10)[source]#

Interactive loop: ask until acting is better.

Parameters:
  • respond (callable) – respond(question) -> 0 or 1: asks the person (or a simulated person).

  • max_questions (int, optional) – Safety limit on the number of questions.

Returns:

dict – choice (hypothesis acted on), probability, history and cost (asking cost only).

simulate(truth_model, true_h, rng, max_questions=10)[source]#

Run the planner against a simulated person.

Parameters:
Returns:

dict – As run(), plus correct and total_cost (asking plus error cost).