İçeriğe geç
Educora
Üniversite22 dk39 / 42

PyTorch: sinir ağları kurmak

Yapay nörondan çok katmanlı ağa: z = w·x + b, aktivasyon fonksiyonları (ReLU, sigmoid, tanh, softmax), nn.Linear katmanları, nn.Module ve nn.Sequential, ileri geçiş ve parametre sayısı.

Kendini test et
Bu derste öğreneceklerin
  • Bir nöronun ve tam bağlantılı bir katmanın matematiksel modelini yazmak
  • ReLU, sigmoid, tanh ve softmax'ı hesaplamak ve nerede kullanıldıklarını bilmek
  • nn.Module ve nn.Sequential ile bir ağ kurmak ve parametrelerini saymak

Elle yazılmış bir rakamı tanımak istiyoruz: görüntü 28 × 28 = 784 pikseldir, cevap ise 0'dan 9'a on rakamdan biridir. Hangi fonksiyon 784 sayıyı on olasılığa çevirebilir? Sinir ağı bu fonksiyonu birçok basit parçadan kurar: doğrusal dönüşümler ve aralarında doğrusal olmayan “bükülmeler”. Adı beyinden esinlenmiştir ama aslında parametreleri gradyan inişiyle seçilen matematiksel bir fonksiyondur; tıpkı önceki derslerde gördüğümüz gibi.

Yapay nöron

z = w · x + b = ∑ᵢ₌₁ⁿ wᵢxᵢ + b a = φ(z)
burada:
  • xgirdi vektörü (n öznitelik)
  • wağırlıklar; her girdinin önemi
  • bsapma (bias); aktivasyon eşiğini kaydırır
  • φaktivasyon fonksiyonu (ReLU, sigmoid…)
  • anöronun çıktısı (aktivasyon)

Bir nöron girdilerinin ağırlıklı toplamını hesaplar, sapmayı ekler ve sonucu bir aktivasyon fonksiyonundan geçirir. Aşağıdaki örnekte z = 0,4 · 0,5 + 0,3 · (−1) + (−0,2) · 2 + 0,1 = −0,4 olur: sigmoid bunu 0,4013'e, ReLU ise 0'a çevirir.

Python
import torch

x = torch.tensor([0.5, -1.0, 2.0])
w = torch.tensor([0.4, 0.3, -0.2])
b = torch.tensor(0.1)
z = w @ x + b
print(round(z.item(), 4))
print(round(torch.sigmoid(z).item(), 4), torch.relu(z).item())
Beklenen çıktı
-0.4
0.4013 0.0

Aktivasyon fonksiyonları

Aktivasyonlar olmasaydı, kaç katman eklersek ekleyelim ağ doğrusal kalırdı. İspatı kısadır: iki katman art arda uygulanınca W₂(W₁x + b₁) + b₂ = (W₂W₁)x + (W₂b₁ + b₂) elde edilir; bu, W = W₂W₁ matrisli ve b = W₂b₁ + b₂ sapmalı tek bir doğrusal katmandır. Doğrusal olmayan φ katmanları “büker” ve ağın karmaşık, eğri sınırları tanımlamasını sağlar.

ReLU(z) = max(0, z) σ(z) = 1 / (1 + e⁻ᶻ) tanh(z) = (eᶻ − e⁻ᶻ) / (eᶻ + e⁻ᶻ)ReLU(z) = max(0, z) σ(z) = 1 / (1 + e⁻ᶻ) tanh(z) = (eᶻ − e⁻ᶻ) / (eᶻ + e⁻ᶻ)
burada:
  • ReLUnegatif değerleri sıfırlar; türevi z > 0 için 1, z < 0 için 0
  • σsigmoid: değerler (0; 1) aralığında, olasılık olarak yorumlanır
  • tanhhiperbolik tanjant: değerler (−1; 1) aralığında, sıfıra göre simetrik
softmax(z)ᵢ = eᶻⁱ / ∑ⱼ₌₁ᴷ eᶻʲsoftmax(z)ᵢ = eᶻⁱ / ∑ⱼ₌₁ᴷ eᶻʲ
burada:
  • zK sınıf için ham puanlar (logitler)
  • softmax(z)ᵢi sınıfının olasılığı; hepsi pozitiftir ve toplamı 1'dir

Softmax, çok sınıflı sınıflandırmada son katmanın logitlerini bir olasılık dağılımına çevirir.

FonksiyonDeğer aralığıNerede kullanılır
ReLU[0; ∞)gizli katmanlarda varsayılan seçim: basit, hızlı, gradyan kaybolmaz
Sigmoid(0; 1)ikili sınıflandırmada çıktı, “kapılar” (LSTM)
tanh(−1; 1)yinelemeli ağlar, sıfır merkezli çıktı gerektiğinde
softmax(0; 1), toplamı 1çok sınıflı sınıflandırmada son katman

ReLU'nun bir zayıf yönü vardır: bir nöronun girdisi tüm örnekler için negatifse gradyanı hep sıfırdır ve nöron bir daha öğrenmez (“ölü ReLU”). LeakyReLU negatif tarafta küçük bir eğim bırakarak (örneğin 0,01z) buna karşı koyar. Transformerlarda ise ReLU'nun pürüzsüz bir türü olan GELU yaygın olarak kullanılır. PyTorch'ta hepsi hazırdır: nn.LeakyReLU(), nn.GELU(), nn.Tanh(), nn.Sigmoid().

Python
import torch

z = torch.tensor([-2.0, 0.0, 1.0, 3.0])
print(torch.relu(z))
print(torch.sigmoid(z).round(decimals=4))
print(torch.tanh(z).round(decimals=4))
p = torch.softmax(z, dim=0)
print(p.round(decimals=4), round(p.sum().item(), 4))
Beklenen çıktı
tensor([0., 0., 1., 3.])
tensor([0.1192, 0.5000, 0.7311, 0.9526])
tensor([-0.9640,  0.0000,  0.7616,  0.9951])
tensor([0.0057, 0.0418, 0.1135, 0.8390]) 1.0
Örnek 1: softmax'ı elle hesaplayalım

Bir ağ üç sınıf için z = (2; 1; 0) logitlerini verdi. Olasılıkları bul.

Çözümü göster
e² ≈ 7,389; e¹ ≈ 2,718; e⁰ = 1. Toplam ≈ 11,107.
p ≈ (7,389 / 11,107; 2,718 / 11,107; 1 / 11,107) ≈ (0,665; 0,245; 0,090).
Logitler yalnızca 1 farklı ama olasılıklar yaklaşık e ≈ 2,72 kat farklı: softmax en büyük puanı “güçlendirir”.
Python
import torch

print(torch.softmax(torch.tensor([2.0, 1.0, 0.0]), dim=0).round(decimals=3))
Beklenen çıktı
tensor([0.6650, 0.2450, 0.0900])

Katmanlar: nn.Linear

Birkaç nöron aynı girdiye baktığında ağırlık vektörleri tek bir W matrisinin satırları olur ve katmanın tüm çıktıları tek bir matris çarpımıyla hesaplanır. Yığın hâlinde gelen girdiler (N, nin), çıktılar ise (N, nout) şeklindedir.

Y = X · Wᵀ + b params = (nin + 1) · nout
burada:
  • Xgirdi yığını, şekil (N, nin)
  • Wağırlıklar, şekil (nout, nin); layer.weight
  • bsapmalar, şekil (nout,); layer.bias
  • Yçıktılar, şekil (N, nout)

Her çıktı nöronunun nin ağırlığı ve bir sapması vardır; bu yüzden tam bağlantılı bir katmanda (nin + 1) · nout parametre bulunur.

Python
import torch
from torch import nn

torch.manual_seed(42)
layer = nn.Linear(in_features=3, out_features=2)
print(layer.weight.shape, layer.bias.shape)
x = torch.randn(4, 3)
print(layer(x).shape)
print(sum(p.numel() for p in layer.parameters()))
Beklenen çıktı
torch.Size([2, 3]) torch.Size([2])
torch.Size([4, 2])
8

Ağırlıklar küçük rastgele değerlerle başlatılır: nn.Linear onları varsayılan olarak (−1/√nin; 1/√nin) aralığından düzgün dağılımla seçer. Rastgelelik önemlidir; tüm ağırlıklar eşit olsaydı katmandaki her nöron aynı gradyanı alır ve aynı şeyi öğrenirdi (simetri sorunu).

nn.Module ve nn.Sequential ile ağ

PyTorch'ta her model nn.Module'ün bir alt sınıfıdır. Katmanlar __init__ içinde oluşturulur, parametreleri otomatik olarak kaydedilir; forward metodu ise girdiden çıktıya hesaplamayı tanımlar. Modeli model(x) şeklinde çağırırız, PyTorch forward'u kendisi çalıştırır.

Python
import torch
from torch import nn

class MLP(nn.Module):
    def __init__(self, n_in, n_hidden, n_out):
        super().__init__()
        self.fc1 = nn.Linear(n_in, n_hidden)
        self.fc2 = nn.Linear(n_hidden, n_out)

    def forward(self, x):
        h = torch.relu(self.fc1(x))
        return self.fc2(h)

torch.manual_seed(42)
model = MLP(784, 128, 10)
print(model)
x = torch.randn(32, 784)
print(model(x).shape)
print(sum(p.numel() for p in model.parameters()))
Beklenen çıktı
MLP(
  (fc1): Linear(in_features=784, out_features=128, bias=True)
  (fc2): Linear(in_features=128, out_features=10, bias=True)
)
torch.Size([32, 10])
101770
32 görüntülük bir yığın (32, 784) → gizli katman (32, 128) → logitler (32, 10). Parametreler: 785 · 128 + 129 · 10 = 100.480 + 1.290 = 101.770.
  • model.parameters() — tüm eğitilebilir parametreler; onları optimizasyon aracına veririz.
  • model.to(device) — tüm parametreleri tek çağrıyla CPU'dan GPU'ya (ya da tersine) taşır.
  • model.train() ve model.eval() — eğitim ve değerlendirme modları (Dropout ve BatchNorm katmanları için önemlidir).
  • model.state_dict() — parametrelerin sözlüğü; bir sonraki derste modeli dosyaya kaydetmek için kullanacağız.
Tanım
Çok katmanlı algılayıcı (MLP) ve logitler

Tam bağlantılı katmanlardan ve aralarındaki aktivasyonlardan oluşan ağa çok katmanlı algılayıcı denir. Son katmanının normalleştirilmemiş çıktıları, yani logitler, herhangi bir gerçek sayı olabilir. Tahmin için en büyük logitin indeksi (logits.argmax(dim=1)), olasılıklar için ise torch.softmax(logits, dim=1) kullanılır.

Mimari nasıl seçilir? Tablo verisi için genellikle 32–256 nöronlu 1–3 gizli katman yeterlidir; küçük bir modelle başla, doğrulama kaybı izin verdikçe büyüt. Görüntüler için ise tam bağlantılı katmanlar çok pahalıdır: 224 × 224 renkli bir görüntüden 1000 nöronluk bir katmana geçmek bile 150 milyondan fazla parametre ister. Bu sorunu evrişimli ağlar çözer.

Katmanlar yalnızca art arda diziliyorsa sınıf yazmaya gerek yoktur: nn.Sequential onları sırayla uygular. Aktivasyon burada bir nn.ReLU() katmanı olarak yazılır. named_parameters() her parametrenin adını ve şeklini gösterir; hata ayıklamada çok işe yarar:

Python
import torch
from torch import nn

model = nn.Sequential(
    nn.Linear(784, 128), nn.ReLU(),
    nn.Linear(128, 64), nn.ReLU(),
    nn.Linear(64, 10),
)
for name, p in model.named_parameters():
    print(name, tuple(p.shape))
print(sum(p.numel() for p in model.parameters()))
Beklenen çıktı
0.weight (128, 784)
0.bias (128,)
2.weight (64, 128)
2.bias (64,)
4.weight (10, 64)
4.bias (10,)
109386
Adlardaki 0, 2, 4, katmanların Sequential içindeki sırasıdır; 1 ve 3 numaralı ReLU katmanlarının parametresi yoktur.
Örnek 2: parametreleri elle say

784 → 128 → 64 → 10 ağında kaç parametre vardır? float32'de ne kadar bellek kaplarlar?

Çözümü göster
1. katman: (784 + 1) · 128 = 100.480.
2. katman: (128 + 1) · 64 = 8.256.
3. katman: (64 + 1) · 10 = 650.
Toplam: 109.386; kodun çıktısıyla aynı.
Bellek: 109.386 · 4 bayt ≈ 0,44 MB. Parametrelerin %92'si ilk katmanda: büyük girdiler pahalıdır.
Örnek 3: elle ileri geçiş

2 → 2 → 1 ağı: W₁ = [[1; −1]; [0,5; 0,5]], b₁ = (0; −1), gizli katmanda ReLU, W₂ = [[2; −3]], b₂ = 1. x = (1; 2) için çıktıyı bul.

Çözümü göster
W₁x + b₁ = (1 − 2 + 0; 0,5 + 1 − 1) = (−1; 0,5).
ReLU: h = (0; 0,5); birinci nöron “söndü”.
y = 2 · 0 + (−3) · 0,5 + 1 = −0,5.
Dikkat: bu x için birinci nöronun çıktıya hiçbir etkisi yoktur; bu yüzden geri geçişte ağırlıklarının gradyanı da sıfır olur. ReLU gradyanı yalnızca etkin nöronlardan geçirir.
Python
import torch

x = torch.tensor([1.0, 2.0])
W1 = torch.tensor([[1.0, -1.0], [0.5, 0.5]])
b1 = torch.tensor([0.0, -1.0])
W2 = torch.tensor([[2.0, -3.0]])
b2 = torch.tensor([1.0])
h = torch.relu(W1 @ x + b1)
y = W2 @ h + b2
print(h, y)
Beklenen çıktı
tensor([0.0000, 0.5000]) tensor([-0.5000])

Önemli noktalar

  • Bir nöron z = w · x + b'yi, ardından a = φ(z) aktivasyonunu hesaplar; katman bunu matris biçiminde yapar: Y = X · Wᵀ + b.
  • Aktivasyonsuz çok katmanlı bir ağ tek bir doğrusal katmana eşittir; gizli katmanlarda genellikle ReLU kullanılır.
  • Softmax logitleri toplamı 1 olan olasılıklara çevirir; nn.CrossEntropyLoss'a ise ham logitler verilir.
  • Model nn.Module'den türer: katmanlar __init__ içinde, hesaplama forward içinde; basit bir zincir için nn.Sequential yeterlidir.
  • Tam bağlantılı bir katmanda (nin + 1) · nout parametre vardır; nn.Linear(n_in, n_out).weight'in şekli (nout, nin)'dir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
nn.Linear(10, 5) katmanında kaç parametre vardır?