1.神经网络中的参数
假设神经网络共有L层,m为样本数量,n[l]代表网络中第l层的节点数,w[l]和b[l]为第l层的参数,X为输入,Y为输出。则各参数的维度为:
w[l]:(n[l],n[l-1]); b[l] :(n[l],1); X:(n[1],m); Y:(n[L],m); Z[l],A[l]:(n[l],m)
其中Z[l]、A[l]的计算如下(A[0]即为X):
Z[l] = w[l]A[l-1] + b[l]
A[l] = g(Z[l] )
其中g(z)函数为激活函数,常用的激活函数有sigmoid函数、tanh函数、ReLU函数、Leaky ReLU函数,目前隐藏层一般使用ReLU函数或Leaky ReLU函数,sigmoid函数和tanh函数在z值较大时斜率趋近于0,会拖慢梯度下降法,所以不推荐使用(tanh函数相较于sigmoid函数表现好一些),除非是解决二元分类问题,才会在输出层使用sigmoid函数。
w参数在初始化时不可初始化为0,可以采用随机数进行初始化,例如在Python中,可以对w[l]进行如下初始化:np.random.randn(n[l], n[l-1]) * 0.01。b参数可以直接初始化为0。
2.梯度下降法
loss function(损失函数)和cost function(成本函数):损失函数是对于一个样本来说,预测的结果与真实的结果之间的差值,成本函数则是对于所有的样本来说,对应的误差值。它们的求取公式如下:
L(y^,y) = -(ylogy^ + (1-y)log(1-y^)),其中y^为网络中最后一层的输出,也即A[L]
对式子中的w,b等参数进行求导,得到以下的式子:
dZ[l] = dA[l] * g'(Z[l]),其中*表示矩阵中逐个元素相乘,g'(Z[l])是对激活函数进行求导
dw[l] = (dZ[l]A[l-1]T)/m,这里是矩阵相乘,A[l-1]T代表A[l-1]矩阵的转置矩阵
db[l] = np.sum(dZ[l],axis=1,keepdims=True)/m,其实就是对矩阵dZ[l]每行相加求和,之后除以m,得到列数为1的矩阵
dA[l-1] = w[l]TdZ[l],矩阵相乘
其中最初始的dA[L] = -(np.divide(Y,AL) - np.divide(1-Y,1-AL))。该式子运用了微积分知识,不需要深入理解该公式的推导。
关于a = g(z),g'(z])为函数的导数。例如当g为relu函数时,在z大于时,其值为1;否则,为0;当g为sigmoid函数时,求导为a-a2;当g为tanh函数时,求导为1-a2。详情可见下一部分的Python代码。
下面进行参数的更新:
w = w - learning_rate*dw
b = b - learning_rate*db
3.正则化
为了防止神经网络在训练过程中发生过拟合,需要进行正则化操作。下面先简单介绍下过拟合、欠拟合。
过拟合:也称高方差(high variance),具体表现为测试集误差相较于训练集误差来说,误差较大。针对过拟合常用的解决方法有:1)使用更多的训练数据;2)进行正则化操作;3)尝试使用更合适的神经网络架构。
欠拟合:也称高偏差(high bias),具体表现为无论是对测试集来说还是训练集来说,误差都很大。针对欠拟合常用的解决方法有:1)尝试使用更大的神经网络(例如,增加隐藏层层数、增加层中的节点数等);2)尝试使用新的神经网络架构。
上面的图片很直观地表现出了过拟合、欠拟合的特点。下面介绍下解决过拟合常用的一种方法:正则化,这里介绍两种正则化方法:1)L2正则化;2)Dropout正则化。
(1)L2正则化
L2正则化是在原来的cost function后面加上了一个正则化项:
cost function相应变成了(其中L表示神经网络的层数,没有把输入层算进去):
由于cost function发生了变化,则back propagation中的dw也发生改变:
dw[l] = (dZ[l]A[l-1]T)/m + (lambda/m)w[l]
lambada是一个超参数,需要不断尝试才能找到效果最好的值。
(2)Dropout正则化
Dropout正则化方法在训练过程中随机地忽略一些神经元,因为是随机的,所以对于不同的训练样本,忽略的神经单元也不同。这么做的效果就是,网络模型对神经元特定的权重不那么敏感。这反过来又提升了模型的泛化能力,不容易对训练数据过拟合。具体做法为:
1)设定一个概率阈值keep_prob,代表神经网络中网络节点被保留的概率,例如设keep_prob=0.8,则表示节点被忽略的概率为0.2,被保留的概率为0.8;
2)神经网络每一层都设置一个对应的过滤矩阵D,用来随机忽略该层的节点。对矩阵Dx进行初始化,它对应神经网络的第x层,Dx中元素的值域为[0,1),神经网络第x层的输出为Ax。用Python实现即,Dx = np.random.rand(Ax.shape[0], Ax.shape[1]);
3)对Dx中的元素,把小于0.8的置为1,否则置为0。用Python实现即,Dx = Dx<keep_prob;
4)随机过滤神经网络中x层的节点,即把该节点的输出结果忽略掉。用Python实现:Ax = np.multiply(Ax, Dx);
5)弥补Ax中被忽略的元素,试图使Ax的期望值保持不变:Ax = Ax/keep_prob;
6)在back propagation中dAx也要乘以Dx,且最后也要除以keep_prob。
以上即是Dropout方法的执行过程,注意:在测试阶段不使用Dropout,并且神经网络中不同层的keep_prob值可以不同。
Dropout正则化方法最常使用在计算机视觉中。
谈一点关于数据集的东西。一般把数据集分为训练集、验证集和测试集三类。训练集就是用来训练模型的;验证集则是在训练出的模型的基础上调整一些参数,以期得到最优的模型;测试集用来检验“最优模型”的性能。一般训练集占到总数据集的60%,验证集和测试集各占20%。不过在数据集很大时,比如已经达到了百万级别,训练集可以占到98%,99%甚至更高。有时数据集中没有测试集,这也是可以的,测试集的主要目的是对最终选定的神经网络做出无偏估计,但如果不需要无偏估计,也可以不设置测试集。所以如果只有验证集,没有测试集,我们要做的就是在训练集上训练,尝试不同的模型框架,在验证集上评估这些模型,然后迭代出适用的模型。不过人们经常在只有训练集和验证集的情况下,把验证集称作测试集,但在实际应用中,人们只是把所谓的“测试集”当成简单交叉验证集使用。
3.提升神经网络训练速度
通过对输入进行归一化,以及合理地初始化w的值,可以提升神经网络的训练速度。
(1)归一化
零均值化:
归一化方差:
其原理可以同过以下两张图来解释。其中左边的那张图是归一化之前的,右边那张图是归一化之后的,中心的圆点即是cost function最小的地方。
注意:要使用相同的均值和标准差来归一化测试集。
(2)初始化w
给一个好的起点,也能使神经网络尽快训练出好的模型。当激活函数为relu时,可以这样初始化w:
而当激活函数为tanh时,后面相乘的那一项可以是:
4.编程示例
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
def sigmoid(z):
s = 1/(1+np.exp(-z))
return s, z
def relu(z):
s = np.maximum(z, 0)
return s, z
def sigmoid_derivative(x):
s = 1/(1+np.exp(-x))
dz = s - np.power(s, 2)
return dz
def relu_derivative(x):
dz = (x > 0) * 1 + (x <= 0) * 0
return dz
def init_parameters(layer_dims):
L = len(layer_dims)
parameters = {}
for l in range(1, L):
parameters["W" + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1])*0.01
parameters["b" + str(l)] = np.zeros((layer_dims[l], 1))
return parameters
def linear_forword(A, W, b):
Z = np.dot(W, A) + b
cache = (A, W, b) #保存A, W和b
return Z, cache
def linear_activation_forword(A_prev, W, b, activation):
if activation == "relu":
Z, linear_cache = linear_forword(A_prev, W, b)
A, activation_cache = relu(Z)
elif activation == "sigmoid":
Z, linear_cache = linear_forword(A_prev, W, b)
A, activation_cache = sigmoid(Z)
cache = (linear_cache, activation_cache) #前一个保存A_prev,W,b;后一个保存z
return A, cache
def L_model_forward(X, parameters):
caches = []
A = X
L = len(parameters) // 2
for l in range(1, L):
A_prev = A
A, cache = linear_activation_forword(A_prev, parameters['W'+str(l)], parameters['b'+str(l)], 'relu')
caches.append(cache)
AL, cache = linear_activation_forword(A, parameters['W'+str(L)], parameters['b'+str(L)], 'sigmoid')
caches.append(cache)
return AL, caches
def compute_cost(AL, Y):
m = Y.shape[1]
cost = -(np.dot(Y, np.log(AL).T) + np.dot(1-Y, np.log(1-AL).T))/m
cost = np.squeeze(cost)
return cost
def linear_backward(dZ, cache):
A_prev, W, b = cache
m = A_prev.shape[1]
dW = np.dot(dZ, A_prev.T)/m
db = np.sum(dZ, axis=1, keepdims=True)/m
dA_prev = np.dot(W.T, dZ)
return dA_prev, dW, db
def linear_activation_backward(dA, cache, activation):
linear_cache, activation_cache = cache
if activation == "sigmoid":
dZ = dA * sigmoid_derivative(activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
elif activation == "relu":
dZ = dA * relu_derivative(activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
return dA_prev, dW, db
def L_model_backward(AL, Y, caches):
grads = {}
L = len(caches) #神经网络层数
m = AL.shape[1]
Y = Y.reshape(AL.shape)
dAL = -(np.divide(Y,AL) - np.divide(1-Y,1-AL))
current_cache = caches[L-1]
grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, "sigmoid")
for l in reversed(range(L-1)):
current_cache = caches[l]
grads["dA" + str(l+1)], grads["dW" + str(l+1)], grads["db" + str(l+1)] = linear_activation_backward(grads["dA" + str(l+2)], current_cache, "relu")
return grads
def update_parameters(parameters, grads, learning_rate):
L = len(parameters) // 2
for l in reversed(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
#主函数
tmp=open('ex2data2.txt').read()
data=tmp.replace(',',' ')
data=data.replace('\n',' ')
#data=data.strip('\n')
data=data.split(' ')
data = [float(x) for x in data ]
data = np.array(data)
data = data.reshape(-1,3)
#print(data);
X = data[:,0:2]
Y = data[:,2]
Y = Y.reshape(118,1)
X = X.T
Y = Y.T
layers_dims = [2, 5, 1] # 5-layer model
learning_rate = 0.5
parameters = init_parameters(layers_dims)
for i in range(0, 5000):
AL, caches = L_model_forward(X, parameters)
cost = compute_cost(AL, Y)
grads = L_model_backward(AL, Y, caches)
parameters = update_parameters(parameters, grads, learning_rate)
print(cost)