14  Algebra liniowa

14.1 Iloczyn skalarny (dot product)

Dla dwóch wektorów, dot oblicza ich iloczyn skalarny.

import numpy as np

# Iloczyn skalarny dwóch wektorów
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = np.dot(a, b)  # 1*4 + 2*5 + 3*6
print(result)  # Wynik: 32

# Alternatywny zapis za pomocą operatora @
result = a @ b
print(result)  # Wynik: 32
32
32

14.2 Mnożenie macierzowe

Dla macierzy (tablic dwuwymiarowych), dot wykonuje standardowe mnożenie macierzowe.

import numpy as np
# Mnożenie macierzowe
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
C = np.dot(A, B)
print(C)
# Wynik:
# [[19 22]
#  [43 50]]

# To samo za pomocą operatora @
C = A @ B
print(C)
[[19 22]
 [43 50]]
[[19 22]
 [43 50]]

14.3 Mnożenie macierz-wektor

Możemy również mnożyć macierz przez wektor:

import numpy as np
# Mnożenie macierz-wektor
A = np.array([[1, 2], [3, 4]])
v = np.array([5, 6])
result = np.dot(A, v)
print(result)  # Wynik: [17 39]
[17 39]

14.4 Rozwiązywanie układów równań liniowych

Funkcja numpy.linalg.solve rozwiązuje układy równań liniowych postaci Ax = b:

import numpy as np
# Rozwiązywanie układu równań liniowych
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = np.linalg.solve(A, b)
print(x)  # Wynik: [2. 3.]

# Sprawdzenie rozwiązania
np.dot(A, x)  # Powinno być równe b
[2. 3.]
array([9., 8.])

14.5 Wyznacznik macierzy

Funkcja numpy.linalg.det oblicza wyznacznik macierzy:

import numpy as np
# Obliczanie wyznacznika
A = np.array([[1, 2], [3, 4]])
det_A = np.linalg.det(A)
print(det_A)  # Wynik: -2.0
-2.0000000000000004

14.6 Wartości i wektory własne

Funkcja numpy.linalg.eig oblicza wartości i wektory własne macierzy:

import numpy as np
# Obliczanie wartości i wektorów własnych
A = np.array([[4, -2], [1, 1]])
eigenvalues, eigenvectors = np.linalg.eig(A)
print("Wartości własne:", eigenvalues)
print("Wektory własne:")
print(eigenvectors)

# Sprawdzenie: A * v = lambda * v
for i in range(len(eigenvalues)):
    lambda_i = eigenvalues[i]
    v_i = eigenvectors[:, i]
    print(f"λ_{i} = {lambda_i}")
    print("A * v =", np.dot(A, v_i))
    print("λ * v =", lambda_i * v_i)
Wartości własne: [3. 2.]
Wektory własne:
[[0.89442719 0.70710678]
 [0.4472136  0.70710678]]
λ_0 = 3.0
A * v = [2.68328157 1.34164079]
λ * v = [2.68328157 1.34164079]
λ_1 = 2.0
A * v = [1.41421356 1.41421356]
λ * v = [1.41421356 1.41421356]

14.7 Rozkład wartości osobliwych (SVD)

Rozkład SVD jest jedną z kluczowych metod stosowanych w analizie danych:

import numpy as np
# Rozkład SVD
A = np.array([[1, 2], [3, 4], [5, 6]])
U, s, Vh = np.linalg.svd(A)
print("Macierz U:")
print(U)
print("Wartości osobliwe:", s)
print("Macierz V^H:")
print(Vh)

# Rekonstrukcja macierzy A
S = np.zeros((A.shape[0], A.shape[1]))
S[:len(s), :len(s)] = np.diag(s)
A_reconstructed = U @ S @ Vh
print("Rekonstruowana macierz A:")
print(A_reconstructed)
Macierz U:
[[-0.2298477   0.88346102  0.40824829]
 [-0.52474482  0.24078249 -0.81649658]
 [-0.81964194 -0.40189603  0.40824829]]
Wartości osobliwe: [9.52551809 0.51430058]
Macierz V^H:
[[-0.61962948 -0.78489445]
 [-0.78489445  0.61962948]]
Rekonstruowana macierz A:
[[1. 2.]
 [3. 4.]
 [5. 6.]]

14.8 Norma macierzy/wektora

NumPy oferuje różne rodzaje norm wektorowych i macierzowych:

import numpy as np

v = np.array([3, 4])
# L1 (suma wartości bezwzględnych): |3| + |4| = 7
print("Norma L1:", np.linalg.norm(v, 1))
# L2 / euklidesowa (pierwiastek z sumy kwadratów): sqrt(9+16) = 5
print("Norma L2 (Euklidesowa):", np.linalg.norm(v))
# Norma maksimum (największa wartość bezwzględna): max(3,4) = 4
print("Norma maksimum:", np.linalg.norm(v, np.inf))

A = np.array([[1, 2], [3, 4]])
# Frobeniusa (jak L2, ale dla macierzy): sqrt(1+4+9+16) ≈ 5.477
print("Norma Frobeniusa:", np.linalg.norm(A, 'fro'))
Norma L1: 7.0
Norma L2 (Euklidesowa): 5.0
Norma maksimum: 4.0
Norma Frobeniusa: 5.477225575051661

14.9 Macierz odwrotna

Funkcja numpy.linalg.inv oblicza macierz odwrotną:

import numpy as np
# Macierz odwrotna
A = np.array([[1, 2], [3, 4]])
A_inv = np.linalg.inv(A)
print("Macierz odwrotna:")
print(A_inv)

# Sprawdzenie: A * A^(-1) = I
print("A * A^(-1):")
print(np.dot(A, A_inv))  # Powinno być bliskie macierzy jednostkowej
Macierz odwrotna:
[[-2.   1. ]
 [ 1.5 -0.5]]
A * A^(-1):
[[1.0000000e+00 0.0000000e+00]
 [8.8817842e-16 1.0000000e+00]]

14.10 Funkcja numpy.inner - iloczyn wewnętrzny

Funkcja inner oblicza iloczyn wewnętrzny dwóch tablic:

import numpy as np
# Iloczyn wewnętrzny
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = np.inner(a, b)
print(result)  # 1*4 + 2*5 + 3*6 = 32

# Dla tablic 2D: inner(A, B) oblicza A @ B.T (nie A @ B!)
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = np.inner(A, B)
print(result)
# [[1*5+2*6, 1*7+2*8],   = [[17, 23],
#  [3*5+4*6, 3*7+4*8]]      [39, 53]]
print("Porównanie z A @ B.T:")
print(A @ B.T)
32
[[17 23]
 [39 53]]
Porównanie z A @ B.T:
[[17 23]
 [39 53]]

14.11 Funkcja numpy.outer - iloczyn zewnętrzny

Funkcja outer oblicza iloczyn zewnętrzny dwóch wektorów:

import numpy as np
# Iloczyn zewnętrzny
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = np.outer(a, b)
print(result)
# Wynik:
# [[ 4  5  6]
#  [ 8 10 12]
#  [12 15 18]]
[[ 4  5  6]
 [ 8 10 12]
 [12 15 18]]

14.12 Funkcja numpy.matmul - mnożenie macierzowe

Funkcja matmul (i równoważny operator @) jest rekomendowanym sposobem mnożenia macierzy. Dla tablic 2D działa identycznie jak dot. Różnice pojawiają się dla tablic 3D+ (matmul traktuje je jako stosy macierzy) oraz dla skalarów (matmul nie akceptuje skalarów, dot tak).

import numpy as np

# Dla 2D -- identyczne wyniki
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

print("dot:", np.dot(a, b)[0])
print("matmul:", np.matmul(a, b)[0])

# Różnica: matmul nie akceptuje skalarów
try:
    np.matmul(a, 3)
except ValueError as e:
    print("matmul ze skalarem:", e)

print("dot ze skalarem:", np.dot(a, 3))
dot: [19 22]
matmul: [19 22]
matmul ze skalarem: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)
dot ze skalarem: [[ 3  6]
 [ 9 12]]