前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(数据科学学习手札34)多层感知机原理详解&Python与R实现

(数据科学学习手札34)多层感知机原理详解&Python与R实现

原创
作者头像
Feffery
发布2018-05-08 18:56:33
2.3K2
发布2018-05-08 18:56:33
举报

一、简介

  机器学习分为很多个领域,其中的连接主义指的就是以神经元(neuron)为基本结构的各式各样的神经网络,规范的定义是:由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界的刺激作出的交互反应。而我们在机器学习中广泛提及的神经网络学习就是机器学习与神经网络的交叉部分,本篇就将介绍基本的神经元模型、感知机模型的知识以及更进一步的多层感知机的具体应用(注意,本篇介绍的内容只是当下流行的深度学习的铺垫,因此只使用了无GPU加速的相应模块,关于深度学习的知识、当下流行的深度学习方法及相应的可GPU加速的训练方法将在后续的博文中陆续介绍)

二、从神经元模型到多层前馈网络

2.1 知识铺垫

  在介绍神经网络学习中的神经元模型之前我们先类比一下生物神经元,神经元是基本的信息处理单元,生物神经元主要由树突、轴突和突触组成,结构简图如下:

通过观察它的结构可以总结出以下特点:

  1、树突由细胞体向外伸出,且有不规则的表面和许多较短的分支,它的角色是信号的输入端,用于接收神经冲动;

  2、轴突指由细胞体向外伸出的最长的一条分支,即神经纤维,相当于信号的输出端,用于传出神经冲动;

  3、神经元之间通过轴突(输出)+树突(输入)的形式进行互连,且对于单个神经元,在输入——>输出这个方向上不可逆;

而关于神经元的功能,也总结出以下几点:

  1、可塑性:神经元存在着这样一种机制:通过新突触的产生和对现有神经突触的调整,使得神经网络能够适应环境(正如当下人们对记忆是存在与神经元间的连接之中的假设);

  2、时空整合:传入某一个中间神经元的刺激,是来自前面所有相关神经元的反复调整过的累积结果;

  3、兴奋与抑制状态:当传入冲动的累计刺激结果使得细胞膜电位上升并成功超过了阈值,则细胞会进入兴奋状态,产生一次传出刺激上的阶跃;若传入的累计刺激结果低于发生阶跃的阈值,则无后续神经活动产生,细胞随之进入抑制状态;

  4、突触的延时和不应期:突触对神经活动的传递具有延时和不应期性,在相邻两次冲动之间存在时间间隔,且在时间间隔内神经元处于休息状态,不会产生自发性的神经冲动;

  5、学习、遗忘和疲劳:突触的传递作用有学习、遗忘和疲劳过程

通过以上说明,我们可以惊喜的发现生物神经元的这些结构特性使得它与我们在机器学习中希望达到的目的完美契合——即通过一系列规则的的刺激传递过程,最终达到正确的决策结果输出,早在上个世纪四五十年代,人们就发现了这种奇妙的联系,并随之构造出基本的神经元模型;

2.2 神经元模型

2.2.1 基本结构

  一个典型的M-P人工神经元模型结构如下:

其中xj表示来自第j条“树突”的输入值,wji表示连接权(每条固定的输入上只有一个唯一的权),ui表示在该神经元i上,所有输入信号的线性组合,系数即为对应的权值,即

θi为该神经元i的阈值,通过ui与θi的简单相加,即可得到中间值vi:

而f()表示激活函数,yi表示该神经元i的输出,即:

2.2.2 激活函数

  理想的激活函数是阶跃函数,如下图,它将连续域上的输入值映射为{0,1},1对应神经元兴奋 ,0对应神经元抑制;

  事实上,类似逻辑回归中的logit函数,我们需要用一些数学性质良好的函数来替代数学性质较差(不处处连续)的阶跃函数,常用的是sigmoid型函数,如下:

2.3 感知机与多层网络

2.3.1 单层感知机

感知机(perceptron)由两层神经元组成,如下图所示,输入层接受外界输入信号后传递给输出层,输出层是M-P神经元,亦称作“阈值逻辑单元”(threshold logic unit);

感知机的学习过程就是对于给定的训练集,在每一轮迭代中同时调整各权重wi(i=1,2,...,n),以及阈值θ,直到满足预设精度为止,为了方便训练,阈值θ可看作第n+1个输入为1的结点的对应权重wn+1,亦称为哑结点(dummy node),这样权重和阈值的学习变得到了统一,例如下图这种表示形式,阈值θ就被视为一个特别的输入:

而单个感知机的学习规则也十分简单,对训练数据集(x,y),若当前感知机的输出为y*,则感知机各权重调整规则如下:

只有在y*=y或训练轮数达到预设的上限或精度第一次达到或超过设定的阈值时,该学习过程才会停止,否则都将进行一轮又一轮的权重调整;

  在整个感知机的结构中,只有输出层神经元包含激活函数的计算过程,输入层只管输入值*权重,即只拥有一层功能神经元(functional neuron),学习能力非常有限,只能处理线性可分问题,否则感知机的学习过程将会发生震荡,w难以稳定下来,即学习失效,例如对于常见的异或问题,感知机就无法习得其规则:

也正是这个原因,对于感知机学习的热度在上世纪60年代跌入谷底,但随着研究的深入,很多用于改造感知机的方法被提出,下面举几个例子:

  1、对神经元施加非线性输入函数

  类似部分非线性回归中我们常使用的线性化方法,对于单纯线性输入的感知机输入层,我们可以根据对具体问题的理解,在个别输入神经元上进行非线性改造,如下例:

  2、使用AND逻辑器构成割平面式的非线性化

  普通感知机的分割平面是线性的,我们思考一下,有哪种机器学习算法本质也是线性分割呢?没错,如果你看过我之前关于决策树的博文,一定还记得,决策树的分割平面式由很多段与各坐标轴平行的拼接而成的,那么我们可以将这种思想类似地迁移到感知机的改造中,用多条线性分割来围成近似的非线性分割:

2.3.2 多层感知机与神经网络

  之前介绍的几种改造线性感知机的方法,实际实施起来存在着很多局限,比如施加输入层非线性函数就存在着很多不确定的部分,要想更加通用地解决非线性可分问题,需要考虑使用多层功能神经元,例如下图所示的这个简单的两层感知机就可以解决异或问题,其中输入层与输出层间的若干层神经元被称为隐层或隐含层(hidden layer),隐层和输出层都是含有激活函数部分的功能神经元:

  而更一般的多层感知机,如下图所示具有规则的层级结构,这时它已经可以称作是神经网络了,每层神经元与下一层神经元全互连,且同层神经元之间不存在连接,也不存在跨层连接,这样的神经网络结构通常被乘称作“多层前馈神经网络”(multi-layer feedforward neural networks),其中输入层神经元依旧单纯地接收外界输入,隐层与输出层对信号进行处理,最终结果依旧是由输出层神经元处理并输出,所以我们平时对一个多层前馈神经网络的层数的称呼都来源于其隐含层的层数,例如下图a就是单隐含层前馈网络,b就是双隐含层前馈网络:

2.3.3 训练方法

  多层感知机的学习能力比单个感知机强得多,但随着其结构的复杂化,对应的训练方法也不同于前面简单感知机的简单规则,最常使用的方法是误差逆传播(error BackPropgation,即常用的BP算法);

算法过程:

对一个给定的训练集D={(x1,y1),(x2,y2)...(xm,ym)},其中xi为d维向量,yi为l维向量,即自变量由d个维度组成,输出值为l个维度,对应的,构造一个由d个输入层神经元、q个隐含层神经元(隐含层神经元个数没有硬性要求)以及l个输出层神经元组成的单隐层前馈神经网络,其中输出层第j个神经元的阈值用θj表示,隐层第h个神经元的阈值用γh表示,输入层第i个神经元与隐层第h个神经元之间的连接权为vih,隐层第h个神经元与输出层第j个神经元之间的连接权为whj,记隐层第h个神经元接收到的输入为

输出层第j个神经元接收到的输入为

结构如下图:

且假设隐层和输出层每个功能神经元都使用Sigmoid型函数:

对于训练集中的任意(xk,yk),假定神经网络的输出为

则该网络在(xk,yk)上的均方误差为:

而整个网络中需要确定的参数共有

需要确定,而BP是一种迭代学习算法,在迭代的每一轮采用广义的感知机学习规则对参数进行更新估计,即其任意参数v的更新估计式为:

  以隐层到输出层的连接权whj为例来进行推导:

  首先我们先确定一个事实,BP算法基于梯度下降(gradient descent)策略,以目标的负梯度方向对参数进行调整,所以对均方误差项

给定学习率η,

注意到whj先影响到第j个输出层神经元的输入值βj,再影响其输出值,最后影响到Ek,

又因为

且Sigmoid型函数有一个很好的性质:

于是综上,有

所有最终得到whj的更新公式:

类似的,其他参数的更新公式:

其中eh为:

学习率η控制着算法每一轮迭代中的更新步长,太大容易震荡(接近理想解时却跨过),太小则收敛速度又会过慢,有时为了做精细调节,可以更加灵活的设置学习率而不必一直固定不变;需要注意的是,标准BP算法在随机初始化各参数(一般是初始化一个较小的非0阵)后,经过一轮一轮地迭代,每一轮都只输入一个样本值来调整各参数,训练目的是逐渐缩小训练集D上的累积误差:

而上面推导的规则是基于每次一个样本输入的调整,即标准BP算法,特点是参数更新的非常频繁,并且前后的不同样本可能会导致训练效果的前后抵消,所以为了达到目标累积误差极小点,需要进行很多次的迭代,但优点是下降和计算都较快,特别是当训练集D非常大时,因此其被使用的最多;

一个重要的论点:

只要一个隐层包含足够多神经元,多层前馈网络就可以以任意精度逼近任意复杂度的连续函数。但是在实际任务多层前馈网络的构造中,选择单隐层 还是双隐层,每一层隐层选取几个神经元,这都尚无可靠的理论支撑,存在着大量试错(trial-by-error)的成分,对神经网络最佳超参数的搜索方法的研究也是一个相当活跃的领域;

  也正是因为其强大的表示能力,多层前馈网络很容易过拟合,即其训练集上误差持续下降,而验证集上误差却可能上升,目前主要有两种缓解多层前馈网络过拟合的方法:

  1、早停(early stopping)

  通过将数据集分成训练集和验证集,训练集用来计算梯度、更新连接权和阈值,验证集用来估计误差,若训练集误差降低但验证集上误差升高,则停止训练,同时返回具有当前最小验证集误差的连接权与阈值(基于贪心算法的原则)

  2、正则化(regularization)

  正则化是指在误差目标函数中增加一个用于描述网络复杂度的部分,常用的是连接权与阈值的平方和,令Ek表示第k个训练样本上的误差,wi表示连接权和阈值,则误差目标函数变为:

其中λ属于(0,1),表示在经验误差和网络复杂度之间进行权衡,具体取值常通过交叉验证来进行搜索确定;

  3、惩罚项

  对目标函数附加惩罚项以强制无用的权值趋于0

局部极小情况:

由于不能保证目标函数在权空间中的正定性,而误差曲面往往复杂且无规则,存在着多个分布无规则的局部极小点,因此基于梯度下降的BP算法很容易陷入局部极小,导致训练效果不好,而常用的改进措施有:

  1、引入全局优化技术

  包括同时训练多个神经网络模型,然后按照在验证集上的表现,选择其中验证误差最小的作为全局最小的近似值;使用诸如随机梯度下降、模拟退火、遗传算法、蚁群算法等启发式的算法来寻找最大可能接近全局最小值的局部最小值;

  2、平坦化优化曲面以消除局部极小

  3、设计合适的网络结构使得其不会产生局部极小

当然,后面两种方案实施起来比较复杂,因此实际任务中常使用的第一种策略。

三、Python实现

  本文暂时不介绍带有GPU加速的神经网络训练方法,因此不适用于过大规模的样本集和过多超参数的神经网络;

  我们利用sklearn.neural_network中集成的方法来进行多层前馈神经网络的训练,下面分别针对分类问题和回归问题进行展开:

3.1 分类问题

  我们使用sklearn.neural_network中的MLPClassifier()来实现BP算法训练的多层感知机,其主要参数如下:

hidden_layer_size:tuple型输入,形如(m,n),其中m用来控制隐层神经元的个数,n用来控制隐层的层数,也可以理解为网络总层数-2(即减去输入层与输出层),默认值为(100,),即单隐层,隐层中含有100个神经元

activation:字符型,用于控制激活函数的类型,可选类型有'identity',表示无非线性激活函数,用于训练线性模型时;'logistic',logit函数;'tanh',双曲函数;'relu',f(x)=max(0,x)

solver:字符型,用来控制BP算法中使用到的求解器,'lbfgs',表示准牛顿法;'sgd',表示标准的随机梯度下降法;'adam',另一种类型的基于随机梯度下降的方法。默认为'adam'

alpha:惩罚项系数,默认为0.0001

batch_size:当solver设置为随机梯度相关的求解器时,此参数控制随机优化器的小批量尺寸

learning_rate:字符型,控制学习率,当设置为'constant'时,训练过程中的学习率为另一参数learning_rate_init预设的常数;当设置为'invscaling',会逐步的降低学习率以减小在最优值附近震荡的风险;当设置为'adaptive'时,且early_stopping被设置为开启时,如果连续两次训练集上的累积误差没有下降或交叉验证得分无法得到提升时,学习率会变为原来的1/5,只有当solver设置为'sgd'时才生效

learning_rate_init:双精度浮点值,控制初始的学习率,配合learning_rate的设置值,共同控制学习率的变化情况,只有当solver设置为'sgd'或'adam'时才生效(即基于梯度)

max_iter:整型,控制训练的最大迭代轮数,默认值是200轮,当然,该方法也是只有solver设置为'sgd'或'adam'时才会生效

shuffle:bool型,控制是否在每一轮迭代中打乱样本的顺序,默认值是True,只有solver设置为'sgd'或'adam'时才会生效

random_state:整型,控制随机数种子,即控制训练开始时随时初始化权重的过程,默认不设置

tol:设定精度的阈值,默认是1e-4,设置的越小,理论上训练精度越高,同时迭代次数也相应越多

verbose:bool型,控制是否打印整个训练过程的细节

warm_start:bool型,控制是否进行预训练,即通过预训练得到一个较好的初始权重,再进行正式训练,默认为False

momentum:float型,设置梯度下降中的动量因子,默认为0.9,只有当solver设置为'sgd'时才会生效

early_stopping:bool型,是否在验证分数不再得到提高且未达到设置的最大迭代轮数之前”早停“训练,默认为False,只有solver设置为'sgd'或'adam'时才会生效

validation_fraction:float型,控制验证集的比例,默认为0.1,且只有在early_stopping设置为True时才会生效

函数返回项:

classes_:数组或列表形式,显示每一个输出对应的类别标签

loss_:float型,输出当前达到的最小累积误差水平

coefs_:列表,返回训练好的网络中的权重矩阵

n_iter_:整型,返回求解器迭代的总次数

n_layers_:整型,返回当前网络层数

n_outputs_:整型,返回当前网络输出个数

out_activation_:字符型,输出当前网络中的激活函数

下面我们以威斯康辛州乳腺癌数据为例,下面是基本功能的演示:

代码语言:javascript
复制
from sklearn.neural_network import MLPClassifier
from sklearn import datasets
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score

'''加载数据集'''
X,y = datasets.load_breast_cancer(return_X_y=True)


'''初始化多层感知机分类器,其中激活函数选择logit函数,求解器选择adam版随机梯度下降法,最小精度设置为1e-5,并设置早停处理,验证集比例为0.3'''
clf = MLPClassifier(activation='logistic',
                    solver='adam',
                    max_iter=1000,
                    tol=1e-5,
                    validation_fraction=0.3)

'''利用训练集训练网络'''
clf = clf.fit(X,y)

'''打印验证集上的混淆矩阵'''
print(confusion_matrix(y,clf.predict(X)))

'''打印验证集上的正确率'''
print(accuracy_score(y,clf.predict(X)))

运行结果:

下面我们来看看这个单隐层(100X1)的网络的各项返回值:

代码语言:javascript
复制
'''显示网络中的训练目标标签'''
print('训练目标标签',clf.classes_)

'''打印输入层与隐层之间所有连接上的权重矩阵的形状,即共30X100个权重'''
print('输入层与隐层之间所有连接上的权重矩阵形状:',clf.coefs_[0].shape)

'''打印隐层与输出层之间的权重矩阵形状,即100X1个权重'''
print('隐层与输出层之间的权重矩阵形状:',clf.coefs_[1].shape)

'''打印隐层上所有神经元内部的阈值向量的形状'''
print('隐层上所有神经元内部的阈值向量的形状:',clf.intercepts_[0].shape)

'''打印输出层神经元(本例中为单个输出)内部的阈值'''
print('输出层神经元(本例中为单个输出)内部的阈值:',clf.intercepts_[1])

'''打印总训练轮数'''
print('总训练轮数',clf.n_iter_)

运行结果:

3.2 回归问题

  讲完了上面的分类问题,下面我们来介绍一下多层感知机在拟合函数曲线上的应用,我们使用sklearn.neural_network.MLPRegressor()来完成回归任务,其主要参数与MLPClassifier相同,这里便不再做介绍,下面我们通过构造一个已知函数解析式的较复杂的非线性函数并生成对应的带有随机误差项修正的数据,进行多层感知机回归任务的演示:

代码语言:javascript
复制
from sklearn.neural_network import MLPRegressor
import matplotlib.pyplot as plt
import numpy as np
import math

'''生成仿真一维数据集'''
X = np.arange(-5,5,0.05).reshape((-1,1))

'''因变量计算函数,添加了随即误差项'''
def My_f(x):
    y = x**2-x**3+x+math.exp(x)+np.random.randn()
    return y

'''根据公式生成因变量仿真值'''
y = np.array([My_f(X[i]) for i in range(len(X))]).reshape((-1,1))

plt.figure(figsize=(14,9))
iter = 1000
for i in range(1,10):
    plt.subplot(330+i)
    plt.scatter(X,y,s=5)
    rg = MLPRegressor(activation='relu',max_iter=iter)
    rg = rg.fit(X,y)
    y_pre = rg.predict(X)
    plt.plot(X,y_pre,c='red')
    plt.title('iterations:'+str(iter))
    iter += 1000

运行结果:

可以看出,随着迭代次数的增多,拟合曲线(红线)越来越逼近与真实情况,为了检验是否存在过拟合现象,我们扩大定义域的范围,并在其上沿用前面的函数解析式创造虚假数据集,并利用9000次迭代后的网络来进行预测:

代码语言:javascript
复制
X = np.arange(-9,9,0.05).reshape((-1,1))

y = np.array([My_f(X[i]) for i in range(len(X))]).reshape((-1,1))

y_pre = rg.predict(X)
plt.scatter(X,y,s=3)
plt.plot(X,y_pre,c='red')

可以看出,随着定义域的扩大化,我们曾经表现优异的网络也发生大幅度的偏差,即我们常说的过拟合现象,所以实际中使用多层感知机进行回归需要在减少过拟合上有更多考虑。

四、R实现

  为了和前面Python保持一致(即不支持GPU加速的神经网络训练方法),我选择R中的nnet包进行演示,nnet中的nnet()可以创建单隐层前馈神经网络,其主要参数如下:

formula:R中常规的因变量~自变量的公式格式

weights:为每一个样本赋权,若无输入则默认每个样本权重都为1

size:因为nnet只能创建单隐层前馈网络,因此size传入的正整数代表了隐层的神经元个数

data:指定样本所在的数据框

Wts:初始化的权重向量,若此项无输入则随机初始化权重向量

rang:控制初始化权重的范围,为[-rang,rang]

maxit:最大迭代轮数,默认为100

abstol:设置精度阈值

代码语言:javascript
复制
library(nnet)

data(iris)
data <- iris

#训练我们的单隐层前馈神经网络,其中隐层神经元个数为2
clf <- nnet(Species~.,
            data=data,
            size=2,
            maxit=200)

pre <- predict(clf,iris[,-5])
pre <- apply(pre,1,which.max)

#打印混淆矩阵
table(pre,data$Species)

#打印正确率
cat('正确率:',sum(prop.table(table(pre,data$Species))))

运行结果:

  以上就是关于多层感知机(多层前馈神经网络)的基本知识,关于当下主流的带GPU加速的神经网络框架,将会在接下来的内容中介绍。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档