目录
- 一、多层感知机
- 1. 隐藏层
- 1.1 线性模型可能会出错
- 1.2 在网络中加入隐藏层
- 1.3 从线性到非线性
- 1.4 通用近似定理
- 2. 激活函数
- 2.1 ReLU函数
- 2.2 sigmoid函数
- 2.3 tanh函数
- 3. 小结
- 二、多层感知机的从零开始实现
- 2.1 初始化模型参数
- 2.2 激活函数
- 2.3 模型
- 2.4 损失函数
- 2.5 训练
- 三、多层感知机的简洁实现
一、多层感知机
1. 隐藏层
1.1 线性模型可能会出错
- 线性意味着单调假设: 特征的增大导致模型输出的增大(权重为正), 或者导致模型输出的减小(权重为负)。
- 数据通过一种表示,这种表示会考虑到特征之间的相关交互作用。
1.2 在网络中加入隐藏层
- 在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。
- 将许多全连接层堆叠在一起, 每一层都输出到上面的层,直到生成最后的输出。 前L-1层看作表示,把最后一层看作线性预测器。这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。
- 这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。 因此,这个多层感知机中的层数为2。
1.3 从线性到非线性
- 对于h个隐藏单元的单隐藏层多层感知机, 用H表示隐藏层的输出, 称为隐藏表示。
- W(1)为隐藏层权重,W(2)为输出层权重,b(1)为隐藏层偏置,b(2)为输出层偏置。
- 此时模型依然是仿射函数,需要在仿射变换之后对每个隐藏单元应用非线性的激活函数,之后多层感知机不会退化成线性模型。
1.4 通用近似定理
- 多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用, 这些神经元依赖于每个输入的值,可以很容易地设计隐藏节点来执行任意计算。
- 可以使用更深的网络来逼近许多函数。
2. 激活函数
- 激活函数通过计算加权和并加上偏置来确定神经元是否应该被激活, 它们将输入信号转换为输出的可微运算。
- 大多数激活函数都是非线性的,如ReLU函数,sigmoid函数和tanh函数。
2.1 ReLU函数
- ReLU函数是修正线性单元,提供了一种非常简单的非线性变换,它实现简单,同时在各种预测任务中表现良好。
- ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。
detach()
:返回一个新的tensor,并且这个tensor是从当前的计算图中分离出来的。但是返回的tensor和原来的tensor是共享内存空间的。figsize
:Matplotlib库中的一个函数,用于设置图形的尺寸大小。
figsize(width, height)
:其中,width和height分别表示图像的宽度和高度,单位为英寸(inch)。requires_grad =True
:输出张量需要梯度。
%matplotlib inline
import torch
from d2l import torch as d2l
# 生成一个从-8.0到7.9的列表,以0.1为跳跃点
# 需要梯度
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
# 通过relu函数激活
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))
ReLU函数的导数
torch.ones_like()
:返回一个与输入张量input形状相同的张量,所有元素都设置为1。retain_graph=True
:第一次计算完梯度后,计算图会被保留下来,再次计算梯度时,就可以重复使用这个计算图,从而避免重复构建计算图,提高计算效率。
y.backward(torch.ones_like(x), retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))
- 当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1(输入值精确等于0时,ReLU函数不可导,但输入不会为0,一般用左边值替代)。
2.2 sigmoid函数
-
sigmoid函数将输入变换为区间(0, 1)上的输出,也叫挤压函数,它是一个平滑的、可微的阈值单元近似。。
-
sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代。
y = torch.sigmoid(x)
d2l.plot(x.detach(), y.detach(), 'x', 'sigmoid(x)', figsize=(5, 2.5))
sigmoid函数的导数
# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of sigmoid', figsize=(5, 2.5))
3.当输入为0时,sigmoid函数的导数达到最大值0.25; 而输入在任一方向上越远离0点时,导数越接近0。
2.3 tanh函数
- tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。
y = torch.tanh(x)
d2l.plot(x.detach(), y.detach(), 'x', 'tanh(x)', figsize=(5, 2.5))
tanh函数的导数
# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of tanh', figsize=(5, 2.5))
- 当输入接近0时,tanh函数的导数接近最大值1。 输入在任一方向上越远离0点,导数越接近0。
3. 小结
- 多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。
- 常用的激活函数包括ReLU函数、sigmoid函数和tanh函数。
二、多层感知机的从零开始实现
使用Fashion-MNIST图像分类数据集 。
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.1 初始化模型参数
- Fashion-MNIST中的每个图像由28×28=784个灰度像素值组成, 所有图像共分为10个类别。
- 将每个图像视为具有784个输入特征 和10个类的简单分类数据集。
- 实现一个具有单隐藏层的多层感知机, 它包含256个隐藏单元。
torch.zeros()函数
:返回一个形状为为size,类型为torch.dtype,里面的每一个值都是0的tensor。torch.randn
:用来生成随机数字的tensor,这些随机数字满足标准正态(0~1)。
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 定义参数w1,w2,b1,b2
# w1,w2为满足标准正态分布的随机数字,b1,b2为0
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]
2.2 激活函数
自己实现ReLU激活函数。
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
2.3 模型
使用reshape将每个二维图像转换为一个长度为num_inputs的向量。
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
2.4 损失函数
- 直接使用高级API中的内置函数来计算softmax和交叉熵损失。
reduction = none
:表示直接返回n分样本的loss。
loss = nn.CrossEntropyLoss(reduction='none')
2.5 训练
直接调用d2l包的train_ch3函数,详细见之前的线性回归。
# 迭代轮数为10,学习率为0.1
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
在测试数据集上进行预测
d2l.predict_ch3(net, test_iter)
三、多层感知机的简洁实现
- 通过高级API更简洁地实现多层感知机。
# 导入d2l包
import torch
from torch import nn
from d2l import torch as d2l
- 添加2个全连接层, 第一层是隐藏层,它包含256个隐藏单元,并使用了ReLU激活函数,第二层是输出层。
nn.Sequential()
:一个序列容器,用于搭建神经网络的模块被按照被传入构造器的顺序添加到nn.Sequential()容器中。把多个模块封装成一个模块。nn.Flatten()
:将连续的维度范围展平为张量。 经常在nn.Sequential()中出现,一般写在某个神经网络模型之后,用于对神经网络模型的输出进行处理,得到tensor类型的数据。nn.Linear()
:定义一个神经网络的线性层:
torch.nn.Linear(in_features, # 输入的神经元个数
out_features, # 输出神经元个数
bias=True # 是否包含偏置
)
torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
:tensor为一个n维torch.Tensor,mean为正态分布的平均值,std为正态分布的标准差。init_weights
:初始化网络的权重。它还将神经网络的权重设置为非零值,这对神经网络来说是有帮助的,因为神经网络往往会陷入局部最小值,所以给它们许多不同的起始值是个好主意。apply(fn)
:该方法会将fn递归的应用于模块的每一个子模块(.children()的结果)及其自身。
net = nn.Sequential(nn.Flatten(), # 展平为张量
# 定义784输入神经单元,256个输出神经单元的线性层
nn.Linear(784, 256),
# ReLU激活函数
nn.ReLU(),
# 定义256输入神经单元,10个输出神经单元的线性层
nn.Linear(256, 10))
# 初始化网络权重
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01) #初始化权重:标准差为0.01
# 每层都循环一下,最后对整个Sequential也进行操作,递归调用
net.apply(init_weights);
# 批量大小:256,学习率:10,迭代轮数:10
batch_size, lr, num_epochs = 256, 0.1, 10
# 交叉熵损失函数
loss = nn.CrossEntropyLoss(reduction='none')
# 内部优化器
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)