Deep Learning笔记1

逻辑回归与神经网络

Posted by mingjie on March 14, 2018

逻辑回归

Binary Classification

Binary Classification指的是输入一堆东西,最后给出一个只有两个结果(0或者1)的输出的分类过程。一个典型而符合惯例的例子是判断某张图片上有没有猫,也就是将图片的红绿蓝像素值矩阵作为输入,是猫(1)以及不是猫(0)作为输出。

在这里我们不考虑这么多细节,直接将红绿蓝的像素排成一列,作为输入的大小就是总的像素数或者

如果按照位置来排呢?

需要在开始之前明确一些记号。

我们在寻找解的时候的输入为,当然就是的组合了。其中是一个行的列向量,即;而。当然这只是一个样本,而我们一般会有很多的训练样本(training set):即为训练样本的数量;当我们需要明确的时候我们会把训练样本数和检验样本(test set)数写成

这么写当然比较麻烦,所以我们有矩阵写法。首先将所有的排成一行,就成了一个列的矩阵。数学语言表达就是,python表达就是X.shape = (nx, m)。把转置也不是不行,不过会比较麻烦,就不用了。

相应的所有的label也这么排,就成了一个一行列的“矩阵”;或者说Y.shape = (1, m)

更多的记号可参阅课程给的pdf。

逻辑回归

实际上逻辑回归是在二项分布之下的一个概念,是实现二项分布的一个方法。我们需要的是在上面的定义下,构造一个盒子,接收输入, 并输出在输入的情况下二项分布结果为1的概率。然后我们有相应的参数

构造输出的第一反应应该是

这其实就是普通的线性回归。但是我们要求是一个概率,取值在0到1之间,而线性回归不仅不能保证结果小于1还会出现负的结果,当然是不对的。

那怎么办?很简单,给线性回归的结果套一个值域在0到1之间的函数不就行了呗。这种函数一般都跟指数有关,所以我们有sigmod函数:

图像请看截图的左下角。

有了sigmod函数之后,只要令

就行了。加上了sigmod函数之后的回归就叫逻辑回归。有的方法把也放进里面,不过这里不这么做。

代价/损失函数

代价/损失函数就是用来训练上面提到的的。这个函数越小,模型的效果越好(对于训练样本来说)。这里定义的损失函数只是对于一个训练样本来说的,而代价函数是对于整个训练样本集在某一堆参数下的评价。

学过线性拟合的同学们马上又该反应过来,一个典型的代价函数就是。但是这个函数的效果并不好,所以我们实际上用的是这个:

大致地说为什么代价函数的样子是这样的原因是,差值的平方会导致函数非凸,但是上面的定义加上sigmod函数会使得是个凸函数,没有局部极值。

对应的代价函数我们选择这个:

只要找到最小化,就行了。实际上逻辑回归就是一个小型的(可能还是最简单的)神经网络。

如何训练参数?

最小化就是在这个表面上(简单来说)迭代地找到这个函数的最小值以及对应的参数。又该反应过来我们需要用到导数来指导我们向哪里走了。具体来说,是计算这个:

被称为学习速率,是人选的;而后面的俩偏导数是需要计算的,在python中会被命名为dw, db

正反向传递

具体请看截图。正向传递的意思是从具体参数计算代价函数值的过程(蓝色、从左到右),而反向传递指的是计算导数的过程(红色、从右到左)。

导数的具体计算

我们举一个简单的例子:

自然地

在代码中我们基本上会用导数的分母作为变量名字。

之后我们需要算的就是代价函数了。其实也没什么,就拿一个变量做代表吧:

具体的计算过程就是一个正向+反向传递的过程:

# 设置初值
J = 0; dw1 = 0; ...; db = 0
for i in np.arange(1,m+1):
  # Forward propagation
  z[i] = w.T * x[i] + b
  a[i] = sigmod(z[i])
  J += L(a[i], y[i])
  # Backward propagation
  dz[i] = a[i] - y[i]
  dw1 += x1[i]dz[i]
  ...
  db += dz[i]
J /= m; dw1 /= m; ...; db /= m
w1 = w1 - alpha * dw1
...
b = b - alpha * db

然后继续迭代。所以“正常”情况下要用两个循环嵌套,显然是不现实的,所以要用到python的向量来做。

向量化

w是一个列向量的时候,比如要算z[i] = w.T * x[i] + b的时候,可以这么做:np.dot(w,x)+b。高级一点的话直接和矩阵相乘:np.dot(w,X)+bdz[i] = a[i] - y[i]可以转为dZ = A - Ydw1 += x1[i]dz[i] -> dW = 1/m * X*dZ.Tdb += dz[i] -> 1/m * np.sum(dZ)。还要注意的一点是在reshape的时候最好把行列都写全。

浅神经网络

[1],[2]代表神经网络的层数;(i)代表某一个训练样本(都是上标)。下标不带任何括号,表示某一层中的第几个节点。当有两层的时候,刚开始的输入是,但是之后的输入会变成了这样。

神经网络的命名

输入层、隐层、输出层。。在数层数的时候,不算输入层。每一层都有一套配套。

每一个节点都有两步:线性回归以及丢进sigmod函数里面。所以都有两条公式:

能看到这些式子排得很整齐,那何不矩阵化了呢?

是一个(本层节点数)行(上层节点数)列的矩阵,也就是负责将上层节点的结果转成本层节点的结果。自然是行1列的向量了。最后再将结果各自丢进sigmod函数里面,不说了。

如果写出个通式的话也行:

不过注意就是输入值,没有放入sigmod函数里面。同时矩阵是一个“行矩阵”,每一行是每一个节点的系数的转置

对于整个训练样本

当然可以继续矩阵化啦。把整个训练样本的列向量排成一行(这也是惟一的排法了),结果也这么做,然后大写,就变成了:

或者对于某一层来说

总结一下,是一个列的矩阵,是一个列的矩阵,是一个列的矩阵,本来应该也是一个列的矩阵(每列都是一样),但是在python里面,同行数的向量和矩阵相加时,向量会被自动展成相应的矩阵,所以也就不用写成大写的了。可以留意的是向量/矩阵的偏导数和它自己有相同的形状。

通式:

不同的activation function

sigmod函数的问题是它恒为正,所以算出来的结果不会关于0对称。所以隐藏层一般用而不是sigmod,但是输出层还是用回sigmod。

但是也还是有问题:当很大的时候,斜率很小,所以会降低学习速率;所以也可以用RelU函数(折线),或者将RelU小于0的折线再改小一点。即使斜率为0实际上也还是可以的。

为什么activation function不是线性的?

也就是说为什么要给它套上一个奇怪的函数?

线性的AF意味着从头到脚都是线性的,也就没有意义(人又不是线性思考的233)。例外是在回归里面,当输出不在0到1之间的时候,最后一层可以用线性的。但是其他层还是要用非线性的。

AF的导数

求就是了,又不是不会。按照链式反应(chain rule啦)来求就行了。按照之前的结论其实不用求,有

完全向量化之后出现的以及求和是因为之前对某一个训练样本来说,只能计算损失函数;但是对于整个训练样本集来说,我们计算的是代价函数,而,所以多了点东西。

随机初值

参数全为0的话,所有的节点都是一样(对称)的,就没用了。所以要随机取值。一般会将取为很小的高斯随机数,但是没所谓。很小的高斯随机数是为了取到比较大的导数值,防止落到导数很靠近0的地方。

深神经网络

多层的神经网络和2层的其实是一样的,只不过多了一堆循环而已。

为什么要用深的神经网络?

层数从小到大:简单元素 -> 复杂元素。所以我们需要比较多层,但是每层里面的节点不一定多。

比单层的好用

正向传递

计算不同层数的时候可以用循环。

反向传递

具体做法

因为反向传递的时候有一个,所以在正向传递的时候要将记录下来。

参数和超参数

超参数:人为指定的参数

看超参数和的关系(选多少的时候最小),是一个经验方法。最佳的超参数集可能会随着时间变化(电脑更新了)

这次的笔记本还演示了如何载入另外一个笔记本的函数。