Skip to content

Matrix Decompositions: Eigendecomposition, SVD, PCA, and the Secret of LoRA

The art of decomposing a matrix into its 'DNA'. Eigendecomposition and SVD, building PCA from SVD from scratch, the mathematical foundation of LoRA — why low-rank updates suffice. Embedding compression practice.

Şükrü Yusuf KAYA
42 min read
Intermediate
Matris Ayrıştırmaları: Eigendecomposition, SVD, PCA ve LoRA'nın Sırrı
🧬 Bu derste bir matrisi 'genom'una ayırmayı öğreneceğiz
Lineer cebirin en güzel teorem'lerinden biri: her gerçek matris SVD ile üç matrise ayrılabilir. Bu, sadece teorik bir gerçek değil — LoRA, PCA, embedding compression, image compression, recommender sistemler hepsi bu teoremin pratik karşılığı. 42 dakika sonra LoRA paper'ını okurken 'Aha, bu sadece truncated SVD'ymiş' diyeceksin.

Ders Haritası#

  1. Eigendecomposition: bir kare matrisin "ekseni" ne demek?
  2. Diagonalization: hesap kolaylığı
  3. SVD: her matris için geçerli teorem
  4. SVD ⇔ Eigendecomp ilişkisi
  5. PCA'yı SVD ile sıfırdan inşa
  6. Truncated SVD ve düşük-rank yaklaşım
  7. LoRA'nın matematiksel temeli
  8. Embedding compression pratiği
  9. Sayısal stabilite: condition number, pseudoinverse

1. Eigendecomposition — Bir Matrisin "Özel Eksenleri"#

Bir kare matris
A
(n×n), aslında bir lineer dönüşüm'dür: girdi vektörünü alıp başka bir vektöre eşler. Çoğu vektörü bu dönüşüm karıştırır — yön değişir, uzunluk değişir.
Ama bazı vektörler özel: dönüşümden sonra yönü değişmez, sadece uzunluğu değişir.
Bu özel vektörlere eigenvector (özvektör), uzunluk değişim katsayısına eigenvalue (özdeğer) denir.
Av=λvA \mathbf{v} = \lambda \mathbf{v}
Burada
v
eigenvector,
λ
eigenvalue.

Sezgi: dönen Dünya'nın ekseni#

Dünya kendi etrafında döner. Çoğu noktayı hareket ettirir. Ama kuzey ve güney kutuplarındaki noktalar sabit kalır (sadece dönme ekseni üzerinde). Bu eksen = Dünya'nın "eigenvector"'ü.
Matematiksel olarak: 3D rotasyon matrisinin eigenvector'ü dönme eksenidir. Eigenvalue = 1 (çünkü uzunluk korunur).

Bir matrisin tüm eigenvalue'larını bul#

Karakteristik polinom: det(AλI)=0\det(A - \lambda I) = 0
Bu n. dereceden bir polinom; çözümleri eigenvalue'lar. Her eigenvalue için
(A - λI) v = 0
çözerek eigenvector bulunur.
n × n
matris için n eigenvalue var (çakışık olabilir, kompleks olabilir).
python
import torch
 
# Basit 2x2 örnek — simetrik
A = torch.tensor([[4., 2.],
[2., 3.]])
 
eigenvalues, eigenvectors = torch.linalg.eig(A)
print("Eigenvalues:", eigenvalues.real) # tensor([5.5616, 1.4384])
print("Eigenvectors:")
print(eigenvectors.real)
# tensor([[ 0.7882, -0.6154],
# [ 0.6154, 0.7882]])
 
# Doğrulama: A @ v ≈ λ * v
v1 = eigenvectors[:, 0].real
lam1 = eigenvalues[0].real
print("A v1 =", A @ v1) # tensor([4.3835, 3.4221])
print("λ v1 =", lam1 * v1) # tensor([4.3835, 3.4221]) ✓
 
# Eigenvalue'lar bize geometrik bilgi verir:
# - Pozitif → eksende uzatma
# - Negatif → ters çevirme
# - Tüm |λ| < 1 → her uygulamada vektör küçülür (contraction)
# - Tüm |λ| = 1 → izometri (uzunluk korunur, örn. rotation)
# - Bazı |λ| > 1 → bazı yönlerde uzatma
Eigendecomposition — PyTorch ile.
💡 Simetrik matrisler altın gibidir
Bir matris simetrik ise (A = A^T) ve gerçekse: (1) tüm eigenvalue'lar gerçek, (2) eigenvector'lar ortogonal (birbirine dik). LLM'de kovaryans, gram matrisi, attention'da bazı yapılar simetriktir. `torch.linalg.eigh` (h = Hermitian) bu durumda daha hızlı ve sayısal olarak stabil.

2. SVD — Singular Value Decomposition#

Eigendecomposition güzel ama sadece kare matrisler için tanımlı (ve hatta her kare matris için garanti çalışmıyor).
SVD ise her matris için çalışır — kare olmayan, full-rank olmayan, kompleks bile.
A=UΣVTA = U \Sigma V^T
burada:
  • A
    ∈ ℝ^{m×n}
  • U
    ∈ ℝ^{m×m}, ortogonal (sütunları sol singular vector'lar)
  • Σ
    ∈ ℝ^{m×n}, diagonal, non-negative (singular value'lar)
  • V
    ∈ ℝ^{n×n}, ortogonal (sütunları sağ singular vector'lar)
Geometrik yorum: Her lineer dönüşüm üç adıma ayrılabilir:
  1. V^T
    : rotasyon (sağda)
  2. Σ
    : eksenlerde ölçekleme
  3. U
    : rotasyon (solda)
Yani rotation → scaling → rotation.

Singular value'lar = matrisin "önem dereceleri"#

Σ
'nin köşegeninde
σ_1 ≥ σ_2 ≥ ... ≥ σ_r ≥ 0
sıralanmış sayılar (r = rank). Bu sayılar matrisin "bilgi içeriği"ni hierarchically temsil eder.
  • σ_1
    en büyük: matrisin en baskın "yönü"
  • σ_i = 0
    : o yönde "bilgi yok"
Matrisin rank'i = sıfır olmayan singular value'ların sayısıdır.

SVD'nin "toplam" formu#

Yukarıdaki ikinci formül kritik: bir matris, r tane rank-1 matrisin ağırlıklı toplamıdır.
A=σ1u1v1T+σ2u2v2T++σrurvrTA = \sigma_1 \mathbf{u}_1 \mathbf{v}_1^T + \sigma_2 \mathbf{u}_2 \mathbf{v}_2^T + \dots + \sigma_r \mathbf{u}_r \mathbf{v}_r^T
İlk terim "en önemli bilgi", ikinci "ikinci önemli", vs.
LoRA'nın temeli: Eğer son k terimi küçükse (σ küçük), onları atıp matrisi yaklaşık olarak tutabiliriz. Buna truncated SVD denir.
python
import torch
 
# (4, 3) bir matris
torch.manual_seed(0)
A = torch.randn(4, 3)
print("A =")
print(A)
 
U, S, Vh = torch.linalg.svd(A, full_matrices=False)
# Vh = V^T (PyTorch konvansiyonu)
 
print("\nU shape:", U.shape) # (4, 3)
print("S (singular values):", S) # (3,)
print("V^T shape:", Vh.shape) # (3, 3)
 
# A'yı geri inşa et
A_reconstructed = U @ torch.diag(S) @ Vh
print("\nReconstruction error:", (A - A_reconstructed).norm().item()) # ~0
 
# rank-1 dekompozisyona ayır
# A = sigma_1 * u_1 * v_1^T + sigma_2 * u_2 * v_2^T + ...
A_rank1 = S[0] * torch.outer(U[:, 0], Vh[0, :])
A_rank2 = A_rank1 + S[1] * torch.outer(U[:, 1], Vh[1, :])
A_rank3 = A_rank2 + S[2] * torch.outer(U[:, 2], Vh[2, :])
 
print("\nrank-1 yaklaşım hatası:", (A - A_rank1).norm().item())
print("rank-2 yaklaşım hatası:", (A - A_rank2).norm().item())
print("rank-3 yaklaşım hatası:", (A - A_rank3).norm().item()) # ~0
SVD ile matris ayrıştırma + adım adım rank reconstruction.

3. Eigendecomposition ⇔ SVD İlişkisi#

Bu ikisinin ilişkisi cebirsel olarak güzel:
  • A^T A
    (n × n simetrik PSD matris) eigendecomposition'u: eigenvalue'lar
    σ_i²
    , eigenvector'lar
    v_i
    (V'nin sütunları).
  • A A^T
    (m × m simetrik PSD): eigenvalue'lar yine
    σ_i²
    , eigenvector'lar
    u_i
    (U'nun sütunları).
Yani: SVD = Gram matrisinin (A^T A) eigendecomposition'unun karekökü.
Bu, SVD'yi sayısal olarak hesaplamanın bir yolu (modern algoritmalar bunu daha akıllı yapıyor ama mantık aynı).

4. PCA (Principal Component Analysis) — SVD ile Sıfırdan#

PCA, verinin en yüksek varyans yönlerini bulup boyut indirme tekniğidir. SVD'nin uygulamasıdır.

Algoritma#

Verilen veri matrisi
X
∈ ℝ^{n × d} (n örnek, d özellik):
  1. Merkezle: her sütundan ortalama çıkar.
    X_c = X - mean(X, axis=0)
  2. SVD:
    X_c = U Σ V^T
  3. V
    'nin sütunları = principal component'lar (özellik uzayındaki ana yönler)
  4. Σ
    'nin köşegeni = her component'in varyansının karekökü
  5. Boyut indirme: ilk k component'i tut:
    X_reduced = X_c @ V[:, :k]

Sezgi#

X'in satırları bir bulut. PCA o bulutun en uzun eksenini bulur (en yüksek varyans), sonra ikinci en uzun (ilkine dik), vs. İlk k eksene projekte et → boyut k'ye iner ama maximum bilgi korunur.

LLM'de PCA?#

  • Embedding compression: 1536-d OpenAI embedding'i 768-d'ye sıkıştırmak
  • Visualization: high-d embedding'leri 2D'de plot
  • Outlier detection: PCA'dan sapan örnekler anormal
  • Whitening: input'u decorrelate etmek (eski ML)
python
import torch
import matplotlib.pyplot as plt
 
torch.manual_seed(42)
 
# Sentetik veri: 2D Gaussian, x ekseninde daha geniş
N = 200
data = torch.randn(N, 2) * torch.tensor([3.0, 0.5])
# Hafif rotation
theta = 0.5
R = torch.tensor([[torch.cos(torch.tensor(theta)), -torch.sin(torch.tensor(theta))],
[torch.sin(torch.tensor(theta)), torch.cos(torch.tensor(theta))]])
data = data @ R.T
 
# 1. Merkezle
mean = data.mean(dim=0)
X_c = data - mean
 
# 2. SVD
U, S, Vh = torch.linalg.svd(X_c, full_matrices=False)
 
# 3. Principal components
print("Variance per component:", (S ** 2 / (N - 1)))
print("First PC direction:", Vh[0])
print("Second PC direction:", Vh[1])
 
# 4. 1D'ye indir
X_reduced = X_c @ Vh.T[:, 0:1] # (N, 1)
print("Reduced shape:", X_reduced.shape)
 
# 5. Geri yansıt (lossy)
X_restored = X_reduced @ Vh[0:1, :] + mean
print("Reconstruction error:", (data - X_restored).norm().item())
 
# Görselleştirme (Colab/Jupyter'da)
# plt.scatter(data[:, 0], data[:, 1], alpha=0.5, label="Original")
# plt.scatter(X_restored[:, 0], X_restored[:, 1], alpha=0.5, label="1D Projection")
# plt.legend()
# plt.axis('equal')
# plt.show()
PCA'yı SVD ile sıfırdan, 200 satır 2D veri üzerinde.

5. Truncated SVD ve Düşük-Rank Yaklaşım#

Eckart-Young teoremi: Bir matrisi en iyi rank-k ile yaklaşıklamak istersen, SVD'yi yap ve ilk k singular value'yu tut.
Ak=i=1kσiuiviTA_k = \sum_{i=1}^{k} \sigma_i \mathbf{u}_i \mathbf{v}_i^T
A_k
, k-rank'lı tüm matrislerin arasında
A
'ya Frobenius-norm açısından en yakın olanıdır.

Sıkıştırma oranı#

Tam matris: m × n parametre. Rank-k SVD: m·k + k + k·n ≈ k(m + n) parametre.
Örnek: A = 4096×4096 (16.7M parametre). k=16: 16·(4096+4096) = 131K parametre. %99 sıkışma.
Hata:
||A - A_k||_F² = σ_{k+1}² + σ_{k+2}² + ...
— atılan singular value'ların karelerinin toplamı.
python
import torch
 
# Bir "doğal" düşük-rank matris simüle et:
# Gerçek rank 5, ama 64x64 boyutunda
torch.manual_seed(0)
A_low = torch.randn(64, 5) @ torch.randn(5, 64) # rank ≤ 5
A_noise = A_low + 0.1 * torch.randn(64, 64) # gürültü ekle
 
# SVD
U, S, Vh = torch.linalg.svd(A_noise, full_matrices=False)
 
# Singular value spectrum'unu yazdır
print("İlk 10 singular value:")
print(S[:10].tolist())
# İlk 5 büyük, sonra hızla düşer — "elbow" rank=5 civarında
 
# Truncated reconstruction
def truncate_svd(U, S, Vh, k):
return U[:, :k] @ torch.diag(S[:k]) @ Vh[:k, :]
 
for k in [1, 3, 5, 10, 20]:
A_k = truncate_svd(U, S, Vh, k)
err = (A_noise - A_k).norm() / A_noise.norm()
print(f"k={k:2d}: relative error = {err*100:.2f}%")
 
# Çıktı (yaklaşık):
# k= 1: relative error = 80.50%
# k= 3: relative error = 40.10%
# k= 5: relative error = 12.40% <- elbow
# k=10: relative error = 11.95%
# k=20: relative error = 11.85%
Truncated SVD ile spektrum analizi ve elbow tespiti.

6. LoRA'nın Sırrı: Hipotez ve Matematik#

LoRA (Low-Rank Adaptation, Hu et al. 2021) modern LLM fine-tuning'inin omurgası. Çoğu kişi onu "küçük matrisli LoRA" diye geçiyor; ama altında zarif bir hipotez var.

Hipotez#

"Pre-trained büyük modelin fine-tuning sırasında parametre değişimi (ΔW) düşük intrinsik rank'lıdır."
Yani, bir downstream task için pre-trained modeli adapte ederken, ağırlık değişimi tam-rank değil, küçük bir rank'la temsil edilebilir.

Matematiksel yapı#

Pre-trained weight:
W ∈ ℝ^{d × d}
. Fine-tuning sırasında
ΔW
'yi öğrenmek yerine, onu iki düşük-rank matrise ayır:
ΔW=BA,ARr×d,BRd×r\Delta W = B A, \quad A \in \mathbb{R}^{r \times d}, \quad B \in \mathbb{R}^{d \times r}
r ≪ d
. Eğitilen parametreler sadece A ve B. Adapted weight:
Wadapted=W+ΔW=W+BAW_{adapted} = W + \Delta W = W + B A
Inference'ta
BA
'yı önceden hesaplayıp
W
'ye ekleyebilirsin (zero latency overhead).

Parametre tasarrufu#

Llama 8B'nin
q_proj
weight'i 4096×4096 = 16.7M parametre. LoRA rank=16: A (16×4096) + B (4096×16) = 131K parametre. Tasarruf: %99.2.

Neden çalışıyor?#

Empirik gözlem (Hu et al. 2021):
r=4
veya
r=8
çoğu görevde yeterli. Bu, fine-tuning hareketinin gerçekten düşük-boyutlu manifold'da olduğunu gösteriyor.

SVD bağlantısı#

LoRA ≠ SVD ama akrabası. SVD bir matrisi düşük-rank ile yaklaşır; LoRA güncellemenin düşük-rank olduğunu öğreniyor. SVD'yi başlangıç initialization olarak kullanan PiSSA (2024) varyantı bu bağlantıyı kullanıyor.
python
import torch
import torch.nn as nn
 
class LoRALinear(nn.Module):
"""Pre-trained Linear üzerine LoRA adapter."""
def __init__(self, base_layer: nn.Linear, rank: int = 16, alpha: float = 32):
super().__init__()
self.base = base_layer
# Base'i dondur
for p in self.base.parameters():
p.requires_grad = False
 
d_out, d_in = base_layer.weight.shape
# A: küçük → büyük; init: gaussian
self.A = nn.Parameter(torch.randn(rank, d_in) * 0.01)
# B: büyük → küçük; init: sıfır → başta ΔW = 0
self.B = nn.Parameter(torch.zeros(d_out, rank))
 
self.rank = rank
self.alpha = alpha
self.scaling = alpha / rank
 
def forward(self, x):
# Base output
out = self.base(x)
# LoRA update: x @ A^T @ B^T (ya da B @ A operasyonunu transpose'lı yaz)
lora_out = (x @ self.A.T) @ self.B.T
return out + lora_out * self.scaling
 
# Test
base = nn.Linear(4096, 4096, bias=False)
lora = LoRALinear(base, rank=16)
print("Base params:", sum(p.numel() for p in base.parameters())) # 16.7M
print("LoRA trainable:", sum(p.numel() for p in lora.parameters() if p.requires_grad)) # 131K
 
x = torch.randn(2, 8, 4096)
y = lora(x)
print(y.shape) # (2, 8, 4096)
 
# Başlangıçta y = base(x) çünkü B = 0 → ΔW = 0
y_base = base(x)
print(torch.allclose(y, y_base)) # True (eğitim başlangıcında)
LoRA'yı sıfırdan PyTorch'ta — sadece 30 satır.
💡 LoRA'nın 'aha' momenti
Modül 21'de LoRA'yı production seviyesinde işleyeceğiz. Ama matematiksel iskelet şuradan: SVD bize 'her matris düşük-rank ile yaklaşıklanabilir' diyor. LoRA bunu 'fine-tuning sırasındaki güncelleme' için uyguluyor. Eğer ΔW gerçekten düşük-rank ise, o zaman tam-matris öğrenmek savurganlık. Bu içgörü 100M+ kez fine-tune edildi.

7. Rank, Condition Number, Sayısal Stabilite#

Rank#

Bir matrisin rank'i = lineer bağımsız satır (veya sütun) sayısı = sıfır olmayan singular value sayısı.
  • Full-rank: m × n matris için min(m,n) rank
  • Rank-deficient: daha az
  • Pratikte "yaklaşık rank": çok küçük singular value'lar sayılmaz

Condition number#

κ(A)=σ1σr\kappa(A) = \frac{\sigma_1}{\sigma_r}
En büyük ve en küçük (sıfır olmayan) singular value oranı. Sayısal hesaplamaların stabilitesini ölçer:
  • κ = 1: ideal (orthogonal matrix)
  • κ < 10: iyi
  • κ > 10^6: sayısal olarak tehlikeli (FP32 hassasiyeti yetersiz olabilir)
  • κ = ∞: singular matrix

LLM'de niye önemli?#

  • Attention matrisi bazen kondisyonsuz olabilir → loss spike
  • Fine-tuning'de condition number izlenir
  • Mixed precision (FP16) ile çalışırken iyi-kondisyonlu matrisler şart
python
import torch
 
# İyi kondisyonlu matris
A_good = torch.randn(100, 100)
print("Good κ:", torch.linalg.cond(A_good).item()) # ~100-1000 (rastgele Gaussian)
 
# Kötü kondisyonlu — son satır birinciye yakın
A_bad = torch.randn(100, 100)
A_bad[-1] = A_bad[0] + 1e-8 * torch.randn(100) # neredeyse aynı
print("Bad κ:", torch.linalg.cond(A_bad).item()) # 10^7+ — tehlikeli
 
# Hilbert matrisi — klasik kötü kondisyonlu örnek
H = torch.tensor([[1 / (i + j + 1) for j in range(5)] for i in range(5)])
print("Hilbert(5) κ:", torch.linalg.cond(H).item()) # 4.8e5 — zaten 5x5'te kötü
Condition number — iyi vs kötü matris.

8. Pseudoinverse — Square Olmayan Matrislerin "Tersi"#

Square olmayan veya singular bir matrisin tersi yok. Ama pseudoinverse (Moore-Penrose) her zaman var:
A+=VΣ+UTA^+ = V \Sigma^+ U^T
Σ^+
= Σ'nin transpoz'unda non-zero singular value'ların reciprocal'leri.
LLM'de: Bir over-determined linear regression çözmek (least squares).
x = A^+ b
en küçük
||Ax - b||²
veren çözüm.
import torch A = torch.randn(100, 5) b = torch.randn(100) # x = (A^T A)^-1 A^T b ya da pinv ile x = torch.linalg.pinv(A) @ b print(x.shape) # (5,)

9. Mini Egzersizler#

  1. Eigendecomp 2x2:
    A = [[2, 1], [1, 2]]
    . Eigenvalue'ları el ile bul (karakteristik polinom). Sonra eigenvector'ları.
  2. SVD shape oyunu:
    A
    shape (1000, 50).
    U
    ,
    Σ
    ,
    V
    'nin tam shape'leri ne? Truncated rank-10'da hangi shape'ler?
  3. PCA elbow: 1000 örneklik 100-d veri. PCA yaparsın, 90% varyansı korumak için kaç component yeter? (İpucu: cumulative explained variance.)
  4. LoRA tasarrufu: 70B Llama modelinde tüm attention katmanları LoRA rank-32 ile fine-tune edilir. Toplam eğitilebilir parametre sayısı? (İpucu: Llama 70B'de 80 layer, her layer 4 attention proj, hidden dim 8192.)
  5. Condition number: Bir LoRA matrisinin condition number'ı 10^9. Bu sorun mu? Neden? Nasıl tespit ederdin?

Bu Derste Neler Öğrendik?#

Eigendecomposition: bir matrisin "özel eksenleri" (eigenvector) + "ölçek faktörü" (eigenvalue) ✓ SVD: her matris için garantili
A = UΣV^T
ayrıştırması ✓ SVD'nin rank-1 toplamı formu — LoRA'nın matematiksel iskeleti ✓ PCA = SVD (merkezlenmiş veri üzerinde) ✓ Truncated SVD: en iyi düşük-rank yaklaşım (Eckart-Young) ✓ LoRA: ΔW'nin düşük-rank olduğu hipotezi, %99+ parametre tasarrufu ✓ Rank, condition number: sayısal stabilite göstergeleri ✓ Pseudoinverse: square-olmayan/singular matrisler için tersin yerine

Sıradaki Ders#

1.3 — Türev, Gradient ve Matrix Calculus Geri yayılım (backpropagation) aslında zincir kuralının tekrarlı uygulanmasıdır. Gradient, Jacobian, Hessian; numerator vs denominator layout; cross-entropy + softmax'ın türevinin neden bu kadar zarif olduğunu göreceksin.

Frequently Asked Questions

**Eigendecomposition**: for symmetric/square matrices when eigenvector and eigenvalues for geometric analysis (e.g., principal axes, mode analysis). **SVD**: any matrix — non-square, rank-deficient, fine-tuning updates. Practically, 95% of LLM engineering uses SVD. Eigendecomposition mainly appears in Hessian analysis, optimization landscape studies.

Yorumlar & Soru-Cevap

(0)
Yorum yazmak için giriş yap.
Yorumlar yükleniyor...

Related Content

Connected pillar topics

Pillar topics this article maps to