当前位置:   article > 正文

python+numpy实现DNN神经网络框架(底层原理)_python ndn

python ndn

前言:

有什么写的不对的地方请大家在评论区指出来,我会及时改正,或是有什么疑问,我也会及时解答

 

1.开始架构之前我先简单介绍一下神经网络

          网络结构是输入层->隐藏层->隐藏层->···->隐藏层->输出层,在每一层中,我会首先计算Z = np.dot(W,A) + b,这叫做【linear_forward】,然后再计算relu(Z) 或者 sigmoid(Z),这叫做【linear_activation_forward】,合并起来就是这一层的计算方法,所以每一层的计算都有这两个步骤,计算误差,然后就开始反向传播(计算出每一层w的导数),最后更新每一层参数信息

你也可以参照下图: 

2.下面开始手写底层代码

2.1 初始化每一层的W,b

       根据上的步骤我们先要把网络架构出来隐藏层层数,宽度,向前传输的公式是Z = np.dot(W,A) + b,我们根据这个公式定义矩阵和W,b,由公式np.dot(W,A)  得到W的形状就必须为(n,X.shape[0])n为该层的宽度,得到的节点结果就是(n,X.shape[1]),矩阵相加第一个维度要必须一样,所以b的形状就为(n,1),得到结果Z,Z经过激活函数变化后不会改变形状依然是(n,Xshape[1]),下一层隐藏层W形状就为(m,n)m为该层的宽度,b的形状为(m,1),依次类推,最后一层要和y的形状一样所以最后一个隐藏层的形状要为(Y.shape[0],上一层宽度),b的形状(Yshape[0],1),知道这样的规律后我们就可以写神经网络了

矩阵乘积如下图

首先,我们创建一个列表来定义深度和宽度,列表的长度就是深度,列表里面的每一个值就是我们要定义的宽度

layers_dims = [X.shape[0],3,4,5,Y.shape[0]]

这个列表的意思就是 有3个隐藏层,3,4,5分别代表每一个隐藏层的宽度,下面就开始初始化我们每一层的W和b值

  1. def initialize_parameters_deep(layers_dims):
  2. """
  3. 此函数是为了初始化多层网络参数而使用的函数。
  4. 参数:
  5. layers_dims - 包含我们网络中每个图层的节点数量的列表
  6. 返回:
  7. parameters - 包含参数“W1”,“b1”,...,“WL”,“bL”的字典:
  8. Wi - 权重矩阵,维度为(layers_dims [i],layers_dims [i-1])
  9. bi - 偏向量,维度为(layers_dims [i],1)
  10. """
  11. import numpy as np
  12. parameters={}
  13. L=len(layers_dims)
  14. for i in range(1,L):
  15. parameters["W"+str(i)]=np.random.normal(size=(layers_dims[i],layers_dims[i-1]))
  16. parameters["b"+str(i)]=np.zeros((layers_dims[i],1))
  17. return parameters

测试一下打印一下W,b的形状看一下对不对

  1. X = np.array([[1,2,3,4],[5,6,7,8]])
  2. Y = np.array([[0,1]])
  3. layers_dims = [X.shape[0],3,4,5,Y.shape[0]]
  4. parameters = initialize_parameters_deep(layers_dims)
  5. L = len(layers_dims)
  6. for i in range(1,L):
  7. print("W",i,"shape:",parameters["W"+str(i)].shape)
  8. print("b",i,"shape:",parameters["b"+str(i)].shape)
  9. 打印结果为:
  10. W1 shape: (3, 2)
  11. b1 shape: (3, 1)
  12. W2 shape: (4, 3)
  13. b2 shape: (4, 1)
  14. W3 shape: (5, 4)
  15. b3 shape: (5, 1)
  16. W4 shape: (1, 5)
  17. b4 shape: (1, 1)

layers_dims大家可以自己改变数值,和长度

2.2 向前传播

      向前传播我们从公式下手比较容易  每一层的的计算公式是一样的  Z= np.dot(W,A_prev) + b  A是前一层的值,W,b是对应当前层的值,A=激活函数(Z) 计算出来的A就是当前层的值,根据公式写代码

激活函数公式入下图

  1. def sigmoid(Z):
  2. return 1/(1+np.exp(-Z))
  3. def linear_activation_forward(A_prev,W,b,activation):
  4. """
  5. 实现LINEAR-> ACTIVATION 这一层的前向传播
  6. 参数:
  7. A_prev - 来自上一层(或输入层)的激活,维度为(上一层的节点数量,示例数)
  8. W - 权重矩阵,numpy数组,维度为(当前层的节点数量,前一层的大小)
  9. b - 偏向量,numpy阵列,维度为(当前层的节点数量,1)
  10. activation - 选择在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
  11. 返回:
  12. A - 激活函数的输出,也称为激活后的值
  13. """
  14. Z = np.dot(W,A_prev)+b
  15. if activation == "relu":
  16. A = np.where(Z>0,Z,0)
  17. elif activation == "sigmoid":
  18. A = sigmoid(Z)
  19. return A

公式定义完了,就可以计算每一层的值一步一步的向前传播了,因为后面要向后传播求导,所以这里求出来的每一层的A我们都要记录一下

  1. def L_model_forward(X,parameters):
  2. """
  3. 实现[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID计算前向传播,也就是多层网络的前向传播,为后面每一层都执行LINEAR和ACTIVATION
  4. 参数:
  5. X - 数据,numpy数组,维度为(输入节点数量,示例数)
  6. parameters - initialize_parameters_deep()的输出
  7. 返回:
  8. AL - 最后的激活值
  9. caches - 包含参数“A1”,“A2”,...,“AL”,”的字典
  10. """
  11. A = X
  12. cache={}
  13. L = len(parameters) // 2
  14. for i in range(1,L):
  15. A = linear_activation_forward(A,parameters["W"+str(i)],parameters["b"+str(i)],"relu")
  16. cache["A"+str(i)]=A
  17. A = linear_activation_forward(A,parameters["W"+str(L)],parameters["b"+str(L)],"sigmoid")
  18. cache["A"+str(L)]=A
  19. return cache

2.3计算成本

我们已经把这模型的前向传播部分完成了,我们需要计算成本(误差),以确定它到底有没有在学习,成本的计算公式如下: 

代码如下

  1. def compute_cost(AL,Y):
  2. """
  3. 成本函数。
  4. 参数:
  5. AL - 与标签预测相对应的概率向量,维度为(1,示例数量)
  6. Y - 标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
  7. 返回:
  8. cost - 交叉熵成本
  9. """
  10. m = Y.shape[1]
  11. cost = -np.sum(np.multiply(np.log(AL),Y) + np.multiply(np.log(1 - AL), 1 - Y)) / m
  12. cost = np.squeeze(cost)
  13. return cost

 

2.4反向传播

反向传播用于计算相对于参数的损失函数的梯度,下面就是求导了,我们一个一个的来求导,先是cost的导数:

激活函数的导数 :

由上面的推导可得第一个倒数信息dZ:

 

之前记录 的每个节点A值现在就要都用上了

A=WA(i-1)-b

dW=dZ*A

db=dZ

  1. def linear_activation_backward(X,parameters,cache):
  2. """
  3. 实现LINEAR-> ACTIVATION层的后向传播。
  4. 参数:
  5. cache - 我们存储的用于有效计算反向传播的值的元组(值为linear_cache,activation_cache)
  6. activation - 要在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
  7. 返回:
  8. dA_prev - 相对于激活(前一层i-1)的成本梯度值,与A_prev维度相同
  9. dW - 相对于W(当前层i)的成本梯度值,与W的维度相同
  10. db - 相对于b(当前层i)的成本梯度值,与b的维度相同
  11. """
  12. grads={}
  13. L=len(cache)
  14. dZ = cache["A"+str(L)] - Y
  15. for i in range(1,L):
  16. dW = (1 / m) * (np.dot(dZ, cache["A"+str(L-i)].T))
  17. db = (1 / m) * (np.sum(dZ, axis=1, keepdims=True))
  18. dZ =(np.dot(parameters["W"+str(L-i+1)].T,dZ))*np.where(cache["A"+str(L-i)] > 0, 1, 0)
  19. grads["dW"+str(L-i)]=dW
  20. grads["db"+str(L-i)]=db
  21. dW = (1 / m) * (np.dot(dZ, X.T))
  22. db = (1 / m) * (np.sum(dZ, axis=1, keepdims=True))
  23. grads["dW"+str(L-i-1)]=dW
  24. grads["db"+str(L-i-1)]=db
  25. return grads

2.5更新参数

我们把向前向后传播都完成了,现在我们就开始更新参数

  1. def update_parameters(parameters, grads, learning_rate):
  2. """
  3. 使用梯度下降更新参数
  4. 参数:
  5. parameters - 包含你的参数的字典
  6. grads - 包含梯度值的字典,是L_model_backward的输出
  7. 返回:
  8. parameters - 包含更新参数的字典
  9. 参数[“W”+ str(i)] = ...
  10. 参数[“b”+ str(i)] = ...
  11. """
  12. L = len(parameters) // 2 #整除
  13. for i in range(L):
  14. parameters["W" + str(i + 1)] = parameters["W" + str(i + 1)] - learning_rate * grads["dW" + str(i)]
  15. parameters["b" + str(i + 1)] = parameters["b" + str(i + 1)] - learning_rate * grads["db" + str(i )]
  16. return parameters

3.已经全部推完了

 

我把它封装成了一个class 

  1. #!usr/bin/env python
  2. #-*- coding:utf-8 _*-
  3. import numpy as np
  4. class Net(object):
  5. def __init__(self, X, Y, layers_dims, learning_rate, num_iterations, num_display, num_learn_rate=None,
  6. learn_decline_rate=0.1,lambd=0.001):
  7. """
  8. X - 训练参数
  9. Y - 目标值
  10. layers_dims - 包含我们网络中每个图层的节点数量的列表 ,例如 [X.shape[0],3,5,Y.shape[0]] 两个隐藏层 宽度分别是3,5
  11. learning_rate - 学习率
  12. num_iterations - 迭代次数
  13. num_display - 每迭代多少次显示cost
  14. num_learn_rate - 每迭代多少次下降学习,默认None,不下降
  15. learn_decline_rate - 学习率下降比例 默认0.1
  16. lambd - 正则权重,默认0.001
  17. parameters - 包含参数“W1”,“b1”,...,“WL”,“bL”的字典:
  18. Wi - 权重矩阵,维度为(layers_dims [i],layers_dims [i-1])
  19. bi - 偏向量,维度为(layers_dims [i],1)
  20. caches - 包含参数 "A1","A2",...,"AL"的字典
  21. Ai - 每个节点值
  22. grads - 包含参数“dW1”,“db1”,...,“dWL”,“dbL”的字典:
  23. dWi - 相对于Wi的成本梯度值,与Wi的维度相同
  24. dbi - 相对于bi的成本梯度值,与bi的维度相同
  25. L - 网络结构的深度
  26. """
  27. self.X = X
  28. self.Y = Y
  29. self.m = self.Y.shape[1]
  30. self.learning_rate = learning_rate
  31. self.layers_dims = layers_dims
  32. self.L = len(self.layers_dims)
  33. self.num_iterations = num_iterations
  34. self.num_display = num_display
  35. self.num_learn_rate = num_learn_rate
  36. self.learn_decline_rate = learn_decline_rate
  37. self.parameters = {}
  38. self.cache = {}
  39. self.grads = {}
  40. self.cost = 0.0
  41. self.lambd = lambd
  42. def softmax(self,Z):
  43. Z = Z - np.max(Z)
  44. exp_Z = np.exp(Z)
  45. softmax_Z = exp_Z / np.sum(exp_Z)
  46. return softmax_Z
  47. def sigmoid(self, Z):
  48. return np.float32(1 / (1 + np.exp(-Z)))
  49. def initialize_parameters_deep(self):
  50. """
  51. 此函数是为了初始化多层网络参数而使用的函数。
  52. """
  53. for i in range(1, self.L):
  54. self.parameters["W" + str(i)] =np.float32 (np.random.normal(size=(self.layers_dims[i], self.layers_dims[i - 1])))
  55. self.parameters["b" + str(i)] =np.float32 (np.zeros((self.layers_dims[i], 1)))
  56. def linear_activation_forward(self, A_prev, W, b, activation):
  57. """
  58. 实现LINEAR-> ACTIVATION 这一层的前向传播
  59. 参数:
  60. A_prev - 来自上一层(或输入层)的激活,维度为(上一层的节点数量,示例数)
  61. W - 权重矩阵,numpy数组,维度为(当前层的节点数量,前一层的大小)
  62. b - 偏向量,numpy阵列,维度为(当前层的节点数量,1)
  63. activation - 选择在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
  64. 返回:
  65. A - 激活函数的输出,也称为激活后的值
  66. """
  67. Z = np.dot(W, A_prev) + b
  68. assert (Z.shape == (W.shape[0], A_prev.shape[1]))
  69. if activation == "relu" :
  70. A = np.where(Z > 0, Z, 0)
  71. elif activation == "sigmoid" :
  72. A = self.sigmoid(Z)
  73. elif activation == "tanh" :
  74. A = np.tanh(Z)
  75. elif activation == "softmax" :
  76. A = self.softmax_x(Z)
  77. return A
  78. def L_model_forward(self):
  79. """
  80. 实现[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID计算前向传播,也就是多层网络的前向传播,为后面每一层都执行LINEAR和ACTIVATION
  81. """
  82. A = self.X
  83. for i in range(1, self.L - 1):
  84. A = self.linear_activation_forward(A, self.parameters["W" + str(i)], self.parameters["b" + str(i)], "relu")
  85. self.cache["A" + str(i)] = np.float32(A)
  86. A = self.linear_activation_forward(A, self.parameters["W" + str(self.L - 1)],
  87. self.parameters["b" + str(self.L - 1)], "softmax")
  88. self.cache["A" + str(self.L - 1)] = np.float32(A)
  89. def regularization_2(self):
  90. L2_regularization_cost = 0.0
  91. for i in range(1,self.L):
  92. L2_regularization_cost += np.nansum(np.square(self.parameters["W"+str(i)]))
  93. return L2_regularization_cost
  94. def pre_model_forward(self, per_X):
  95. """
  96. 实现[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID计算前向传播,也就是多层网络的前向传播,为后面每一层都执行LINEAR和ACTIVATION
  97. """
  98. A = per_X
  99. for i in range(1, self.L - 1):
  100. A = self.linear_activation_forward(A, self.parameters["W" + str(i)], self.parameters["b" + str(i)], "relu")
  101. self.cache["A" + str(i)] = np.float32(A)
  102. A = self.linear_activation_forward(A, self.parameters["W" + str(self.L - 1)],
  103. self.parameters["b" + str(self.L - 1)], "sigmoid")
  104. self.cache["A" + str(self.L - 1)] = np.float32(A)
  105. def compute_cost(self):
  106. """
  107. 实施定义的成本函数。
  108. 返回:
  109. cost - 交叉熵成本
  110. """
  111. m = self.Y.shape[1]
  112. # self.cost = -np.sum(self.Y * np.log(self.cache["A" + str(self.L - 1)]) + (1 - self.Y) * np.log(1 - self.cache["A" + str(self.L - 1)])) / m
  113. self.cost = (-np.nansum(np.multiply(np.log(self.cache["A" + str(self.L - 1)]),self.Y)\
  114. + np.multiply(np.log(1 - self.cache["A" + str(self.L - 1)]), 1 - self.Y)) / m)
  115. self.cost = np.squeeze(self.cost)
  116. L2_regularization_cost = self.regularization_2()
  117. self.cost += self.lambd * L2_regularization_cost / (2*m)
  118. self.cost = np.float32(self.cost)
  119. def linear_activation_backward(self):
  120. """
  121. 实现LINEAR-> ACTIVATION层的后向传播。
  122. """
  123. m = self.Y.shape[1]
  124. dZ = self.cache["A" + str(self.L - 1)] - self.Y
  125. for i in range(1, self.L - 1):
  126. dW = (1 / self.m) * (np.dot(dZ, self.cache["A" + str(self.L - i - 1)].T)) \
  127. + ((self.lambd*self.parameters["W" + str(self.L - i )])/m)
  128. db = (1 / self.m) * (np.sum(dZ, axis=1, keepdims=True))
  129. dZ = (np.dot(self.parameters["W" + str(self.L - i)].T, dZ)) * np.where( \
  130. self.cache["A" + str(self.L - i - 1)] > 0, 1, 0)
  131. self.grads["dW" + str(self.L - i - 1)] = np.float32(dW)
  132. self.grads["db" + str(self.L - i - 1)] = np.float32(db)
  133. dW = (1 / self.m) * (np.dot(dZ.astype(np.float32), self.X.T)) \
  134. + ((self.lambd*self.parameters["W" + str(self.L - i - 1)])/m)
  135. db = (1 / self.m) * (np.sum(dZ.astype(np.float32), axis=1, keepdims=True))
  136. self.grads["dW" + str(self.L - i - 2)] = np.float32(dW)
  137. self.grads["db" + str(self.L - i - 2)] = np.float32(db)
  138. def update_parameters(self):
  139. """
  140. 使用梯度下降更新参数
  141. """
  142. L = len(self.parameters) // 2 # 整除
  143. for i in range(L):
  144. self.parameters["W" + str(i + 1)] = self.parameters["W" + str(i + 1)].astype(np.float32) - self.learning_rate * self.grads[
  145. "dW" + str(i)]
  146. self.parameters["b" + str(i + 1)] = self.parameters["b" + str(i + 1)].astype(np.float32) - self.learning_rate * self.grads[
  147. "db" + str(i)]
  148. def run(self):
  149. self.initialize_parameters_deep()
  150. for j in range(self.num_iterations + 1):
  151. self.L_model_forward()
  152. self.compute_cost()
  153. self.linear_activation_backward()
  154. self.update_parameters()
  155. if self.num_learn_rate and j % self.num_learn_rate == 0 and j:
  156. print("当前学习率:",self.learning_rate)
  157. self.learning_rate -= np.multiply(self.learning_rate , self.learn_decline_rate)
  158. print("下降后学习率:",self.learning_rate)
  159. if j % self.num_display == 0:
  160. print("第", j, "次迭代,cost值为:" + str(self.cost))
  161. def predict(self, per_X):
  162. """
  163. 返回
  164. predictions - 我们模型预测的向量
  165. """
  166. self.pre_model_forward(per_X)
  167. self.predictions = np.round(self.cache["A" + str(self.L - 1)])
  168. return self.predictions

有什么不对的地方还请多多指正

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/523269
推荐阅读
相关标签
  

闽ICP备14008679号