- Je zet een logit om in een kans en berekent stabiele log-loss.
- Je traint logistische regressie zelf en gebruikt een echte bibliotheekpipeline.
Een spamfilter moet uiteindelijk kiezen: blokkeren of doorlaten. Maar vóór die keuze kan het model schatten hoe waarschijnlijk spam is. Logistische regressie (logistic regression) leert zo'n kans voor een binaire klasse: hier betekent label 1 spam en label 0 gewone mail.
Je begint met de bekende rechte score w maal x plus b. Die score mag negatief of groter dan één zijn; een kans niet. Een S-vormige omzetting drukt de score daarom naar een waarde tussen nul en één, waarna een aparte beslisdrempel de actie bepaalt.
1. Buig een score om naar een kans
Noem de lineaire score z, de logit. De sigmoid geeft kans p op klasse 1. Bij z = 0 is de noemer 2, dus p = 0.5; een positieve z geeft meer dan een halve kans.
e is hier het getal ongeveer 2.718, niet een foutvariabele. math.exp(a) berekent e tot de macht a. Bij z = ln(3) is exp(−z) gelijk aan 1/3, zodat p = 3/4 = 0.75; ln is de natuurlijke logaritme, de inverse van exp.
Bij sterk negatieve z zou exp(−z) te groot worden. Vermenigvuldig teller en noemer dan met exp(z): je krijgt exp(z)/(1+exp(z)). Beide rekenpaden gebruiken zo uitsluitend niet-positieve exponenten.
import math
def sigmoid(z):
if z >= 0:
return 1 / (1 + math.exp(-z))
a = math.exp(z)
return a / (1 + a)
print(sigmoid(0)) # 0.5
print(sigmoid(math.log(3))) # 0.75
print(sigmoid(-1000)) # 0.0
Maak sigmoid(z) voor eindige getallen, waaronder ±1000. Wiskundig bereikt de sigmoid nul en één niet bij eindige z; computerafronding kan die uitkomsten wel geven. Zet in het diagram w = 0 en b = 0: alle kansen zijn 0.5; met w = 1 en b = 0 is de kans bij x = 1 ongeveer 0.731.
p=1/(1+exp(−(w*x+b))). De stippellijn is de veelgebruikte drempel 0.5; het punt toont x=1.
2. Bestraf zekerheid die verkeerd uitpakt
Je kent cross-entropy uit informatietheorie. Voor één binair doel y neemt log-loss bij y = 1 de waarde −ln(p), en bij y = 0 de waarde −ln(1−p). We gebruiken hier natuurlijke logaritmen, dus geen bits.
Bij p = 0.75 kost een werkelijk positief voorbeeld ongeveer 0.288; een negatief voorbeeld ongeveer 1.386. Dezelfde zekerheid krijgt dus een andere straf afhankelijk van de waarheid. Gemiddeld leveren twee kansen 0.5 altijd ln(2), ongeveer 0.693.
Rechtstreeks logaritmen van afgeronde kansen nemen kan log(0) veroorzaken. Bereken daarom de equivalente straf rechtstreeks uit z:
ℓ benoemt de straf voor één voorbeeld; max kiest de grootste van z en nul. math.log1p(a) berekent ln(1+a), ook nauwkeurig wanneer a heel klein is. De absolute waarde zorgt opnieuw voor een niet-positieve exponent.
def straf(z, y):
basis = max(z, 0) - z * y
extra = math.log1p(math.exp(-abs(z)))
return basis + extra
z = math.log(3)
print(straf(z, 1)) # ongeveer 0.288
print(straf(z, 0)) # ongeveer 1.386
print(straf(1000, 0)) # 1000.0
Maak logit_loss(zs,ys) als gemiddelde van deze straffen. Lege of ongelijk lange lijsten en labels buiten 0/1 geven ValueError. Veelgemaakte fout: de kans eerst hard op 0 of 1 afronden; daarmee verlies je de informatie waarmee het model leert.
3. Leer uit kans min label
Bij sigmoid plus log-loss vereenvoudigt de kettingregel de afgeleide naar z tot p−y. Daardoor krijg je voor w het gemiddelde van x maal (p−y), en voor b het gemiddelde van (p−y). Hier staat geen factor twee: die hoorde bij MSE.
Neem x = [−1,1], y = [0,1], w = b = 0. De kansen zijn [0.5,0.5], dus de verschillen [0.5,−0.5]; dw = −0.5 en db = 0. Met eta = 1 wordt de nieuwe w = 0.5 en blijft b = 0. Beide voorbeelden krijgen dezelfde nieuwe straf, ongeveer 0.474.
xs = [-1, 1]
ys = [0, 1]
w = 0.0
b = 0.0
sw = 0
sb = 0
for i in range(len(xs)):
fout = sigmoid(w * xs[i] + b) - ys[i]
sw = sw + xs[i] * fout
sb = sb + fout
w, b = w - sw / len(xs), b - sb / len(xs)
print(w, b) # 0.5 0.0
print(straf(w * xs[0] + b, ys[0]))
# ongeveer 0.474, lager dan 0.693
Maak train_logistiek(xs,ys,eta,stappen) met (w,b,history), beginnend bij nul. Hergebruik de gezamenlijke update en verliesgeschiedenis uit regressie; nul stappen geeft één beginverlies. Weiger ongeldige paren/labels en een negatief geheel aantal stappen.
Bij perfect scheidbare data kunnen gewichten blijven groeien terwijl de loss daalt. Een lagere loss betekent dan niet dat de parameters naar een eindig optimum gaan.
034. Vraag de bibliotheek om kansen
Gebruik de pipeline en NumPyvormen uit het regressielab, nu met LogisticRegression(C=1.0). Deze variant gebruikt regularisatie: grote gewichten krijgen een extra straf. C regelt de sterkte: een kleinere C geeft meer straf. Daardoor hoeven haar gewichten niet exact bij onze ongestrafte trainingslus te passen.
import numpy as np
from sklearn.pipeline import (
make_pipeline)
from sklearn.preprocessing import (
StandardScaler)
from sklearn.linear_model import (
LogisticRegression)
model = make_pipeline(
StandardScaler(),
LogisticRegression(C=1.0))
train = np.array([-2, -1, 1, 2])
train = train.reshape(-1, 1)
model.fit(train, [0, 0, 1, 1])
kolom = list(model.classes_).index(1)
kansen = model.predict_proba([[0.]])
print(kansen[:, kolom].tolist()) # [0.5]
classes_ geeft de kolomlabels; .index(1) zoekt de positie van klasse 1. predict_proba geeft één rij per query met een kans per klasse; [:,kolom] selecteert de juiste kans en .tolist() maakt een gewone lijst. Maak logistiek_sklearn(xs,ys,query) voor niet-lege training met beide klassen en een niet-lege query.
Wat neem je mee?
- Een lineaire logit wordt een kans, waarna je apart een beslisdrempel kiest.
- Stabiele rekenvormen voorkomen overloop en logaritmen van afgeronde nul.
- Training gebruikt kans min label; een geldige kanswaarde is nog geen bewezen betrouwbare kans.
Hierna bekijk je een classifier die voorbeelden bewaart in plaats van gewichten leert.