一步步手写神经网络

一步步手写神经网络

        这篇博客主要是一步步的手撸一个神经网络出来,相信看完这篇博客,大家会对神经网络有一个比较深刻的认识。看完后,你可能会发现只要会基本的求导,其实bp(backward propagation)并没有想象中的那么难。下面就来一步步手写一个神经网络。我会先拿一个简单的3层神经网络做demo展示下求解神经网络的整体过程,其实主要就是forward propagation(前向传播)和backward propagation(后向传播/逆向传播)这两个过程。
一个简单的神经网络:

这是一个简单的只有三层的神经网络,先来定义一些notation(这个notation沿用ng的):

具体到上面给出的神经网络demo图,这里有:

其实这其中最难确定的是参数w,b,a 的维度,其实看上面图中参数的维度,也能够发现参数w,b,a 的维度:

        还有就是这个demo中隐藏层激活函数使用的是现在最常用的ReLU当做激活函数,输出层因为是个二元分类就使用sigmoid函数作为激活函数(如果是多分类现在最常用的是softmax函数),关于几种激活函数,详见本人另一篇博客:几种常见的激活函数,代价函数则是使用的交叉熵函数,神经网络的交叉熵代价函数为:

接下来正式进入到forward propagation阶段,前向传播的计算过程为:

总结下上面的公式其实能够发现:

下面是backward propagation(bp)阶段,其实就是求导(得到梯度),我会详细的写出求导过程:

W [ 3 ] W^{[3]} W[3] 求导

在求出 W [ 3 ] W^{[3]} W[3] 的同时其实也得到了 d b [ 3 ] db^{[3]} db[3]

W [ 2 ] W^{[2]} W[2]求导

同样能够得到 d b [ 2 ] db^{[2]} db[2]

W [ 1 ] W^{[1]} W[1]求导,得:

d b [ 1 ] db^{[1]} db[1]为:

其实bp的过程也是有规律可循的,根据链式法则,一个变量的导数依赖它的“上家”,通过上面我的 “总结”,应该发现:

        以上就是这个3层神经网络Foward propagation和backward propagation的详细具体过程。并且我们能够发现一些规律,很容易的推广到L层。下面就来一步步手撸一个神经网络,我们在实现的时候一定要注意矩阵的维度,而且要搞清楚矩阵之间到底是乘(线代里矩阵相乘的定义)还是点乘(对应元素相乘)。前向传播的还倒好,bp的向量化实现如下:

其实bp过程就是逆过来,一步接着一步。

下面进入到代码实现阶段:

**|| **forward propagation阶段

其实整个fp的过程就是:linear_forward->ReLU_forward->linear_forward->…

1.首先初始化参数w,b,对于参数w,b的初始化要随机初始化,至于为什么要随机初始化,请参见本人博客:神经网络(neural network)。关于对权重的初始化方法,详见本人另一篇博客:深度学习中神经网络的几种权重初始化方法下面代码中随机初始化参数w时之所以*0.01,目的是:如果我们不乘的话,有可能会把参数初始化为一个比较大的数,这样会导致z=w.T * X + b,z的值有时候会比较大,这样的话,如果你使用sigmoid或者tanH作为激活函数,得到的导数(梯度)会非常的小,这样参数更新的速度会比较慢。

#initialize parameters(w,b)
def initialize_parameters(layer_dims):
	"""
	:param layer_dims: list,每一层单元的个数(维度)
	:return:dictionary,存储参数w1,w2,...,wL,b1,...,bL
	"""
	np.random.seed(3)
	L = len(layer_dims)#the number of layers in the network
	parameters = {}
	for l in range(1,L):
		parameters["W" + str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*0.1
		parameters["b" + str(l)] = np.zeros((layer_dims[l],1))
	return parameters

2.实现线性函数部分:

def linear_forward(x, w, b):
	"""
	:param x:
	:param w:
	:param b:
	:return:
	"""
	z = np.dot(w, x) + b  # 计算z = wx + b
	return z

3.实现激活函数(ReLU和sigmoid):

#implement the activation function(ReLU and sigmoid)
def relu_forward(Z):
	"""
	:param Z: Output of the activation layer
	:return:
	A: output of activation
	"""
	A = np.maximum(0,Z)
	return A

def sigmoid(Z):
	"""
	:param Z: Output of the linear layer
	:return:
	"""
	A = 1 / (1 + np.exp(-Z))
	return A

4.整个Foward propagation的过程:

def forward_propagation(X, parameters):
	"""
	X -- input dataset, of shape (input size, number of examples)
    parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2",...,"WL", "bL"
                    W -- weight matrix of shape (size of current layer, size of previous layer)
                    b -- bias vector of shape (size of current layer,1)
    :return:
	AL: the output of the last Layer(y_predict)
	caches: list, every element is a tuple:(W,b,z,A_pre)
	"""
	L = len(parameters) // 2  # number of layer
	A = X
	caches = []
	# calculate from 1 to L-1 layer
	for l in range(1,L):
		W = parameters["W" + str(l)]
		b = parameters["b" + str(l)]
		#linear forward -> relu forward ->linear forward....
		z = linear_forward(A, W, b)
		caches.append((A, W, b, z))  # 以激活函数为分割,到z认为是这一层的,激活函数的输出值A认为是下一层的输入,划归到下一层。注意cache的位置,要放在relu前面。
		A = relu_forward(z) #relu activation function
	# calculate Lth layer
	WL = parameters["W" + str(L)]
	bL = parameters["b" + str(L)]
	zL = linear_forward(A, WL, bL)
	caches.append((A, WL, bL, zL))
	AL = sigmoid(zL)
	return AL, caches

5.整个forward propagation完后,开始计算代价函数:

#calculate cost function
def compute_cost(AL,Y):
	"""
	:param AL: 最后一层的激活值,即预测值,shape:(1,number of examples)
	:param Y:真实值,shape:(1, number of examples)
	:return:
	"""
	m = Y.shape[1]
	cost = 1. / m * np.nansum(np.multiply(-np.log(AL), Y) +
	                          np.multiply(-np.log(1 - AL), 1 - Y))
	#从数组的形状中删除单维条目,即把shape中为1的维度去掉,比如把[[[2]]]变成2
	cost = np.squeeze(cost)
	return cost

**|| **backward propagation阶段

其实BP过程就是从后往前,一层接着一层,具体过程为 sigmoid_backward(输出层比较特殊一般特殊考虑):ReLU_backward->linear_backward->ReLU_backward->…

1、relu导数:

#derivation of relu
def relu_backward(dA, Z):
	"""
	:param Z: the input of activation function
	:param dA:
	:return:
	"""
	dout = np.multiply(dA, np.int64(Z > 0)) #J对z的求导
	return dout

2、线性部分导数

#derivation of linear
def linear_backward(dZ, cache):
	"""
	:param dZ: Upstream derivative, the shape (n^[l+1],m)
	:param A: input of this layer
	:return:
	"""
	A, W, b, z = cache
	dW = np.dot(dZ, A.T)
	db = np.sum(dZ, axis=1, keepdims=True)
	da = np.dot(W.T, dZ)
	return da, dW, db

3、下面是bp过程:

def backward_propagation(AL, Y, caches):
	"""
	Implement the backward propagation presented in figure 2.
	Arguments:
	X -- input dataset, of shape (input size, number of examples)
	Y -- true "label" vector (containing 0 if cat, 1 if non-cat)
	caches -- caches output from forward_propagation(),(W,b,z,pre_A)

	Returns:
	gradients -- A dictionary with the gradients with respect to dW,db
	"""
	m = Y.shape[1]
	L = len(caches) - 1
	#calculate the Lth layer gradients
	dz = 1. / m * (AL - Y)
	da, dWL, dbL = linear_backward(dz, caches[L])
	gradients = {"dW" + str(L + 1): dWL, "db" + str(L + 1): dbL}

	#calculate from L-1 to 1 layer gradients
	for l in reversed(range(0,L)): # L-1,L-3,....,0
		A, W, b, z = caches[l]
		#ReLu backward -> linear backward
		#relu backward
		dout = relu_backward(da, z)
		#linear backward
		da, dW, db = linear_backward(dout, caches[l])
		# print("========dW" + str(l+1) + "================")
		# print(dW.shape)
		gradients["dW" + str(l+1)] = dW
		gradients["db" + str(l+1)] = db
	return gradients

5、下面就是更新参数w,b:

def update_parameters(parameters, grads, learning_rate):
	"""
	:param parameters: dictionary,  W,b
	:param grads: dW,db
	:param learning_rate: alpha
	:return:
	"""
	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

以上就是整个bp的过程,至此,fp和bp整个流程已经全部写完了,下面再写个主函数,把fp和bp串联下,整个dnn网络就完成了:

def L_layer_model(X, Y, layer_dims, learning_rate, num_iterations):
	"""
	:param X:
	:param Y:
	:param layer_dims:list containing the input size and each layer size
	:param learning_rate:
	:param num_iterations:
	:return:
	parameters:final parameters:(W,b)
	"""
	costs = []
	# initialize parameters
	parameters = initialize_parameters(layer_dims)
	for i in range(0, num_iterations):
		#foward propagation
		AL,caches = forward_propagation(X, parameters)
		# calculate the cost
		cost = compute_cost(AL, Y)
		if i % 1000 == 0:
			print("Cost after iteration {}: {}".format(i, cost))
			costs.append(cost)
		#backward propagation
		grads = backward_propagation(AL, Y, caches)
		#update parameters
		parameters = update_parameters(parameters, grads, learning_rate)
	print('length of cost')
	print(len(costs))
	plt.clf()
	plt.plot(costs)  # o-:圆形
	plt.xlabel("iterations(thousand)")  # 横坐标名字
	plt.ylabel("cost")  # 纵坐标名字
	plt.show()
	return parameters

这样就得到了,这个网络的参数W,b,这样我们就可以用这个参数去做分类了,下面顺手在撸个predict函数吧:

#predict function
def predict(X_test,y_test,parameters):
	"""
	:param X:
	:param y:
	:param parameters:
	:return:
	"""
	m = y_test.shape[1]
	Y_prediction = np.zeros((1, m))
	prob, caches = forward_propagation(X_test,parameters)
	for i in range(prob.shape[1]):
		# Convert probabilities A[0,i] to actual predictions p[0,i]
		if prob[0, i] > 0.5:
			Y_prediction[0, i] = 1
		else:
			Y_prediction[0, i] = 0
	accuracy = 1- np.mean(np.abs(Y_prediction - y_test))
	return accuracy

写个入口函数:

#DNN model
def DNN(X_train, y_train, X_test, y_test, layer_dims, learning_rate= 0.001, num_iterations=30000):
	parameters = L_layer_model(X_train, y_train, layer_dims, learning_rate, num_iterations)
	accuracy = predict(X_test,y_test,parameters)
	return accuracy

至此,整个deep neural network已经写完了,虽然简单,可能代码也写的不够严谨。但对于理解neural network,即forward propagation和backward propagation非常有帮助。拿sklearn中的load_breast_cancer数据集测了下,当隐藏层单元设置为[10,5,1]时,准去率达到了92%,代价函数示意图如下:

p.s:这里另外提一下,ng的在Coursera上的课把每个步骤划分的更加细致,但是我个人认为那样不太好,有点打乱了fp和bp的整体性,这个每个人的口味都不一样。最重要的一点。。。是我觉得ng的relu求导写错了,现在把ng的relu求导代码贴出来,然后把我改正的(我认为是对的)也贴出来,供路过的大侠鉴定下,欢迎留言交流。
ng的relu_backward:

![ng_relu_backward](https://img-blog.csdn.net/20180425233542517)

我改正后的:

![me_back](https://img-blog.csdn.net/20180425233648335)

博客完整的代码已经放到github上了:deep_neural_network
改正版的ngCoursera作业上的代码:deep_neural_network_ng



  • 参考资料:Coursera上ng的《neural network and deep learning》课
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值