5.1. Katmanlar ve Bloklar
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in SageMaker Studio Lab

Sinir ağlarını ilk tanıttığımızda, tek çıktılı doğrusal modellere odaklandık. Burada tüm model sadece tek bir nörondan oluşuyor. Tek bir nöronun (i) bazı girdiler aldığını, (ii) karşılık gelen skaler bir çıktı ürettiğini ve (iii) ilgili bazı amaç işlevlerini optimize etmek için güncellenebilen bir dizi ilişkili parametreye sahip olduğunu unutmayın. Sonra, birden çok çıktıya sahip ağları düşünmeye başladığımızda, tüm bir nöron katmanını karakterize etmek için vektörleştirilmiş aritmetikten yararlandık. Tıpkı bireysel nöronlar gibi, katmanlar (i) bir dizi girdi alır, (ii) karşılık gelen çıktıları üretir ve (iii) bir dizi ayarlanabilir parametre ile açıklanır. Softmaks bağlanımı üzerinde çalıştığımızda, tek bir katmanın kendisi modeldi. Bununla birlikte, daha sonra MLP’leri devreye soktuğumuzda bile, modelin bu aynı temel yapıyı koruduğunu düşünebiliriz.

İlginç bir şekilde, MLPler için hem tüm model hem de kurucu katmanları bu yapıyı paylaşır. Tam model ham girdileri (öznitelikleri) alır, çıktılar (tahminler) üretir ve parametrelere (tüm kurucu katmanlardan birleşik parametreler) sahiptir. Benzer şekilde, her bir katman girdileri alır (önceki katman tarafından sağlanır) çıktılar (sonraki katmana girdiler) üretir ve sonraki katmandan geriye doğru akan sinyale göre güncellenen bir dizi ayarlanabilir parametreye sahiptir.

Nöronların, katmanların ve modellerin bize işimiz için yeterince soyutlama sağladığını düşünseniz de, genellikle tek bir katmandan daha büyük ancak tüm modelden daha küçük olan bileşenler hakkında konuşmayı uygun bulduğumuz ortaya çıkıyor. Örneğin, bilgisayarla görmede aşırı popüler olan ResNet-152 mimarisi, yüzlerce katmana sahiptir. Bu katmanlar, katman gruplarının tekrar eden desenlerinden oluşur. Böyle bir ağın her seferinde bir katman olarak uygulanması sıkıcı bir hal alabilir. Bu endişe sadece varsayımsal değildir—bu tür tasarım modelleri pratikte yaygındır. Yukarıda bahsedilen ResNet mimarisi, hem tanıma hem de tespit için 2015 ImageNet ve COCO bilgisayarla görme yarışmalarını kazandı (He et al., 2016) ve birçok görme görevi için bir ilk tatbik edilen mimari olmaya devam ediyor. Katmanların çeşitli yinelenen desenlerde düzenlendiği benzer mimariler artık doğal dil işleme ve konuşma dahil olmak üzere diğer alanlarda da her yerde mevcuttur.

Bu karmaşık ağları uygulamak için, bir sinir ağı bloğu kavramını sunuyoruz. Bir blok, tek bir katmanı, birden çok katmandan oluşan bir bileşeni veya tüm modelin kendisini tanımlayabilir! Blok soyutlamayla çalışmanın bir yararı, bunların genellikle yinelemeli olarak daha büyük yapay nesnelerle birleştirilebilmeleridir. Bu, Fig. 5.1.1 içinde gösterilmiştir. İsteğe bağlı olarak rastgele karmaşıklıkta bloklar oluşturmak için kod tanımlayarak, şaşırtıcı derecede sıkıştırılmış kod yazabilir ve yine de karmaşık sinir ağlarını uygulayabiliriz.

../_images/blocks.svg

Fig. 5.1.1 Çoklu katmanlar bloklara birleştirilir, daha geniş modelleri oluşturan tekrar eden desenler oluştururlar

Programlama açısından, bir blok sınıf ile temsil edilir. Onun herhangi bir alt sınıfı, girdisini çıktıya dönüştüren ve gerekli parametreleri depolayan bir ileri yayma yöntemi tanımlamalıdır. Bazı blokların herhangi bir parametre gerektirmediğini unutmayın. Son olarak, gradyanları hesaplamak için bir blok geriye yayma yöntemine sahip olmalıdır. Neyse ki, kendi bloğumuzu tanımlarken otomatik türev almanın sağladığı bazı perde arkası sihir sayesinde (Section 2.5 içinde tanıtıldı), sadece parametreler ve ileri yayma işlevi hakkında endişelenmemiz gerekir.

Başlangıç olarak MLPleri uygulamak için kullandığımız kodları yeniden gözden geçiriyoruz (Section 4.3). Aşağıdaki kod, 256 birim ve ReLU etkinleştirmesine sahip tam bağlı bir gizli katmana sahip bir ağ oluşturur ve ardından 10 birimle (etkinleştirme işlevi yok) tam bağlı çıktı katmanı gelir.

from mxnet import np, npx
from mxnet.gluon import nn

npx.set_np()

net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()

X = np.random.uniform(size=(2, 20))
net(X)
array([[ 0.06240274, -0.03268593,  0.02582653,  0.02254181, -0.03728798,
        -0.04253785,  0.00540612, -0.01364185, -0.09915454, -0.02272737],
       [ 0.02816679, -0.03341204,  0.03565665,  0.02506384, -0.04136416,
        -0.04941844,  0.01738529,  0.01081963, -0.09932579, -0.01176296]])

Bu örnekte, modelimizi bir nn.Sequential örneğini oluşturarak döndürülen nesneyi net değişkenine atayarak oluşturduk. Daha sonra, katmanları çalıştırılmaları gereken sırayla eklemek için tekrar tekrar add işlevini çağırıyoruz. Kısaca, nn.Sequential, Gluon’da bir blok sunan sınıf olan özel bir Block türünü tanımlar. Oluşturucu Block’ların sıralı bir listesini tutar. add işlevi, basitçe her ardışık Block’un listeye eklenmesini kolaylaştırır. Her katmanın, kendisinin Block’un alt sınıfı olan Dense sınıfının bir örneği olduğuna dikkat edin. İleri yayma (forward) işlevi de oldukça basittir: Listedeki her Block birbirine zincirlenerek her birinin çıktısını bir sonrakine girdi olarak iletir. Şimdiye kadar, çıktılarını elde etmek için modellerimizi net(X) yapısı aracılığıyla çağırdığımızı unutmayın. Bu aslında Block sınıfının __call__ işlevi aracılığıyla elde edilen ustaca bir Python marifeti olan net.forward(X) için kısaltmadır.

import torch
from torch import nn
from torch.nn import functional as F

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

X = torch.rand(2, 20)
net(X)
tensor([[-0.2314,  0.0426, -0.1038, -0.0008,  0.1609, -0.0306,  0.0023, -0.0257,
         -0.1857, -0.0899],
        [-0.1886,  0.0906, -0.1492, -0.0039,  0.1726,  0.0048, -0.0374, -0.0620,
         -0.2537, -0.0323]], grad_fn=<AddmmBackward0>)

Bu örnekte, modelimizi bir nn.Sequential örneğini oluşturarak, katmanları bağımsız değişken olarak iletilmeleri gereken sırayla oluşturduk. Kısaca, nn.Sequential, PyTorch’ta bir blok sunan özel bir sınıf Module türünü tanımlar. Kurucu Module’lerin sıralı bir listesini tutar. Tam bağlı iki katmanın her birinin, kendisi de Module’ün bir alt sınıfı olan Linear sınıfının bir örneği olduğuna dikkat edin. İleri yayma (forward) işlevi de oldukça basittir: Listedeki her bloğu birbirine zincirleyerek her birinin çıktısını bir sonrakine girdi olarak iletir. Şimdiye kadar, çıktıları elde etmek için modellerimizi net(X) yapısı aracılığıyla çağırdığımızı unutmayın. Bu aslında net.__call__(X) için kısa yoldur.

import tensorflow as tf

net = tf.keras.models.Sequential([
    tf.keras.layers.Dense(256, activation=tf.nn.relu),
    tf.keras.layers.Dense(10),
])

X = tf.random.uniform((2, 20))
net(X)
<tf.Tensor: shape=(2, 10), dtype=float32, numpy=
array([[-0.12117177,  0.07105556,  0.09895928, -0.05512274,  0.14506425,
         0.12194501, -0.150309  ,  0.09142203,  0.23326892, -0.05487332],
       [-0.03861865, -0.07037442,  0.0326989 , -0.12724477,  0.20256203,
        -0.00141095, -0.03525295,  0.30992243,  0.03046432, -0.05945783]],
      dtype=float32)>

Bu örnekte, modelimizi bir keras.models.Sequential örneğini oluşturarak, katmanları bağımsız değişken olarak iletilmeleri gereken sırayla oluşturduk. Kısaca Sequential, Keras’ta bir blok sunan özel bir sınıf keras.Model’i tanımlar, ki kurucu Model’lerin sıralı bir listesini tutar. İki tam bağlı katmanın her birinin, kendisi Model’in alt sınıfı olan Dense sınıfının bir örneği olduğuna dikkat edin. İleri yayma (call) işlevi de oldukça basittir: Listedeki her bloğu birbirine zincirleyerek her birinin çıkışını bir sonrakine girdi olarak iletir. Şimdiye kadar, çıktılarını elde etmek için modellerimizi net(X) yapısı aracılığıyla çağırdığımızı unutmayın. Bu aslında Block sınıfının __call__ işlevi aracılığıyla elde edilen ustaca bir Python marifeti olan net.call(X) için kısaltmadır.

5.1.1. Özel Yapım Blok

Bir bloğun nasıl çalıştığına dair sezgiyi geliştirmenin belki de en kolay yolu, bloğu kendimiz uygulamaktır. Kendi özel yapım bloğumuzu uygulamadan önce, her bloğun sağlaması gereken temel işlevleri kısaca özetliyoruz:

  1. Girdi verilerini ileri yayma yöntemine bağımsız değişkenler olarak alın.

  2. İleri yayma işlevi kullanarak bir değer döndürüp bir çıktı oluşturun. Çıktının girdiden farklı bir şekle sahip olabileceğini unutmayın. Örneğin, yukarıdaki modelimizdeki ilk tam bağlı katman, rastgele bir boyut girdisi alır, ancak 256 boyutunda bir çıktı verir.

  3. Girdiye göre çıktısının gradyanını hesaplayın, ki bu da geriye yayma yöntemiyle erişilebilir. Genellikle bu otomatik olarak gerçekleşir.

  4. İleri yaymayı hesaplamayı yürütmek için gerekli olan bu parametreleri saklayın ve bunlara erişim sağlayın.

  5. Model parametrelerini gerektiği gibi ilkletin.

  1. Girdi verilerini ileri yayma yöntemine bağımsız değişkenler olarak alın.

  2. İleri yayma işlevi kullanarak bir değer döndürüp bir çıktı oluşturun. Çıktının girdiden farklı bir şekle sahip olabileceğini unutmayın. Örneğin, yukarıdaki modelimizdeki ilk tam bağlı katman 20 boyutlu bir girdi alır, ancak 256 boyutunda bir çıktı verir.

  3. Girdiye göre çıktısının gradyanını hesaplayın, ki bu da geriye yayma yöntemiyle erişilebilir. Genellikle bu otomatik olarak gerçekleşir.

  4. İleri yaymayı hesaplamayı yürütmek için gerekli olan bu parametreleri saklayın ve bunlara erişim sağlayın.

  5. Model parametrelerini gerektiği gibi ilkletin.

  1. Girdi verilerini ileri yayma yöntemine bağımsız değişkenler olarak alın.

  2. İleri yayma işlevi kullanarak bir değer döndürüp bir çıktı oluşturun. Çıktının girdiden farklı bir şekle sahip olabileceğini unutmayın. Örneğin, yukarıdaki modelimizdeki ilk tam bağlı katman, rastgele bir boyut girdisi alır, ancak 256 boyutunda bir çıktı verir.

  3. Girdiye göre çıktısının gradyanını hesaplayın, ki bu da geriye yayma yöntemiyle erişilebilir. Genellikle bu otomatik olarak gerçekleşir.

  4. İleri yaymayı hesaplamayı yürütmek için gerekli olan bu parametreleri saklayın ve bunlara erişim sağlayın.

  5. Model parametrelerini gerektiği gibi ilkletin.

Aşağıdaki kod parçacığında, 256 gizli birime sahip bir gizli katman ve 10 boyutlu bir çıktı katmanı ile bir MLP’ye karşılık gelen bir bloğu sıfırdan kodladık. Aşağıdaki MLP sınıfının bir bloğu temsil eden sınıftan kalıtsal çoğaltıldığını unutmayın. Yalnızca kendi kurucumuzu (Python’daki ‘init’ işlevi) ve ileri yayma işlevini sağlayarak, büyük ölçüde ana sınıfın işlevlerine güveneceğiz.

class MLP(nn.Block):
    # Model parametreleriyle bir katman ilan edin.
    # Burada, tam bağlı iki katman ilan ediyoruz.
    def __init__(self, **kwargs):
        # Gerekli ilklemeyi gerçekleştirmek için `MLP` üst sınıfının `Block`
        # kurucusunu çağırın. Bu şekilde, sınıf örneği yaratma sırasında model
        # parametreleri, `params` (daha sonra açıklanacak) gibi diğer fonksiyon
        # argümanları da belirtilebilir.
        super().__init__(**kwargs)
        self.hidden = nn.Dense(256, activation='relu')  # Gizli katman
        self.out = nn.Dense(10)  # Çıktı katmanı

    # Modelin ileri yaymasını tanımla, yani `X` girdisine dayalı
    # olarak gerekli model çıktısının nasıl döndürüleceğini tanımla.
    def forward(self, X):
        return self.out(self.hidden(X))
class MLP(nn.Module):
    # Model parametreleriyle bir katman tanımlayın.
    # Burada tam bağlı iki katman tanımlıyoruz.
    def __init__(self):
        # Gerekli ilklemeyi gerçekleştirmek için `MLP` üst sınıfının `Module`
        # kurucusunu çağırın. Bu şekilde, sınıf örneği yaratma sırasında model parametreleri,
        # `params` (daha sonra açıklanacak) gibi diğer fonksiyon argümanları da belirtilebilir.
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # Gizli katman
        self.out = nn.Linear(256, 10)  # Çıktı katmanı

    # Modelin ileri yaymasını tanımla, yani `X` girdisine dayalı
    # olarak gerekli model çıktısının nasıl döndürüleceğini tanımla.
    def forward(self, X):
        # Burada, nn.function modülünde tanımlanan ReLU'nun fonksiyonel
        # versiyonunu kullandığımıza dikkat edin.
        return self.out(F.relu(self.hidden(X)))
class MLP(tf.keras.Model):
    # Model parametreleriyle bir katman tanımlayın.
    # Burada tam bağlı iki katman tanımlıyoruz.
    def __init__(self):
        # Gerekli ilklemeyi gerçekleştirmek için `MLP` üst sınıfının `Model`
        # kurucusunu çağırın. Bu şekilde, sınıf örneği yaratma sırasında model parametreleri,
        # `params` (daha sonra açıklanacak) gibi diğer fonksiyon argümanları da belirtilebilir.
        super().__init__()
        # Gizli katman
        self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu)
        self.out = tf.keras.layers.Dense(units=10)  # Çıktı katmanı

    # Modelin ileri yaymasını tanımla, yani `X` girdisine dayalı
    # olarak gerekli model çıktısının nasıl döndürüleceğini tanımla.
    def call(self, X):
        return self.out(self.hidden((X)))

İlk olarak ileri yayma işlevine odaklanalım. Girdi olarak X’i aldığını, gizli gösterimi etkinleştirme işlevi uygulanmış olarak hesapladığını ve logitlerini çıktı verdiğini unutmayın. Bu MLP uygulamasında, her iki katman da örnek değişkenlerdir. Bunun neden makul olduğunu anlamak için, iki MLP’yi (net1 ve net2) somutlaştırdığınızı ve bunları farklı veriler üzerinde eğittiğinizi hayal edin. Doğal olarak, iki farklı öğrenilmiş modeli temsil etmelerini bekleriz.

MLP’nin katmanlarını kurucuda ilkliyoruz ve daha sonra bu katmanları her bir ileri yayma işlevi çağrısında çağırıyoruz. Birkaç önemli ayrıntıya dikkat edin. İlk olarak, özelleştirilmiş __init__ işlevimiz, super().__init__() aracılığıyla üst sınıfın __init__ işlevini çağırır ve bizi çoğu bloğa uygulanabilen standart şablon kodunu yeniden biçimlendirme zahmetinden kurtarır. Daha sonra, tam bağlı iki katmanımızı self.hidden ve self.out’a atayarak somutlaştırıyoruz. Yeni bir operatör uygulamadığımız sürece, geriye yayma işlevi veya parametre ilkleme konusunda endişelenmemize gerek olmadığını unutmayın. Sistem bu işlevleri otomatik olarak üretecektir. Bunu deneyelim.

net = MLP()
net.initialize()
net(X)
array([[-0.03989595, -0.10414709,  0.06799038,  0.05245074,  0.0252606 ,
        -0.00640342,  0.04182098, -0.01665318, -0.02067345, -0.07863816],
       [-0.03612847, -0.07210435,  0.09159479,  0.07890773,  0.02494171,
        -0.01028665,  0.01732427, -0.02843244,  0.03772651, -0.06671703]])
net = MLP()
net(X)
tensor([[ 0.0986,  0.0048,  0.0926,  0.0814, -0.0960, -0.0417, -0.0494, -0.0037,
          0.1997, -0.1000],
        [ 0.0230, -0.0899,  0.0527,  0.1209, -0.0111, -0.1575, -0.0703, -0.0153,
          0.0516, -0.1078]], grad_fn=<AddmmBackward0>)
net = MLP()
net(X)
<tf.Tensor: shape=(2, 10), dtype=float32, numpy=
array([[ 0.0220683 ,  0.15039396,  0.11085281, -0.03473502, -0.03731011,
         0.07866513,  0.12678556,  0.0706055 , -0.03519306, -0.10937315],
       [ 0.02165729,  0.05662628,  0.13636291, -0.13518727, -0.07808358,
        -0.02653112,  0.30129403, -0.04731131,  0.02025647, -0.01348933]],
      dtype=float32)>

Blok soyutlamanın önemli bir özelliği çok yönlülüğüdür. Katmanlar (tam bağlı katman sınıfı gibi), tüm modeller (yukarıdaki MLP sınıfı gibi) veya ara karmaşıklığın çeşitli bileşenlerini oluşturmak için bir bloğu alt sınıflara ayırabiliriz. Bu çok yönlülüğü sonraki bölümlerde, mesela evrişimli sinir ağlarını, ele alırken kullanıyoruz.

5.1.2. Dizili Blok

Artık Sequential (dizili) sınıfının nasıl çalıştığına daha yakından bakabiliriz. Sequential’nin diğer blokları birbirine zincirleme bağlamak için tasarlandığını hatırlayın. Kendi basitleştirilmiş MySequential’ımızı oluşturmak için, sadece iki anahtar işlev tanımlamamız gerekir: 1. Blokları birer birer listeye eklemek için bir işlev. 2. Bir girdiyi blok zincirinden, eklendikleri sırayla iletmek için bir ileri yayma işlevi.

Aşağıdaki MySequential sınıfı aynı işlevselliği varsayılan Sequential sınıfıyla sunar:

class MySequential(nn.Block):
    def add(self, block):
        # Burada, `block`, `Block` alt sınıfının bir örneğidir ve eşsiz bir
        # ada sahip olduğunu varsayıyoruz. Bunu `Block` sınıfının `_children`
        # üye değişkenine kaydederiz ve türü OrderedDict'tir. `MySequential`
        # örneği `initialize` işlevini çağırdığında, sistem tüm `_children`
        # üyelerini otomatik olarak ilkler.

        self._children[block.name] = block

    def forward(self, X):
        # OrderedDict, üyelerin eklendikleri sırayla işletileceğini garanti eder.
        for block in self._children.values():
            X = block(X)
        return X

add işlevi, sıralı _children sözlüğüne tek bir blok ekler. Neden her Gluon Block’unun bir _children özelliğine sahip olduğunu ve sadece bir Python listesi tanımlamak yerine bunu neden kullandığımızı merak edebilirsiniz. Kısacası, _children’in başlıca avantajı, bloğumuzun parametre ilklemesi sırasında Gluon’un, parametreleri de ilklemesi gereken alt blokları bulmak için _children sözlüğüne bakmayı bilmesidir.

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # Burada, `module` , `mMdule` alt sınıfının bir örneğidir.
            # Bunu, `Module` sınıfının `_modules` üye değişkenine kaydederiz
            # ve türü OrderedDict'tir.
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict, üyelerin eklendikleri sırayla işletileceğini garanti eder.
        for block in self._modules.values():
            X = block(X)
        return X

__init__ yönteminde, her modülü sıralı _modules sözlüğüne tek tek ekliyoruz. Neden her Module’ün bir _modules özelliğine sahip olduğunu ve sadece bir Python listesi tanımlamak yerine bunu neden kullandığımızı merak edebilirsiniz. Kısacası, _modules’ün başlıca avantajı, modülün parametre ilklemesi sırasında, sistemin, parametreleri de ilklemesi gereken alt modülleri bulmak için _modules sözlüğüne bakmayı bilmesidir.

class MySequential(tf.keras.Model):
    def __init__(self, *args):
        super().__init__()
        self.modules = []
        for block in args:
            # Burada, `block`, bir `tf.keras.layers.Layer` alt sınıfının bir örneğidir
            self.modules.append(block)

    def call(self, X):
        for module in self.modules:
            X = module(X)
        return X

MySequential’ımızın ileri yayma işlevi çağrıldığında, eklenen her blok eklendikleri sırayla yürütülür. Artık MySequential sınıfımızı kullanarak bir MLP’yi yeniden uygulayabiliriz.

net = MySequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
net(X)
array([[-0.0764568 , -0.01130233,  0.04952145, -0.04651389, -0.04131571,
        -0.05884131, -0.06213811,  0.01311471, -0.01379425, -0.02514282],
       [-0.05124623,  0.00711232, -0.00155933, -0.07555379, -0.06675334,
        -0.01762914,  0.00589085,  0.0144719 , -0.04330775,  0.03317727]])
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)
tensor([[ 0.1398,  0.1564, -0.0464, -0.0877,  0.2311,  0.1336, -0.0970, -0.0086,
          0.0315, -0.0298],
        [ 0.1132,  0.0785, -0.1431,  0.0261,  0.2176, -0.0221, -0.1219,  0.0359,
          0.0496, -0.0944]], grad_fn=<AddmmBackward0>)
net = MySequential(
    tf.keras.layers.Dense(units=256, activation=tf.nn.relu),
    tf.keras.layers.Dense(10))
net(X)
<tf.Tensor: shape=(2, 10), dtype=float32, numpy=
array([[-0.12663801,  0.28410044, -0.02021371,  0.17225057, -0.24790484,
         0.2616481 , -0.12881094,  0.15046239,  0.15561551, -0.24188828],
       [-0.32742888,  0.16609511,  0.11098152,  0.1686855 , -0.25428334,
         0.2895748 , -0.2792338 ,  0.3357003 ,  0.08122895, -0.11401816]],
      dtype=float32)>

Bu MySequential kullanımının, daha önce Sequential sınıfı için yazdığımız kodla aynı olduğuna dikkat edin (Section 4.3 içinde açıklandığı gibi).

5.1.3. İleri Yayma İşlevinde Kodu Yürütme

Sequential sınıfı, model yapımını kolaylaştırarak, kendi sınıfımızı tanımlamak zorunda kalmadan yeni mimarileri bir araya getirmemize olanak tanır. Bununla birlikte, tüm mimariler basit papatya zinciri değildir. Daha fazla esneklik gerektiğinde, kendi bloklarımızı tanımlamak isteyeceğiz. Örneğin, Python’un kontrol akışını ileri yayma işlevi ile yürütmek isteyebiliriz. Dahası, önceden tanımlanmış sinir ağı katmanlarına güvenmek yerine, keyfi matematiksel işlemler gerçekleştirmek isteyebiliriz.

Şimdiye kadar ağlarımızdaki tüm işlemlerin ağımızın etkinleştirmelerine ve parametrelerine göre hareket ettiğini fark etmiş olabilirsiniz. Ancak bazen, ne önceki katmanların sonucu ne de güncellenebilir parametrelerin sonucu olmayan terimleri dahil etmek isteyebiliriz. Bunlara sabit parametreler diyoruz. Örneğin, \(f(\mathbf{x},\mathbf{w}) = c \cdot \mathbf{w}^\top \mathbf{x}\) işlevini hesaplayan bir katman istediğimizi varsayalım, burada \(\mathbf{x}\) girdi, \(\mathbf{w}\) bizim parametremiz ve \(c\) optimizasyon sırasında güncellenmeyen belirli bir sabittir. Bu yüzden FixedHiddenMLP sınıfını aşağıdaki gibi uyguluyoruz.

class FixedHiddenMLP(nn.Block):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # `get_constant` işleviyle oluşturulan rastgele ağırlık parametreleri
        # eğitim sırasında güncellenmez (yani sabit parametreler)
        self.rand_weight = self.params.get_constant(
            'rand_weight', np.random.uniform(size=(20, 20)))
        self.dense = nn.Dense(20, activation='relu')

    def forward(self, X):
        X = self.dense(X)
        # Oluşturulan sabit parametrelerin yanı sıra `relu` ve `dot`
        # işlevlerini kullanın
        X = npx.relu(np.dot(X, self.rand_weight.data()) + 1)
        # Tam bağlı katmanı yeniden kullanın. Bu, parametreleri tamamen bağlı iki katmanla paylaşmaya eşdeğerdir.
        X = self.dense(X)
        # Kontrol akışı
        while np.abs(X).sum() > 1:
            X /= 2
        return X.sum()
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # Gradyanları hesaplamayan ve bu nedenle eğitim sırasında sabit kalan
        # rastgele ağırlık parametreleri
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # Oluşturulan sabit parametrelerin yanı sıra `relu` ve `mm`işlevlerini kullanın
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # Tam bağlı katmanı yeniden kullanın. Bu, parametreleri tamamen bağlı iki
        # katmanla paylaşmaya eşdeğerdir.
        X = self.linear(X)
        # Kontrol akışı
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()
class FixedHiddenMLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.flatten = tf.keras.layers.Flatten()
        # `tf.constant` ile oluşturulan rastgele ağırlık parametreleri eğitim sırasında güncellenmez (yani sabit parametreler)
        self.rand_weight = tf.constant(tf.random.uniform((20, 20)))
        self.dense = tf.keras.layers.Dense(20, activation=tf.nn.relu)

    def call(self, inputs):
        X = self.flatten(inputs)
        # Oluşturulan sabit parametrelerin yanı sıra `relu` ve `matmul`
        # işlevlerini kullanın
        X = tf.nn.relu(tf.matmul(X, self.rand_weight) + 1)
        # Tam bağlı katmanı yeniden kullanın. Bu, parametreleri tamamen bağlı iki
        # katmanla paylaşmaya eşdeğerdir.
        X = self.dense(X)
        # Kontrol akışı
        while tf.reduce_sum(tf.math.abs(X)) > 1:
            X /= 2
        return tf.reduce_sum(X)

Bu FixedHiddenMLP modelinde, ağırlıkları (self.rand_weight) örneklemede rastgele ilkletilen ve daha sonra sabit olan gizli bir katman uygularız. Bu ağırlık bir model parametresi değildir ve bu nedenle asla geri yayma ile güncellenmez. Ağ daha sonra bu “sabit” katmanın çıktısını tam bağlı bir katmandan geçirir.

Çıktıyı döndürmeden önce, modelimizin olağandışı bir şey yaptığını unutmayın. Bir while-döngüsü çalıştırdık, \(L_1\) normunun \(1\)’den büyük olması koşulunu test ettik ve çıktı vektörümüzü bu koşulu karşılayana kadar \(2\)’ye böldük. Son olarak, X’deki girdilerin toplamını döndürdük. Bildiğimiz kadarıyla hiçbir standart sinir ağı bu işlemi gerçekleştirmez. Bu özel işlemin herhangi bir gerçek dünya sorununda yararlı olmayabileceğini unutmayın. Amacımız, yalnızca rastgele kodu sinir ağı hesaplamalarınızın akışına nasıl tümleştirebileceğinizi göstermektir.

net = FixedHiddenMLP()
net.initialize()
net(X)
array(0.52637565)
net = FixedHiddenMLP()
net(X)
tensor(0.0956, grad_fn=<SumBackward0>)
net = FixedHiddenMLP()
net(X)
<tf.Tensor: shape=(), dtype=float32, numpy=0.57843995>

Blokları bir araya getirmenin çeşitli yollarını karıştırıp eşleştirebiliriz. Aşağıdaki örnekte, blokları birtakım yaratıcı yollarla iç içe yerleştiriyoruz.

class NestMLP(nn.Block):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.net = nn.Sequential()
        self.net.add(nn.Dense(64, activation='relu'),
                     nn.Dense(32, activation='relu'))
        self.dense = nn.Dense(16, activation='relu')

    def forward(self, X):
        return self.dense(self.net(X))

chimera = nn.Sequential()
chimera.add(NestMLP(), nn.Dense(20), FixedHiddenMLP())
chimera.initialize()
chimera(X)
array(0.9772054)
class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, X):
        return self.linear(self.net(X))

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)
tensor(0.1101, grad_fn=<SumBackward0>)
class NestMLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.net = tf.keras.Sequential()
        self.net.add(tf.keras.layers.Dense(64, activation=tf.nn.relu))
        self.net.add(tf.keras.layers.Dense(32, activation=tf.nn.relu))
        self.dense = tf.keras.layers.Dense(16, activation=tf.nn.relu)

    def call(self, inputs):
        return self.dense(self.net(inputs))

chimera = tf.keras.Sequential()
chimera.add(NestMLP())
chimera.add(tf.keras.layers.Dense(20))
chimera.add(FixedHiddenMLP())
chimera(X)
<tf.Tensor: shape=(), dtype=float32, numpy=0.8947748>

5.1.4. Verimlilik

Hevesli okuyucu, bu işlemlerden bazılarının verimliliği konusunda endişelenmeye başlayabilir. Sonuçta, yüksek performanslı bir derin öğrenme kütüphanesinde yer alan çok sayıda sözlük arama, kod yürütme ve daha birçok Pythonik (Python dilince) şeyimiz var. Python’un global yorumlayıcı kilidi sorunları iyi bilinmektedir. Derin öğrenme bağlamında, son derece hızlı GPU’larımızın, cılız bir CPU’nun Python kodunu çalıştırması için başka bir işe girmeden önce onu beklemeleri gerekebileceğinden endişeleniyoruz. Python’u hızlandırmanın en iyi yolu, ondan tamamen kaçınmaktır.

Gluon’un daha sonra tanımlayacağımız melezleştirmeye izin vermesi bunu yapmanın bir yoludur. Burada, Python yorumlayıcısı, ilk çalıştırıldığında bir blok yürütür. Gluon çalışma zamanında neler olduğunu kaydeder ve bir dahaki sefere kısa devre yaparak Python’a çağrı yapar. Bu, bazı durumlarda işleri önemli ölçüde hızlandırabilir, ancak kontrol akışı (yukarıdaki gibi) ağdan farklı geçişlerde farklı dallara yol açtığı zaman dikkatli olunması gerekir. İlgili okuyucunun, mevcut bölümü bitirdikten sonra derleme hakkında bilgi edinmek için melezleştirme bölümüne (Section 12.1) bakmasını öneririz.

Hevesli okuyucular, bu işlemlerin bazılarının verimliliği konusunda endişe duymaya başlayabilir. Ne de olsa, yüksek performanslı bir derin öğrenme kitaplığı olması gereken yerde çok sayıda sözlük araması, kod koşturma ve birçok başka Pythonik şey var. Python’un genel yorumlayıcı kilidi sorunları iyi bilinmektedir. Derin öğrenme bağlamında, son derece hızlı GPU’larımızın, başka bir işi çalıştırmadan önce cılız bir CPU’nun Python kodunu çalıştırmasını beklemesi gerekebileceğinden endişe duyabiliriz.

Hevesli okuyucular, bu işlemlerin bazılarının verimliliği konusunda endişe duymaya başlayabilir. Ne de olsa, yüksek performanslı bir derin öğrenme kitaplığı olması gereken yerde çok sayıda sözlük araması, kod koşturma ve birçok başka Pythonik şey var. Python’un genel yorumlayıcı kilidi sorunları iyi bilinmektedir. Derin öğrenme bağlamında, son derece hızlı GPU’larımızın, başka bir işi çalıştırmadan önce cılız bir CPU’nun Python kodunu çalıştırmasını beklemesi gerekebileceğinden endişe duyabiliriz. Python’u hızlandırmanın en iyi yolu, ondan tamamen kaçınmaktır.

5.1.5. Özet

  • Katmanlar bloklardır.

  • Birçok katman bir blok içerebilir.

  • Bir blok birçok blok içerebilir.

  • Bir blok kod içerebilir.

  • Bloklar, parametre ilkleme ve geri yayma dahil olmak üzere birçok temel yürütme işlerini halleder.

  • Katmanların ve blokların dizili birleştirmeleri Sequential blok tarafından gerçekleştirilir.

5.1.6. Alıştırmalar

  1. Blokları bir Python listesinde saklamak için MySequential’ı değiştirirseniz ne tür sorunlar ortaya çıkacaktır?

  2. Bağımsız değişken olarak iki blok alan bir blok uygulayın, örneğin net1 ve net2 ve ileri yaymada her iki ağın birleştirilmiş çıktısını döndürsün. Buna paralel blok da denir.

  3. Aynı ağın birden çok örneğini birleştirmek istediğinizi varsayın. Aynı bloğun birden çok örneğini oluşturan ve ondan daha büyük bir ağ oluşturan bir fabrika işlevi uygulayın.