Learning.set_stats
def set_stats(
**kv
):Change fields of the stats document.
Each question, hint and lesson uses one way to teach: its method. The tutor picks the method before it writes the prompt. So the tag is known, not guessed. When a question closes, its outcome counts for its method: solid 1, shaky 0.5, a miss 0.
The counts give each method a Beta posterior, for each subject and over all subjects. choose uses them in two ways:
explore in the profile). First choose tries a method that got better recently. Else it uses a random draw (a Thompson sample).The counts are in one stats document on the shelf. This document is only a cache: method_stats builds it again from the attempts.
Import the modules that this notebook uses.
METHODS maps each method to the line that the prompt gets. EXAMPLES_TO maps the profile’s “examples that help” to methods, to set the prior.
stats reads the stats document. It gives empty counts when there is no document. set_stats writes the document. A lock makes sure that two closes at the same time do not lose a count.
The cached stats: method counts for each subject (* is all), recent credits, and calibration.
credit adds one outcome to the counts and returns the new stats. It keeps the last 8 credits of each method, to find a method that gets better.
Count outcome kind for method in the stats document.
The stats st with one outcome kind added for method, in subject and in *.
Solid adds 1 to a. A miss adds 1 to b. Shaky adds half to each. An unknown outcome or method changes nothing.
st = credit(credit(credit(EMPTY, 'code', 'ml', 'solid'), 'code', 'ml', 'fragile'), 'maths', 'ml', 'gap')
test_eq(st['methods']['ml'], {'code': [1.5, .5], 'maths': [0., 1.]})
test_eq(st['methods']['*'], st['methods']['ml'])
test_eq(st['recent'], {'code': [1., .5], 'maths': [0.]})
test_eq(credit(st, 'code', 'ml', 'closed'), st)posterior gives (a, b) for a method: Beta(1, 1), plus 2 on a for a method in the profile’s examples. For a subject, it adds the subject counts and the * counts. It scales the * counts down to at most 4 pseudo-counts. So a new subject starts from what works for you in other subjects.
(right, n) over the last 5 credits of m, when they are at least 0.2 above its mean over all; else None.
The Beta (a, b) of method m in subject.
With no counts, the prior decides. improving finds a method that got better recently.
test_eq(posterior(EMPTY, 'code'), (1., 1.))
test_eq(posterior(EMPTY, 'code', prior=['code']), (3., 1.))
gm = dict(methods={'*': {'maths': [2., 4.]}}, recent={'maths': [0., 0., 1., 1., 1., 1.]})
test_eq(improving(gm, 'maths'), (4, 5))
test_is(improving(dict(gm, recent={'maths': [1., 0., 0., 0.]}), 'maths'), None)choose returns (method, note). The note goes in the line that says why you get this question.
The method for the next question, and a note on why. A weak concept gets the best method; else, sometimes, another one.
A weak concept gets the best method. With explore='never', choose always gives the best method. With sometimes, it tries a method that got better at times. It still gives the best method most of the time.
cs = dict(methods={'*': {'code': [7., 2.], 'maths': [2., 4.]}}, recent={'maths': [0., 0., 1., 1., 1., 1.]})
m, note = choose(cs, 'ml', weak=True)
test_eq(m, 'code'); assert note.startswith('drill with code: 7 of 9')
test_eq({choose(cs, 'ml', rng=random.Random(i), explore='never')[0] for i in range(30)}, {'code'})
picks = [choose(cs, 'ml', rng=random.Random(i)) for i in range(60)]
assert any(n.startswith('trying maths: you got better') for _, n in picks)
test_eq(sum(m == 'code' for m, _ in picks) > 30, True) # with `sometimes`, the best method still comes most of the timemethods_text says what works, for a prompt and for the page. method_stats builds the counts again from the attempts, in order.
Count every final attempt with a method again, oldest first, and save the counts. Return the stats.
The methods with counts, best first, as one line; empty when nothing is counted.
The counts that credit_method keeps match a rebuild from the attempts.
ml_ = temp_learning(); ml_.put('Ranking', 'Order items by a score.', 'ml')
for m, k in (('code', 'solid'), ('code', 'fragile'), ('maths', 'gap')):
ml_.record('ranking', f'{m} {k}?', final=True, method=m, outcome=k); ml_.credit_method(m, 'ml', k); time.sleep(.01)
inc = ml_.stats()
test_eq(ml_.method_stats()['methods'], inc.methods)
assert methods_text(inc, 'ml').startswith('Ways that work for you: code 2 of 2')