【深度学习】用NumPy和PyTorch分别实现神经网络的反向传播

时间:2023-04-02 11:20:17


反向传播算法的例子

假设我们的神经网络中有层,第层有个神经元,第层与层之间的权重矩阵为,偏置向量为。对于一个具有个样本的数据集,我们定义输入为,输出为。

在前向传播中,我们使用以下公式计算每一层的值:

其中是激活函数,表示层数,表示输入层。

在反向传播中,我们需要计算每一层的误差并更新权重。计算输出层的误差时,可以使用以下公式:

其中表示输出层。

然后,我们可以通过反向传播输出误差来计算隐藏层的误差:

其中,是激活函数的导数。我们可以使用计算出的误差来更新权重矩阵:

最后我们更新权重矩阵和偏置向量:

其中是学习率。我们可以通过多次迭代该过程来训练模型,直到模型达到预定的性能。

代码示例

  • 以下是实现反向传播算法的Python代码,使用向量化计算来提高效率:
import numpy as np

def initialize_parameters(n_x, layers, n_y):
    """
    用于初始化多层神经网络的参数

    参数:
        n_x -- 输入层的大小
        layers -- 包含每一层大小的数组
        n_y -- 输出层的大小

    返回值:
        paramters -- 包含W和b的Python字典
    """
    np.random.seed(1)
    L = len(layers)  # 网络层数
    parameters = {}
    # 初始化第1层
    parameters['W1'] = np.random.randn(layers[0], n_x) * 0.01
    parameters['b1'] = np.zeros((layers[0], 1))
    # 初始化其他层
    for i in range(1, L):
        parameters['W' + str(i + 1)] = np.random.randn(layers[i], layers[i - 1]) * 0.01
        parameters['b' + str(i + 1)] = np.zeros((layers[i], 1))
    # 初始化输出层
    parameters['W' + str(L + 1)] = np.random.randn(n_y, layers[L - 1]) * 0.01
    parameters['b' + str(L + 1)] = np.zeros((n_y, 1))
    return parameters


def sigmoid(Z):
    """
    sigmoid激活函数的向量化实现

    参数:
        Z -- 线性求和的结果

    返回值:
        A -- 激活后的结果
    """
    A = 1 / (1 + np.exp(-Z))
    return A


def sigmoid_derivative(Z):
    """
    sigmoid激活函数的导数

    参数:
        Z -- 线性求和的结果

    返回值:
        dZ -- sigmoid函数的导数
    """
    A = sigmoid(Z)
    dZ = A * (1 - A)
    return dZ


def tanh_derivative(Z):
    """
    tanh激活函数的导数

    参数:
        Z -- 线性求和的结果

    返回值:
        dZ -- tanh函数的导数
    """
    A = np.tanh(Z)
    dZ = 1 - np.power(A, 2)
    return dZ


def forward_propagation(X, parameters):
    """
    实现前向传播算法

    参数:
        X  -- 输入数据,shape为(n_x, m)
        parameters -- 包含W和b的Python字典的网络参数

    返回值:
        AL -- 最后一层的激活值
        caches -- 包含每层中的(A, Z)的列表
    """
    caches = []
    A_prev = X
    L = len(parameters) // 2  # 网络层数

    # 前L-1层使用tanh激活函数
    for i in range(1, L):
        # 线性求和
        Z = np.dot(parameters['W' + str(i)], A_prev) + parameters['b' + str(i)]
        # 缓存A和Z
        cache = (A_prev, Z)
        caches.append(cache)
        # 应用tanh激活函数
        A = np.tanh(Z)
        A_prev = A

    # 最后一层使用sigmoid激活函数
    Z = np.dot(parameters['W' + str(L)], A_prev) + parameters['b' + str(L)]
    cache = (A_prev, Z)
    caches.append(cache)
    AL = sigmoid(Z)

    return AL, caches


def compute_cost(AL, Y):
    """
    计算代价函数

    参数:
        AL -- 模型的输出
        Y  -- 真实值

    返回值:
        cost -- 代价函数
    """
    m = Y.shape[1]
    cost = -1 / m * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1 - AL))
    return cost


def backward_propagation(AL, Y, caches):
    """
    实现反向传播算法

    参数:
        AL -- 模型的输出
        Y  -- 真实值
        caches -- 包含每层中的(A, Z)的列表

    返回值:
        grads -- 包含每个参数的梯度的Python字典
    """
    m = Y.shape[1]
    L = len(caches)
    grads = {}

    # 对最后一层使用sigmoid的反向传播
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    current_cache = caches[L - 1]
    A_prev, Z = current_cache
    dZ = dAL * sigmoid_derivative(Z)
    grads["dW" + str(L)] = 1. / m * np.dot(dZ, A_prev.T)
    grads["db" + str(L)] = 1. / m * np.sum(dZ, axis=1, keepdims=True)

    # 对前L-1层使用tanh的反向传播
    for l in reversed(range(L - 1)):
        current_cache = caches[l]
        A_prev, Z = current_cache
        dA = np.dot(parameters["W" + str(l + 2)].T, dZ)
        dZ = dA * tanh_derivative(Z)
        grads["dW" + str(l + 1)] = 1. / m * np.dot(dZ, A_prev.T)
        grads["db" + str(l + 1)] = 1. / m * np.sum(dZ, axis=1, keepdims=True)

    return grads


def update_parameters(parameters, grads, learning_rate):
    """
    使用梯度下降算法更新参数

    参数:
        parameters -- 包含W和b的Python字典
        grads -- 包含对应W和b的梯度的Python字典
        learning_rate -- 学习率

    返回值:
        parameters -- 更新后的包含W和b的Python字典
    """
    L = len(parameters) // 2  # 网络层数
    for l in range(L):
        parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads["dW" + str(l + 1)]
        parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]
    return parameters


def L_layer_model(X, Y, layers_dims, learning_rate=0.01, num_iterations=3000, print_cost=False):
    """
    实现一个L层神经网络

    参数:
        X  -- 输入数据,shape为(n_x,数量)
        Y  -- 真实值,shape为(1,数量)
        layers_dims -- 包含每一层大小的数组
        learning_rate -- 学习率
        num_iterations -- 迭代次数
        print_cost -- 每100次迭代后是否打印代价函数值

    返回值:
        parameters -- 训练后的模型参数
    """

    np.random.seed(1)
    costs = []
    parameters = initialize_parameters(X.shape[0], layers_dims, Y.shape[0])

    for i in range(0, num_iterations):

        AL, caches = forward_propagation(X, parameters)
        cost = compute_cost(AL, Y)
        grads = backward_propagation(AL, Y, caches)
        parameters = update_parameters(parameters, grads, learning_rate)

        # 打印代价函数值
        if print_cost and i % 100 == 0:
            print("迭代次数: %i,代价函数值: %f" % (i, cost))
        if print_cost and i % 100 == 0:
            costs.append(cost)

    # 打印代价函数图像
    if print_cost:
        plt.plot(np.squeeze(costs))
        plt.ylabel('cost')
        plt.xlabel('iterations (per tens)')
        plt.title("Learning rate =" + str(learning_rate))
        plt.show()

    return parameters
import matplotlib.pyplot as plt

np.random.seed(1)
X = np.random.randn(3, 5) # 随机生成三个特征,五个样本的输入矩阵 
Y = np.array([[1, 0, 1, 1, 0]]) # 随机生成一个真实的输出结果矩阵 
parameters = initialize_parameters(3, [4], 1) # 随机初始化参数矩阵 
AL, caches = forward_propagation(X, parameters) # 前向传播 
cost = compute_cost(AL, Y) # 计算代价函数 
grads = backward_propagation(AL, Y, caches) # 反向传播 
parameters = update_parameters(parameters, grads, 0.1) # 参数更新

print("AL = " + str(AL))
print("Cost = " + str(cost))
print("grads = " + str(grads))
print("Updated parameters = " + str(parameters))

# 绘制代价函数变化曲线
costs = []
for i in range(1000):
    AL, caches = forward_propagation(X, parameters)
    cost = compute_cost(AL, Y)
    grads = backward_propagation(AL, Y, caches)
    parameters = update_parameters(parameters, grads, 0.1)
    costs.append(cost)
    if i % 100 == 0:
        print("迭代次数: %i,代价函数值: %f" % (i, cost))
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(0.1))
plt.show()
AL = [[0.50037097 0.49966438 0.49999165 0.49988645 0.50016688]]
Cost = 0.6929801704859408
grads = {'dW2': array([[-0.00132582,  0.00726017, -0.00334984,  0.00957421]]), 'db2': array([[-0.09998393]]), 'dW1': array([[-7.54439262e-05, -1.36498952e-03,  5.40864706e-04],
       [-9.28841089e-05, -1.61946803e-03,  6.38971061e-04],
       [ 2.68016294e-04,  4.79458799e-03, -1.89805307e-03],
       [-2.66171334e-04, -4.63875616e-03,  1.82969020e-03]]), 'db1': array([[ 0.00032198],
       [ 0.00038421],
       [-0.00113165],
       [ 0.00110063]])}
Updated parameters = {'W1': array([[ 0.016251  , -0.00598107, -0.0053358 ],
       [-0.0107204 ,  0.00881602, -0.02307928],
       [ 0.01742132, -0.00809153,  0.0033802 ],
       [-0.00246709,  0.01508495, -0.02078438]]), 'b1': array([[-3.21981777e-05],
       [-3.84209780e-05],
       [ 1.13164711e-04],
       [-1.10063132e-04]]), 'W2': array([[-0.00309159, -0.00456656,  0.01167268, -0.01195633]]), 'b2': array([[0.00999839]])}
迭代次数: 0,代价函数值: 0.691971
迭代次数: 100,代价函数值: 0.510630
迭代次数: 200,代价函数值: 0.097315
迭代次数: 300,代价函数值: 0.036653
迭代次数: 400,代价函数值: 0.021111
迭代次数: 500,代价函数值: 0.014502
迭代次数: 600,代价函数值: 0.010934
迭代次数: 700,代价函数值: 0.008725
迭代次数: 800,代价函数值: 0.007234
迭代次数: 900,代价函数值: 0.006164

【深度学习】用NumPy和PyTorch分别实现神经网络的反向传播

使用PyTorch实现

这段代码定义了一个简单的神经网络,并通过随机生成的样本进行了训练。步骤如下:

  1. 首先引入了PyTorch库,这是一个深度学习框架库,包括各种深度学习模块和功能。
  2. 定义了一个名为Net的神经网络模型,它继承了PyTorch的nn.Module类,其中n_input、n_hidden和n_output分别表示输入、隐藏和输出层的节点数。
  3. 在Net中,我们通过nn.Linear()定义了两个全连接层,同时又定义了Sigmoid和Tanh激活函数。
  4. 然后定义了一个train()函数,在该函数中,我们通过nn.BCELoss()定义了二元交叉熵损失函数,并通过optim.SGD()定义了Stochastic Gradient Descent(随机梯度下降)优化器。在每次迭代训练中,我们都将梯度归零optimizer.zero_grad(),计算输出outputs = model(X),计算损失loss = criterion(outputs, Y),通过反向传播计算梯度loss.backward(),更新模型中的参数optimizer.step(),最后打印代价函数loss的值。
  5. 最后我们随机生成5个样本,每个样本包括三个特征,并随机指定一个真实的输出结果矩阵Y。然后初始化一个有一个隐藏层、三个特征和一个输出的神经网络模型Net,并利用train()函数在随机生成的样本上进行了训练。
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
#定义绘图函数
def plot_loss(loss_values): 
    plt.plot(loss_values) 
    plt.xlabel("Epochs") 
    plt.ylabel("Loss") 
    plt.title("Training Loss") 
    plt.show()
# 定义神经网络模型
class Net(nn.Module):
    def __init__(self, n_input, n_hidden, n_output):
        super(Net, self).__init__()
        # 定义两个全连接层
        self.fc1 = nn.Linear(n_input, n_hidden)
        self.fc2 = nn.Linear(n_hidden, n_output)        
        # 定义激活函数
        self.sigmoid = nn.Sigmoid()
        self.tanh = nn.Tanh()
    
    def forward(self, x):
        x = self.tanh(self.fc1(x))   # 使用Tanh激活函数
        x = self.sigmoid(self.fc2(x))    # 使用sigmoid激活函数
        return x

def train(X, Y, model, learning_rate=0.01, num_epochs=3000, print_cost=False):
    # 定义损失函数
    criterion = nn.BCELoss()
    # 定义优化器
    optimizer = optim.SGD(model.parameters(), lr=learning_rate)
    loss_values = []
    for epoch in range(num_epochs):
        optimizer.zero_grad()   # 梯度清零
        outputs = model(X)  # 前向传播得到输出y_hat
        loss = criterion(outputs, Y)    # 计算损失
        loss.backward() # 反向传播求导
        optimizer.step()    # 更新参数
        loss_values.append(loss.item()) # 将损失值加入列表中 
        # 打印训练过程中的损失值
        if print_cost and epoch % 100 == 0:
            print("迭代次数: %i,代价函数值: %f" % (epoch, loss.item()))
    plot_loss(loss_values)

# 测试代码
torch.manual_seed(1)    # 设置随机种子,保证结果可以重现
X = torch.randn(5, 3)   # 随机生成三个特征,五个样本的输入矩阵
Y = torch.tensor([[1], [0], [1], [1], [0]], dtype=torch.float32)    # 随机生成一个真实的输出结果矩阵
model = Net(3, 4, 1)    # 初始化模型,三个特征,一个输出,一个隐藏层
train(X, Y, model, learning_rate=0.1, num_epochs=1000, print_cost=True)   # 利用训练数据进行模型训练,将输出代价函数值
迭代次数: 0,代价函数值: 0.641908
迭代次数: 100,代价函数值: 0.355435
迭代次数: 200,代价函数值: 0.181952
迭代次数: 300,代价函数值: 0.094983
迭代次数: 400,代价函数值: 0.056912
迭代次数: 500,代价函数值: 0.038369
迭代次数: 600,代价函数值: 0.028081
迭代次数: 700,代价函数值: 0.021755
迭代次数: 800,代价函数值: 0.017556
迭代次数: 900,代价函数值: 0.014603

【深度学习】用NumPy和PyTorch分别实现神经网络的反向传播

绘制网络图

from torchviz import make_dot
X = torch.randn(5, 3) # 随机生成三个特征,五个样本的输入矩阵
model = Net(3, 4, 1) # 初始化模型

# 可视化计算图

make_dot(model(X), params=dict(model.named_parameters()))

【深度学习】用NumPy和PyTorch分别实现神经网络的反向传播