阅读 82

吴恩达 神经网络与深度学习

课程地址:https://www.coursera.org/learn/neural-networks-deep-learning?specialization=deep-learning

第一步 理解逻辑回归

我们先从逻辑回归开始,再引出神经网络。
在上一节机器学习课中,我们已经介绍了监督学习的概念,其中我们会选择特征,然后根据这些特征,我们通过数据去训练特征前的权重。而达到训练出的权重,可以更好的预测未来的输入。
我们再来回顾一下个二分分类算法。就是我给一组特征,你告诉我他是A分类还是B分类的问题。常见应用如 它是不是猫。


image.png

我们从左往右依次看这个公式。
S 就是最终的输出,这里可以保证这个值的范围是落在[0, 1]之间。然后我们套了一个sigmoid函数,目的就是为了把输出控制在0到1的范围里。


image.png

函数里面有W, X 和B
X就是我们的输入,是一组特征的具体值。如果是一张猫的图片的话。那么就是每个像素的具体值,展开成一个向量。


image.png

W 是个系数向量。为了更好的使用矩阵乘法,这里需要做个转置。就是把原本的行向量转成列向量或者相反。 B在我们常规理解里是截距,在这里就是我们假设还有一个0号特征,值恒为1.

偏移量是分割面不可缺少的组成部分。w1x1 + w2x2 + ...这个,就是用来分割不同类别的超平面,你考虑只有一个x1的情况(二维),这时候y=w1x1是通过原点的直线,你当然不能指望所有分类面都是通过原点的,因此加个偏移量b,这样直线的位置是任意的了。多元的情况也是如此,显然不能要求分割面都通过原点,因此加偏移量b。

上面这个函数,我们只要有了合适的W 和 B,就可以做预测了。
那么合适的W和B,计算机怎么算出来的呢?
这就需要计算机的算法知道它的优化目标是什么。一般优化目标,我们希望我们的数据可以更好的被函数画出的图像给拟合,同时也不会因为个别点而太过拟合。(至于过拟合是什么这里稍后展开,目前只要知道模型函数更好的反应数据的分布就是好模型)
那么这样我们只需要定义一个损失函数,来表示这个函数求出来的Y值,和输入实际的Y值的差是多少。
对单个样本的损失我们得到后,我们对所有样本的这个误差求总和,就得到了代价函数

最简单的损失函数定义方式为平方差损失


image.png

但 Logistic 回归中我们并不倾向于使用这样的损失函数,因为之后讨论的优化问题会变成非凸的,最后会得到很多个局部最优解,梯度下降法可能找不到全局最优值。一般使用


image.png

损失函数是在单个训练样本中定义的,它衡量了在单个训练样本上的表现。而代价函数(cost function,或者称作成本函数)衡量的是在全体训练样本上的表现,即衡量参数 w 和 b 的效果。


image.png

看到这里有小伙伴会思考,这个损失函数是怎么来的呢,有没有其他的凸函数可以用呢?

什么是凸函数?

对于函数上任意2点,总有
(f(a) + f(b)) / 2 > f( (a + b) / 2 )


image.png

凸函数的任何极小值也是最小值。 严格凸函数最多有一个最小值,可以使用梯度下降。

第二步 理解损失函数的由来

2.1 sigmoid 由来

从Logistic曲线就能看出,无论横坐标取什么值,它的值总是在[0,1]之间变化,它的物理含义很明确,指单个样例,在条件x下,出现的【概率】。我们还要时刻注意一点,横坐标【x】的物理含义是什么?要知道这个,我们需要拿出一个实际的生物模型,它在生物学中有广泛的应用。如下图所示:

image

这是草履虫密度的分布图,你会发现它的拟合曲线即为我们定义的Logistic曲线或Sigmoid曲线,呵呵,怎么那么巧呢。简单解释下该图的物理内涵,刚开始,种群的数量非常少,繁殖的速度会比较慢。随着数量的增加,繁殖速度越来越快,然后,会因为食物不足,有天敌出现等原因,增速开始下降,最后稳定在一个范围内。Logistic曲线非常好的描述了这个变化规律。

2.2 理解什么是似然函数

“似然性”与“或然性”或“概率”意思相近,都是指某种事件发生的可能性,但是在统计学中,“似然性”和“概率”(或然性)又有明确的区分
概率,用于在已知一些参数的情况下,预测接下来在观测上所得到的结果;似然性,则是用于在已知某些观测所得到的结果时,对有关事物之性质的参数进行估值。

而极大似然估计就是就是利用已知的样本结果信息,反推最具有可能(最大概率)导致这些样本结果出现的模型参数值!

例子一

假如有一个罐子,里面有黑白两种颜色的球,数目多少不知,两种颜色的比例也不知。我 们想知道罐中白球和黑球的比例,但我们不能把罐中的球全部拿出来数。现在我们可以每次任意从已经摇匀的罐中拿一个球出来,记录球的颜色,然后把拿出来的球 再放回罐中。这个过程可以重复,我们可以用记录的球的颜色来估计罐中黑白球的比例。假如在前面的一百次重复记录中,有七十次是白球,请问罐中白球所占的比例最有可能是多少?

很多人马上就有答案了:70%。而其后的理论支撑是什么呢?

我们假设罐中白球的比例是p,那么黑球的比例就是1-p。因为每抽一个球出来,在记录颜色之后,我们把抽出的球放回了罐中并摇匀,所以每次抽出来的球的颜 色服从同一独立分布。

这里我们把一次抽出来球的颜色称为一次抽样。题目中在一百次抽样中,七十次是白球的,三十次为黑球事件的概率是P(样本结果|Model)。

如果第一次抽象的结果记为x1,第二次抽样的结果记为x2....那么样本结果为(x1,x2.....,x100)。这样,我们可以得到如下表达式:

P(样本结果|Model)

= P(x1,x2,…,x100|Model)

= P(x1|Mel)P(x2|M)…P(x100|M)

= p70(1-p)30.

好的,我们已经有了观察样本结果出现的概率表达式了。那么我们要求的模型的参数,也就是求的式中的p。

那么我们怎么来求这个p呢?

不同的p,直接导致P(样本结果|Model)的不同。

好的,我们的p实际上是有无数多种分布的。如下:

image

那么求出 p70(1-p)30为 7.8 * 10^(-31)

p的分布也可以是如下:

image

那么也可以求出p70(1-p)30为2.95* 10^(-27)

那么问题来了,既然有无数种分布可以选择,极大似然估计应该按照什么原则去选取这个分布呢?

答:采取的方法是让这个样本结果出现的可能性最大,也就是使得p70(1-p)30值最大,那么我们就可以看成是p的方程,求导即可!

那么既然事情已经发生了,为什么不让这个出现的结果的可能性最大呢?这也就是最大似然估计的核心。

我们想办法让观察样本出现的概率最大,转换为数学问题就是使得:

p70(1-p)30最大,这太简单了,未知数只有一个p,我们令其导数为0,即可求出p为70%,与我们一开始认为的70%是一致的。其中蕴含着我们的数学思想在里面。

2.3 推导

image.png

第三步理解梯队下降求最优

函数的梯度(gradient)指出了函数的最陡增长方向。即是说,按梯度的方向走,函数增长得就越快。那么按梯度的负方向走,函数值自然就降低得最快了。

模型的训练目标即是寻找合适的 w 与 b 以最小化代价函数值。简单起见我们先假设 w 与 b 都是一维实数,那么可以得到如下的 J 关于 w 与 b 的图:

image.png
image.png

梯度下降法是如何运用在逻辑回归:

假设输入的特征向量维度为 2,即输入参数共有 x1, w1, x2, w2, b 这五个。可以推导出如下的计算图:


image.png

上面是一个计算图,我们需要给出5个输入,其中2个是数据(x1, x2)
另外3个是权重。我们希望知道我们对这5个值的变化为如何影响最后的L。
这就需要我们用导数来处理。
比如我们看一个最简答的例子。y = 2 * x
原本x = 1, 我们变化x = 1.001
可以知道y = 2.002 , 也就是X变换了1个单位,Y变化了2个单位。其背后是因为Y对X的导数为2.
如果 x = a + b. y = 2 * x. 我们可以用链式法则来求导,得到a对Y的改变。也就是Y对A的导数。以及Y对B的导数。

下面我们就针对上面的式子做一个链式求导的推导:
首先是损失函数L 对 a 求导, a 再 对Z, Z再对5个参数。

image.png

有了上述的求导,我们就可以写出梯度下降的代码了。
目标是每一次沿着导数方向根据学习速率去走一小步
下面就是伪代码,应该不难理解了

J=0;dw1=0;dw2=0;db=0;
for i = 1 to m
    z(i) = wx(i)+b;
    a(i) = sigmoid(z(i));
    J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
    dz(i) = a(i)-y(i);
    dw1 += x1(i)dz(i);
    dw2 += x2(i)dz(i);
    db += dz(i);
J/= m;
dw1/= m;
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db

基于上面的步骤,我们已经可以自己构建出优秀的逻辑回归的训练算法。它的大框架是我们先准备一组【X,Y】。 然后根据X的定义出多少个W。 然后随机给W 赋值,然后不断用上面的代码去更新W 和 B。 最后看J 足够小了之后,就可以用这个模型去检验新的X数据,来获得模型的准确率了。

另外是一些写代码的小技巧,我们可以用矩阵乘法来取代掉一些FOR循环。这可以大大加快算法的执行速度,因为底层的矩阵乘法库是被很多科学家进行高度优化的。


image.png

第四步 掌握浅层神经网络

image.png

约定俗成的符号表示是:

输入层的激活值为 a[0];
同样,隐藏层也会产生一些激活值,记作 a[1] 隐藏层的第一个单元(或者说节点)就记作 a[1] 下标1,输出层同理。
另外,隐藏层和输出层都是带有参数 W 和 b 的。它们都使用上标[1]来表示是和第一个隐藏层有关,或者上标[2]来表示是和输出层有关。

我们来单独看某一个隐藏层的神经元。


image.png

实际上,神经网络只不过将 Logistic 回归的计算步骤重复很多次。这里为什么是用逻辑回归呢,因为我们大脑中的神经元就是这样工作,一个神经元会连着许多别的神经元。当需要发射信号的时候,会产生一个神经电流。去激活下一个神经元。所以和2分类问题一直,一个开或关的作用。


image.png

其中,a[0]可以是一个列向量,也可以将多个列向量堆叠起来得到矩阵。如果是后者的话,得到的 z[1]和 a[1]也是一个矩阵。

在神经网络中我们需要训练的权重为W矩阵和B
一般W[1].T 的shape为(当前层神经元数量,前一层神经元数量)
b[1]的shape为(当前层神经元数量, 1)

第五步 理解不同的激活函数

我们在逻辑回归里使用了SIGMOID的激活函数。不过在讲解不同的激活函数前,我们要说下如果没有激活函数会如何?
其实没有激活函数,就是使用了线性激活函数,就是到Z那里停下来了


image.png

用线性激活函数的问题就是它只能画出直线的空间表示。使得训练出的模型有局限性不能很好的拟合数据。
再引入了非线性的激活函数后,模型的表达性可以大大的增强。如下图


image.png

但随之而来的问题就是,不可解释。因为激活函数所能表达的输出就变成了一个复杂的, 复杂的, 超级复杂的函数。没人可以绘制它长什么样。所以使得调参变成了一门手艺活。而不是那么严谨,因为没有人知道会变成什么样,而使得不能严谨的去推理。

下面是常用的四类激活函数。


image.png
image.png

image.png
image.png

第六步 神经网络的梯度下降法

上面一节,我们已经介绍了通过引入非线性的激活函数,可是使得网络可以构建任意特征的曲线而达到表达性大大加强。那么接下来的目标是一致的。我们的数据会从输入层流向输出层,那么和真实Y之间肯定会有比较。为了使得这个误差尽可能小。我们需要训练的每一层的W矩阵和B矩阵。使得模型可以更好的在训练数据获得不错的拟合。
下面我会先正向走一遍一个最简单的神经网络,假设我们输入层到隐藏层用的是RELU,隐藏层到输出层是一个二分类问题用了sigmoid

image.png

用程序的向量化技术来模拟


image.png

然后是反向传播


image.png

转换为代码为


image.png

因为左边的式子,我们是一个一个X去求的。右边我们使用了向量化的技术,所以是所有M个数据一起求了,那么这个值会被自动求和,那么最后需要除以M。

最后我们讲一下随机初始化的重要性。
如果在一开始,将2个隐藏神经元的参数设置为相同的,那么他们对输出单元的影响也是相同的,所以即使经过了多次迭代,2个神经元也是对称的。
我们思考一下,正向过去的时候,值是一样的,反向回来也必然一样。所以我们在初始化的时候就要对W参数进行随机初始化。这个和之前的逻辑回归不同,逻辑回归因为一个权重只对应1个特征,所以不同的权重接收到的输入不会一样。但是再神经元这里,一个神经元的权重矩阵来自于前一层的所有输入。所以每个神经元的权重接受到的输入是一致的。就需要随机初始化。

第7步 深层神经网络

一般来说,三层神经网络可以逼近任何一个非线性函数 为什么还需要深度神经网络
来自于花书

当前馈网络具备一个线性输出层和至少一层具备非线性激活函数的隐藏层,只要给予网络足够的神经元,就可以以任意精度逼近任何一个 从有限空间(可以离散)到另一个有限空间的borel可测函数。(定义在R^n的有界闭集的任意连续函数都是borel可测的)

实际问题在于,我们通常都是需要使用有限的样本在有限的时间上对一个函数进行拟合. 更深的网络有如下一些好处:

  1. 学习方便。给定相同的参数,深的网络比大而浅的网络,拟合效率更高。

  2. 表现能力更强。可以模拟出很多高维特征。 如对于人脸识别,神经网络的第一层从原始图片中提取人脸的轮廓和边缘,每个神经元学习到不同边缘的信息;网络的第二层将第一层学得的边缘信息组合起来,形成人脸的一些局部的特征,例如眼睛、嘴巴等;后面的几层逐步将上一层的特征组合起来,形成人脸的模样。随着神经网络层数的增加,特征也从原来的边缘逐步扩展为人脸的整体,由整体到局部,由简单到复杂。层数越多,那么模型学习的效果也就越精确。

  3. 训练处的模型可以迁移到其他任务和数据集上的特性好。

既然有了深度网络,那么我们需要考虑的参数就多加了一个隐藏层层数。

所以现在我们构建一个神经网络的算法需要手动指点的参数如下:

学习速率:α
迭代次数:N
隐藏层的层数:L
每一层的神经元个数:n[1],n[2],...
激活函数 g(z) 的选择

当开发新应用时,预先很难准确知道超参数的最优值应该是什么。因此,通常需要尝试很多不同的值。应用深度学习领域是一个很大程度基于经验的过程。

在下一章中,我们会详细介绍超参数的调试。

最后是深度神经网络的python代码
总体项目见github

项目核心分为5个模块。
第一个是一些工具类叫dnn_utils(放的是一些公式的计算方法): 如sigmoid, relu , 以及对应的他们的导数, 计算cost

第二个是专门处理前向传播的类叫nn_forward:

  1. linear_forward, 单层神经元的前半部分计算。根据A, W, B 计算Z,并且把输入放进CACHE
  2. linear_activation_forward, 单层神经元的完整计算(包含线性,非线性)。调用linear_forward
  3. L层全部的前向传播,前面都是RELU,最后输出层SIGMOID, 调用linear_activation_forward; caches存放了每一层线性输入,和非线性输入,用于之后的反向传播
def L_model_forward(X, parameters):
    caches = []
    A = X
    L = len(parameters) // 2  # number of layers in the neural network
    # Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
    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)

    # Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.
    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

第三个模块是反向传播
很前向类似,也是分为解决线性的反向;整体一层神经元的反向;和总的L层的反向传播

def L_model_backward(AL, Y, caches):
    """
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    Returns:
    grads -- A dictionary with the gradients
             grads["dA" + str(l)] = ... 
             grads["dW" + str(l)] = ...
             grads["db" + str(l)] = ... 
    """
    grads = {}
    L = len(caches)  # the number of layers
    m = AL.shape[1]
    Y = Y.reshape(AL.shape)  # after this line, Y is the same shape as AL

    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))

    # Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "dAL, current_cache". Outputs: "grads["dAL-1"], grads["dWL"], grads["dbL"]
    current_cache = caches[L - 1]
    grads["dA" + str(L - 1)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL,current_cache, "sigmoid")

    for l in reversed(range(L - 1)):
        # lth layer: (RELU -> LINEAR) gradients.
        current_cache = caches[l]
        grads["dA" + str(l)], grads["dW" + str(l + 1)], grads["db" + str(l + 1)] = linear_activation_backward(grads["dA" + str(l + 1)], current_cache, "relu")

    return grads

第四个模块,就是用前向传播和反向传播算法,整合起来的神经网络算法。
我取名为netural_network
里面包含了随机初始化参数, 训练L层的模型, 更新参数,最后预测

def initialize_parameters_deep(layer_dims):
    parameters = {}
    L = len(layer_dims)  # number of layers in the network

    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l - 1]) * np.sqrt(1 / layer_dims[l-1])
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
        assert (parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l - 1]))
        assert (parameters['b' + str(l)].shape == (layer_dims[l], 1))
    return parameters


def update_parameters(parameters, grads, learning_rate):
    L = len(parameters) // 2  # number of layers in the neural network
    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


def L_layer_model(X, Y, layers_dims, learning_rate=0.0075, num_iterations=3000, print_cost=False):  # lr was 0.009
    costs = []  # keep track of cost
    parameters = initialize_parameters_deep(layers_dims)
    for i in range(0, num_iterations):
        # Forward propagation: [LINEAR -> RELU]*(L-1) -> LINEAR -> SIGMOID.
        AL, caches = L_model_forward(X, parameters)

        # Compute cost.
        cost = compute_cost(AL, Y)

        # Backward propagation.
        grads = L_model_backward(AL, Y, caches)

        # Update parameters.
        parameters = update_parameters(parameters, grads, learning_rate)

        # Print the cost every 100 training example
        if print_cost and i % 100 == 0:
            print("Cost after iteration %i: %f" % (i, cost))
        if print_cost and i % 100 == 0:
            costs.append(cost)
    if print_cost:
        # plot the cost
        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 predict(X, y, parameters):
    """
    Returns:
    p -- predictions for the given dataset X
    """
    m = X.shape[1]
    n = len(parameters) // 2  # number of layers in the neural network
    p = np.zeros((1, m))

    # Forward propagation
    probas, caches = L_model_forward(X, parameters)

    # convert probas to 0/1 predictions
    for i in range(0, probas.shape[1]):
        if probas[0, i] > 0.5:
            p[0, i] = 1
        else:
            p[0, i] = 0
    acc = np.sum((p == y) / m)
    # print("Accuracy: " + str(acc))
    return p, acc

最后就是使用神经网络的APP了。数据集用了课程里提供的猫的图片。
程序枚举了多种网络结构,找到正确率最高的网络结构

for layer_length in range(1, 5):
    print("----------start " + str(layer_length) + " hidden layer ----------")
    cur_layer_max_acc = 0
    cur_layer_dims = []
    for layers_dims in permutation(layer_length):
        parameters = L_layer_model(train_x, train_y, layers_dims, 0.012, 1500, False)
        pred_train, acc_train = predict(train_x, train_y, parameters)
        pred_test, acc_test = predict(test_x, test_y, parameters)
        print(str(layers_dims) + ", accuracy " + str(acc_train) + "," + str(acc_test))
        if acc_test > cur_layer_max_acc:
            cur_layer_dims = layers_dims
            cur_layer_max_acc = acc_test
    if cur_layer_max_acc > max_accuracy:
        max_accuracy = cur_layer_max_acc
        tar_layers_dims = cur_layer_dims
    print ("--------------" + str(layer_length) + " hidden layer winer -------------")
    print(str(cur_layer_dims) + ", accuracy " + str(cur_layer_max_acc))
    print()

print ("--------------final win -------------")
print(str(final_layers_dims) + ", accuracy " + str(max_accuracy))

作者:西部小笼包

原文链接:https://www.jianshu.com/p/0c139796f472

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐