Skip to content

手撕反向传播

约 8 个字 261 行代码 预计阅读时间 3 分钟

Python
import numpy as np
import matplotlib.pyplot as plt
def init_parameters(layers_dim):
    L = len(layers_dim)
    parameters ={}
    for i in range(1,L):
        parameters["w"+str(i)] = np.random.random([layers_dim[i],layers_dim[i-1]])
        parameters["b"+str(i)] = np.zeros((layers_dim[i],1))
    return parameters

def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
        return sigmoid(z) * (1-sigmoid(z))

def forward(x,parameters):
    a = []
    z = []
    caches = {}
    a.append(x)
    z.append(x)
    layers = len(parameters)//2
    for i in range(1,layers):
        z_temp =parameters["w"+str(i)].dot(x) + parameters["b"+str(i)]
        z.append(z_temp)
        a.append(sigmoid(z_temp))
    z_temp = parameters["w"+str(layers)].dot(a[layers-1]) + parameters["b"+str(layers)]
    z.append(z_temp)
    a.append(z_temp)

    caches["z"] = z
    caches["a"] = a    
    return  caches,a[layers]

def backward(parameters,caches,al,y):
    layers = len(parameters)//2
    grades = {}
    m = y.shape[1]
    grades["dz"+str(layers)] = al - y
    grades["dw"+str(layers)] = grades["dz"+str(layers)].dot(caches["a"][layers-1].T) /m
    grades["db"+str(layers)] = np.sum(grades["dz"+str(layers)],axis = 1,keepdims = True) /m
    for i in reversed(range(1,layers)):
        grades["dz"+str(i)] = parameters["w"+str(i+1)].T.dot(grades["dz"+str(i+1)]) * sigmoid_prime(caches["z"][i])
        grades["dw"+str(i)] = grades["dz"+str(i)].dot(caches["a"][i-1].T)/m
        grades["db"+str(i)] = np.sum(grades["dz"+str(i)],axis = 1,keepdims = True) /m
    return grades   

def update_grades(parameters,grades,learning_rate):
    layers = len(parameters)//2
    for i in range(1,layers+1):
        parameters["w"+str(i)] -= learning_rate * grades["dw"+str(i)]
        parameters["b"+str(i)] -= learning_rate * grades["db"+str(i)]
    return parameters

def compute_loss(al,y):
    return np.mean(np.square(al-y))

def load_data():
    x = np.arange(0.0,1.0,0.01)
    y =20* np.sin(2*np.pi*x)
    plt.scatter(x,y)
    return x,y

x,y = load_data()
x = x.reshape(1,100)
y = y.reshape(1,100)
plt.scatter(x,y)
parameters = init_parameters([1,25,1])
al = 0
for i in range(4000):
    caches,al = forward(x, parameters)
    grades = backward(parameters, caches, al, y)
    parameters = update_grades(parameters, grades, learning_rate= 0.3)
    if i %100 ==0:
        print(compute_loss(al, y))
plt.scatter(x,al)
plt.show()

注释

Python
import numpy as np
import matplotlib.pyplot as plt
def init_parameters(layers_dim):
    # 定义一个名为 init_parameters 的函数,
    # 它接收一个参数 layers_dim,这是一个列表,包含了每一层的神经元数量。
    # print(layers_dim)  # [1, 25, 1] 
    # 输入层有 1 个神经元,隐藏层有 25 个神经元,输出层有 1 个神经元
    L = len(layers_dim) # 获取层的数量
    parameters ={} # 初始化一个空字典,用于存储每一层的参数
    for i in range(1,L): # 从第1层开始遍历到倒数第二层
        # 初始化权重 w1 w2  第一层输入层 不初始化
        '''
        - parameters["w"+str(i)] 初始化权重矩阵,形状为 [layers_dim[i], layers_dim[i-1]]
          这表示第 i 层的权重矩阵的形状,其中 layers_dim[i] 是第 i 层的神经元数量,layers_dim[i-1] 是前一层的神经元数量。
        - parameters["b"+str(i)] 初始化偏置项,形状为 [layers_dim[i], 1]
          这表示第 i 层的偏置项的形状,其中 layers_dim[i] 是第 i 层的神经元数量。
        '''
        parameters["w"+str(i)] = np.random.random([layers_dim[i],
                                                   layers_dim[i-1]])
        # 初始化偏置项 b1 b2 第0层输入层 不初始化
        parameters["b"+str(i)] = np.zeros((layers_dim[i],1))
    # 返回包含所有参数的字典
    # print(parameters['w1'].shape)  (25, 1)
    '''
        parameters['w1'].shape 是 (25, 1):
        表示从输入层到第 1 层的权重矩阵的形状。
        输入层有 1 个神经元,第 1 层有 25 个神经元,
        因此权重矩阵的形状是 (25, 1)。
    '''
    # print(parameters['w2'].shape)  (1, 25)
    '''
        parameters['w2'].shape 是 (1, 25):
        表示从第 1 层到输出层的权重矩阵的形状。
        第 1 层有 25 个神经元,输出层有 1 个神经元,
        因此权重矩阵的形状是 (1, 25)。
    '''
    # print(parameters['b1'].shape)  (25, 1)
    '''
        parameters['b1'].shape 是 (25, 1):
        表示第 1 层的偏置项的形状。
        第 1 层有 25 个神经元,
        因此偏置项的形状是 (25, 1)。
    '''
    # print(parameters['b2'].shape)  (1, 1)
    '''
        parameters['b2'].shape 是 (1, 1):
        表示输出层的偏置项的形状。
        输出层有 1 个神经元,
        因此偏置项的形状是 (1, 1)。'''

    '''
        权重矩阵的形状是 [当前层的神经元数量, 前一层的神经元数量]。
        偏置项的形状是 [当前层的神经元数量, 1]。'''

    return parameters


def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
        return sigmoid(z) * (1-sigmoid(z))

def forward(x,parameters):
    # forward 函数接收两个参数:输入数据 x 和神经网络的参数 parameters
    # a 和 z 是两个列表,分别用于存储每一层的激活值和线性变换值
    # caches 是一个字典,用于存储 a 和 z 列表,以便在反向传播时使用
    # a.append(x) 和 z.append(x) 将输入数据 x 添加到 a 和 z 列表中,作为第 0 层的激活值和线性变换值
    # layers 计算神经网络的层数,假设 parameters 字典中包含每一层的权重和偏置项,因此层数为 len(parameters) // 2
    a = []
    z = []
    caches = {}
    a.append(x)
    z.append(x)
    layers = len(parameters)//2  
    # 因为即有w又有b,所以除以2  len(parameters) = 4
    # print(layers)  # 2
    for i in range(1,layers):
        # 这段代码遍历从第 1 层到倒数第二层的所有层
        # 第0层输入层 不进行计算
        # 如果是3层的话,第0层 input  第1层 w1、b1  第2层 w2、b2
        # 所以这个 for循环 遍历不到 输出层
        z_temp =parameters["w"+str(i)].dot(x) + parameters["b"+str(i)]
        # print("w"+str(i),"b"+str(i))  # w1 b1
        z.append(z_temp)
        a.append(sigmoid(z_temp))
    z_temp = parameters["w"+str(layers)].dot(a[layers-1]) + parameters["b"+str(layers)]
    # 这边 只有3层,可以直接写,隐含层和输入层点乘;最后一层和前一层点乘
    # 如果再增加一层,这个代码是有点问题的:parameters["w"+str(i)].dot(x)
    # 如果很多层 通用的话 应该是.dot(a[layers-1])
    z.append(z_temp)
    a.append(z_temp) 
    # 最后一层的激活值直接使用线性变换值 z_temp,不使用 sigmoid 激活函数。
    # 将 z_temp 添加到 a 列表中。

    caches["z"] = z
    caches["a"] = a 
    # 将 z 和 a 列表存储在 caches 字典中,键分别为 "z" 和 "a"。
    # 返回 caches 字典和最后一层的激活值 a[layers]。
    return  caches,a[layers]

def backward(parameters,caches,al,y):
    # backward 函数接收四个参数:神经网络的参数 parameters、
    # 前向传播的缓存 caches、
    # 前向传播的输出 al 和真实标签 y。
    layers = len(parameters)//2
    # layers 计算神经网络的层数,
    # 假设 parameters 字典中包含每一层的权重和偏置项,
    # 因此层数为 len(parameters) // 2。
    grades = {}
    # grades 是一个空字典,用于存储每一层的梯度。
    m = y.shape[1]
    # print(y.shape) (1, 100)
    # m 是样本数量,即 y 的列数。
    grades["dz"+str(layers)] = al - y
    # print("dz"+str(layers))  dz2
    # al 是前向传播得到的输出层的激活值(预测值)。
    # y 是真实标签。
    # dz 表示输出层的误差,计算公式为 dz = al - y。
    # 这个公式表示预测值与真实值之间的差异,即误差。
    grades["dw"+str(layers)] = grades["dz"+str(layers)].dot(caches["a"][layers-1].T) /m
    # print("dw"+str(layers)) dw2
    # 计算输出层的权重梯度 dw:
    # grades["dz" + str(layers)] 是输出层的误差。
    # caches["a"][layers - 1] 是前一层的激活值。
    # m 是样本数量
    # dw 表示输出层的权重梯度,计算公式为 dw = dz.dot(a_prev.T) / m,
    # 其中 a_prev 是前一层的激活值。
    # 这个公式表示误差与前一层激活值的点积,然后除以样本数量,得到权重的平均梯度。
    grades["db"+str(layers)] = np.sum(grades["dz"+str(layers)],axis = 1,keepdims = True) /m
    # print("db"+str(layers))  db2
    # 计算输出层的偏置梯度 db
    # 反向传播 从输出层开始
    # grades["dz" + str(layers)] 是输出层的误差。
    # np.sum(grades["dz" + str(layers)], axis=1, keepdims=True) 
    # 计算误差在样本维度上的总和。
    # m 是样本数量。
    # db 表示输出层的偏置梯度,
    # 计算公式为 db = np.sum(dz, axis=1, keepdims=True) / m。
    # 这个公式表示误差在样本维度上的平均值,得到偏置的平均梯度。
    # dz 表示输出层的误差,计算公式为 dz = al - y。
    # dw 表示输出层的权重梯度,计算公式为 dw = dz.dot(a_prev.T) / m。
    # db 表示输出层的偏置梯度,计算公式为 db = np.sum(dz, axis=1, keepdims=True) / m。
    # 这些梯度用于更新神经网络的参数,以最小化损失函数
    for i in reversed(range(1,layers)):
        grades["dz"+str(i)] = parameters["w"+str(i+1)].T.dot(grades["dz"+str(i+1)]) * sigmoid_prime(caches["z"][i])
        grades["dw"+str(i)] = grades["dz"+str(i)].dot(caches["a"][i-1].T)/m
        grades["db"+str(i)] = np.sum(grades["dz"+str(i)],axis = 1,keepdims = True) /m
    # 返回包含所有梯度的字典 grades
    return grades  


def update_grades(parameters,grades,learning_rate):
    layers = len(parameters)//2
    for i in range(1,layers+1):
        parameters["w"+str(i)] -= learning_rate * grades["dw"+str(i)]
        parameters["b"+str(i)] -= learning_rate * grades["db"+str(i)]
    return parameters

def compute_loss(al,y):
    return np.mean(np.square(al-y))

def load_data():
    x = np.arange(0.0,1.0,0.01)
    y =20* np.sin(2*np.pi*x)
    plt.scatter(x,y)
    return x,y

x,y = load_data()
x = x.reshape(1,100)
y = y.reshape(1,100)
plt.scatter(x,y)
parameters = init_parameters([1,25,1])
al = 0
# for i in range(4000):
for i in range(1):
    caches,al = forward(x, parameters)
    grades = backward(parameters, caches, al, y)
    parameters = update_grades(parameters, grades, learning_rate= 0.3)
    # if i %100 ==0:
    #     print(compute_loss(al, y))
# plt.scatter(x,al)
# plt.show()
add_circle2024-11-25 21:14:08update2024-11-25 21:19:08