深度学习笔记(二) 浅层神经网络详细实现(含代码)

简单的引入

简单的神经网络示意图如下:

其中的每个节点都做着和logistic回归类似的工作,只不过仅输出层计算成本

每一层其实就是把前一层输入的特征向量进行预测,并把这些每个节点的预测值作为新的特征向量来传给下一层。简单来说就是把原本可以”具体”反应数据的最原始的特征向量抽象化,而每个节点都有自己的参数

最后通过不断迭代,得到满意的参数

 正向传播

 首先对于单个样本,在隐藏层:

当隐藏层节点数很多时,这样就显得不高效了,因此要将这几步计算向量化,即:

可以帮助记忆的特点是:

        1)参数矩阵在整个过程中,维度是不会改变的

        2)这些参数(w、b)矩阵,各层算出来的关键数值矩阵(z、a),他们的行数都是当前层的节点数

 每一层都有自己输出的”特征向量“,或者说“激活向量”,用于激活下一层的运作。比如这个例子中,隐藏层对单个样本输出的是一个(4,1)向量,4代表隐藏层节点数,1代表一个样本,对于它的下一层——输出层,这个(4,1)向量就是输出层用于学习参数的”学习资料“。

下文中的特征向量和激活向量可能会交替使用,但是只要知道都是表示当前层的输出产物就行了

所以总体是这样的(单个样本):

那么对于整个样本,就和我们在logistic回归得到的一样:把代表每个样本的特征向量堆叠成列。所以我们要做的就是把a[0]变成A[0]:

这样其实后面的关键数值矩阵的维度也会有相应的变化,这些矩阵的列也从1变成了m——样本数!

 至此,我们完成了整个样本的正向传播

def forward_propagation(X , parameters):
    """
    参数:
         X - 维度为(n_x,m)的输入数据。
         parameters - 初始化函数(initialize_parameters)的输出
    
    返回:
         A2 - 使用sigmoid()函数计算的第二次激活后的数值
         cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型变量
     """
    
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    
    #开始传播
    Z1 = np.dot(W1,X) + b1  # Z1的维度是 (n_h,n_x)
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2,A1) + b2 # Z2的维度是 (n_y,n_h)
    A2 = sigmoid(Z2)
    
    assert(A2.shape == (n_y,X.shape[1]))
    
    #创建字典存储正向传播过程中产生的预测值
    cache = {
        "Z1":Z1,
        "A1":A1,
        "Z2":Z2,
        "A2":A2
    }
    
    return A2,cache

计算成本

这个例子中,我们的损失函数还是

这里的y_hat对应着a[2]

成本也只是各样本损失值求和后取平均而已

#构建计算成本的函数
def compute_cost(A2,Y,parameters):
    """
    
    参数:
         A2 - 使用sigmoid()函数计算的第二次激活后的数值
         Y - "True"标签向量,维度为(1,数量)
         parameters - 一个包含W1,b1,W2和b2的字典类型的变量
    
    返回:
         成本 
    """
        
    m = Y.shape[1]
    W1 = parameters["W1"]
    W2 = parameters["W2"]
    
    #计算成本
    cost = (-1/m)*np.sum(Y * np.log(A2) + (1 - Y) * (np.log(1-A2)))  #  两个矩阵用 '*' 代表着对应位置的元素相乘 
    cost = float(np.squeeze(cost))
    
    assert(isinstance(cost,float))
    
    return cost

这里Y、A2的维度是(1,m)每一列都是样本的损失值

反向传播

 我们还是从单个样本讲起:

 

 其实就是链式求导法则的运用,只不过运算是在矩阵上运行的。不过我们可以总结出几点:

        1)链式求导可以化简约掉的就大胆地约,比如这里求dz[2]的过程

        2)求导公式化出来后,如果是两个不同维度矩阵相乘,那么可以根据答案矩阵的维度(dw[2]的维度又等于w[2]的维度)来进行等式右边矩阵的相乘位置或者转置的变换,比如这里求dw[2]的过程,就进行了转置

        3)公式化出来后如果是两个维度相同的矩阵相乘,那么就是两矩阵相同位置的元素相乘。用的是 matrix1 * matrix2 或者numpy.multiply(matrix1,matrix2)

 下面来看整个训练集的反向传播

其实变化的只有:

        1)a[2]变A[2],这一步其实在正向传播已经完成

        2)   参数dw变dW,前面再除以m,因为得出dW的运算式变了但是他的维度却没变,事实上也可以看出dW中的每个参数都是各个样本对其影响之和

        3)而db因为它的得出过程,就只是dz,没有另一个矩阵来相乘从而保持db的维度不变但是又包含各样本的影响。而就像我们前面所说那样,参数矩阵的维度是不能变的,因此我们需要手动将各列的样本对b的影响求和,从而把db维度保持和b一致。用到的是numpy.sum(matrix1,axis=1,keepdims = True)  —— axis=1代表压缩列,即变成(n,1);axis=0代表压缩0;keepdims = True保证了得出的运算结果是矩阵而不是列表或者其他结构

所以整个反向传播有6个关键公式

def backward_propagation(parameters,cache,X,Y):
    """
    使用上述说明搭建反向传播函数。
    
    参数:
     parameters - 包含我们的参数的一个字典类型的变量。
     cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型的变量。
     X - 输入数据,维度为(2,数量)
     Y - “True”标签,维度为(1,数量)
    
    返回:
     grads - 包含W和b的导数一个字典类型的变量。
    """
    
    m = X.shape[1]
    W1 = parameters["W1"]
    W2 = parameters["W2"]
    
    A1 = cache["A1"]
    A2 = cache["A2"]
    
    dZ2 = A2 - Y
    dW2 = (1/m) * np.dot(dZ2,A1.T) #要除以m的原因是,这里参数矩阵中的每一个参数都是m个样本对这个参数的影响之和
    db2 = (1/m) * np.sum(dZ2,axis=1,keepdims=True)
    dZ1 = np.multiply(np.dot(W2.T,dZ2),1-np.power(A1,2))
    dW1 = (1/m) * np.dot(dZ1,X.T)
    db1 = (1/m) * np.sum(dZ1,axis=1,keepdims=True)
    
    grads = {
        "dW1":dW1,
        "db1":db1,
        "dW2":dW2,
        "db2":db2
    }
    
    return grads

 更新参数

这个没什么好说的了,就是用梯度下降法来更新。注意函数参数的选择,下面直接贴出代码

#更新参数,使用(dw1,db1,dw2,db2) 来更新 (w1,b1,w2,b2)
def update_parameters(parameters,grads,learning_rate = 1.2):
    
    W1,W2 = parameters["W1"],parameters["W2"]
    b1,b2 = parameters["b1"],parameters["b2"]
    
    dW1,dW2 = grads["dW1"],grads["dW2"]
    db1,db2 = grads["db1"],grads["db2"]
    
    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters

整合

至此,学习过程的每个步骤就完成了,下面就是把这些步骤整合起来,通过循环来进行优化了

#整合
def nn_model(X,Y,n_h,num_iterations,print_cost = False):
    """
    参数:
        X - 数据集,维度为(2,示例数)
        Y - 标签,维度为(1,示例数)
        n_h - 隐藏层的数量
        num_iterations - 梯度下降循环中的迭代次数
        print_cost - 如果为True,则每1000次迭代打印一次成本数值
    
    返回:
        parameters - 模型学习的参数,它们可以用来进行预测。
     """
    np.random.seed(3)
    n_x = layer_sizes(X,Y)[0]
    n_y = layer_sizes(X,Y)[2]
    
    parameters = initialize_parameters(n_x,n_h,n_y)
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    
    for i  in range(num_iterations):
        A2, cache = forward_propagation(X,parameters)
        cost = compute_cost(A2,Y,parameters)
        grads = backward_propagation(parameters,cache,X,Y)
        parameters = update_parameters(parameters,grads,learning_rate = 0.5)
        
        if print_cost:
            if i%1000 == 0:
                print("第",i,"次循环,成本为:",cost)
    
    return parameters

学习流程图:

 

预测

学习完后干什么?当然就是做题检验了!这里我们就只需要进行正向传播,返回得到的最终预测值

#预测
def predict(parameters,X):
    
    A2, cache = forward_propagation(X,parameters)
    predictions = np.round(A2)
    
    return predictions

展示结果

        1)传入训练集

        2)传入测试集进行预测

        3)对比预测值和label(答案),打印正确率

        4)   绘制边界——用图像展示分类成果 (具体的代码还没弄懂,待续)

#正式运行
parameters = nn_model(X,Y,n_h = 4,num_iterations=10000,print_cost = True)

#绘制边界
plot_decision_boundary(lambda x: predict(parameters,x.T),X,Y)
plt.title("Decision Boundary for hidden layer size is four")

predictions = predict(parameters,X)
print ('准确率: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')

个人未明白记录(2021.8.16)

        1.绘制边界

        2.正确率的输出方法

        3.该作业的具体要求(到底是怎么完成红蓝点的分类)

更加具体的整个过程代码参考:

https://blog.csdn.net/u013733326/article/details/79702148 (吴恩达深度学习week3编程作业)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值