A legkisebb négyzetes illesztést gyakran csak képletként tanuljuk meg, pedig mögötte nagyon egyszerű geometria van: egy vektort merőlegesen vetítünk egy altérre. Ebből a képből közvetlenül következik a normálegyenlet, és az is láthatóvá válik, miért veszélyes a majdnem kollineáris adat.
Adott három megfigyelt pont: , , . A megfigyelési egység egy pár; a koordináták dimenzió nélküli tanulóadatok, így itt nincs mértékegység. Célunk olyan egyenest illeszteni, amely minimalizálja a
négyzetes hibát. Mielőtt tovább olvasnál, gyors becslésként rajzold fel a pontokat: az első szakaszon , a másodikon az -emelkedés, ezért a legkisebb négyzetes meredekség kompromisszumként körül várható, a tengelymetszet pedig valamivel fölé esik. A levezetés után ellenőrizzük ezt a sejtést.
Írjuk a modellt mátrixalakba, ahogyan 10.2 a lineáris transzformációt bevezette:
A design mátrix : minden sora egy megfigyelés, az első oszlop az intercept csupa -ese, a második az értékek oszlopa. A súlyvektor , a predikció pedig . A vektor tehát az oszlopainak lineáris kombinációja, ezért eleme az oszloptérnek (column space):
Jelen példában ez egy kétdimenziós sík a háromdimenziós megfigyelési térben, amelyet a és oszlopvektorok feszítenek ki. A legkisebb négyzetes illesztés geometriai feladata: válasszuk -ből az -hoz legközelebbi pontot.
A reziduum (residual) a megfigyelés és a predikció eltérése:
ahol , és koordinátánként .
A kulcsfelismerés: akkor lesz a legközelebbi pont, ha az reziduum merőleges az oszloptérre. Ha ugyanis merőleges minden vektorára, akkor bármely más esetén Pitagorasz tétele szerint
tehát valóban minimális távolságú. A merőlegességet elég az altér bázisvektoraira, vagyis oszlopaira megkövetelni:
Ezt normálegyenletnek (normal equations) nevezzük. Behelyettesítve:
A shape-ek: mérete , mérete , tehát a megoldandó rendszer két egyenlet két ismeretlenre.
Ha oszlopai lineárisan függetlenek, akkor invertálható, és a legkisebb négyzetes megoldás egyértelmű:
A predikció ekkor az vektor merőleges vetítése (projection) az oszloptérre:
A mátrix a vetítőmátrix (projection matrix), mérete ; szimmetrikus és idempotens, vagyis . A vetítés fogalmát kis vektoros ábrán is érdemes látni. Az alábbi Matplotlib-kód egy -beli altérre vetítést rajzol; a piros reziduum merőleges a zöld altéregyenesre.
import matplotlib.pyplot as plt
import numpy as np
a = np.array([3.0, 1.0]) # az egydimenziós altér bázisvektora
y = np.array([2.0, 3.0])
y_hat = (a @ y) / (a @ a) * a # merőleges vetítés
r = y - y_hat
fig, ax = plt.subplots()
ax.quiver(0, 0, *y, angles='xy', scale_units='xy', scale=1,
color='black', label='y')
ax.quiver(0, 0, *a, angles='xy', scale_units='xy', scale=1,
color='green', label='altér')
ax.quiver(0, 0, *y_hat, angles='xy', scale_units='xy', scale=1,
color='blue', label='vetített y')
ax.quiver(*y_hat, *r, angles='xy', scale_units='xy', scale=1,
color='red', label='reziduum')
ax.set_xlim(-1, 4); ax.set_ylim(-1, 4)
ax.set_aspect('equal'); ax.legend()Az ábra a futás után szemlélteti, hogy a reziduum derékszöget zár be az altérrel. A kód itt nem futtatott; a leírt geometriai viszony a vetítési képletből következik.
A rang (rank) az
oszlopvektorai között található lineárisan független vektorok maximális
száma; NumPy-ban np.linalg.matrix_rank. Ha a rang egyenlő
az oszlopok számával
(),
akkor
teljes oszloprangú, és
invertálható. Ha
,
akkor az egyenletrendszernek több
megoldása is ugyanazt a
predikciót adja; a predikció egyedi, a súlyvektor nem.
A pszeudoinverz (pseudoinverse,
Moore–Penrose-inverz) ilyenkor is előállít egy egyértelmű legkisebb
négyzetes megoldást: ez a legkisebb normájú megoldás. NumPy-ban
np.linalg.pinv(X). A pszeudoinverz szinguláris felbontásra
épül, és ranghiányos mátrixra is működik. A teljes rangú esetben
ugyanazt adja, mint a normálegyenlet-megoldás, de numerikusan általában
stabilabb.
A kondíciószám (condition number) azt méri, mennyire érzékeny a megoldás a bemeneti perturbációkra:
ahol
és
az
legnagyobb és legkisebb szinguláris értéke; NumPy-ban
np.linalg.cond. Ha
közel van
-hez,
a feladat stabil; ha
nagyon kicsi,
nagy, és kis adatváltozás is nagy súlyváltozást okozhat. Fontos: a
normálegyenlet mátrixának kondíciója
,
tehát a normálegyenletben még erősebb az instabilitás, mint magában az
-ben.
Az alábbi kód a hárompontos feladatot oldja meg, és minden fontos invariánst ellenőriz. A kód a kezdőlabor alap numerikus csomagjain kívül mást nem igényel. Itt nem futtatjuk; a kimenethez tartozó várt értékeket kézi számításból adjuk meg.
import numpy as np
X = np.array([
[1.0, 0.0],
[1.0, 1.0],
[1.0, 2.0],
])
y = np.array([1.0, 3.0, 4.0])
w_lstsq, residuals, rank, singular_values = np.linalg.lstsq(X, y, rcond=None)
y_hat = X @ w_lstsq
r = y - y_hat
# Kézi normálegyenlet, az elméleti képlet ellenőrzésére.
w_direct = np.linalg.solve(X.T @ X, X.T @ y)
w_pinv = np.linalg.pinv(X) @ y
print("w_lstsq =", w_lstsq)
print("y_hat =", y_hat)
print("r =", r)
print("X.T @ r =", X.T @ r)
print("rank =", rank)
print("singular_values =", singular_values)
print("cond(X) =", np.linalg.cond(X))
print("w_direct =", w_direct)
print("w_pinv =", w_pinv)A kézi vezetésből elméletileg várt kimenet:
,
azaz w_lstsq = [1.16666667, 1.5]. A predikciók:
,
azaz körülbelül [1.1667, 2.6667, 4.1667]. A reziduumok:
.
Ellenőrizzük az invariánsokat:
tehát a reziduum valóban merőleges mindkét oszlopra. A rang
,
mert a két oszlop nem arányos. A szinguláris értékek
és
,
ezért
,
ami stabil kis feladat. A solve, a lstsq és a
pinv által adott súlyok ezen a jól kondicionált példán
gyakorlatilag megegyeznek.
Hozzunk létre olyan adatot, ahol a második magyarázó változó majdnem konstansszorosa az elsőnek. Ez a gyakorlatban akkor fordul elő, ha két feature erősen összefügg. Az alábbi kód a jól értelmezhető kapcsolatot használja; a zaj miatt a pontos súlyok nem reprodukálhatók gépfüggetlenül, ezért az itt közölt kimenet szemléltető jellegű, nem futtatott mérési jegyzék.
rng = np.random.default_rng(42)
n = 40
x1 = np.linspace(-2.0, 2.0, n)
x2 = 3.0 * x1 + 0.005 * rng.normal(size=n)
y_true = 2.0 * x1 + 1.0 * x2 + 0.5
y = y_true + 0.1 * rng.normal(size=n)
X = np.column_stack([np.ones(n), x1, x2])
w, *_ = np.linalg.lstsq(X, y, rcond=None)
# Kis perturbáció ugyanarra a design mátrixra.
y_perturbed = y + 0.02 * rng.normal(size=n)
w2, *_ = np.linalg.lstsq(X, y_perturbed, rcond=None)
print("cond(X) =", np.linalg.cond(X))
print("w =", w)
print("w_perturbed =", w2)
print("||w - w_perturbed|| =", np.linalg.norm(w - w2))
print("||y_hat - y_hat_perturbed|| =",
np.linalg.norm(X @ w - X @ w2))A megadott konstrukcióban
majdnem
,
tehát a második és harmadik oszlop majdnem kollineáris. A kondíciószám
várhatóan több száz vagy ezer körüli. A látványos jelenség: a
és
súlyvektorok önmagukban jelentősen eltérhetnek, mert a
két súly kompenzálja egymást, miközben a predikciók különbsége kicsi
marad. Ez nem hiba a kódban: az adat nem azonosítja egyértelműen a két
együtthatót, csak a közelítő összegüket. A stabil eszköz itt is
lstsq; a predikció marad stabil, a súlyvektorok értelmezése
viszont óvatosságot igényel.
Értelmezés. A hárompontos példában számold ki a , és értékeket, és igazold a Pitagorasz-összefüggést: . Magyarázd el, miért pontosan a vetítésből következik ez.
Megvalósítás. Illessz egyenest a
,
,
pontokra. Írj NumPy-kódot lstsq-val, ellenőrizd a
normálegyenletet solve-val, és írd ki a reziduumvektorra a
feltételt. Add meg a kondíciószámot is.
Hibadiagnózis. Egy kolléga megoldásként
w_bad = np.linalg.inv(X.T @ X) @ X.T @ y sort használ. A
majdnem kollineáris példán a predikciója mégsem rossz. Milyen
diagnosztikai kísérlettel tudnád kimutatni, hogy a módszer instabilabb a
kelleténél? Javasolj legalább egy konkrét numerikus
összehasonlítást.
Megváltoztatott adat. A három eredeti ponthoz adj hozzá egy negyediket, amelyik a pont pontos duplikátuma. Futtasd az illesztést, és válaszold meg: változik-e a design mátrix rangja, a becsült egyenes, és a reziduumok négyzetösszege? Hogyan kapcsolódik ez ahhoz a duplikációs szerződéshez, amelyről 6.3 szólt?
és , tehát a Pitagorasz-összefüggés teljesül. A magyarázat: az merőleges vetülete az oszloptérre, ezért merőleges -ra; az általános Pitagorasz-tételből épp ez az invariáns következik.
A normálegyenlet:
ezért . A predikciók , , , a reziduumok , és . A szinguláris értékek és , ezért ; ez nagyon stabil. A kód:
X = np.array([[1.0, -1.0], [1.0, 0.0], [1.0, 1.0]])
y = np.array([0.0, 2.0, 1.0])
w, *_ = np.linalg.lstsq(X, y, rcond=None)
r = y - X @ w
print("w =", w)
print("X.T @ r =", X.T @ r)
print("cond(X) =", np.linalg.cond(X))
print("solve check =", np.linalg.solve(X.T @ X, X.T @ y))A diagnosztikai kísérlet: legyártható egy kis perturbált
,
ahol
nagyon kicsi, például a fenti majdnem kollineáris példában
-szeres
zaj. Hasonlítsd össze a
eltérést a lstsq megoldás és az inv(X.T @ X)
megoldás között. Az explicit inverz a normálegyenlet
-szeres
kondíciójával dolgozik, ezért a súlyeltérés ott általában sokkal
nagyobb. A javítás: np.linalg.lstsq(X, y, rcond=None) vagy
legalább np.linalg.solve(X.T @ X, X.T @ y); utóbbi még
mindig a normálegyenletet oldja, ezért a solve is lehet
érzékeny, de elkerüli az explicit inverzképzés plusz műveleteit. A
legjobb a QR- vagy SVD-alapú lstsq vagy
pinv.
A kibővített adat:
A rang marad, mert a duplikált sor nem hoz be új irányt. A normálegyenlet new mátrixa:
amelynek megoldása . A meredekség nem változott, az intercept azonban -ről -ra nőtt, mert a pont kétszeres súlyt kapott. Ez összhangban van 6.3 tanulságával: a duplikátumot explicit döntéssel kell kezelni, mert ha nem valódi ismételt mérés, akkor torzítja a súlyozást.
A tankönyvi képletben szereplő miatt sokan azt gondolják, hogy a helyes megvalósítás az explicit inverz:
w_bad = np.linalg.inv(X.T @ X) @ X.T @ yEz a megoldás kis, jól kondicionált mátrixokra gyakorlatilag rendben
van, de nem általánosan a legjobb numerikus választás.
Az ellenpélda: ha
,
akkor
,
tehát a normálegyenlet már eleve nyolc nagyságrendnyi érzékenységet visz
a rendszerbe, és az explicit inverz további kerekítést ad. A
lstsq QR- vagy SVD-felbontáson alapulva elkerüli az
explicit képzését. Ez ugyanaz a véges pontossági tanulság, amelyet 9.3 a lépésköz-optimumnál bemutatott: a kerekítési
hiba a rossz képletből előbb-utóbb elrontja a numerikus eredményt.
A legkisebb négyzetes illesztés geometriailag az
vektor merőleges vetítése az
mátrix oszlopterére; a reziduum merőlegessége adja a
normálegyenletet. Teljes oszloprang esetén a súlyvektor egyértelmű,
ranghiány esetén a pszeudoinverz a legkisebb normájú megoldást
választja. A kondíciószám a megoldás érzékenységét méri; a majdnem
kollineáris változóknál a súlyok instabilak lehetnek, miközben a
predikció stabil marad. Tényleges számításhoz az explicit mátrixinverz
helyett np.linalg.lstsq vagy np.linalg.pinv
ajánlott. A következő alfejezetben a szinguláris felbontás és a
dimenziócsökkentés épít erre a vetítési képre.