手撕反向传播¶
约 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()
2024-11-25 21:14:08 2024-11-25 21:19:08