Vault.ratings
def ratings(
doc_id:str=None, limit:int=None
)->fastcore.foundation.L:The feedback log for this shelf, newest first.
A vault fills up with things that match many searches and answer none: cookie banners, site footers, navigation pages, licence boilerplate. This page describes three tools for that problem. All three are off until you turn them on.
suggest_noisy, then marks documents with mark_noisy or accept_noisy.learn() is on.evals/RESULTS.md). Leave the ranker off unless it helps on your own collection.rate records one judgement about one section for one question. SIGNALS maps each signal to a label (1 good, 0 bad) and a weight.
| signal | label | weight |
|---|---|---|
up, good |
1 | 3 |
down, bad |
0 | 3 |
read: the person read the section |
1 | 1.5 |
cited: an answer cited the section |
1 | 1 |
shown: the section was offered and not cited |
0 | 0.35 |
ratings returns the log, newest first.
The feedback log for this shelf, newest first.
def rate(
q:str, # the question this judgement is about
node_id:str=None, # the section being judged
doc_id:str=None, # its document; derived from `node_id` when not given
signal:str='up', # one of SIGNALS
rank:int=None, # where it was ranked when it was shown
score:float=None, # the retrieval score it was shown with
ask_id:str=None, # groups the rows from one question together
)->dict:Record one judgement about one section for one question.
ask returns the sections its answer cited. After learn(), every ask calls log_ask. log_ask writes the cited sections to the log as positives and the other sections as weak negatives. An answer that cites nothing writes nothing, unless the caller passes up or down. The ranker and the document prior learn from these rows.
Write feedback rows for one answered question from what ask returned; learn() does this for every ask.
doc_prior turns the log into a multiplier on the retrieval score of each document. It models the usefulness of a document as a Beta distribution. The weighted wins and losses of the document update the distribution. A document with little evidence stays near neutral. A document cited once does not move far ahead.
gamma sets how strongly the prior changes a score. explore is the chance, from 0 to 1, that doc_prior samples from the distribution (Thompson sampling) and does not use its mean. Sampling keeps documents with little evidence in play. lo and hi clamp every multiplier, so no amount of evidence can bury a document.
def doc_prior(
gamma:float=0.35, # how hard the prior is allowed to push; 0 disables it
explore:float=1.0, # 1 -> Thompson sample the posterior, 0 -> use its mean
lo:float=0.6, hi:float=1.6, # clamp, so no amount of evidence can bury a document
seed:int=None, exclude_ask:NoneType=None, # ignore these questions' rows; for an out-of-fold prior
stats:dict=None, # `{doc_id: (wins, losses)}` to use instead of reading the log
)->dict:Each document’s multiplier on its retrieval score, from a Beta distribution over its feedback, as {doc_id: multiplier}.
Each document’s weighted wins and losses in this shelf’s feedback log, as {doc_id: (wins, losses)}.
suggest_noisy ranks the documents that look most like junk. It leaves out each document that a person has already judged for noise. mark_noisy records the decision. search, sections, context and ask leave out a document marked as noise. accept_noisy marks the current top suggestions in one call. mark_noisy_many marks a list of documents.
The features and the Ranker come from litesearch, which computes them from the chunk store alone. The 0.988 AUC is measured there. The features include these:
| feature | what it measures |
|---|---|
hub |
how often a chunk appears among the nearest neighbours of other chunks |
spread_chunk |
how evenly a chunk sits between topics |
off_centre |
how far a chunk is from the middle of the collection |
dup_out |
how much of a chunk is duplicated in other documents |
This page adds the vault’s side. It adds the documents that a person marked and the questions each document was retrieved for (promiscuity). It also adds the rule that the score only suggests.
(ids, doc_ids, texts, V) for this shelf: litesearch holds the vectors, this names the store.
Score every document for noise, most suspicious first, with the features behind each score.
litesearch’s noise features for this shelf’s documents, with promiscuity taken from the vault’s feedback log.
Mark the current top k noise suggestions as noise.
Mark many documents as noise at once, for example a reviewed suggest_noisy list.
The k documents most likely to be noise, leaving out those a person has already judged.
The fixed weights need no labels. After a person marks a few documents as noisy and a few as worth keeping, fit_noise fits the weights to those marks. On held-out documents the fitted weights scored 0.996 AUC. They were fitted from six noisy documents and eighteen confirmed keepers (evals/RESULTS.md). A fitted blend changes nothing until you call use_noise(True).
The ranker is a pairwise linear model trained on the feedback log. For each question it learns which of two sections ranks higher. fit_ranker(save=True) fits the ranker and stores it. use_ranker(True) is a separate call. It makes context use the ranker, and ask uses context. The ranker is off by default. In the measurements no learned reranker beat plain reciprocal rank fusion in a repeatable way.
PAIR_FEATURES are the signals that exist when a section is retrieved. They are the retrieval score and rank, the gap to the top score, the words the section shares with the question (raw and weighted by rarity), the cosine similarity to the question, the section length, the document prior, the kind and age of the document, and the noise features. The method is in evals/.
The design matrix for one question’s hits, in PAIR_FEATURES order.
Logging, fitting and switching on are separate calls. Nothing changes retrieval until you call use_ranker(True). learn() starts the log. training_data turns the log into rows grouped by question. fit_ranker and fit_noise fit the models. use_ranker and use_noise switch the stored models on. use_noise affects only suggest_noisy. evals/ holds the measurements on whether a collection should use a ranker.
Reorder hits by baseline reciprocal rank fusion plus the ranker’s order, weighted by alpha. At alpha=0.25 nDCG@10 changed by -0.001. Replacing the baseline order with the ranker’s order changed it by -0.127.
Switch the stored ranker on (or off) in context, and so in ask.
Switch the stored noise weights on (or off) for suggest_noisy.
Fit the noise weights to the documents a person marked; save=True stores them, and use_noise(True) switches them on.
Fit a ranker on the feedback logged so far; save=True stores it without switching it on.
Turn the feedback log into (X, groups, y, weights) for the ranker, dropping questions whose rows are all one label.
Start (or with on=False stop) logging every ask as feedback; nothing is logged until this is called.
The cells below only check that the parts connect, on a small made-up collection; they measure nothing. The measurements are in evals/.