机器学习 --- 深度前馈网络

对于某些问题,其特征的表述极其困难,比如人脸的识别,其影响因素可能涉及到角度,光影,颜色,形状等。深度学习旨在将原复杂的映射关系分解成一系列嵌套的简单映射。

一、深度前馈网络简要

深度前馈网络又称多层感知机(MLP),对于分类器,函数 y=f^*(x) 将输入 x 映射到类别 y 上。而MLP定义了一个映射 y=f(x;\theta) ,通过学习参数 \theta ,以达到最佳近似 f^* 的效果。由于模型的输出和模型本身没有反馈连接,所以称作前馈神经网络,例如卷积神经网络(在之后的文章中会详细说明,本次先说明神经网络的基础部分)。而被扩展为包含反馈连接的,称作循环神经网络(后续文章会陆续涉及)。

二、深层神经网络的直观理解

深层神经网络(Deep Neural Network)模型图

神经网络分为三层:输入层、隐藏层、输出层。网络模型通过一个有向无环图关联,图中描述了各节点(函数)如何复合在一起

深度学习中,网络即意味着将很多不同的函数复合在一起,以能达到表示复杂问题的能力。

网络模型通过一个有向无环图关联,图中描述了各节点(函数)如何复合在一起能得到最佳的函数的近似效果。整个网络工作的流程从图中反应就是,变量 X 的特征属性导入至输入层,通过隐藏层内部的计算,从输出层导出输出结果 \tilde{Y} ,再经过比对 Y 与 \tilde{Y} ,不断地从函数集群中选取合适函数(简单但不准确地理解就是:调整参数 \theta ),以能更好地近似 f^{*} 。

网络模型中含有很多层次,每个层次可以抽象成函数 f^{(n)} ,其中 n 表示当前层数,相对于最开始的模型图,可以得到一个链式结构: f(x)=f^{4}(f^{3}(f^{2}(f^{1}(x)))),对于某层来说,上层的输出作为该层的输入。

三、神经节点

网络模型中含有多个节点,这形象的称作"神经元",又称"神经节点"。神经节点的意义其实就类似于之前提到的多个函数复合(这里是神经节点之间通过某种规则互连),而单个神经节点又是最基本的函数。除输入层外,每个神经节点根据功能的划分一般有线性函数激活函数两大部分(当然不是绝对的)。仅通过线性函数难以去表述某些非线性问题,这可以从XOR实例中很好地体现。从线性代数的角度上理解,线性变换一般只能在该向量空间中表示某些情况,对于高维度的信息,则难以表达,因此需要通过激活函数跳转至更高维的向量空间。单个神经元节点如下图所示:

上图中 w^{T}x+b = z 属于线性部分, x 为上层的输出,作为本层的输入。之后经过一个激活函数 \sigma(z) = a ,因此每个神经元的信息主要包括有参数 w,b 和选取哪个激活函数。

四、激活函数

激活函数负责将神经节点的输入映射到输出端上,如果不使用激活函数,那么无论神经网络有多少层,都是输入的线性组合,因此不能解决非线性问题。

列举一些常用的激活函数:

Sigmoid:

双曲正切:

线性整流:

渗漏整流线性:

softmax: a_i=\frac{e^{z_i}}{\sum_{k=1}^{k}{e^{z_k}}}

五、代价函数

1.代价函数概述

网络模型通过学习算法,在数据集上训练模型,发掘 X\rightarrow Y 的映射关系。引入代价函数评估算法性能,为学习算法指明优化方向。这样来说,参数模型经过训练样本定义了一个分布 P(y|x;\theta) ,所以使用最大似然原理估计模型参数。

2.交叉熵代价函数

以二分类问题为例,其符合伯努利分布,对样本 X_{1},X_{2},...,X_{n} 有:

P(y=1|x;\theta)=\tilde{y}

P(y=0|x;\theta)=1-\tilde{y}

因此样本 x 的分布律是:

P(y|x;\theta)=\tilde{y}^{y}\cdot(1-\tilde{y})^{1-y}

由似然函数:

L(\theta)=p(\tilde{y}|X;\theta)=\prod_{i=1}^{m}p(y^{(i)}|x^{(i)};\theta)=\prod_{i=1}^{m}\tilde{y}^{y}\cdot(1-\tilde{y})^{1-y}

为了构建近似样本真实的概率分布,需要取似然函数的最大值,因此对似然函数两边取对数并乘以-1,即最小化下面的式子:

C=-\sum_{i=1}^{m}{y^{(i)}log\tilde{y}^{(i)}+(1-y^{(i)})log(1-\tilde{y}^{(i)})}

该式子称作交叉熵代价函数,常用作二分类问题,此时神经网络输出层一般会选择Sigmoid作为激活函数。观察上式,直观地理解下,当出现预测错误,交叉熵代价函数的值将会变得非常大。

3.对数释然代价函数

对于多分类问题,输出层一般会选择softmax作为激活函数。此时代价函数一般选择对数释然代价函数,对共有 k 个类别问题有:

C=-\sum_{i=1}^{k}{y^{(i)}log \tilde{y}^{(i)}}

注意:这里为什么不选择均方误差代价函数的原因在于,使用均方误差代价函数容易造成softmax和sigmoid运算单元的饱和现象,这样不利于基于梯度的学习方法,使用上述函数可以消去函数中的指数运算从而有利于梯度学习过程。

六、正向传播

神经网络的训练过程主要分为正向传播反向传播过程。

1.正向传播

假定输入矩阵 x\epsilon R^{m\times n} ,即有 m 个样本, n 个特征值。正向传播过程中,对于第 l(l > 0) 层(第0层为输入层),有:

z^{[l]}={w^{[l]}}^Ta^{[l-1]} + b^{[l]} \quad (a^{[0]} = x)

a^{[l]}=g^{[l]}(z^{[l]})

其中 g^{[l]} 表示第 l 层激活函数。当传至输出层时,此时:

A^{[l]} = \tilde{y}

2.正向传播示例

假设网络模型如下图所示,网络共有 L=3 层

输入矩阵维度表示成: x=(m,n)

对于第 l 层:

w^{[l]}=(l,l-1)

b^{[l]}=(l,1)

第一层向第二层传播过程:

z^{[1]}={w^{[1]}}^Tx+b^{[1]}

a^{[1]}=g^{[1]}(z^{[1]})

第二层向第三层传播过程:

z^{[2]}={w^{[2]}}^Ta^{[1]}+b^{[2]}

\tilde{y}=a^{[2]}=g^{[2]}(z^{[2]})

七、反向传播

反向传播是根据代价函数通过链式求导算得,以上图的网络模型图作为示例,此处是针对二分类问题(选用交叉熵代价函数,输出层激活函数sigmoid):

正向传播过程:

z^{[1]}={w^{[1]}}^Tx+b^{[1]}

a^{[1]}=g^{[1]}(z^{[1]})

z^{[2]}={w^{[2]}}^Ta^{[1]}+b^{[2]}

a^{[2]}=g^{[2]}(z^{[2]})

z^{[3]}={w^{[3]}}^Ta^{[2]}+b^{[3]}

a^{[3]}=g^{[3]}(z^{[3]})

z^{[4]}={w^{[4]}}^Ta^{[3]}+b^{[4]}

a^{[4]}=g^{[4]}(z^{[4]})

反向传播过程:

* 表示点乘

\cdot 表示矩阵乘法

\frac{\partial C}{\partial a^{[4]}}=\frac{y}{a^{[4]}}-\frac{1-y}{1-a^{[4]}}

\frac{\partial C}{\partial z^{[4]}}=\frac{\partial C}{\partial a^{[4]}}\frac{\partial a^{[4]}}{\partial z^{[4]}}=a^{[4]}-y

一次循环开始-----------------------------------------

\frac{\partial C}{\partial w^{[4]}}=\frac{\partial C}{\partial z^{[4]}}\frac{\partial z^{[4]}}{\partial w^{[4]}}=\frac{1}{m} * dz^{[4]} \cdot  {a^{[3]}}^T

\frac{\partial C}{\partial b^{[4]}}=\frac{\partial C}{\partial z^{[4]}}\frac{\partial z^{[4]}}{\partial w^{[4]}}= \frac {1}{m}np.sum(dz^{[4]},axis=1,keepdim=True)

(这里解释一下为什么乘以 \frac{1}{m} ,因为在正向传播中, w^Tx+b=z 的矩阵乘法过程相当于将 m 样本的结果压缩到矩阵 z 中,反向传播过程可看成一个逆向的过程)
\frac{\partial C}{\partial a^{[3]}}=\frac{\partial C}{\partial z^{[4]}}\frac{\partial z^{[4]}}{\partial a^{[3]}}= {w^{[4]}}^T \cdot dz^{[4]}

\frac{\partial C}{\partial z^{[3]}}=\frac{\partial C}{\partial a^{[3]}}\frac{\partial a^{[3]}}{\partial z^{[3]}}={w^{[4]}}^T \cdot  dz^{[4]} * g'^{[3]}(z^{[3]})

一次循环结束-----------------------------------------

所以,整个反向传播算法的过程:

dz^{[l]}=a^{[l]}-y

dw^{[l]}=\frac{1}{m}*dz^{[l]} \cdot {a^{[l-1]}}^T

db^{[l]}=\frac{1}{m}np.sum(dz^{[l]},axis=1,keepdim =True)

da^{[l-1]}={w^{[l]}}^T \cdot dz^{[l]}

dz^{[l-1]}={w^{[l]}}^T \cdot dz^{[l]} * g'^{[l]}(z^{[l]})

八、代码

为了更清晰的表示这些传播过程,下面使用python代码来具体体现(来自Andrew ng 作业):

import numpy as np
def sigmoid(Z):
    cache = Z
    A = 1.0 / (1 + np.exp(-Z))
    assert(A.shape == Z.shape)
    return A, cache

def sigmoid_derivative(dA,cache):
    Z = cache
    f = 1.0 / (1 + np.exp(-Z))
    dZ = dA * f * (1 - f)
    assert(dZ.shape == Z.shape)
    return dZ

def ReLU(Z):
    cache = Z
    A = np.maximum(0, Z)
    assert(A.shape == Z.shape)
    return A, cache

def ReLU_derivative(dA,cache):
    Z = cache
    dZ = np.array(dA, copy = True)
    dZ[Z <= 0] = 0
    assert(dZ.shape == Z.shape)
    return dZ

# 初始化参数
def initialize_parameters(layer):
    np.random.seed(1)
    parameters = {}
    L = len(layer)
    for i in range(1,L):
        parameters["W" + str(i)] = np.random.randn(layer[i], layer[i-1]) * np.sqrt(1 / layer[i-1])
        parameters["b" + str(i)] = np.zeros((layer[i], 1))
        
        assert(parameters["W" + str(i)].shape == (layer[i], layer[i-1]))
        assert(parameters["b" + str(i)].shape == (layer[i], 1))
    return parameters


# 正向线性化
def linear_forward(A_prev, W, b):
    Z = np.dot(W, A_prev) + b
    assert(Z.shape == (W.shape[0], A_prev.shape[1]))
    cache = (A_prev, W, b)
    return Z,cache

# 正向激活
def linear_forward_activation(A_prev, W, b, activation_function):    
    if activation_function == 'sigmoid':
        Z, linear_cache = linear_forward(A_prev, W, b)
        A, activation_cache = sigmoid(Z)
    elif activation_function == 'ReLU':
        Z, linear_cache = linear_forward(A_prev, W, b)
        A, activation_cache = ReLU(Z)
    assert(A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)
    
    return A, cache

# 正向传播
def L_model_forward(X, parameters):
    A = X
    L = len(parameters) // 2
    caches = []
    
    # 输入层与隐藏层正向传播
    for i in range(1, L):
        A_prev = A
        A, cache = linear_forward_activation(A_prev, parameters['W' + str(i)], parameters['b' + str(i)], 'ReLU')
        caches.append(cache)
        
    # 输出层正向传播
    AL, cache = linear_forward_activation(A, parameters['W' + str(L)], parameters['b' + str(L)],'sigmoid')
    caches.append(cache)
    assert(AL.shape == (1, X.shape[1]))
    return AL, caches
        
# 计算成本函数
def compute_cost(AL, Y):
    m = Y.shape[1]
    cost = -np.sum(np.multiply(np.log(AL),Y) + np.multiply(np.log(1 - AL), 1 - Y)) / m
    cost = np.squeeze(cost)
    return cost

# 反向线性传播
def linear_backward(dZ, cache):
    A_prev, W, b = cache
    m = A_prev.shape[1]
    dW = 1.0 / m * np.dot(dZ, A_prev.T)
    db = 1.0 / m * np.sum(dZ, axis = 1, keepdims = True)
    dA_prev = np.dot(W.T, dZ)
    
    assert(dW.shape == (dZ.shape[0], A_prev.shape[0]))
    assert(db.shape == db.shape)
    assert(dA_prev.shape == (W.T.shape[0], dZ.shape[1]))
    return dA_prev, dW, db    

# 反向激活
def linear_backward_activation(dA, cache, function_activation):
    linear_cache, activation_cache = cache
    if function_activation == 'sigmoid':
        dZ = sigmoid_derivative(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
    elif function_activation == 'ReLU':
        dZ = ReLU_derivative(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
    return dA_prev, dW, db

# 反向传播
def L_model_backward(Y, AL, caches):
    grads = {}
    L = len(caches)
    Y = Y.reshape(AL.shape)
    dAL = -(np.divide(Y, AL) - np.divide(1-Y, 1-AL))
    grads['dA' + str(L)], grads['dW' + str(L)], grads['db' + str(L)] = \
    linear_backward_activation(dAL, caches[L-1], 'sigmoid')
    for i in reversed(range(L - 1)):
        grads['dA' + str(i + 1)], grads['dW' + str(i + 1)], grads['db' + str(i + 1)] = \
        linear_backward_activation(grads['dA' + str(i + 2)], caches[i],'ReLU')
    return grads

# 参数更新
def updata_parameters(parameters, grads, learning_rate):
    L = len(parameters) // 2
    for i in range(L):
        parameters['W' + str(i + 1)] = parameters['W' + str(i + 1)] \
        - learning_rate * grads['dW' + str(i + 1)]
        parameters['b' + str(i + 1)] = parameters['b' + str(i + 1)] \
        - learning_rate * grads['db' + str(i + 1)]
    return parameters


# 输出结果评估
def predict(X, y, parameters, print_result = True):
    m = X.shape[1]
    p = np.zeros((1, m))
    AL, caches = L_model_forward(X, parameters)
    for i in range(m):
        if AL[0, i] > 0.5:
            p[0,i] = 1
        elif AL[0, i] < 0.5:
            p[0, i] = 0
    if print_result == True:
        print("Accuracy:" + str(np.sum(p == y) / m))
    return p

# 神经网络传播
def L_layer_model(X, Y, layer, iteration, learning_rate):
    costs = []
    parameters = initialize_parameters(layer)
    for i in range(0, iteration):
        AL, caches = L_model_forward(X, parameters)
        cost = compute_cost(AL, Y)
        grads = L_model_backward(Y, AL, caches)
        parameters = updata_parameters(parameters, grads, learning_rate)
        if i % 100 == 0:
            print("cost after iteration %d:%f" %(i, cost))
            costs.append(cost)
    plt.plot(np.squeeze(costs))
    plt.xlabel('iteration')
    plt.ylabel('cost')
    plt.title('learning-rate:' + str(learning_rate))
    plt.show()
    return parameters

猜你喜欢

转载自blog.csdn.net/adorkable_thief/article/details/84337075
今日推荐