Build your own feedback on the model's output

Quran Lab ships no scorer. What the heads are, the thresholds that ship as data, and what your app builds itself.

The SDK is an inference engine: audio in, the model's output out. It never compares a recitation with an expected ayah and never says that something was wrong. Quran Lab ships no scorer. What a learner sees as feedback is your app's work, built on what the model heard. This page says what you get and what is yours to build.

What the SDK gives you

For every take:

  • The decoded layer. What the model heard, ready to show: text, tokens (each with id, name, firstMs, peakMs, lastMs and confidence), words, and per 40 ms frame the top token with its runner-up and the margin between them. settled is the count of leading tokens that will not change any more in this take; isFinal is true on the last one.
  • The heads. The raw outputs of the network, per chunk while the take runs and for the whole take when it ends.
  • The vocabulary. model.tokens: 38 tokens. Blank, the word boundary, the 28 consonants, the ikhfa nasal, the short vowels a, i and u, the long vowels aa, ii and uu, and one token for foreign sounds.

And two files of read-only data that come with the model. The SDK never applies or reads either one:

  • The calibration (model.calibration): the thresholds Quran Lab measured for this model.
  • The reference data (model.reference): the expected units of every ayah.

Where the expected units and the rules come from: model.reference is made from Quran Lab's reference for the phonetics and tajweed rulings of the Quran, which also gives each unit its phones, its rules and where the classical books state them. The reference shows how to join it to the model's units, with a worked example.

The heads

Twelve outputs, with the same names on every platform. A session computes log_probs, inter1_log_probs, timing_log_probs, gem, attr_qalqalah and conf unless you ask for others.

Head Rate What it is
log_probs 40 ms The final CTC over the 38 tokens: what was said
inter1_log_probs 40 ms CTC from an early layer. It stays closer to the sound
inter4_log_probs 40 ms CTC from a middle layer, for diagnosis
id_log_probs 20 ms Identity CTC over the same tokens, at finer time resolution
timing_log_probs 20 ms When each sound is held: durations, and how long a lengthening lasts
gem 20 ms Gemination: single or held (shadda)
attr_tafkheem 20 ms Thin, heavy or low heavy
attr_ghunna 20 ms None, plain, geminate, ikhfa, idgham with ghunna
attr_qalqalah 20 ms None, sughra, kubra
attr_face 20 ms None, imala, tasheel, sakt
boundary 20 ms A logit per frame for where a unit begins
conf 20 ms A confidence logit per frame

The CTC heads are log-probabilities over the tokens; gem and the attr_ heads are probabilities per class; boundary and conf are logits (apply a sigmoid).

The model uses no language model anywhere: no transducer, no rescoring, no text prior. A language model quietly corrects what a reciter said, which is exactly what you do not want here.

The thresholds ship as data

calibration.json travels with the model, signed and verified like every other file, and the SDK hands it to you exactly as shipped. It belongs to one model and one vocabulary; model_id and tokens_sha256 in it say which. It holds:

  • the classes of single-unit difference a reading can test: vowel_sub, vowel_del, vowel_ins, short_to_long, long_to_short, cons_sub, cons_del and insertion;
  • four named sets of thresholds (strict, balanced, sensitive, very_sensitive), each fitted to a target false-alarm rate on correct recitation;
  • per context, fusion: the two heads the thresholds apply to (log_probs and inter1_log_probs), the weight a per class, how inter1_log_probs is prepared (the word boundary merged into blank) and the clip of the values;
  • per context and class, a threshold in nats for each named set, and a status: validated, experimental, unverified or off;
  • per context, the numbers for lengthening (madd, read from timing_log_probs), qalqalah (from attr_qalqalah), gemination (from gem) and, when the build has it, a per-word readout (word).

unverified means that the thresholds were fitted on development data and have not been read on held-out data yet, so nothing is claimed about their false-alarm rate. A build on an early channel can carry it on a class or on the madd, qalqalah and gemination numbers. Treat it as you treat experimental: usable, not promised. It never means that a held-out read passed.

A build may also come without the per-word readout: its calibration then has no word block. Check that the block is there before you read it.

The thresholds may differ from one model version to the next. Read them from the loaded model; never copy them into your code.

What your app builds itself

  1. The expected units of the ayah. model.reference is a lookup: surah and ayah in, the unit ids and the word boundaries out. It is not part of the first download; fetch it once. Its ids are those of model.tokens.
  2. The evidence at each place. For each position of the expected sequence and each single-unit alternative of a class, compute the full-sequence CTC forward log-probability of the expected sequence and of the alternative, on log_probs and again on inter1_log_probs prepared as the calibration says. The log-likelihood ratio of a head is log P(best alternative) minus log P(expected). Fuse the two heads with the class's weight, a * llr(log_probs) + (1 - a) * llr(inter1_log_probs), clip it, and compare it with the calibration's threshold for that context and class.
  3. Lengthening, qalqalah and gemination. A lengthening is counted from timing_log_probs: the span a long vowel is held against the median short-vowel span of the take, with the cuts in the calibration. Qalqalah is read from attr_qalqalah over the frames after the letter, and gemination from gem over the letter's span, each against its value in the calibration.
  4. Which set of thresholds a learner gets, and any change per class. That is your app's setting.
  5. The words a learner reads. How a difference is shown, and when, is yours.

Each SDK's docs/migrating-to-3.md describes the alignment and the ratios step by step.

Limits you should design around

From the model card, for TajweedFormer V4:

  • The calibration of this release marks gemination off and qalqalah experimental; another build may mark them otherwise, so read each status from the loaded model. The attr_ghunna and attr_tafkheem heads have no validated thresholds in this release: treat them as raw signals.
  • Lengthening of the lazim kind has too few calibration sites to be reliable, and muttasil is the weakest lengthening class.
  • Phone recordings and children's voices are the weakest case for every threshold.
  • The thresholds were fitted for the fused value of two heads. One head alone, or the greedy text, is not evidence that a learner recited something wrong.

What the licence asks of your screen

Where your app shows feedback built on the model, say so in one line: "Automatic tajweed feedback can be wrong. It does not replace a qualified teacher." Never present the model's output as a religious ruling on anyone's recitation, and never charge for the model, for access to it or for a feature it powers. Your own teaching and work stay yours.

Analytics for what you build

The SDK counts what it measures itself: takes, seconds of audio, speech and silence, latency, tokens. What it cannot know without your feedback, your app may report; nothing is sent if it does not:

session.reportRecited(1, 1, 7);                       // surah, first ayah, last ayah of this take
client.analytics.reportChecks('vowel_sub', 31, 1);    // your rule, times checked, times flagged

Both go into the day's anonymous totals and are marked on your app's page as reported by the app.