【人工智能学习之全连接神经网络篇】
- 1 机器学习基础
- 1.1 机器学习是什么
- 1.2 机器学习
- 2 模型预测
- 2.1 问题类型
- 2.2 机器学习的上限和逼近
- 3 机器学习实现步骤
- 3.1 选择模型
- 3.2 模型训练
- 3.3 输出最优结果
- 4 深度学习是什么
- 4.1 深度学习概念
- 4.2 深度学习与机器学习的区别与联系
- 4.3 常见的深度学习框架
- 5 神经网络(深度学习)的起源的算法
- 5.1 感知机
- 5.4 感知机模型能够解决的问题
- 5.5 单层神经网络
- 5.6 激活函数 Sigmoid
- 5.7 激活函数:ReLu(线性整流函数)
- 5.8 激活函数:tanh(双曲正切)
- 5.9 数据预处理
- 5.10 Softmax:归一化指数函数
- 6 One-Hot编码:一位有效编码
- 6.1 One-hot编码作用
- 7 全连接神经网络
- 7.1 网络结构
- 7.2 前向传播
- 7.3 损失函数
- 7.4 损失函数:均方差损失函数
- 7.5 损失函数:交叉熵损失函数
- 7.6 反向传播
- 7.7 梯度下降算法
- 7.8 全连接神经网络--矩阵相乘
- 7.9 多层神经网络参数
- 8 反向传播:链式法则
- 9 全连接神经网络代码实现与一些理解
- 1、我对梯度下降的理解
- 2、sigmoid和tanh激活函数的区别是什么?sigmoid做为网络中间激活层和网络输出层的区别是什么?
- 3、softmax输出函数和sigmoid输出函数的区别是什么?什么情况下二者可以替换?
- 4、为什么要使用归一化
- 5、全连接神经网络的缺点
- 6、神经网络中激活函数的意义是什么?一个激活函数需要具有哪些必要的要求?
- 7、说说你对softmax和sigmoid的理解?
- 8、什么是反向传播?
- 9、反向传播是如何进行梯度更新的?
- 10、神经网络案例
- 10 案例:手写数字图像识别
1 机器学习基础
1.1 机器学习是什么
机器学习是计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析的学科。
机器学习要做的就是根据已有的训练数据得到模型,并根据模型实现对未知的数据的最优预测。
机器学习涉及统计学、概率论、逼近论、凸分析、算法复杂度理论等多门学科。
1.2 机器学习
机器学习主要包括:
- 监督学习(Supervised Learning): 在监督学习中,模型从带有标签的训练数据中学习,目标是预测新的、未标记数据的标签。典型的任务包括分类和回归。
- 无监督学习(Unsupervised Learning): 无监督学习中,模型在没有标签的数据上学习。常见的任务包括聚类(将相似的数据分组)
- 半监督学习(Semi-Supervised Learning): 半监督学习结合了监督和无监督学习的元素,其中模型使用带有标签和没有标签的数据进行训练。这对于标注数据较为昂贵或稀缺的情况下是有用的。
- 强化学习(Reinforcement Learning): 强化学习涉及智能体(agent)从与环境的交互中学习,通过执行动作来最大化某种奖励信号。这在涉及决策和序列学习的任务中很有用,如游戏和机器人控制。
2 模型预测
2.1 问题类型
根据输出类型的不同,预测问题主要分为:
- 分类问题:输出变量为有限个离散变量,当个数为 2 时即为最简单的二分类问题
- 回归问题:输入变量和输出变量均为连续变量
2.2 机器学习的上限和逼近
数据和标签决定了机器学习的上限。
数据的质量: 机器学习模型的性能很大程度上取决于用于训练和测试的数据的质量。如果数据包含错误或者不代表真实场景,模型就很难学到有用的特征。高质量、多样性的数据集有助于训练出泛化
能力更强的模型。
泛化性能关注模型在未知数据上的表现,它不仅仅在训练数据上表现好,还能够推广到新的、未曾见过的数据上。
标签的准确性:标签是指训练数据中每个样本对应的正确输出或目标值。如果标签不准确,那么模型就会学到错误的关系,从而影响其在新数据上的表现。准确的标签是确保机器学习模型有效性的关键。
算法和模型只能趋近于这个上限。
由于实际情况中很难获取完美的数据,也可能存在标签的不准
确性或噪声,因此模型很难达到性能的上限。算法和模型的作用就是
通过学习数据中的特征和规律,尽可能地逼近这个性能上限。
3 机器学习实现步骤
3.1 选择模型
当前主要是了解你要解决的问题是分类、还是回归问题。不同类型的问题需要不同的模型。
3.2 模型训练
- 数据准备: 将数据集划分,确保模型在未见过的数据上能够泛化良好。
- 特征缩放: 对数据进行特征缩放,确保不同特征的尺度一致,有助于模型的训练。
- 模型初始化:根据选择的模型,初始化模型的参数。有些模型可能需要更复杂的初始化过程。
- 损失函数: 确定模型的损失函数,它衡量模型预测和实际值之间的差异。
- 优化算法: 选择适当的优化算法,通过迭代调整模型参数,选择合适的损失函数。
- 训练迭代: 多次迭代训练模型,直到模型收敛或达到预定的训练轮数。
3.3 输出最优结果
- 模型评估: 使用测试集对训练好的模型进行评估,了解模型在未见过的数据上的性能。
- 性能指标:使用合适的性能指标量化模型的表现。
- 优化模型:根据评估结果,考虑进一步优化模型,可能包括调整模型参数、增加特征等。
- 实际部署:将训练好的模型部署到实际应用中,以便进行实时预测或决策。
4 深度学习是什么
4.1 深度学习概念
深度学习(DL:Deep Learning)是一种机器学习方法,其灵感来源于人类大脑的结构和工作原理。通俗地说,深度学习模仿人脑的神经网络,通过层层堆叠的神经元(类似人脑中的神经元)来学习和理解复杂的模式和特征。
神经网络就像搭积木: 想象一下,你有一堆不同形状和颜色的积木,每个积木都代表一个神经元。你可以把这些积木按照一定的规则和顺序堆叠在一起,形成一个复杂的结构,这就是一个神经网络。每一层积木都负责处理某些特征,最终组成一个完整的模型。
深度学习是端到端的学习:
有时候,我们不需要手动设计特征提取器,深度学习模型可以端到端地从原始数据中学习特征,使得整个学习过程更加自动化。
举个通俗的例子:
假设你要训练一个计算机程序来识别车辆照片。
在传统的方式中,你可能需要进行一系列手动的步骤:
- 手动提取特征: 需要手动提取特征(轮廓以及面积大小),然后编写代码去提取这些特征。
- 设计分类器: 之后,你可能需要设计一个分类器,告诉程序如何利用这些手动提取的特征来判断一张照片是否包含车辆。
- 调整参数: 你可能还需要不断调整这些特征提取的参数,以提高准确性。
而在端到端的深度学习中,情况就不同了:
- 输入和输出: 你只需告诉程序:“这是一堆车的照片,这是对应的标签(有车或没车)。”
- 神经网络学习: 使用深度学习模型,这个模型会自动学习从输入(车的照片)到输出(有车或没车)的映射关系。程序会自发学习车的特征,不需要你手动去告诉它。
- 端到端训练: 整个过程就像一个黑盒子,你只需要关心输入和输出,而深度学习模型会自动调整内部的参数,使得整个系统能够尽可能地正确分类车。
端到端学习的核心思想:不再需要手动设计特征提取器,而是直接通过大量的数据告诉模型应该如何完成任务。
4.2 深度学习与机器学习的区别与联系
首先,深度学习是机器学习的一个分支:
- 模型复杂度:
机器学习: 通常涉及手动选择和设计特征提取器,然后使用相对简单的模型(例如线性模型、KNN等)进行学习。
深度学习: 使用深度神经网络,这是一种由多层神经元组成的复杂模型。深度学习模型可以学习更高层次的抽象特征,而无需手动设计特征提取器。 - 特征表示:
机器学习: 通常需要手动选择和提取特征,这可能涉及较复杂的知识和经验。
深度学习: 模型可以端到端地学习特征表示,不需要手动提取特征。深度学习模型通过多层神经网络逐渐学习数据的抽象特征。 - 任务适用性:
机器学习: 在处理一些传统的机器学习问题上表现良好,如分类、回归和聚类。
深度学习: 在处理大规模、高维度的数据以及复杂任务,如图像识别、自然语言处理和语音识别方面表现出色。 - 数据量需求:
机器学习: 对于相对较小的数据集,机器学习算法可能仍然能够取得良好的结果。
深度学习: 通常需要大量的标记数据进行训练,因为深度学习模型的参数数量较大,需要足够的样本来泛化。 - 计算资源:
机器学习: 相对较轻量,通常可以在较普通的计算机上运行。
深度学习: 对于训练深度神经网络,通常需要更强大的计算资源,例如GPU或TPU,以加速训练过程。(TPU 是谷歌公司设计的专用硬件加速器,用于高效地执行深度学习中的张量计算。TPU 是 Tensor Processing Unit 的缩写)
总体而言,深度学习是机器学习的一个分支,它使用深度神经网络来自动学习特征。在处理大规模和复杂数据时,深度学习通常表现更出色。
深度神经网络的复杂性导致它们的决策过程不太容易解释,这被称为深度学习模型的“黑盒”性质。
- 低层特征比喻为画的基本元素:
想象你在纸上画了一些点、线和颜色。这些就像图像中的像素、边缘和颜色。就像画一样,图像也由基本的元素构成。这些元素是图像的基础,但我们可能无法准确识别它们代表的是什么。
- 中层特征比喻为图案和形状的组合:
你用这些点和线条画出了一个人脸的眼睛、嘴巴等局部轮廓。中层特征是基本元素的组合,通过这些组合,能够识别一些简单的形状和局部结构。
- 高层特征比喻为整体对象或场景:
最后,你用这些形状和结构画出了一个完整的人脸。高层特征是对图像中整体对象的理解,它们让我们能够识别出人脸,提供了更高级别的语义信息(语义信息就是让计算机能够理解图像或文本的“意思”,而不仅仅是看到图像的像素)
4.3 常见的深度学习框架
PyTorch:Facebook出品,语法和 python 相同,操作类似于numpy
TensorFlow:Google出品
MindSpore:华为推出的新一代AI框架
百度: paddlepaddle
5 神经网络(深度学习)的起源的算法
5.1 感知机
对于神经元的研究由来已久,1904年生物学家就已经知晓了神经元的组成结构。
一个神经元通常具有多个树突,主要用来接受传入信息;而轴突只有一条,轴突尾端有许多轴突末梢可以给其他多个神经元传递信息。轴突末梢跟其他神经元的树突产生连接,从而传递信号。这个连接的位置在生物学上叫做“突触”。细胞核包含遗传信息,决定了神经元的特性。
1943年,心理学家McCulloch和数学家Pitts参考了生物神经元的结构,发表了抽象的神经元模型MP(根据两个作者的名字起的)
感知机模型是神经元的建模,是将输入值映射到输出值的一个数学函数。有n个输入值,分别是x1, x2, x3 … xn,对应的是权重是w1, w2, w3 … wn,输出值为y。
例如:
f(x) = w * x + b
x:输入 w:斜率(权重) b:截距(权重) y:输出
def perceptron(x, w):
x = torch.tensor(x, dtype=torch.float32)
w = torch.tensor(w, dtype=torch.float32)
b = 0.5
return torch.dot(w, x) + b
x = [0, 10, 20, 30, 40, 50, 60, 70, 90]
w = [0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1, 0.2, 0.3]
y = perceptron(x, w)
print(y.item())
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如何让这个感知机学习起来?
让感知机学习起来涉及到许多关键概念。以下是与感知机学习相关的一些重要概念:
- 优化: 在机器学习中,优化是指通过调整模型参数,使模型的预测尽可能接近实际标签。
- 学习率: 学习率是一个重要的超参数,用于控制模型参数在每次迭代中的调整幅度。太大的学习率可能导致模型震荡,而太小的学习率可能导致模型收敛缓慢。
- 更新权值: 感知机学习规则定义了如何根据误差信号来更新权重。通过使用梯度下降算法,不断迭代来最小化损失函数。
- 损失: 损失是模型预测与实际标签之间的差异。常用的损失函数包括均方误差(Mean Squared Error)或交叉熵损失函数,具体选择取决于问题的性质。
- 过拟合: 过拟合指的是模型在训练数据上表现很好,但在未见过的数据上表现差。为了避免过拟合,可以采用一些技术提前停止训练、数据增强等。
- 欠拟合: 欠拟合是指模型未能捕捉到数据中的复杂关系,导致在训练和测试数据上都表现不佳。
5.4 感知机模型能够解决的问题
单层感知机只能解决线性问题,不能解决非线性问题。
5.5 单层神经网络
单层神经网络:感知机模型 + 激活函数输出
激活函数:把神经网络模型的线性输出变成非线性输出。如果没有激活函数,神经网络的每一层都相当于矩阵相乘,叠加了若干层之后,其结果还是矩阵相乘(线性工具)
这是因为如果使用线性激活函数,多层神经网络将等同于单层网络。
如下图所示:
5.6 激活函数 Sigmoid
Sigmoid激活函数被称为S型函数,定义域为[-∞, +∞],值域(0,1),因此被用于计算概率。
优点:
- 将很大范围内的输入特征值压缩到0 – 1之间,使得在深层网络中可以保持数据幅度不会出现较大的变化;
- 在物理意义上最为接近生物神经元;
- Sigmoid 函数在输入接近零时,输出接近 0.5。这类似于生物神经元在没有明显刺激时的基础活动水平。
- 随着输入的增加,Sigmoid 函数的输出逐渐趋近于 1,这可以被解释为生物神经元在受到足够刺激时产生的兴奋反应。
- 当输入减小到负无穷时,Sigmoid 函数的输出逐渐趋近于0,这可以类比为生物神经元在受到抑制性信号时逐渐静止的过程。
- 根据输出范围,该函数适用于将预测概率作为输出的模型;
缺点:
- 当输入非常大或非常小的时候,输出基本为常数,即变化非常小,进而导致梯度接近于0(容易造成梯度消失问题);
- 梯度可能会过早消失,进而导致收敛速度较慢;("收敛速度"是指训练神经网络时模型的参数如何逐渐接近最优解或理想值的速度。)
5.7 激活函数:ReLu(线性整流函数)
线性整流函数(Rectified Linear Unit, ReLU),在输入大于0时,直接输出该值;在输入小于等于 0时,输出 0。其作用在于增加神经网络各层之间的非线性关系。
优点:
- 相较于sigmoid函数以及Tanh函数来看,在输入为正时,Relu函数不存在饱和问题。(即解决了梯度消失问题,使得深层网络可训练);
- 即解决了梯度消失问题,使得深层网络可训练:0.99^365
- 计算速度非常快,只需要判断输入是否大于0值;
- 收敛速度远快于sigmoid以及Tanh函数;(备注:"收敛速度"是指训练神经网络时模型的参数如何逐渐接近最优解或理想值的速度)
- 当输入为正值,导数为1,在“链式反应”中,不会出现梯度消失
缺点:
- 存在Dead Relu Problem,即某些神经元可能永远不会被激活(输出永远是零),进而导致相应参数一直得不到更新;
- Relu函数的输出不是以0为均值的函数;
- 可能导致模型收敛速度慢(当所有输入的权重都是正数或负数时,梯度在训练过程中始终指向同一个方向导致收敛速度较慢)
x = np.arange(-10, 10, 0.1)
# 用于逐位比较取最大值
y = np.maximum(0, x)
plt.plot(x, y)
plt.show()
- 1
- 2
- 3
- 4
- 5
5.8 激活函数:tanh(双曲正切)
双曲函数,tanh为双曲正切,由双曲正弦和双曲余弦这两种基本双曲函数推导而来。
优点:
- 解决了的Sigmoid函数输出不是0均值的问题;
- tanh 的输出范围在 [-1, 1] 之间,相对于 sigmoid 来说,输出的动态范围更大;
- 在原点附近,tanh函数与y=x函数形式相近,使得当激活值较低时,训练相对容易;
- tanh 函数的输出范围在 −1到1 之间,且在原点附近的斜率较大,使得它在输入值接近零的地方变化更为敏感。这使得网络在这个区域的权重调整更为明显,有助于梯度的传播。相比之下,sigmoid 函数在原点附近的梯度较小,可能导致梯度消失的问题。零中心化有助于缓解梯度消失问题,使得权重更新更为稳定。
缺点:
- 与Sigmoid函数类似,梯度消失问题仍然存在;
5.9 数据预处理
分析一下以下代码:
a = torch.tensor([[120, 220], [50, 80]])
b = 1 / (1 + np.exp(-a))
print(b)
b = torch.sigmoid(a)
print(b)
b = torch.nn.Sigmoid()(a)
print(b)
# 输出:tensor([[1., 1.], [1., 1.]])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个输出结果能表达激活的效果吗?如果不能,该怎么优化?
数据预处理:归一化
归一化是一种简化计算的方式,把数据幅度归一化到更小的范围。
其主要目的是将不同特征的数据缩放到相同的尺度范围,以确保模型训练的稳定性和收敛速度。
如下图,数据255归一化为1,在图像的运算中会经常用到这种归一化思想。
数据归一化的公式:
那么什么是量纲,为什么需要将有量纲转化为无量纲呢?举一个例子。当我们在做对房价的预测时,收集到的数据中,如房屋的面积、房间的数量、到地铁站的距离等,都是量纲,而他们对应的量纲
单位分别为平方米、个数、米等。这些量纲单位的不同,导致数据之间不具有可比性。同时,对于不同的量纲,数据的数量级大小也是不同的,比如房屋到地铁站的距离可以是上千米,而房屋的面积几十到几百。经过归一化处理后,不仅可以消除量纲的影响,也可将各数据归一化至同一量级,从而解决数据间的可比性问题。
5.10 Softmax:归一化指数函数
softmax函数通常在多分类问题中用于神经网络的输出层。它的主要作用是将神经网络的原始输出转换为类别概率分布。
Softmax函数可以将数据转化为(0,1)之间的数值。
import matplotlib.pyplot as plt
import numpy as np
z = np.arange(-10, 10, 0.01)
y = np.exp(z) / np.sum(np.exp(z))
plt.plot(z, y)
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
例如:
当 z 分别为 2.0 ,1.0 , 0.1 输出分别为 0.7 , 0.2 , 0.1
a = torch.tensor([0.1, 0.2, 0.3]) # 数据归一化
# softmax()函数
b = torch.softmax(a, dim=0) # dim=0:在第0维上
sum=1
print(b)
b = torch.nn.Softmax(dim=0)(a)
print(b)
# 输出:tensor([0.3006, 0.3322, 0.3672])
# 把输出值相加,结果是几?没错,就是1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
6 One-Hot编码:一位有效编码
如果我们想将图片或一些数据进行分类,该怎么操作呢?
分类:
猫 -->图片数据集
狗 -->图片数据集
鸟 -->图片数据集
1 0 0
0 1 0
0 0 1
One-hot编码是一个不错的选择。
6.1 One-hot编码作用
One-hot编码是一种常用的对分类变量进行编码的方法,其主要作用是将离散的、具有类别信息的变量转换为计算机能够处理的向量形式。
以下是一些one-hot编码的作用:
length = 10
index = [2, 4, 6, 8]
# 生成10 * 10对角线为1的矩阵,然后花式索引。
one_hots = np.eye(length)[index]
print(one_hots)
'''
[[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]]
通常分类问题中会将模型学习中分出的类与标准进行比较,就需要索引后与one-hot编码进行比对
'''
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
对 j、a、c、k 进行 one-hot编码
确定样本数:jack共4个字母,也就是4个样本,字母共26个,也就是26个特征。
7 全连接神经网络
从单层神经网络(感知器)开始,到包含一个隐藏层的两层神经网络,再到多层的深度神经网络
深度神经网络,是多层感知机(MLP)结构,每一层的每一个节点都与上下层节点全部连接,这就是全连接的由来。
7.1 网络结构
网络结构包括:输入层、隐藏层、输出层。
我们可以在这里清晰地了解什么是神经网络,以及它是如何工作的,三种层面各有什么影响,神经元数量与神经网络效率,Loss曲线变化等等:
全连接神经网络
7.2 前向传播
正向传播(forward propagation)是指对神经网络沿着从输入层到输出层的顺序。
如图指的是x0、x1、x2、xn与权重(w0、w1、w2、wn)相乘求和,激活输出,这个过程就是前向传播。
7.3 损失函数
前向计算得到结果后,通过损失函数(均方差损失函数、交叉熵损失函数)与标签对比差异,得到损失(loss)。
7.4 损失函数:均方差损失函数
均方差损失函数:前向计算结果和样本标签的对应点的差的平方然后取均值,既可以处理分类问题,也可以处理回归问题,使用频率高。
均方误差在分类任务中使用相对(注意是相对)较少,因为它通常**(注意是通常)**用于回归任务,对于分类任务来说,它对单一样本的误差不够敏感。
a = torch.randn(3, 5)
b = torch.randn(3, 5)
# 实例化MSELoss类
print(torch.nn.MSELoss()(a, b))
# mse_loss()方法
print(torch.nn.functional.mse_loss(a, b))
# numpy 实现
loss = np.mean((np.array([1, 2]) - np.array([3,
4])) ** 2)
print(loss.item())
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
7.5 损失函数:交叉熵损失函数
交叉熵损失函数主要用于计算两个概率分布间的差异性,神经网络所预测的类别分布概率与实际类别分布概率之间的差距越小越好,即交叉熵越小越好。
交叉熵损失函数更适合用于分类问题,和Sigmoid()、Softmax()配合使用。
# 多分类交叉熵,自带Softmax输出函数
torch.nn.CrossEntropyLoss()
# 二分类交叉熵,自带Sigmoid输出函数
torch.nn.BCEWithLogitsLoss()
# 二分类交叉熵,不带输出函数,需要用Sigmoid()激活
torch.nn.BCELoss()
# 多分类交叉熵,不带输出函数,需要用Softmax()激活
torch.nn.NLLLoss()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
7.6 反向传播
前向计算会得到loss,反向传播使用优化器通过学习率更新权重(w、b),更新之后再次进行前向计算,周而复始。
后向传播则是一个优化过程。反向传播就是在神经网络中,根据输出结果与实际结果的误差,从输出层开始,反向调整每一层的权重参数,使得误差逐渐减小,网络预测结果更准确的过程。
就像我们做题时,如果发现最后答案错了,我们会反过来检查每一步的计算过程,找出错误所在并改正,最终得到正确答案。反向传播就是这样一种“反过来”调整权重参数的过程。
7.7 梯度下降算法
数学公式:
ωi+1: 表示下一个值
ωi: 表示当前值
-(负号): 梯度的反方向
α: 学习率或者步长,控制每一步移动的距离,不能太大,太
大移动比较快回会错过最佳点,太小移动较慢时间比较长。
梯度: 函数变化最快的点
7.8 全连接神经网络–矩阵相乘
z1 = g(a1 * w1 + a2 * w2 + a3 * w3)
z2 = g(a1 * w4 + a2 * w5 + a3 * w6)
当前表达式不足之处是 w1, w2,w3 与 w4、w5、w6没有关联存在不足之处。
因此改用二维的下标,用wx,y来表达一个权值。下标中的x代表后一层神经元的序号,而y代表前一层神经元的序号(序号的顺序从上到下)。
例如,w1,2代表后一层的第1个神经元与前一层的第2个神经元的连接的权值(这种标记方式表达较多)。
两个公式就是线性代数方程组。因此可以用矩阵乘法来表达这两个公式。
例如,输入的变量是[a1,a2,a3]T(代表由a1,a2,a3组成的列向量),用向量a来表示。方程的左边是[z1,z2]T,用向量z来表示系数则是矩阵W(2行3列的矩阵,排列形式与公式中的一样)。
于是,输出公式可以改写成:Z = g(W * A);这个公式就是神经网络中从前一层计算后一层的矩阵运算。
Z = g(W * A)
7.9 多层神经网络参数
假设我们将中间层的节点数做一下调整。第一个中间层改为3个单元,第二个中间层改为4个单元。经过调整以后,整个网络的参数变成了33个。
层数保持不变,但是神经网络的参数数量却是第一个神经网络的接近两倍之多,从而带来了更好的表达(represention)能力。
表达能力是多层神经网络的一个重要性质。
更好的表达(represention)能力可以这样理解,随着网络的层数增加,每一层对于前一层次的抽象表示更深入。在神经网络中,每一层神经元学习到的是前一层神经元值的更抽象的表示。例如第一个隐藏层学习到的是“边缘”的特征,第二个隐藏层学习到的是由“边缘”组成的“形状”的特征等。通过抽取更抽象的特征来对事物进行区分,从而获得更好的区分能力。
在深度学习中,网络的不同层可以被看作是在不同抽象级别上提取特征。低层次的特征可以捕获局部细节,而高层次的特征则可以捕获更抽象的信息。这使得网络能够适应不同的特征。
随着层数的增加, 网络的表达能力更强,同时整个网络的参数就越多。
研究人员发现,在参数数量一样的情况下,更深的网络往往具有比浅层的网络更好的表达能力(这点也在很多的大赛中得到了证实)
在单层神经网络时,使用的激活函数是sgn函数。到了两层神经网络时,使用的最多的是sigmoid函数。多层神经网络时,通过一系列的研究发现,ReLU函数在训练多层神经网络时,更容易收敛,并且预测性能更好。因此,目前在深度学习中,最流行的非线性函数是ReLU函数。
8 反向传播:链式法则
9 全连接神经网络代码实现与一些理解
名词小结
FCNN:全连接神经网络
MLP:多层感知机
DNN:深度神经网络
FNN:前馈神经网络
1、我对梯度下降的理解
梯度向量的方向是函数值变化率最大的方向,所以沿着梯度方向下降可以最快找到目标函数的最小值。
2、sigmoid和tanh激活函数的区别是什么?sigmoid做为网络中间激活层和网络输出层的区别是什么?
sigmoid和tanh激活函数的区别有:
- 值域不同,sigmoid值域为(0,1),tanh值域是(-1,1),tanh解决了的Sigmoid函数输出不是0均值的问题。
- 在神经网络中,我们希望网络在训练过程中能够适应数据的分布,不要偏向某个方向。零中心性使得梯度在正负方向上有较大的动态范围,更有利于网络参数的更新,有助于更好地适应数据分布,提高训练效果。
- tanh 的输出范围在 [-1, 1] 之间,相对于 sigmoid 来说,输出的动态范围更大。
- 在原点附近,tanh函数与y=x函数形式相近,使得当激活值较低时,训练相对容易。
- 导数(梯度)不同,sigmoid的导数最大是0.25,tanh的导数最大是1。
- 作为激活而言,提供的非线性能力不同,tanh的非线性能力要强于sigmoid的非线性能力。
- 作为输出函数,代表的意义不同,经过sigmoid输出的是一个概率值+,而经过tanh输出的是一个带有方向性的分布值。
sigmoid做为网络中间激活层和网络输出层的区别是:
- sigmoid作为网络中间层提供模型的非线性能力,提高模型稳定性。而作为输出层的作用是压缩数据,让输出的实数值变成概率值。
3、softmax输出函数和sigmoid输出函数的区别是什么?什么情况下二者可以替换?
- softmax输出函数的作用是进行多分类,它将输入的多个实数值转化成一组慨率值,将输入实数转化为相同占比的概率值,这一组概率值的总和为1。
- sigmoid输出函数的作用是分类,它只输出一个值,表示的是概率。首先sigmoid输入的是一个实数, 然后输出的时候将这个实数转化为一个概率值,如果概率值趋近于0,表示这个类别, 如果概率趋近于1,表示另一个类别。
- 如果在sigmoid输出函数中传入多个实数值,
输出的多个概率结果之间是没有任何关系的。当所要处理的问题是二分类的时候softmax和sigmoid二者之间可以替换。
4、为什么要使用归一化
- 消除量纲差异
- 避免权重不平衡
- 提高模型收敛速度
5、全连接神经网络的缺点
- 网络的连接参数巨大,计算量巨大
- 网络的连接参数巨大,存储空间巨大
- 只能通过单纯地增加网络节点增加网络的功能
- 将图像展开为向量会丢失空间信息
- 其次参数过多效率低下,训练困难,缓慢
- 很容易导致网络过拟合
- 很难自动发现输入数据更抽象的特征,即深层特征,只粗暴的把原始的输入作为数据的特征,根据原始数据进行分类
6、神经网络中激活函数的意义是什么?一个激活函数需要具有哪些必要的要求?
意义是:
- 为保证多层网络不退化成单层线性网络,即神经网络必须是非线性的
- 增强网络的表达能力
- 解决分类问题,激活函数能够将网络的输出映射到概率分布,使得神经网络能够输出类别的概率,从而进行分类
必要的要求:
- 非线性
- 几乎处处可微
- 计算简单
- 非饱和性
- 单调性
- 输出范围有限
- 接近恒等变换
- 参数少
7、说说你对softmax和sigmoid的理解?
sigmoid函数针对两点分布提出。神经网络的输出经过它的转换,可以将数值压缩到(0,1)之间,得到的结果可以理解成“分类成目标类别的概率P”。而不分类到该类别的概率,就是(1 - P),典型的两点分布。
softmax本身针对多项分布提出,当类别数是2时,它退化为二项分布,个人更喜欢softmax(_)
8、什么是反向传播?
后向传播则是一个优化过程。反向传播就是在神经网络中,根据输出结果与实际结果的误差,从输出层开始,反向调整每一层的权重参数,使得误差逐渐减小,网络预测结果更准确的过程。
9、反向传播是如何进行梯度更新的?
反向传播就是根据正向传播时保留的中间结果求上面连乘的项。
设置了requires_grad=True,pytorch就会自动同时生成这个计算过程的计算图。pytorch会把你的计算步骤给细分到一个个它底层定义的运算颗粒,这样一个复杂的运算就被分解成一堆有序的、有向的、极简的运算颗粒。通过这些一个个的偏导连乘就是反向传播回去调整参数,连乘的最终结果就是权重参数沿梯度方向的更新值,最后乘以学习率也就是学习的步长进行最终参数的更新。
10、神经网络案例
输入数据如下:
种子是一个整数,用于初始化随机数生成器。相同的种子将导致生成相同的随机数序列。通过设置种子,你可以确保每次运行代码时都能得到相同的随机数,这有助于实验的可重复性
(42)
X = (100, 1) * 10
#100, 1
y = (X) + 0.1 * (100, 1)
测试数据如下:# 100, 1
X_test = ((0, 10,100).reshape(-1, 1))
import torch
from torch import nn
import numpy as np
import as plt
class NetV1():
def __init__(self):
super().__init__()
# 连接多层
= (
(1, 10),
(), #激活
(10, 6),
(), #激活
(6, 1)
)
def forward(self, x):
return (x)
if __name__ == '__main__':
# 设置参数
epoches = 10000
learn_radio = 0.01
# 模型初始化
net_v1 = NetV1()
# 均方差损失
criterion = ()
# 优化器
opt = (net_v1.(), lr=learn_radio)
# 生成随机数据
torch.manual_seed(42)
X = (100, 1) * 10
y = (X) + 0.1 * (100, 1)
# 转换为PyTorch张量
x_pt = torch.from_numpy(X).float()
y_pt = torch.from_numpy(y).float()
# 训练
for epoch in range(epoches):
outputs = net_v1(x_pt)
loss = criterion(outputs, y_pt)
opt.zero_grad()
()
()
# 测试模型
X_test = ((0, 10, 100).reshape(-1, 1))
y_test = net_v1(X_test)
# 绘制效果图
(figsize=(10, 5))
(X, y, color='blue', label='Original data')
(X_test.numpy(), y_test.detach().numpy(), color='red', linewidth=2, label='Model prediction')
('Model Prediction vs Original Data')
('X')
('y')
()
()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
10 案例:手写数字图像识别
首先需要下载手写数字的图像资料,网上有公开的训练集,运行以下代码即可下载。
数据下载代码:
import cv2
from torchvision import datasets, transforms
def download_test01():
train_set = datasets.MNIST(root='mnist', train=True, download=True)
test_set = datasets.MNIST(root='mnist', train=False, download=True)
imgPil, target = train_set[0]
print(target)
imgPil.show()
t = transforms.Compose([transforms.ToTensor(),transforms.Resize(size=(300, 300),antialias=True),
transforms.RandomVerticalFlip()])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
以下是神经网络代码的实现。
神经网络代码:
import glob
import json
import os.path
import cv2
import torch
import tqdm
import numpy as np
from torch import nn
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
from torchvision import transforms
from torch.utils.data import Dataset
from torch.utils.tensorboard import SummaryWriter
# 设置参数
epoches = 20
learn_radio = 0.001
train_batch_size = 1000
test_batch_size = 10
class MNISTDataset(Dataset):
def __init__(self,root=r"./mnist/MNIST/raw",isTrain=True):
super().__init__()
type = "train" if isTrain else "test"
img_paths = glob.glob(os.path.join(root,type,"*","*"))
self.dataset = []
for path in img_paths:
label = path.rsplit('\\',maxsplit=2)[-2]
self.dataset.append((label,path))
def __len__(self):
return len(self.dataset)
def __getitem__(self, idx):
label, img_path = self.dataset[idx]
img = cv2.imread(img_path)
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_normal = img_gray/255
img_tensor = torch.tensor(img_normal,dtype=torch.float32)
img_vector = img_tensor.flatten()
one_hot = torch.zeros(10)
one_hot[int(label)] = 1
return one_hot,img_vector,img_path
class Net(nn.Module):
def __init__(self):
super().__init__()
# 连接多层
self.layer = nn.Sequential(
nn.Linear(28 * 28, 512),
nn.ReLU(), # 激活
nn.Linear(512, 256),
nn.ReLU(), # 激活
nn.Linear(256, 128),
nn.ReLU(), # 激活
nn.Linear(128, 64),
nn.ReLU(), # 激活
nn.Linear(64, 32),
nn.ReLU(), # 激活
nn.Linear(32, 16),
nn.ReLU(), # 激活
nn.Linear(16, 10),
# (dim=1) # 分布,交叉熵自带
)
def forward(self, x):
return self.layer(x)
class Trainer:
def __init__(self):
# 1. 准备数据
train_dataset = MNISTDataset(isTrain=True)
test_dataset = MNISTDataset(isTrain=False)
self.train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
self.test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
# 初始化网络
net = Net()
net.load_state_dict(torch.load("./")) # 加载之前的学习成果,权重记录
self.net = net
# 损失函数
# self.loss_fn = () #均方差
self.loss_fn = nn.CrossEntropyLoss() #交叉熵
# 优化器
self.opt = torch.optim.Adam(self.net.parameters(), lr=learn_radio)
# 指标可视化
self.writer = SummaryWriter("./logs")
def train(self,epoch):
sum_loss = 0
sum_acc = 0
for target, input, _ in tqdm.tqdm(self.train_loader,total=len(self.train_loader), desc="训练中。。。"):
# 前向传播得到模型的输出值
pred_out = self.net(input)
# 计算损失
loss = self.loss_fn(pred_out, target)
sum_loss += loss.item()
# 梯度清零
self.opt.zero_grad()
# 反向传播求梯度
loss.backward()
# 更新参数
self.opt.step()
# 准确率
pred_cls = torch.argmax(pred_out, dim=1)
target_cls = torch.argmax(target, dim=1)
sum_acc += torch.mean((pred_cls == target_cls).to(torch.float32)).item()
print('\n')
avg_loss = sum_loss / len(self.train_loader)
avg_acc = sum_acc / len(self.train_loader)
print(f"轮次:{epoch+1} 训练平均损失率:{avg_loss}")
print(f"轮次:{epoch+1} 训练平均准确率:{avg_acc}")
self.writer.add_scalars("loss", {"train_avg_loss":avg_loss}, epoch)
self.writer.add_scalars("acc", {"train_avg_acc":avg_acc}, epoch)
def test(self):
sum_loss = 0
sum_acc = 0
paths = []
for target, input, _ in tqdm.tqdm(self.test_loader, total=len(self.test_loader), desc="测试中。。。"):
# 前向传播得到模型的输出值
pred_out = self.net(input)
# 计算损失
loss = self.loss_fn(pred_out, target)
sum_loss += loss.item()
# 准确率
pred_cls = torch.argmax(pred_out, dim=1)
target_cls = torch.argmax(target, dim=1)
sum_acc += torch.mean((pred_cls == target_cls).to(torch.float32)).item()
# 找出测试不准确的图片路径,并显示
for idx in range(len(pred_cls)):
if pred_cls[idx] != target_cls[idx]:
print('\n测试不准确的图片路径:',self.test_loader.dataset[idx][2])
paths.append(self.test_loader.dataset[idx][2])
# img_warn = (self.test_loader.dataset[idx][2])
# ('img_warning',img_warn)
# (50)
# 存储图片路径
with open('./mnist/MNIST/raw/test/test_data.json','w') as file:
if paths is not None:
json.dump(paths,file)
print('\n')
avg_loss = sum_loss / len(self.test_loader)
avg_acc = sum_acc / len(self.test_loader)
print(f"测试平均损失率:{avg_loss}")
print(f"测试平均准确率: {avg_acc}")
torch.save(self.net.state_dict(), './')
def run(self):
for epoch in range(epoches):
self.train(epoch)
self.test()
if __name__ == '__main__':
tra = Trainer()
tra.run()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163