神经网络学习笔记

神经网络学习笔记

前言

本笔记主要是我研读《神经网络与深度学习》一书之后,对重要知识点的整理和公式的推导。这里讲的神经网络是最简单的前馈神经网络,学习算法采用基于误差反向传播的(随机)梯度下降算法。

1 神经网络结构和符号定义

一个三层的神经网络结构(包含输入层)如下:

在这里插入图片描述

注意:输入层节点没有运算功能,直接将输入信号传递给隐藏层,而隐藏层和输出层将输入首先进行线性变换,然后再经过激活函数映射到输出。

神经网络中的符号定义

L L L : 神经网络层数(包含输入层)

x = ( x 1 , x 2 , . . . , x m ) T x=(x_1,x_2,...,x_m)^T x=(x1,x2,...,xm)T : 输入

y ^ = ( y ^ 1 , y ^ 2 , . . . , y ^ n ) T \widehat{y}=(\widehat{y}_1,\widehat{y}_2,...,\widehat{y}_n)^T y =(y 1,y 2,...,y n)T : 输出

a l = ( a 1 l , a 2 l , . . . ) T a^l=(a^l_1,a^l_2,...)^T al=(a1l,a2l,...)T : 第 l l l 层输出,特别地, a 1 = x , a L = y ^ a^1=x, a^L = \widehat{y} a1=x,aL=y

z l = ( z 1 l , z 2 l , . . . ) T z^l=(z^l_1,z^l_2,...)^T zl=(z1l,z2l,...)T : 第 l l l 层带权输入

W l W^l Wl : 第 l l l 层与第 l − 1 l-1 l1 层之间的权重矩阵, w i j l w^l_{ij} wijl : 第 l l l 层第 i i i 个节点与第 l − 1 l-1 l1 层第 j j j 个节点之间的权重.

b l b^l bl : 第 l l l 层偏置向量, b j l b^l_j bjl : 第 l l l 层第 j j j 个节点偏置

2 信号前向传播与误差反向传播公式

假设损失函数采用二次代价函数(均方差),激活函数采用 s i g m o i d sigmoid sigmoid 函数。

二次代价函数:
C x = 1 2 ∥ a L − y ∥ 2 C_x=\frac{1}{2}\left\|a^L-y\right\|^2 Cx=21aLy2
s i g m o i d sigmoid sigmoid 函数定义:
σ ( x ) = 1 1 + e − x \sigma{(x)}=\frac{1}{1+e^{-x}} σ(x)=1+ex1
s i g m o i d sigmoid sigmoid 函数导数有:
σ ′ ( x ) = σ ( x ) ( 1 − σ ( x ) ) \sigma^{'}{(x)}=\sigma{(x)}(1-\sigma{(x))} σ(x)=σ(x)(1σ(x))
信号前向传播公式:
{ a 1 = x z l = W l a l − 1 + b l , l ≥ 2 a l = σ ( z l ) , l ≥ 2 y ^ = a L \begin{cases} a^1=x \\ z^l=W^la^{l-1}+b^l , l\ge{2}\\ a^l=\sigma{(z^l)} , l\ge{2}\\ \widehat{y}=a^L \end{cases} a1=xzl=Wlal1+bl,l2al=σ(zl),l2y =aL
误差的反向传播公式:
{ δ L = ∂ C ∂ a L ⊙ σ ′ ( z L ) = ( a L − y ) ⊙ σ ′ ( z L ) δ l = [ ( W l + 1 ) T δ l + 1 ] ⊙ σ ′ ( z l ) , l &lt; L ∂ C ∂ b l = δ l ∂ C ∂ W l = δ l ( a l − 1 ) T \begin{cases} \delta^L=\frac{\partial C}{\partial a^L}\odot \sigma^{&#x27;}(z^L) =(a^L-y) \odot \sigma^{&#x27;}(z^L) \\ \delta^l=[(W^{l+1})^T\delta^{l+1}] \odot \sigma{&#x27;}(z^l), l&lt;L \\ \frac{\partial C}{\partial b^l}=\delta^l\\ \frac{\partial C}{\partial W^l}=\delta ^l(a^{l-1})^T \end{cases} δL=aLCσ(zL)=(aLy)σ(zL)δl=[(Wl+1)Tδl+1]σ(zl),l<LblC=δlWlC=δl(al1)T
误差反向传播公式中引入中间变量 δ l \delta^l δl , 定义为 ∂ C ∂ z l \frac{\partial C}{\partial z^l} zlC .

公式推导的基本思想是“链式求导法则”,证明时直接进行矩阵求导不易,可先证明分量形式,最后在写成矩阵或向量形式。

3 梯度下降算法

上面的误差反向传播公式是为梯度下降算法而服务的,梯度下降算法是神经网络最常用的学习算法。具体来讲又分为:

  • 批量梯度下降算法
  • 小批量梯度下降算法
  • 随机梯度下降算法
  • 小批量随机梯度下降算法等

参数更新公式:
{ W l ← W l − η ∂ C ∂ W l b l ← b l − η ∂ C ∂ b l \begin{cases} W^l \leftarrow W^l - \eta \frac{\partial C}{\partial W^l} \\ b^l \leftarrow b^l - \eta \frac{\partial C}{\partial b^l} \end{cases} {WlWlηWlCblblηblC
其中, η \eta η 为学习速率.
{ W l ← W l − η ∂ C ∂ W l b l ← b l − η ∂ C ∂ b l \begin{cases} W^l \leftarrow W^l -\eta\frac{\partial C}{\partial W^l} \\ b^l \leftarrow b^l - \eta\frac{\partial C}{\partial b^l} \end{cases} {WlWlηWlCblblηblC

4 采用小批量随机梯度下降算法的神经网络训练流程

神经网络学习过程的流程图:

在这里插入图片描述

5 经典神经网络存在的问题和改进

5.1 神经元饱和问题

经典的神经网络采用的激活函数是 s i g m o i d sigmoid sigmoid 函数,代价采用二次代价函数,两者配合使用共同导致在输出误差较大时学习的速度反而很慢,随着误差的逐渐减小,学习速度出现先增大后又减小的现象。(如下图) 为什么会出现这种反常识的现象呢?按照人类的学习经验,不应该是误差越大学习速度越大吗?

在这里插入图片描述

要解释这个问题我们首先看看 s i g m o i d sigmoid sigmoid 函数的输入输出曲线:

在这里插入图片描述

s i g m o i d sigmoid sigmoid 函数将输入 ( − ∞ , + ∞ ) (-\infty, +\infty) (,+)的数值挤压到 ( 0 , 1 ) (0, 1) (0,1) 之间。当输入的绝对值很大时, s i g m o i d sigmoid sigmoid 函数的导数趋近于0,再来看看上面的误差反向传播公式, δ L \delta^L δL 的公式中恰好含有 σ ′ ( z L ) \sigma^{&#x27;} (z^L) σ(zL), 这就是原因所在。

改进措施之一:采用交叉熵代价函数,效果是将 δ L \delta^L δL 的公式中的 σ ′ ( z L ) \sigma^{&#x27;} (z^L) σ(zL) 项约掉。其对应的误差反向传播公式为:
{ δ L = ∂ C ∂ a L ⊙ σ ′ ( z L ) = a L − y δ l = [ ( W l + 1 ) T δ l + 1 ] ⊙ σ ′ ( z l ) , l &lt; L ∂ C ∂ b l = δ l ∂ C ∂ W l = δ l ( a l − 1 ) T \begin{cases} \delta^L=\frac{\partial C}{\partial a^L}\odot \sigma^{&#x27;}(z^L) = a^L-y \\ \delta^l=[(W^{l+1})^T\delta^{l+1}] \odot \sigma{&#x27;}(z^l), l&lt;L \\ \frac{\partial C}{\partial b^l}=\delta^l\\ \frac{\partial C}{\partial W^l}=\delta ^l(a^{l-1})^T \end{cases} δL=aLCσ(zL)=aLyδl=[(Wl+1)Tδl+1]σ(zl),l<LblC=δlWlC=δl(al1)T
可见将代价函数变为交叉熵之后,对比两组公式,只有 δ L \delta^L δL 发生了改变。

推导 δ L \delta ^L δL 的过程(先证明分量形式):

在这里插入图片描述

改进措施之二:输出层采用** s o f t m a x softmax softmax激活函数对数代价函数**。

softmax 定义如下:
s o f t m a x ( x j ) = e x j ∑ k e x k softmax(x_j)=\frac{e^{x_j}}{\sum_{k}{e^{x_k}}} softmax(xj)=kexkexj
特点:对每层神经元的输出值进行归一化(之和为1),因此,最终的输出值可以看作是“概率”。

与sigmoid函数类似,其导数也有类似性质:
∂ s o f t m a x ( x i ) ∂ x j = { s o f t m a x ( x i ) ( 1 − s o f t m a x ( x i ) ) , i = j − s o f t m a x ( x i ) s o f t m a x ( x j ) , i ≠ j \frac{\partial softmax(x_i)}{\partial x_j} = \begin{cases} softmax(x_i)(1-softmax(x_i)), i=j \\ -softmax(x_i)softmax(x_j), i\ne j \end{cases} xjsoftmax(xi)={softmax(xi)(1softmax(xi)),i=jsoftmax(xi)softmax(xj),i̸=j
对数代价函数的定义:
C x = − l n ( a y L ) C_x = -ln (a^L_y) Cx=ln(ayL)
巧妙的是输出层采用softmax激活函数和对数代价函数与sigmoid激活函数和交叉熵代价函数的反向传播公式是一样的。

下面推导采用softmax激活函数和对数代价函数的 δ L \delta^L δL 的计算式:
δ L = a L − y \delta^{L}=a^L-y δL=aLy
推导过程:

在这里插入图片描述

有了这样的相似性,你应该使一个具有交叉熵代价的 sigmoid 型输出层,还是一个具有对数似然
代价的柔性最大值输出层呢?柔性最大值加上对数似然的组合更加适合于那些需要将输出激活值解释为概率的场景。

5.2 过度拟合问题

过度拟合(overfit)是指神经网络在训练过程中过分追求较高的分类准确度,学习到“噪声”等非本质特征的信号,而丧失泛化能力,在训练样本之外表现的很差。一般出现在训练样本很少的情况下。

解决过度拟合有以下几种策略:

  • 规范化
  • 弃权
  • 认为增加训练样本等

规范化中的L2规范化是最常用的手段。基本思想是在原来的代价函数的基础上引入网络所有权重的平方和项。即,
C = C 0 + λ 2 n ∑ w w 2 C=C_0+\frac{\lambda}{2n}\sum_{w}{w^2} C=C0+2nλww2
其中, C 0 C_0 C0是原来的代价函数, λ &gt; 0 \lambda&gt;0 λ>0 是规范化参数。
则,权重的更新公式变成
w ← w − η ( ∂ C 0 ∂ w + λ n w ) = ( 1 − η λ n ) w − η ∂ C 0 ∂ w w \leftarrow w - \eta( \frac{\partial C_0}{\partial w}+\frac{\lambda}{n}w) \\ =(1-\frac{\eta \lambda}{n})w-\eta \frac{\partial C_0}{\partial w} wwη(wC0+nλw)=(1nηλ)wηwC0

这种调整有时被称为权重衰减,因为它使得权重变小。

6 补充:熵与交叉熵的理解

熵、交叉熵属于信息论中的概念。首先明确几个概念:

信息量:与事件空间中的某一事件相对应。刻画某一事件发生的不确定行。定义为 I ( x ) = − l o g ( p ( x ) ) I(x)=-log(p(x)) I(x)=log(p(x)) , 事件发生的概率越小,信息量越大。

熵:与某一随机变量相对应。刻画某一随机变量的不确定性。定义为 H X = E [ I ] = − ∑ k p ( x k ) l o g ( p ( x k ) ) H_X=E[I]=-\sum_{k}{p(x_k)log(p(x_k))} HX=E[I]=kp(xk)log(p(xk))

当某一随机变量服从均匀分布时,该随机变量的熵最大。

交叉熵:刻画两个随机变量分布的相似性。定义为 C E H ( p , q ) = − ∑ k p ( x k ) l o g ( q ( x k ) ) CEH(p,q)=-\sum_k{p(x_k)log(q(x_k))} CEH(p,q)=kp(xk)log(q(xk)) , 其中,p, q分别是两个分布函数。p是真实样本分布,q是待估计样本分布。交叉熵越小,反映两个分布越接近。

7 基于python numpy 实现神经网络

# -*- coding:utf-8 -*-

"""全连接前馈神经网络训练和测试实现。学习算法采用小批量随机梯度下降算法。
输出层采用softmax函数,代价函数采用对数似然函数,有正则化"""

import numpy as np
import random
import mnist_loader
import matplotlib.pyplot as plt


class Network(object):

    def __init__(self, struct, w=None, b=None, batsize=5, i_max=30, c_min=1e-3, rate=1.0, lam=10):
        # 初始化神经网络
        self.struct = struct
        self.batsize = batsize
        self.nlayer = len(struct) # 神经网络层数,不包含输入层
        self.rate = rate
        self.lam = lam
        self.c = []
        if w is None:
            w, b = self.initwb()
        self.w = w
        self.b = b
        self.i_max = i_max
        self.c_min = c_min
        self.nin = struct[0]
        self.nout = struct[-1]

    def initwb(self):
        # 初始化权重和偏置
        w = [None] + [np.random.randn(a,b)/np.sqrt(b) for a,b in zip(self.struct[1:], self.struct[:-1])]
        b = [None] + [np.random.randn(a, 1) for a in self.struct[1:]]
        # for i in range(0, self.nlayer-1):
        #     w.append(np.ones((self.struct[i+1], self.struct[i]))/self.struct[i])
        #     b.append(np.zeros((self.struct[i+1], 1)))
        return w, b

    def batpro(self, num):
        # 随机分组
        ind = list(range(num))
        # random.shuffle(ind)
        self.nbat = num//self.batsize+1
        t = self.batsize-num%self.batsize
        tt = random.sample(ind, t)
        ind_2 = ind + tt
        random.shuffle(ind_2)
        return [ind_2[i:i+self.batsize] for i in range(0, num, self.batsize)]
        # return np.array(ind_2).reshape((self.nbat, self.batsize))

    def train(self, x, y, x_t, y_t):
        # 训练
        num = x.shape[1]
        self.t = 1 - self.rate*self.lam/num
        epoch = 0
        c = float('inf')
        while epoch < self.i_max:
            bat_inds = self.batpro(num)
            for inds in bat_inds:
                x_bat = x[:, inds]
                y_bat = y[:, inds]
                self.update(x_bat, y_bat)
            epoch += 1
            _, aa = self.forword(x)
            al = aa[-1]
            c = self.calcc(al, y)
            print('c', c)
            self.c.append(c)
            print('Epoch:', epoch)
            self.test(x_t, y_t)
        plt.plot(self.c)
        plt.show()

    def test(self, x, y):
        # 测试
        a = x
        for i in range(1, self.nlayer):
            z = self.w[i]@a+self.b[i]
            a = sigmoid(z)
        ind_p = np.argmax(a, 0)
        ind = np.argmax(y, 0)
        print(sum(ind_p == ind)/y.shape[1])

    def forword(self, x_bat):
        aa = [x_bat] + [np.zeros((t.shape[0], self.batsize)) for t in self.b[1:]]
        zz = [None] + [np.zeros((t.shape[0], self.batsize)) for t in self.b[1:]]
        for i in range(1, self.nlayer - 1):
            zz[i] = self.w[i] @ aa[i - 1] + self.b[i]
            aa[i] = sigmoid(zz[i])
        zz[-1] = self.w[-1] @ aa[-2] + self.b[-1]
        aa[-1] = softmax(zz[-1])
        return zz, aa

    def backward(self, zz, aa, y_bat):
        # 对小批量数据计算各个参数的平局梯度,反向传播算法的关键所在
        delt = [None] + [np.zeros((t.shape[0], self.batsize)) for t in self.b[1:]]
        dw = [None] + [np.zeros(w.shape) for w in self.w[1:]]
        db = [None] + [np.zeros((t.shape[0], self.batsize)) for t in self.b[1:]]

        delt[-1] = aa[-1] - y_bat  # 采用对数似然函数时的 delta_L
        db[-1] = np.mean(delt[-1], 1)
        db[-1].shape = (len(db[-1]), 1)
        dw[-1] = delt[-1]@(aa[-2].T)/self.batsize
        for i in range(self.nlayer-2, 0, -1):
            d_l = ((self.w[i+1].T)@delt[i+1])*sigmoid_d(zz[i])
            delt[i] = d_l
            b_l = np.mean(d_l, 1)
            b_l.shape = (len(b_l), 1)
            db[i] = b_l
            w_l = d_l@(aa[i-1].T)/self.batsize
            dw[i] = w_l
        return dw, db

    def update(self, x_bat, y_bat):
        # 更新参数
        zz, aa = self.forword(x_bat)
        dw, db = self.backward(zz, aa, y_bat)
        self.w = [None]+[self.t*w1-self.rate*w2 for w1, w2 in zip(self.w[1:], dw[1:])]
        self.b = [None]+[b1-self.rate*b2 for b1, b2 in zip(self.b[1:], db[1:])]

    def calcc(self, al, y):
        ind = np.argmax(y, 0)
        return - np.mean(np.log(al[ind, list(range(y.shape[1]))]))

def softmax(z):
    return np.exp(z)/sum(np.exp(z))

def sigmoid(z):
    # 激活函数
    return 1.0/(1.0 + np.exp(-z))

def sigmoid_d(z):
    # 激活函数导数
    return sigmoid(z) * (1 - sigmoid(z))

def parse_data(data):
    # 解析数据
    x_list = [sample[0] for sample in data]
    y_list = [sample[1] for sample in data]
    x = np.array(x_list)
    y = np.array(y_list)
    x = x.T
    y = y.T
    x.shape = x.shape[1:]
    y.shape = y.shape[1:]
    return x, y


if __name__ == '__main__':
    training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
    training_data = list(training_data)
    test_data = list(test_data)
    test_data1 = []
    for i, data in enumerate(test_data):
        t = np.zeros((10, 1))
        t[data[1], 0] = 1.0
        test_data1.append([data[0], t])

    x, y = parse_data(training_data)
    x_t, y_t = parse_data(test_data1)
    nn = Network([784, 100, 10], batsize=10, rate=0.5, i_max=30, lam=1)
    nn.train(x, y, x_t, y_t)

数据集和详细代码参考:https://github.com/Daibingh/network-based-on-numpy

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值