图为神经网络的工作流程。
代码包括四大部分:数据处理部分,正向传播部分,反向传播部分以及最后的分析部分。
数据处理部分包括:图像转.h5文件函数,数据加载函数,相关的激活函数
正向传播部分包括:linear_forward,linear_activation_forward()以及forward_propagation函数
反向传播部分包括:linear_backward,linear_activation_backward,back_propagation以及update_params函数
分析部分包括:predict(),print_mislabled_images()两个函数
"""
报错信息汇总:
dZ[Z <= 0] = 0
IndexError: boolean index did not match indexed array along dimension 0; dimension is 1 but corresponding boolean dimension is 3
>>>print(dZ)
[[-0.39202432 -0.13325855 -0.04601089]]
>>>print([Z <= 0])
[array([[ True, False],
[ True, True],
[ True, False]])]
此处Z的维度为(3, 2),dZ的维度是(1,3)
报错的原因是dZ的维度和Z的维度不一致导致的,造成这种错误的原因有很多,我的错误是因为返回元素的顺序不一致导致的
def linear_backward(dZ,cache):
return dW,db,dA_prev
def linear_activation_backward(dA,cache,activation='relu'):
dA_prev,dW,db = linear_backward(dZ,linear_cache)
修正之后即可(低级错误,今后注意)
"""
import pickle as pkl
import testCases as tC
import numpy as np
import h5py
import os
import matplotlib.pyplot as plt #用于显示图片
import matplotlib.image as mpimg#用于读取图片,直接返回numpy.ndarray格式通道顺序是RGB,通道值默认范围0-255。
#准备相关的函数-数据加载函数,激活函数,求导函数
def image2H5file(file_dir,tofile:str,isdataset=True):
"""将图片转化为.h5文件(dataset格式)
参数:
file_dir -- 图片所在的目录
tofile -- 输出的.h5文件的名称
isdataset -- 是否是按照dataset格式存储,如果不是则按照group形式存储
"""
if isdataset:
cats = []
labels_cat = []
nocats = []
labels_nocat = []
#将每个图片的文件名,标签名加入到列表中
for file in os.listdir(file_dir+'/cat'):
cats.append(file_dir+'/cat'+file)
labels_cat.append(1)
for file in os.listdir(file_dir+'/not_cat'):
nocats.append(file_dir+'/not_cat'+file)
labels_nocat.append(0)
#将两个类别合起来组成一个list
image_list = np.hstack((cats,nocats)) #组合成(cats,nocats)形式,Horizontal
labels_list = np.hstack((labels_cat,labels_nocat))
#利用shuffle打乱顺序,增加随机性
temp = np.array([image_list,labels_list])
temp = temp.transpose()
np.random.shuffle(temp)
#从打乱的temp中取出image,labels
image_list = temp[:,0]
labels_list = temp[:,1]
#创建存储图片的数组-指定为浮点数是为了反向传播计算时的方便
image = np.random.rand(len(image_list),64,64,3).astype('float32')
labels = np.random.rand(len(image_list),1).astype('float32')
#使用plt.imread()将图片以像素形式读入
for i in range(len(image_list)):
image[i] = mpimg.imread(image_list[i])
labels[i] = np.array(labels_list[i])
#创建.h5文件
f = h5py.File(tofile,'w')
f.create_dataset('X',data=image)
f.create_dataset('y',data=labels)
f.close()
else:
pass
def load_dataset():
"""加载数据:形如(m,64,64,3)
数据形状:
(209, 64, 64, 3)
(209,)
(50, 64, 64, 3)
返回:
train_set_x_orig -- 保存的是训练集里面的图像数据(本训练集有209张64x64的图像)。
train_set_y_orig -- 保存的是训练集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
test_set_x_orig -- 保存的是测试集里面的图像数据(本训练集有50张64x64的图像)。
test_set_y_orig -- 保存的是测试集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
classes -- 保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]。
"""
#本数据采用group形式存储,故以键值形式进行取值
train_dataset = h5py.File("./code/train_catvnoncat.h5","r")
train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels
test_dataset = h5py.File("./code/test_catvnoncat.h5", "r")
test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels
classes = np.array(test_dataset["list_classes"][:]) # the list of classes
train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes
def relu(Z):
"""
正向传播的激活函数
参数:
Z -- 每一层的加权计算结果
返回:
A -- 激活函数作用Z后的结果
cache -- Z的缓存
"""
A = np.maximum(0,Z)
assert(A.shape == Z.shape)
cache = Z
return A,cache
def relu_backward(dA,cache):
"""用于在反向传播中计算dZ
参数:
Z -- 每一层的加权计算结果
返回:
A -- 激活函数作用Z后的结果
cache -- Z的缓存
"""
Z = cache
dZ = np.array(dA,copy=True)
dZ[Z <= 0] = 0
assert(dZ.shape == Z.shape)
return dZ
def sigmoid(Z):
"""
正向传播的激活函数
参数:
Z -- 每一层的加权计算结果
返回:
A -- 激活函数作用Z后的结果
cache -- Z的缓存
"""
A = 1/(1+np.exp(-Z))
cache = Z
return A,cache
def sigmoid_backward(dA,cache):
"""用于在反向传播中计算dZ
参数:
dA -- 代价函数(最优化的目标函数)关于输出A的导数,基于逻辑回归实现的神经网络一般认为代价函数为-(yloga+(1-y)log(1-a)),a=yHat
cache -- 每层存储的Z
返回:
dZ -- 代价函数关于Z的导数,由链式法则而来
"""
Z = cache
s = 1/(1+np.exp(-Z))
dZ = dA * s * (1-s)
assert(dZ.shape == Z.shape)
return dZ
def costfunc(AL,labels):
"""代价函数:因为每一次迭代之后都需要进行代价函数的计算,所以封装成函数
参数:
AL -- 神经网络的输出预测值
m -- 数据集的样本数量
labels -- 数据的标签,维度为(1,样本数量)
返回:
cost -- 代价函数的值
"""
#样本数
m = labels.shape[1]
J = -np.sum(np.multiply(np.log(AL),labels)+np.multiply(np.log(1-AL),1-labels))/m
cost = np.squeeze(J)
assert(cost.shape == ())
return cost
#正向传播
def initialize_params(layers_dims):
"""初始化多层网络参数
参数:
layers_dims -- 包含我们网络中每个层的节点数量的列表
返回:
params -- 包含各层W,b参数的字典\n
wl - 权重矩阵,维度为(layers_dims[l],layers_dims[l-1])\n
bl - 偏向量,维度为(layers_dims[l],1)
"""
np.random.seed(3)
params = {}
L = len(layers_dims)
for l in range(1,L):
params['W' + str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1])/np.sqrt(layers_dims[l-1])
params['b' + str(l)] = np.zeros((layers_dims[l],1))
#确保数据格式符合要求
assert(params['W' + str(l)].shape == (layers_dims[l],layers_dims[l-1]))
return params
def linear_forward(A_prev,W,b):
"""实现前向传播的线性部分
参数:
A_prev -- 来自上一层的激活函数输出值,维度为(上一层的节点数量,样本数)
w -- 权重矩阵,维度为(当前层的节点数量,上一层的节点数量)
b -- 偏向量,维度为(当前层的节点数量,1)
返回:
Z -- 激活功能的输入,也称为预激活参数\n
cache -- 一个包含“A”“W”“b”的字典,存储这些变量用于反向传播
"""
Z = np.dot(W,A_prev) + b
assert(Z.shape == (W.shape[0],A_prev.shape[1]))
cache = (A_prev,W,b)
return Z,cache
def linear_activation_forward(A_prev,W,b,activation):
"""实现每一层预激活参数的激活
参数:
A_prev -- 来自上一层的激活函数输出值,维度为(上一层的节点数量,样本数)\n
w -- 权重矩阵,维度为(当前层的节点数量,上一层的节点数量)\n
b -- 偏向量,维度为(当前层的节点数量,1)\n
activation -- 在此层中选择的激活函数,字符串类型
返回:
A -- 激活函数的输出\n
cache -- 一个包含((A_prev,W,b),Z)=(linear_cache,activation_cache)的缓存字典
"""
if activation == 'sigmoid':
Z,linear_cache = linear_forward(A_prev,W,b)
A,activation_cache = sigmoid(Z)
elif activation == 'relu':
Z,linear_cache = linear_forward(A_prev,W,b)
A,activation_cache = relu(Z)
else:
print("所选择的激活函数不在程式库中")
assert(A.shape == (W.shape[0],A_prev.shape[1]))
cache = (linear_cache,activation_cache)
return A,cache
def forward_propagation(X,parameters):
"""实现多层的正向传播
参数:\n
X -- 数据,Numpy数组,维度为(输入节点数量,样本数)\n
parameters -- 初始化的参数字典
返回:\n
AL -- 神经网络最后的激活值,等于yHat\n
caches -- 包含以下内容的缓存列表:\n
使用Relu函数的linear_activation_forward()的每个cache(有L-1个,索引从0到L-2)\n
使用sigmoid函数的linear_activation_forward()的每个cache(最后一层,索引L-1)
"""
#存储每层的相关参数和数据
caches = []
A = X
#整除--字典仅包含每层的W,b两个参数,可由此得出神经网络的层数
L = len(parameters)//2
#计算所有隐藏层
for l in range(1,L):
A_prev = A
A,cache = linear_activation_forward(A_prev,parameters['W'+str(l)],parameters['b'+str(l)],'relu')
caches.append(cache)
#计算输出层
AL,cache = linear_activation_forward(A,parameters['W'+str(L)],parameters['b'+str(L)],'sigmoid')
caches.append(cache)
assert(AL.shape == (1,X.shape[1]))
return AL,caches
#反向传播--修正相关参数
def linear_backward(dZ,cache):
"""为单层实现反向传播的线性部分(第L层):return dW,db,dA_prev
参数:\n
dZ -- dJ/dZ[L],第L层的线性输出的成本梯度\n
cache -- 来自当前层前向传播的值的元组(A_prev,W,b)
返回:\n
dW -- 相对于当前层L的W的成本梯度,维度为(当前层节点个数,上一层节点个数)\n
db -- 相对于当前层L的b的成本梯度,维度为(当前层节点个数,1)\n
dA_prev -- 相对于前一层(L-1)层的激活A的成本梯度,维度为(上一层节点个数,样本数)
"""
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)
assert(dW.shape == W.shape)
assert(db.shape == b.shape)
assert(dA_prev.shape == A_prev.shape)
return dA_prev,dW,db
def linear_activation_backward(dA,cache,activation='relu'):
"""计算每一层的dZ并计算相应的参数A_prev,W,b的梯度
参数:
dA -- 当前层L的激活后的梯度值
cache -- 存储的用于反向传播的元组((A_prev,W,b),Z)=(linear_cache,activation_cache)
返回:
dW -- 相对于当前层L的W的成本梯度,维度为(当前层节点个数,上一层节点个数)\n
db -- 相对于当前层L的b的成本梯度,维度为(当前层节点个数,1)\n
dA_prev -- 相对于前一层(L-1)层的激活A的成本梯度,维度为(上一层节点个数,样本数)
"""
linear_cache,activation_cache = cache
if activation == 'relu':
dZ = relu_backward(dA,activation_cache)
dA_prev,dW,db = linear_backward(dZ,linear_cache)
elif activation == 'sigmoid':
dZ = sigmoid_backward(dA,activation_cache)
dA_prev,dW,db = linear_backward(dZ,linear_cache)
else:
print("选择的激活函数不在程式库中")
return dA_prev,dW,db
def backward_propagation(AL,labels,caches):
"""反向传播
参数:
AL -- 正向传播输出层的输出,也就是yHat
labels -- 标签向量,维度为(1,样本数)
caches -- 存储的用于反向传播的元组(((A_prev,W,b),Z),...,)=(linear_cache,activation_cache)
返回:
grads -- 梯度的字典
grads["dA"+str(l)] = ...
"""
grads = {}
L = len(caches)
m = labels.shape[1]
#确保维度一致
labels = labels.reshape(AL.shape)
#dAL = dJ/dAL J=-{yloga + (1-y)log(1-a)}
dAL = -(np.divide(labels,AL) - np.divide(1-labels,1-AL))
#计算最后一层的dW,db和上一层的dA_prev(索引从0开始)
curr_cache = caches[L-1]
grads['dA_prev'+str(L-1)],grads['dW'+str(L)],grads['db'+str(L)] = linear_activation_backward(dAL,curr_cache,'sigmoid')
for l in reversed(range(L-1)):
curr_cache = caches[l]
grads['dA_prev'+str(l)],grads['dW'+str(l+1)],grads['db'+str(l+1)] = linear_activation_backward(grads['dA_prev'+str(l+1)],curr_cache,'relu')
return grads
def update_params(params,grads,leaning_rate=0.001):
"""使用梯度下降更新相关的参数
参数:
params -- 每层参数的字典(W,b)\n
grads -- 每层参数梯度的字典,是backward_propagation()函数的输出
返回:
params -- 更新参数的字典
params['W'+str(l)] = ...
"""
L = len(params)//2
for l in range(L):
params['W'+str(l+1)] = params['W'+str(l+1)] - leaning_rate * grads['dW'+str(l+1)]
params['b'+str(l+1)] = params['b'+str(l+1)] - leaning_rate * grads['db'+str(l+1)]
return params
def test_func():
#测试参数初始化函数
layers_dims = [5,4,3]
parameters = initialize_params(layers_dims)
print(parameters['W1'])
#测试正向线性部分
A,W,b = tC.linear_forward_test_case()
Z,linear_cache = linear_forward(A,W,b)
print(Z)
#测试激活函数
A_prev,w,b = tC.linear_activation_forward_test_case()
A,linear_cache = linear_activation_forward(A_prev,w,b,activation='sigmoid')
print('sigmoid:A='+str(A))
#测试正向传播
X,params = tC.L_model_forward_test_case()
AL,caches = forward_propagation(X,params)
print("==============")
print("AL="+str(AL))
print('caches的长度为='+str(len(caches)))
#测试代价函数
Y,AL = tC.compute_cost_test_case()
print("cost="+str(costfunc(AL,Y)))
#测试linear_activation_backward
print("="*20+"测试")
AL,linear_activation_cache = tC.linear_activation_backward_test_case()
dA_prev,dW,db = linear_activation_backward(AL,linear_activation_cache,activation='sigmoid')
print('sigmoid:')
print('dA_prev='+str(dA_prev))
dA_prev,dW,db = linear_activation_backward(AL,linear_activation_cache,activation='relu')
print('relu:')
print('dA_prev='+str(dA_prev))
#测试反向传播
Al,Y_asses,caches = tC.L_model_backward_test_case()
grads = backward_propagation(Al,Y_asses,caches)
print("dW1="+str(grads['dW1']))
#测试更新参数
params,grads = tC.update_parameters_test_case()
params = update_params(params,grads,0.1)
print('W1='+str(params['W1']))
def two_layer_model(X,Y,layers_dims,learning_rate=0.0075,num_iterations=3000,print_cost=False,isPlot=True):
"""
实现一个两层的神经网络,【LINEAR->RELU】 -> 【LINEAR->SIGMOID】
参数:
X - 输入的数据,维度为(n_x,例子数)
Y - 标签,向量,0为非猫,1为猫,维度为(1,数量)
layers_dims - 层数的向量,维度为(n_y,n_h,n_y)
learning_rate - 学习率
num_iterations - 迭代的次数
print_cost - 是否打印成本值,每100次打印一次
isPlot - 是否绘制出误差值的图谱
返回:
parameters - 一个包含W1,b1,W2,b2的字典变量
"""
np.random.seed(1)
grads = {}
costs = []
(n_x,n_h,n_y) = layers_dims
#初始化参数
parameters = initialize_params((n_x, n_h, n_y))
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
#开始进行迭代
for i in range(0,num_iterations):
#前向传播
A1, cache1 = linear_activation_forward(X, W1, b1, "relu")
A2, cache2 = linear_activation_forward(A1, W2, b2, "sigmoid")
#计算成本
cost = costfunc(A2,Y)
#后向传播
##初始化后向传播
dA2 = - (np.divide(Y, A2) - np.divide(1 - Y, 1 - A2))
##向后传播,输入:“dA2,cache2,cache1”。 输出:“dA1,dW2,db2;还有dA0(未使用),dW1,db1”。
dA1, dW2, db2 = linear_activation_backward(dA2, cache2, "sigmoid")
dA0, dW1, db1 = linear_activation_backward(dA1, cache1, "relu")
##向后传播完成后的数据保存到grads
grads["dW1"] = dW1
grads["db1"] = db1
grads["dW2"] = dW2
grads["db2"] = db2
#更新参数
parameters = update_params(parameters,grads,learning_rate)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
#打印成本值,如果print_cost=False则忽略
if i % 100 == 0:
#记录成本
costs.append(cost)
#是否打印成本值
if print_cost:
print("第", i ,"次迭代,成本值为:" ,np.squeeze(cost))
#迭代完成,根据条件绘制图
if isPlot:
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per hundres)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
#返回parameters
return parameters
def predict(X,y,params):
"""该函数用于预测L层神经网络的结果
参数:
X -- 测试集
y -- 标签
params -- 已完成训练的训练模型的参数
返回:
p -- 给定数据集X的预测
"""
#样本数
m = X.shape[1]
#预测结果
p = np.zeros((1,m))
#根据参数正向传播
probs,caches = forward_propagation(X,params)
for i in range(0,m):
#最后的输出使用的是sigmoid函数
if probs[0][i] > 0.5:
p[0,i] = 1
else:
p[0,i] = 0
print('准确度为:'+str(float(np.sum(p==y))/m))
return p
def L_layer_model(X,Y,layers_dims,learning_rate=0.0075,num_iterations=3000,print_cost=False,isPlot=True):
"""实现一个两层的神经网络,【LINEAR->RELU】 -> 【LINEAR->SIGMOID】
参数:
X - 输入的数据,维度为(n_x,例子数)
Y - 标签,向量,0为非猫,1为猫,维度为(1,数量)
layers_dims - 层数的向量,维度为(n_y,n_h,n_y)
learning_rate - 学习率
num_iterations - 迭代的次数
print_cost - 是否打印成本值,每100次打印一次
isPlot - 是否绘制出误差值的图谱
返回:
parameters - 一个包含W1,b1,W2,b2的字典变量"""
np.random.seed(1)
costs = []
parameters = initialize_params(layers_dims)
#迭代开始
for i in range(0,num_iterations):
#正向传播
AL,caches = forward_propagation(X,parameters)
#计算成本
cost = costfunc(AL,Y)
#反向传播
grads = backward_propagation(AL,Y,caches)
#更新参数
parameters = update_params(parameters,grads,learning_rate)
#打印每100次迭代的成本值
if i % 100 == 0:
costs.append(cost)
if print_cost:
print("第",i,"次迭代,成本值为",np.squeeze(cost))
#迭代完成,根据条件绘图
if isPlot:
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations(per hundreds)')
plt.title('learning rate='+str(learning_rate))
plt.show()
return parameters
def print_mislabeled_images(classes,X,y,p):
"""分析结果
绘制预测和实际不同,即分错类的图像
参数:
classes -- 保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]
X -- 数据集
y -- 实际的标签
p -- 预测的值
"""
#错误分类的样本的索引
a = p + y
#np.where(conditions)返回满足条件的索引,如果为二维数组返回的是行列索引,先行后列,有点像np.nonzero()的返回值
mislabelsed_indices = np.asarray(np.where(a == 1))
#使用rcParams设置图像的大小。通过figsize参数可以设置figure的size,即(width, height),单位为inch。
plt.rcParams['figure.figsize'] = (40.0,40.0)
num_images = len(mislabelsed_indices[0])
for i in range(num_images):
#mislabelsed_indices维度为(2,错分标本数量)
index = mislabelsed_indices[1][i]
plt.subplot(2,num_images,i+1)
#还原图片
plt.imshow(X[:,index].reshape(64,64,3),interpolation='nearest')
plt.axis('off')
plt.title("Prediction:" + classes[int(p[0,index])].decode('utf-8') + '\n Class:' + classes[y[0,index]].decode('utf-8'))
plt.show()
if __name__ == '__main__':
#导入数据
train_set_x_orig , train_set_y , test_set_x_orig , test_set_y , classes = load_dataset()
#转换为(12288,1)的数据
train_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T
#标准化数据
train_x = train_x_flatten / 255
train_y = train_set_y
test_x = test_x_flatten / 255
test_y = test_set_y
layers_dims = (12288,7,1)
#两层网络模型
# parameters = two_layer_model(train_x, train_set_y, layers_dims = (n_x, n_h, n_y), num_iterations = 2500, print_cost=True,isPlot=True)
# predict_train = predict(train_x,train_y,parameters)
# predict_test = predict(test_x,test_y,parameters)
#多层神经网络
layers_dims = [12288,20,7,5,1]
parameters = L_layer_model(train_x,train_y,layers_dims,num_iterations=2500,print_cost=True,isPlot=False)
#训练集错误率
pred_train = predict(train_x,train_y,parameters)
#测试集错误率
pred_test = predict(test_x,test_y,parameters)
print_mislabeled_images(classes,test_x,test_y,pred_test)