- Döngülü kodu vektörleştirilmiş NumPy ifadeleriyle yeniden yazmak ve hız farkını açıklamak
- Broadcasting kurallarıyla iki dizi arasındaki işlemin sonuç şeklini önceden belirlemek
@,np.linalg.solve,det,invveeighfonksiyonlarını matematiksel anlamlarıyla birlikte uygulamak
Murad bir çevrim içi mağazanın bir milyon satışını işliyor: her satış için tutarı hesaplamalı, indirim uygulamalı ve günlük değişimi bulmalı. for döngüsüyle bu saniyeler sürer, NumPy ile milisaniyeler. Sır vektörleştirmededir: işlemi her eleman için ayrı ayrı değil, dizinin tamamı için bir kez yazarız. Bu derste veri biliminin matematiksel dili olan doğrusal cebirle de çalışacağız.
Vektörleştirme: döngüsüz hesaplama
Elemanlar üzerindeki Python döngüsünü dizilerin tamamına uygulanan işlemlerle değiştirmek. Döngü yine vardır ama NumPy'ın içinde, derlenmiş C kodunda çalışır. Bu şekilde eleman eleman çalışan fonksiyonlara ufunc (evrensel fonksiyon) denir: +, *, np.sqrt, np.exp, np.where vb.
Python döngüsü neden yavaştır? Her tekrarda yorumlayıcı bir komut okur, elemanın türünü kontrol eder ve sonuç için yeni bir float nesnesi oluşturur. NumPy ise bellekte art arda duran aynı türden sayılar için türü bir kez kontrol eder ve işlemcinin vektör (SIMD) komutlarını kullanır. Kodu çalıştır ve farkı kendi gözünle gör:
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')| Döngüyle | Vektörleştirilmiş |
|---|---|
| toplamı biriktiren bir değişken | a.sum(), a @ b |
her eleman için if / else | np.where(cond, x, y) |
| koşula uyan elemanları saymak | (a > 0).sum() |
| birikimli toplam | np.cumsum(a) |
| komşu elemanların farkı | np.diff(a) |
| en büyük değer ve konumu | a.max(), a.argmax() |
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))▸ Beklenen çıktı
[ 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.]
Broadcasting: farklı şekilli diziler
a * 10 yazdığında 10 sayısı sanki a ile aynı şekilde bir diziye “genişletilir”. Bu mekanizmaya broadcasting (yayınlama) denir ve yalnızca tek sayılarla değil, farklı şekilli dizilerle de çalışır. NumPy şekilleri sağdan sola karşılaştırır:
- Bir şeklin boyut sayısı azsa soluna 1'ler eklenir: (4,) → (1, 4).
- Her eksende iki uzunluk ya eşit olmalı ya da biri 1 olmalıdır.
- Uzunluğu 1 olan eksen, diğer dizinin uzunluğuna kadar “uzatılır” (bellekte kopyalanmadan); sonucun şekli her eksendeki en büyük değerdir.
- Herhangi bir eksende uzunluklar farklıysa ve hiçbiri 1 değilse
ValueErroroluşur.
| A | B | Sonuç |
|---|---|---|
| (3, 4) | (4,) | (3, 4) |
| (3, 1) | (1, 4) | (3, 4) |
| (2, 1, 5) | (3, 1) | (2, 3, 5) |
| (3, 4) | (3,) | hata: 4 ≠ 3 |
Broadcasting'in klasik bir uygulaması standartlaştırmadır: her sütundan ortalamasını çıkarıp standart sapmasına böleriz; böylece farklı ölçekteki öznitelikler (cm cinsinden boy, kg cinsinden ağırlık) karşılaştırılabilir hâle gelir. Şekli (4, 2) olan diziden şekli (2,) olan ortalama çıkarılır; kural 1 ve 3 işi yapar.
- xᵢⱼi. örneğin j. özniteliği
- μⱼ, σⱼj. sütunun ortalaması ve standart sapması
- zᵢⱼstandartlaştırılmış değer (z-puanı): ortalaması 0, standart sapması 1
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))▸ Beklenen çıktı
[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.]
Bir sütun vektörüyle bir satır vektörünü birleştirince “her biri her biriyle” tablosu elde edilir. a[:, np.newaxis] (ya da a[:, None]) şekli (3,) olan diziyi (3, 1) sütununa çevirir:
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])▸ Beklenen çıktı
(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.]
Her işlemin sonuç şeklini bul ya da hata vereceğini göster: a) (5, 1, 3) + (4, 3); b) (8, 1) · (1, 6); c) (2, 3) + (2,); d) (256, 256, 3) · (3,).
Çözümü gösterÇözümü gizle
b) 8 ve 1 → 8, 1 ve 6 → 6. Sonuç (8, 6): “her biri her biriyle” tablosu.
c) Sağdan: 3 ve 2 eşit değil ve hiçbiri 1 değil → ValueError.
d) Son eksenler 3 ve 3 uyuşuyor: sonuç (256, 256, 3). Böylece bir görüntünün her pikselindeki R, G, B kanalları üç farklı katsayıyla çarpılır.
Şekli değiştirmek
reshape aynı elemanları farklı bir şekilde gösterir; eleman sayısı değişmemelidir, boyutlardan biri ise -1 olabilir, NumPy onu kendisi hesaplar. .T devrik alır, ravel() diziyi tek boyuta indirir (mümkünse görünüm döndürür), np.vstack, np.hstack ve np.concatenate ise dizileri birleştirir.
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)▸ Beklenen çıktı
[[ 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)
Doğrusal cebir: np.linalg
Matrislerin çarpımı eleman eleman çarpma değildir. A * B karşılık gelen elemanları çarpar; A @ B (ya da np.dot) ise matematiksel matris çarpımını hesaplar: A'nın bir satırı B'nin bir sütunuyla skaler olarak çarpılır. Bunun için A'nın sütun sayısı B'nin satır sayısına eşit olmalıdır.
- aᵢₖA matrisinin i. satır, k. sütun elemanı
- bₖⱼB matrisinin k. satır, j. sütun elemanı
- nA'nın sütun ve B'nin satır sayısı (ortak)
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)▸ Beklenen çıktı
[[ 5 12] [21 32]] [[19 22] [43 50]] (2, 2) (2, 5)
Bir doğrusal denklem sistemi matris biçiminde Ax = b olarak yazılır. det A ≠ 0 ise sistemin tek bir çözümü vardır ve A'nın tersi mevcuttur: x = A⁻¹b. 2 × 2 bir matris için her şey elle hesaplanabilir:
- det Adeterminant; 0 ise matris tekildir ve tersi yoktur
- A⁻¹ters matris: A · A⁻¹ = I (birim matris)
3 defter ve 2 kalem 5,5 manat, 1 defter ve 4 kalem 3,5 manat. Bir defterin ve bir kalemin fiyatını matris yöntemiyle bul.
Çözümü gösterÇözümü gizle
det A = 3 · 4 − 2 · 1 = 10 ≠ 0; tek bir çözüm 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).
Defter 1,5 manat, kalem 0,5 manat. Kontrol: 3 · 1,5 + 2 · 0,5 = 5,5 ✓.
Üç bilinmeyenli bir sistemde (defter, kalem, cetvel) elle hesap uzar; NumPy ise onu tek satırda çözer:
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))▸ Beklenen çıktı
[1.5 0.5 2. ] True -30.0 True
np.allclose, çözümün denklemleri sağladığını kontrol eder; ekrandaki determinant yuvarlanmıştır.Özvektör, A matrisinin yalnızca uzattığı ya da kısalttığı, yönünü değiştirmediği sıfırdan farklı bir v vektörüdür; uzama katsayısı λ ise özdeğerdir. λ değerlerini karakteristik denklemden buluruz. Uygulamaları: temel bileşenler analizi (PCA), Google'ın PageRank algoritması, köprü ve binaların doğal titreşim frekansları, dinamik sistemlerin kararlılığı.
- vözvektör (v ≠ 0)
- λözdeğer
- Ibirim matris
İkinci eşitlik karakteristik denklemdir: (A − λI)v = 0 sisteminin sıfırdan farklı bir çözümü ancak determinant 0 olduğunda vardır.
A = [[2, 1], [1, 2]] matrisinin özdeğerlerini ve özvektörlerini bul.
Çözümü gösterÇözümü gizle
λ = 3: (A − 3I)v = 0 → −v₁ + v₂ = 0 → v = (1; 1).
λ = 1: v₁ + v₂ = 0 → v = (1; −1).
Matris (1; 1) yönünü 3 kat uzatır, (1; −1) yönünü ise değiştirmez.
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))▸ Beklenen çıktı
[1. 3.] True 3.0 3.0 4.0 4.0
eigh kullan: özdeğerleri artan sırada döndürür. Vektörler 1 uzunluğunda olacak şekilde normalleştirilir ama işaretleri kütüphaneye göre değişebilir.Doğrusal cebir doğrudan makine öğrenmesine götürür. Noktalardan geçen en iyi doğruyu bulmak (en küçük kareler yöntemi) normal denkleme varır. X matrisine birlerden oluşan bir sütun eklersek sabit terim de otomatik olarak bulunur:
- Xn × d öznitelik matrisi (ilk sütun birlerden oluşur)
- yhedef değerler vektörü
- wkaresel hataların toplamını en küçük yapan katsayılar
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))▸ Beklenen çıktı
[1.236 2.458] [1.236 2.458]
np.linalg.lstsq kullanılır; scikit-learn'deki LinearRegression da aynı problemi çözer.X matrisinin her sütununu döngüsüz, broadcasting ile [0; 1] aralığına getir (min–max normalleştirme): (X − sütunun minimumu) / (sütunun maksimumu − sütunun minimumu). Sonucu 2 ondalık basamağa yuvarlayarak yazdır.
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▸ Beklenen çıktı
[[0. 0. 0. ] [0.33 0.67 0.5 ] [0.67 0.33 0.25] [1. 1. 1. ]]
Pazarda: 2 kg elma + 1 kg armut + 1 kg üzüm = 9,5 manat; 1 kg elma + 2 kg armut + 1 kg üzüm = 10 manat; 1 kg elma + 1 kg armut + 3 kg üzüm = 13,5 manat. np.linalg.solve ile 1 kg elma, armut ve üzümün fiyatını bulup 2 ondalık basamakla yazdır, ardından matrisin determinantını 1 ondalık basamakla yazdır.
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▸ Beklenen çıktı
[2. 2.5 3. ] 7.0
Önemli noktalar
- Vektörleştirme Python döngülerini C'de çalışan ufunc'larla değiştirir ve kodu onlarca, yüzlerce kat hızlandırır.
- Broadcasting şekilleri sağdan karşılaştırır: uzunluklar eşit olmalı ya da biri 1 olmalıdır.
v[:, None]vekeepdims=True, vektörü sütuna çevirerek şekilleri uyumlu hâle getirir.*eleman eleman çarpar,@matris çarpımıdır: (m × n) @ (n × p) → (m × p).- Ax = b sistemini
np.linalg.solveile çöz; det A = 0 olduğunda tek bir çözüm yoktur. - Av = λv; özdeğerlerin toplamı ize, çarpımı determinanta eşittir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.