6.1 Sluoksniai ir moduliai

Kai pirmą kartą pristatėme neuroninius tinklus, susitelkėme į tiesinius modelius su viena išvestimi. Čia visą modelį sudaro tik vienas neuronas. Atkreipkite dėmesį, kad vienas neuronas (i) priima tam tikrą įvesčių aibę; (ii) generuoja atitinkamą skaliarinę išvestį; ir (iii) turi susijusių parametrų aibę, kurią galima atnaujinti siekiant optimizuoti kokią nors dominančią tikslo funkciją. Tada, kai pradėjome galvoti apie tinklus su keliomis išvestimis, pasitelkėme vektorizuotą aritmetiką, kad apibūdintume visą neuronų sluoksnį. Lygiai kaip atskiri neuronai, sluoksniai (i) priima įvesčių aibę, (ii) generuoja atitinkamas išvestis ir (iii) yra aprašomi derinamų parametrų aibe. Kai nagrinėjome softmax regresiją, vienas sluoksnis pats buvo modelis. Tačiau net kai vėliau pristatėme MLP, vis tiek galėjome laikyti, kad modelis išlaiko tą pačią pagrindinę struktūrą.

Įdomu, kad MLP atveju ir visas modelis, ir jo sudedamieji sluoksniai turi šią struktūrą. Visas modelis priima neapdorotas įvestis (požymius), generuoja išvestis (prognozes) ir turi parametrus (visų sudedamųjų sluoksnių parametrų visumą). Lygiai taip pat kiekvienas atskiras sluoksnis priima įvestis (kurias pateikia ankstesnis sluoksnis), generuoja išvestis (kito sluoksnio įvestis) ir turi derinamų parametrų aibę, kuri atnaujinama pagal signalą, tekantį atgal iš kito sluoksnio.

Nors galite manyti, kad neuronai, sluoksniai ir modeliai suteikia mums pakankamai abstrakcijų savo darbui, paaiškėja, kad dažnai patogu kalbėti apie komponentus, kurie yra didesni nei atskiras sluoksnis, bet mažesni nei visas modelis. Pavyzdžiui, ResNet-152 architektūra, kuri be galo populiari kompiuteriniame matyme, turi šimtus sluoksnių. Šiuos sluoksnius sudaro pasikartojantys sluoksnių grupių dėsningumai. Įgyvendinti tokį tinklą po vieną sluoksnį gali tapti varginantis. Šis rūpestis nėra vien hipotetinis – tokie projektavimo šablonai praktikoje yra įprasti. Minėtoji ResNet architektūra laimėjo 2015 m. ImageNet ir COCO kompiuterinio matymo konkursus tiek atpažinimo, tiek aptikimo kategorijose (He ir kt., 2016) ir tebėra pagrindinė architektūra daugeliui matymo užduočių. Panašios architektūros, kuriose sluoksniai išdėstyti įvairiais pasikartojančiais dėsningumais, dabar yra visur kitose srityse, įskaitant natūraliosios kalbos apdorojimą ir kalbą.

Kad įgyvendintume šiuos sudėtingus tinklus, pristatome neuroninio tinklo modulio sąvoką. Modulis gali aprašyti atskirą sluoksnį, kelių sluoksnių komponentą arba patį visą modelį! Viena darbo su modulio abstrakcija nauda yra ta, kad juos galima jungti į didesnius darinius, dažnai rekursyviai. Tai iliustruota 6.1.1 pav.. Apibrėždami kodą, generuojantį savavališko sudėtingumo modulius pagal poreikį, galime parašyti stebėtinai kompaktišką kodą ir vis tiek įgyvendinti sudėtingus neuroninius tinklus.

Keli sluoksniai sujungiami į modulius, sudarydami didesnių modelių pasikartojančius dėsningumus.

6.1.1 pav. Keli sluoksniai sujungiami į modulius, sudarydami didesnių modelių pasikartojančius dėsningumus.

Programavimo požiūriu modulis atvaizduojamas klase. Bet kuri jos poklasė turi apibrėžti tiesioginio sklidimo metodą, kuris transformuoja jos įvestį į išvestį, ir turi saugoti visus reikalingus parametrus. Atkreipkite dėmesį, kad kai kuriems moduliams parametrų visai nereikia. Galiausiai modulis turi turėti atgalinio sklidimo metodą gradientams apskaičiuoti. Laimei, dėl užkulisinės magijos, kurią suteikia automatinis diferencijavimas (pristatytas 2.5 skyrelis), apibrėždami savo modulį, turime rūpintis tik parametrais ir tiesioginio sklidimo metodu.

from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
import torch
from torch import nn
from torch.nn import functional as F
import tensorflow as tf
from typing import List
from d2l import jax as d2l
from flax import linen as nn
import jax
from jax import numpy as jnp

Pradžioje peržiūrime kodą, kurį naudojome MLP įgyvendinti (5.1 skyrelis). Šis kodas sugeneruoja tinklą su vienu pilnai sujungtu paslėptuoju sluoksniu, turinčiu 256 vienetus ir ReLU aktyvaciją, po kurio eina pilnai sujungtas išvesties sluoksnis su dešimčia vienetų (be aktyvacijos funkcijos).

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).shape
net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))

X = torch.rand(2, 20)
net(X).shape
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).shape
net = nn.Sequential([nn.Dense(256), nn.relu, nn.Dense(10)])

# get_key is a d2l saved function returning jax.random.PRNGKey(random_seed)
X = jax.random.uniform(d2l.get_key(), (2, 20))
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape

Šiame pavyzdyje sukonstravome savo modelį, sukurdami nn.Sequential egzempliorių ir priskirdami grąžintą objektą kintamajam net. Toliau pakartotinai kviečiame jo metodą add, pridėdami sluoksnius ta tvarka, kuria jie turėtų būti vykdomi. Trumpai tariant, nn.Sequential apibrėžia ypatingą Block rūšį – klasę, kuri Gluon atvaizduoja modulį. Ji palaiko sutvarkytą sudedamųjų Block sąrašą. Metodas add tiesiog palengvina kiekvieno paskesnio Block pridėjimą prie sąrašo. Atkreipkite dėmesį, kad kiekvienas sluoksnis yra klasės Dense egzempliorius, kuris pats yra Block poklasė. Tiesioginio sklidimo (forward) metodas taip pat nepaprastai paprastas: jis sujungia kiekvieną sąrašo Block į grandinę, perduodamas kiekvieno išvestį kaip įvestį kitam. Atkreipkite dėmesį, kad iki šiol savo modelius kvietėme konstrukcija net(X), kad gautume jų išvestis. Tai iš tikrųjų tėra santrumpa net.forward(X) – graži Python gudrybė, pasiekiama per klasės Block metodą __call__.

Šiame pavyzdyje sukonstravome savo modelį, sukurdami nn.Sequential egzempliorių, perduodami sluoksnius kaip argumentus ta tvarka, kuria jie turėtų būti vykdomi. Trumpai tariant, (nn.Sequential apibrėžia ypatingą Module rūšį) – klasę, kuri PyTorch atvaizduoja modulį. Ji palaiko sutvarkytą sudedamųjų Module sąrašą. Atkreipkite dėmesį, kad kiekvienas iš dviejų pilnai sujungtų sluoksnių yra klasės Linear egzempliorius, kuris pats yra Module poklasė. Tiesioginio sklidimo (forward) metodas taip pat nepaprastai paprastas: jis sujungia kiekvieną sąrašo modulį į grandinę, perduodamas kiekvieno išvestį kaip įvestį kitam. Atkreipkite dėmesį, kad iki šiol savo modelius kvietėme konstrukcija net(X), kad gautume jų išvestis. Tai iš tikrųjų tėra santrumpa net.__call__(X).

Šiame pavyzdyje sukonstravome savo modelį, sukurdami keras.models.Sequential egzempliorių, perduodami sluoksnius kaip argumentus ta tvarka, kuria jie turėtų būti vykdomi. Trumpai tariant, Sequential apibrėžia ypatingą keras.Model rūšį – klasę, kuri Keras atvaizduoja modulį. Ji palaiko sutvarkytą sudedamųjų Model sąrašą. Atkreipkite dėmesį, kad kiekvienas iš dviejų pilnai sujungtų sluoksnių yra klasės Dense egzempliorius, kuris pats yra Model poklasė. Tiesioginio sklidimo (call) metodas taip pat nepaprastai paprastas: jis sujungia kiekvieną sąrašo modulį į grandinę, perduodamas kiekvieno išvestį kaip įvestį kitam. Atkreipkite dėmesį, kad iki šiol savo modelius kvietėme konstrukcija net(X), kad gautume jų išvestis. Tai iš tikrųjų tėra santrumpa net.call(X) – graži Python gudrybė, pasiekiama per modulio klasės metodą __call__.

6.1.1 Pasirinktinis modulis

Galbūt lengviausias būdas išsiugdyti intuiciją apie tai, kaip veikia modulis, yra patiems jį įgyvendinti. Prieš tai trumpai apibendriname pagrindines funkcijas, kurias turi teikti kiekvienas modulis:

  1. Priimti įvesties duomenis kaip savo tiesioginio sklidimo metodo argumentus.
  2. Generuoti išvestį, kad tiesioginio sklidimo metodas grąžintų reikšmę. Atkreipkite dėmesį, kad išvestis gali turėti kitą formą nei įvestis. Pavyzdžiui, pirmasis pilnai sujungtas sluoksnis mūsų aukščiau pateiktame modelyje priima savavališkos dimensijos įvestį, bet grąžina 256 dimensijos išvestį.
  3. Apskaičiuoti savo išvesties gradientą įvesties atžvilgiu, kurį galima pasiekti per jo atgalinio sklidimo metodą. Paprastai tai vyksta automatiškai.
  4. Saugoti ir suteikti prieigą prie tų parametrų, kurie reikalingi tiesioginio sklidimo skaičiavimui atlikti.
  5. Prireikus inicializuoti modelio parametrus.

Šiame fragmente užkoduojame nuo nulio modulį, atitinkantį MLP su vienu paslėptuoju sluoksniu su 256 paslėptaisiais vienetais ir 10 dimensijų išvesties sluoksniu. Atkreipkite dėmesį, kad žemiau pateikta klasė MLP paveldi klasę, atvaizduojančią modulį. Labai pasikliausime tėvinės klasės metodais, pateikdami tik savo konstruktorių (Python metodą __init__) ir tiesioginio sklidimo metodą.

class MLP(nn.Block):
    def __init__(self):
        # Call the constructor of the MLP parent class nn.Block to perform
        # the necessary initialization
        super().__init__()
        self.hidden = nn.Dense(256, activation='relu')
        self.out = nn.Dense(10)

    # Define the forward propagation of the model, that is, how to return the
    # required model output based on the input X
    def forward(self, X):
        return self.out(self.hidden(X))
class MLP(nn.Module):
    def __init__(self):
        # Call the constructor of the parent class nn.Module to perform
        # the necessary initialization
        super().__init__()
        self.hidden = nn.LazyLinear(256)
        self.out = nn.LazyLinear(10)

    # Define the forward propagation of the model, that is, how to return the
    # required model output based on the input X
    def forward(self, X):
        return self.out(F.relu(self.hidden(X)))
class MLP(tf.keras.Model):
    def __init__(self):
        # Call the constructor of the parent class tf.keras.Model to perform
        # the necessary initialization
        super().__init__()
        self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu)
        self.out = tf.keras.layers.Dense(units=10)

    # Define the forward propagation of the model, that is, how to return the
    # required model output based on the input X
    def call(self, X):
        return self.out(self.hidden((X)))
class MLP(nn.Module):
    def setup(self):
        # Define the layers
        self.hidden = nn.Dense(256)
        self.out = nn.Dense(10)

    # Define the forward propagation of the model, that is, how to return the
    # required model output based on the input X
    def __call__(self, X):
        return self.out(nn.relu(self.hidden(X)))

Pirmiausia susitelkime į tiesioginio sklidimo metodą. Atkreipkite dėmesį, kad jis priima X kaip įvestį, apskaičiuoja paslėptąjį atvaizdą su pritaikyta aktyvacijos funkcija ir išveda savo logitus. Šiame MLP įgyvendinime abu sluoksniai yra egzemplioriaus kintamieji. Kad suprastume, kodėl tai pagrįsta, įsivaizduokite sukuriant du MLP, net1 ir net2, ir apmokant juos su skirtingais duomenimis. Natūralu, kad tikėtumėmės, jog jie atvaizduos du skirtingus išmoktus modelius.

Konstruktoriuje sukuriame MLP sluoksnius (ir vėliau kviečiame šiuos sluoksnius) kiekvieną kartą kviečiant tiesioginio sklidimo metodą. Atkreipkite dėmesį į keletą esminių detalių. Pirma, mūsų pritaikytas metodas __init__ kviečia tėvinės klasės metodą __init__ per super().__init__(), taip pasaugodamas mus nuo pasikartojančio šablonistinio kodo, taikomo daugumai modulių, kartojimo. Tada sukuriame du savo pilnai sujungtus sluoksnius, priskirdami juos self.hidden ir self.out. Atkreipkite dėmesį, kad jei neįgyvendiname naujo sluoksnio, nereikia rūpintis nei atgalinio sklidimo metodu, nei parametrų inicializavimu. Sistema šiuos metodus sugeneruos automatiškai. Išbandykime tai.

net = MLP()
if tab.selected('mxnet'):
    net.initialize()
net(X).shape
net = MLP()
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape

Pagrindinė modulio abstrakcijos dorybė yra jos universalumas. Galime sukurti modulio poklasę, kad sukurtume sluoksnius (pavyzdžiui, pilnai sujungto sluoksnio klasę), visus modelius (pavyzdžiui, aukščiau pateiktą klasę MLP) arba įvairius vidutinio sudėtingumo komponentus. Šį universalumą išnaudosime visuose būsimuose skyriuose, pavyzdžiui, nagrinėdami konvoliucinius neuroninius tinklus.

6.1.2 Nuoseklusis modulis

Dabar galime atidžiau pažvelgti, kaip veikia klasė Sequential. Prisiminkite, kad Sequential buvo sukurta kitiems moduliams sujungti į grandinę. Kad sukurtume savo supaprastintą MySequential, tereikia apibrėžti du pagrindinius metodus:

  1. Metodą, skirtą po vieną pridėti modulius į sąrašą.
  2. Tiesioginio sklidimo metodą, skirtą perduoti įvestį per modulių grandinę ta pačia tvarka, kuria jie buvo pridėti.

Tą patį funkcionalumą, kurį teikia numatytoji klasė Sequential, pateikia ir žemiau pateikta klasė MySequential.

class MySequential(nn.Block):
    def add(self, block):
        # Here, block is an instance of a Block subclass, and we assume that
        # it has a unique name. We save it in the member variable _children of
        # the Block class, and its type is OrderedDict. When the MySequential
        # instance calls the initialize method, the system automatically
        # initializes all members of _children
        self._children[block.name] = block

    def forward(self, X):
        # OrderedDict guarantees that members will be traversed in the order
        # they were added
        for block in self._children.values():
            X = block(X)
        return X
class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            self.add_module(str(idx), module)

    def forward(self, X):
        for module in self.children():            
            X = module(X)
        return X
class MySequential(tf.keras.Model):
    def __init__(self, *args):
        super().__init__()
        self.modules = args

    def call(self, X):
        for module in self.modules:
            X = module(X)
        return X
class MySequential(nn.Module):
    modules: List

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

Metodas add prideda vieną bloką į sutvarkytą žodyną _children. Galbūt stebitės, kodėl kiekvienas Gluon Block turi atributą _children ir kodėl jį naudojome, užuot tiesiog patys apibrėžę Python sąrašą. Trumpai tariant, pagrindinis _children pranašumas yra tas, kad mūsų bloko parametrų inicializavimo metu Gluon žino, kad reikia pažvelgti į _children žodyną, norint rasti pogrupius blokus, kurių parametrus taip pat reikia inicializuoti.

Metode __init__ pridedame kiekvieną modulį kviesdami metodą add_modules. Šiuos modulius vėliau galima pasiekti metodu children. Tokiu būdu sistema žino pridėtus modulius ir tinkamai inicializuos kiekvieno modulio parametrus.

Kai iškviečiamas mūsų MySequential tiesioginio sklidimo metodas, kiekvienas pridėtas modulis vykdomas ta tvarka, kuria jie buvo pridėti. Dabar galime iš naujo įgyvendinti MLP naudodami savo klasę MySequential.

net = MySequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
net(X).shape
net = MySequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))
net(X).shape
net = MySequential(
    tf.keras.layers.Dense(units=256, activation=tf.nn.relu),
    tf.keras.layers.Dense(10))
net(X).shape
net = MySequential([nn.Dense(256), nn.relu, nn.Dense(10)])
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape

Atkreipkite dėmesį, kad šis MySequential naudojimas yra identiškas kodui, kurį anksčiau parašėme klasei Sequential (kaip aprašyta 5.1 skyrelis).

6.1.3 Kodo vykdymas tiesioginio sklidimo metode

Klasė Sequential palengvina modelio konstravimą, leisdama mums surinkti naujas architektūras neapibrėžiant savo klasės. Tačiau ne visos architektūros yra paprastos grandinės. Kai reikia didesnio lankstumo, norėsime apibrėžti savo blokus. Pavyzdžiui, galime norėti vykdyti Python valdymo srautą tiesioginio sklidimo metode. Be to, galime norėti atlikti savavališkas matematines operacijas, nepasikliaudami vien iš anksto apibrėžtais neuroninių tinklų sluoksniais.

Galbūt pastebėjote, kad iki šiol visos mūsų tinklų operacijos veikė mūsų tinklo aktyvacijas ir jo parametrus. Tačiau kartais galime norėti įtraukti narius, kurie nėra nei ankstesnių sluoksnių rezultatas, nei atnaujinami parametrai. Juos vadiname pastoviaisiais parametrais. Tarkime, pavyzdžiui, kad norime sluoksnio, kuris apskaičiuoja funkciją f(x,w)=c⋅w⊤xf(\mathbf{x},\mathbf{w}) = c \cdot \mathbf{w}^\top \mathbf{x}, kur x\mathbf{x} yra įvestis, w\mathbf{w} yra mūsų parametras, o cc yra kokia nors nurodyta konstanta, kuri optimizavimo metu neatnaujinama. Taigi įgyvendiname klasę FixedHiddenMLP taip.

class FixedHiddenMLP(nn.Block):
    def __init__(self):
        super().__init__()
        # Random weight parameters created with the get_constant method
        # are not updated during training (i.e., constant parameters)
        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)
        # Use the created constant parameters, as well as the relu and dot
        # functions
        X = npx.relu(np.dot(X, self.rand_weight.data()) + 1)
        # Reuse the fully connected layer. This is equivalent to sharing
        # parameters with two fully connected layers
        X = self.dense(X)
        # Control flow
        while np.abs(X).sum() > 1:
            X /= 2
        return X.sum()
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # Random weight parameters that will not compute gradients and
        # therefore keep constant during training
        self.rand_weight = torch.rand((20, 20))
        self.linear = nn.LazyLinear(20)

    def forward(self, X):
        X = self.linear(X)        
        X = F.relu(X @ self.rand_weight + 1)
        # Reuse the fully connected layer. This is equivalent to sharing
        # parameters with two fully connected layers
        X = self.linear(X)
        # Control flow
        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()
        # Random weight parameters created with tf.constant are not updated
        # during training (i.e., constant parameters)
        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)
        # Use the created constant parameters, as well as the relu and
        # matmul functions
        X = tf.nn.relu(tf.matmul(X, self.rand_weight) + 1)
        # Reuse the fully connected layer. This is equivalent to sharing
        # parameters with two fully connected layers
        X = self.dense(X)
        # Control flow
        while tf.reduce_sum(tf.math.abs(X)) > 1:
            X /= 2
        return tf.reduce_sum(X)
class FixedHiddenMLP(nn.Module):
    # Random weight parameters that will not compute gradients and
    # therefore keep constant during training
    rand_weight: jnp.array = jax.random.uniform(d2l.get_key(), (20, 20))

    def setup(self):
        self.dense = nn.Dense(20)

    def __call__(self, X):
        X = self.dense(X)
        X = nn.relu(X @ self.rand_weight + 1)
        # Reuse the fully connected layer. This is equivalent to sharing
        # parameters with two fully connected layers
        X = self.dense(X)
        # Control flow
        while jnp.abs(X).sum() > 1:
            X /= 2
        return X.sum()

Šiame modelyje įgyvendiname paslėptąjį sluoksnį, kurio svoriai (self.rand_weight) inicializuojami atsitiktinai sukuriant egzempliorių ir vėliau yra pastovūs. Šis svoris nėra modelio parametras, todėl jis niekada neatnaujinamas atgaliniu sklidimu. Tada tinklas perduoda šio „fiksuoto“ sluoksnio išvestį per pilnai sujungtą sluoksnį.

Atkreipkite dėmesį, kad prieš grąžindamas išvestį, mūsų modelis padarė kai ką neįprasto. Paleidome ciklą while, tikrindami sąlygą, ar jo ℓ1\ell_1 norma didesnė už 11, ir dalijome savo išvesties vektorių iš 22, kol jis tenkino sąlygą. Galiausiai grąžinome X elementų sumą. Kiek mums žinoma, joks standartinis neuroninis tinklas neatlieka šios operacijos. Atkreipkite dėmesį, kad ši konkreti operacija gali būti nenaudinga jokioje realaus pasaulio užduotyje. Mūsų tikslas yra tik parodyti, kaip integruoti savavališką kodą į savo neuroninio tinklo skaičiavimų srautą.

net = FixedHiddenMLP()
if tab.selected('mxnet'):
    net.initialize()
net(X)
net = FixedHiddenMLP()
params = net.init(d2l.get_key(), X)
net.apply(params, X)

Galime maišyti ir derinti įvairius modulių sujungimo būdus. Šiame pavyzdyje modulius įdedame vieną į kitą kai kuriais kūrybiškais būdais.

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)
class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.LazyLinear(64), nn.ReLU(),
                                 nn.LazyLinear(32), nn.ReLU())
        self.linear = nn.LazyLinear(16)

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

chimera = nn.Sequential(NestMLP(), nn.LazyLinear(20), FixedHiddenMLP())
chimera(X)
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)
class NestMLP(nn.Module):
    def setup(self):
        self.net = nn.Sequential([nn.Dense(64), nn.relu,
                                  nn.Dense(32), nn.relu])
        self.dense = nn.Dense(16)

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

chimera = nn.Sequential([NestMLP(), nn.Dense(20), FixedHiddenMLP()])
params = chimera.init(d2l.get_key(), X)
chimera.apply(params, X)

6.1.4 Santrauka

Atskiri sluoksniai gali būti moduliai. Daug sluoksnių gali sudaryti modulį. Daug modulių gali sudaryti modulį.

Modulis gali turėti kodo. Moduliai pasirūpina daugybe buities darbų, įskaitant parametrų inicializavimą ir atgalinį sklidimą. Nuoseklius sluoksnių ir modulių sujungimus tvarko modulis Sequential.

6.1.5 Pratimai

  1. Kokių problemų kils, jei pakeisite MySequential, kad moduliai būtų saugomi Python sąraše?
  2. Įgyvendinkite modulį, kuris kaip argumentus priima du modulius, tarkime, net1 ir net2, ir tiesioginio sklidimo metu grąžina abiejų tinklų sujungtą išvestį. Tai taip pat vadinama lygiagrečiuoju moduliu.
  3. Tarkime, kad norite sujungti kelis to paties tinklo egzempliorius. Įgyvendinkite gamyklos funkciją, kuri sugeneruoja kelis to paties modulio egzempliorius, ir iš jų sukurkite didesnį tinklą.

Diskusijos

Diskusijos

Diskusijos

Diskusijos