Skip to content
academia.sh

Lesson 14 / 14

Diversity and the Echo Chamber

The narrowing of coverage and the growth of popularity bias when the system is fed by its own recommendations: over forty-eight periods the narrow setup after the eighth period never shows 8 of 24 items in any period, six items appear in none of the last twenty-four periods, and the impression share of the top five items rises from 0.4711 to 0.5461. The setup that gives the fifth slot to diversity loses 1.0000 interactions per subscriber in the first eleven periods, finds the item whose conditions changed in the twelfth period and moves ahead, breaks even cumulatively in the twenty-seventh period and finishes the forty-eighth period 1.2250 ahead.

Published Updated

In this lesson

  • Runs the feedback loop for forty-eight periods and counts the periods in which coverage narrows and popularity bias grows
  • Compares the short-term cost and the long-term gain of giving the fifth recommendation slot to diversity on the same measure
  • Distinguishes which number the echo chamber metaphor corresponds to in this setup and which it does not
Contents

The previous lesson had each setup look at the log once. In a real system the decision repeats: the item shown produces an interaction, the interaction grows the log, and the growing log determines the next impression. This lesson runs that loop for forty-eight periods and counts where the coverage goes.

  • RS57. The setup is the same block built in the first lesson; the starting log of the loop is the 3,002 impressions and 577 interactions of that setup.
  • RS58. Each period the system shows each subscriber five items and estimates the score as the interaction rate per impression of that item within the subscriber’s group: (interactions + 0.5) / (impressions + 5). The group is the dominant domain in the subscriber’s past and is the only subscriber feature the system sees.
  • RS59. Two setups are compared. The narrow setup gives all five slots to the estimate and is the baseline, since it contains no diversity rule. The wide setup gives four to the estimate and reserves the fifth for the item shown least until then.
  • RS60. In the twelfth period the response rate of one item rises eightfold and hits the 0.95 ceiling; this is the moment the environment changes, and no setup is told.
  • RS61. Three numbers are written each period: interactions per subscriber (higher is better), the number of items not shown at all that period (lower is better) and the share of impressions on the five most shown items (lower is better) — the last is the measure of popularity bias. Selection bias is in the Applied Statistics course.

The Setup Fed by Its Own Log

# COMMON -- FICTIONAL. The subscriber x service item interaction matrix is built inside the lesson.
# Impression, selection and score are generated separately: an item is first SHOWN, then
# it can be selected, and only if it is selected can it be scored.
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


ITEMS = [  # (name, domain, channel, paid, commitment, appointment, base_share)
    ("auto_payment_instruction", "billing", "online", 0, 0, 0, 0.90),
    ("e_invoice_switch", "alert", "online", 0, 0, 0, 0.85),
    ("payment_reminder_sms", "alert", "online", 0, 0, 0, 0.78),
    ("installment_plan", "billing", "branch", 0, 1, 0, 0.62),
    ("remote_reading_application", "meter", "field", 0, 1, 1, 0.55),
    ("outage_alert_subscription", "alert", "online", 0, 0, 0, 0.50),
    ("debt_restructuring", "billing", "branch", 0, 1, 0, 0.44),
    ("address_update", "subscription", "online", 0, 0, 0, 0.40),
    ("meter_replacement", "meter", "field", 1, 0, 1, 0.36),
    ("consumption_report_subscription", "alert", "online", 0, 0, 0, 0.32),
    ("invoice_dispute_record", "billing", "branch", 0, 0, 0, 0.28),
    ("subscription_transfer", "subscription", "branch", 0, 0, 0, 0.25),
    ("illicit_use_check_appointment", "meter", "field", 0, 0, 1, 0.22),
    ("installation_maintenance_package", "technical", "field", 1, 1, 1, 0.19),
    ("second_meter_request", "meter", "field", 1, 0, 1, 0.16),
    ("business_tariff_switch", "subscription", "branch", 0, 1, 0, 0.14),
    ("pressure_regulator_mounting", "technical", "field", 1, 0, 1, 0.12),
    ("garden_irrigation_tariff", "subscription", "branch", 0, 1, 0, 0.10),
    ("meter_relocation", "meter", "field", 1, 0, 1, 0.09),
    ("common_area_sharing_record", "subscription", "branch", 0, 1, 0, 0.08),
    ("installation_compliance_report", "technical", "field", 1, 0, 1, 0.07),
    ("late_interest_waiver", "billing", "branch", 0, 0, 0, 0.06),
    ("mobile_access_permission", "alert", "online", 0, 0, 0, 0.05),
    ("extra_line_installation", "technical", "field", 1, 1, 1, 0.04),
]
SEGMENT = {  # propensity of the segment toward the domains; used only inside the generator
    "saver":    {"billing": 0.5, "meter": 1.7, "subscription": 0.6, "alert": 1.5, "technical": 0.6},
    "payer":    {"billing": 1.9, "meter": 0.5, "subscription": 0.6, "alert": 1.1, "technical": 0.3},
    "business": {"billing": 0.7, "meter": 0.9, "subscription": 1.6, "alert": 0.7, "technical": 1.9},
    "new":      {"billing": 0.8, "meter": 0.7, "subscription": 1.8, "alert": 1.3, "technical": 0.7},
}
SEGMENT_NAMES = list(SEGMENT)
rq = rng(SEED + 101)
QUALITY = [0.25 + 0.70 * rq() for _ in ITEMS]   # the item's own satisfaction

N_SUBSCRIBERS, N_ITEMS = 360, len(ITEMS)
SUBSCRIBERS, IMPRESSIONS, INTERACTIONS, SCORES, PROPENSITY = [], {}, {}, {}, {}
for i in range(N_SUBSCRIBERS):
    r = rng(SEED + 7919 * i)
    a, segment = 20001 + i, SEGMENT_NAMES[int(r() * 4)]
    activity = 0.35 + 1.15 * r() ** 1.6
    SUBSCRIBERS.append(a)
    for j, (name, domain, channel, paid, commitment, appointment, share) in enumerate(ITEMS):
        propensity = min(0.95, SEGMENT[segment][domain] * share * activity * 0.55)
        PROPENSITY[(a, j)] = propensity
        if r() < min(0.95, share ** 0.55 * 0.72):        # was it shown
            IMPRESSIONS[(a, j)] = 1
            if r() < propensity:                          # was it selected
                INTERACTIONS[(a, j)] = r()                # value: sequence stamp
                if r() < 0.32:                            # did it leave a score
                    raw = (0.16 + 0.50 * SEGMENT[segment][domain] / 1.9
                           + 0.42 * QUALITY[j] + 0.26 * (r() - 0.5))
                    SCORES[(a, j)] = max(1, min(5, int(round(raw * 5))))
        else:
            r(), r(), r()
DOMAIN_OF = [ITEMS[j][1] for j in range(N_ITEMS)]
COUNTS = {a: {} for a in SUBSCRIBERS}
for (a, j) in INTERACTIONS:                         # subscriber group: dominant domain in the past
    COUNTS[a][DOMAIN_OF[j]] = COUNTS[a].get(DOMAIN_OF[j], 0) + 1
GROUP_OF = {a: (max(sorted(counts), key=lambda x: counts[x]) if counts else "none")
            for a, counts in COUNTS.items()}
PERIODS, K, SHIFT, RISING = 48, 5, 12, 9            # in period 12 the response of item 9 changes


def run_loop(wide):
    """The system is fed by its own log: it measures what it shows, it shows what it measures."""
    shown, selected = {}, {}
    for (a, j) in IMPRESSIONS:
        shown[(GROUP_OF[a], j)] = shown.get((GROUP_OF[a], j), 0) + 1
    for (a, j) in INTERACTIONS:
        selected[(GROUP_OF[a], j)] = selected.get((GROUP_OF[a], j), 0) + 1
    total, rows = [0] * N_ITEMS, []
    for d in range(1, PERIODS + 1):
        period_shown, period_inter = [0] * N_ITEMS, 0
        for a in SUBSCRIBERS:
            g = GROUP_OF[a]
            order = sorted(range(N_ITEMS),
                           key=lambda j: (-(selected.get((g, j), 0) + 0.5)
                                          / (shown.get((g, j), 0) + 5.0), j))
            top = order[:K] if not wide else order[:K - 1] + [        # fifth slot to diversity
                min((j for j in range(N_ITEMS) if j not in order[:K - 1]),
                    key=lambda j: (total[j], (j + a + d) % N_ITEMS))]
            r = rng(SEED + 660000 + a * 53 + d * 1301)
            for j in top:
                period_shown[j] += 1
                total[j] += 1
                shown[(g, j)] = shown.get((g, j), 0) + 1
                e = PROPENSITY[(a, j)]
                if j == RISING and d >= SHIFT:
                    e = min(0.95, e * 8.0)
                if r() < e:
                    period_inter += 1
                    selected[(g, j)] = selected.get((g, j), 0) + 1
        top5 = sorted(range(N_ITEMS), key=lambda j: -period_shown[j])[:5]
        rows.append((period_inter / len(SUBSCRIBERS),
                     [j for j in range(N_ITEMS) if period_shown[j] == 0],
                     sum(period_shown[j] for j in top5) / sum(period_shown),
                     period_shown[RISING]))
    return rows


NARROW = run_loop(False)
print(f"narrow setup: top {K} every period, {PERIODS} periods, {len(SUBSCRIBERS)} subscribers")
print(f"in period {SHIFT} the response rate of {ITEMS[RISING][0]} rises eightfold")
print("period  interactions/subscriber  items never shown that period  top-five impression share")
for d in (1, 8, 24, 48):
    e, never, p, n = NARROW[d - 1]
    print(f"  {d:>2}        {e:.4f}                        {len(never):>2}/{N_ITEMS}                    {p:.4f}")
LOCKED = set(range(N_ITEMS))
for x in NARROW[PERIODS // 2:]:
    LOCKED &= set(x[1])
print(f"items not shown in ALL of the last {PERIODS - PERIODS // 2} periods: {len(LOCKED)}/{N_ITEMS}")
narrow setup: top 5 every period, 48 periods, 360 subscribers
in period 12 the response rate of consumption_report_subscription rises eightfold
period  interactions/subscriber  items never shown that period  top-five impression share
   1        1.2250                         0/24                    0.4711
   8        1.3917                         8/24                    0.5461
  24        1.4583                         8/24                    0.5461
  48        1.3194                         8/24                    0.5461
items not shown in ALL of the last 24 periods: 6/24

In the first period all 24 items are shown. In the eighth period eight items drop out; the number freezes at 8/24 there and does not move until the forty-eighth period. Six items are shown in none of the last twenty-four periods. Popularity bias is the other face of the same movement: the impression share of the top five items rises from 0.4711 to 0.5461 and stays there.

The loop closes on itself. No new information is born about an item that is not shown, its estimate stays low, and the low estimate leaves it out again in the next period: the item stays out not because it is bad but because it is not measured. In the twelfth period the environment changes but the narrow setup cannot see it, because the only way to tell the system is for that item to be shown.

Giving the Fifth Slot to Diversity

WIDE = run_loop(True)
WIDE_LOCKED = set(range(N_ITEMS))
for x in WIDE[PERIODS // 2:]:
    WIDE_LOCKED &= set(x[1])
print(f"wide setup: top {K - 1} by model, fifth slot to the least shown item")
print("period  interactions/subscriber  items never shown that period  top-five impression share  shifted item")
for d in (1, 8, 24, 48):
    e, never, p, n = WIDE[d - 1]
    print(f"  {d:>2}        {e:.4f}                        {len(never):>2}/{N_ITEMS}                    "
          f"{p:.4f}            {n:>3}")
cost = sum(WIDE[d][0] - NARROW[d][0] for d in range(SHIFT - 1))
gain = sum(WIDE[d][0] - NARROW[d][0] for d in range(SHIFT - 1, PERIODS))
equal = [d + 1 for d in range(PERIODS)
         if sum(x[0] for x in WIDE[:d + 1]) > sum(x[0] for x in NARROW[:d + 1])]
print(f"short-term cost (total of periods 1-{SHIFT - 1}): per subscriber {cost:+.4f}")
print(f"long-term gain (total of periods {SHIFT}-{PERIODS}): per subscriber {gain:+.4f}")
print(f"cumulative break-even period: {equal[0] if equal else 'none'}; at period {PERIODS} the "
      f"cumulative difference is {sum(x[0] for x in WIDE) - sum(x[0] for x in NARROW):+.4f}")
print(f"the shifted item at period {PERIODS}: narrow setup {NARROW[-1][3]} impressions, "
      f"wide setup {WIDE[-1][3]}")
print(f"data collected -- narrow: {len(LOCKED)} items never shown, top-five impression share "
      f"{NARROW[-1][2]:.4f}; wide: {len(WIDE_LOCKED)} items, {WIDE[-1][2]:.4f}")
wide setup: top 4 by model, fifth slot to the least shown item
period  interactions/subscriber  items never shown that period  top-five impression share  shifted item
   1        1.1833                         0/24                    0.4611             37
   8        1.2722                         0/24                    0.5011             81
  24        1.4806                         0/24                    0.4989             97
  48        1.3889                         0/24                    0.4989             97
short-term cost (total of periods 1-11): per subscriber -1.0000
long-term gain (total of periods 12-48): per subscriber +2.2250
cumulative break-even period: 27; at period 48 the cumulative difference is +1.2250
the shifted item at period 48: narrow setup 17 impressions, wide setup 97
data collected -- narrow: 6 items never shown, top-five impression share 0.5461; wide: 0 items, 0.4989

The cost is paid in the first eleven periods: the wide setup finishes 1.0000 interactions per subscriber behind, because one of the five slots goes not to the best estimate but to the least known item. In return coverage never narrows and the share of the top five items stays at 0.4989.

In the twelfth period the account turns. Since the wide setup already shows the item whose response changed in its rotating slot, it notices the change in the same period. In the forty-eighth period that item receives 97 impressions in the wide setup and 17 in the narrow setup. The total gain between the twelfth and the forty-eighth is +2.2250 per subscriber; together with the −1.0000 cost of the first eleven periods, the cumulative difference reaches zero in the twenty-seventh period and becomes +1.2250 in the forty-eighth. Diversity is not a matter of taste but a measurement budget: a system that never measures wins as long as the environment stays constant and loses every time it changes.

“Echo chamber” is a metaphor that describes this table and has no number of its own; its counterpart is the number of items not shown and the impression share of the top five. The place where it misleads is this: in the setup no subscriber’s propensity changes. What narrows is not the interest of the subscriber but the domain the system measures, and an item that is not measured being counted as bad is not a belief but a gap in the record.

Summary

  • After the eighth period the narrow setup does not show 8 of 24 items in any period, 6 items appear in none of the last twenty-four periods, and the impression share of the top five rises from 0.4711 to 0.5461.
  • The short-term cost of diversity is −1.0000 per subscriber in the first eleven periods and its long-term gain is +2.2250 between the twelfth and forty-eighth; the cumulative difference breaks even in the twenty-seventh period.
  • The echo chamber is a metaphor; in this setup what narrows is not the interest of the subscriber but the domain the system measures.

Course Wrap-Up

The account of fourteen lessons is in a single table, and each number was read from its own lesson. The rows do not carry a common unit; the last column writes the metric and its direction. The difference is given in a single sign convention: the method’s number minus the baseline and is in parentheses in the third column. The fourth column is the course’s own number: the unseen state, the untried action, the item not shown.

Lesson Baseline Method’s number Data collected Metric
Reinforcement 01 random action 10.432 hand-written rule 20.621 (+10.189) 1,355 of 1,458 pairs unseen return, higher is better
Reinforcement 02 random action 10.432 rule with history field 23.148 (+12.716) state space from 243 to 1,215; no count return, higher is better
Reinforcement 03 agent without exploration −16.492 exploration rate 0.20 gives 25.659 (+42.151) 880 of 1,458 pairs untried return, higher is better
Reinforcement 04 rule policy 20.621 table with exploration 24.419 (+3.798) 221 of 1,458 cells not updated return, higher is better
Reinforcement 05 random action 10.432 replay buffer 26.233 / 25.302 (+15.801) 678 of 1,458 cells not updated return, higher is better
Reinforcement 06 zero weights 10.177 baseline subtraction 22.866 / 22.190 (+12.689) agreement 0.290; no count return, higher is better
Reinforcement 07 raw return 14.207 / 17.218 advantage 23.401 / 23.755 (+9.194) 0 of 5 subscribers left unvisited return, higher is better
Recommender 01 random order 0.2036 most chosen item 0.6527 (+0.4491) 15 of 24 items never recommended top-five hit rate, higher is better
Recommender 02 popularity 0.6527 content-based 0.4431 (−0.2096) 0 of 24 items not shown top-five hit rate, higher is better
Recommender 03 popularity 0.6527 user-based 0.6946 (+0.0419) 8 of 24 items not shown top-five hit rate, higher is better
Recommender 04 popularity 0.6527 skipping setup 0.7006 (+0.0479) 15 of 24 items not shown top-five hit rate, higher is better
Recommender 05 popularity fallback 0.5752 three-question intake 0.5487 (−0.0265) 11 of 24 items not shown cold-start top-five hit rate, higher is better
Recommender 06 online baseline 1.4581 light setup 1.4788 (+0.0207) 12 of 24 items not shown interactions per subscriber, higher is better
Recommender 07 narrow setup, 48 periods wide setup (+1.2250) 8 of 24 items not shown cumulative interactions, higher is better

The rule of the course is this: the measure of a system that makes decisions cannot be read without accounting for the fact that those decisions change its own data; a system that does not write down the data it collected counts as unmeasured.

The fourth column reduces to a single number: the total of the unseen pairs, the cells not updated, the subscribers left unvisited and the items not shown in the twelve lessons that print a count is 3,203 units. The units are not the same and the total is a count, not a mean: behind every performance number measured in this course stand more than three thousand units about which the method learned nothing. Reinforcement 02 and 06 print no count and do not enter the sum.

The second claim of the course is read from the same table: a number measured offline does not tell online behavior. In four lessons the two numbers diverged. In Recommender 06 the setup that was first offline with 0.6886 was last with 1.4563 when it made its own impression decisions, and fell below the baseline. In Recommender 07 the narrow setup that looked good on the log finished forty-eight periods 1.2250 behind. In Recommender 05 the hit rate read from the log fell from 0.5752 to 0.5487 once questions were asked. In Reinforcement 03 the divergence appeared in the opposite direction: the behavior of the agent with exploration rate 1.00 was bad but the data it collected was good, and the frozen table gave 23.000. There is a single source: the log the number is read from is a log whose decisions were made by another setup.

In fifty-two lessons the baseline was written beside every number, and in the last fourteen it was counted that the system changes its own data. The only thing never asked is for whom it is measured. Every number in the table is the mean over all subscribers: if the same number says two different things in two subgroups, which one is reported; what is the basis on which the system shows an item to one person and not to another, and can it be read; and with which number is the harm written that a system with a good measure does to a group that never enters the measure. The next course, Model Evaluation, Interpretability and Ethics, opens with these three questions.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close