深度学习(1)
动机
万事皆有因果。我之所以想要学习深度学习,是因为深度学习在学术界获得了巨大的成功。现在在数据挖掘顶会上大多数的论文都运用了深度学习。深度学习凭借其优秀的实验结果,即使解释性不强也大受欢迎,大有压过机器学习的势头。
为了个人的发展以及发paper,我其实一直想入门深度学习。但是苦于实验室环境以及导师方向,只能自学。之前也零零散散、断断续续看过一点教程,但是都没有形成系统的知识体系。所以下定决心,利用写文档的手段督促自己保持学习的连续性,同时在这里与大家分享自己的拙见。
说在前面
在此之前,我对深度学习的认识仅限于听过名字而已,什么CNN,RNN根本不懂其中的原理。另外,我的数学也非常薄弱,可能一些简单的推导我都会卡半天才能理解。所以学习深度学习的过程必然是非常痛苦的,我也不知道自己能够坚持到什么时候。如果各位看官觉得我的水平不行,请出门左转。但是也欢迎留下你的高见。
在写这篇文章之前,我已经看过一点吴恩达教授 DeepLearning的课程,所以 to be honest,这里的内容会和他的课程有一些雷同。
交叉熵损失函数
logistic regression: 逻辑回归是解决二分类问题的经典方法,在这里介绍它主要是想引出交叉熵损失函数 : J(W;b)= - \frac{1}{m}\sum_{i=1}^m y_i log \hat{y}_i +(1-y_i)log(1-\hat{y}_i), 其中m是样本数量, y_i是真实的样本类别, \hat{y}_i是样本属于正类的概率。
哦?忘了介绍什么是损失函数了。 作为学习深度学习的童鞋,想必大家对损失函数肯定了解的吧,所以我还是不介绍了!!#¥%……&
当初在学习机器学习的时候,我并没有多想为什么逻辑回归的损失函数是这样的形式,而不是(y-\hat{y})^2。直到后来发现在深度学习中交叉熵损失函数也长这样,我才发现原来事情没这么简单。
首先吴恩达教授在他的课程里给出了一种解释:
我们预测得到的结果\hat{y}实际上样本为正类(y=1)的概率,即P(y=1) = \hat{y}; P(y=0) = 1-\hat{y}。
这两个概率计算公式正好可以合并为:P(y=1) = {\hat{y}}^y({1-\hat{y}})^{1-y}
这个概率计算公式就非常厉害了!!!我们可以想象一下:如果真实label = 1, 则我们希望\hat{y}越大越好;反之,如果真实label = 0,则希望\hat{y}越小越好。而上面这个公式正好可以满足。
然后我们对这个公式求对数:
logP(y=1) = ylog{\hat{y}} + (1-y)log({1-\hat{y}}), 然后对于多个样本进行累加,则得到 \sum_{i=1}^m y_i log \hat{y}_i +(1-y_i)log(1-\hat{y}_i), 姑且记为O好了。 那么现在我们的目标是要最大化O,换句话讲就是最小化-O,所以,就有了J(W;b)= - \sum_{i=1}^m y_i log \hat{y}_i +(1-y_i)log(1-\hat{y}_i)。
交叉熵函数的梯度下降
现在假设神经网络结构是最简单的单层结构,也就是说所有特征输入经过一定权重累加之后,再通过sigmoid函数求出最后的输出\hat{y}。
用公式表示就是 \hat{y} = sigmoid(W^TX+b),其中W是权重向量,X是特征向量,b是偏执项,可以理解为直线方程y=ax+b中的截距。
深度学习通过对梯度下降法不断调整参数W和b,使得最终的损失函数J(W;b)达到最小。
sigmoid函数:S(x) = \frac{1}{1+e^{-x}},它的导数S'(x) = \frac{e^{-x}}{(1+e^{-x})^2} = S(x)(1-S(x))
首先我们考虑简单的情形,只有一个样本。之后考虑多个样本的时候只要加上累加符号即可:
令L= y log \hat{y}+(1-y)log(1-\hat{y}) , A = W^TX+b,则\hat{y} = S(A),
b的梯度
我们首先看下关于偏执项b的梯度:
\frac{\partial L}{\partial b} = \frac{\partial L}{\partial \hat{y}} \times \frac{\partial \hat{y}}{\partial A} \times \frac{\partial A}{\partial b}
其中:
\frac{\partial L}{\partial \hat{y}} = -\frac{y}{\hat{y}}+\frac{1-y}{1- \hat{y}}
\frac{\partial \hat{y}}{\partial A} = S(A)(1-S(A))=\hat{y}(1- \hat{y})
\frac{\partial A}{\partial b} = 1
\therefore \frac{\partial L}{\partial b} = (-\frac{y}{\hat{y}}+\frac{1-y}{1- \hat{y}}) \times (\hat{y}(1- \hat{y})) \times 1 = -y(1-\hat{y}) + \hat{y}(1-y) = \hat{y}-y
w_i的梯度
然后我们看下关于每一个权重w_j的梯度:
\frac{\partial L}{\partial w_j} = \frac{\partial L}{\partial \hat{y}} \times \frac{\partial \hat{y}}{\partial A} \times \frac{\partial A}{\partial w_j}
其中:
\frac{\partial L}{\partial \hat{y}} = \frac{y}{\hat{y}}+\frac{1-y}{1- \hat{y}}
\frac{\partial \hat{y}}{\partial A} = S(A)(1-S(A))=\hat{y}(1- \hat{y})
\frac{\partial A}{\partial w_j} = x_j
\therefore \frac{\partial L}{\partial w_j} = (-\frac{y}{\hat{y}}+\frac{1-y}{1- \hat{y}}) \times (\hat{y}(1- \hat{y})) \times x_j = [-y(1-\hat{y}) + \hat{y}(1-y) ] \times x_j= (\hat{y}-y)x_j
多个样本的梯度
上面考虑只有一个样本的情况,现在考虑所有的样本时,只需要累加起来即可。
\frac{\partial J}{\partial b} = \frac{1}{m}\sum_{i=1}^m\hat{y}^{(i)}-y^{(i)}
\frac{\partial J}{\partial w_j} = \frac{1}{m}\sum_{i=1}^m(\hat{y}^{(i)}-y^{(i)})x^{(i)}_j
这里的上标(i)表示第i个样本, 下标j表示第j维的特征。
最后更新w_i和b:
w_j = w_j - \alpha \frac{\partial L}{\partial w_j}
b = b - \alpha \frac{\partial L}{\partial b}
向量化
为了方便大家的理解,上面的内容使用累加符号对每个样本进行计算,比较直观。如果按照上面的思路,在代码实现时相信大家肯定会优先想到使用for循环。但是在所有编程语言中,for循环的效率并不高,而使用向量化的操作能够大大加快代码的运行效率。所以,在这一节,将介绍如何把上面的公式转化成向量的表达形式。
因为电脑编辑矩阵比较麻烦,所以我手写了一份资料。
vector.jpeg
现在假设有样本 \bold{X},维度是 n*m,表示有m个样本,每个样本有n维特征,每一列代表一个完整的样本;
标签数据\bold{Y},维度是1 \times m,表示每个样本的真实类别。
另外权重矩阵W是 n \times 1维的,每一行是单独的权重w_j
然后直接利用矩阵乘法来代替原来的for循环。
最后的\hat{y} = W^TX+b
\frac{\partial L}{\partial b} =\frac{1}{m}sum (\hat{y}-Y)
\frac{\partial L}{\partial w} = \frac{1}{m}X(\hat{y}-Y)^T
从我个人学习感受来讲,这里需要注意的是原来的X矩阵中 一列代表一个样本,而不是一行代表一个样本。 然后关于权重的梯度下降向量化公式中要注意是X矩阵在前,然后要对(\hat{y}-Y)转置。
python代码实现
import numpy as np
##定义sigmoid函数
def sigmoid(x):
return 1/(1+np.exp(-x))
##初始化参数
def initialization_parameters(dim):
w = np.zeros((dim,1))
b= 0.0
return w, b
##前向传播
def forward_propagate(w,b, X,Y):
m = X.shape[1]
A = np.dot(w.T, X) + b
y_hat = sigmoid(A)
cost = -1/m * np.sum(Y*np.log(y_hat) + (1-Y)*np.log(1-y_hat))
##计算梯度
dw = np.dot(X, (y_hat-Y).T)/m
db = np.sum(y_hat-Y)/m
grad = { 'dw':dw,
'db':db
}
return grad, cost
##后向传播,调整参数
def back_propagate(w,b,grad,alpha):
dw = grad['dw']
db = grad['db']
w = w - alpha*dw
b = b - alpha*db
return w,b