- Je verwerkt iedere trainingsrij eenmaal per epoch, inclusief de laatste kleine batch.
- Je berekent minibatch-gradiënten en houdt parameters tijdens één update consistent.
Je netwerk heeft veel trainingsvoorbeelden. Wachten op alle rijen vóór iedere update kan duur zijn; één rij geeft juist een grillige richting. Een minibatch laat een klein groepje voorbeelden samen de volgende stap bepalen.
Eén doorgang langs alle trainingsrijen heet een epoch. Deze manier van leren heet stochastic gradient descent (SGD); we gebruiken lineaire regressie om het batchgedrag te volgen. Een groter netwerk gebruikt hetzelfde principe: bereken één gemiddelde batchfout, stuur die terug en werk daarna de parameters bij.
1. Schud rijindices en neem de staart mee
Stel dat je vijf rijen hebt en batches van twee maakt. Dat geeft twee volle batches en een laatste batch met één rij: 2+2+1. Ook die laatste rij hoort bij de epoch; haar weggooien zou betekenen dat een deel van de data niet leert.
Schud de indices, zodat X en y met precies dezelfde rijselectie gekoppeld blijven. Gebruik voor deze oefening random.Random(seed).shuffle met een lokale generator. Dezelfde seed herhaalt de volgorde zonder de globale Python-toevalsreeks te veranderen.
import random
def batchindices(n, batch_size, seed=7):
if n < 0 or batch_size <= 0:
raise ValueError('Ongeldige batch')
indices = list(range(n))
random.Random(seed).shuffle(indices)
return [indices[i:i+batch_size]
for i in range(0,n,batch_size)]
print(batchindices(5, 2, 7))
# [[4, 0], [3, 1], [2]]
De slice neemt een stuk van de lijst; de laatste slice mag korter zijn. Maak batchindices met gehele n en batch_size, weiger negatieve n en niet-positieve batch_size met ValueError, en geef bij n=0 een lege lijst. Iedere geldige rijindex komt eenmaal voor.
Willekeurig schudden veronderstelt hier uitwisselbare trainingsrijen. Bij afhankelijke tijddata of een betekenisvolle volgorde kan dat de taak veranderen. Een nieuwe epoch kan een andere seed krijgen; houd die keuze reproduceerbaar.
012. Middel over de huidige batch
Het model voorspelt Xw+b. X bevat één voorbeeld per rij, w één gewicht per feature, b is de bias en y de doelvector. Noem het verschil tussen voorspelling en doel r; de MSE-gradiënt gebruikt het aantal rijen van deze batch, niet het totale aantal trainingsrijen.
Neem de eerste batch met featurewaarden [4,0], doelen [9,1] en beginwaarden w=0.5, b=1. De voorspellingen zijn [3,1], dus de fouten [−6,0]. Dan is dw gelijk aan −24 en db aan −6.
import numpy as np
def batchgradient(x, y, w, b):
x = np.asarray(x, dtype=float)
y = np.asarray(y, dtype=float)
w = np.asarray(w, dtype=float)
r = x@w + b - y
return 2*x.T@r/len(x),float(2*r.mean())
dw, db = batchgradient([[4],[0]], [9,1],
[.5], 1.)
print(dw.tolist(), db) # [-24.0] -6.0
Maak deze functie voor geldige, niet-lege batches met één of meer features. Geef (dw,db) terug, met dw als NumPy-vector met dezelfde vorm als w en db als getal; verander X, y of w niet. Verdubbel je exact dezelfde batchrijen, dan blijven de gemiddelde gradiënten gelijk; een som zonder deling zou verdubbelen.
3. De volgende batch ziet de nieuwe parameters
Met leersnelheid 0.1 wordt de eerste update w=0.5−0.1×(−24)=2.9 en b=1−0.1×(−6)=1.6. De volgende batch [3,1] gebruikt deze waarden en levert dw=11.4, db=4.8. Daarna zijn w=1.76 en b=1.12; de laatste rij brengt ze op 1.904 en 1.192.
| Rijindices | w na update | b na update |
|---|---|---|
| [4,0] | 2.9 | 1.6 |
| [3,1] | 1.76 | 1.12 |
| [2] | 1.904 | 1.192 |
x = np.arange(5.).reshape(-1,1)
y = 2*x[:,0] + 1
w, b = np.array([.5]), 1.
for rows in batchindices(5, 2, 7):
dw, db = batchgradient(
x[rows],y[rows],w,b)
w, b = w-.1*dw, b-.1*db
print(round(w[0],3), round(b,3))
# 2.9 1.6
# 1.76 1.12
# 1.904 1.192
Bereken dw en db allebei vóór je iets bijwerkt. Herbereken je db met de nieuwe w, dan hoort één update bij twee verschillende modellen. Middel de laatste gradiënt over haar eigen ene rij; delen door batch_size=2 zou die bijdrage ten onrechte halveren.
Maak sgd_epoch(x,y,w,b,eta,batch_size,seed=7) met dezelfde loop. Begin met een kopie van w en geef (new_w,new_b) terug, met een nieuwe NumPy-vector met dezelfde vorm als w en een biasgetal. Zo blijft de oorspronkelijke beginarray beschikbaar voor een eerlijke vergelijking.
def sgd_epoch(x,y,w,b,eta,batch_size,
seed=7):
x = np.asarray(x, dtype=float)
y = np.asarray(y, dtype=float)
w = np.array(w, dtype=float)
batches = batchindices(len(x),
batch_size,seed)
for rows in batches:
dw, db = batchgradient(
x[rows], y[rows], w, b)
w, b = w-eta*dw, b-eta*db
return w, b
start = np.array([.5])
eind, bias = sgd_epoch(x,y,start,1.,.1,2)
print(start.tolist()) # [0.5]
print(np.round(eind,3), round(bias,3))
# [1.904] 1.192
03Wat neem je mee?
- Iedere epoch verwerkt alle rijen, inclusief een kleinere laatste batch.
- Middel over de actuele batch en bereken alle gradiënten met dezelfde oude parameters.
- Volgende batches zien nieuwe waarden; volgorde, batchgrootte en leersnelheid beïnvloeden samen het traject.
Hierna voeg je geheugen aan die opeenvolgende zoekrichtingen toe met momentum.