神经网络识别手写优化(一)

来源:互联网 发布:电信宽带刷新端口 编辑:程序博客网 时间:2024/05/23 16:56

手写识别优化

前言

之前实现的神经网络还有很多可以优化的地方。本文主要正对其进行优化。

初始化

在训练神经网络之前参数初始化。这里的参数就是w,b。
w,b的初始化。
权重w初始化基于正态分布模型(高斯分布):
以0为均值,1为方差然后除以当前神经元的个数的算数平方根。
偏执b初始化基于高斯模型:
以0为均值,1为方差。
公式为:

为什么?
首先观察一下高斯分布的函数图像
此处输入图片的描述

符合高斯分布的数据也就是说,数据的大部分点在中间“山峰”的区域。高斯分布就是说两端的很少,中间的很多。这跟人类社会的2 8法则一项。顶尖的人很少,智障也很少。大家都是中庸的。包括看客和我。

在这里w和b 会对神经元的输出产生影响。w称为权重,我们也可以理解为神经元的重要性(因为w越大输出越大,w越小输出越小)。
因为我们在没有训练模型之前最好是对每个神经元都不“偏心”。那么我们干脆就认为大家都差不多,差不多的那就是中庸的。那就符合高斯分布好啦。有因为权值不能过大,大了收敛很慢。而且难算,那么干脆除一个定值(当前层的节点个数)好了。

import randomimport numpy as npclass Network(object):    def __init__(self, sizes):        """        基本参数初始化        :param sizes:         """        # 网络层数        self.num_layers = len(sizes)        # 网络每层神经元个数        self.sizes = sizes        #调用权重初始化函数        self.default_weight_initializer()    def default_weight_initializer(self):        """        权重和偏置的初始化        :return:         """        # 初始化每层的偏置        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]        # 初始化每层的权重        self.weights = [np.random.randn(y, x)/np.sqrt(x)                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

正则化

正则化的目的就是为了提高模型的泛化能力,一定基础上防止过拟合。

L1正则化

在前面的博文中我们定义了LOSS函数,也就是COST函数。
损失函数
这个应该很熟悉了,就是每个神经元真实标签值和预测值之间的gap。(这里只是讨论待定系数,还是忽略激励函数的存在)。
我们姑且把这个损失函数成为C0
加了正则项之后损失函数变形:
正则化后的损失函数
我们很容易看出损失函数其实就是多了后面一项。
image.png
其中的amda不是学习率是我们手动设置的一个参数。
还是老规矩,我们对这个COST求偏w
image.png
其中sgn(x)是一个 函数。也称做符号函数sign函数。就是改变输入的符号。正号变成负号,负号变成正号。
函数表达式为:
image.png

函数图像为:
image.png

那我们的权重更新也发生了变化。
image.png
式子中的前面两项已经很熟悉了,就是梯度下降。关键是后面加了一个东西。这个作用是什么样子的呢?

还记得一元凸函数的样子
image.png

我们最终目的就是要让cost滚到谷底。

第一种情况:
当w是个负数的时候,并且COST在山谷的左边的时候!w应该往右边滚动。
由梯度下降的知识可以知道前面两项就是可以让其向右边挪动。
因为第二项偏导数会变成一个负数,正数减去一个负数自然会增大!!!
我们看后面那一项:
因为w是一个负数,sgn(w)变成了-1。
那么这里就是减去一个负数。相当于w往下滚的越快了。

这是怎么回事,那么会不会滚来滚去导致无法收敛呢?
别急当w在这个点的时候。
image.png
w也会向右边滚动(同理),但是我们现在sgn(x)就是一个正数了。那么现在往右边挪就不那么顺畅了,这个第三项那个正数会狠狠的拉它一把。让它滚向右边的步伐缩小。

还不明白?看第二种情况!

第二种情况:
当w在山谷的右边的时候,应该往左边滚动。
由梯度下降的知识就知道,因为偏导数是个正数自然会向左边挪动。这里的sgn(x)也是个正数。
那么w会加速往左边滚。

总结一下这个sgn(x)就是为了让w为负数的时候迅速增大,w为正数的时候迅速见效。让w收敛的同时尽可能的等于0。

以前梯度下降很单纯往谷底走就好了

现在 谷底和原点就两个磁铁一样吸引着我们的w
image.png

w会找到一个比较合适的中间值停下来,从而在一定程度上面避免过拟合!!!

以上是一元凸函数而且只有一个参数,我们上升一下维度(盗个图)
image.png
其中彩色的波浪叫做损失等高线(借助地理的概念)。就是说在w在一个圈上面产生的cost是一样的。那么我们的w应该尽量靠近紫色的圈圈。这个时候损失会最小。
但是这里加了一个正方形也就是w的绝对值围城的正放形。同样的它想要w尽可能的靠近它的圆心。于是就出现了上述的情况。w为了满足这两个条件就在黑点的地方停下来了。正则项成功的限制住了w。让其一定程度上避免过拟合的发生。

L2正则

与L1正则是一样的。也是为了避免过拟合。
看公式
image.png
后面不再是绝对值。而是w的平方。前面的2n纯属为了求导方便。
我的理解就是
L2正则就是为了方便计算
其余的跟L1正则没有任何区别!!!
那就看看计算
1、求偏导
image.png
2、设置更新函数
image.png

是不是基本一毛一样!大家可以根据L1正则和梯度下降的法则分析一下过程。其实L2正则也是限制W的。(又偷个图)
image.png
这一次再是个圆。其余一样的!!

代码部分

说了这么其实多代码其实超级简单,在我们之前实现的代码基础上加一行即可,就是是w的更新公式改改!!!

class Network(object):    def __init__(self, sizes):        # 网络层数        self.num_layers = len(sizes)        # 网络每层神经元个数        self.sizes = sizes        self.default_weight_initializer()    def default_weight_initializer(self):        # 初始化每层的偏置        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]        # 初始化每层的权重        self.weights = [np.random.randn(y, x)/np.sqrt(x)                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]    # 随机梯度下降    def SGD(self, training_data, epochs, mini_batch_size, eta,lmbda=0.0,            test_data=None):        if test_data: n_test = len(test_data)        # 训练数据总个数        n = len(training_data)        # 开始训练 循环每一个epochs        for j in xrange(epochs):            # 洗牌 打乱训练数据            random.shuffle(training_data)            # mini_batch            mini_batches = [training_data[k:k + mini_batch_size]                            for k in range(0, n, mini_batch_size)]            # 训练mini_batch            for mini_batch in mini_batches:                self.update_mini_batch(mini_batch, eta,lmbda,n)            if test_data:                print "Epoch {0}: {1} / {2}".format(                    j, self.evaluate(test_data), n_test)            print "Epoch {0} complete".format(j)    # 更新mini_batch    def update_mini_batch(self, mini_batch, eta,lmbda,n):        # 保存每层偏倒        nabla_b = [np.zeros(b.shape) for b in self.biases]        nabla_w = [np.zeros(w.shape) for w in self.weights]        # 训练每一个mini_batch        for x, y in mini_batch:            delta_nable_b, delta_nabla_w = self.update(x, y)            # 保存一次训练网络中每层的偏倒            nabla_b = [nb + dnb for nb, dnb in zip(nabla_b, delta_nable_b)]            nabla_w = [nw + dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]        # 更新权重和偏置 Wn+1 = wn - eta * nw 我们只改了这里        self.weights = [w -eta*(lmbda/n)*w-(eta / len(mini_batch)) * nw                        for w, nw in zip(self.weights, nabla_w)]        self.biases = [b - (eta / len(mini_batch)) * nb                       for b, nb in zip(self.biases, nabla_b)]    # 前向传播    def update(self, x, y):        # 保存每层偏倒        nabla_b = [np.zeros(b.shape) for b in self.biases]        nabla_w = [np.zeros(w.shape) for w in self.weights]        activation = x        # 保存每一层的激励值a=sigmoid(z)        activations = [x]        # 保存每一层的z=wx+b        zs = []        # 前向传播        for b, w in zip(self.biases, self.weights):            # 计算每层的z            z = np.dot(w, activation) + b            # 保存每层的z            zs.append(z)            # 计算每层的a            activation = sigmoid(z)            # 保存每一层的a            activations.append(activation)        # 反向更新了        # 计算最后一层的误差        delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])        # 最后一层权重和偏置的倒数        nabla_b[-1] = delta        nabla_w[-1] = np.dot(delta, activations[-2].transpose())        # 倒数第二层一直到第一层 权重和偏置的倒数        for l in range(2, self.num_layers):            z = zs[-l]            sp = sigmoid_prime(z)            # 当前层的误差            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp            # 当前层偏置和权重的倒数            nabla_b[-l] = delta            nabla_w[-l] = np.dot(delta, activations[-l - 1].transpose())        return (nabla_b, nabla_w)

注意!这代码是不能允许的啊。只是结合之前的做一下优化,看一下修改的地方即可!

原创粉丝点击