【Deep Learning - 01】深度神经网络

图为神经网络的工作流程。
【Deep Learning - 01】深度神经网络_第1张图片
代码包括四大部分:数据处理部分,正向传播部分,反向传播部分以及最后的分析部分。
数据处理部分包括:图像转.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)




你可能感兴趣的:(深度学习,神经网络,python,机器学习,深度学习,人工智能)