zoukankan      html  css  js  c++  java
  • [手把手系列之二]实现多层神经网络

    完整代码:>>点我 欢迎star、fork;一起学习

    网络用途

    或者说应用场景:使用单层神经网络来识别一张图片是否是猫咪的图片。

    数学表示

    给定一张图片(X) 送到网络中,判断这张图片是否是猫咪的照片?

    网络架构

    多层神经网络处理过程:

    • X --> ([linear + relu]^{(L-1)}) --->[linear + sigmoid] ---> (hat{y})

    数学表示

    训练集: (X = [x^{(1)},x^{(2)},...,x^{(i)},....,x^{(m)}]) ;对应标签:(Y=[y^{(1)},y^{(2)},...,y^{(i)},...,y^{(m)}]) ;

    对于训练集中的每张照片(x^{(i)}) 的处理过程:

    repeat:

    (z^{(i)} = w^Tx^{(i)}+b)

    (hat{y}^{(i)} = a^{(i)} = g(z^{(i)}))

    (L(a^{(i)},y^{(i)}) = -y^{(i)}log(a^{(i)})-(1-y^{(i)})log(1-a^{(i)}))

    成本函数:

    (J = frac{1}{m} sum_{i=1}^{m} L(a^{(i)},y^{(i)}))

    最后通过反向传播算法,计算参数(W)(b)

    模型定义

    模型定义步骤

    1. 定义模型结构(如输入向量的特征数目)
    2. 初始化模型参数;
    3. 循环:
      • 前向传播,计算loss;
      • 反向传播,计算梯度;
      • 梯度下降,更新参数;

    代码实现

    激活函数

    1. sigmoid 激活函数及其反向传播过程
    def sigmoid(Z):
        """
        sigmoid激活函数;
        :param Z:
        :return:
        - A: 激活函数值sigmoid(z),
        - cache: (存储Z值,方便反向传播时直接使用)
        """
        A = 1.0/(1+np.exp(-Z))
        cache = Z
        return A, cache
    
    def sigmoid_backward(dA,cache):
        """
        激活函数的反向传播
        :param dA: loss对A的导数
        :param cache:前向传播中缓存的sigmoid输入Z;
        :return:dZ
        """
        Z = cache
        s = 1.0/(1 + np.exp(-Z))
        dZ = dA * s * (1-s)
        return dZ
    
    1. relu激活函数及其反向传播过程
    def relu(Z):
        """
        relu激活函数;
        :param Z:
        :return:
        - A:
        - cache:
        """
        A = np.maximum(0,Z)# max适合单个数值间的比较
        cache = Z
        return A, cache
    
    def relu_backward(dA,cache):
        """
        relu 反向传播计算方法;relu = np.maximum(0,A);导数值:1 or 0.----> dZ= dA or 0
        :param dA:
        :param cache:
        :return: dZ
        """
        Z = cache
        dZ = np.array(dA, copy=True)
    
        #当Z<=0时,dZ=0
        dZ[Z <= 0] = 0
        assert(dZ.shape == Z.shape) #确保维度相同
        return dZ
    

    参数初始化

    权重系数(W)(b) 全都初始化为0.

    def initialize_parameters_deep(layer_dims,type='he'):
        """
        深度神经网络系数初始化函数
        :param layer_dims: 神经网络各层神经元列表, eg:[12288,100,10,1]
        :param type: 系数初始化方法:zeros,random,he;
        :return: parameters:系数字典
        """
        np.random.seed(10)
    
        parameters = {}
        L = len(layer_dims)
    
        if type == "zeros":
            for i in range(1, L):
                parameters['W'+str(i)] = np.zeros((layer_dims[i], layer_dims[i-1]))
                parameters['b'+str(i)] = np.zeros((layer_dims[i], 1))
    
                assert (parameters['W' + str(i)].shape == (layer_dims[i], layer_dims[i - 1]))
                assert (parameters['b' + str(i)].shape == (layer_dims[i], 1))
        elif type == "random":
            for i in range(1, L):
                parameters['W'+str(i)] = np.random.randn(layer_dims[i],layer_dims[i-1]) * 0.01
                parameters['b'+str(i)] = np.zeros((layer_dims[i], 1))
    
                assert (parameters['W' + str(i)].shape == (layer_dims[i], layer_dims[i - 1]))
                assert (parameters['b' + str(i)].shape == (layer_dims[i], 1))
        elif type == "he":
            for i in range(1, L):
                parameters['W'+str(i)] = np.random.randn(layer_dims[i], layer_dims[i-1]) / np.sqrt(layer_dims[i-1])
                parameters['b'+str(i)] = np.zeros((layer_dims[i], 1))
    
                assert (parameters['W' + str(i)].shape == (layer_dims[i], layer_dims[i - 1]))
                assert (parameters['b' + str(i)].shape == (layer_dims[i], 1))
    
        return parameters
    

    前向传播

    前向传播过程

    训练集: $$X = [x^{(1)},x^{(2)},...,x^{(i)},....,x^{(m)}]$$ ;对应标签:$$Y=[y^{(1)},y^{(2)},...,y^{(i)},...,y^{(m)}] $$;

    对于训练集中的每张照片(x^{(i)}) 的处理过程:

    (z^{(i)} = w^Tx^{(i)}+b)

    (hat{y}^{(i)} = a^{(i)} = sigmoid(z^{(i)}))

    (L(a^{(i)},y^{(i)}) = -y^{(i)}log(a^{(i)})-(1-y^{(i)})log(1-a^{(i)}))

    成本函数:(J = frac{1}{m} sum_{i=1}^{m} L(a^{(i)},y^{(i)}))

    代码实现
    1. 线性部分的前向传播过程
    def linear_forward(A_pre,W,b):
        """
        前向传播-线性部分
        :param A_pre:前一层的输出值-激活值
        :param W:系数矩阵
        :param b:偏置矩阵
        :return:线性部分Z,cache(A,W,b)
        """
        Z = np.dot(W, A_pre) + b
        assert(Z.shape == (W.shape[0], A_pre.shape[1])) #可能有多个样本A_pre.shape[1]样本容量
        cache = (A_pre, W, b)
        return Z, cache
    
    1. 单层网络的前向传播过程[线性部分 + 激活函数]
    def linear_activation_forward(A_pre, W, b, activation):
        """
        单层网络(构成:线性部分+激活函数) 的输出结果;
        :param A_pre: 上一层的输出激活值;
        :param W: 本层网络的系数矩阵
        :param b: 偏置
        :param activation: 本层网络的激活函数类型:sigmoid, relu;
        :return:
        - A:激活函数值;
        - cache:linear_cache, activation_cache;加快反向传播计算速度;
        """
        if activation == 'sigmoid':
            Z, linear_cache = linear_forward(A_pre, W, b)
            A, activation_cache = sigmoid(Z)
        elif activation == 'relu':
            Z, linear_cache = linear_forward(A_pre, W, b)
            A, activation_cache = relu(Z)
    
        assert(A.shape == (W.shape[0], A_pre.shape[1]))
        cache = (linear_cache, activation_cache)
    
        return A, cache
    
    1. 神经网络的前向传播过程
    def L_model_forward(X, parameters):
        """
        L层深度神经网络的前向传播过程;
        网络架构:X-->(linear-relu)[L-1]-->(linear-sigmoid)-->AL;
        :param X: 输入
        :param parameters: 各层网络系数字典;
        :return:
        - AL:最终的输出值;水平排列
        - caches:各层网络的cache列表;
        """
        caches = []
        A = X
        L = len(parameters) // 2 #确保是一个整数值;
    
        #前(L-1)层都是相同的架构,可以用for循环计算;最后一层单独计算;
        for i in range(1, L):
            A_pre = A
            A, cache = linear_activation_forward(A_pre,parameters['W'+str(i)],parameters['b'+str(i)],
                                                 activation='relu')
            caches.append(cache)
        AL, cache = linear_activation_forward(A, parameters['W'+str(L)], parameters['b'+str(L)],
                                              activation='sigmoid')
        caches.append(cache)
    
        assert (AL.shape == (1, X.shape[1]))
    
        return AL, caches
    

    由于网络为单层神经网络,前向传播过程和反向传播过程比较简单,所以整合到一起。直接计算出相应的成本函数和相应的系数梯度。

    反向传播

    反向传播过程

    编码实现
    1. 线性部分的反向传播
    def linear_backward(dZ, cache):
        """
        反向传播的线性部分
        :param dZ:
        :param cache: 前向传播中的缓存值(A_pre, W, b)
        :return:
        - dA_pre:关于前一层A的导数值;
        - dW:关于权重的偏导数;
        - db:关于偏置的偏导数;
        """
        A_pre, W, b = cache
        m = A_pre.shape[1]
    
        dA_pre = np.dot(W.T, dZ)
        dW = 1./m * np.dot(dZ, A_pre.T)
        db = 1./m * np.sum(dZ, axis=1, keepdims=True)
    
        assert (dA_pre.shape == A_pre.shape)
        assert (dW.shape == W.shape)
        assert (db.shape == b.shape)
    
        return dA_pre, dW, db
    
    1. 单层网络的反向传播过程[线性部分+激活函数]
    def linear_activation_backward(dA,cache,activation):
        """
        反向传播-单层网络;一个网络层的反向传播计算方法
        :param dA: 对本层网络输出的偏导数
        :param cache:前向传播过程中缓存的元组(linear_cache,activation_cache)
        :param activation:激活函数类型:sigmoid,relu
        :return:
        - dA_pre:
        - dW:
        - db:
        """
        linear_cache, activation_cache = cache
        if activation == 'relu':
            dZ = relu_backward(dA,activation_cache)
            dA_pre, dW, db = linear_backward(dZ, linear_cache)
        elif activation == 'sigmoid':
            dZ = sigmoid_backward(dA, activation_cache)
            dA_pre, dW, db = linear_backward(dZ, linear_cache)
    
        return dA_pre, dW, db
    
    1. 神经网络的反向传播过程
    def L_model_backward(AL, Y, caches):
        """
        反向传播-L层深度NN;整合到一块
        网络架构:X-->(linear+relu)[L-1]-->(linear+sigmoid)-->AL
        :param AL: 最终输出值;
        :param Y: 标签;
        :param caches: 各层网络的系数
        :return: grads 各层网络系数变量的梯度计算值;
        """
        grads = {}
        L = len(caches)
        m = AL.shape[1]
        Y = Y.reshape(AL.shape) # 确保AL和Y shape相同;
    
        #cost:交叉熵函数
        dAL = -(np.divide(Y, AL) - np.divide(1-Y, 1-AL))
        #最后一层单独计算,之后for loop循环;
        current_cache = caches[L-1]
        grads['dA'+str(L)], grads['dW'+str(L)], grads['db'+str(L)] = linear_activation_backward(dAL, current_cache,
                                                                                                activation='sigmoid')
        # 从倒数第二层开始 for-loop:linear+relu
        for i in reversed(range(L-1)):
            #backward: relu->linear
            current_cache = caches[i]
            dA_pre_temp, dW_temp, db_temp = linear_activation_backward(grads['dA'+str(i+2)],current_cache,activation='relu')
            grads['dA'+str(i+1)] = dA_pre_temp
            grads["dW"+str(i+1)] = dW_temp
            grads["db"+str(i+1)] = db_temp
    
        return grads
    

    参数优化

    参数更新过程--使用梯度下降算法;

    def update_parameters_with_gd(parameters,grads,learning_rate):
        """
        系数更新
        :param parameters: 系数;
        :param grads: 关于系数的梯度值;
        :param learning_rate: 学习率更新速度
        :return: parameters更新后的系数
        """
        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 score(params, X, y):
        """
        由测试集判断训练模型的好坏
        :param params: 训练得到的参数
        :param X: 测试集 [n_px*n_px*3, m]
        :param y: 测试集标签 [1, m]
        :return: accuracy 准确率
        """
        m = X.shape[1]
        result = np.zeros((1, m))
    
        probs, _ = L_model_forward(X, params)
       
    
        for i in range(probs.shape[1]):
            if probs[0, i] >= 0.5:
                result[0, i] = 1
    
        accuracy = np.mean(result == y)
    
        return accuracy
    

    模型预测

    输入测试集,输出测试标签.

    运算过程:做一次前向传播,得到输出;再对输出和threshold阈值作比较,得出类别标签。

    def predict(params, X):
        """
        给定图片进行测试,输出预测标签
        :param params: 训练的参数
        :param X: 待预测数据
        :return: 预测结果
        """
        preds = np.zeros((1,X.shape[1]))
        probs, _ = self.__model_forward(X,params)
    
        for i in range(X.shape[1]):
            if probs[0, i] >= 0.5:
                preds[0, i] = 1
    
        preds = np.squeeze(preds)
    
        return preds
    

    函数整合

    def L_layer_model(X, Y, layer_dims, learning_rate=0.0052, num_iters=5000, print_cost=True):
        """
        L层网络模型:包括初始化、训练;
        :param X: 训练数据
        :param Y: 数据标签
        :param layer_dims: 各网络层神经元数目
        :param learning_rate: 学习率
        :param num_iters: 迭代次数
        :param print_cost: 输出cost变化
        :return: paramters 训练后的系数
        """
        np.random.seed(12)
        costs = []
        parameters = initialize_parameters_deep(layer_dims,type='he')
    
        for i in range(0, num_iters):
            AL, caches = L_model_forward(X, parameters)
            cost = compute_cost(AL, Y)
            grads = L_model_backward(AL,Y,caches)
            parameters = update_parameters_with_gd(parameters,grads,learning_rate)
    
            if print_cost and i % 100==0:
                print("Cost after iteration %i:%f" %(i, cost))
                costs.append(cost)
    
        return parameters
    

    测试:1000次迭代、学习率为0.001;

    layers_dims = [12288, 100, 20, 1]
    params = model(X_train,y_train,layers_dims,num_iters=1000,learning_rate=0.001)
    results = score(parameters,test_X,test_Y)
    print(results)
    

    输出结果变化:

    Cost after iteration 0:0.697
    Cost after iteration 100:0.620
    Cost after iteration 200:0.599
    Cost after iteration 300:0.581
    Cost after iteration 400:0.564
    Cost after iteration 500:0.549
    Cost after iteration 600:0.534
    Cost after iteration 700:0.520
    Cost after iteration 800:0.506
    Cost after iteration 900:0.492
    Accuracy on test set: 52%
    

    比随机猜测效果好一点点。网络层更深,优化梯度算法,超参数优化---提高准确率!

    重点是我们自己实现了一个神经网络

    小结

    1. 理解网络运算过程时,画一个运算图很很大程度上帮助理解;
    2. 编码实现时,注意变量的shape变化是否正确!
    3. 优化算法:Momentum、RMSprop、Adam
    4. 批量梯度更新算法
    5. 网络模型越大,参数越多,训练时间越长

    完整代码:>>点我

  • 相关阅读:
    童鞋,[HttpClient发送文件] 的技术实践请查收
    有关[Http持久连接]的一切,卷给你看
    浅谈MemoryCache的原生插值方式
    HTTP1.1 KeepAlive到底算不算长连接?
    C2 hits the assertion assert(base>is_AddP()) failed: should be addp but is Phi
    C2 EA
    OOM Hook
    C2 Loop predicate
    C2 Build IR
    C2 CCP
  • 原文地址:https://www.cnblogs.com/ysugyl/p/9120136.html
Copyright © 2011-2022 走看看