PyTorch教程-5.2. 多层感知器的实现

电子说

1.2w人已加入

描述

多层感知器 (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 的较大次幂整除。由于内存在硬件中分配和寻址的方式,这在计算上是高效的。

同样,我们将用几个张量表示我们的参数。请注意, 对于每一层,我们必须跟踪一个权重矩阵和一个偏置向量。与往常一样,我们为这些参数的损失梯度分配内存。

在下面的代码中,我们使用 `nn.Parameter< https://pytorch.org/docs/stable/generated/torch.nn.parameter.Parameter.html >`__ 自动将类属性注册为要跟踪的参数autograd(第 2.5 节) .

 

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 `__ to define the model parameter.

 

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 `__ to define the model parameter.

 

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 的训练循环与 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)

 

pytorch

 

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)

 

pytorch

 

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)

 

pytorch

 

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)

 

pytorch

5.2.2. 简洁的实现

正如您所料,通过依赖高级 API,我们可以更简洁地实现 MLP。

5.2.2.1. 模型

与我们对 softmax 回归实现的简洁实现(第 4.5 节)相比,唯一的区别是我们添加了两个完全连接的层,而我们之前只添加了 一个。第一个是隐藏层,第二个是输出层。

 

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 节MLP)继承 方法以简单地调用 (是输入),现在定义为通过类进行的一系列转换。该类抽象了前向过程,使我们能够专注于转换。我们将进一步讨论如何forwardModuleself.net(X)XSequentialSequentialSequential类在第 6.1.2 节中起作用 。

5.2.2.2. 训练

训练循环与我们实现 softmax 回归时完全相同。这种模块化使我们能够将有关模型架构的问题与正交考虑分开。

 

model = MLP(num_outputs=10, num_hiddens=256, lr=0.1)
trainer.fit(model, data)

 

pytorch

 

model = MLP(num_outputs=10, num_hiddens=256, lr=0.1)
trainer.fit(model, data)

 

pytorch

 

model = MLP(num_outputs=10, num_hiddens=256, lr=0.1)
trainer.fit(model, data)

 

pytorch

 

model = MLP(num_outputs=10, num_hiddens=256, lr=0.1)
trainer.fit(model, data)

 

pytorch

5.2.3. 概括

现在我们在设计深度网络方面有了更多的实践,从单层到多层深度网络的步骤不再构成如此重大的挑战。特别是,我们可以重用训练算法和数据加载器。但请注意,从头开始实施 MLP 仍然很麻烦:命名和跟踪模型参数使得扩展模型变得困难。例如,假设想要在第 42 层和第 43 层之间插入另一层。这可能是第 42b 层,除非我们愿意执行顺序重命名。此外,如果我们从头开始实施网络,框架就很难执行有意义的性能优化。

尽管如此,您现在已经达到了 1980 年代后期的最先进水平,当时完全连接的深度网络是神经网络建模的首选方法。我们的下一个概念性步骤将是考虑图像。在我们这样做之前,我们需要回顾一些关于如何有效计算模型的统计基础知识和细节。

5.2.4. 练习

更改隐藏单元的数量num_hiddens并绘制其数量如何影响模型的准确性。这个超参数的最佳值是多少?

尝试添加隐藏层以查看它如何影响结果。

为什么用单个神经元插入隐藏层是个坏主意?会出什么问题?

改变学习率如何改变你的结果?在所有其他参数固定的情况下,哪个学习率能给你最好的结果?这与纪元数有何关系?

让我们联合优化所有超参数,即学习率、时期数、隐藏层数和每层隐藏单元数。

通过对所有这些进行优化可以获得的最佳结果是什么?

为什么处理多个超参数更具挑战性?

描述联合优化多个参数的有效策略。

比较框架的速度和从头开始实施一个具有挑战性的问题。它如何随着网络的复杂性而变化?

测量对齐良好和未对齐矩阵的张量矩阵乘法的速度。例如,测试维度为 1024、1025、1026、1028 和 1032 的矩阵。

这在 GPU 和 CPU 之间有何变化?

确定 CPU 和 GPU 的内存总线宽度。

尝试不同的激活函数。哪一个效果最好?

网络的权重初始化之间是否存在差异?有关系吗?

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分