多層感知器 (MLP) 的實現并不比簡單的線性模型復雜多少。關鍵的概念差異是我們現在連接多個層。
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. 從零開始實施
讓我們從頭開始實現這樣一個網絡。
5.2.1.1. 初始化模型參數
回想一下,Fashion-MNIST 包含 10 個類,并且每個圖像由一個28×28=784灰度像素值網格。和以前一樣,我們暫時忽略像素之間的空間結構,因此我們可以將其視為具有 784 個輸入特征和 10 個類別的分類數據集。首先,我們將實現一個具有一個隱藏層和 256 個隱藏單元的 MLP。層數和寬度都是可調的(它們被認為是超參數)。通常,我們選擇層寬度可以被 2 的較大次冪整除。由于內存在硬件中分配和尋址的方式,這在計算上是高效的。
同樣,我們將用幾個張量表示我們的參數。請注意, 對于每一層,我們必須跟蹤一個權重矩陣和一個偏置向量。與往常一樣,我們?yōu)檫@些參數的損失梯度分配內存。
在下面的代碼中,我們使用 `nn.Parameter< https://pytorch.org/docs/stable/generated/torch.nn.parameter.Parameter.html >`__ 自動將類屬性注冊為要跟蹤的參數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. 模型
為了確保我們知道一切是如何工作的,我們將自己實現 ReLU 激活,而不是直接調用內置relu函數。
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)
由于我們忽略了空間結構,我們將reshape每個二維圖像轉換為長度為 的平面向量num_inputs。最后,我們只用幾行代碼就實現了我們的模型。由于我們使用框架內置的 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. 訓練
幸運的是,MLP 的訓練循環(huán)與 softmax 回歸完全相同。我們定義模型、數據、訓練器,最后fit在模型和數據上調用方法。
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. 簡潔的實現
正如您所料,通過依賴高級 API,我們可以更簡潔地實現 MLP。
5.2.2.1. 模型
與我們對 softmax 回歸實現的簡潔實現(第 4.5 節(jié))相比,唯一的區(qū)別是我們添加了兩個完全連接的層,而我們之前只添加了 一個。第一個是隱藏層,第二個是輸出層。
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為模型定義了使用模型參數轉換輸入的方法。這些操作本質上是一個管道:您獲取一個輸入并應用一個轉換(例如,矩陣與權重相乘,然后是偏差加法),然后重復使用當前轉換的輸出作為下一個轉換的輸入。但是,您可能已經注意到 forward這里沒有定義任何方法。實際上,從類(第 3.2.2 節(jié)MLP)繼承 方法以簡單地調用 (是輸入),現在定義為通過類進行的一系列轉換。該類抽象了前向過程,使我們能夠專注于轉換。我們將進一步討論如何forwardModuleself.net(X)XSequentialSequentialSequential類在第 6.1.2 節(jié)中起作用 。
5.2.2.2. 訓練
訓練循環(huán)與我們實現 softmax 回歸時完全相同。這種模塊化使我們能夠將有關模型架構的問題與正交考慮分開。
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. 概括
現在我們在設計深度網絡方面有了更多的實踐,從單層到多層深度網絡的步驟不再構成如此重大的挑戰(zhàn)。特別是,我們可以重用訓練算法和數據加載器。但請注意,從頭開始實施 MLP 仍然很麻煩:命名和跟蹤模型參數使得擴展模型變得困難。例如,假設想要在第 42 層和第 43 層之間插入另一層。這可能是第 42b 層,除非我們愿意執(zhí)行順序重命名。此外,如果我們從頭開始實施網絡,框架就很難執(zhí)行有意義的性能優(yōu)化。
盡管如此,您現在已經達到了 1980 年代后期的最先進水平,當時完全連接的深度網絡是神經網絡建模的首選方法。我們的下一個概念性步驟將是考慮圖像。在我們這樣做之前,我們需要回顧一些關于如何有效計算模型的統計基礎知識和細節(jié)。
5.2.4. 練習
更改隱藏單元的數量num_hiddens并繪制其數量如何影響模型的準確性。這個超參數的最佳值是多少?
嘗試添加隱藏層以查看它如何影響結果。
為什么用單個神經元插入隱藏層是個壞主意?會出什么問題?
改變學習率如何改變你的結果?在所有其他參數固定的情況下,哪個學習率能給你最好的結果?這與紀元數有何關系?
讓我們聯合優(yōu)化所有超參數,即學習率、時期數、隱藏層數和每層隱藏單元數。
通過對所有這些進行優(yōu)化可以獲得的最佳結果是什么?
為什么處理多個超參數更具挑戰(zhàn)性?
描述聯合優(yōu)化多個參數的有效策略。
比較框架的速度和從頭開始實施一個具有挑戰(zhàn)性的問題。它如何隨著網絡的復雜性而變化?
測量對齊良好和未對齊矩陣的張量矩陣乘法的速度。例如,測試維度為 1024、1025、1026、1028 和 1032 的矩陣。
這在 GPU 和 CPU 之間有何變化?
確定 CPU 和 GPU 的內存總線寬度。
嘗試不同的激活函數。哪一個效果最好?
網絡的權重初始化之間是否存在差異?有關系嗎?
-
感知器
+關注
關注
0文章
34瀏覽量
11849 -
pytorch
+關注
關注
2文章
808瀏覽量
13251
發(fā)布評論請先 登錄
相關推薦
評論