多層感知器 (MLP) 的實(shí)現(xiàn)并不比簡單的線性模型復(fù)雜多少。關(guān)鍵的概念差異是我們現(xiàn)在連接多個(gè)層。
import torch from torch import nn from d2l import torch as d2l
from mxnet import np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np()
import jax from flax import linen as nn from jax import numpy as jnp from d2l import jax as d2l
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
import tensorflow as tf from d2l import tensorflow as d2l
5.2.1. 從零開始實(shí)施
讓我們從頭開始實(shí)現(xiàn)這樣一個(gè)網(wǎng)絡(luò)。
5.2.1.1. 初始化模型參數(shù)
回想一下,F(xiàn)ashion-MNIST 包含 10 個(gè)類,并且每個(gè)圖像由一個(gè)28×28=784灰度像素值網(wǎng)格。和以前一樣,我們暫時(shí)忽略像素之間的空間結(jié)構(gòu),因此我們可以將其視為具有 784 個(gè)輸入特征和 10 個(gè)類別的分類數(shù)據(jù)集。首先,我們將實(shí)現(xiàn)一個(gè)具有一個(gè)隱藏層和 256 個(gè)隱藏單元的 MLP。層數(shù)和寬度都是可調(diào)的(它們被認(rèn)為是超參數(shù))。通常,我們選擇層寬度可以被 2 的較大次冪整除。由于內(nèi)存在硬件中分配和尋址的方式,這在計(jì)算上是高效的。
同樣,我們將用幾個(gè)張量表示我們的參數(shù)。請(qǐng)注意, 對(duì)于每一層,我們必須跟蹤一個(gè)權(quán)重矩陣和一個(gè)偏置向量。與往常一樣,我們?yōu)檫@些參數(shù)的損失梯度分配內(nèi)存。
在下面的代碼中,我們使用 `nn.Parameter< https://pytorch.org/docs/stable/generated/torch.nn.parameter.Parameter.html >`__ 自動(dòng)將類屬性注冊(cè)為要跟蹤的參數(shù)autograd(第 2.5 節(jié)) .
class MLPScratch(d2l.Classifier): def __init__(self, num_inputs, num_outputs, num_hiddens, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens) * sigma) self.b1 = nn.Parameter(torch.zeros(num_hiddens)) self.W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs) * sigma) self.b2 = nn.Parameter(torch.zeros(num_outputs))
In the code below, we first define and initialize the parameters and then enable gradient tracking.
class MLPScratch(d2l.Classifier): def __init__(self, num_inputs, num_outputs, num_hiddens, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.W1 = np.random.randn(num_inputs, num_hiddens) * sigma self.b1 = np.zeros(num_hiddens) self.W2 = np.random.randn(num_hiddens, num_outputs) * sigma self.b2 = np.zeros(num_outputs) for param in self.get_scratch_params(): param.attach_grad()
In the code below we use `flax.linen.Module.param
class MLPScratch(d2l.Classifier): num_inputs: int num_outputs: int num_hiddens: int lr: float sigma: float = 0.01 def setup(self): self.W1 = self.param('W1', nn.initializers.normal(self.sigma), (self.num_inputs, self.num_hiddens)) self.b1 = self.param('b1', nn.initializers.zeros, self.num_hiddens) self.W2 = self.param('W2', nn.initializers.normal(self.sigma), (self.num_hiddens, self.num_outputs)) self.b2 = self.param('b2', nn.initializers.zeros, self.num_outputs)
In the code below we use `tf.Variable
class MLPScratch(d2l.Classifier): def __init__(self, num_inputs, num_outputs, num_hiddens, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.W1 = tf.Variable( tf.random.normal((num_inputs, num_hiddens)) * sigma) self.b1 = tf.Variable(tf.zeros(num_hiddens)) self.W2 = tf.Variable( tf.random.normal((num_hiddens, num_outputs)) * sigma) self.b2 = tf.Variable(tf.zeros(num_outputs))
5.2.1.2. 模型
為了確保我們知道一切是如何工作的,我們將自己實(shí)現(xiàn) ReLU 激活,而不是直接調(diào)用內(nèi)置relu函數(shù)。
def relu(X): a = torch.zeros_like(X) return torch.max(X, a)
def relu(X): return np.maximum(X, 0)
def relu(X): return jnp.maximum(X, 0)
def relu(X): return tf.math.maximum(X, 0)
由于我們忽略了空間結(jié)構(gòu),我們將reshape每個(gè)二維圖像轉(zhuǎn)換為長度為 的平面向量num_inputs。最后,我們只用幾行代碼就實(shí)現(xiàn)了我們的模型。由于我們使用框架內(nèi)置的 autograd,這就是它所需要的全部。
@d2l.add_to_class(MLPScratch) def forward(self, X): X = X.reshape((-1, self.num_inputs)) H = relu(torch.matmul(X, self.W1) + self.b1) return torch.matmul(H, self.W2) + self.b2
@d2l.add_to_class(MLPScratch) def forward(self, X): X = X.reshape((-1, self.num_inputs)) H = relu(np.dot(X, self.W1) + self.b1) return np.dot(H, self.W2) + self.b2
@d2l.add_to_class(MLPScratch) def forward(self, X): X = X.reshape((-1, self.num_inputs)) H = relu(jnp.matmul(X, self.W1) + self.b1) return jnp.matmul(H, self.W2) + self.b2
@d2l.add_to_class(MLPScratch) def forward(self, X): X = tf.reshape(X, (-1, self.num_inputs)) H = relu(tf.matmul(X, self.W1) + self.b1) return tf.matmul(H, self.W2) + self.b2
5.2.1.3. 訓(xùn)練
幸運(yùn)的是,MLP 的訓(xùn)練循環(huán)與 softmax 回歸完全相同。我們定義模型、數(shù)據(jù)、訓(xùn)練器,最后fit在模型和數(shù)據(jù)上調(diào)用方法。
model = MLPScratch(num_inputs=784, num_outputs=10, num_hiddens=256, lr=0.1) data = d2l.FashionMNIST(batch_size=256) trainer = d2l.Trainer(max_epochs=10) trainer.fit(model, data)
model = MLPScratch(num_inputs=784, num_outputs=10, num_hiddens=256, lr=0.1) data = d2l.FashionMNIST(batch_size=256) trainer = d2l.Trainer(max_epochs=10) trainer.fit(model, data)
model = MLPScratch(num_inputs=784, num_outputs=10, num_hiddens=256, lr=0.1) data = d2l.FashionMNIST(batch_size=256) trainer = d2l.Trainer(max_epochs=10) trainer.fit(model, data)
model = MLPScratch(num_inputs=784, num_outputs=10, num_hiddens=256, lr=0.1) data = d2l.FashionMNIST(batch_size=256) trainer = d2l.Trainer(max_epochs=10) trainer.fit(model, data)
5.2.2. 簡潔的實(shí)現(xiàn)
正如您所料,通過依賴高級(jí) API,我們可以更簡潔地實(shí)現(xiàn) MLP。
5.2.2.1. 模型
與我們對(duì) softmax 回歸實(shí)現(xiàn)的簡潔實(shí)現(xiàn)(第 4.5 節(jié))相比,唯一的區(qū)別是我們添加了兩個(gè)完全連接的層,而我們之前只添加了 一個(gè)。第一個(gè)是隱藏層,第二個(gè)是輸出層。
class MLP(d2l.Classifier): def __init__(self, num_outputs, num_hiddens, lr): super().__init__() self.save_hyperparameters() self.net = nn.Sequential(nn.Flatten(), nn.LazyLinear(num_hiddens), nn.ReLU(), nn.LazyLinear(num_outputs))
class MLP(d2l.Classifier): def __init__(self, num_outputs, num_hiddens, lr): super().__init__() self.save_hyperparameters() self.net = nn.Sequential() self.net.add(nn.Dense(num_hiddens, activation='relu'), nn.Dense(num_outputs)) self.net.initialize()
class MLP(d2l.Classifier): num_outputs: int num_hiddens: int lr: float @nn.compact def __call__(self, X): X = X.reshape((X.shape[0], -1)) # Flatten X = nn.Dense(self.num_hiddens)(X) X = nn.relu(X) X = nn.Dense(self.num_outputs)(X) return X
class MLP(d2l.Classifier): def __init__(self, num_outputs, num_hiddens, lr): super().__init__() self.save_hyperparameters() self.net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(num_hiddens, activation='relu'), tf.keras.layers.Dense(num_outputs)])
以前,我們forward為模型定義了使用模型參數(shù)轉(zhuǎn)換輸入的方法。這些操作本質(zhì)上是一個(gè)管道:您獲取一個(gè)輸入并應(yīng)用一個(gè)轉(zhuǎn)換(例如,矩陣與權(quán)重相乘,然后是偏差加法),然后重復(fù)使用當(dāng)前轉(zhuǎn)換的輸出作為下一個(gè)轉(zhuǎn)換的輸入。但是,您可能已經(jīng)注意到 forward這里沒有定義任何方法。實(shí)際上,從類(第 3.2.2 節(jié)MLP)繼承 方法以簡單地調(diào)用 (是輸入),現(xiàn)在定義為通過類進(jìn)行的一系列轉(zhuǎn)換。該類抽象了前向過程,使我們能夠?qū)W⒂谵D(zhuǎn)換。我們將進(jìn)一步討論如何forwardModuleself.net(X)XSequentialSequentialSequential類在第 6.1.2 節(jié)中起作用 。
5.2.2.2. 訓(xùn)練
訓(xùn)練循環(huán)與我們實(shí)現(xiàn) softmax 回歸時(shí)完全相同。這種模塊化使我們能夠?qū)⒂嘘P(guān)模型架構(gòu)的問題與正交考慮分開。
model = MLP(num_outputs=10, num_hiddens=256, lr=0.1) trainer.fit(model, data)
model = MLP(num_outputs=10, num_hiddens=256, lr=0.1) trainer.fit(model, data)
model = MLP(num_outputs=10, num_hiddens=256, lr=0.1) trainer.fit(model, data)
model = MLP(num_outputs=10, num_hiddens=256, lr=0.1) trainer.fit(model, data)
5.2.3. 概括
現(xiàn)在我們?cè)谠O(shè)計(jì)深度網(wǎng)絡(luò)方面有了更多的實(shí)踐,從單層到多層深度網(wǎng)絡(luò)的步驟不再構(gòu)成如此重大的挑戰(zhàn)。特別是,我們可以重用訓(xùn)練算法和數(shù)據(jù)加載器。但請(qǐng)注意,從頭開始實(shí)施 MLP 仍然很麻煩:命名和跟蹤模型參數(shù)使得擴(kuò)展模型變得困難。例如,假設(shè)想要在第 42 層和第 43 層之間插入另一層。這可能是第 42b 層,除非我們?cè)敢鈭?zhí)行順序重命名。此外,如果我們從頭開始實(shí)施網(wǎng)絡(luò),框架就很難執(zhí)行有意義的性能優(yōu)化。
盡管如此,您現(xiàn)在已經(jīng)達(dá)到了 1980 年代后期的最先進(jìn)水平,當(dāng)時(shí)完全連接的深度網(wǎng)絡(luò)是神經(jīng)網(wǎng)絡(luò)建模的首選方法。我們的下一個(gè)概念性步驟將是考慮圖像。在我們這樣做之前,我們需要回顧一些關(guān)于如何有效計(jì)算模型的統(tǒng)計(jì)基礎(chǔ)知識(shí)和細(xì)節(jié)。
5.2.4. 練習(xí)
更改隱藏單元的數(shù)量num_hiddens并繪制其數(shù)量如何影響模型的準(zhǔn)確性。這個(gè)超參數(shù)的最佳值是多少?
嘗試添加隱藏層以查看它如何影響結(jié)果。
為什么用單個(gè)神經(jīng)元插入隱藏層是個(gè)壞主意?會(huì)出什么問題?
改變學(xué)習(xí)率如何改變你的結(jié)果?在所有其他參數(shù)固定的情況下,哪個(gè)學(xué)習(xí)率能給你最好的結(jié)果?這與紀(jì)元數(shù)有何關(guān)系?
讓我們聯(lián)合優(yōu)化所有超參數(shù),即學(xué)習(xí)率、時(shí)期數(shù)、隱藏層數(shù)和每層隱藏單元數(shù)。
通過對(duì)所有這些進(jìn)行優(yōu)化可以獲得的最佳結(jié)果是什么?
為什么處理多個(gè)超參數(shù)更具挑戰(zhàn)性?
描述聯(lián)合優(yōu)化多個(gè)參數(shù)的有效策略。
比較框架的速度和從頭開始實(shí)施一個(gè)具有挑戰(zhàn)性的問題。它如何隨著網(wǎng)絡(luò)的復(fù)雜性而變化?
測(cè)量對(duì)齊良好和未對(duì)齊矩陣的張量矩陣乘法的速度。例如,測(cè)試維度為 1024、1025、1026、1028 和 1032 的矩陣。
這在 GPU 和 CPU 之間有何變化?
確定 CPU 和 GPU 的內(nèi)存總線寬度。
嘗試不同的激活函數(shù)。哪一個(gè)效果最好?
網(wǎng)絡(luò)的權(quán)重初始化之間是否存在差異?有關(guān)系嗎?
-
感知器
+關(guān)注
關(guān)注
0文章
34瀏覽量
11841 -
pytorch
+關(guān)注
關(guān)注
2文章
807瀏覽量
13199
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論