多层感知机

专栏:神经网络复现目录

多层感知机理论部分

本文系统的讲解多层感知机的pytorch复现,以及详细的代码解释。
部分文字和代码来自《动手学深度学习》!!

获取最新神经网络信息可以加我微信公众号
请添加图片描述



隐藏层

上两节我们使用的网络都是线性的,即只有输入层和输出层两层,但生活中线性模型有很大的限制。例如,如何对猫和狗的图像进行分类呢? 增加位置处像素的强度是否总是增加(或降低)图像描绘狗的似然? 对线性模型的依赖对应于一个隐含的假设, 即区分猫和狗的唯一要求是评估单个像素的强度。 在一个倒置图像后依然保留类别的世界里,这种方法注定会失败。

所以我们在网络中加入隐藏层来克服线性模型的限制,使其能更普遍的处理函数关系模型。

要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。像下面的图示:
在这里插入图片描述
这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。 输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。 因此,这个多层感知机中的层数为2。 注意,这两个层都是全连接的。 每个输入都会影响隐藏层中的每个神经元, 而隐藏层中的每个神经元又会影响输出层中的每个神经元。


多层感知机数学逻辑

多层感知机(Multilayer Perceptron, MLP)是一种基于多个全连接层的前馈神经网络,其公式如下:

假设输入为 x ∈ R d \boldsymbol{x} \in \mathbb{R}^d xRd,第 i i i 个全连接层的权重和偏差参数为 W i ∈ R h i − 1 × h i \boldsymbol{W}i \in \mathbb{R}^{h{i-1}\times h_i} WiRhi1×hi b i ∈ R 1 × h i \boldsymbol{b}_i \in \mathbb{R}^{1\times h_i} biR1×hi,激活函数为 ϕ i ( ⋅ ) \phi_i(\cdot) ϕi()。输出为 o ∈ R q \boldsymbol{o} \in \mathbb{R}^{q} oRq,则有:

h 0 = x h i = ϕ i ( h i − 1 W i + b i ) , i = 1 , 2 , … , n − 1 o = h n − 1 W n + b n \begin{aligned} \boldsymbol{h}_0 &= \boldsymbol{x} \\ \boldsymbol{h}i &= \phi_i(\boldsymbol{h}_{i-1} \boldsymbol{W}_i + \boldsymbol{b}i), \quad i=1,2,\ldots,n-1\\ \boldsymbol{o} &= \boldsymbol{h}_{n-1} \boldsymbol{W}_n + \boldsymbol{b}_n \end{aligned} h0hio=x=ϕi(hi1Wi+bi),i=1,2,,n1=hn1Wn+bn

其中 n n n 表示全连接层数, h i \boldsymbol{h}_i hi 表示第 i i i 层的输出, h 0 \boldsymbol{h}_0 h0 为输入, W n ∈ R h n − 1 × q \boldsymbol{W}n \in \mathbb{R}^{h{n-1}\times q} WnRhn1×q b n ∈ R 1 × q \boldsymbol{b}_n \in \mathbb{R}^{1\times q} bnR1×q 是输出层的权重和偏差参数。 h i h_i hi 表示第 i i i 层的输出维度,也就是该层的神经元个数。最后一层的激活函数一般不加,也可以视为 ϕ n ( ⋅ ) = i d e n t i t y ( ⋅ ) \phi_n(\cdot) = \mathrm{identity}(\cdot) ϕn()=identity()


激活函数

激活函数将非线性特性引入到神经网络中。在下图中,输入的 inputs 通过加权,求和后,还被作用了一个函数f,这个函数f就是激活函数。引入激活函数是为了增加神经网络模型的非线性。没有激活函数的每层都相当于矩阵相乘。就算你叠加了若干层之后,无非还是个矩阵相乘罢了。
在这里插入图片描述

为什么使用激活函数
如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。


1. ReLU函数

最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU), 因为它实现简单,同时在各种预测任务中表现良好。 ReLU提供了一种非常简单的非线性变换。 给定元素x,ReLU函数被定义为该元素与的0最大值:
R e L U ( x ) = m a x ( x , 0 ) ReLU(x) = max(x, 0) ReLU(x)=max(x,0)
通俗地说,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。 为了直观感受一下,我们可以画出函数的曲线图。 正如从图中所看到,激活函数是分段线性的。

x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))

在这里插入图片描述
当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。 注意,当输入值精确等于0时,ReLU函数不可导。 在此时,我们默认使用左侧的导数,即当输入为0时导数为0。 我们可以忽略这种情况,因为输入可能永远都不会是0。
观察其求导图像

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

在这里插入图片描述
观察图像可知,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题


2. sigmoid函数

sigmoid函数是一种常用的激活函数,是早期深度学习和神经网络研究比较常用的激活函数,但在现在使用较少,其常用于神经网络的输出层,将输入的值映射到[0, 1]区间内,公式如下:

s i g m o i d ( x ) = 1 1 + e x p ( − x ) sigmoid(x)=\frac{1}{1+exp(-x)} sigmoid(x)=1+exp(x)1
图像为:
在这里插入图片描述

其中,x为输入值。sigmoid函数的输出值范围在(0, 1)之间,当x为0时,sigmoid函数的值为0.5。sigmoid函数的导数可以用其自身表示:

σ ( x ) = 1 1 + e − x   d d x σ ( x ) = e − x ( 1 + e − x ) 2 & = 1 1 + e − x ⋅ e − x 1 + e − x   = σ ( x ) ( 1 − σ ( x ) ) \begin{aligned}\sigma(x) & =\frac{1}{1+e^{-x}} \ \frac{d}{dx} \sigma(x) &= \frac{e^{-x}}{(1+e^{-x})^2}\&=\frac{1}{1+e^{-x}}\cdot \frac{e^{-x}}{1+e^{-x}} \ &=\sigma(x)(1-\sigma(x))\end{aligned} σ(x)=1+ex1 dxdσ(x)=(1+ex)2ex&=1+ex11+exex =σ(x)(1σ(x))

sigmoid函数具有连续可导、单调递增、输出值在(0, 1)之间等特点,但其缺点也比较明显,如当输入值过大或过小时,其导数趋于0,容易出现梯度消失的问题。

感兴趣的同学可以参照上面的代码自行绘制sigmoid函数导数的图像


3. tanh函数

tanh函数是双曲正切函数(hyperbolic tangent function)的缩写,数学表达式为: tanh ( x ) = 1 − e − 2 x 1 + e − 2 x \text{tanh}(x) = \frac{1 - e^{-2x}}{1 + e^{-2x}} tanh(x)=1+e2x1e2x

图像为:
在这里插入图片描述

与sigmoid函数类似,tanh函数也是一种常用的激活函数。它的取值范围为 ( − 1 , 1 ) (-1, 1) (1,1),且在原点处取值为0。相比于sigmoid函数,在输入接近0的时候,tanh函数的函数值变化更为明显,可以使得模型更快地学习到特征。同时,tanh函数在原点两侧的函数值的变化也更加平缓,避免了在输入较大或较小的时候出现梯度消失的问题。
其导数为:
d d x tanh ⁡ ( x ) = 1 − tanh ⁡ 2 ( x ) \frac{d}{dx}\operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x) dxdtanh(x)=1tanh2(x)


多层感知机的从零开始实现

数据集

我们同样使用fashion-MNIST数据集,为了与上一节softmax回归做对比,理解线性和非线性之间的差别。

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)

注:load_data_fashion_mnist函数在上一节实现过,这里我们直接调用李沐大佬做的d2l包中的函数,源码在上一节。


初始化参数模型

回想一下,Fashion-MNIST中的每个图像由784个灰度像素值组成。 所有图像共分为10个类别。 忽略像素之间的空间结构, 我们可以将每个图像视为具有784个输入特征 和10个类的简单分类数据集。 首先,我们将实现一个具有单隐藏层的多层感知机, 它包含256个隐藏单元。 注意,我们可以将这两个变量都视为超参数。 通常,我们选择2的若干次幂作为层的宽度。 因为内存在硬件中的分配和寻址方式,这么做往往可以在计算上更高效。

为了简化代码,我们实现三层的感知机,故参数为输入层到隐藏层的W1,b1和隐藏层到输出层的W2,b2

num_inputs, num_outputs, num_hiddens = 784, 10, 256

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]

Parameter 是一个 Tensor 的子类,在定义神经网络时常用于将需要优化的参数包裹在其中。与普通的 Tensor 不同, Parameter 会被自动添加到模型的参数列表中,以便在反向传播过程中自动求导更新参数。当我们在使用优化算法训练模型时,我们只需要调用模型的 parameters() 方法即可获得所有需要优化的参数。

torch.randn(num_inputs, num_hiddens, requires_grad=True) 生成了一个大小为 (num_inputs, num_hiddens) 的张量,表示输入和隐藏层之间的权重。由于该张量是需要被优化的,因此 requires_grad=True 表示该张量的导数需要被计算和存储,以便在反向传播中计算梯度。

生成的随机数乘以 0.01 的目的是将其缩小,从而避免初始参数值过大,导致神经网络训练过程中出现梯度消失或爆炸的问题。


激活函数

def relu(X):
    a = torch.zeros_like(X)
    return torch.max(X, a)

模型定义

def net(X):
    X = X.reshape((-1, num_inputs))
    H = relu(X@W1 + b1)  # 这里“@”代表矩阵乘法
    return (H@W2 + b2)

损失函数

我们采用交叉熵作为损失函数

loss = nn.CrossEntropyLoss(reduction='none')

训练

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)

其中train_ch3的实现部分在上一节


多层感知机的pytorch实现(pytorch实现神经网络的流程)

1. 数据集

import torch
from torch import nn
from d2l import torch as d2l
import torchvision
from torch.utils import data
from torchvision import transforms
def get_dataloader_workers():  #@save
    """使用4个进程来读取数据"""
    return 4

def load_data_fashion_mnist(batch_size, resize=None):  #@save
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

模型定义及参数初始化

net = nn.Sequential(nn.Flatten(),
                    nn.Linear(784, 256),
                    nn.ReLU(),
                    nn.Linear(256, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

损失函数,优化器和超参数的定义

loss = torch.nn.CrossEntropyLoss()#损失函数
lr = 0.01#学习率
optim = torch.optim.SGD(net.parameters(),lr=lr)#优化器
epoch = 10
batch_size=256

tensorboard的初始化

tensorboard用于存储和可视化损失函数和精确度等数据

from torch.utils.tensorboard import SummaryWriter
log_dir = ''
writer = SummaryWriter(log_dir)

训练

train_iter, test_iter = load_data_fashion_mnist(batch_size)
train_step = 0 #每轮训练的次数
net.train()#模型在训练状态
for i in range(epoch):
    print("第{}轮训练".format(i+1))
    train_step = 0
    total_loss = 0.0#总损失
    total_correct = 0#总正确数
    total_examples = 0#总训练数
    for X,y in train_iter:
        # 前向传播
        output=net(X)
        l=loss(output,y)
         # 反向传播和优化
        optim.zero_grad()
        l.backward()
        optim.step()
        #记录/输出损失和精确率
        total_loss += l.item()
        total_correct += (output.argmax(dim=1) == y).sum().item()
        total_examples += y.size(0)
         # 记录训练损失和准确率到 tensorboard
        writer.add_scalar('train/loss', l.item(), epoch * len(train_iter) + i)
        writer.add_scalar('train/accuracy', total_correct/total_examples, epoch * len(train_iter) + i)

        train_step+=1
        if(train_step%100==0):#每训练一百组输出一次损失
            print("第{}轮的第{}次训练的loss:{}".format((i+1),train_step,l.item()))

    # 在测试集上面的效果
    net.eval() #在验证状态
    total_test_correct = 0
    total_test_examples = 0
    with torch.no_grad(): # 验证的部分,不是训练所以不要带入梯度
        for X,y in test_iter:
            outputs = net(X)
            test_result_loss=loss(outputs,y)
            total_test_correct += (outputs.argmax(1)==y).sum()
            total_test_examples += y.size(0)
        print("第{}轮训练在测试集上的准确率为{}".format((i+1),(total_test_correct/total_test_examples)))

这段代码是用于训练和测试深度学习模型的代码。首先使用 load_data_fashion_mnist(batch_size) 函数载入了Fashion-MNIST数据集并将训练数据和测试数据分别封装成 train_iter 和 test_iter 迭代器。

然后,将模型设置为训练状态:net.train()。接下来,开始训练模型。epoch 表示要迭代的轮数,外层循环控制训练轮数。内层循环遍历 train_iter 迭代器中的所有数据,并通过前向传播和反向传播更新模型参数。

在训练过程中,通过 total_loss、total_correct 和 total_examples 分别记录总的损失值、总的正确预测数量和总的样本数量,用于计算训练过程中的损失值和精确度。同时,将训练损失和准确率写入 tensorboard 中,以便后续可视化分析。

每训练一百次,将当前训练的损失值输出显示。

在训练完一轮后,使用 net.eval() 将模型转换为测试状态。在 with torch.no_grad() 的上下文管理器中,遍历 test_iter 迭代器中的所有测试数据,并通过模型进行预测,计算总的正确预测数量和总的样本数量,以计算测试准确率。最后,输出该轮训练在测试集上的准确率。

结果:
在这里插入图片描述

预测

def predict(net, test_iter, n=6):  #@save
    for X, y in test_iter:
        break
    trues = d2l.get_fashion_mnist_labels(y)
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
    titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(
        X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

predict(net, test_iter)

结果如下:
在这里插入图片描述

模型的保存与获取

torch.save(net,'这里填保存地址')
modlue = torch.load('这里填模型地址')
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青云遮夜雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值