机器学习之监督学习(三)神经网络基础
- 0. 文章传送
- 1. 深度学习 Deep Learning
- 深度学习的关键特点
- 深度学习VS传统机器学习
- 2. 生物神经网络 Biological Neural Network
- 3. 神经网络模型基本结构
- 模块一:TensorFlow搭建神经网络
- 4. 反向传播梯度下降 Back Propagation Gradient Descent
- 模块二:激活函数 activation function
- 模块三:优化方法 optimizatoin method
- Adam优化器
- 正则化
- 5. 多分类问题 Multiclass Classification
- softmax激活函数
- from_logits=True
- 6. 数据增强 Data Augmentation
- 实战篇——手写数字识别
0. 文章传送
机器学习之监督学习(一)线性回归、多项式回归、算法优化[巨详细笔记]
机器学习之监督学习(二)二元逻辑回归
机器学习之实战篇——预测二手房房价(线性回归)
机器学习实战篇——肿瘤良性/恶性分类器(二元逻辑回归)
1. 深度学习 Deep Learning
神经网络是深度学习算法的基础。在监督学习板块中,前面我们学习了线性回归模型和逻辑回归模型,这些被称为传统机器学习模型。何为深度学习?和传统机器学习模型相比又有什么优势?
深度学习是机器学习的一个子领域,它主要通过使用深层神经网络(即具有多个隐含层的神经网络)来学习和提取数据中的复杂模式和特征。
深度学习的关键特点
多层结构:深度学习模型通常由多个层级组成,包括输入层、多个隐含层和输出层。每一层都可以提取不同级别的特征。
非线性激活函数:通过使用非线性激活函数(如ReLU、Sigmoid、Tanh等),深度学习模型能够学习复杂的非线性关系。
自动特征学习:深度学习能够自动从原始数据中提取特征,减少了对人工特征工程的需求。
大数据处理:深度学习在大规模数据集上表现优异,能够从中学习出有用的信息和模式。
端到端学习:深度学习模型可以直接从原始输入学习到最终输出,无需中间的特征处理步骤。
迁移学习能力:已经在一个任务上训练的深度学习模型可以被迁移到其他相关任务上,充分利用已有的知识。
深度学习VS传统机器学习
- 复杂数据表示
图像处理:深度学习能够自动从原始像素中提取特征,识别复杂模式,如图像分类和目标检测,而传统机器学习依赖手动特征提取,难以处理高维度和复杂结构。
自然语言处理:深度学习模型(如循环神经网络、Transformer)能够处理序列数据,捕捉上下文信息,适用于机器翻译和文本生成等任务。 - 大规模数据处理
大数据应用:深度学习在处理大规模数据集(如图像、视频和文本)时表现突出,能够利用数据的复杂性和多样性进行学习,而传统方法在数据量庞大时可能面临过拟合等问题。 - 多模态学习
结合多种数据类型:深度学习能够同时处理图像、文本和音频等多种信息,适用于多模态任务,如图像描述生成和视频分析,而传统机器学习通常不具备这种能力。 - 生成模型
内容生成:深度学习能够生成新的数据样本,如GANs生成高度逼真的图像或音频,传统机器学习方法在这方面的能力相对有限。 - 实时处理与反馈
快速决策:深度学习在实时应用(如自动驾驶、实时语音识别)中,能够处理大量数据并迅速做出决策,而传统模型可能无法满足实时性要求。 - 复杂的非线性关系建模
高维非线性映射:深度学习能够有效地捕捉复杂的非线性关系,适用于高度复杂的应用场景,而传统机器学习方法如线性回归、决策树在处理这些问题时会受到限制。 - 迁移学习
知识迁移:深度学习允许模型在一个任务上训练后迁移到另一个相关任务,利用已有知识优化新任务的学习,而传统机器学习往往需要从头开始训练。
2. 生物神经网络 Biological Neural Network
神经网络可以分为生物神经网络和人工神经网络:
(1)生物神经网络,指的是生物脑内的神经元、突触等构成的神经网络,可以使生物体产生意识,并协助生物体思考、行动和管理各机体活动。
(2)人工神经网络,是目前热门的深度学习的研究基础。目前对人工神经网络的定义多种多样,采用TKohonen1988年在NeuralNetworks 创刊号上给出的定义,即:“人工神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它能够模拟生物神经系统对真实世界物体所做出的交互反应”(Kohonen,1988)
先回顾一下高中生物所学的神经元:
回忆生物神经元的结构,细胞体作为神经元的主体,由细胞体向外延伸的多个突起神经纤维称为树突,树突作为细胞体的输入端,轴突为细胞体向外延伸最长的突起,末端由很多细的分支被称为神经末梢,每一条神经末梢相当于细胞体的输出端。每个神经元通过轴突的神经末梢与其他神经元的细胞体或树突相连接,这种连接称为突触。
细胞体的细胞膜在正常状态下形成内负外正的膜电位,当神经元突触与上千个其他神经元连接时,接受不同输入对神经元点位的影响也不用(视为不同输入具有权重),当膜电位升高到一个阈值时,就会产生一个脉冲。突触也可以分为兴奋性和抑制性两种,兴奋性的突触可能引起下一个神经元兴奋,抑制性的突出会抑制下一个神经元的兴奋。
前面所说的逻辑回归模型可视为最简单的神经网络结构(单个神经元模型),输入的各个特征相当于神经元树突接收的各个信号,不同特征乘以不同权重相当于不同输入对神经元具有不同影响,最后将线性组合的结果通过激活函数(逻辑回归中用的是sigmoid函数)作用后输出。
引入激活函数的原因是引入非线性,如果神经网络中没有激活函数(只使用线性激活函数),那无论层次结构多复杂,总体来看还是一个线性回归模型。
单个神经元的数学表达式:
z = ∑ i = 1 d w i x i + b = w ⃗ ⋅ x ⃗ + b a = f ( z ) z=\sum_{i=1}^{d}{w_ix_i}+b=\vec{w}\cdot\vec{x}+b\\a=f(z) z=i=1∑dwixi+b=w⋅x+ba=f(z)
3. 神经网络模型基本结构
了解了单个神经元的基本功能后,我们开始探讨由多个神经元构成的神经网络结构的基本组成。
假设我们想要制作一个模型判断服装产品是否能畅销,考虑的特征有价格、运费、营销力度和制作材料。
对于一件产品,输入层(input layer)输入这四个特征。进一步考虑,价格和运费联系到客户可购买能力,营销力度联系到客户对产品了解程度,材料和价格涉及到客户对产品质量感知,用这三个新特征构造一个隐层(hidden layer),下图中黄色箭头指示了哪些特征输入相应的隐层神经元。隐层的三个神经元接收输入后,进行线性运算,激活后输出,三个输出数值再传入最后一个神经元(即输出层 output layer),输出最终结果,用来预测服装畅销的概率。在这个案例中,我们通过对初级特征的提炼总结构造了一层新特征,提升了模型的复杂性,有利于更充分挖掘原始特征,取得更好的预测效果。
这种引入了隐层的神经网络,也称为多层感知机(multiple perception)。值得注意的是在上图中,由于我们人为地对原始特征进行了一定归纳,每个隐层神经元都只与特点的部分输入特征连接。但事实上神经网络具备自主提炼特征的能力,一般神经网络结构都采用全连接的形式,全连接层(Dense layer)是神经网络中的一种基本层类型,它的特点是每个神经元(节点)与前一层的所有神经元都有连接。可以认为在层层递进过程中,神经元逐渐学习到越来越复杂的特征表示,很难定义每一层神经元具体表示什么特征(比较抽象),举人脸识别的案例来说:
图片转化为像素向量输入(卷积)神经网络,经过三层隐层后输出,可以看到第一层学习到了图像的边缘/线条特征,第二层用边缘特征组合出更复杂的几何图形结构,表示人的眼睛、鼻子等区域,第三层几乎提炼出了整个面部结构,然后模型输出人脸对应某人的概率,实现人脸识别。这个过程体现了深度学习的核心优势:自动特征提取。相比传统的计算机视觉方法需要手动设计特征提取器,深度学习模型能够自动学习到从低级到高级的特征层次结构。
在讲解了几个案例后,相信大家已经领会到了神经网络的魅力,接下来从数学上进行定量描述。
下面定义:
n [ i ] : 第 i 层神经元数量 x ⃗ : 输入特征向量 a ⃗ [ i ] : 第 i 层输出向量 z j [ i ] : 第 i 层第 j 个神经元线性组合值 a j [ i ] : 第 i 层第 j 个神经元输出值 W [ i ] : 第 i 层权重矩阵 w ⃗ j [ i ] : 第 i 层第 j 个神经元权重向量 b ⃗ [ i ] : 第 i 层偏置向量 b j [ i ] : 第 i 层第 j 个神经元偏置值 y ⃗ :输出目标向量 n^{[i]}:第i层神经元数量\\ \vec{x}:输入特征向量\\ \vec{a}^{[i]}:第i层输出向量\\ z_j^{[i]}:第i层第j个神经元线性组合值\\ a_j^{[i]}:第i层第j个神经元输出值\\ W^{[i]}:第i层权重矩阵\\ \vec{w}_{j}^{[i]}:第i层第j个神经元权重向量 \\ \vec{b}^{[i]}:第i层偏置向量\\ b^{[i]}_j:第i层第j个神经元偏置值\\ \vec{y}:输出目标向量\\ n[i]:第i层神经元数量x:输入特征向量a[i]:第i层输出向量zj[i]:第i层第j个神经元线性组合值aj[i]:第i层第j个神经元输出值W[i]:第i层权重矩阵wj[i]:第i层第j个神经元权重向量b[i]:第i层偏置向量bj[i]:第i层第j个神经元偏置值y:输出目标向量
对于第i层第j个神经元,其数学表达式为:
输入向量 : i n p u t ⃗ [ i ] = a ⃗ [ i − 1 ] ( i n p u t ⃗ [ 1 ] = x ⃗ ) 输入向量 :\vec{input}^{[i]}=\vec{a}^{[i-1]}(\vec{input}^{[1]}=\vec{x}) 输入向量:input[i]=a[i−1](input[1]=x)
线性组合: z j [ i ] = w ⃗ j [ i ] ⋅ i n p u t ⃗ [ i ] + b j [ i ] 线性组合:z_j^{[i]}=\vec{w}_{j}^{[i]}\cdot\vec{input}^{[i]}+ b^{[i]}_j 线性组合:zj[i]=wj[i]⋅input[i]+bj[i]
激活(激活函数 g ): a j [ i ] = g ( z j [ i ] ) 激活(激活函数g):a_j^{[i]}=g(z_j^{[i]}) 激活(激活函数g):aj[i]=g(zj[i])
如上图的神经网络,有一个输入层,输入特征数量为4,有两个隐层,第一隐层有5个神经元,第二隐藏有3个神经元;还有一个输出层,包含一个神经元。
Q1 这个神经网络总共包含多少参数(包含权重和偏置)?我们来进行简单计算:
第一层(不考虑输入层)输入向量长度为4,每一个神经元参数包含长度为4的权重向量和一个偏置值,共5*(4+1)=25个参数,由此可总结规律:第i层有 n i n_i ni个神经元,上一层有 n i − 1 n_{i-1} ni−1个神经元(或输出),则该层参数数量计算公式为 n i ∗ ( n i − 1 + 1 ) n_i*(n_{i-1}+1) ni∗(ni−1+1)
由于四层大小分别为4、5、3、1,参数总数为
5 × ( 4 + 1 ) + 3 × ( 5 + 1 ) + 1 × ( 3 + 1 ) = 25 + 18 + 4 = 47 5×(4+1)+3×(5+1)+1×(3+1)=25+18+4=47 5×(4+1)+3×(5+1)+1×(3+1)=25+18+4=47
Q2 从前往后写出每个神经元的数学表达式:
了解了神经网络中符号表示和单个神经元的数学表达式后,我们不难写出这个神经网络中所有神经元的数学表达式,初步入门的读者可以自行练习,熟悉神经网络这个前向传播(forward propagation)的过程。
注意:当输入多个数据时,输入神经网络的为特征矩阵
模块一:TensorFlow搭建神经网络
在前面的线性回归、逻辑回归中,我们都自己编写了实现代码,在神经网络中,由于模型结构比较复杂(特别是后面说的反向传播梯度下降),个人觉得编写代码难度很高。因此个人建议把神经网络模型的基本数学原理了解清楚后,推荐借助python强大的机器学习库例如TensorFlow、pytorch来构建神经网络。
这里主要先介绍TensorFlow。TensorFlow 是一个开源的机器学习框架,由 Google Brain 团队开发。它被广泛用于深度学习、神经网络和其他机器学习任务。
TensorFlow 的核心数据结构是张量,可以看作是多维数组。所有的数据在 TensorFlow 中都是以张量的形式表示的,支持标量、向量、矩阵和更高维的数据结构。TensorFlow 使用计算图来表示计算过程。计算图由节点(操作)和边(数据流)组成,便于进行并行计算和优化。
Keras 是一个高级神经网络 API,旨在简化深度学习模型的构建和训练过程。它提供了简洁且易于使用的接口,使得用户可以快速构建和实验各种神经网络架构。 从 TensorFlow 2.x 开始,Keras 被作为 TensorFlow 的官方高层 API 集成。这意味着用户可以直接通过 TensorFlow 使用 Keras,享受两者的优势。如遇到两个模块不兼容的情况,解决方案参照传送门。在机器学习入门阶段,经常会遇到numpy、pandas、matplotlib、scipy、scikit-learn、TensorFlow等等这些数据分析、机器学习基本模块不兼容的情况,建议这时候重新创建新的虚拟环境,按照上面教程推荐的顺序和版本去安装各个库,使无后患之忧。有时在jupyter notebook中遇到不兼容错误,重启pycharm后可能会解决问题。
下面代码演示用TensorFlow搭建上图的神经网络:
第一步:导入tensorflow和相应模块
import tensorflow as tf
from tensorflow.keras.layers import Normalization,Input,Dense#(Input:输入层,Dense:全连接层,Normalization:标准化层)
from tensorflow.keras.models import Sequential #Sequential:联系各层,构造完整的神经网络
from tensorflow.keras.activations import sigmoid,relu,linear #激活函数:sigmoid、relu、linear
from tensorflow.keras.losses import BinaryCrossentropy,MeanSquaredError #损失函数,二元交叉熵损失函数、平均平方损失函数
第二步:可先用Normalizatoin模块对输入特征矩阵标准化
Norm_l=Normalization()
Norm_l.adapt(X)
Xn=Norm_l(X)
第三步:使用Sequential搭建神经网络模型
model=Sequential([ Input(shape=(4,)), #标明输入每一个特征向量的长度,即特征数Dense(units=5,activation='sigmoid',name='1'), #第一层,5个神经元,使用sigmoid激活函数Dense(units=3,activation='sigmoid',name='L2'), #第二层,3个神经元,使用sigmoid激活函数Dense(units=1,activation='sigmoid',name='L_out') #输出层,1个神经元,使用sigmoid激活函数]
)
第四步:查看模型结构
model.summary()
Model: "model1"
_________________________________________________________________Layer (type) Output Shape Param #
=================================================================L1 (Dense) (None, 5) 25 L2 (Dense) (None, 3) 18 L_out (Dense) (None, 1) 4 =================================================================
Total params: 47
Trainable params: 47
Non-trainable params: 0
第五步:编译模型,指定损失函数
#分类模型:二元交叉熵损失函数
model.compile(loss=BinaryCrossentropy())
#回归模型:平均平方损失函数
model.compile(loss=MeanSquaredError())
第六步:拟合数据,开始训练
model.fit(Xn,y,epochs=100) #epochs:迭代次数
第七步:应用模型进行预测,返回值是numpy对象,在分类问题中返回的是预测概率向量,需要通过设置阈值后转换为预测类别向量
y_pred=model.predict(X_pred)
查看训练后每层的参数:
layer_1=model.get_layer(name='L1')
w_1,b_1=layer_1.get_weights()
print(f'第一层参数w:{w_1},b:{b_1}')
在机器学习实战篇——肿瘤良性/恶性分类器(二元逻辑回归)中,我们使用逻辑回归模型取得了准确率高于95%的预测效果,而读者可以尝试使用神经网络模型训练,准确率可以达到100%,足见神经网络模型的强大。
4. 反向传播梯度下降 Back Propagation Gradient Descent
上面我们已经学习了如何使用tensorflow搭建神经网络,可以看到不需要对内部原理有多少认识,就足以搭建出一个强大的机器学习模型。我们不满足于此,希望了解神经网络模型是如何发挥魔力的。其具体训练机制是什么?学习了线性回归模型和逻辑回归模型后,我们能直接猜出答案:使用梯度下降算法迭代训练各个参数。但问题是,神经网络模型结构相当复杂,定义了代价函数后,计算偏导异常困难。在神经网络模型中,计算偏导的策略是反向传播(Back Propagation),为什么叫"反向"呢,因为梯度的计算是从网络的输出层开始,逐层向输入层传播,与数据的前向传播方向相反。反向传播依赖于微积分中的链式法则。这允许我们将复杂的复合函数的导数分解为更简单的部分。由于这部分涉及比较复杂的数学运算,如果我们的学习目标并非是从0手撕神经网络,那不必过分纠结,可以只了解其主要思想。
关于损失函数,在机器学习系列前两篇文章中已有详细解读,这里不再赘述,回归问题中通常采用平均平方损失函数,二元分类问题中通常采用二元交叉熵损失函数(对数损失函数)。梯度下降每一次迭代过程中都需要计算代价函数对各个参数的偏导,并更新各个参数。
关于反向传播的具体细节,参照笔者的两张图片笔记,可以算是最容易理解的推导版本:
在tensorflow神经网络模型中,默认采用批量梯度下降进行训练,可以在model.fit()中通过batch_size设置单次训练批量,默认值32,关于批量梯度下降之前已经进行过详细介绍,迁移到神经网络中,工作流程如下:
- 数据打乱:在每个训练周期(称为 epoch)开始时,通常会对整个训练数据集进行打乱,以确保每个 mini-batch 都是随机抽取的数据。
- 分批次处理:将训练数据按预定的批次大小(batch size)进行分割,每个批次的数据将被用来进行一次前向传播和反向传播。
- 梯度计算:在每个批次上计算损失函数的梯度。
- 参数更新:使用计算出的梯度更新模型参数。
- 重复:重复上述步骤,直到遍历完整个数据集(完成一个 epoch),然后开始下一个 epoch。
在训练过程中,可以看到每次迭代的相关信息,1875表示训练批次数,8s表示每次迭代所用时间,loss表示迭代后损失值。
模块二:激活函数 activation function
在逻辑回归章节中,我们介绍了Sigmoid激活函数,在这里对常用的几种激活函数进行总结。
线性激活函数 linear activation function
线性激活函数即
g ( z ) = z g(z)=z g(z)=z
可以看出相当于无激活输出,输出范围为 ( − ∞ , + ∞ ) (-\infty,+\infty) (−∞,+∞),回归问题输出连续数值,因此神经网络回归模型输出层采用线性激活函数。
Sigmoid激活函数 sigmoid activation function
S型生长曲线是一类在自然和社会现象中常出现的曲线形状,它由三个阶段组成:起始阶段、加速阶段和饱和阶段。在起始阶段,增长速度较慢;在加速阶段,增长速度逐渐加快;在饱和阶段,增长速度逐渐减缓。
在数学上,描述S型曲线的函数称为sigmoid函数,表达式为:
g ( z ) = 1 1 + e − z g(z)=\frac{1}{1+e^{-z}} g(z)=1+e−z1
(为了和模型表达式一致,将函数用g表示,输入变量用z表示)
1、函数图像,单调递增,奇函数
2、值域:g ∈ \in ∈(0,1),由于输出范围在0到1之间,Sigmoid函数可以将任意实数映射到一个有限的范围内,适合处理概率问题,这使它成为了机器学习模型中常用的激活函数。
3、导数:
①sigmoid函数处处连续可微
②sigmoid函数的导数很容易计算,可以直接由求导处函数值经简单计算得出:
g ′ ( z ) = g ( z ) ( 1 − g ( z ) ) g'(z)=g(z)(1-g(z)) g′(z)=g(z)(1−g(z))
(导数计算并不复杂,读者可以自行动笔进行计算)
ReLU激活函数 ReLU activation function
-
数学表达式:
f ( x ) = max ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x)
-
输出范围: [ 0 , + ∞ ) [0, +\infty) [0,+∞)
-
非线性:ReLU 是一个分段线性的非线性函数。
-
导数:ReLU 的导数要么是 0,要么是 1,这取决于输入是负数还是正数。
-
图像:
-
在神经网络隐层中,通常采用ReLU激活函数,因为相比sigmoid激活函数它具备几点优势:
①导数容易计算,计算开销小,加快了训练的速度
②见下图,ReLU在正区间的梯度始终为1,这有助于缓解梯度消失的问题。在深层网络中,使用ReLU可以使得梯度更有效地传播,从而加快训练速度和提高模型性能
③ReLU激活函数会将负值输出为零,这意味着在任意给定的输入数据点上,只有一部分神经元会被激活(即输出非零值)。这种稀疏性特性可以使模型在计算上更加高效,并减少过拟合的风险。
④虽然ReLU本身是分段线性的,但它引入的非线性特性使得网络能够更好地拟合复杂数据,实验表明,使用ReLU作为隐层激活函数的网络通常能取得更好的性能。
模块三:优化方法 optimizatoin method
Adam优化器
作用:Adam是一种自适应学习率的优化算法,对不同参数设置不同学习率,根据梯度下降的情况调节学习率,加速梯度下降,提高运行效率
在TensorFlow中,在编译模型时设置优化参数为Adam:
from tensorflow.keras.optimizers import Adam
model.compile(optimizer=Adam(learning_rate=1e-3)) #设置初始学习率
正则化
在前面的文章中,我们详细讨论过欠拟合,过拟合和偏差,方差的问题并提出了机器学习模型的优化方案。神经网络模型中,随着模型复杂程度增大,也容易出现过拟合的情况,因此正则化仍然能派上用场,但使用正则化未必一定能提升模型的性能(甚至可能降低模型性能),所以需要结合实际情况灵活运用。一般来说,当模型复杂度较高或数据集规模较小时,容易出现过拟合,这时候推荐使用正则化;而当数据集规模足够大时或模型较简单时,不必使用正则化。关于正则化系数,也需灵活设置。
有这样一个结论:神经网络只要合理正则化,模型越大,训练误差越小。
下面是典型的神经网络训练思路:
①训练,如果训练集误差大(high bias)->增大神经网络规模->满意的训练效果
②验证,如果验证集误差大(high variance)->增大数据量
重复①②直到模型满足low bias and low variance->输出最终模型
常见的正则化方法有:
-
L2正则化(权重衰减):在损失函数中添加权重参数的平方和的乘积,鼓励模型的权重更小。
Loss = Original Loss + λ ∑ i w i 2 \text{Loss} = \text{Original Loss} + \lambda \sum_{i} w_i^2 Loss=Original Loss+λ∑iwi2
-
L1正则化:在损失函数中添加权重参数的绝对值和的乘积,鼓励稀疏模型(即大多数权重为零)。
Loss = Original Loss + λ ∑ i ∣ w i ∣ \text{Loss} = \text{Original Loss} + \lambda \sum_{i} |w_i| Loss=Original Loss+λ∑i∣wi∣
-
Dropout:在训练过程中随机丢弃一部分神经元,以减少神经元之间的共适应性。
在训练时:每次迭代,随机选择一部分神经元(Dropout率)设置为0。
前向传播和反向传播都只在保留的神经元上进行。
在测试时:所有神经元都被保留。但是输出需要乘以保留率(1-Dropout率)来平衡训练和测试时的期望输出。
选择合适的Dropout率需要根据具体问题和网络结构进行实验。通常,较小的网络使用较小的Dropout率(如0.1-0.3),而较大的网络可能使用较大的Dropout率(如0.3-0.5)。
在TensorFlow中,实现正则化很简单,L1、L2正则化在创建层的时候设置:
from tensorflow.keras.regularizers import l2...
Dense(...,kernel_regularizer=l2(0.01)) #设置正则化系数为
...
引入Dropout层示例代码,可以在隐层之后设置Dropout层,并设置Dropout率:
from tensorflow.keras.layers import Dropoutmodel = Sequential([Input(shape=(n,)),Dense(units=25, activation='sigmoid', name='L1'),Dropout(0.2),Dense(units=15, activation='sigmoid', name='L2'),Dropout(0.2),Dense(units=1, activation='sigmoid', name='L_out')
])
推荐:
对于小型数据集或简单模型,L2正则化通常是一个好的起点。
对于大型、复杂的深度学习模型,Dropout往往更有效。
5. 多分类问题 Multiclass Classification
前面我们讨论的分类问题都属于二元分类问题,输出为1或0,表示阳(阴)性或正(负)性。当输出类别大于二时,例如手写数字0~9识别(输出10个类别),这时候称之为多分类问题(multiclass classification)。
在解决多分类问题的神经网络模型中,输出层神经元数量不再是1个,而是输出类别数,每个输出值表示样本对应该类别的概率,例如手写数字0-9识别案例中,若输出概率向量为 [ 0.10 0.10 0.09 0.11 0.30 0.06 0.09 0.01 0.07 0.07 ] [0.10~ 0.10~0.09~0.11~0.30~0.06~0.09~0.01~0.07~0.07] [0.10 0.10 0.09 0.11 0.30 0.06 0.09 0.01 0.07 0.07],则表示该手写图片对应数字4的概率最大,因此模型将其识别为4。
在多分类问题中,需引入新的激活函数softmax激活函数:
softmax激活函数
假设输出层有N个神经元,输入向量为 x ⃗ \vec{x} x,每个神经元的权重向量是 w j ⃗ \vec{w_j} wj ( j = 1 、 2 、 . . . 、 N ) (j=1、2、...、N) (j=1、2、...、N),线性组合输出值为 z j z_j zj,激活输出值为 a j a_j aj,则
z j = w j ⃗ ⋅ x ⃗ + b j ( j = 1 、 2 、 . . . 、 N ) z_j=\vec{w_j}\cdot{\vec{x}}+b_j(j=1、2、...、N) zj=wj⋅x+bj(j=1、2、...、N)
softmax函数可表达为:
a j = e z j ∑ i = 1 N e z j = P ( y = j ∣ x ⃗ ) a_j=\frac{e^{z_j}}{\sum_{i=1}^Ne^{z_j}}=P(y=j|\vec{x}) aj=∑i=1Nezjezj=P(y=j∣x)
N=4时:
可以看到所有激活输出值范围均在0~1之间,且和为1,满足所有类别预测概率之和为1的基本规律。
由于指数运算输出值庞大,为了避免溢出问题,通常将softmax函数进行优化。将指数全减去指数中最大值,这样一个指数值为1,其他指数值都小于1,这样有些避免了指数溢出,增强算法的健壮性。下面是手写参考代码:
def my_softmax_ns(z):"""numerically stablility improved"""bigz = np.max(z)ez = np.exp(z-bigz) # minimize exponentsm = ez/np.sum(ez)return(sm)
tensorflow提供了内置sigmoid函数:
import tensorflow as tf
tf.nn.softmax()#softmax激活函数
多分类问题中目标输出值采用独热(one-hot)编码,即对应类别输出为1,其他类别输出为0。
与soft激活函数配合使用的损失函数是分类交叉熵损失函数(Categorical Cross-Entropy Loss Function),可简单表示为:
l o s s = − l o g ( a j ) i f y = j loss=-log(a_j)~if~y=j loss=−log(aj) if y=j
更一般的表达式是:
l o s s = − ∑ j = 1 N y j l o g ( a j ) loss=-\sum_{j=1}^{N}{y_jlog(a_j)} loss=−j=1∑Nyjlog(aj)
由于输出值 a j a_j aj可能取0,而0log0无意义,解决方案有两种:
1、在代码中定义0log0=0
2、将损失函数修改为
l o s s = − ∑ j = 1 N y j l o g ( m a x ( a j , ϵ ) ) ( ϵ 取极小值 ) loss=-\sum_{j=1}^{N}{y_jlog(max(a_j,\epsilon))}(\epsilon取极小值) loss=−j=1∑Nyjlog(max(aj,ϵ))(ϵ取极小值)
在TensorFlow中,分类交叉熵损失函数用 SparseCategoricalCrossentropy 表示,
‘Sparse’ 的含义:
“Sparse” 在这里指的是标签的编码方式,而不是数据的稀疏性。
它表示真实标签是以整数形式给出的,而不是one-hot编码。
例如,对于三类分类问题:
Sparse标签:[0, 2, 1]
非Sparse(one-hot)标签:[[1,0,0], [0,0,1], [0,1,0]]
如果标签已经是one-hot编码,则应使用CategoricalCrossentropy。
from tensorflow.keras.losses import SparseCategoricalCrossentropy #分类交叉熵损失函数
from_logits=True
神经网络分类模型在使用交叉熵函数时,另一个优化方法是通过设置from_logits参数。Logits 是模型最后一层线性输出的原始值,还没有被转换成概率。
from_logits=True 表示模型的最后一层输出的是原始的、未经过 softmax 处理的值(称为 logits)。
from_logits=False(默认值)表示模型的最后一层已经应用了 softmax 函数,输出的是概率分布。
如下图,在二元分类模型中,将输出层激活函数由’sigmoid’修改为‘linear’,即将Logits直接输出,而编译模型时在损失函数参数中设置from_logits=True,即损失函数直接使用Logits而不是激活后的值进行计算。
loss=BinaryCrossEntropy(from_logits=True)
对于分类交叉熵损失函数,同样可以设置from_logits=True。由于模型输出Logits,因此需要手动通过激活函数后输出预测概率向量。
为什么将激活函数和损失函数合二为一,能优化模型?在计算损失函数时,直接在 logits 上计算(一步到位)通常比先将 logits 转换为概率再计算损失(分步计算)更稳定和精确。这涉及到计算机数值精度的问题,参照下图示例:
上面的一步到位计算相比下面的分步计算,结果精度更高。from_logits优化即是为了消除分布计算积累的误差,提高模型预测的精确度。
6. 数据增强 Data Augmentation
在ai训练过程中,除了构建和完善一个强大的机器学习模型之外,充分大并且全面的数据集也是必不可少。前面说过,在面临过拟合问题时,一个有效的应对思路就是增大训练数据规模,以此降低方差,提高模型的泛化能力。下面讨论机器学习过程中如何针对性地添加数据,并且如何从原有数据集中衍生新数据。
在机器学习训练过程的评估过程中,我们在进行误差分析后,如果发现模型在某一类数据上表现不佳,便可以有针对性的增加某一类数据规模。例如,假设你正在训练一个图像分类模型,在评估过程中发现模型在识别某一类图像(比如狗)的准确率较低。这可能是由于训练数据中这一类图像的数量相对较少,导致模型无法充分学习该类图像的特征。
解决方法是收集更多该类图像的训练数据,并将其添加到训练集中。这可以通过网上搜索、自行拍摄等方式获得。除此外,可以采用数据增强(Data Augmentation)技术,对图像训练数据进行变形比如随机旋转、缩放、裁剪等,进一步扩充该类图像的数量和多样性。
对于音频数据,可以通过给原始音频加上背景音实现数据增强。例如在训练语音识别模型时,为了让模型在各种场景中都能取得较好的识别效果,可以给原始训练音频加上嘈杂背景音(例如引入打电话声音、汽车声音或模拟信号断续的状况)。
下面用代码展示如何实现图像数据增强:
import tensorflow as tf
import matplotlib.pyplot as plt# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()# 数据增强的transforms,对图像进行随机旋转、随机缩放、随机对比度调整
data_augmentation = tf.keras.Sequential([tf.keras.layers.RandomRotation(0.1),tf.keras.layers.RandomZoom(0.1),tf.keras.layers.RandomContrast(0.1),
])# 可视化部分训练数据
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
for i, ax in enumerate(axes.flat):ax.imshow(x_train[i], cmap='gray')ax.axis('off')
plt.suptitle('Original MNIST Training Data')
plt.show()# 应用数据增强
#expand_dims (10,28,28)->(10,28,28,1) #最后一个维度表示通道(灰色)
augmented_images = data_augmentation(tf.expand_dims(x_train[:10], axis=-1), training=True)# 可视化增强后的数据
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
for i, ax in enumerate(axes.flat):ax.imshow(augmented_images[i, ..., 0], cmap='gray') #这里的0表示选取最后一个维度(通道维度)的第0个通道,也就是灰度通道ax.axis('off')
plt.suptitle('Augmented MNIST Training Data')
plt.show()
实战篇——手写数字识别
机器学习之实战篇——手写数字0~9识别