11.1 Meredekség és numerikus derivált

11.1.1 Cél és konkrét helyzet

A Northstar késési modelljében (lásd Kérdés, célváltozó és döntési helyzet) rendelésenként becsülünk egy kockázatot. A modell bemenete tartalmazhatja a kosárértéket, a távolságot vagy a prioritási jelzést, kimenete pedig egy valószínűség. Ha egy ilyen modellt ábrázolunk, azonnal felmerül a kérdés: egy adott pontban mennyire meredek a görbe? A derivált (derivative) erre a pillanatnyi változási rátára ad számot. A derivált előjele és nagysága mondja meg, hogy egy kis bemeneti növelés körülbelül mennyivel növeli vagy csökkenti a kimenetet, ezért később az optimalizáló algoritmusok és a veszteségfüggvények elemzésének is alapja.

Ebben az alfejezetben a bemenet egy valós szám xx, a modell egy f(x)f(x) függvény, a megfigyelési egység pedig egy konkrét kiértékelési pont x=ax=a. A cél az f′(a)f'(a) meredekség numerikus becslése és ellenőrzése. A tanuló labor két tesztfüggvényt használ: f(x)=x2f(x)=x^2 és a logisztikus σ(x)=1/(1+e−x)\sigma(x)=1/(1+e^{-x}) függvényt. Az egység kulcsfontosságú: ha xx mértékegysége km és ff értéke eurócent, a derivált egysége eurócent/km.

Induló feladat a megoldás előtt: becsüld meg kézzel f(x)=x2f(x)=x^2 meredekségét az x=3x=3 pontban a 3 és 4 között húzott szelővel! A szelő meredeksége (42−32)/(4−3)=7(4^2-3^2)/(4-3)=7. Vajon az érintő ennél nagyobb vagy kisebb? A függvény gyorsulva nő, ezért a 3 és 4 közötti átlagos meredekség nagyobb, mint a 3-beli pillanatnyi érték; előzetesen nagyjából 6 körüli eredményt várunk.

11.1.2 Szelőből érintő: a differenciahányados

Az ff függvény grafikonján az aa és a+ha+h pontokon átmenő egyenes a szelő. A szelő meredekségét differenciahányadosnak (difference quotient) nevezzük:

Dhf(a)=f(a+h)−f(a)h,D_h f(a)=\frac{f(a+h)-f(a)}{h},

ahol h≠0h\neq 0 a lépésköz (step size). A képletben aa és hh ugyanazon mértékegységben értendő, hh előjeles; negatív hh hátrafelé vett differenciát ad. A hányados egysége [f]/[x][f]/[x], vagyis a kimenet egysége osztva a bemenet egységével. Ha ff eurócentben, xx kilométerben adott, a differenciahányados eurócent/km dimenziójú.

Kis számpélda f(x)=x2f(x)=x^2, a=3a=3 mellett:

A sorozat a 6 felé tart. Az ábrán a szelő egyre jobban rásimul az érintőre, ahogy a két metszéspont közeledik egymáshoz. A következő kód a szelőt és az érintőt rajzolja fel:

import numpy as np
import matplotlib.pyplot as plt

def f_sq(x):
    return x * x

a = 3.0
h = 1.0
tangent_slope = 2.0 * a  # f'(x)=2x, tehát f'(3)=6

xs = np.linspace(2.0, 4.5, 200)
plt.figure(figsize=(6.5, 4))
plt.plot(xs, f_sq(xs), label="$x^2$", color="black")
plt.plot([a, a + h], [f_sq(a), f_sq(a + h)], marker="o",
         label="szelő (h=1)")
plt.plot([a - 0.6, a + 0.6],
         [f_sq(a) - 0.6 * tangent_slope, f_sq(a) + 0.6 * tangent_slope],
         linestyle="--", label="érintő")
plt.xlabel("x")
plt.ylabel("f(x)")
plt.title("Szelő és érintő az x=3 pontban")
plt.legend()
plt.tight_layout()
plt.savefig("derivative_segment.png", dpi=120)

Az ábrán a szelő meredeksége 7, az érintőé — a következő szakasz analitikus képlete szerint — 6.

11.1.3 A derivált mint határérték

A Határérték mint közelítési folyamat alfejezetben a határértéket úgy vezettük be, mint közelítési folyamatot: egy sorozat értékei egy számhoz tartanak anélkül, hogy feltétlenül elérnék. A differenciahányados-sorozat ugyanezt a gondolatot használja. Ha a

limh→0f(a+h)−f(a)h\lim_{h\to 0}\frac{f(a+h)-f(a)}{h}

határérték létezik és véges, akkor azt az ff függvény aa pontbeli deriváltjának nevezzük, és f′(a)f'(a)-val jelöljük. A h→0h\to 0 jelölés nem jelenti azt, hogy h=0h=0-t behelyettesíthetünk; a 0/00/0 alak önmagában értelmetlen. Azt jelenti, hogy a hányadost tetszőlegesen kicsi, de nullától különböző lépésközökkel közelítjük.

A derivált geometriai jelentése az érintő meredeksége, adatmodellezési jelentése pedig pillanatnyi változási ráta. Kis hh-ra a függvényérték lineárisan közelíthető:

f(a+h)≈f(a)+f′(a)h.f(a+h)\approx f(a)+f'(a)h.

Ez a linearizálás a későbbi optimalizáló lépések alapja: ha f′f' pozitív, a függvény lokálisan nő, ha negatív, akkor csökken. A meredekség előjele mondja meg, merre érdemes elmozdulni.

Fontos: f′(a)f'(a) pontbeli mennyiség, általában függ aa értékétől. Az x2x^2 deriváltfüggvénye 2x2x, tehát f′(3)=6f'(3)=6, de f′(10)=20f'(10)=20. A konstans függvény deriváltja mindenütt 0. Erre a különbségre a gyakori tévedéseknél visszatérünk.

11.1.4 Lépésköz, centrális differencia és a numerikus hiba

A fenti egyoldali, előre vett forward differencia mellett gyakori a centrális differencia (central difference):

Chf(a)=f(a+h)−f(a−h)2h.C_h f(a)=\frac{f(a+h)-f(a-h)}{2h}.

Ez a két egyoldali hányados átlaga, és szimmetrikus az aa pont körül. Az x2x^2 példában a=3a=3-ra:

Chx2|a=3=(3+h)2−(3−h)22h=12h2h=6.C_h x^2\big|_{a=3}=\frac{(3+h)^2-(3-h)^2}{2h}=\frac{12h}{2h}=6.

Itt a centrális differencia minden nullától különböző hh-ra pontosan 6-ot ad, mert a másodfokú hibatag kiesik. Általános függvénynél a centrális differencia sem pontos, de a csonkítási hibája jellemzően h2h^2-tel arányos, míg a forward differenciáé hh-val arányos. Csökkenő hh mellett tehát a centrális változat gyorsabban javul.

A numerikus becslésnek két hibaforrása van. (1) A csonkítási hiba (truncation error) abból adódik, hogy a szelő nem azonos az érintővel; ez nagy hh-nál dominál. (2) A kerekítési hiba (rounding error) abból adódik, hogy f(a+h)f(a+h) és f(a)f(a) lebegőpontos közelítés; nagyon kis hh esetén a különbségképzés elveszti az értékes jegyeket, és a hányados zajossá válik.

A float64 gépi pontosság ε≈2,22⋅10−16\varepsilon\approx 2{,}22\cdot10^{-16}. A forward differenciánál a hasznos lépésköz nagyságrendje ε≈1,5⋅10−8\sqrt{\varepsilon}\approx 1{,}5\cdot10^{-8}, a centrálisnál ε1/3≈6⋅10−6\varepsilon^{1/3}\approx 6\cdot10^{-6}. A pontos optimum függ a függvénytől és a skálázástól, ezért a lépésközt mindig a konkrét feladaton kell ellenőrizni. A szabály itt sem az, hogy „minél kisebb hh, annál jobb”.

11.1.5 Teljes minimális munkapélda

A következő önálló szkript a két tesztfüggvényen becsüli a deriváltat, kiírja a hibát több lépésközre, és log-log ábrát ment. Nincs véletlenszám, ezért seedre nincs szükség; a futási eredmény a NumPy float64 kerekítésétől függhet, ezért platformok közötti bitazonosságot nem ígérünk. A kódot a lab projekt gyökérkönyvtárában, a projekt Python-környezetében futtatjuk:

# platform: Linux/macOS bash, munkakönyvtár: a könyv lab/ projektgyökere
python s041_derivative_demo.py
import numpy as np
import matplotlib.pyplot as plt

def f_sq(x):
    """f(x)=x^2, skalár bemenetre."""
    return x * x

def df_sq_exact(x):
    """f'(x)=2x analitikus derivált."""
    return 2.0 * x

def sigmoid(x):
    """Numerikusan stabil logisztikus függvény.

    A közvetlen 1/(1+exp(-x)) nagy negatív x-re túlcsordulást
    kockáztat; ez az alak mindkét előjel mellett stabil.
    """
    return np.exp(np.minimum(x, 0.0)) / (1.0 + np.exp(-np.abs(x)))

def d_sigmoid_exact(x):
    """sigma'(x)=sigma(x)(1-sigma(x))."""
    s = sigmoid(x)
    return s * (1.0 - s)

def forward_difference(f, x, h):
    return (f(x + h) - f(x)) / h

def central_difference(f, x, h):
    return (f(x + h) - f(x - h)) / (2.0 * h)

# --- 1. rész: x^2 az x=3 pontban -------------------------------
a = 3.0
exact_sq = df_sq_exact(a)
print(f"x^2, a={a}: analitikus derivált {exact_sq:.6f}")
for h in (1e-3, 1e-5, 1e-8):
    fd = forward_difference(f_sq, a, h)
    cd = central_difference(f_sq, a, h)
    print(f"h={h:.0e}: forward {fd:.9f}, central {cd:.9f}")

# --- 2. rész: logisztikus függvény az x=1 pontban --------------
b = 1.0
exact_sig = d_sigmoid_exact(b)
print(f"\nsigma, x={b}: analitikus derivált {exact_sig:.9f}")
h_sig = 1e-6
fd_sig = forward_difference(sigmoid, b, h_sig)
cd_sig = central_difference(sigmoid, b, h_sig)
print(f"h={h_sig:.0e}: forward {fd_sig:.9f}, central {cd_sig:.9f}")

# --- 3. rész: hiba a lépésköz függvényében ----------------------
hs = np.logspace(-16.0, -1.0, 61)
err_f = [abs(forward_difference(f_sq, a, h) - exact_sq) for h in hs]
err_c = [abs(central_difference(f_sq, a, h) - exact_sq) for h in hs]

plt.figure(figsize=(7, 4.2))
plt.loglog(hs, err_f, marker=".", label="forward")
plt.loglog(hs, err_c, marker=".", label="central")
plt.xlabel("h lépésköz")
plt.ylabel("abszolút hiba")
plt.title("x^2 numerikus deriváltjának hibája x=3-ban")
plt.legend()
plt.tight_layout()
plt.savefig("s041_derivative_error.png", dpi=120)
print("\nÁbra elmentve: s041_derivative_error.png")

A szemléltető, elméletileg várt kimenet a képletekből számolva (a helyi futás utolsó jegyekben a hardverfüggő kerekítések miatt eltérhet):

x^2, a=3: analitikus derivált 6.000000
h=1e-03: forward 6.001000000, central 6.000000000
h=1e-05: forward 6.000010000, central 6.000000000
h=1e-08: forward 6.000000010, central 6.000000000

sigma, x=1: analitikus derivált 0.196611933
h=1e-06: forward 0.196611888, central 0.196611933

Ábra elmentve: s041_derivative_error.png

A forward differencia h=10−8h=10^{-8} melletti értéke a csonkítási hiba csökkenése és a kerekítési zaj megjelenése közötti átmenetet mutatja. A scipy.optimize-ot itt még nem hívjuk; a következő alfejezetek gradiens alapú keresői erre a meredekségfogalomra épülnek.

11.1.6 Eredmény értelmezése és kézi kontroll

A forward differencia h=0,001h=0{,}001 esetén:

D0.001(x2)|3=3,0012−90,001=9,006001−90,001=6,001.D_{0.001}(x^2)\big|_{3}=\frac{3{,}001^2-9}{0{,}001}=\frac{9{,}006001-9}{0{,}001}=6{,}001.

Az analitikus érték 6, tehát az abszolút hiba pontosan 0,0010{,}001. Ez a várt hh-val arányos csonkítási hiba: másodfokú függvénynél az eltérés éppen a lépésköz nagyságrendjébe esik. A centrális differencia ugyanerre a lépésközre pontosan 6-ot ad, mert a másodfokú hibatag kiesik. Ez egyben a kód invariánsa: x2x^2-re a centrális differencia matematikailag 6, numerikusan pedig 6-hoz közeli mindaddig, amíg a kerekítési hiba kicsi.

A logisztikus függvény deriváltjának analitikus alakja az összetett függvény szabályából adódik, amelyet a Láncszabály és számítási gráf tárgyal részletesen; itt csak felhasználjuk:

σ′(x)=σ(x)(1−σ(x)).\sigma'(x)=\sigma(x)\bigl(1-\sigma(x)\bigr).

Az x=1x=1 pontban σ(1)=1/(1+e−1)≈0,7310586\sigma(1)=1/(1+e^{-1})\approx0{,}7310586, ezért

σ′(1)≈0,7310586⋅0,2689414≈0,1966119.\sigma'(1)\approx0{,}7310586\cdot0{,}2689414\approx0{,}1966119.

A centrális differencia h=10−6h=10^{-6} mellett az elméleti hibája h2h^2 nagyságrendű, azaz 10−1210^{-12} alatti; a fenti várt kimenetben 9 tizedesjegyre egyezik az analitikus értékkel.

A hibaábra alakja a következő: nagy hh-nál a forward görbe hh-val, a centrális h2h^2-tel arányosan csökken; körülbelül 10−810^{-8} (forward), illetve 10−610^{-6} (centrális) alatt a kerekítési zaj felnövekszik, és a hiba újra nő. Az ábra egyszerre mutatja a csonkítási és a kerekítési hibát, ezért alkalmas arra, hogy a lépésköz-választást indokoljuk.

A numerikus differencia hányadosának hibája a lépésköz csökkentésével nem monoton javul: a csonkolási hiba mérséklődik, miközben a kerekítési hiba és a kivonási pontatlanság felerősödhet. A hibatáblázatot ezért logaritmikus hh-rácson is érdemes ábrázolni.

11.1.7 Feladatok

  1. Értelmezés és kézi kontroll. Egy fiktív szállítási költségmodell c(d)=30d2c(d)=30d^2 eurócent, ahol dd a távolság km-ben. Add meg a derivált egységét! Számold ki kézzel d=2d=2 km-nél a forward differenciahányadost h=0,1h=0{,}1 km-rel, és hasonlítsd össze az analitikus c′(2)=120c'(2)=120 eurócent/km értékkel!

  2. Implementálás. Írj central_difference(f, x, h) függvényt, amelynek bemenete egy skalár függvény, egy pont és egy lépésköz, kimenete pedig egy lebegőpontos szám. Alkalmazd a logisztikus függvényre az x=1,2x=1{,}2 pontban, h=10−5h=10^{-5} lépésközzel! Add meg az abszolút hibát a σ′(1,2)=σ(1,2)(1−σ(1,2))\sigma'(1{,}2)=\sigma(1{,}2)(1-\sigma(1{,}2)) analitikus alakhoz képest!

  3. Hibadiagnózis. Egy kolléga ezt a kódot futtatta:

def bad_derivative(f, x):
    h = 1e-16
    return (f(x + h) - f(x)) / h

print(bad_derivative(lambda x: x * x, 3.0))

Azt kapta, hogy a függvény 0.0-t ír ki, pedig a helyes érték 6 lenne. Mi a numerikus hiba ezzel a lépésközzel, és milyen második veszély fenyegetne akkor is, ha a lépésköz valamivel nagyobb lenne? Javítsd úgy, hogy a forward differenciánál szokásos 10−810^{-8} nagyságrendű lépésközt használjon!

  1. Megváltozott feltétel. A Northstar késési modelljében a kosárérték hatását logisztikus függvénnyel írjuk le: p(x)=σ(1,2x−3)p(x)=\sigma(1{,}2x-3), ahol xx a kosárérték 100 euró egységben. Mennyi p′(2,5)p'(2{,}5) analitikus értéke, és mit jelent a kapott szám a döntéshozónak? Ellenőrizd centrális differenciával, h=10−6h=10^{-6} lépésköz mellett!

11.1.8 Megoldások és indoklás

1. A derivált egysége eurócent/km, mert a számláló eurócentben, a nevező km-ben van. Kézi számolás:

D0.1c(2)=30(2,1)2−30⋅220,1=132,3−1200,1=123eurócent/km.D_{0.1} c(2)=\frac{30(2{,}1)^2-30\cdot2^2}{0{,}1}=\frac{132{,}3-120}{0{,}1}=123\ \text{eurócent/km}.

Az analitikus derivált c′(d)=60dc'(d)=60d, tehát c′(2)=120c'(2)=120 eurócent/km. A forward becslés hibája 3 eurócent/km, relatíve 2,5%2{,}5\%; ez a nagy lépésköz miatt várható. A centrális differencia ugyanezzel a hh-val pontosan 120-at adna, mert a másodfokú függvény hibatagja kiesik.

2. Egy lehetséges implementáció:

import math

def central_difference(f, x, h):
    return (f(x + h) - f(x - h)) / (2.0 * h)

def sigmoid_stable(x):
    if x >= 0.0:
        return 1.0 / (1.0 + math.exp(-x))
    e = math.exp(x)
    return e / (1.0 + e)

x = 1.2
h = 1e-5
est = central_difference(sigmoid_stable, x, h)
exact = sigmoid_stable(x) * (1.0 - sigmoid_stable(x))
print(f"becsült {est:.12f}, analitikus {exact:.12f}, "
      f"abszolút eltérés {abs(est - exact):.2e}")

Az x=1,2x=1{,}2 pontban σ(1,2)≈0,7685248\sigma(1{,}2)\approx0{,}7685248, a derivált analitikus értéke 0,17789440{,}1778944. A centrális differencia h=10−5h=10^{-5} mellett a h2h^2-tel arányos csonkítás miatt 10−1010^{-10} nagyságrendű abszolút hibát ad; a fenti kimenet szemléltető, a tényleges futást a saját környezetben kell elvégezni. Ellenőrzési tolerancia: az eltérés 10−610^{-6} alatt marad.

3. Az első hiba: h=10−16h=10^{-16} kisebb, mint a float64 fél ulp-je az x=3x=3 pontban (az ulp ott 2−51≈4,44⋅10−162^{-51}\approx4{,}44\cdot10^{-16}), ezért az összeg 3+10−163+10^{-16} ugyanarra a lebegőpontos számra kerekedik, mint a 3. A számláló így pontosan 0, a hányados 0. A második veszély: ha a lépésköz valamivel nagyobb lenne, de még mindig nagyon kicsi, két közeli szám különbségképzése katasztrofális kiejtést (catastrophic cancellation) okozna, és a hányados zajossá válna. Javítás:

def good_forward(f, x, h=1e-8):
    return (f(x + h) - f(x)) / h

print(good_forward(lambda x: x * x, 3.0))  # várt: 6.00000001 körül

A várt érték az analitikus 6-tól hh nagyságrendű hibával, azaz 10−810^{-8}-nal tér el; a tesztben a 6-tól mért abszolút eltérés 10−710^{-7} alatt marad.

4. A láncszabály (amelyet a Láncszabály és számítási gráf alfejezetben igazolunk) szerint, ha a belső függvény u=1,2x−3u=1{,}2x-3, akkor

p′(x)=1,2σ(u)(1−σ(u)).p'(x)=1{,}2\,\sigma(u)\bigl(1-\sigma(u)\bigr).

Az x=2,5x=2{,}5 pontban u=0u=0 és σ(0)=0,5\sigma(0)=0{,}5, tehát

p′(2,5)=1,2⋅0,5⋅0,5=0,3.p'(2{,}5)=1{,}2\cdot0{,}5\cdot0{,}5=0{,}3.

A gyakorlati jelentés: a modell szerint ebben a pontban 100 euró kosárérték-növekedés körülbelül 0,3-mal növeli a becsült késési valószínűséget. Lineáris közelítéssel egy egységnyi növelés 0,500{,}50-ről 0,800{,}80-ra emeli a becslést. A centrális differenciás ellenőrzés h=10−6h=10^{-6} mellett 0,3000000{,}300000 körüli értéket ad; az elfogadható eltérés 10−610^{-6} alatt van.

11.1.9 Gyakori tévedés és önellenőrzés

A leggyakoribb tévedés, hogy a deriváltat egyetlen számmal azonosítjuk, mintha a függvény meredeksége állandó lenne. Ellenpélda: az x2x^2 függvény deriváltja x=3x=3-ban 6, de x=10x=10-ben 20; ugyanaz a függvény más pontban más meredekséget ad. Ha a szállítási költség deriváltját kiszámoljuk 2 km-nél, az nem érvényes 50 km-nél. A konstans függvény a kivétel: például f(x)=7f(x)=7 deriváltja mindenütt 0. Hasonló tévedés, hogy a legkisebb hh mindig pontosabb; a hibaábra éppen azt mutatja, hogy túl kis lépésköznél a kerekítési zaj elrontja a becslést.

Visszautalás: a Vektor, skalárszorzat, norma és távolság alfejezetben a vektort rendezett számlistaként, a különbségvektort pedig a távolság alapjaként vezettük be. A differenciahányados egy dimenzióban ugyanezt a különbséget skálázza: a függvény kimenetének változását osztjuk a bemenet változásával. A Reláció, NULL és elemzési szemcsézettség megfigyelési egység fogalma itt a kiértékelési pontban tér vissza: a derivált mindig egy konkrét aa ponthoz tartozik, és nem vihető át automatikusan más pontra.

Önellenőrzés: (a) Miért nem helyettesíthetjük be h=0h=0-t a differenciahányadosba? (b) Mi a különbség a forward és a centrális differencia képlete között? (c) Milyen mértékegysége van a deriváltnak egy euró-költség és km-távolság függvénynél? (d) Miért nő meg a numerikus hiba nagyon kis hh esetén?

11.1.10 Rövid összefoglaló

A differenciahányados a szelő meredeksége, és ahogy a lépésköz nullához tart, az érintő meredekségét, a deriváltat közelíti. A derivált pontbeli változási ráta, mértékegysége a kimenet és a bemenet egységének hányadosa. Numerikusan a forward differencia egyszerű, a centrális differencia szimmetrikus és általában pontosabb, de a lépésközt a csonkítási és a kerekítési hiba egyensúlya szabja meg. Az x2x^2 és a logisztikus függvény laborja kézi kontrollal és hibaábrával igazolja ezt. A következő alkalmazás a Parciális derivált és gradiens parciális derivált, amely több bemeneti koordinátára terjeszti ki ezt az egyváltozós meredekséget.