机器学习-->深度学习-->人工神经网络

xiaoxiao2021-02-28  74

这篇博文将详细介绍人工神经网络内部原理及其纯底层代码的详细实现。

首先来聊聊什么是神经网络? 神经网络结构大致如下:

它的最底层可以看成从逻辑回归到神经元的[感知机] 上面得出的a是介于0到1之内的值,可以作为预测值的概率值,也可以给定一个阈值,来判定得出的是正样本还是负样本。

然后添加少量隐藏层=>浅层神经网络

增加中间层==>深度神经网络(DNN)

在分类问题上,传统的分类方法例如LR,Linear SVM,softmax等方法在大部分情况下分类效果都不如神经网络的分类效果,这是为什么呢?下面咱们就详细聊聊这个原因

传统分类方法进行分类时都是得出一条或者多条直线,即决策边界(decision bounary)

LR,SVM对于非线性可分,怎么处理?通常在低维难以线性划分,需要引入高维特征,例如x*x,x*x*x等等做高维映射,然后找到一个超平面进行划分,但是对于一些数据样本来说,做高维映射划分,这个维度可能非常大,而且难以做可视化处理,难以知道哪个高维特征有用,不知道样本是怎么样的分布。所以通常在工业界还是用线性分类器做个组合进行分类,不做高维映射,例如GBDT,RandomForest等等,但是即使如此,他的划分效果也不是那么的好。

神经网络从理论上来说可以在平面,空间做任意的划分切分。下面从理论上进行阐述。 首先看看神经元如何完成逻辑与的操作:即可以处理非线性划分 上图中,有两类数据样本,为了很好的做出划分,我们需要找到如图所示的两条直线p1,p2进行划分,显然我们无法由一个线性分类器来进行很好的划分。 我们可以通过一个感知机选取不同的参数,做两次二分类的划分,取其交集即可以很好的进行划分。 上图的-30,20,20为参数,g函数为sigmoid函数,sigmoid函数就可以做个二分类,小于0.5(即在y轴左侧)归为0类,大于0.5(即在y轴右侧时)归为1类,把x1=0,x2=1带入在进行sigmoid运算可知结果为0类,同理其他结果如图中表格,这样就完成了一个逻辑与操作。

选取两组不同的参数,分别找出p1,p2分类直线,然后再选取一组参数对p1,p2进行逻辑与操作,这样就相当与取个交集,很好的完成了分类。同理也可以完成一个逻辑或(or)操作。 神经网络能任意的进行与和或操作,也能进行与和或任意的组合操作。

在无隐层时,只能做一个单一的二分类的划分。在由单隐层时,我们可以做一个and或者or的操作。在多隐层时,我们可以随意的进行and,or操作和其组合操作,这样我们划分的区域图形就是任意的了。所以神经网络从来不怕复杂的问题,复杂的问题不断的分解就变得简单了。

理论上说单隐层神经网络可以逼近任何连续函数。 虽然从数学上看表达能力一致,但是多隐层效果比单隐层效果好很多。 提升隐层层数或者隐层神经元个数,神经网络“容量”会变大,空间表达力会变强。 过多的隐层和神经元节点,会带来过拟合问题。 不要试图通过降低神经网络参数量来减缓过拟合,用正则化或者dropout。过拟合时说明这个模型有能力做出很好的预测或者分类,换句话说这是个有潜力的模型,只需要进行好的引导(正则化,dropOut)就能达到很好的效果。如果降低神经网络参数数量,那么这个模型他的能力瓶颈就会降低,潜力会消失。

下面说说神经网络的正向传播和反向更新的详细过程

在前向运算过程中,有权重w和偏置项b,其中每两层之间都存在权重w,,偏置项b只和神经元有关,从第二层开始,每一个神经元都有一个偏置项b,前向运算时对于每个神经元来说,其输出值与对应权重相乘再加上偏置项b作为下一个连接的神经元的输入,然后在经过激活函数g运算,作为下一个神经元的输出。 下图以三层神经网络为例:

需要讲讲激活函数g,激活函数g其实是用来过滤神经元的输入信息h的,对不重要的信息将其过滤。激活函数的另外一个作用是将信息做非线性变换,试想一下,如果没有这个激活函数g,那么就是对输入信息不断叠加的做线性变换,那么这个变换有用吗???故激活函数g肯定是一个非线性函数。

我们再看看反向更新参数的过程:在正向运算中,我们能得出一个输出值y,我们再把y和真实值t在LossFunction计算出error,然后从最后一层开始往回算,看看每一层应该如何做修正使其error变小,我们通常求出error对每个参数的偏导,再用这个参数值减去其偏导得出更新后的参数。 这里就涉及到BP算法,BP算法的核心就是计算error对每个参数的偏导。 利用SGD方法不断更新参数: GD是一个循环的过程,每次循环都用全部的数据样本,做前向反向运算来更新参数,设置最大循环次数或者设置最小的误差。 当在数据样本很大时,难以一次性的把全部数据样本放进去训练,SGD是一个循环的过程,每次循环都是随机取出一批数据样本,来做前向运算和反向更新,每次循环更新都能得出各个参数新的梯度值dw,即每次循环都要将各个参数梯度重新赋值,将上一次更新得出的w减去这一次的dw得出这批数据训练更新出的参数w。这是一个循环的过程,我们可以设置最大循环次数,不断的进行迭代循环更新参数,也可以设置误差范围,就是用更新的参数计算出输出值与真实值做个比较,如果在误差范围内,则停止循环

因为在正向运算过程中,其实是对输入样本数据一层一层的进行运算,是一个复合函数,所以在BP算法求参数偏导时其实是链式法则求偏导。 反向更新的计算过程:以上图为例,更新参数W5 有两个输出,代入lossFunction中计算出 其他参数更新同理。

这里面有两个问题:我们理想情况是让神经网络学习更快。 1)在我们选择激活函数g时,例如选择sigmoid函数作为激活函数,我们都知道sigmoid函数图像,在图像两端,函数变换梯度会非常低,这就表示如果数据很小或者很大时,我们在反向更新时,基本上学不到什么东西,因为对sigmoid函数求偏导基本上保存不变。 2)在lossFunction选择上,通常选择cross-entropy cost比二次cost函数好 二次cost函数: cross-entropy cost 具体数学证明看这里

下面详细说说神经网络避免过拟合的问题: 神经网络可以逼近任意的连续函数,可以处理任意复杂的分类问题,随着神经网络层数的增加,就免不了过拟合的问题。在训练集上能得出很好的结果,但是在测试集上的结果却并不好。我们有以下的途径来避免神经网络过拟合的问题。 1)通过增加训练集的数据量来减少overfitting。 2)正则化(这里主要讲L2的正则化) 3)dropout 注意:千万不要试图减小神经网络的规模来减少overfitting,因为更深层更大的网络潜在有更强的学习能力。 正则化(Regularization): 最常见的一种正则化:(weight decay) L2 regularization 以cross-entropy为例:

增加了一项:权重之和(对于神经网络里面所有参数w相加) lambda>0 n:训练集包含实例个数

对于二次cost:

以上两种情况可以概括为: 神经网络在训练和预测时,其目标就是使得LossFunction也即是上面的C变小,减少预测值与真实值的误差。那么其正则化后的cost偏向让神经网络学习比较小的权重W,除非第一项Co明显减少。 lambda:调整两项的相对重要程度,较小lambda倾向于让第一项Co最小化,较大的lambda倾向于最小化权重之和。 对以上公式求偏导得: 正则化后的lossFunction,其实就在backpropagation中,也即是在反向更新时,更新权重w时在后面加上一项: 对于偏置项b,偏导不变

那么为什么正则化以后可以避免过拟合呢? 假设有一个数据集: 哪个模型更好? y=2x更简单,任然能很好的描述了数据,巧合的概率很小,所以我们偏向y=2x x^9的模型更可能是对局部数据带有数据噪音的捕捉 在正则化的神经网络中,更倾向于鼓励较小的权重,在小的权重情况下,数据样本的一些随机变化不会对神经网络造成太大的影响,所以更小可能受到数据局部噪音的影响。 未正则化的神经网络中,权重更大,容易通过神经网络模型学到比较大的改变来适应数据,更容易受到局部数据噪音的影响。 正则化的神经网络更容易学到简单一些的模型。

再来看看dropout是如何避免过拟合的: 和正则化不同,不是针对cost函数增加一项,而是对神经网络本身的结构做改变。 假设我们有一个神经网络: 通常我们正向运算得出输出值,再反向更新参数,但是dropout不同,开始时,我们删掉隐藏层随机选取的一半神经元 然后,在更改的神经网络上做正向反向运算。 然后,恢复之前删除的神经元,重新随机选择一半神经元删除,再正向反向更新参数。 重复以上过程。 为什么dropout可以减少overfitting? 假设我们对于同一组训练数据,利用不同的神经网络进行训练,训练完成后,求出输出的平均值,这样可以减少overfitting Dropout同样的道理,每次我们随机删除一半隐藏层的神经元,这就相当于在不同的神经网络模型上做训练预测。 减少神经元的依赖性,也就是每个神经元不能依赖某个或者几个其他神经元,迫使神经网络学习和其他神经元联合起来更加健硕的特征。

下面说说深度神经网络训练的难点:消失的gradient(梯度)问题 上面我在BP算法中说到,反向更新参数,就是从最后一层依次向前面每一层更新参数,求出Error对每一层每个参数的偏导,因为在正向运算时,是一个复合函数,故在依次求偏导时,是一个链式求导法则,后面每层的梯度都是前面层梯度累积的乘积,所以神经网络非常不稳定,越到后面更新参数,梯度变化越来越小。这就使得学习的越来越慢。故在神经网络训练时,层数越深越难训练。 选择合适的激活函数可以适当的避免梯度消失问题,一般对于DNN来说不用sigmoid函数作为激活函数,sigmoid函数在左右两端梯度变化很小,在训练过程中非常容易产生梯度消失。通常可以采用relu函数作为激活函数。 sigmoid函数图像: relu函数图像:

纯手工 撸制版代码:

#coding:utf-8 import numpy as np from sklearn.datasets import make_moons import matplotlib.pyplot as plt # 手动生成一个随机的平面点分布,并画出来 np.random.seed(0) X, y = make_moons(200, noise=0.20) plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral) plt.show() num_examples = len(X) # 样本数 nn_input_dim = 2 # 输入的维度 nn_output_dim = 2 # 输出的类别个数 # 梯度下降参数 epsilon = 0.01 # 学习率 reg_lambda = 0.01 # 正则化参数 # 定义损失函数(才能用梯度下降啊...) def calculate_loss(model): W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2'] # 向前推进,前向运算 z1 = X.dot(W1) + b1 a1 = np.tanh(z1) z2 = a1.dot(W2) + b2 exp_scores = np.exp(z2) probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # 计算损失 corect_logprobs = -np.log(probs[range(num_examples), y]) data_loss = np.sum(corect_logprobs) # 也得加一下正则化项 data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2))) return 1./num_examples * data_loss # 完整的训练建模函数定义 def build_model(nn_hdim, num_passes=20000, print_loss=False): ''' 参数: 1) nn_hdim: 隐层节点个数 2)num_passes: 梯度下降迭代次数 3)print_loss: 设定为True的话,每1000次迭代输出一次loss的当前值 ''' # 随机初始化一下权重呗 np.random.seed(0) W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim) b1 = np.zeros((1, nn_hdim)) W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim) b2 = np.zeros((1, nn_output_dim)) # 这是咱们最后学到的模型 model = {} # 开始梯度下降... for i in xrange(0, num_passes): # 前向运算计算loss z1 = X.dot(W1) + b1 a1 = np.tanh(z1)#中间隐层的激活函数采用双曲正切函数,sigmod函数容易产生梯度消失 z2 = a1.dot(W2) + b2 exp_scores = np.exp(z2) probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)#最后一层激活函数采用的是softmax,这样就使得输出的概率之和为1。如果输出层为sigmoid函数,不能默认输出总和为1,所以不能轻易描述概率分布。 # 反向传播 delta3 = probs delta3[range(num_examples), y] -= 1 dW2 = (a1.T).dot(delta3) db2 = np.sum(delta3, axis=0, keepdims=True) delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2)) dW1 = np.dot(X.T, delta2) db1 = np.sum(delta2, axis=0) # 加上正则化项 dW2 += reg_lambda * W2 dW1 += reg_lambda * W1 # 梯度下降更新参数 W1 += -epsilon * dW1 b1 += -epsilon * db1 W2 += -epsilon * dW2 b2 += -epsilon * db2 # 得到的模型实际上就是这些权重 model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2} # 如果设定print_loss了,那我们汇报一下中间状况 if print_loss and i % 1000 == 0: print "Loss after iteration %i: %f" %(i, calculate_loss(model)) return model # 判定结果的函数 def predict(model, x): W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2'] # 前向运算 z1 = x.dot(W1) + b1 a1 = np.tanh(z1) z2 = a1.dot(W2) + b2 exp_scores = np.exp(z2) # 计算概率输出最大概率对应的类别 probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) return np.argmax(probs, axis=1) # 建立隐层有3个节点(神经元)的神经网络 model = build_model(3, print_loss=True) 咱们先顶一个一个函数来画决策边界 def plot_decision_boundary(pred_func): # 设定最大最小值,附加一点点边缘填充 x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5 y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5 h = 0.01 xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) # 用预测函数预测一下 Z = pred_func(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) # 然后画出图 plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral) plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral) # 然后再把决策/判定边界画出来 plot_decision_boundary(lambda x: predict(model, x)) plt.title("Decision Boundary for hidden layer size 3") plt.show()
转载请注明原文地址: https://www.6miu.com/read-77536.html

最新回复(0)