Derin öğrenme araştırmalarının olgunlaşmasıyla birlikte standart mimariler ve hazır kayıp fonksiyonları, alan spesifik problemlerin gerçek karmaşıklığını karşılamakta yetersiz kalmaya başladı. Görüntü sınıflandırma ya da metin üretimi gibi genel amaçlı görevler için CrossEntropy veya MSE yeterli olabilir; ancak tıbbi görüntü segmentasyonu, moleküler yapı tahmini, fizik simülasyonu ya da ses kaynak ayrıştırma gibi özel alanlarda probleme özgü tümevarımsal önyargı (inductive bias) taşıyan özel modüllerin geliştirilmesi hem doğruluk hem de öğrenme verimliliği açısından kritik bir fark yaratır. Bu makalede PyTorch ekosistemi referans alınarak custom katman, kayıp fonksiyonu ve differentiable modül geliştirmenin teorik temelleri ve pratik mühendislik detayları ele alınmaktadır.
Differentiability: Neden Her Şeyin Temeli Budur?
Derin öğrenme optimizasyonunun motoru geri yayılım (backpropagation) algoritmasıdır ve bu algoritma, hesaplama grafiğindeki her operasyonun girişlerine göre türevlenebilir (differentiable) olmasını zorunlu kılar. Bir modüle “custom” diyebilmek için yalnızca ileri geçişi (forward pass) doğru uygulamak yetmez; geri geçişte (backward pass) gradyanların hatasız akmasını sağlamak da eşit ölçüde kritiktir.
PyTorch’un otomatik türev motoru olan autograd, hesaplama grafiğini dinamik olarak inşa eder ve her tensör operasyonu için gradyan fonksiyonlarını otomatik kaydeder. Bu mekanizma sayesinde geliştiriciler çoğu durumda geri geçişi manuel olarak yazmak zorunda kalmaz. Ancak NumPy operasyonları, harici C/CUDA çağrıları veya ayrık (discrete) örnekleme adımları gibi autograd grafiğini kıran durumlar söz konusu olduğunda özel torch.autograd.Function sınıfları aracılığıyla analitik gradyanların el ile tanımlanması gerekir. Bu ayrımı kavramadan yazılan custom modüller, sessiz sedasız sıfır gradyan üretebilir; bu da modelin öğrenmediği halde loss değerinin stabil göründüğü sinsi bir hata moduna yol açar.
nn.Module ile Custom Katman Geliştirme
PyTorch’ta öğrenilebilir parametreler taşıyan her yapı nn.Module sınıfından türetilir. Temel bir custom katman şu üç bileşeni içermelidir: __init__ metodunda nn.Parameter ile sarmalanmış öğrenilebilir tensörler, forward metodunda tanımlı ileri geçiş mantığı ve gerekiyorsa ek hiperparametreler.
import torch
import torch.nn as nn
import torch.nn.functional as F
class ScaledDotProductAttention(nn.Module):
def __init__(self, d_model: int, dropout: float = 0.1):
super().__init__()
self.scale = d_model ** -0.5
self.dropout = nn.Dropout(dropout)
def forward(self, q, k, v, mask=None):
scores = torch.matmul(q, k.transpose(-2, -1)) * self.scale
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn_weights = self.dropout(F.softmax(scores, dim=-1))
return torch.matmul(attn_weights, v), attn_weightsBu örnekte scale sabiti bir nn.Parameter değildir; sabit bir ölçekleme faktörüdür. Öğrenilebilir olmasını istediğiniz her tensörü nn.Parameter ile sarmalamamak, optimizer’ın o parametreyi güncellemeyeceği anlamına gelir. Bu, custom katman geliştirmede en sık karşılaşılan hatalardan biridir.
Daha ileri bir senaryo olarak alan spesifik bir normalleşme katmanı düşünelim. Batch Normalization, küçük batch boyutlarında ya da sıralı veride kötü performans gösterir. Bu durumda Layer Normalization veya Group Normalization tercih edilebilir; ancak medikal görüntüleme gibi alanlarda örnek bazında istatistik hesabı gerektiren Instance Normalization ya da kanal gruplarına özel ölçekleme içeren özel normalizasyon şemaları daha uygun olabilir:
class AdaptiveInstanceNorm2d(nn.Module):
def __init__(self, num_features: int, eps: float = 1e-5):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(1, num_features, 1, 1))
self.bias = nn.Parameter(torch.zeros(1, num_features, 1, 1))
def forward(self, x):
mean = x.mean(dim=[2, 3], keepdim=True)
std = x.std(dim=[2, 3], keepdim=True) + self.eps
return self.weight * (x - mean) / std + self.biastorch.autograd.Function ile Manuel Gradyan Tanımlama
Standart autograd grafiğinin dışına çıkıldığında, yani bir operasyonun analitik türevi otomatik olarak hesaplanamadığında, torch.autograd.Function sınıfının forward ve backward static metodları elle implement edilmelidir.
Klasik bir örnek olarak Straight-Through Estimator (STE) kullanılan ayrık kuantizasyon operasyonunu ele alalım. Vektör kuantizasyonu (VQ-VAE) veya ikili ağırlıklı ağlarda (binary networks) kullanılan bu teknik, ileri geçişte ayrık bir yuvarlama uygularken geri geçişte gradyanı sanki yuvarlama hiç olmamış gibi doğrudan geçirir:
class StraightThroughQuantize(torch.autograd.Function):
@staticmethod
def forward(ctx, x, n_bits=8):
scale = (2 ** n_bits - 1)
return torch.round(x * scale) / scale
@staticmethod
def backward(ctx, grad_output):
# Straight-through: gradyanı olduğu gibi geçir
return grad_output, None
# Kullanım
quantize = StraightThroughQuantize.applyctx.save_for_backward mekanizması, ileri geçişte hesaplanan ara tensörlerin geri geçişte kullanılabilmesi için kritiktir. Sigmoid aktivasyonunun özel implementasyonunu düşünelim; geri geçişte σ(x) * (1 - σ(x)) ifadesini hesaplamak için ileri geçişteki çıkışı kaydetmek gerekir:
class CustomSigmoid(torch.autograd.Function):
@staticmethod
def forward(ctx, x):
output = 1.0 / (1.0 + torch.exp(-x))
ctx.save_for_backward(output)
return output
@staticmethod
def backward(ctx, grad_output):
output, = ctx.saved_tensors
return grad_output * output * (1.0 - output)Alan Spesifik Kayıp Fonksiyonları Tasarımı
Kayıp fonksiyonu, modelin neyi optimize ettiğini doğrudan belirler ve yanlış seçilmiş bir kayıp fonksiyonu, mükemmel bir mimariden elde edilebilecek potansiyeli tamamen ortadan kaldırabilir. Alan spesifik problem yapısını kayıp fonksiyonuna yansıtmak, çoğu zaman yalnızca doğruluğu artırmakla kalmaz; öğrenme dinamiklerini de dramatik biçimde iyileştirir.
Focal Loss, sınıf dengesizliğinin yoğun yaşandığı nesne tespiti problemleri için Lin ve arkadaşları tarafından RetinaNet makalesinde önerilmiştir. Temel fikir, kolay sınıflandırılan örneklerin kayıp katkısını azaltarak modelin zor örneklere odaklanmasını sağlamaktır:
class FocalLoss(nn.Module):
def __init__(self, alpha: float = 0.25, gamma: float = 2.0,
reduction: str = 'mean'):
super().__init__()
self.alpha = alpha
self.gamma = gamma
self.reduction = reduction
def forward(self, inputs, targets):
bce_loss = F.binary_cross_entropy_with_logits(
inputs, targets, reduction='none'
)
pt = torch.exp(-bce_loss)
focal_loss = self.alpha * (1 - pt) ** self.gamma * bce_loss
if self.reduction == 'mean':
return focal_loss.mean()
elif self.reduction == 'sum':
return focal_loss.sum()
return focal_lossSegmentasyon görevlerinde ise Dice Loss ve IoU Loss geometrik örtüşmeyi doğrudan optimize ettiğinden piksel bazlı CrossEntropy’ye kıyasla çok daha anlamlı gradyan sinyalleri üretir. Tıbbi görüntülerde küçük lezyonların tespiti gibi kritik sınıf dengesizliği senaryolarında bu ayrım hayat kurtarıcı olabilir:
class DiceLoss(nn.Module):
def __init__(self, smooth: float = 1.0):
super().__init__()
self.smooth = smooth
def forward(self, preds, targets):
preds = torch.sigmoid(preds)
preds_flat = preds.view(-1)
targets_flat = targets.view(-1)
intersection = (preds_flat * targets_flat).sum()
dice = (2.0 * intersection + self.smooth) / (
preds_flat.sum() + targets_flat.sum() + self.smooth
)
return 1.0 - diceBileşik kayıp fonksiyonları (composite losses), birden fazla hedefi aynı anda optimize etmek için yaygın kullanılır. Örneğin görüntü restorasyonu için perceptual loss ile piksel bazlı L1 kaybını birleştirmek:
class PerceptualL1Loss(nn.Module):
def __init__(self, vgg_model, lambda_perc=0.1):
super().__init__()
self.vgg = vgg_model
self.lambda_perc = lambda_perc
self.l1 = nn.L1Loss()
def forward(self, pred, target):
l1_loss = self.l1(pred, target)
pred_features = self.vgg(pred)
target_features = self.vgg(target).detach()
perc_loss = self.l1(pred_features, target_features)
return l1_loss + self.lambda_perc * perc_lossBurada target_features.detach() çağrısı kritiktir. VGG ağırlıklarının güncellenmemesi için hedef tensörün hesaplama grafiğinden koparılması şarttır; aksi halde hem bellek tüketimi patlar hem de hatalı gradyan akışı oluşur.
Gradyan Akışının Doğrulanması ve Hata Ayıklama
Custom modüllerin ürettiği gradyanların doğruluğunu doğrulamak için PyTorch’un torch.autograd.gradcheck fonksiyonu kullanılmalıdır. Bu araç, analitik gradyanları sonlu farklar (finite differences) yöntemiyle hesaplanan nümerik gradyanlarla karşılaştırır:
from torch.autograd import gradcheck
custom_fn = StraightThroughQuantize.apply
x = torch.randn(4, 4, dtype=torch.float64, requires_grad=True)
# gradcheck double precision gerektirir
result = gradcheck(custom_fn, (x,), eps=1e-6, atol=1e-4)
print(f"Gradyan doğrulaması: {result}")Gradyan patlaması ve kayboluşu sorunlarını tespit etmek için hook mekanizması son derece değerlidir:
def gradient_hook(module, grad_input, grad_output):
for i, g in enumerate(grad_output):
if g is not None:
print(f"{module.__class__.__name__} grad_output[{i}]: "
f"norm={g.norm():.4f}, nan={g.isnan().any()}")
model.custom_layer.register_backward_hook(gradient_hook)Performans: CUDA ve Triton ile Özel Kernel Yazımı
Python katmanında uygulanan custom operasyonlar, yoğun eğitim döngülerinde belirgin performans darboğazları yaratabilir. Gerçek anlamda üretim kalitesinde bir custom modül, kritik operasyonların CUDA kernel’larına taşınmasını gerektirebilir. OpenAI’ın geliştirdiği Triton kütüphanesi, CUDA’nın düşük seviyeli karmaşıklığını önemli ölçüde soyutlayarak Python benzeri sözdizimle yüksek performanslı GPU kernel’ları yazmayı mümkün kılar. FlashAttention’ın temel prensibi olan IO-aware hesaplama bu yaklaşımın en çarpıcı uygulamasıdır: standart attention’ın HBM-SRAM arasındaki veri transferini minimize ederek bellek bant genişliği darboğazını ortadan kaldırması, Triton tabanlı kernel tasarımının neleri mümkün kıldığını somut biçimde ortaya koymaktadır.
Custom katman ve kayıp fonksiyonu geliştirmek, derin öğrenme mühendisliğinin en özgün ve tatmin edici boyutlarından birini oluşturur. Problemi gerçekten anlayan, alan bilgisini matematiksel kısıtlara dönüştürebilen ve bu kısıtları differentiable modüllere işleyebilen araştırmacılar, jenerik çözümlerin ulaşamadığı performans eşiklerini aşabilir. Ancak bu süreç dikkat ve titizlik ister: her custom bileşen gradyan akışı açısından doğrulanmalı, nümerik kararlılık test edilmeli ve üretime geçmeden önce kapsamlı biçimde profillemeye tabi tutulmalıdır.
İleri Okuma Tavsiyeleri
- Paszke, A. et al. (2019). “PyTorch: An Imperative Style, High-Performance Deep Learning Library.” NeurIPS 2019. https://arxiv.org/abs/1912.01703
- Lin, T.-Y. et al. (2017). “Focal Loss for Dense Object Detection.” ICCV 2017. https://arxiv.org/abs/1708.02002
- Dao, T. et al. (2022). “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.” NeurIPS 2022. https://arxiv.org/abs/2205.14135








