Werkplaats / De werkplaats
In jouw eigen tempo
← Taalmodellen
MODULE 06 · LES 18

Leer een voorkeursscore en begrens de beleidsverandering

Reken Bradley-Terry-rewardleren en een exact oplosbaar KL-geregulariseerd beleidsprobleem door, inclusief volledige gradiëntcontrole.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt menselijke voorkeuren, een geleerd rewardmodel en het generatiebeleid.
  • Je berekent de afweging tussen verwachte reward en afwijking van een referentiebeleid.

Twee antwoorden kunnen allebei vloeiend klinken, terwijl een beoordelaar het ene duidelijk behulpzamer vindt. Hoe maak je van zo’n vergelijking een leersignaal? Bij reinforcement learning from human feedback (RLHF) leert een scoremodel voorkeuren benaderen, waarna het generatiebeleid wordt aangepast.

Een gebruikelijke route begint met SFT, leert daarna een rewardmodel uit vergelijkingen en optimaliseert vervolgens het beleid, bijvoorbeeld met PPO. Een geleerde voorkeursscore is geen universele menselijke waarde of garantie op waarheid. Hier rekenen we een eindig speelgoedprobleem exact uit; we bouwen geen volledige PPO-trainingslus.

1. Leer een relatief scoreverschil

Stel dat een beoordelaar antwoord A kiest boven B. In het Bradley-Terry-model voorspelt sigmoid(rA−rB) hoe waarschijnlijk die keuze is. Alleen het verschil telt: dezelfde constante bij beide scores optellen verandert niets.

Neem rA=log(3) en rB=0. De kans wordt 3/(1+3)=.75, dus de negatieve logkans is −log(.75)=.287682. Gradient descent moet de gekozen score verhogen en de afgewezen score verlagen.

chosen = np.array([np.log(3)])
rejected = np.array([0.])
loss,gc,gr = preference_loss(
    chosen,rejected)
print(round(loss,4))  # .2877
print(gc,gr)  # [-.25] [.25]

Maak preference_loss met een gemiddelde over de voorkeurparen. Bereken de loss stabiel met softplus, bijvoorbeeld via logaddexp. De twee scoregradiënten hebben tegengesteld teken; absolute scores zijn uit alleen één vergelijking niet vastgelegd.

In echte voorkeurdata kunnen beoordelaars het oneens zijn. Bewaar beoordelingsinstructies en informatie over de verzamelde populatie. Anders leert een technisch correct rewardmodel misschien een ander criterium dan je bedoelt.

01
CHECKPOINTEen vergelijking hangt af van het scoreverschil

2. Geef afwijken van de referentie een prijs

Een beleid π geeft kansen aan mogelijke antwoorden. We willen een hoge verwachte reward, maar begrenzen verandering met een KL-term ten opzichte van een vaste referentie ρ. Dit is een zachte prijs, geen harde inhoudelijke beveiligingsgrens.

J=∑iπiri−βKL(π∥ρ)

r is de rewardvector, i een antwoord en β>0 de prijs van verschuiving. We schrijven π als softmax van logits. J wordt gemaximaliseerd: voor zijn logitgradiënt gebruik je gradient ascent, of je minimaliseert −J.

Bereken eerst de hulpgrootheid g per antwoord. De softmax-kettingregel trekt daarna het policygewogen gemiddelde van g af. z zijn de logits; j doorloopt alle antwoorden in de som.

gi=ri−β(logπiρi+1)∂J∂zi=πi(gi−∑jπjgj)

Neem referentie [.5,.5], rewards [0,log(3)] en β=1. Met gelijke logits is π gelijk aan de referentie, KL nul en J=.5log(3)=.549306. De logitgradiënt is ongeveer [−.274653,.274653].

ref = np.array([.5,.5])
rewards = np.array([0.,np.log(3)])
j,gradient,p = policy_objective(
    np.zeros(2),rewards,ref,1.)
print(round(j,4))  # .5493
print(np.round(gradient,4))
# [-.2747 .2747]

Maak policy_objective met de opgegeven positieve referentiekansen. De gradiënten sommeren tot nul: dezelfde constante bij alle logits verandert softmax niet. Controleer J en de afgeleide met een onafhankelijk handvoorbeeld, naast numerieke verschillen.

REWARD MET EEN PRIJS VOOR VERSCHUIVINGWelke antwoordkansen zijn optimaal?

Drie verzonnen antwoordopties en scores. Exact optimum van E[reward] − β KL(π || πref).

Een hogere score betekent hier alleen een hogere opgegeven reward.
AntwoordRewardReferentieOptimum
Kort antwoord1.040%30.07%
Uitgebreid antwoord0.040%7.21%
Antwoord uitstellen2.020%62.73%
Kort antwoordUitgebreid antwoordAntwoord uitstellen
Verwachte reward1.555KL in nats0.508Doelfunctie J1.200

De referentie heeft kansen 40%, 40%, 20%. Een fout in de reward wordt meegeoptimaliseerd; dit is geen beoordeling van echte antwoordkwaliteit.

02
CHECKPOINTBeloon gedrag en bereken de prijs van afwijking

3. Reken het eindige optimum uit

Voor dit probleem is de optimale kans evenredig met ρi×exp(ri/β). Ons voorbeeld krijgt ongenormaliseerde gewichten [.5,1.5], dus optimum [.25,.75]. Gelijke rewards zouden juist de oorspronkelijke referentie behouden, niet vanzelf een uniforme policy maken.

best = optimal_policy(rewards,ref,1.)
print(best)  # [.25 .75]
j,gradient,_ = policy_objective(
    np.log(best),rewards,ref,1.)
print(round(j,4))  # .6931
print(np.allclose(gradient,0.))  # True

Bij dit optimum is de verwachte reward ongeveer .823959 en KL ongeveer .130812; het verschil is .693147. Maak optimal_policy in logruimte. Een grote β houdt de policy dichter bij de referentie; een kleine β geeft reward meer invloed.

Een veelgemaakte fout is een hoge reward verwarren met goede inhoud. Een scoremodel dat lengte te sterk waardeert kan steeds langere antwoorden uitlokken. Houd menselijke en taakgerichte evaluaties apart; ons exact oplosbare somvoorbeeld vervangt die niet.

03
CHECKPOINTHet exacte optimum laat de rol van de referentie zien

Wat neem je mee?

  • Een voorkeurspaar leert een relatief scoreverschil.
  • Het beleidsdoel weegt reward af tegen afwijking van een referentie.
  • De juistheid van de berekening garandeert niet de kwaliteit van de beloning.

Hierna optimaliseer je voorkeuren direct vanuit responslogkansen met DPO.

DE VOLGENDE STAPOptimaliseer voorkeuren direct vanuit responslogkansen
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
Een vergelijking hangt af van het scoreverschil

Schrijf preference_loss(chosen,rejected) met (mean_loss,grad_chosen,grad_rejected). Beide arrays zijn niet-lege evenlange eindige 1D-scorevectoren. Anders ValueError. Middel over het aantal paren.

Een hint

dL/ddifference=−sigmoid(−difference)/N. exp(−logaddexp(0,difference)) berekent sigmoid(−difference) stabiel.

02
Beloon gedrag en bereken de prijs van afwijking

Schrijf policy_objective(logits,rewards,reference,beta) met (objective,gradient_logits,policy). Gebruik evenlange eindige 1D-vectoren; reference is strikt positief en sommeert tot 1 binnen 1e-10, beta is positief en eindig. Anders ValueError.

Een hint

Bereken log-softmax rechtstreeks; log(softmax) kan bij zeer kleine kansen onnodig onderlopen. Trek het gewogen gemiddelde van g af voor de softmax-terugweg.

03
Het exacte optimum laat de rol van de referentie zien

Schrijf optimal_policy(rewards,reference,beta) met de optimale kansvector. Gebruik dezelfde validatie als hiervoor. Bereken in logruimte en trek maxima af voor stabiliteit.

Een hint

Begin met log(reference)+(rewards−max(rewards))/beta. Softmax normaliseert de resulterende scores.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def preference_loss(chosen,rejected):
    chosen=np.asarray(chosen,float);rejected=np.asarray(rejected,float)
    if chosen.ndim!=1 or not chosen.size or chosen.shape!=rejected.shape or not np.isfinite(chosen).all() or not np.isfinite(rejected).all():
        raise ValueError('Ongeldige rewards')
    difference=chosen-rejected
    loss=float(np.logaddexp(0.,-difference).mean())
    derivative=-np.exp(-np.logaddexp(0.,difference))/chosen.size
    return loss,derivative,-derivative

def policy_objective(logits,rewards,reference,beta):
    scalars=[np.asarray(v) for v in [beta]]
    if any(v.ndim!=0 or v.dtype.kind not in 'biuf' for v in scalars):
        raise ValueError('Gebruik eindige scalaire getallen')
    beta,=map(float,scalars)
    logits=np.asarray(logits,float);rewards=np.asarray(rewards,float);reference=np.asarray(reference,float)
    if logits.ndim!=1 or not logits.size or logits.shape!=rewards.shape or logits.shape!=reference.shape or any(not np.isfinite(a).all() for a in [logits,rewards,reference]) or np.any(reference<=0) or not np.isclose(reference.sum(),1,atol=1e-10,rtol=0) or not np.isfinite(beta) or beta<=0:
        raise ValueError('Ongeldige policy of beta')
    with np.errstate(over='ignore'):
        shifted=logits-logits.max()
    logp=shifted-np.log(np.exp(shifted).sum());p=np.exp(logp)
    active=p>0;ratio=logp[active]-np.log(reference[active])
    objective=float(np.sum(p[active]*(rewards[active]-beta*ratio)))
    upstream=rewards[active]-beta*(ratio+1)
    grad=np.zeros_like(p);grad[active]=p[active]*(upstream-np.sum(p[active]*upstream))
    return objective,grad,p

def optimal_policy(rewards,reference,beta):
    scalars=[np.asarray(v) for v in [beta]]
    if any(v.ndim!=0 or v.dtype.kind not in 'biuf' for v in scalars):
        raise ValueError('Gebruik eindige scalaire getallen')
    beta,=map(float,scalars)
    rewards=np.asarray(rewards,float);reference=np.asarray(reference,float)
    if rewards.ndim!=1 or not rewards.size or rewards.shape!=reference.shape or not np.isfinite(rewards).all() or not np.isfinite(reference).all() or np.any(reference<=0) or not np.isclose(reference.sum(),1,atol=1e-10,rtol=0) or not np.isfinite(beta) or beta<=0:
        raise ValueError('Ongeldige rewards of referentie')
    scores=np.log(reference)+(rewards-rewards.max())/beta;scores-=scores.max()
    p=np.exp(scores)
    return p/p.sum()

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