Skip to content
academia.sh

Lesson 22 / 22

Bias-Reducing Prompts

Measuring how much of a subgroup gap prompt balancing closes and what it leaves: on 200 fictional tickets the central hit rate is 0.9016, the rural hit rate 0.5513 and the gap 0.3504. A bias-reducing notice in the prompt moves the gap to 0.3586 and adds 3800 tokens; a balanced example brings it down to 0.2888 and adds 14400 tokens. The hit rate of the sampled local spelling rises from 0.4848 to 0.8788, while the unsampled one stays fixed at 0.3023. The prompt closes only a 0.1758 share of the gap. The course wrap-up gathers the previous prompt, changed prompt and cost rows of twenty-two lessons.

Published Updated

In this lesson

  • Traces the source of a subgroup gap down to the cue table of the model and shows it with numbers.
  • Measures the bias-reducing notice and the balanced example on the same gap and separates what each closes.
  • Shows with numbers, through the unsampled spelling, why a prompt does not solve data bias.
  • Gathers the course's twenty-two lessons in one table and names the lessons whose difference stays below the two-run difference.
Contents

The previous lesson split the errors into two sources. What went unasked was who the errors were concentrated on: the overall hit rate can be acceptable while all the remaining wrong answers accumulate in one set of subscribers; for that set the system does not work, and the average hides it.

Subgroup, gap and fairness measures were established in the Model Evaluation, Interpretability and Ethics course and are not repeated. This lesson’s question is narrow: how much of a subgroup gap can a prompt close, and what part remains.

  • OC41. The code is a simulator; no real endpoint is called. Subscriber groups, ticket spellings and the cue table are fictional, seed 20260218.
  • OC42. There are two fictional subscriber groups, central and rural; the group name never enters the decision and serves only measurement. Each topic has one standard and two local spellings; central subscribers mostly write the standard one, rural subscribers mostly the local ones.
  • OC43. The source of the bias is in the data: the cue table was built only from the standard spelling, and when no cue is found the decision falls to the most frequent class.
  • OC44. The first balancer is a notice written into the prompt (the spelling does not affect the decision), which in the simulator turns the fall into an equal draw. The second balancer is the balanced example: each topic’s local A spelling goes into the prompt with its label, and the simulator extracts a temporary cue table from the examples. Local B is not sampled.
  • OC45. The measures are the topic hit rate per group and the gap (central minus rural, small is good). Cost is written as input tokens; the second run has seed 20260219.

The Source of the Gap

The gap does not come from the model knowing something about the groups; the group name is never in the prompt. Its source is which texts built the cue table.

# SIMULATOR -- FICTIONAL. No real model endpoint is called. Subscriber groups,
# ticket spellings and the cue table are FICTIONAL.
SEED, M32 = 20260218, 0xFFFFFFFF


def rng(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def next_value():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return next_value


def pick(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


def tokenize(text):                     # FICTIONAL tokenizer, a piece has at most five characters
    out = []
    for piece in text.lower().replace(",", " , ").split():
        while len(piece) > 5:
            out.append(piece[:5])
            piece = piece[5:]
        out.append(piece)
    return out


def trace_id(text):
    h = 2166136261
    for c in text:
        h = ((h ^ ord(c)) * 16777619) & M32
    return h


# each topic: one standard and two local spellings; the local ones are FICTIONAL
SPELLINGS = {
    "fatura": ["bu ayki fatura tutari gecen aya gore cok yuksek geldi",
               "bu ayki hesap pusulasi baya kabarik cikmis",
               "gecen ayki odemeye gore rakam iki misli olmus"],
    "ariza": ["evde su akmiyor bir ariza var galiba",
              "muslugu actim damla bile gelmiyor",
              "avludaki boru patlamis her yer golet"],
    "sayac": ["sayac okumasi yanlis girilmis endeks tutmuyor",
              "saatin rakamlari bizimkiyle uyusmuyor",
              "kutunun icindeki numara yerinde saymis"],
    "tarife": ["hangi tarife basamagindayim ogrenmek istiyorum",
               "bizden hangi ucretlendirmeyle para aliniyor",
               "kademeli hesaplama nasil yapiliyor anlamadim"],
    "kesinti": ["mahallede kesinti var ne zaman gelecek",
                "iki gundur su yok kimse haber vermedi",
                "koyun ustunde bir gundur akmiyor diyorlar"],
}
TOPICS = list(SPELLINGS)
# central mostly write the standard spelling, rural mostly the local ones
SPELLING_SHARE = {"merkez": [0.86, 0.08, 0.06], "kirsal": [0.24, 0.40, 0.36]}
TICKETS = []
for i in range(200):
    r = rng(SEED + i)
    group = "merkez" if r() < 0.62 else "kirsal"
    topic = TOPICS[pick(r(), [0.24, 0.22, 0.18, 0.20, 0.16])]
    j = pick(r(), SPELLING_SHARE[group])
    TICKETS.append({"id": 4001 + i, "topic": topic, "group": group, "spelling": j,
                    "text": SPELLINGS[topic][j]})

# the cue table was built ONLY from the standard spelling: this is the data bias
CUES = {}
for k in TOPICS:
    for t in tokenize(SPELLINGS[k][0]):
        CUES.setdefault(t, k)
MOST_COMMON = "fatura"                  # with no evidence it falls to the most frequent class
COUNTER = [0]


def example_table(toks):
    """Temporary cue table from the 'example: <text> -> <topic>' pieces."""
    table, buffer = {}, []
    for i, t in enumerate(toks):
        if t == "->" and i + 1 < len(toks) and toks[i + 1] in TOPICS:
            for s in buffer:
                table.setdefault(s, toks[i + 1])
            buffer = []
        elif t == "ornek":
            buffer = []
        else:
            buffer.append(t)
    return table


def call(prompt, ticket, seed=SEED):
    """Topic assignment. Without evidence it falls to the most frequent class; the
    bias-reducing notice turns this fall into an equal draw."""
    COUNTER[0] += 1
    toks = tokenize(prompt)
    table = dict(CUES)
    table.update(example_table(toks))           # examples in the prompt extend the table
    p = {k: 0.0 for k in TOPICS}
    for t in tokenize(ticket["text"]):
        if table.get(t) in p:
            p[table[t]] += 1.0
    if max(p.values()) == 0.0:                  # no cue at all
        if "esit" in toks:                      # bias-reducing notice
            return TOPICS[pick(rng((seed + 5) ^ trace_id(prompt))(), [0.2] * 5)]
        return MOST_COMMON
    return max(sorted(p), key=lambda a: p[a])


NOTICE = ("Talebin yazim bicimi karari etkilemez ; her yazim esit olcutle "
          "degerlendirilir . ")
EXAMPLES = " ".join(f"ornek: {SPELLINGS[k][1]} -> {k} ." for k in TOPICS) + " "
print(f"FICTIONAL tickets {len(TICKETS)}: central {sum(x['group'] == 'merkez' for x in TICKETS)}, "
      f"rural {sum(x['group'] == 'kirsal' for x in TICKETS)}")
local_count = {g: sum(x["group"] == g and x["spelling"] > 0 for x in TICKETS)
               for g in ("merkez", "kirsal")}
print(f"tickets with local spelling: central {local_count['merkez']}, "
      f"rural {local_count['kirsal']}")
print(f"cue table {len(CUES)} entries, all from the standard spelling")
for j, name in enumerate(("standard", "local A", "local B")):
    x = next(t for t in TICKETS if t["topic"] == "sayac" and t["spelling"] == j)
    print(f"  {name:<9} {x['text'][:42]:<43} -> {call('Talep: ' + x['text'], x)}")
FICTIONAL tickets 200: central 122, rural 78
tickets with local spelling: central 18, rural 58
cue table 49 entries, all from the standard spelling
  standard  sayac okumasi yanlis girilmis endeks tutmu  -> sayac
  local A   saatin rakamlari bizimkiyle uyusmuyor       -> fatura
  local B   kutunun icindeki numara yerinde saymis      -> fatura

Three spellings of one topic give three results. The standard spelling is read correctly; both local spellings come out as fatura (invoice), because no cue is found in the table and the decision falls to the most frequent class. 58 of the rural tickets are local, only 18 of the central ones; the error accumulates on rural subscribers. The model does not distinguish them — it only fails to recognize a text with no counterpart in its own table, and who wrote that text is a fact from the data.

What the Prompt Closes and What It Does Not

SETUPS = [("baseline", ""), ("+ notice", NOTICE), ("+ balanced example", EXAMPLES),
          ("+ both", NOTICE + EXAMPLES)]


def measure(prefix, seed=SEED):
    COUNTER[0] = 0
    by_group = {"merkez": [0, 0], "kirsal": [0, 0]}
    by_spelling = [[0, 0], [0, 0], [0, 0]]
    g = 0
    for x in TICKETS:
        prompt = prefix + f"Talep {x['id']} : {x['text']}"
        topic = call(prompt, x, seed)
        by_group[x["group"]][0] += topic == x["topic"]
        by_group[x["group"]][1] += 1
        by_spelling[x["spelling"]][0] += topic == x["topic"]
        by_spelling[x["spelling"]][1] += 1
        g += len(tokenize(prompt))
    m, ru = by_group["merkez"][0] / by_group["merkez"][1], by_group["kirsal"][0] / by_group["kirsal"][1]
    return m, ru, m - ru, [a / b for a, b in by_spelling], g


print("setup              central   rural      gap      gap B  localA  localB  input")
for name, prefix in SETUPS:
    m, ru, a, y, g = measure(prefix)
    ab = measure(prefix, SEED + 1)[2]
    print(f"{name:<18} {m:>7.4f} {ru:>7.4f} {a:>8.4f} {ab:>10.4f} {y[1]:>7.4f} "
          f"{y[2]:>7.4f} {g:>6}")
t, e = measure("")[2], measure(NOTICE + EXAMPLES)[2]
print(f"\nbaseline gap {t:.4f}, closed by the prompt {t - e:.4f} "
      f"({(t - e) / t:.4f} share), remaining {e:.4f}")
setup              central   rural      gap      gap B  localA  localB  input
baseline            0.9016  0.5513   0.3504     0.3504  0.4848  0.3023   2624
+ notice            0.9098  0.5513   0.3586     0.3457  0.4848  0.3256   6424
+ balanced example  0.9426  0.6538   0.2888     0.2888  0.8788  0.3023  17024
+ both              0.9426  0.6538   0.2888     0.2760  0.8788  0.3023  20824

baseline gap 0.3504, closed by the prompt 0.0616 (0.1758 share), remaining 0.2888

The second row best fits intuition and sits emptiest in the table. Writing “her yazim esit olcutle degerlendirilir” (every spelling is judged by an equal standard) into the prompt leaves the rural hit rate at 0.5513 and raises the gap to 0.3586; with a two-run difference of 0.0129 for the row, even this worsening of 0.0082 counts as unmeasured. The only thing measured is the cost: input tokens from 2624 to 6424. A declaration of intent adds no cue to the model.

The third row carries a real gain: the balanced example brings the gap down to 0.2888, a gain of 0.0616 against a two-run difference of 0.0000. Its cost is 14400 tokens, 6.5 times the baseline. That the examples also raise the central hit rate (from 0.9016 to 0.9426) is why the gap closes by less than the rural gain.

The last two columns explain the remainder. The hit rate of the sampled local A spelling rises from 0.4848 to 0.8788; the unsampled local B spelling does not move from 0.3023. A prompt does not solve data bias. Only a 0.1758 share of the baseline gap closed; the rest closes not with a better sentence but by building the cue table from local spellings too.

Summary

  • A subgroup gap comes not from the model recognizing the group but from which texts the cue table was built from; at baseline central is 0.9016, rural is 0.5513 and the gap is 0.3504.
  • The bias-reducing notice moves the gap to 0.3586 and adds 3800 tokens; with a two-run difference of 0.0129, this change counts as unmeasured.
  • The balanced example brings the gap down to 0.2888 and adds 14400 tokens; the 0.0616 gain is measured, but the examples also raise the central hit rate, so the gap closes by less than the rural gain.
  • The hit rate of the sampled local spelling rises from 0.4848 to 0.8788 and the unsampled spelling stays fixed at 0.3023: the prompt closes a 0.1758 share of the gap, and the remaining 0.2888 is data bias.

Course Wrap-Up

The accounting of twenty-two lessons is in a single table, and every number was read from its own lesson. The rows share no unit; the second and third columns state by which measure each row is read. The difference uses a single sign convention: the changed prompt’s number minus the previous prompt’s number, in parentheses in the third column.

Lesson Previous prompt Changed prompt Cost
Design 01 What Is a Prompt context-free prompt 0.7083 full prompt 0.7833 (+0.0750) +1797 tokens; two-run difference 0.2000 → 0.0583
Design 02 System Instruction prompt without instruction 0.4583 medium instruction 0.7500 (+0.2917) +2871 tokens; long instruction +3487 tokens (−0.0083)
Design 03 Specifying Role and Behavior baseline prompt 0.7500 role prefix 0.6833 (−0.0667) +1080 to +3240 tokens; seed band 0.1500, all four prefixes unmeasurable
Design 04 Clear and Measurable Requests ambiguous request, 4.81 distinct responses closed set 3.89 (−0.92) +15 tokens per request; unlabeled share 0.4333 → 0.2889
Design 05 Format and Length Control free response, parseable 0.0000 format notice 0.9750 (+0.9750) input 6140 → 8780, output 1392 → 2026
Design 06 Using Examples zero examples 0.8571 three examples 0.9643 (+0.1071) +4928 tokens, +44 per request
Design 07 Stating Constraints and Prohibitions prompt without notice, violation 0.2583 positive notice 0.0167 (−0.2416) +3360 tokens, 115.9 per prevented violation; a negative constraint makes the violation 0.5333
Design 08 Prompt Iteration starting prompt, test 0.3778 fifth iteration 0.9333 (+0.5555) 2640 calls; falls to 0.8111 at the eighth
Reasoning 01 Chain of Thought direct answer, calculation 0.3000 intermediate step prompt 0.8833 (+0.5833) tokens 1.82 times; unit cost 243.2 → 150.1
Reasoning 02 Self-Consistency single sample 0.7667 nine samples 0.8833 (+0.1167) calls 60 → 540, tokens 7954 → 71211, per correct answer 172.9 → 1343.6
Reasoning 03 Tree of Thoughts solution in a single prompt 0.4792 branch 3 tree 0.6250 (+0.1458) calls 48 → 795, tokens 43182; branch 4 (−0.0208)
Reasoning 04 Step-Back Prompting single-stage prompt 0.5417 two-stage prompt 0.7500 (+0.2083) calls 48 → 96, tokens 1.66 times, per correct code 198 → 237
Reasoning 05 Reason and Act action space prompt, valid field 0.1000 prompt with shortened thought 0.8000 (+0.7000) input 5300 → 8060; per valid field 645.8 → 102.1 tokens
Reasoning 06 Reasoning Models single-pass class, two-field task reasoning class (+0.1000 and +0.1667) tokens 1.51 times, per correct answer 83.9 → 110.1
Reasoning 07 Prompt Ensemble best single prompt 0.7833 majority vote of three prompts 0.8000 (+0.0167) calls 120 → 360, per correct label 61.1 → 180.7 tokens
Reasoning 08 Automatic Prompt Optimization single candidate, test 0.6750 selected from ten candidates 0.6500 (−0.0250) calls 40 → 400; validation +0.1250, optimism share 0.2500
Output 01 Sampling Parameters baseline setting 0.6417 all three narrowed together 0.8667 (+0.2250) input 5300 fixed, output 191 → 204, calls 120 → 120; run difference 0.5667 → 0.0083
Output 02 Penalties and Stop Sequences plain prompt, repetition 0.3431 penalty and stop sequence 0.0578 (−0.2853) output 4073 → 2653, input fixed; usability 0.8833 → 0.8333
Output 03 Structured Output format description, conformance 0.8000 imposed schema 1.0000 (+0.2000) input 5614 → 2974 (−2640 tokens), output 1789 → 1810
Output 04 Output Validation schema and business rule, acceptance 0.6000 one retry 0.8167 (+0.2167) +48 calls, +2947 tokens, 1.85 calls per recovery
Output 05 Prompt and Context Engineering Difference plain prompt, both correct 0.4083 rule and code table 0.8667 (+0.4584) input 1534 → 17734; instruction fixes 10 errors with 1680 tokens, information fixes 66 with 14520
Output 06 Bias-Reducing Prompts plain prompt, gap 0.3504 notice and balanced example 0.2888 (−0.0616) input 2624 → 20824; unsampled spelling fixed at 0.3023

This is the rule of the course: a prompt is a design object; a prompt changed without measurement is not an improvement but a guess. In no row did the model change: the same simulator, seed and set of fictional tickets. Only the text changed, and the fourth column states twenty-two times that every change of it has a cost.

The second claim of the course also reads from the same table, by name: reasoning techniques are expensive and do not pay off on every task. In Reasoning 01 chain of thought raised tokens 1.81 times in classification and left the difference inside the band; in Reasoning 04 the step-back prompt raised them 2.69 times on the grade task without moving the hit rate; in Reasoning 06 the reasoning class raised them 1.75 times on the single-field task and left the difference inside the band of 0.1167; in Reasoning 07 the prompt ensemble tripled calls and gained +0.0167; in Reasoning 08 the search budget grew tenfold and the test fell by 0.0250. The same reading holds outside reasoning: in Design 02 the long instruction added 3487 tokens and lost 0.0083, in Design 06 going from the third to the eighth example gained 0.0089 for 8736 tokens, in Output 03 the example-bearing description produced a difference equal to the two-run difference for 2040 tokens, and in Output 04 the second retry stays below that difference at 1366 tokens.

The third number stands alone: in three of the twenty-two lessons, the difference in the table stayed below the two-run difference of the same lesson — Design 03 (role specification), Reasoning 07 (prompt ensemble) and Reasoning 08 (automatic prompt optimization). In all three the cost column is full and the gain column empty.

Over twenty-two lessons the prompt became a design object and the cost of every change was written down. But one assumption was never questioned: that every call happens alone, immediately and without error. The number of calls was counted as a cost item; what happens to these prompts where calls travel over a network, queue up, are cut off midway, hit a rate limit and spend tokens instead of money was not asked. A prompt ensemble of three hundred sixty calls and a prompt search of two thousand six hundred forty calls were measured in a sequential loop on a single machine. The next course, Building Applications with Model APIs, opens this assumption.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close