pytorch 实现多层神经网络MLP(Pytorch 05)

一 多层感知机

最简单的深度网络称为多层感知机。多层感知机由 多层神经元 组成,每一层与它的上一层相连,从中接收输入;同时每一层也与它的下一层相连,影响当前层的神经元。

softmax 实现了 如何处理数据,如何将 输出转换为有效的概率分布,并应用适当的 损失函数,根据 模型参数最小化损失

线性意味着单调假设:任何特征的增大都会导致模型输出的增大(如果对应的权重为正),或者导致模 型输出的减小(如果对应的权重为负)。

在网络中加入隐藏层,我们可以通过 在网络中加入一个或多个隐藏层来克服线性模型的限制,使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是 将许多全连接层堆叠在一起。每一层都输出到上面的层,直到生成最后的输出。我们可以把前L−1层看作表示,把最后一层看作线性预测器。这种架构通常称为多层感知机(multilayer perceptron),通常缩写为 MLP。下面,我们以图的方式描述了多层感知机。

下面是一个单隐藏层的多层感知机,具有5个隐藏单元:

这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。输入层不涉及任何计算,因此使用此网络 产生输出 只需要实现隐藏层和输出层的计算。因此,这个多层感知机中的 层数为2。注意,这两个层都是全连 接的。每个输入都会影响隐藏层中的每个神经元,而隐藏层中的每个神经元又会影响输出层中的每个神经元。

线性模型公式:

 不加激活函数,参数可能会组到一起:

加上激活函数 σ 后公式:

为了发挥多层架构的潜力,我们还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性的 激活函数(activation function)σ。激活函数的输出(例如,σ(·))被称为活性值(activations)。一般来说, 有了激活函数,就不可能再将我们的多层感知机退化成线性模型
由于X中的每一行对应于小批量中的一个样本,出于记号习惯的考量,我们定义非线性函数σ也以按行的方 式作用于其输入,即一次计算一个样本。

但是 应用于隐藏层的激活函数通常不仅按行操作,也按元素操作。这意味着在计算每一层的线性部分之 后,我们可以 计算每个活性值,而不需要查看其他隐藏单元所取的值。对于大多数激活函数都是这样。 为了构建更通用的多层感知机,我们可以继续堆叠这样的隐藏层,一层叠一层,从而产生更有表达能力的模型。

而且,虽然一个单隐层网络能学习任何函数,但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。 事实上,通过 使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数

二 激活函数

激活函数(activation function)通过 计算加权和 加上偏置 来确定神经元是否应该被激活,它们将输入信号 转换为输出的可微运算。大多数激活函数都是非线性的

%matplotlib inline
import torch
from d2l import torch as d2l

2.1 ReLU函数

最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU),因为它实现简单,同时在各种预测任务 中表现良好。ReLU提供了一种非常简单的非线性变换。给定元素x,ReLU函数被定义为该元素与0的最大值,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素

        ReLU(x) = max(x, 0)

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

# tensor([-8., -7., -6., -5., -4., -3., -2., -1.,  0.,  1.,  2.,  3.,  4.,  5.,
#          6.,  7.], requires_grad=True)

当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。注意,当输入值精确等于0时, ReLU函数不可导。在此时,我们默认使用左侧的导数,即当输入为0时导数为0。

2.1.1 反向传播后查看X的梯度

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

 使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。这使得优化表现得更好,并 且 ReLU 减轻了困扰以往神经网络的梯度消失问题

2.2 sigmoid 函数

对于一个定义域在R中的输入,sigmoid函数将输入变换为区间(0, 1) 上的输出。

sigmoid函数是一个自然的选择,因为它是一个 平滑的、可微 的阈值单元近似。当我们想要将输出视作二元分类问题的概率时,sigmoid仍然被广泛用作 输出单元上的激活函数(sigmoid可以视为softmax的特例)。sigmoid在隐藏层中已经较少使用,它在大部分时候被更简单、 更容易训练的ReLU 所取代。

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

sigmoid函数的导数图像如下所示。注意,当输入为0时,sigmoid函数的导数达到最大值0.25;而输入在任一 方向上越远离0点时,导数越接近0

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))

2.3 tanh函数

与sigmoid函数类似,tanh(双曲正切)函数 也能将其输入压缩转换到区间(‐1, 1)上

下面我们绘制tanh函数。注意,当输入在0附近时,tanh函数接近线性变换。函数的形状类似于sigmoid函数, 不同的是tanh函数 关于 坐标系原点中心对称

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

tanh函数的导数图像如下所示。当输入接近0时,tanh函数的导数接近最大值1。与我们在sigmoid函数图像 中看到的类似,输入在任一方向上越远离0点,导数越接近0

  • 多层感知机在输出层和输入层之间 增加一个或多个全连接隐藏层,并 通过激活函数转换隐藏层的输出
  • 常用的激活函数包括 ReLU函数sigmoid函数tanh函数

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

3.1 导入数据

Fashion‐MNIST中的每个图像由 28 × 28 = 784个灰度像素值组成。所有图像共分为10个类别

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)
len(train_iter), len(test_iter)

# (235, 40)

3.2 初始化模型参数

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

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]
params

3.3 激活函数

使用 relu 函数:

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

3.4 定义模型

因为我们忽略了空间结构,所以我们使用 reshape将每个二维图像转换为一个长度为num_inputs的向量

def net(x):
    x = x.reshape((-1, num_inputs))
    h = relu(x@W1 + b1)
    return (h@W2 + b2)

3.5 定义损失函数

因此在这里我们直接使用高级API中的内置函数来 计 算softmax和交叉熵损失

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

3.6 执行训练

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)

3.7 执行预测

d2l.predict_ch3(net, test_iter)

 

四 直接调包实现 MLP

与softmax回归的简洁实现 相比,唯一的区别是我们 添加了2个全连接层(之前我们只添加了1个全 连接层)。第一层是隐藏层,它包含128个隐藏单元,并使用了ReLU激活函数。第二层是输出层。  (隐藏层单元个数可以改,保证两个全连接层输出和输入的层数要一致)

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

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

# Sequential(
#   (0): Flatten(start_dim=1, end_dim=-1)
#   (1): Linear(in_features=784, out_features=128, bias=True)
#   (2): ReLU()
#   (3): Linear(in_features=128, out_features=10, bias=True)
# )
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)

 

 4.1 模型拟合相关知识

将模型在 训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting),用于对抗过拟合的技术 称为 正则化(regularization)。

我们需要了解训练误差和泛化误差。训练误差(training error)是指,模型在训 练数据集上计算得到的误差泛化误差(generalization error)是指,模型应用在 同样从原始样本的分布中 抽取的无限多数据样本时,模型误差的期望

为了评估模型,将我们的数据分成三份,除了训练和测试数据集之外,还增加一个 验证数据集(val‐ idation dataset),也叫验证集(validation set)。

训练误差和验证误差都很严重,但它们之间仅有一点差距。如果模型不能降低训练误差,这可能意味着模型过于简单(即 表达能力不足),无法捕获试图学习的模式。此外,由于我们的训练和验证误差之间的泛化误差很小,我们有 理由相信可以 用一个更复杂的模型 降低训练误差。这种现象被称为欠拟合(underfitting)。验证集可以用于模型选择,但不能过于随意地使用它

另一方面,当我们的训练误差明显低于验证误差时要小心,这表明严重的 过拟合(overfitting)。最终,我们 通常更关心验证误差,而不是训练误差和验证误差之间的差距

另一个重要因素是数据集的大小。训练数据集中的样本越少,我们就越有可能过拟合。随着 训练数据量的增加,泛化误差通常会减小

我们应该选择一个复杂度适当的模型,避免使用数量不足的训练样本。

我们总是可以通过去收集更多的 训练数据来缓解过拟合。但这可能成本很高,耗时颇多,或者完全超出我们的控制,因而在短期内不可能做 到。假设我们已经拥有尽可能多的高质量数据,我们便可以将重点放在 正则化技术 上。

4.2 正则化相关

1 权重衰减(weight decay)是最广泛使用的正则化的技术之一,它通常也被 称为 L2正则化

2 暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为 训练神经网络的常用技术。这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(drop out)一些神经元。在整个训练过程的每一次迭代中,标准暂退法包括在计算下一层之前 将当前层中的一些节 点置零

当我们将暂退法应用到隐藏层,以p的概率 将隐藏单元置为零时,结果可以看作一个只包含原始神经元子集的网络。比如在 图4.6.1中,删除了h2和h5, 因此输出的计算不再依赖于h2或h5,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算 不能过度依赖于h1, . . . , h5的任何一个元素

  • 暂退法在前向传播过程中,计算每一内部层的同时丢弃一些神经元
  • 暂退法可以避免过拟合,它通常与控制权重向量的维数和大小结合使用的。
  • •暂退法仅在训练期间使用。

五 前向传播、反向传播和计算图

前向传播(forward propagation或forward pass)指的是:按顺序(从输入层到输出层)计算和存储神经网 络中每层的结果

反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法

该方 法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络

在训练神经网络时,前向传播和反向传播相互依赖。对于前向传播,我们沿着依赖的方向遍历计算图并计算 其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图的相反。一方面,在前向传播期间计算正则项取决于 模型参数W(1)和 W(2)的当前值。它 们是由优化算法根据最近迭代的反向传播给出的。另一方面,反向传播期间参数的梯度计算,取决于 由前向传播给出的隐藏变量h的当前值

因此,在训练神经网络时,在初始化模型参数后,我们交替使用前向传播和反向传播,利用反向传播给出的 梯度来更新模型参数。注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。带来的影响之 一是我们 需要保留中间值,直到反向传播完成。这也是 训练比单纯的预测需要更多的内存(显存)的原因之 一。此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。因此,使用更大的批量来训练更深 层次的网络更容易导致内存不足(out of memory)错误。

  • 前向传播在神经网络定义的计算图中按顺序计算和存储中间变量,它的顺序是从输入层到输出层。
  • 反向传播按相反的顺序(从输出层到输入层)计算和存储神经网络的中间变量和参数的梯度。
  • 在训练深度学习模型时,前向传播和反向传播是相互依赖的。
  • 训练比预测需要更多的内存。

5.1 参数初始化

初始化方案的选择在神经网络学习中起着举足轻重的作用,它对保持数值稳定性至关重要。此外, 这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。我们选择哪个函数以及如何初始化 参数可以决定优化算法收敛的速度有多快糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失

神经网络设计中的另一个问题是 其参数化所固有的对称性

默认初始化,我们使用正态分布来初始化权重值。如果我们不指定初始化方法,框架将 使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。

需要用启发式的初始化方法来确保初始梯度既不太大也不太小,ReLU激活函数缓解了梯度消失问题,这样可以加速收敛。随机初始化是保证在进行优化前打破对称性的关键

Xavier初始化,,Xavier初始化从均值为零,方差  的高斯分布中采样权重。Xavier初始化表明,对于每一层,输出的方差不受输入数量的影响,任何梯度的方差不受输出数量的影 响。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法】小强爱数学(迭代公式+数论取模)

文章目录 1. 问题2. 输入3. 输出4. 示例5. 分析6. 思路7. 数论,取模相关公式8. 数论,同余定理9. 代码 1. 问题 小强发现当已知 x y B xyB xyB以及 x y A xyA xyA时,能很轻易的算出 x n x_ {n} xn​ y n y_ {n} yn​ 的值.但小强想请你在已知A和B的…

Java IO的基本使用和常见类的介绍及其案例讲解

Java IO(Input/Output)是Java编程语言中用于处理输入输出的机制。IO包含了读取和写入数据的功能,可以实现文件的读写、网络通信、和各种设备的输入输出操作。在Java中,IO操作主要由输入流(Input Stream)和输…

mysql基础2多表查询

多表查询 多表关系: 一对多 案例: 部门 与 员工的关系 关系: 一个部门对应多个员工,一个员工对应一个部门 实现: 在多的一方建立外键,指向一的一方的主键 多对多 案例: 学生 与 课程的关系 关系: 一个学生可以选修多门课程,一门课程也可以…

javaWeb奶茶商城前后台系统

一、简介 在当前数字化时代,电子商务已成为人们生活中不可或缺的一部分。为了满足用户对奶茶的需求,我设计并实现了一个基于JavaWeb的奶茶商城前后台系统。该系统涵盖了用户前台和管理员后台两大模块,包括登录注册、商品展示、购物车管理、订…

java面向对象编程基础

对象: java程序中的对象: 本质上是一种特殊的数据结构 对象是由类new出来的,有了类就可以创建对象 对象在计算机的执行原理: student s1new student();每次new student(),就是在堆内存中开辟一块内存区域代表一个学生对象s1变…

蓝桥杯物联网Lora通信功能总结

1、LORA通信在函数LORA被初始化的时候就已经处于接收状态 即开机即能接收数据 2、LORA数据的接收以及发送都通过FIFO数据线 3、LORA的收发同时进行会产生FIFO数据线的通信干扰 4、LORA_Rx在FIFO中有数据的时候才会取出数据,FIFO没有数据会直接跳过 当LORA在发送数…

UDP建立聊天群

参考网上代码 接收端 #include<myhead.h> #define PRINT_ERR(msg) \ do \ { \ printf("%s,…

求解线性方程组

如图题意看出x1有且仅有两种可能&#xff0c;1或者0&#xff0c;且知道了所有a的值&#xff0c;且因为要求所得答案字典序最小&#xff0c;所以先假设x10。 又因a2x1x2所以可以求出x2的值&#xff0c;又如a2x1x2x3,所以可以求出x3的值依次求出所有x的值&#xff0c;但每求出一…

Java 基础知识- 创建线程的几种方式

大家好我是苏麟 , 今天聊聊创建线程的几种方式 . 创建线程的几种方式 1. 继承Thread类实现多线程 /*** className: ThreadTest* author: SL 苏麟**/ public class ThreadTest extends Thread{public static void main(String[] args) {ThreadTest threadTest new ThreadTes…

第十二届蓝桥杯省赛CC++ 研究生组-路径

记录到每个结点的最短距离&#xff0c;以此为基础计算后续结点最优值 #include<iostream> #include<algorithm> using namespace std; typedef long long ll;ll gcd(int a, int b){if(!b) return a;return gcd(b, a % b); }int main(){ll dp[2022] {0};//dp[i]记…

ppp协议

一.实验拓扑 二.实验要求 1.R1和R2使用PPP链路直连&#xff0c;R2和R3把2条PPP链路捆绑为PPP MP直连 2.按照图示配置IP地址 3.R2对R1的PPP进行单向chap验证 4.R2和R3的PPP进行双向chap验证 三.实验思路 1.R2对R1进行ppp单向chap验证&#xff1a; R2配置为主&#xff0c;…

数据库语言一些基本操作

1&#xff0c;消除取值重复的行。 例如&#xff1a;查成绩不及格的学号&#xff1a;SELECT DISTINCT sno FROM SC WHERE grade<60. 这里使用DISTINCT表示取消取值重复的行。 2&#xff0c;比较。 例如&#xff1a;查计算机系全体学生的姓名&#xff1a;SELECT Sname FROM…

模拟实现字符串库函数(一)

在C语言的标准库中提供了很多针对字符串的库函数&#xff0c;这篇文章我们会学习并模拟实现几个简单的库函数 求字符串长度函数strlen strlen函数我们在之前已经用过很多次了&#xff0c;同时也模拟实现过&#xff0c;但是都不是模仿标准库中的strlen来实现&#xff0c;首先我…

三.寄存器(内存访问)

1.内存中字的存储 2.并不是所有cpu都支持将数据段送入段寄存器&#xff0c;所以有时候用个别的寄存器先把数据段存储起来&#xff0c;再把该寄存器mov到段寄存器。 3.字的传送 4.栈 5.栈机制 举例说明 6.栈顶超界问题 push超界 pop超界 7.栈段

pta-洛希极限

科幻电影《流浪地球》中一个重要的情节是地球距离木星太近时&#xff0c;大气开始被木星吸走&#xff0c;而随着不断接近地木“刚体洛希极限”&#xff0c;地球面临被彻底撕碎的危险。但实际上&#xff0c;这个计算是错误的。 洛希极限&#xff08;Roche limit&#xff09;是一…

python写爬虫爬取京东商品信息

工具库 爬虫有两种方案&#xff1a; 第一种方式是使用request模拟请求&#xff0c;并使用bs4解析respond得到数据。第二种是使用selenium和无头浏览器&#xff0c;selenium自动化操作无头浏览器&#xff0c;由无头浏览器实现请求&#xff0c;对得到的数据进行解析。 第一种方…

实战高效RPC方案在嵌入式环境中的应用与揭秘

实战高效RPC方案在嵌入式环境中的应用与揭秘 开篇 在嵌入式系统开发中&#xff0c;大型项目往往采用微服务架构来构建&#xff0c;其核心思想是将一个庞大的单体应用分割成一系列小型、独立、松耦合的服务模块&#xff0c;这些模块可以是以线程或进程形式存在的多个服务单元。…

OpenHarmony开发-线程安全阻塞队列

概述 简介 ​线程安全阻塞队列SafeBlockQueue类&#xff0c;提供阻塞和非阻塞版的入队入队和出队接口&#xff0c;并提供可最追踪任务完成状态的的SafeBlockQueueTracking类。 #include <safe_block_queue.h> 涉及功能 接口说明 OHOS::SafeBlockQueue OHOS::SafeBl…

[Java C++] JNI开发

JNI&#xff08;Java Native Interface&#xff09;是 Java 提供的一种编程桥梁&#xff0c;它允许 Java 代码和本地&#xff08;Native&#xff09;代码进行交互。通过 JNI&#xff0c;Java 程序可以调用本地语言&#xff08;如C、C&#xff09;编写的代码&#xff0c;并且本地…

如何用python编写记录你女友的生日呢?

如何用python编写记录你女友的生日呢&#xff1f; 我这边写一个简单的 Python 程序示例,可以用来记录生日.这个程序将用户输入的姓名和生日信息保存到一个字典中,并允许用户查找特定姓名对应的生日信息. def record_birthday():birthdays {}while True:print("1. 添加生…