美文网首页
神经网络识别手写优化(一)

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

作者: skullfang | 来源:发表于2017-11-17 19:36 被阅读0次

    手写识别优化

    前言

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

    初始化

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

    为什么?
    首先观察一下高斯分布的函数图像

    损失函数

    这个应该很熟悉了,就是每个神经元真实标签值和预测值之间的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

    这一次再是个圆。其余一样的!!
    <h1>代码部分</h1>
    说了这么其实多代码其实超级简单,在我们之前实现的代码基础上加一行即可,就是是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)
    

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

    相关文章

      网友评论

          本文标题:神经网络识别手写优化(一)

          本文链接:https://www.haomeiwen.com/subject/amxsvxtx.html