Rozwiązywanie układów równań liniowych
Rozwiąż Ax=b za pomocą np.linalg.solve i porównaj to podejście z naiwnym mnożeniem przez macierz odwrotną pod kątem stabilności numerycznej.
Rozwiązywanie układów równań liniowych to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Pandas & NumPy Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Czym jest układ równań liniowych
Układ równań liniowych można zapisać w postaci macierzowej jako Ax = b, gdzie A jest macierzą współczynników, x jest wektorem niewiadomych, a b jest wektorem prawej strony. Na przykład dwa równania z dwiema niewiadomymi można wyrazić jako równanie macierzowe z macierzą 2×2. Rozwiązanie układu oznacza znalezienie x takiego, że A pomnożone przez x jest równe b. Funkcja np.linalg.solve(A, b) w NumPy wykonuje to wydajnie i dokładnie.
import numpy as np
# System: 2x + y = 5
# x + 3y = 10
A = np.array([[2.0, 1.0],
[1.0, 3.0]])
b = np.array([5.0, 10.0])
x = np.linalg.solve(A, b)
print('Solution x:', x)
print('Verify A @ x == b:', np.allclose(A @ x, b))Dlaczego nie używać macierzy odwrotnej
Matematycznie rozwiązaniem równania Ax = b jest x = A⁻¹b. Jednak obliczenie np.linalg.inv(A) @ b jest wolniejsze i mniej dokładne niż bezpośrednie wywołanie np.linalg.solve(A, b). Funkcja solve() wewnętrznie używa rozkładu LU, który wymaga mniejszej liczby operacji i powoduje kumulowanie mniejszych błędów zmiennoprzecinkowych. Zasada praktyczna: jeśli rozwiązują Państwo równanie Ax=b względem x, należy zawsze używać solve, a nigdy inv.
import numpy as np
A = np.random.rand(100, 100)
b = np.random.rand(100)
# Avoid this pattern
x_inv = np.linalg.inv(A) @ b
# Prefer this
x_solve = np.linalg.solve(A, b)
print('Max difference:', np.max(np.abs(x_inv - x_solve)))
# solve is faster AND more accurateWymagania funkcji solve()
np.linalg.solve(A, b) wymaga, aby A była kwadratowa i nieosobliwa. Jeśli A jest osobliwa (wyznacznik wynosi zero), funkcja zgłasza np.linalg.LinAlgError: Singular matrix. Macierz A jest osobliwa, gdy jej wiersze są liniowo zależne — na przykład gdy jedno równanie jest po prostu wielokrotnością innego. Przed wywołaniem solve należy zawsze sprawdzić, czy układ jest poprawnie określony. Jeśli A nie jest kwadratowa, należy zamiast tego użyć np.linalg.lstsq().
import numpy as np
# Well-posed system
A = np.array([[1.0, 2.0], [3.0, 4.0]])
b = np.array([5.0, 6.0])
print('Solution:', np.linalg.solve(A, b))
# Singular system (row 2 = 3 * row 1)
A_singular = np.array([[1.0, 2.0], [3.0, 6.0]])
try:
np.linalg.solve(A_singular, b)
except np.linalg.LinAlgError as e:
print('Error:', e)Rozkład LU od środka
np.linalg.solve wewnętrznie stosuje rozkład LU: rozkłada A na macierz trójkątną dolną L i macierz trójkątną górną U, tak że A = LU. Rozwiązanie sprowadza się wtedy do dwóch prostszych układów trójkątnych (podstawiania w przód i wstecz), które są znacznie wydajniejsze niż wielokrotne wykonywanie eliminacji Gaussa od początku. Jest to ten sam algorytm, którego używają MATLAB, Fortran LAPACK i praktycznie każda biblioteka do obliczeń naukowych.
import numpy as np
from scipy import linalg
A = np.array([[2.0, 1.0, -1.0],
[-3.0, -1.0, 2.0],
[-2.0, 1.0, 2.0]])
b = np.array([8.0, -11.0, -3.0])
# NumPy solve (uses LAPACK dgesv under the hood)
x = np.linalg.solve(A, b)
print('Solution:', x)
print('Check:', np.allclose(A @ x, b))Rozwiązywanie dla wielu prawych stron
np.linalg.solve(A, B) może obsługiwać macierz B z wieloma kolumnami, rozwiązując układ jednocześnie dla każdej kolumny. Jeśli B ma kształt (n, k), wynik X ma kształt (n, k), przy czym każda kolumna X jest rozwiązaniem odpowiadającym właściwej kolumnie B. Jest to przydatne w symulacjach fizycznych i uczeniu maszynowym, gdy ten sam układ współczynników trzeba rozwiązać dla wielu różnych prawych stron.
import numpy as np
A = np.array([[3.0, 1.0],
[1.0, 2.0]])
# Three right-hand side vectors at once
B = np.array([[9.0, 3.0, 6.0],
[8.0, 4.0, 2.0]])
X = np.linalg.solve(A, B)
print('Solutions X shape:', X.shape) # (2, 3)
print('Verify A @ X == B:', np.allclose(A @ X, B))Metoda najmniejszych kwadratów z np.linalg.lstsq()
Gdy układ jest nadokreślony (ma więcej równań niż niewiadomych), nie istnieje rozwiązanie dokładne. np.linalg.lstsq(A, b) znajduje takie x, które minimalizuje sumę kwadratów reszt ||Ax - b||² — dokładnie to oblicza regresja liniowa. Funkcja zwraca rozwiązanie x, reszty, rząd oraz wartości osobliwe. Argument rcond=None wycisza ostrzeżenie o przestarzałym użyciu i ustawia próg, poniżej którego wartości osobliwe są traktowane jako zero.
import numpy as np
# Over-determined: 4 equations, 2 unknowns
A = np.array([[1.0, 1.0],
[1.0, 2.0],
[1.0, 3.0],
[1.0, 4.0]])
b = np.array([2.0, 2.5, 3.5, 4.5])
x, residuals, rank, sv = np.linalg.lstsq(A, b, rcond=None)
print('Least-squares solution:', x)
print('Residuals:', residuals)
print('Rank:', rank)Zastosowanie solve() w regresji liniowej
Równania normalne zwykłej metody najmniejszych kwadratów mają postać A.T @ A @ x = A.T @ b, czyli kwadratowego układu, który można rozwiązać za pomocą np.linalg.solve. Daje to taki sam wynik jak lstsq, gdy A.T @ A jest dobrze uwarunkowana. W praktyce preferowane jest lstsq, ponieważ używa SVD i pozostaje stabilne nawet wtedy, gdy kolumny są prawie współliniowe, ale równania normalne pomagają zrozumieć matematykę stojącą za regresją.
import numpy as np
np.random.seed(0)
X = np.column_stack([np.ones(50), np.random.rand(50) * 10])
y = 3.0 + 2.0 * X[:, 1] + np.random.randn(50)
# Solve normal equations
coeffs = np.linalg.solve(X.T @ X, X.T @ y)
print('Intercept:', round(coeffs[0], 3))
print('Slope:', round(coeffs[1], 3))Sprawdzanie współczynnika uwarunkowania
Przed rozwiązaniem warto sprawdzić współczynnik uwarunkowania za pomocą np.linalg.cond(A). Współczynnik bliski 1 oznacza, że układ jest dobrze uwarunkowany, a rozwiązanie — wiarygodne. Bardzo duży współczynnik (np. 1e12) oznacza, że macierz jest prawie osobliwa: małe zmiany b powodują ogromne zmiany x, a rozwiązanie może być numerycznie niewiarygodne. W takim przypadku należy rozważyć regularyzację (regresję ridge) albo użycie bardziej odpornego solvera.
import numpy as np
# Well-conditioned
A1 = np.array([[2.0, 1.0], [1.0, 3.0]])
print('Condition number (well):', np.linalg.cond(A1))
# Ill-conditioned (nearly linearly dependent rows)
A2 = np.array([[1.0, 1.0], [1.0, 1.0001]])
print('Condition number (ill):', np.linalg.cond(A2))Układy rzadkie: kiedy nie używać linalg
W przypadku bardzo dużych układów, w których A ma głównie elementy zerowe (jest macierzą rzadką), przechowywanie pełnej macierzy marnuje pamięć, a użycie gęstej funkcji np.linalg.solve jest powolne. SciPy udostępnia scipy.sparse oraz scipy.sparse.linalg.spsolve, które wykorzystują rzadkość macierzy w ogromnych układach, takich jak modele elementów skończonych lub problemy sieciowe. Rozpoznanie, kiedy macierz jest rzadka, jest ważnym aspektem wydajności w obliczeniach naukowych.
import numpy as np
# Dense solve is fine for small-to-medium systems
A = np.random.rand(200, 200)
b = np.random.rand(200)
x = np.linalg.solve(A, b)
print('Dense solve OK, solution shape:', x.shape)
# For large sparse systems, use scipy.sparse.linalg.spsolve instead
# from scipy.sparse.linalg import spsolve
# x = spsolve(A_sparse, b)Interpretowanie wektora rozwiązania
Po uzyskaniu wektora rozwiązania x za pomocą np.linalg.solve(A, b) należy zawsze zweryfikować rozwiązanie, obliczając A @ x i porównując wynik z b za pomocą np.allclose(A @ x, b). W arytmetyce zmiennoprzecinkowej dokładna równość występuje rzadko — allclose sprawdza zgodność w ramach niewielkiej tolerancji. Norma residuum ||Ax - b|| powinna być bliska iloczynowi epsilon maszynowego i skali problemu dla dobrze uwarunkowanego układu.
import numpy as np
A = np.array([[4.0, 3.0, 2.0],
[1.0, 5.0, 3.0],
[2.0, 1.0, 6.0]])
b = np.array([10.0, 20.0, 15.0])
x = np.linalg.solve(A, b)
print('Solution:', np.round(x, 4))
# Residual check
residual = np.linalg.norm(A @ x - b)
print('Residual ||Ax - b||:', residual)
print('Valid:', np.allclose(A @ x, b))Rozwiązywanie układów w potokach uczenia maszynowego
Solvery liniowe są wykorzystywane w całym uczeniu maszynowym: regresja liniowa rozwiązuje równania normalne, procesy Gaussa rozwiązują układy z macierzami jąder, a filtry Kalmana rozwiązują równania aktualizacji stanu. Rozumiejąc działanie np.linalg.solve i lstsq, mogą Państwo implementować te algorytmy od podstaw i diagnozować problemy, gdy wyniki są nieoczekiwane. Moduł linalg biblioteki NumPy udostępnia te same narzędzia numeryczne, które są używane w produkcyjnych bibliotekach naukowych.
import numpy as np
# Ridge regression: (X.T @ X + lambda * I) @ w = X.T @ y
np.random.seed(42)
X = np.random.rand(100, 5)
y = np.random.rand(100)
lambda_reg = 0.1
n_features = X.shape[1]
A = X.T @ X + lambda_reg * np.eye(n_features)
bvec = X.T @ y
w = np.linalg.solve(A, bvec)
print('Ridge regression weights:', np.round(w, 4))Szybkie sprawdzenie
Sprawdź swoją wiedzę na temat koncepcji analizy danych z tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczyli się Państwo, że: np.linalg.solve(A, b) efektywnie rozwiązuje kwadratowe układy równań liniowych za pomocą rozkładu LU, np.linalg.lstsq() znajduje rozwiązanie metodą najmniejszych kwadratów dla układów nadokreślonych, a liczba uwarunkowania mierzy stabilność numeryczną — duże wartości ostrzegają o macierzach bliskich osobliwym. W następnej części omówimy wartości własne i SVD, które stanowią podstawę PCA oraz wielu algorytmów uczenia maszynowego.
Często zadawane pytania
Czy lekcja „Rozwiązywanie układów równań liniowych” jest bezpłatna?
Tak — pełny tekst „Rozwiązywanie układów równań liniowych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Pandas & NumPy Academy, przejdź na CoddyKit PRO. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Rozwiązywanie układów równań liniowych”?
Rozwiąż Ax=b za pomocą np.linalg.solve i porównaj to podejście z naiwnym mnożeniem przez macierz odwrotną pod kątem stabilności numerycznej. Ćwiczysz Pandas & NumPy Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Pandas & NumPy Academy?
Nie wymagamy żadnego doświadczenia. Pandas & NumPy Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Rozwiązywanie układów równań liniowych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Pandas & NumPy Academy?
Tak. Każda lekcja Pandas & NumPy Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Mnożenie macierzy za pomocą np.matmul i @
- Wyznaczniki, macierze odwrotne i transpozycje
- Rozwiązywanie układów równań liniowych
- Wartości własne i przegląd SVD