Werkplaats / De werkplaats
In jouw eigen tempo
← AI in de wereld
MODULE 05 · LES 10

Onderscheid een beloningsproxy van het bedoelde doel

Kies acties op een expliciete proxytabel en meet hun doelresultaat en doelregret op dezelfde contexten en achtergrondgewichten.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt een beloningssignaal, een gedeclareerd doel en de keuze van contextgewichten.
  • Je meet doelregret van proxyacties en beperkt conclusies tot de opgegeven oefentabellen.

Een helpdeskassistent krijgt punten voor snel afgesloten tickets. Hij leert ieder ticket meteen te sluiten, terwijl sommige klanten nog geen oplossing hebben. De score stijgt en het bedoelde resultaat verslechtert: waar ging de opdracht mis?

Denk aan een meetlat die iets anders meet dan je wilt weten. Een makkelijk te tellen beloningssignaal is een proxy voor een doel. Beter optimaliseren helpt alleen wanneer die meetlat het gewenste gedrag voldoende vertegenwoordigt.

Alignment gaat over het afstemmen van systeemgedrag op bedoelingen en waarden. Wiens bedoelingen tellen, hoe je tegenstrijdige wensen afweegt en wie gevolgen ondervindt, blijven menselijke ontwerpvragen. In deze oefening vul je daarom zelf zichtbare proxy- en doelscores in; er bestaat hier geen verborgen universeel juiste waarde.

1. Welke actie wint volgens de meetlat?

Een context is een soort ticket; een actie is een mogelijke aanpak. De rijen van je tabel zijn contexten en de kolommen acties. Alle getallen zijn verzonnen scores tussen nul en één, waarbij groter beter betekent.

Actie nul is direct afsluiten; actie één is eerst zorgvuldig oplossen. In context nul blijft een probleem bestaan wanneer je meteen afsluit. In context één was afsluiten wel passend, waardoor dezelfde actie daar een hoge doelscore krijgt.

| Context | Proxy: actie 0 / 1 | Doel: actie 0 / 1 | |---|---|---| | Probleem nog open | 0,9 / 0,6 | 0,2 / 0,8 | | Afsluiten past | 0,9 / 0,8 | 0,9 / 0,7 |

VERKEN HET ZELFEen hoge proxyscore kan een laag doelresultaat geven

Kies de meetlat en verander welke context zwaar meetelt. De proxyacties blijven bepaald door de scoretabel; het doelregret meet wat die keuze op de doeltabel laat liggen.

De proxy kiest steeds actie 0; in de eerste context mist zij het bedoelde resultaat.

Proxyscore
0,9
Doelresultaat
0,55
Beste doelresultaat
0,85
Gemist doelresultaat
0,3
00,51C00,6Context 0: gemist doelresultaat 0,6, gewicht 0,5C10Context 1: gemist doelresultaat 0, gewicht 0,5Gemiste doelscore (0–1)

Gewogen doelregret: 0,3. Het verschil vergelijkt twee acties op dezelfde doeltabel. De gestreepte lijn toont het gemiddelde.

Context 0Gewicht 0,5

Dezelfde acties, twee gedeclareerde meetlatten
ActieProxyDoelKeuze
00,90,2proxy
10,60,8doel

Doel bij proxyactie: 0,2. Beste doel: 0,8. Gemist: 0,6.

Context 1Gewicht 0,5

Dezelfde acties, twee gedeclareerde meetlatten
ActieProxyDoelKeuze
00,90,9proxydoel
10,80,7

Doel bij proxyactie: 0,9. Beste doel: 0,9. Gemist: 0.

Doelregret is geen proxyscore min doelscore. Het vergelijkt de gekozen en beste actie binnen dezelfde doeltabel. Bij gelijke scores bepaalt de laagste index de keuze; een contextgewicht van nul verwijdert de context niet.

Alle tabellen zijn vooraf ingevulde oefenaannames. Een doelscore is hier beschikbaar omdat de oefenauteur haar opgeeft; dit bewijst geen universele waarde, causaliteit of alignment van een echt systeem.

Noem een proxyscore r_ij voor context i en actie j. De gekozen actie a_i krijgt de grootste proxyscore in die rij. Bij een gelijke score kiezen we steeds de laagste kolomindex; dat is een vaste afspraak, geen morele rangorde.

aiproxy=argmaxjrij
proxy = [[.9,.6],[.9,.8]]
goal = [[.2,.8],[.9,.7]]
actions = best_actions(proxy)
print(actions.tolist())
# [0, 0]
01
CHECKPOINTKies per context volgens de opgegeven score

2. Meet de gekozen acties op het bedoelde resultaat

Beoordeel nu dezelfde acties met de doeltabel. In de eerste rij levert actie nul 0,2 op en in de tweede 0,9. Hun gewone gemiddelde is 0,55; de hoge proxyscore van 0,9 vertelde dit resultaat niet.

Voor een gewogen gemiddelde geef je elke context een niet-negatief gewicht w_i. Delen door de totale gewichtssom geeft α_i; alle α-gewichten tellen samen op tot één. Zonder gewichten telt iedere context even zwaar.

αi=wi∑kwkV=∑iαisiai

De letter s staat voor de scoretabel die je beoordeelt: hier het doel. De actie-index a_i blijft gelijk wanneer je die tabel wisselt. Anders zou je verschillende gedragingen vergelijken.

report = policy_returns(goal,actions)
print(report['selected'].tolist())
print(round(report['mean'],3))
# [0.2, 0.9]
# 0.55
02
CHECKPOINTBeoordeel één vaste actie per context

3. Hoeveel doelresultaat laat de proxykeuze liggen?

Een bruikbare vergelijking blijft binnen de doelschaal. In context nul is de beste doelscore 0,8, terwijl de proxyactie 0,2 behaalt: er blijft 0,6 liggen. In context één kiest de proxy ook de beste doelactie, dus daar is het verlies nul.

Dit verschil heet hier doelregret: het gemiste doelresultaat ten opzichte van de beste beschikbare actie in dezelfde rij. Noem de doeltabel q en het regret g_i. Middel die verschillen daarna met dezelfde contextgewichten.

gi=maxjqij−qiaiproxyG=∑iαigi
gap = specification_gap(proxy,goal)
print(gap['goal_actions'].tolist())
print(round(gap['best_goal_mean'],3))
print(round(gap['mean_regret'],3))
# [1, 0]
# 0.85
# 0.3
03
CHECKPOINTMeet gemist doelresultaat van de proxykeuze

Een goed gemiddelde kan een probleem verbergen

Geef de eerste context een kwart van het gewicht en de tweede drie kwart. Het gemiddelde doelregret wordt 0,15. Geef de eerste context gewicht nul en het gemiddelde wordt zelfs nul, terwijl haar ongunstige keuze niet uit de tabel verdwijnt.

weighted = specification_gap(
    proxy,goal,[.25,.75])
focused = specification_gap(
    proxy,goal,[0.,1.])
print(round(weighted['mean_regret'],3))
print(round(focused['mean_regret'],3))
first = float(focused['regrets'][0])
print(round(first,1))
# 0.15
# 0.0
# 0.6

Doelregret is niet proxyscore min doelscore. Je vergelijkt twee acties op dezelfde doeltabel, met dezelfde contextgewichten. De tabel en gewichten zijn aannames; in de echte wereld is het bedoelde resultaat vaak juist moeilijk te meten.

Menselijke feedback kan een trainingssignaal helpen vormen, zoals bij RLHF. De gekozen beoordelaars en hun voorkeuren blijven daarbij van belang; een hoge beloning bewijst geen universele waarden of foutloos gedrag. Onze tabel optimaliseert geen taalmodel en beoordeelt geen echte klanten.

Wat je nu kunt

  • Je maakt onderscheid tussen een beloningsproxy en het bedoelde resultaat.
  • Je beoordeelt één vaste keuze op expliciete tabellen en contextgewichten.
  • Je berekent doelregret zonder een goed gemiddelde als algemene garantie te zien.

Hierna houd je nut en mogelijke schade apart en onderzoek je een grens op alle bekende scenario’s.

DE VOLGENDE STAPBewaar bekende scenario’s en kies binnen een expliciete grens
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Kies per context volgens de opgegeven score

Schrijf best_actions(scores). Gebruik de gegeven _al_scores. Scoretabelvorm is N×A met 1–64 contexten en 2–8 acties; alle geconverteerde waarden binnen 0–1. Arrays worden via np.asarray beoordeeld: dtype i/u/f, geen pure bool/object/complex; gemengde bool/numerieke reeksen kunnen numeriek worden. Controleer eindigheid en bounds ook na veilige floatconversie. Geef een zelfstandige 1D-integerarray met N actie-indices: grootste score per rij, bij gelijke scores de laagste kolomindex. Optimaliseer uitsluitend de aangeleverde tabel, zonder RNG of training. Behoud invoerwaarden, vormen, volgorde, readonlyflags en de globale Python-/NumPy-RNG. Geef zelfstandige uitvoerarrays/containers; ongeldige invoer geeft ValueError.

Een hint

Doorloop per rij de acties; behoud bij een gelijke score de eerste index.

02
Beoordeel één vaste actie per context

Schrijf policy_returns(scores,actions,weights=None). Gebruik _al_scores, _al_actions, _al_weights en _al_mean. Scoretabelvorm is N×A met 1–64 contexten en 2–8 acties; alle geconverteerde waarden binnen 0–1. Arrays worden via np.asarray beoordeeld: dtype i/u/f, geen pure bool/object/complex; gemengde bool/numerieke reeksen kunnen numeriek worden. Controleer eindigheid en bounds ook na veilige floatconversie. Actions is na np.asarray een 1D-vector met N gehele indices 0 tot A-1, dtype i/u; pure bool/float/object ongeldig, gemengde bool/int kan integer worden. Weights is None voor uniform of een 1D-vector met N eindige geconverteerde waarden binnen 0–1 en positieve som. Normaliseer naar som één. Alle gedeclareerde contexten blijven in de losse uitvoer aanwezig, ook bij gewicht nul. Geef een dict met selected (zelfstandige floatarray vorm N met scores[i,actions[i]]), weights (zelfstandige genormaliseerde floatarray vorm N) en mean (Python-float, gewogen gemiddelde van selected). Gebruik de gegeven helper voor het gewogen floatgemiddelde; wegens afronding begrenst die het op minimum/maximum van de gemeten vector. Behoud invoerwaarden, vormen, volgorde, readonlyflags en de globale Python-/NumPy-RNG. Geef zelfstandige uitvoerarrays/containers; ongeldige invoer geeft ValueError. Dezelfde acties blijven vast wanneer je een andere scoretabel beoordeelt; de data bewijzen geen echt doel of populatieverdeling.

Een hint

Selecteer eerst de score bij iedere opgegeven actie en weeg daarna de contexten.

03
Meet gemist doelresultaat van de proxykeuze

Schrijf specification_gap(proxy,goal,weights=None). Gebruik _al_scores, _al_weights en _al_mean. Proxy/goal hebben exact dezelfde geldige tabelvorm. Scoretabelvorm is N×A met 1–64 contexten en 2–8 acties; alle geconverteerde waarden binnen 0–1. Arrays worden via np.asarray beoordeeld: dtype i/u/f, geen pure bool/object/complex; gemengde bool/numerieke reeksen kunnen numeriek worden. Controleer eindigheid en bounds ook na veilige floatconversie. Weights is None voor uniform of een 1D-vector met N eindige geconverteerde waarden binnen 0–1 en positieve som. Normaliseer naar som één. Alle gedeclareerde contexten blijven in de losse uitvoer aanwezig, ook bij gewicht nul. Kies per context proxy_actions en goal_actions door argmax met laagste-index-ties. Geef een dict met proxy_actions/goal_actions (zelfstandige integerarrays vorm N), goal_at_proxy/best_goal/regrets/weights (zelfstandige floatarrays vorm N), en proxy_mean/goal_mean/best_goal_mean/mean_regret (Python-floats). Goal_at_proxy is goal[i,proxy_actions[i]]; best_goal is de hoogste doelscore in iedere rij; regrets=best_goal-goal_at_proxy. Proxy_mean beoordeelt de proxyacties op proxy, goal_mean op goal; best_goal_mean en mean_regret wegen hun genoemde vectors. Gebruik de gegeven helper voor het gewogen floatgemiddelde; wegens afronding begrenst die het op minimum/maximum van de gemeten vector. Behoud invoerwaarden, vormen, volgorde, readonlyflags en de globale Python-/NumPy-RNG. Geef zelfstandige uitvoerarrays/containers; ongeldige invoer geeft ValueError. Geen proxy-minus-doelscore als regret gebruiken. De tabellen zijn vooraf gedeclareerde oefenaannames, geen training, empirische causaliteit, universele waarde of algemene alignmentgarantie.

Een hint

Kies op de proxy; vergelijk vervolgens twee acties binnen dezelfde doeltabel.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

# Jouw functies

def best_actions(scores):
    values = _al_scores(scores)
    return np.argmax(values, axis=1).astype(np.int64, copy=True)

def policy_returns(scores, actions, weights=None):
    values = _al_scores(scores)
    actions = _al_actions(actions, len(values), values.shape[1])
    weights = _al_weights(weights, len(values))
    selected = values[np.arange(len(values)), actions].copy()
    return {'selected': selected, 'weights': weights.copy(), 'mean': _al_mean(selected, weights)}

def specification_gap(proxy, goal, weights=None):
    proxy = _al_scores(proxy)
    goal = _al_scores(goal, proxy.shape)
    weights = _al_weights(weights, len(proxy))
    rows = np.arange(len(proxy))
    proxy_actions = np.argmax(proxy, axis=1).astype(np.int64, copy=True)
    goal_actions = np.argmax(goal, axis=1).astype(np.int64, copy=True)
    goal_at_proxy = goal[rows, proxy_actions].copy()
    best_goal = goal[rows, goal_actions].copy()
    regrets = best_goal - goal_at_proxy
    return {'proxy_actions': proxy_actions, 'goal_actions': goal_actions, 'goal_at_proxy': goal_at_proxy, 'best_goal': best_goal, 'regrets': regrets, 'weights': weights.copy(), 'proxy_mean': _al_mean(proxy[rows, proxy_actions], weights), 'goal_mean': _al_mean(goal_at_proxy, weights), 'best_goal_mean': _al_mean(best_goal, weights), 'mean_regret': _al_mean(regrets, weights)}

# Gegeven helpers

def _al_scores(value, shape=None):
    raw = np.asarray(value)
    if raw.ndim != 2 or not 1 <= raw.shape[0] <= 64 or (not 2 <= raw.shape[1] <= 8) or (raw.dtype.kind not in 'iuf') or (not np.isfinite(raw).all()):
        raise ValueError('Een tot 64 contexten en twee tot acht numerieke acties vereist')
    with np.errstate(over='ignore', invalid='ignore'):
        values = raw.astype(float, copy=True)
    if not np.isfinite(values).all() or np.any(values < 0) or np.any(values > 1) or (shape is not None and values.shape != shape):
        raise ValueError('Gelijke scorevormen en geconverteerde waarden binnen 0–1 vereist')
    return values

def _al_actions(value, rows, options):
    raw = np.asarray(value)
    if raw.ndim != 1 or len(raw) != rows or raw.dtype.kind not in 'iu' or np.any(raw < 0) or np.any(raw >= options):
        raise ValueError('Een geldige gehele actie-index per context vereist')
    return raw.astype(np.int64, copy=True)

def _al_weights(value, rows):
    if value is None:
        return np.ones(rows, dtype=float) / rows
    raw = np.asarray(value)
    if raw.ndim != 1 or len(raw) != rows or raw.dtype.kind not in 'iuf' or (not np.isfinite(raw).all()):
        raise ValueError('Een eindig numeriek gewicht per context vereist')
    with np.errstate(over='ignore', invalid='ignore'):
        weights = raw.astype(float, copy=True)
    if not np.isfinite(weights).all() or np.any(weights < 0) or np.any(weights > 1) or (float(np.sum(weights)) <= 0):
        raise ValueError('Geconverteerde gewichten binnen 0–1 met positieve som vereist')
    return weights / np.sum(weights)

def _al_mean(values, weights):
    return float(np.clip(weights @ values, float(np.min(values)), float(np.max(values))))

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.