【Pytorch(二)】Numpy 搭建全连接神经网络(2)

简介: 【Pytorch(二)】Numpy 搭建全连接神经网络(2)

7. 损失函数

损失函数可以监测训练进展,确保我们向着正确的方向移动。“一般来说,损失函数显示了我们与’理想’解决方案之间的距离。”损失函数包含很多种(例如 Pytorch 中提供了很多选项,可见其官方网站 https://pytorch.org/docs/stable/nn.html#loss-functions ),我们通常需要根据具体问题来选择。


我们计划将神经网络应用于二元分类问题,即模型最后的预测结果只有两种情况,对于每个类别我们预测得到的概率将分别为 𝑝 和 1−𝑝。在这个问题中我们将使用交叉熵损失函数 (cross entropy loss):

image.png


def get_cost_value(Y_hat, Y):
    # number of examples (=900)
    m = Y_hat.shape[1]
    # calculation of the cost according to the formula
    # - Y: each element is either 0 or 1
    # - shape of Y & Y_hat: (1,900)
    # - shape of cost: (1,1)
    cost = -1 / m * (np.dot(Y, np.log(Y_hat).T) + np.dot(1 - Y, np.log(1 - Y_hat).T))
    return np.squeeze(cost)  # np.squeeze() -  从数组的形状中删除单维条目,即把shape中为1的维度去掉

为了取得更多关于学习过程的信息,我们还可以另外实现一个计算精确度的函数。


# an auxiliary function that converts probability into class
def convert_prob_into_class(probs):
    probs_ = np.copy(probs)
    probs_[probs_ > 0.5] = 1
    probs_[probs_ <= 0.5] = 0
    return probs_
def get_accuracy_value(Y_hat, Y):
    Y_hat_ = convert_prob_into_class(Y_hat)  # shape: (1,900)
    accuracy = (Y_hat_ == Y).all(axis=0).mean()  # numpy.all(): 测试沿给定轴的所有数组元素是否都计算为True
                                                 # (Y_hat_ == Y).all(axis=0).shape = (900,)
                                                 # numpy.mean(): 求取均值
    return accuracy

8. 实现神经网络的反向传播过程

和前向传播一样,我们将把网络的反向传播过程拆分成两个函数来实现:


函数 8-1 (single_layer_backward_propagation): 实现单个全连接层的反向传播过程

函数 8-2 (full_backward_propagation): 基于函数 8-1,实现整个神经网络的反向传播过程

8.1 函数 8-1 (single_layer_backward_propagation): 实现单个全连接层的反向传播过程

下图显示了单个神经元上的前向传播和反向传播过程。对一个神经网络层 (𝑙),我们将接收传输自 𝑙+1 层的 𝑑𝐴[𝑙],并根据前向传播时保存在 𝑚𝑒𝑚𝑜𝑟𝑦 的中间数据 𝑍_𝑐𝑢𝑟𝑟 和 𝐴_𝑝𝑟𝑒𝑣,计算 𝑙𝑜𝑠𝑠对权重 𝑊[𝑙]和偏置 𝑏[𝑙]的偏导数——𝑑𝑊[𝑙]和 𝑑𝑏[𝑙]。

image.png



下面的公式描述了单个全连接层上的反向传播过程。由于本次实验的重点在于实际实现,所以此处省略求导过程,感兴趣的同学请阅读课本相关章节。从公式上我们可以很明显地看到,为什么之前需要在前向传播时记住中间层的 𝐴_𝑝𝑟𝑒𝑣 和 𝑍_𝑐𝑢𝑟𝑟 矩阵的值。

image.png



def single_layer_backward_propagation(dA_curr, W_curr, b_curr, Z_curr, A_prev, activation="relu"):
    # number of examples
    m = A_prev.shape[1]
    # selection of activation function
    if activation is "relu":
        backward_activation_func = relu_backward
    elif activation is "sigmoid":
        backward_activation_func = sigmoid_backward
    else:
        raise Exception('Non-supported activation function')
    # step-1: {dA_curr, Z_curr} -> dZ_curr (activation function derivative)
    dZ_curr = backward_activation_func(dA_curr, Z_curr)
    # step-2: {dZ_curr, A_prev} -> dW_curr (derivative of the matrix W)
    #         dZ_curr -> db_curr (derivative of the vector b)
    ###############  Please finish this part  ################
    # [Hint]: get dW_curr and db_curr from dZ_curr and A_prev
    #   dW_curr = np.dot(XXX,XXX) / m
    dW_curr = np.dot(dZ_curr, A_prev.T) / m
    ########################  end  ###########################
    db_curr = np.sum(dZ_curr, axis=1, keepdims=True) / m  # np.sum(): sum of array elements over a given axis
    # or, you can write it as:
    # db_curr = np.dot(dZ_curr, np.ones((dZ_curr.shape[1], 1))) / m  # dZ_curr.shape[1] = 900
    # step-3: {dZ_curr, W_curr} -> dA_prev (derivative of the matrix A_prev)
    ###############  Please finish this part  ################
    # [Hint]: get dA_prev from dZ_curr and W_curr
    #   dA_prev = np.dot(XXX,XXX)
    dA_prev = np.dot(W_curr.T, dZ_curr)
    ########################  end  ###########################
    return dA_prev, dW_curr, db_curr

代码解释:根据公式可知,矩阵dW_curr的微分是(dZl*A[l-1]T)/m,不难发现dZl正是向量dZ_curr,而A[l-1]T则是向量A_prev的转置。向量dA_prev是 WlTdZl*,WlT是向量W_curr的转置,dZ*l是向量dZ_curr。


8.2 函数 8-2 (full_backward_propagation): 实现整个神经网络的反向传播过程

基于单个网络层的反向传播函数,我们从最后一层开始迭代计算所有参数上的导数(𝑑𝑊 和 𝑑𝑏),并最终返回包含所需梯度的字典。为了计算各层参数上的导数,函数需要输入前向传播时的网络输出 𝑌_ℎ𝑎𝑡 和标准结果 𝑌,并交由损失函数计算 𝑙𝑜𝑠𝑠。𝑙𝑜𝑠𝑠对于 𝑌_ℎ𝑎𝑡 的偏导数可计算如下:

image.png

此外,函数还需要输入如下信息:


前向传播时的中间数据 𝑚𝑒𝑚𝑜𝑟𝑦(含 𝐴_𝑝𝑟𝑒𝑣 和 𝑍_𝑐𝑢𝑟𝑟) —— 用以计算各层的 𝑑𝑊 和 𝑑𝑏;

神经网络的参数 𝑝𝑎𝑟𝑎𝑚𝑠_𝑣𝑎𝑙𝑢𝑒𝑠 —— 用以计算各层的 𝑑𝐴_𝑝𝑟𝑒𝑣。

函数的输出是各层的 𝑑𝑊 和 𝑑𝑏,保存在 𝑔𝑟𝑎𝑑𝑠_𝑣𝑎𝑙𝑢𝑒𝑠 中。


在编写函数8-2前,我们先来试验一下等下将怎样从最后一层开始,向前逐层处理网络:


# let's see how we will access each layer in the backward pass
for layer_idx_prev, layer in reversed(list(enumerate(NN_ARCHITECTURE))):  # we will use this code line in the function 8-2
    print(layer_idx_prev)  # from 4 to 0
    print(layer)  # from layer L to layer 1 (we number network layers from 1)
4
{'input_dim': 25, 'output_dim': 1, 'activation': 'sigmoid'}
3
{'input_dim': 50, 'output_dim': 25, 'activation': 'relu'}
2
{'input_dim': 50, 'output_dim': 50, 'activation': 'relu'}
1
{'input_dim': 25, 'output_dim': 50, 'activation': 'relu'}
0
{'input_dim': 2, 'output_dim': 25, 'activation': 'rel
def full_backward_propagation(Y_hat, Y, memory, params_values, nn_architecture):
    grads_values = {}
    # number of examples (Y.shape=(1,900))
    m = Y.shape[1]
    # a hack ensuring the same shape of the prediction vector and labels vector
    Y = Y.reshape(Y_hat.shape)
    # initiation of gradient descent algorithm
    dA_prev = - (np.divide(Y, Y_hat) - np.divide(1 - Y, 1 - Y_hat));
    for layer_idx_prev, layer in reversed(list(enumerate(nn_architecture))):
        # we number network layers from 1
        layer_idx_curr = layer_idx_prev + 1
        # extraction of the activation function for the current layer
        activ_function_curr = layer["activation"]
        dA_curr = dA_prev
        A_prev = memory["A" + str(layer_idx_prev)]
        Z_curr = memory["Z" + str(layer_idx_curr)]
        W_curr = params_values["W" + str(layer_idx_curr)]
        b_curr = params_values["b" + str(layer_idx_curr)]
        dA_prev, dW_curr, db_curr = single_layer_backward_propagation(
            dA_curr, W_curr, b_curr, Z_curr, A_prev, activ_function_curr)
        grads_values["dW" + str(layer_idx_curr)] = dW_curr
        grads_values["db" + str(layer_idx_curr)] = db_curr
    return grads_values

9. 更新参数值

反向传播是为了计算梯度,以根据梯度进行优化,更新网络的参数值。为了完成这一任务,我们将使用如下两组数据:


𝑝𝑎𝑟𝑎𝑚𝑠_𝑣𝑎𝑙𝑢𝑒𝑠,其中保存了当前参数值;

𝑔𝑟𝑎𝑑𝑠_𝑣𝑎𝑙𝑢𝑒𝑠,其中保存了用于更新参数值所需的梯度信息。

接下来我们只需在每个网络层上应用以下公式即可。其中,𝛼 指学习率 (learning rate)。

image.png

def update(params_values, grads_values, nn_architecture, learning_rate):
    # iteration over network layers
    for idx, layer in enumerate(nn_architecture):
        layer_idx = idx + 1
        ###############  Please finish this part  ################
        # [Hint]: update params_values
        #   params_values["W" + str(layer_idx)] = XXX
        #   params_values["b" + str(layer_idx)] = XXX
        params_values["W" + str(layer_idx)] -= learning_rate * grads_values["dW" + str(layer_idx)]
        params_values["b" + str(layer_idx)] -= learning_rate * grads_values["db" + str(layer_idx)]
        ########################  end  ###########################
    return params_values

代码解释:每一层的权重矩阵 𝑊 和偏置向量 𝑏均以Wl与bl的形式保存在params_values中,而grads_values中保存了用于更新参数值所需的梯度信息。根据已经给出的公式可知,参数的更新是每次减去梯度乘以学习率,据此可以填写代码。


10. 整合:完成 train 函数

到目前为止,我们已经完成了最困难的部分,准备好了所需的函数,接下来只需以正确的顺序把它们整合到一起。为了更好地理解每一步操作的顺序,请同学们再次查看“0. 概览”中的训练流程图。


train 函数将返回经过训练优化后的参数值,并显示在训练期间模型在训练集上的准确率。后期在测试集上,只需使用训练好的模型参数并运行一次完整的前向传播过程即可。


def train(X, Y, nn_architecture, epochs, learning_rate, verbose=True, callback=None):
    # initiation of neural net parameters
    params_values = init_layers(nn_architecture, 2)
    # initiation of lists storing the history of metrics calculated during the learning process 
    cost_history = []
    accuracy_history = []
    # performing calculations for subsequent iterations
    for i in range(epochs):
        # step forward
        Y_hat, cache = full_forward_propagation(X, params_values, nn_architecture)
        if i==1:
            print("X.shape: ", X.shape)  # (2, 900)
            print("Y_hat.shape: ", Y_hat.shape)  # (1, 900)
            print("Y.shape: ", Y.shape)  # (1, 900)
        # calculating metrics and saving them in history
        cost = get_cost_value(Y_hat, Y)
        cost_history.append(cost)
        accuracy = get_accuracy_value(Y_hat, Y)
        accuracy_history.append(accuracy)
        # step backward - calculating gradient
        grads_values = full_backward_propagation(Y_hat, Y, cache, params_values, nn_architecture)
        # updating model state
        params_values = update(params_values, grads_values, nn_architecture, learning_rate)
        if(i % 50 == 0):
            if(verbose):
                print("Iteration: {:05} - cost: {:.5f} - accuracy: {:.5f}".format(i, cost, accuracy))
            if(callback is not None):
                callback(i, params_values)
    return params_values

相关文章
|
2月前
|
传感器 运维 物联网
蓝牙Mesh网络:连接未来的智能解决方案
蓝牙Mesh网络:连接未来的智能解决方案
218 12
|
6天前
|
物联网 5G 数据中心
|
10天前
|
Docker 容器
docker swarm启动服务并连接到网络
【10月更文挑战第16天】
15 5
|
17天前
|
安全 网络架构
无线网络:连接未来的无形纽带
【10月更文挑战第13天】
54 8
|
29天前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
49 2
|
20天前
|
人工智能 安全 搜索推荐
|
23天前
|
监控 安全 5G
|
2月前
|
机器学习/深度学习
小土堆-pytorch-神经网络-损失函数与反向传播_笔记
在使用损失函数时,关键在于匹配输入和输出形状。例如,在L1Loss中,输入形状中的N代表批量大小。以下是具体示例:对于相同形状的输入和目标张量,L1Loss默认计算差值并求平均;此外,均方误差(MSE)也是常用损失函数。实战中,损失函数用于计算模型输出与真实标签间的差距,并通过反向传播更新模型参数。
|
1月前
|
安全 5G 网络安全
5G 网络中的认证机制:构建安全连接的基石
5G 网络中的认证机制:构建安全连接的基石
47 0