返回顶部

请叫我杨先生

导航

Pytorch 4.1 多层感知机认识

多层感知机认识

隐藏层 Hidden Layer

隐藏层通过放射变换,其实也就是 通过\(y = wx +b\)这种变换,本质上是线性的变换得到的。

按道理说,我们只需要设置一个单层隐藏层,通过不断增加Unit单元个数,就可以模拟所有的函数以及曲线,但是这样无疑是非常有难度的,我们可以将其看成是一个泰勒展开,无限求导逼近原函数,只不过我们通过隐藏层无限添加单元个数逼近真实的函数。但是我们可以增加神经网络的深度以效仿泰勒展开中的无限求导,通过增加隐藏层数,我们可以在较少的单元数的情况下比较好模拟真实的函数。或者说在神经网络深度和隐藏层做一个权衡。

从线性到非线性

我们通过矩阵 \(\mathbf{X} \in \mathbb{R}^{n \times d}\)表示 \(n\) 个样本的小批量,其中每个样本具有 \(d\) 个输入特征,用 \(\mathbf{H_i} \in \mathbb{R}^{n \times h_i}\) 表示第 \(i\) 隐藏层的输出,第 \(i\) 隐藏层权重 \(\mathbf{W}^{(i)} \in \mathbb{R}^{d \times h_i}\) 和偏置 \(\mathbf{b}^{(1)} \in \mathbb{R}^{1 \times h_i}\)

如果我们使用上面的神经网络作为例子,则我们的隐藏层和输出层可以写成:

\[\begin{split}\begin{aligned} \mathbf{H} & = \mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)}, \\ \mathbf{O} & = \mathbf{H}\mathbf{W}^{(2)} + \mathbf{b}^{(2)}. \end{aligned}\end{split}\]

上面的式子可以通过一个单层神经网络来完成 : \(\mathbf{O} = (\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)})\mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W}^{(1)}\mathbf{W}^{(2)} + \mathbf{b}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W} + \mathbf{b}\) ,验证隐藏层中的一个观点。

激活函数

ReLU函数

\[\operatorname{ReLU}(x) = \max(x, 0) \]

%matplotlib inline 
import torch
from d2l import torch as d2l 

x = torch.arange(-0.8,0.8,0.01,requires_grad=True) 
y = torch.relu(x) 
d2l.plot(x.detach().numpy() ,y.detach().numpy(),xlabel ='x',ylabel='relu(x)',figsize=(5,2.5))

ReLU导数

y = torch.relu(x)
y.backward(torch.ones_like(y),retain_graph=True)
d2l.plot(x.detach().numpy(),x.grad.numpy(),figsize=(5,2.5)) 

sigmoid函数

\[\operatorname{sigmoid}(x) = \frac{1}{1 + \exp(-x)} \]

y = torch.sigmoid(x)
d2l.plot(x.detach().numpy(), y.detach().numpy(), 'x', 'sigmoid(x)', figsize=(5, 2.5))

sigmoid导数

\[\frac{\partial}{\partial x} \operatorname{sigmoid}(x) = \frac{\exp(-x)}{(1 + \exp(-x))^2} = \operatorname{sigmoid}(x)\left(1-\operatorname{sigmoid}(x)\right) \]

x.grad.data.zero_() # 将其全部设置成 0. 
y.backward(torch.ones_like(y),retain_graph=True)
d2l.plot(x.detach().numpy(),x.grad.numpy(),figsize=(5,2.5))

tanh函数

\[\operatorname{tanh}(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)} \]

y = torch.tanh(x) 
d2l.plot(x.detach().numpy(),y.detach().numpy(),figsize=(5,2.5))
![](https://img2020.cnblogs.com/blog/2275059/202201/2275059-20220102001113804-1850547380.svg)

tanh导数

\[\frac{\partial}{\partial x} \operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x) \]

x.grad.data.zero_() # 将其全部设置成 0.
y.backward(torch.ones_like(y),retain_graph=True)
d2l.plot(x.detach().numpy(),x.grad.numpy(),figsize=(5,2.5))

章节答疑

Q1: 在运行d2l.plot()的时候为何torch.Tensor的数据直接转换成numpy时候会报错?

d2l.plot(x.numpy(),y.numpy()) 
RuntimeError: Can't call numpy() on Variable that requires grad. Use var.detach().numpy() instead.

A1:报错的原因是这个torch.Tensor中存在梯度信息,如果直接转换成numpy的话,会损失掉其中的梯度信息,所以numpy拒绝转换。 我们可以在其后面添加一个 *.detach()分离梯度和其他信息。官网是这样描述它的 Tensor.detach() 1. Returns a new Tensor, detached from the current graph. 2. The result will never require gradient. 3. This method also affects forward mode AD gradients and the result will never have forward mode AD gradients.

Q2:对于 *.backward的用法复习?为何使用 *.backward要传入一个大小和 * 一样的tensor?里面另外一个参数 retain_variables = True 又是什么?

x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
y.backward(torch.ones_like(y),retain_graph=True)

A2:官网给出的标准的method: Tensor.backward(gradient=None, retain_graph=None, create_graph=False, inputs=None)

  • Computes the gradient of current tensor w.r.t. graph leaves.
  • The graph is differentiated using the chain rule. If the tensor is non-scalar (i.e. its data has more than one element) and requires gradient, the function additionally requires specifying gradient. It should be a tensor of matching type and location, that contains the gradient of the differentiated function w.r.t. self.
  • This function accumulates gradients in the leaves - you might need to zero .grad attributes or set them to None before calling it. See Default gradient layouts for details on the memory layout of accumulated gradients.
  1. w.r.t. : with respect to 的缩写。是 关于;谈及,谈到的意思。
  2. i.e. :也就是,亦即(源自拉丁文id est),换而言之。

用人话来描述就是,我们求梯度要满足几个要求: 1.输入的不能够是scale,要是tensor才行。 2.使用 *.backward计算出来的梯度是叶子节点的梯度。 3. *.backward是积累形的method,每次使用它的时候都要清空之前的梯度。

此外,我们传入一个和 * 大小一样的tensor.ones_like(*)是为了存储梯度。 retain_variables = True 如果我们传入的不是一个scale的话,我们要指定 True。这也就是说为什么在计算标量的时候不用指定传入参数。

参考:
Automatic differentiation package - torch.autograd
TORCH.TENSOR.BACKWARD

posted on 2022-01-02 00:13  YangShusen'  阅读(184)  评论(0编辑  收藏  举报