单神经元预测猫准确率为70%,实际上这个效果很一般,数据集的数据都是比较好的,类似这种:
(我表情包随便截图的)
回顾
单神经元的构成:
1)传播函数,由输入x、偏置w、阈值b计算出a
2)激活函数,将a映射到0~1之间的结果y,可理解为(是、否)的概率
3)反向传播函数,通过y、label计算出dw、db(用以更新w和b)
4)损失函数,计算y与label间的误差
浅层神经网络的构成:
在浅层神经网络中,主要也是实现了这四个函数,区别只是在输入与输出间多了一层隐藏层。
以反向传播为例:
dZ2= A2 - Y
dW2 = (1 / m) * np.dot(dZ2, A1.T)
db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1 - np.power(A1, 2))
dW1 = (1 / m) * np.dot(dZ1, X.T)
db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)
浅层神经网络有两层
单神经元只有一层:
可以看出基本一样,对于深层网络加个for
构建深层神经网络模型
随机初始化参数
# 该函数用于初始化所有层的参数w和b
def initialize_parameters_deep(layer_dims):
np.random.seed(1)
parameters = {}
L = len(layer_dims) # 获取神经网络总共有几层
# 遍历每一层,为每一层的W和b进行初始化
for l in range(1, L):
# 构建并随机初始化该层的W
parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l - 1]) / np.sqrt(layer_dims[l-1])
# 构建并初始化b
parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
return parameters
利用上面的循环,我们就可以为任意层数的神经网络进行参数初始化,只要我们提供每一层的神经元个数就可以了。
layer_dims包含了每层神经元个数,如[10,5,4,1]表示是一个三层的网络,第一层5个神经元、第二层4个神经元、第三层1个神经元。注意输入层是不算一层的
测试:
parameters = initialize_parameters_deep([5,4,3])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
前向传播
def linear_forward(A, W, b):
Z = np.dot(W, A) + b
cache = (A, W, b)
return Z, cache
自定义一个sigmoid函数和relu函数用于该层选择什么激活。
def linear_activation_forward(A_prev, W, b, activation):
Z, linear_cache = linear_forward(A_prev, W, b)
if activation == "sigmoid":
A = sigmoid(Z)
elif activation == "relu":
A = relu(Z)
cache = (linear_cache, Z) # 缓存一些变量,后面的反向传播会用到它们
return A, cache
前向传播
这个函数构建了一个完整的前向传播过程。这个前向传播一共有L层,前面的L-1层用的激活函数是relu,最后一层使用sigmoid。
def L_model_forward(X, parameters):
caches = []
A = X
# 获取参数列表的长度,这个长度的一半就是神经网络的层数。
# 为什么是一半呢?因为列表是这样的[w1,b1,w2,b2...wl,bl],里面的w1和b1代表了一层
L = len(parameters) // 2
# 循环L-1次,即进行L-1步前向传播,每一步使用的激活函数都是relu
for l in range(1, L):
A_prev = A
A, cache = linear_activation_forward(A_prev,
parameters['W' + str(l)],
parameters['b' + str(l)],
activation='relu')
caches.append(cache)# 把一些变量数据保存起来,以便后面的反向传播使用
# 进行最后一层的前向传播,这一层的激活函数是sigmoid。得出的AL就是y'预测值
AL, cache = linear_activation_forward(A,
parameters['W' + str(L)],
parameters['b' + str(L)],
activation='sigmoid')
caches.append(cache)
assert(AL.shape == (1, X.shape[1]))
return AL, caches
反向传播
下面的linear_backward函数用于根据后一层的dZ来计算前面一层的dW,db和dA。也就是实现了下面3个公式
def linear_backward(dZ, cache):
A_prev, W, b = cache
m = A_prev.shape[1]
dW = np.dot(dZ, cache[0].T) / m
db = np.sum(dZ, axis=1, keepdims=True) / m
dA_prev = np.dot(cache[1].T, dZ)
return dA_prev, dW, db
linear_activation_backward用于根据本层的dA计算出本层的dZ。就是实现了下面的公式
def linear_activation_backward(dA, cache, activation):
linear_cache, activation_cache = cache
if activation == "relu":
dZ = relu_backward(dA, activation_cache) #计算relu导数
elif activation == "sigmoid": #计算sigmoid导数
dZ = sigmoid_backward(dA, activation_cache)
# 根据本层的dZ算出本层的dW和db以及前一层的dA
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) # 获取神经网络层数。caches列表的长度就等于神经网络的层数
Y = Y.reshape(AL.shape) # 让真实标签的维度和预测标签的维度一致
# 计算出最后一层的dA,前面文章我们以及解释过,最后一层的dA与前面各层的dA的计算公式不同,
# 因为最后一个A是直接作为参数传递到成本函数的,所以不需要链式法则而直接就可以求dA(A相当于成本函数的偏导数)
dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
# 计算最后一层的dW和db,因为最后一层使用的激活函数是sigmoid
current_cache = caches[-1]
grads["dA" + str(L-1)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(
dAL,
current_cache,
activation = "sigmoid")
# 计算前面L-1层到第一层的每层的梯度,这些层都使用relu激活函数
for c in reversed(range(1,L)): # reversed(range(1,L))的结果是L-1,L-2...1。是不包括L的。第0层是输入层,不必计算。
# 这里的c表示当前层
grads["dA" + str(c-1)], grads["dW" + str(c)], grads["db" + str(c)] = linear_activation_backward(
grads["dA" + str(c)],
caches[c-1],
# 这里我们也是需要当前层的caches,但是为什么是c-1呢?因为grads是字典,我们从1开始计数,而caches是列表,
# 是从0开始计数。所以c-1就代表了c层的caches。数组的索引很容易引起莫名其妙的问题,大家编程时一定要留意。
activation = "relu")
return grads
梯度下降:
上面的反向传播,我们得到了每一层的梯度,更新/优化每一层的w和b。
def update_parameters(parameters, grads, learning_rate):
L = len(parameters) // 2 # 获取层数。//除法可以得到整数
for l in range(1,L+1):
parameters["W" + str(l)] = parameters["W" + str(l)] - learning_rate * grads["dW" + str(l)]
parameters["b" + str(l)] = parameters["b" + str(l)] - learning_rate * grads["db" + str(l)]
return parameters
损失函数
def compute_cost(AL, Y):
m = Y.shape[1]
cost = (-1 / m) * np.sum(np.multiply(Y, np.log(AL)) + np.multiply(1 - Y, np.log(1 - AL)))
cost = np.squeeze(cost)
return cost
至此模型的函数都写完了
模型的主要代码:
# 按照指示的次数来训练深度神经网络
for i in range(0, num_iterations):
# 进行前向传播
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)
# 打印出成本
if i % 100 == 0:
if print_cost and i > 0:
print ("训练%i次后成本是: %f" % (i, cost))
costs.append(cost)
训练模型
layers_dims = [12288, 20, 7, 5, 1]
# 根据上面的层次信息来构建一个深度神经网络,并且用之前加载的数据集来训练这个神经网络,得出训练后的参数
parameters = dnn_model(train_x, train_y, layers_dims, num_iterations=2000, print_cost=True)
为什么是12288,因为数据集的图片是64643。第一层20个神经元,第二层7个。。
结果:
这中间多了个突起我也没搞明白,过几天再想想。
预测函数
def predict(X,parameters):
m = X.shape[1]
n = len(parameters)
p = np.zeros((1,m))
probas, caches = L_model_forward(X, parameters)
# 阈值设为0.5
for i in range(0, probas.shape[1]):
if probas[0,i] > 0.5:
p[0,i] = 1
else:
p[0,i] = 0
return p
结果:
最后
从单神经元到浅层神经元再到深层神经元主要架构没有改变依然是这四个函数的组成:
By the way
加宽网络模型的宽度,不改变深度:
可以看见损失函数降低了,同时也增加神经网络的宽度:
加深神经网络测试集的结果反而降低说明过拟合了!
增加神经网络的深度
结果:
六层的神经网络还不如四层的神经网络,这并不是说网络加深就效果不好了,可能是梯度爆炸了