多层感知机

news2024/9/23 5:24:26

多层感知机理论部分

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


目录

  • 多层感知机理论部分
    • 隐藏层
    • 多层感知机数学逻辑
    • 激活函数
      • 1. ReLU函数
      • 2. sigmoid函数
      • 3. tanh函数
  • 多层感知机的从零开始实现
    • 数据集
    • 初始化参数模型
    • 激活函数
    • 模型定义
    • 损失函数
    • 训练
  • 多层感知机的pytorch实现(pytorch实现神经网络的流程)
    • 1. 数据集
    • 模型定义及参数初始化
    • 损失函数,优化器和超参数的定义
    • tensorboard的初始化
    • 训练
    • 预测
    • 模型的保存与获取


隐藏层

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

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

要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(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('这里填模型地址')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Allegro如何快速把推挤的走线变平滑操作指导

Allegro如何快速把推挤的走线变平滑操作指导 Allegro有个非常强大的功能,推挤命令,可以快速的让走线以不报DRC的形式避让目标 推挤后的效果如下图 但是走线不够平滑,如果每一段都去再推一下比较费时间,下面介绍allegro本身自带的优化类似走线的功能 具体操作如下 点击Rout…

sklearn学习-朴素贝叶斯

文章目录一、概述1、真正的概率分类器2、sklearn中的朴素贝叶斯二、不同分布下的贝叶斯1、高斯朴素贝叶斯GaussianNB2、探索贝叶斯:高斯朴素贝叶斯擅长的数据集3、探索贝叶斯:高斯朴素贝叶斯的拟合效果与运算速度总结一、概述 1、真正的概率分类器 算法…

计算机组成与体系结构

目录 1.计算机结构 2.寻址方式 3.CISC与RISC 4.流水线 1.计算机结构 运算器 算术逻辑单元ALU:数据的算术运算和逻辑运算累加寄存器AC:通用寄存器,为ALU提供一个工作区,用在暂存数据数据缓存寄存器DR:写内存中&…

Linux LVM逻辑卷

目录 LVM逻辑卷 什么是LVM LVM常用术语 管理逻辑卷相关命令 创建LVM逻辑卷 LVM扩容 LVM缩小 LVM快照卷 删除LVM LVM逻辑卷 什么是LVM LVM(Logical Volume Manager)逻辑卷管理器,是一种硬盘的虚拟化技术,能够实现用户对硬…

基于微信小程序的校园顺路代送小程序

文末联系获取源码 开发语言:Java 框架:ssm JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏览器…

还真不错,今天 Chatgpt 教会我如何开发一款小工具开发(Python 代码实现)

上次使用 Chatgpt 写爬虫,虽然写出来的代码很多需要修改后才能运行,但Chatgpt提供的思路和框架都是没问题。 这次让 Chatgpt 写一写GUI程序,也就是你常看到的桌面图形程序。 由于第一次测试,就来个简单点的,用Python…

Linux命令之grep

Linux grep是一个非常强大的文本搜索工具。按照给定的正则表达式对目标文本进行匹配检查,打印匹配到的行。grep命令可以跟其他命令一起使用,对其他命令的输出进行匹配。 grep语法如下: grep [options] [pattern] content 文本检索 grep可以对…

51单片机蜂鸣器的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、有源蜂鸣器和无源蜂鸣器的区别二、代码编写总结前言 本文旨在介绍如何使用51单片机驱动蜂鸣器。 一、有源蜂鸣器和无源蜂鸣器的区别 有源蜂鸣器是一种电子…

easyExcel 写复杂表头

写模板 模板图片: 实体类(这里没有用Data 是因为Lombok和easyExcal的版本冲突,在导入读取的时候获取不到值) package cn.iocoder.yudao.module.project.controller.admin.goods.vo;import com.alibaba.excel.annotation.ExcelI…

编译安装MySQL

MySQL 5.7主要特性 随机root 密码:MySQL 5.7 数据库初始化完成后,会自动生成一个 rootlocalhost 用户,root 用户的密码不为空,而是随机产生一个密码。原生支持:Systemd 更好的性能:对于多核CPU、固态硬盘、…

【蓝桥集训】第四天——双指针

作者:指针不指南吗 专栏:Acwing 蓝桥集训每日一题 🐾或许会很慢,但是不可以停下🐾 文章目录1.字符串删减2.最长连续不重复子序列3.数组元素的目标和1.字符串删减 给定一个由 n 个小写字母构成的字符串。 现在&#xff…

GCC 同名符号冲突解决办法

一、绪论 作为 C/C 的开发者,大多数都会清楚课本上动态库以及静态库的优缺点,在教科书上谈及到动态库的一个优点是可以节约磁盘和内存的空间,多个可执行程序通过动态库加载的方式共用一段代码段 ;而时至今日,再看看上…

数据库浅谈之常见树结构

数据库浅谈之常见树结构 HELLO,各位博友好,我是阿呆 🙈🙈🙈 这里是数据库浅谈系列,收录在专栏 DATABASE 中 😜😜😜 本系列阿呆将记录一些数据库领域相关的知识 &#…

metasploit穷举模块

目录 工具介绍 常用模块 参数介绍 工具使用 工具介绍 Metasploit框架(Metasploit Framework, MSF)是一个开源工具, 旨在方便渗透测试,它是由Ruby程序语言编写的模板化框架,具有很好的扩展性,便于渗透测试人员开发、使用定制的…

浅析C++指针与引用,栈传递的关系

目录 前言 C 堆指针 栈指针 常量指针 指针常量 引用 常量引用 总结 前言 目前做了很多项目,接触到各种语言,基本上用什么学什么,语言的边际就会很模糊,实际上语言的设计大同小异,只是语言具备各自的特性区别。…

Python学习-----模块3.0(正则表达式-->re模块)

目录 前言: 导入模块 1.re.match() 函数 (1)匹配单个字符 (2)匹配多个字符 (3) 匹配开头和结尾 2.re.search() 函数 3.re.findall() 函数 4.re.finditer() 函数 5.re.split() 函数 6.re.sub() 函数 7.re.sub…

JAVA BIO,NIO,AIO区别(建议收藏)

Java中的IO原理 首先Java中的IO都是依赖操作系统内核进行的,我们程序中的IO读写其实调用的是操作系统内核中的read&write两大系统调用。 操作系统内核是如何进行IO交互的呢? 网卡中的收到经过网线传来的网络数据,并将网络数据写到内存…

Flink01: 基本介绍

一、什么是Flink 1. Flink是一个开源的分布式,高性能,高可用,准确的流处理框架 (1)分布式:表示flink程序可以运行在很多台机器上, (2)高性能:表示Flink处理性…

LabVIEW使用实时跟踪查看器调试多核应用程序

LabVIEW使用实时跟踪查看器调试多核应用程序随着多核CPU的推出,开发人员现在可以在LabVIEW的帮助下充分利用这项新技术的功能。并行编程在为多核CPU开发应用程序时提出了新的挑战,例如同步多个线程对共享内存的并发访问以及处理器关联。LabVIEW可自动处理…

基于SpringBoot+vue的无偿献血后台管理系统

基于SpringBootvue的无偿献血后台管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背…