Məzmuna keç
Educora
Universitet25 dəq30 / 42

NumPy: vektorlaşdırma və xətti cəbr

Dövrləri massiv ifadələri ilə əvəz et, broadcasting qaydalarını mənimsə, massivlərin formasını dəyiş və `np.linalg` ilə tənliklər sistemini həll et, determinant və məxsusi qiymətləri tap.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Dövrlü kodu vektorlaşdırılmış NumPy ifadələri ilə yenidən yazmaq və sürət fərqini izah etmək
  • Broadcasting qaydaları ilə iki massivin nəticə formasını əvvəlcədən müəyyən etmək
  • @, np.linalg.solve, det, inv və eigh funksiyalarını riyazi mənası ilə birlikdə tətbiq etmək

Murad onlayn mağazanın bir milyon satışını emal edir: hər satış üçün məbləği hesablamaq, endirim tətbiq etmək, gündəlik dəyişməni tapmaq lazımdır. for dövrü ilə bu, saniyələr çəkir, NumPy ilə isə millisaniyələr. Sirr vektorlaşdırmadadır: əməliyyatı hər element üçün ayrıca yox, bütöv massiv üçün bir dəfə yazırıq. Bu dərsdə həm də verilənlər elminin riyazi dili olan xətti cəbrlə işləyəcəyik.

Vektorlaşdırma: dövrsüz hesablamalar

Tərif
Vektorlaşdırma

Elementlər üzərində Python dövrünü bütöv massivlərə tətbiq olunan əməliyyatlarla əvəz etmək. Dövr yenə də var, amma o, NumPy-ın daxilində kompilyasiya olunmuş C kodunda işləyir. Belə elementbəelement işləyən funksiyalara ufunc (universal function) deyilir: +, *, np.sqrt, np.exp, np.where və s.

Python dövrü niyə yavaşdır? Hər təkrarda interpretator əmri oxuyur, elementin tipini yoxlayır və nəticə üçün yeni float obyekti yaradır. NumPy isə yaddaşda ardıcıl duran eyni tipli ədədlər üzərində bir dəfə tip yoxlaması aparır və prosessorun vektor (SIMD) əmrlərindən istifadə edir. Kodu işə sal və fərqi öz gözünlə gör:

Python
import time
import numpy as np

x = np.random.default_rng(0).random(1_000_000)

t0 = time.perf_counter()
total = 0.0
for v in x:
    total += v * v
t_loop = time.perf_counter() - t0

t0 = time.perf_counter()
total_vec = np.dot(x, x)
t_vec = time.perf_counter() - t0

print(f'loop:       {t_loop:.4f} s')
print(f'vectorized: {t_vec:.4f} s')
print('same result:', bool(np.isclose(total, total_vec)))
print(f'speed-up: about {t_loop / t_vec:.0f} times')
Vaxtlar kompüterdən və brauzerdən asılıdır, ona görə burada hazır nəticə verilmir. Adətən sürət fərqi onlarla, hətta yüzlərlə dəfədir.
DövrləVektorlaşdırılmış
cəmi toplayan sayğaca.sum(), a @ b
hər elementdə if / elsenp.where(cond, x, y)
şərtə uyğun elementləri saymaq(a > 0).sum()
yığılan cəmnp.cumsum(a)
qonşu elementlərin fərqinp.diff(a)
ən böyüyü və onun yeria.max(), a.argmax()
Python
import numpy as np

prices = np.array([12.0, 45.5, 8.9, 120.0, 64.0])
qty = np.array([3, 1, 10, 1, 2])

revenue = prices * qty
discount = np.where(revenue > 100, 0.10, 0.0)
to_pay = (revenue * (1 - discount)).round(2)
print(revenue)
print(to_pay, to_pay.sum())

daily = np.array([520.0, 610.0, 580.0, 700.0, 655.0])
print(np.diff(daily))
print((np.diff(daily) / daily[:-1] * 100).round(1))
print(np.cumsum(daily))
▸ Gözlənilən nəticə
[ 36.   45.5  89.  120.  128. ]
[ 36.   45.5  89.  108.  115.2] 393.7
[ 90. -30. 120. -45.]
[17.3 -4.9 20.7 -6.4]
[ 520. 1130. 1710. 2410. 3065.]
100 manatdan baha alışlara 10% endirim bir sətirdə tətbiq olunur. Sonra gündəlik gəlirin dəyişməsi (manat və faizlə) və yığılan cəm hesablanır.

Broadcasting: fərqli formalı massivlər

a * 10 yazanda 10 ədədi sanki a ilə eyni formalı massivə «genişləndirilir». Bu mexanizmə broadcasting (yayılma) deyilir və o, təkcə ədədlərlə deyil, fərqli formalı massivlərlə də işləyir. NumPy formaları sağdan sola müqayisə edir:

  1. Ölçülərin sayı azdırsa, formanın soluna 1-lər əlavə olunur: (4,) → (1, 4).
  2. Hər ox üzrə iki uzunluq ya bərabər olmalıdır, ya da onlardan biri 1 olmalıdır.
  3. Uzunluğu 1 olan ox digər massivin uzunluğuna qədər «uzadılır» (yaddaşda surət çıxarılmadan); nəticənin forması hər ox üzrə maksimumdur.
  4. Hər hansı oxda uzunluqlar fərqlidirsə və heç biri 1 deyilsə — ValueError.
ABNəticə
(3, 4)(4,)(3, 4)
(3, 1)(1, 4)(3, 4)
(2, 1, 5)(3, 1)(2, 3, 5)
(3, 4)(3,)xəta: 4 ≠ 3

Broadcasting-in klassik tətbiqi standartlaşdırmadır: hər sütundan onun ortasını çıxarıb standart meylinə bölürük ki, müxtəlif miqyaslı əlamətlər (boy sm-lə, çəki kq-la) müqayisə oluna bilsin. Forması (4, 2) olan massivdən forması (2,) olan orta çıxılır — qayda 1 və 3 işləyir.

zᵢⱼ = (xᵢⱼ − μⱼ) / σⱼzᵢⱼ = (xᵢⱼ − μⱼ) / σⱼ
burada:
  • xᵢⱼi-ci nümunənin j-ci əlaməti
  • μⱼ, σⱼj-ci sütunun ortası və standart meyli
  • zᵢⱼstandartlaşdırılmış qiymət (z-bal): ortası 0, standart meyli 1
Python
import numpy as np

X = np.array([[170.0, 65.0],
              [160.0, 52.0],
              [182.0, 80.0],
              [175.0, 71.0]])
mean = X.mean(axis=0)
std = X.std(axis=0)
Z = (X - mean) / std
print(mean, std.round(3))
print(Z.round(2))
print(np.allclose(Z.mean(axis=0), 0), Z.std(axis=0).round(6))
▸ Gözlənilən nəticə
[171.75  67.  ] [ 8.012 10.173]
[[-0.22 -0.2 ]
 [-1.47 -1.47]
 [ 1.28  1.28]
 [ 0.41  0.39]]
True [1. 1.]

Sütun vektoru ilə sətir vektorunu birləşdirəndə «hər biri hər biri ilə» cədvəli alınır. a[:, np.newaxis] (və ya a[:, None]) forması (3,) olan massivi (3, 1) sütununa çevirir:

Python
import numpy as np

a = np.arange(1, 4)
b = np.arange(1, 6)
table = a[:, np.newaxis] * b
print(table.shape)
print(table)

M = np.ones((3, 4))
v = np.array([10.0, 20.0, 30.0])
try:
    M + v
except ValueError as e:
    print('Error:', str(e).strip())
print((M + v[:, None])[:, 0])
▸ Gözlənilən nəticə
(3, 5)
[[ 1  2  3  4  5]
 [ 2  4  6  8 10]
 [ 3  6  9 12 15]]
Error: operands could not be broadcast together with shapes (3,4) (3,)
[11. 21. 31.]
Nümunə 1: nəticənin formasını tap

Aşağıdakı əməliyyatların nəticə formasını tap və ya xəta olacağını göstər: a) (5, 1, 3) + (4, 3); b) (8, 1) · (1, 6); c) (2, 3) + (2,); d) (256, 256, 3) · (3,).

Həllini göstər
a) (4, 3) → (1, 4, 3); oxlar üzrə: 5 və 1 → 5, 1 və 4 → 4, 3 və 3 → 3. Nəticə (5, 4, 3).
b) 8 və 1 → 8, 1 və 6 → 6. Nəticə (8, 6) — «hər biri hər biri ilə» cədvəli.
c) Sağdan: 3 və 2 — bərabər deyil və heç biri 1 deyil → ValueError.
d) Son oxlar 3 və 3 üst-üstə düşür: nəticə (256, 256, 3). Beləliklə, şəklin hər pikselinin R, G, B kanalları üç müxtəlif əmsala vurulur.

Formanı dəyişmək

reshape eyni elementləri başqa formada göstərir; elementlərin sayı dəyişməməlidir, ölçülərdən biri isə -1 ola bilər — NumPy onu özü hesablayır. .T transponə edir, ravel() massivi birölçülü edir (mümkün olanda görünüş qaytarır), np.vstack, np.hstack və np.concatenate isə massivləri yapışdırır.

Python
import numpy as np

a = np.arange(12)
print(a.reshape(3, 4))
print(a.reshape(2, -1).shape)
m = a.reshape(3, 4)
print(m.T.shape, m.ravel()[:5])
print(np.vstack([m[0], m[1]]))
print(np.concatenate([m, m], axis=1).shape)
▸ Gözlənilən nəticə
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(2, 6)
(4, 3) [0 1 2 3 4]
[[0 1 2 3]
 [4 5 6 7]]
(3, 8)

Xətti cəbr: np.linalg

Matrislərin hasili elementbəelement vurma deyil. A * B uyğun elementləri vurur, A @ B (və ya np.dot) isə riyazi matris hasilini hesablayır: A-nın sətri B-nin sütununa skalyar vurulur. Bunun üçün A-nın sütunlarının sayı B-nin sətirlərinin sayına bərabər olmalıdır.

cᵢⱼ = ∑ₖ₌₁ⁿ aᵢₖ · bₖⱼ (m × n) @ (n × p) → (m × p)
burada:
  • aᵢₖA matrisinin i-ci sətir, k-cı sütundakı elementi
  • bₖⱼB matrisinin k-cı sətir, j-ci sütundakı elementi
  • nA-nın sütunlarının və B-nin sətirlərinin ortaq sayı
Python
import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A * B)
print(A @ B)
print(np.dot(A, B).shape, (np.ones((2, 3)) @ np.ones((3, 5))).shape)
▸ Gözlənilən nəticə
[[ 5 12]
 [21 32]]
[[19 22]
 [43 50]]
(2, 2) (2, 5)
Yoxla: (A @ B)₀₀ = 1 · 5 + 2 · 7 = 19. Elementbəelement hasil isə 1 · 5 = 5 verir.

Xətti tənliklər sistemi matris şəklində Ax = b kimi yazılır. det A ≠ 0 olarsa, sistemin yeganə həlli var və A-nın tərs matrisi mövcuddur: x = A⁻¹b. 2 × 2 matris üçün hər şeyi əl ilə hesablamaq olar:

A = [[a, b], [c, d]]: det A = a·d − b·c A⁻¹ = (1 / det A) · [[d, −b], [−c, a]]A = [[a, b], [c, d]]: det A = a·d − b·c A⁻¹ = (1 / det A) · [[d, −b], [−c, a]]
burada:
  • det Adeterminant; 0-dırsa, matris cırlaşmışdır və tərsi yoxdur
  • A⁻¹tərs matris: A · A⁻¹ = I (vahid matris)
Nümunə 2: dəftər və qələm

3 dəftər və 2 qələm 5,5 manat, 1 dəftər və 4 qələm 3,5 manatdır. Dəftər və qələmin qiymətini matris üsulu ilə tap.

Həllini göstər
A = [[3, 2], [1, 4]], b = (5,5; 3,5).
det A = 3 · 4 − 2 · 1 = 10 ≠ 0 — yeganə həll var.
A⁻¹ = (1/10) · [[4, −2], [−1, 3]].
x = A⁻¹b = (1/10) · (4 · 5,5 − 2 · 3,5; −5,5 + 3 · 3,5) = (1/10) · (15; 5) = (1,5; 0,5).
Dəftər 1,5 manat, qələm 0,5 manat. Yoxlama: 3 · 1,5 + 2 · 0,5 = 5,5 ✓.

Üç naməlumlu sistemdə (dəftər, qələm, xətkeş) əl ilə hesablama uzanır, NumPy isə onu bir sətirdə həll edir:

Python
import numpy as np

A = np.array([[2.0, 1.0, 3.0],
              [1.0, 3.0, 1.0],
              [4.0, 2.0, 0.0]])
b = np.array([9.5, 5.0, 7.0])

x = np.linalg.solve(A, b)
print(x.round(2))
print(np.allclose(A @ x, b))
print(round(np.linalg.det(A), 2))
x_inv = np.linalg.inv(A) @ b
print(np.allclose(x, x_inv))
▸ Gözlənilən nəticə
[1.5 0.5 2. ]
True
-30.0
True
Dəftər 1,5, qələm 0,5, xətkeş 2 manat. np.allclose həllin tənlikləri ödədiyini yoxlayır; ekrandakı determinant yuvarlaqlaşdırılıb.

Məxsusi vektor elə sıfırdan fərqli v vektorudur ki, A matrisi onu yalnız uzadır və ya qısaldır, istiqamətini dəyişmir; uzanma əmsalı λ məxsusi qiymətdir. λ-ları xarakteristik tənlikdən tapırıq. Tətbiqləri: əsas komponentlər üsulu (PCA), Google-un PageRank alqoritmi, körpü və binaların məxsusi rəqs tezlikləri, dinamik sistemlərin dayanıqlığı.

A · v = λ · v det(A − λ · I) = 0
burada:
  • vməxsusi vektor (v ≠ 0)
  • λməxsusi qiymət
  • Ivahid matris

İkinci bərabərlik xarakteristik tənlikdir: (A − λI)v = 0 sisteminin sıfırdan fərqli həlli yalnız determinant 0 olanda var.

Nümunə 3: 2 × 2 matrisin məxsusi qiymətləri

A = [[2, 1], [1, 2]] matrisinin məxsusi qiymətlərini və vektorlarını tap.

Həllini göstər
det(A − λI) = (2 − λ)² − 1 = 0 → 2 − λ = ±1 → λ₁ = 1, λ₂ = 3.
λ = 3: (A − 3I)v = 0 → −v₁ + v₂ = 0 → v = (1; 1).
λ = 1: v₁ + v₂ = 0 → v = (1; −1).
Matris (1; 1) istiqamətini 3 dəfə uzadır, (1; −1) istiqamətini isə dəyişmir.
Python
import numpy as np

A = np.array([[2.0, 1.0], [1.0, 2.0]])
values, vectors = np.linalg.eigh(A)
print(values.round(4))
v = vectors[:, 1]
print(np.allclose(A @ v, values[1] * v))
print(round(np.linalg.det(A), 4), round(values.prod(), 4))
print(round(np.trace(A), 4), round(values.sum(), 4))
▸ Gözlənilən nəticə
[1. 3.]
True
3.0 3.0
4.0 4.0
Simmetrik matrislər üçün eigh işlət: məxsusi qiymətləri artan sıra ilə qaytarır. Vektorlar uzunluğu 1 olacaq şəkildə normallaşdırılır, işarələri isə kitabxanadan asılı olaraq fərqli ola bilər.

Xətti cəbr maşın öyrənməsinə birbaşa aparır. Nöqtələrdən ən yaxşı düz xətti keçirmək (ən kiçik kvadratlar üsulu) normal tənliyə gətirir. X matrisinə birlərdən ibarət sütun əlavə etsək, sərbəst hədd də avtomatik tapılır:

w = (Xᵀ X)⁻¹ Xᵀ y
burada:
  • Xn × d əlamətlər matrisi (birinci sütun birlərdir)
  • yhədəf qiymətlər vektoru
  • wkvadratik xətaların cəmini minimuma endirən əmsallar
Python
import numpy as np

rng = np.random.default_rng(7)
x = rng.uniform(0, 10, 50)
y = 2.5 * x + 1.0 + rng.normal(0, 1, 50)
X = np.column_stack([np.ones_like(x), x])
w = np.linalg.solve(X.T @ X, X.T @ y)
print(w.round(3))
w2, *_ = np.linalg.lstsq(X, y, rcond=None)
print(w2.round(3))
▸ Gözlənilən nəticə
[1.236 2.458]
[1.236 2.458]
Verilənlər y = 2,5x + 1 düz xəttindən küy əlavə etməklə yaradılıb; tapılan sərbəst hədd ≈ 1,24, bucaq əmsalı ≈ 2,46. Praktikada daha dayanıqlı olan np.linalg.lstsq işlədilir; scikit-learn-dəki LinearRegression də eyni məsələni həll edir.
Tapşırıq

X matrisinin hər sütununu dövrsüz, broadcasting ilə [0; 1] aralığına gətir (min–max normallaşdırma): (X − sütunun minimumu) / (sütunun maksimumu − sütunun minimumu). Nəticəni 2 onluq rəqəmə qədər yuvarlaqlaşdırıb çap et.

Tapşırıq · Python
import numpy as np

X = np.array([[2.0, 50.0, 1.0],
              [4.0, 80.0, 3.0],
              [6.0, 65.0, 2.0],
              [8.0, 95.0, 5.0]])
# min-max scale every column
▸ Gözlənilən nəticə
[[0.   0.   0.  ]
 [0.33 0.67 0.5 ]
 [0.67 0.33 0.25]
 [1.   1.   1.  ]]
Tapşırıq

Bazarda: 2 kq alma + 1 kq armud + 1 kq üzüm = 9,5 manat; 1 kq alma + 2 kq armud + 1 kq üzüm = 10 manat; 1 kq alma + 1 kq armud + 3 kq üzüm = 13,5 manat. np.linalg.solve ilə 1 kq alma, armud və üzümün qiymətini tapıb 2 onluq rəqəmlə çap et, sonra matrisin determinantını 1 onluq rəqəmlə çap et.

Tapşırıq · Python
import numpy as np

# A: kilograms in each purchase, b: amounts paid (manat)
A = np.array([[2.0, 1.0, 1.0],
              [1.0, 2.0, 1.0],
              [1.0, 1.0, 3.0]])
b = np.array([9.5, 10.0, 13.5])
# solve A x = b and print the prices, then the determinant
▸ Gözlənilən nəticə
[2.  2.5 3. ]
7.0

Əsas fikirlər

  • Vektorlaşdırma Python dövrünü C-də işləyən ufunc-larla əvəz edir və kodu onlarla, yüzlərlə dəfə sürətləndirir.
  • Broadcasting formaları sağdan müqayisə edir: uzunluqlar bərabər olmalı və ya biri 1 olmalıdır.
  • v[:, None] və keepdims=True vektoru sütuna çevirərək formaları uyğunlaşdırır.
  • * elementbəelement, @ isə matris hasilidir: (m × n) @ (n × p) → (m × p).
  • Ax = b sistemini np.linalg.solve ilə həll et; det A = 0 olanda yeganə həll yoxdur.
  • Av = λv; məxsusi qiymətlərin cəmi iz, hasili determinantdır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Forması (4, 1) olan massivlə forması (3,) olan massivi toplayanda nəticənin forması nə olur?