PyTorch教程-3.5. 线性回归的简洁实现

电子说

1.2w人已加入

描述

在过去的十年中,深度学习见证了某种形式的寒武纪大爆发。技术、应用和算法的绝对数量远远超过了前几十年的进步。这是由于多种因素的偶然组合,其中之一是许多开源深度学习框架提供的强大的免费工具。Theano (Bergstra等人,2010 年)、DistBelief (Dean等人,2012 年)和 Caffe (Jia等人,2014 年)可以说代表了被广泛采用的第一代此类模型。与 SN2 (Simulateur Neuristique) 等早期(开创性)作品相比 (Bottou 和 Le Cun,1988),它提供了类似 Lisp 的编程体验,现代框架提供了自动微分和 Python 的便利性。这些框架使我们能够自动化和模块化实现基于梯度的学习算法的重复性工作。

在3.4 节中,我们仅依靠 (i) 张量进行数据存储和线性代数;(ii) 计算梯度的自动微分。在实践中,由于数据迭代器、损失函数、优化器和神经网络层非常普遍,现代图书馆也为我们实现了这些组件。在本节中,我们将向您展示如何 使用深度学习框架的高级 API 简洁地实现3.4 节中的线性回归模型。

 

import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

 

 

from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

 

 

import jax
import optax
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 numpy as np
import tensorflow as tf
from d2l import tensorflow as d2l

 

3.5.1. 定义模型

当我们在第 3.4 节中从头开始实现线性回归时 ,我们明确定义了我们的模型参数并编写了计算代码以使用基本线性代数运算生成输出。你应该知道如何做到这一点。但是一旦您的模型变得更加复杂,并且一旦您几乎每天都必须这样做,您就会很高兴获得帮助。这种情况类似于从头开始编写自己的博客。做一两次是有益和有益的,但如果你花一个月重新发明轮子,你将成为一个糟糕的 Web 开发人员。

对于标准操作,我们可以使用框架的预定义层,这使我们能够专注于用于构建模型的层,而不用担心它们的实现。回想一下图 3.1.2中描述的单层网络的架构。该层称为全连接层,因为它的每个输入都通过矩阵向量乘法连接到它的每个输出。

在 PyTorch 中,全连接层定义在Linear和 LazyLinear(自版本 1.8.0 起可用)类中。后者允许用户仅指定输出维度,而前者额外询问有多少输入进入该层。指定输入形状很不方便,这可能需要大量的计算(例如在卷积层中)。因此,为简单起见,我们将尽可能使用此类“惰性”层。

 

class LinearRegression(d2l.Module): #@save
  """The linear regression model implemented with high-level APIs."""
  def __init__(self, lr):
    super().__init__()
    self.save_hyperparameters()
    self.net = nn.LazyLinear(1)
    self.net.weight.data.normal_(0, 0.01)
    self.net.bias.data.fill_(0)

 

In Gluon, the fully connected layer is defined in the Dense class. Since we only want to generate a single scalar output, we set that number to 1. It is worth noting that, for convenience, Gluon does not require us to specify the input shape for each layer. Hence we do not need to tell Gluon how many inputs go into this linear layer. When we first pass data through our model, e.g., when we execute net(X) later, Gluon will automatically infer the number of inputs to each layer and thus instantiate the correct model. We will describe how this works in more detail later.

 

class LinearRegression(d2l.Module): #@save
  """The linear regression model implemented with high-level APIs."""
  def __init__(self, lr):
    super().__init__()
    self.save_hyperparameters()
    self.net = nn.Dense(1)
    self.net.initialize(init.Normal(sigma=0.01))

 

 

class LinearRegression(d2l.Module): #@save
  """The linear regression model implemented with high-level APIs."""
  lr: float

  def setup(self):
    self.net = nn.Dense(1, kernel_init=nn.initializers.normal(0.01))

 

In Keras, the fully connected layer is defined in the Dense class. Since we only want to generate a single scalar output, we set that number to 1. It is worth noting that, for convenience, Keras does not require us to specify the input shape for each layer. We do not need to tell Keras how many inputs go into this linear layer. When we first try to pass data through our model, e.g., when we execute net(X) later, Keras will automatically infer the number of inputs to each layer. We will describe how this works in more detail later.

 

class LinearRegression(d2l.Module): #@save
  """The linear regression model implemented with high-level APIs."""
  def __init__(self, lr):
    super().__init__()
    self.save_hyperparameters()
    initializer = tf.initializers.RandomNormal(stddev=0.01)
    self.net = tf.keras.layers.Dense(1, kernel_initializer=initializer)

 

在forward方法中,我们只调用预定义层的内置__call__ 方法来计算输出。

 

@d2l.add_to_class(LinearRegression) #@save
def forward(self, X):
  return self.net(X)

 

 

@d2l.add_to_class(LinearRegression) #@save
def forward(self, X):
  return self.net(X)

 

 

@d2l.add_to_class(LinearRegression) #@save
def forward(self, X):
  return self.net(X)

 

 

@d2l.add_to_class(LinearRegression) #@save
def forward(self, X):
  return self.net(X)

 

3.5.2. 定义损失函数

该类MSELoss计算均方误差(没有 1/2(3.1.5)中的因素)。默认情况下,MSELoss 返回示例的平均损失。它比我们自己实现更快(也更容易使用)。

 

@d2l.add_to_class(LinearRegression) #@save
def loss(self, y_hat, y):
  fn = nn.MSELoss()
  return fn(y_hat, y)

 

The loss module defines many useful loss functions. For speed and convenience, we forgo implementing our own and choose the built-in loss.L2Loss instead. Because the loss that it returns is the squared error for each example, we use meanto average the loss across over the minibatch.

 

@d2l.add_to_class(LinearRegression) #@save
def loss(self, y_hat, y):
  fn = gluon.loss.L2Loss()
  return fn(y_hat, y).mean()

 

 

@d2l.add_to_class(LinearRegression) #@save
def loss(self, params, X, y, state):
  y_hat = state.apply_fn({'params': params}, *X)
  return optax.l2_loss(y_hat, y).mean()

 

The MeanSquaredError class computes the mean squared error (without the 1/2 factor in (3.1.5)). By default, it returns the average loss over examples.

 

@d2l.add_to_class(LinearRegression) #@save
def loss(self, y_hat, y):
  fn = tf.keras.losses.MeanSquaredError()
  return fn(y, y_hat)

 

3.5.3. 定义优化算法

Minibatch SGD 是用于优化神经网络的标准工具,因此 PyTorch 支持它以及模块中该算法的许多变体optim。当我们实例化一个SGD实例时,我们指定要优化的参数,可通过 和我们的优化算法所需的self.parameters()学习率 ( ) 从我们的模型中获得。self.lr

 

@d2l.add_to_class(LinearRegression) #@save
def configure_optimizers(self):
  return torch.optim.SGD(self.parameters(), self.lr)

 

Minibatch SGD is a standard tool for optimizing neural networks and thus Gluon supports it alongside a number of variations on this algorithm through its Trainer class. Note that Gluon’s Trainer class stands for the optimization algorithm, while the Trainer class we created in Section 3.2 contains the training method, i.e., repeatedly call the optimizer to update the model parameters. When we instantiate Trainer, we specify the parameters to optimize over, obtainable from our model net via net.collect_params(), the optimization algorithm we wish to use (sgd), and a dictionary of hyperparameters required by our optimization algorithm.

 

@d2l.add_to_class(LinearRegression) #@save
def configure_optimizers(self):
  return gluon.Trainer(self.collect_params(),
             'sgd', {'learning_rate': self.lr})

 

 

@d2l.add_to_class(LinearRegression) #@save
def configure_optimizers(self):
  return optax.sgd(self.lr)

 

Minibatch SGD is a standard tool for optimizing neural networks and thus Keras supports it alongside a number of variations on this algorithm in the optimizers module.

 

@d2l.add_to_class(LinearRegression) #@save
def configure_optimizers(self):
  return tf.keras.optimizers.SGD(self.lr)

 

3.5.4. 训练

您可能已经注意到,通过深度学习框架的高级 API 表达我们的模型需要更少的代码行。我们不必单独分配参数、定义损失函数或实施小批量 SGD。一旦我们开始处理更复杂的模型,高级 API 的优势就会显着增加。现在我们已经准备好所有基本部分,训练循环本身与我们从头开始实施的循环相同。所以我们只需要调用 依赖于3.4节方法 实现的方法( 3.2.4fit节介绍)来训练我们的模型。fit_epoch

 

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=torch.tensor([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

 

pytorch

 

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=np.array([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

 

pytorch

 

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=jnp.array([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

 

pytorch

 

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=tf.constant([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

 

pytorch

下面,我们将通过有限数据训练学习到的模型参数与生成数据集的实际参数进行比较。为了访问参数,我们访问了我们需要的层的权重和偏差。正如我们从头开始实施一样,请注意我们估计的参数接近于它们的真实对应物。

 

@d2l.add_to_class(LinearRegression) #@save
def get_w_b(self):
  return (self.net.weight.data, self.net.bias.data)
w, b = model.get_w_b()

print(f'error in estimating w: {data.w - w.reshape(data.w.shape)}')
print(f'error in estimating b: {data.b - b}')

 

 

error in estimating w: tensor([ 0.0022, -0.0069])
error in estimating b: tensor([0.0080])

 

 

@d2l.add_to_class(LinearRegression) #@save
def get_w_b(self):
  return (self.net.weight.data(), self.net.bias.data())
w, b = model.get_w_b()

 

 

@d2l.add_to_class(LinearRegression) #@save
def get_w_b(self, state):
  net = state.params['net']
  return net['kernel'], net['bias']

w, b = model.get_w_b(trainer.state)

 

 

@d2l.add_to_class(LinearRegression) #@save
def get_w_b(self):
  return (self.get_weights()[0], self.get_weights()[1])

w, b = model.get_w_b()

 

3.5.5. 概括

本节包含深度网络(在本书中)的第一个实现,以利用现代深度学习框架提供的便利,例如 MXNet (Chen等人,2015 年)、JAX (Frostig等人,2018 年)、PyTorch (Paszke等人,2019 年)和 Tensorflow (Abadi等人,2016 年). 我们使用框架默认值来加载数据、定义层、损失函数、优化器和训练循环。每当框架提供所有必要的功能时,使用它们通常是个好主意,因为这些组件的库实现往往会针对性能进行大量优化,并针对可靠性进行适当测试。同时,尽量不要忘记这些模块是可以直接实现的。这对于希望生活在模型开发前沿的有抱负的研究人员尤其重要,您将在其中发明任何当前库中不可能存在的新组件。

在PyTorch中,该data模块提供了数据处理的工具,该 nn模块定义了大量的神经网络层和常见的损失函数。我们可以通过用以结尾的方法替换它们的值来初始化参数_。请注意,我们需要指定网络的输入维度。虽然这目前微不足道,但当我们想要设计具有多层的复杂网络时,它可能会产生重大的连锁反应。需要仔细考虑如何参数化这些网络以实现可移植性。

In Gluon, the data module provides tools for data processing, the nn module defines a large number of neural network layers, and the loss module defines many common loss functions. Moreover, the initializer gives access to many choices for parameter initialization. Conveniently for the user, dimensionality and storage are automatically inferred. A consequence of this lazy initialization is that you must not attempt to access parameters before they have been instantiated (and initialized).

In TensorFlow, the data module provides tools for data processing, the keras module defines a large number of neural network layers and common loss functions. Moreover, the initializers module provides various methods for model parameter initialization. Dimensionality and storage for networks are automatically inferred (but be careful not to attempt to access parameters before they have been initialized).

3.5.6. 练习

如果将小批量的总损失替换为小批量损失的平均值,您需要如何更改学习率?

查看框架文档以查看提供了哪些损失函数。特别是,用 Huber 的稳健损失函数替换平方损失。即使用损失函数

(3.5.1)l(y,y′)={|y−y′|−σ2 if |y−y′|>σ12σ(y−y′)2 otherwise

您如何访问模型权重的梯度?

如果改变学习率和迭代次数,解决方案会如何变化?它是否不断改进?

当您更改生成的数据量时,解决方案如何变化?

绘制估计误差 w^−w和b^−b作为数据量的函数。提示:以对数方式而不是线性方式增加数据量,即 5、10、20、50、...、10,000 而不是 1,000、2,000、...、10,000。

为什么提示中的建议是合适的?

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

全部0条评论

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

×
20
完善资料,
赚取积分