甲乙小朋友的房子

甲乙小朋友很笨,但甲乙小朋友不会放弃

0%

Follow up in Code Interview

必做

1
2
3
4
5
401. 排序矩阵中的从小到大第k个数 中等27 
402. 和大于S的最小子数组 中等27 ——209 Minimum Size Subarray Sum
403. 最长无重复字符的子串 中等23 —— 3 Longest Substring Without Repeating Characters
404. 最多有k个不同字符的最长子字符串 中等27 —— 340,锁,Longest Substring with At Most K Distinct Characters
405. 两个排序数组和的第K小 困难 ——373 Find K Pairs with Smallest Sums

选做

1
2
3
4
5
6
543. N数组第K大元素 容易
544. 两数和-小于或等于目标值 中等
545. 无序数组K小元素 中等
546. 三角形计数 中等
547. 最小子串覆盖 中等
548. 第k大元素 中等

高级数据结构-上

lintcode 知识点 leetcode
437. 图是否是树 并查集 261 Graph Valid Tree
433. 岛屿的个数 并查集 200 Number of Islands
442. 岛屿的个数II 并查集 711 Number of Distinct Islands II
443. 单词搜索 II 并查集 212 word search 2
441. 单词搜索 DFS 79 word search
l 知识点 leetcode
438. 单词的添加与查找 Trie 211 Add and Search Word - Data structure design
438. 实现 Trie Trie 208 Implement Trie (Prefix Tree)

必做

选做

1
2
3
4
5
6
477. 被围绕的区域 中等
478. 拼字游戏 困难
479. 单词矩阵 困难
480. 两个排序数组和的第K小 困难
481. 统计前面比自己小的数的个数 困难
482. 区间求和 II 困难

高级数据结构-下

必做

1
2
3
4
5
6
7
8
9
10
11
575. 表达式展开 中等37 
576. 接雨水 中等41
577. 用栈实现队列 中等33
578. 带最小值操作的栈 中等18
579. 滑动窗口的中位数 困难25
580. 接雨水 II 困难28
581. 最大矩形 困难33
582. 最大树 困难27
583. 直方图最大矩形覆盖 困难29
584. 数据流中位数 困难27
585. 滑动窗口的最大值 超难

选做

1
2
3
4
5
6
7
8
9
10
11
12
13
475. 二叉树的最大路径和 II 中等
476. 堆化 中等
477. 用栈实现队列 中等
478. 带最小值操作的栈 中等
479. K步编辑 困难
480. 最大矩形 困难
481. 表达树构造 困难
482. 将表达式转换为逆波兰表达式 困难
483. 将表达式转换为波兰表达式 困难
484. 表达式求值 困难
485. 最大树 困难
486. 直方图最大矩形覆盖 困难
487. 大楼轮廓 超难

Binary Search + Sweep Line

1
2
3
4
5
6
7
8
9
10
11
12
必做

141. x的平方根 容易17
142. 最大平均值子数组 中等32
143. 对x开根II 中等26
144. 数飞机 中等17
145. 两个整数相除 中等49
146. 寻找峰值 中等34
147. 第一个错误的代码版本 中等29
148. 书籍复印 困难37
149. 找峰值 II 困难23
150. 木材加工 困难

选做

1
2
3
633. 寻找重复的数 中等
634. 包裹黑色像素点的最小矩形 困难
635. 大楼轮廓 超难

动态规划-上

必做

1
2
3
4
5
6
7
8
9
10
397. 最长上升连续子序列 容易39 
398. 最大子数组 容易29
399. 最大正方形 中等28
400. 最长回文子串 中等32
401. 硬币排成线 II 中等42
402. 硬币排成线 中等33
403. 打劫房屋 中等30
404. 乘积最大子序列 中等30
405. 最长上升子序列 中等28
406. 最长上升连续子序列 II 困难

选做

1
2
3
4
5
6
7
631. 最大矩阵II 中等
632. 最长重复子序列 中等
633. 书籍复印 II 困难
634. 书籍复印 困难
635. 邮局问题 困难
636. 硬币排成线 III 困难
637. 买卖股票的最佳时机 IV 困难

动态规划 - 下

面试当中的常见算法拓展

堆(heap)又被为优先队列(priority queue)。尽管名为优先队列,但堆并不是队列。回忆一下,在队列中,我们可以进行的限定操作是dequeue和enqueue。dequeue是按照进入队列的先后顺序来取出元素。而在堆中,我们不是按照元素进入队列的先后顺序取出元素的,而是按照元素的优先级取出元素。

这就好像候机的时候,无论谁先到达候机厅,总是头等舱的乘客先登机,然后是商务舱的乘客,最后是经济舱的乘客。每个乘客都有头等舱、商务舱、经济舱三种个键值(key)中的一个。头等舱->商务舱->经济舱依次享有从高到低的优先级。

Linux内核中的调度器(scheduler)会按照各个进程的优先级来安排CPU执行哪一个进程。计算机中通常有多个进程,每个进程有不同的优先级(该优先级的计算会综合多个因素,比如进程所需要耗费的时间,进程已经等待的时间,用户的优先级,用户设定的进程优先程度等等)。内核会找到优先级最高的进程,并执行。如果有优先级更高的进程被提交,那么调度器会转而安排该进程运行。优先级比较低的进程则会等待。“堆”是实现调度器的理想数据结构。

阅读全文 »

在说钢条切割问题之前,我们先说说动态规划。

动态规划——Dynamic programming(这个词指表格):通过组合子问题的解求解原问题。

与分治法对比:

  1. 相同点:都是通过子问题组合求解原问题
  2. 不同点:分治法将问题划分为不相交的子问题,求解再合并。动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题,此时如果用分治法就会出现重复计算求解。为了避免重复动态规划对子问题只求解一次,将其保存在表格中,从而无需每求解一个子子问题时重复计算。

求解步骤

  1. 刻画最优解的结构特征
  2. 递归定义最优解的值
  3. 计算最优解的值,通常采用自底向上的方法(可能需要同时维护一些额外信息)
  4. 利用计算出的信息构造最优解(不是必须)

钢条切割

Serling公司购买一根长钢管,将其切割成短钢管出售,给定钢管长度和对应的价钱如下表:

img

问题要求根据上面的价格,给出最佳的切割方案,使得收益最大。

以n=4为例,可以将钢条切割成如下图所示的8种情况,其中收益最大的是(c):

img

这个题的切入点非常重要,也就是——当知道长度为1到i的钢条的切割方案时,可以推导出长度为i+1的钢条如何切割最优。

我们定义长度=i,且长度为i时最优切割方案的收益是\(r_i\)

  • i=1时, 当然不切割,即\(r_1=p_1\)
  • i=2时,有两种方案,要么切一刀,要么不切,即\(r_2=max[p_2,r_1+r_1]\)
  • i=3时,我们从下图可以看到,假设我们从它上面随便切一刀,我们会发现无论是左边还是右边都是已知的。即\(r_3=max[p_3,r_1+r_2,r_2+r_1]\)
  • i=4时,还是可以看到,无论我们从哪里切一刀,会发现无论是左边还是右边,都是已知的!即\(r_4=max[p_4,r_1+r_3,r_2+r_2,r_3+r_1]\)

因此,我们可以用更短的钢条的最优切割收益来描述它:

\[r_n = max[p_n,r_1+r_{n-1},r_2+r_{n-2},...,r_{n-1}+r_1]\]

代码:

1
2
3
4
5
6
7
8
9
10
11
public int[] q;
private void solve(int n,int k,int[] p){
if(k>n)return;
//长度k的钢条
q[k] = p[k];
//遍历,也就是计算max = [r_1+r_{n-1},r_2+r_{n-2},...,r_{n-1}+r_1]
for(int i=1;i<k;i+=1){
q[k] = Math.max(q[i]+ q[k-i], q[k]);
}
solve(n,k+1,p);
}

钢条切割升级版

《算法导论》练习题15.1-3提出:除了切割下的钢条段具有不同价格\(p_i\)外,每次切割还要付出固定成本\(c\)。求修改后的钢条切割问题。

1
2
3
4
5
6
7
8
9
10
11
public int[] q;

private void solve(int n,int k,int[] p,int c){
if(k>n)return;
//长度k的钢条
q[k] = p[k];
for(int i=1;i<k;i+=1){
q[k] = Math.max(q[i]+ q[k-i] - c, q[k]);
}
solve(n,k+1,p,c);
}

一般来说,图片是非常大的。至少有\(n\times n\times 3\)的像素,即有这么多特征。 对于小尺寸的图片问题,也许我们用深度神经网络的结构可以较为简单的解决一定的问题。但是当应用在大尺寸的图片上,输入规模将变得十分庞大,使用神经网络将会有非常多的参数需要去学习,这个时候神经网络就不再适用。

卷积神经网络在计算机视觉问题上是一个非常好的网络结构。

概述

图像识别过程

当我们给定一个"X"的图案,计算机怎么识别这个图案就是“X”呢?一个可能的办法就是计算机存储一张标准的“X”图案,然后把需要识别的未知图案跟标准"X"图案进行比对,如果二者一致,则判定未知图案即是一个"X"图案。

而且即便未知图案可能有一些平移或稍稍变形,依然能辨别出它是一个X图案。如此,CNN是把未知图案和标准X图案一个局部一个局部的对比,如下图所示:

而未知图案的局部和标准X图案的局部一个一个比对时的计算过程,便是卷积操作。卷积计算结果为1表示匹配,否则不匹配。

图像边缘检测

卷积运算是卷积神经网络的基本组成部分。下面以边缘检测的例子来介绍卷积运算。

所谓边缘检测,在下面的图中,分别通过垂直边缘检测和水平边缘检测得到不同的结果:

垂直边缘检测:

假设对于一个 6×6 大小的图片(以数字表示),以及一个 3×3 大小的 filter(卷积核) 进行卷积运算,以“∗”符号表示。图片和垂直边缘检测器分别如左和中矩阵所示:

filter 不断地和其大小相同的部分做【对应元素的乘法运算并求和】,最终得到的数字相当于新图片的一个像素值,如右矩阵所示,最终得到一个 4×4 大小的图片。

边缘检测的原理:

以一个有一条垂直边缘线的简单图片来说明。通过垂直边缘 filter 我们得到的最终结果图片可以明显地将边缘和非边缘区分出来:

卷积运算提供了一个方便的方法来检测图像中的边缘,成为卷积神经网络中重要的一部分。

多种边缘检测:

垂直和水平边缘检测

更复杂的filter

对于复杂的图片,我们可以直接将filter中的数字直接看作是需要学习的参数,其可以学习到对于图片检测相比上面filter更好的更复杂的filter,如相对于水平和垂直检测器,我们训练的 filter 参数也许可以知道不同角度的边缘。

通过卷积运算,在卷积神经网络中通过反向传播算法,可以学习到相应于目标结果的filter,将其应用于整个图片,输出其提取到的所有有用的特征。

卷积网络结构

本节主要来自参考文献CNN笔记:通俗理解卷积神经网络

cs231n课程里给出了卷积神经网络各个层级结构,如下图:

img

图中CNN要做的事情是:给定一张图片,是车还是马未知,是什么车也未知,现在需要模型判断这张图片里具体是一个什么东西,总之输出一个结果:如果是车,那是什么车。

上图的网络结构为:

  • 最左边是数据输入层,对数据做一些处理,比如去均值(把输入数据各个维度都中心化为0,避免数据过多偏差,影响训练效果)、归一化(把所有的数据都归一到同样的范围)、PCA/白化等等。CNN只对训练集做“去均值”这一步。
  • 中间是
    • CONV:卷积计算层,线性乘积求和。
    • ReLU:激励层,上文2.2节中有提到:ReLU是激活函数的一种。
    • POOL:池化层,简言之,即取区域平均或最大。
  • 最右边是
    • FC:全连接层

这几个部分中,卷积计算层是CNN的核心,下文将重点阐述。

接下来,我们详细介绍一下这几个部分。

卷积网络结构

卷积网络结构如下所示:

1
2
3
4
5

输入层 --> 卷积计算层 --> 激励层 --> 池化层
--> 卷积计算层 --> 激励层 --> 池化层
--> ...
-->全连接层

卷积网络中一个典型层包括三级:卷积计算层、激励层(探测层)和池化层。

通过上一步的卷积运算,然后经过了激活函数,我们将此时的输入会输入池化层。

卷积层:并行计算多个卷积,产生一组线性激活响应;

激励层(探测层)(detector stage):每个线性激活响应会通过一个非线性激活函数;

池化层:我们使用池化(pooling)函数来进一步调整这一层的输出。

卷积计算层

卷积计算层最重要的就是卷积运算。接下来我们介绍卷积运算。

卷积运算

假设我们在用激光传感器追踪宇宙飞船的位置,\(t\)时刻位置在\(x(t)\)

为了更好地估计,我们将时间越近的测量给予更高的权重\(w(a)\),其中\(a\)表示测量结果距当前的时间间隔,那么:

\[s(t)=\int x(a)w(t-a)da = (x*w)(t)\]

tips: \(a\):距离当前时间的间隔; \(x(a)\)\(a\)时刻,飞船位置,\(x\)也叫输入 \(w(t-a)\)\(t-a\)时刻,也就是a秒前,飞船的权重,也是一种概率密度,叫做核函数

离散形式的卷积是: \[s(t)=(x*w)(t)=\sum_{\alpha=-\infty}^{\infty}x(a)w(t-a)\]

二维形式的卷积(我们定义核为K):

\[S(i,j)=(I*K)(i,j)=\sum_m \sum_n I(m,n)K(i-m,j-n)\]

卷积是可交换的,也可写作:

\[S(i,j)=(K*I)(i,j)=\sum_m \sum_n I(i-m,j-n)K(m,n)\]

出现上面可交换的原因是:我们将核的相对输入进行了翻转,也相当于一种变量替换;

然而,在许多神经网络中采用的是互相关函数(cross-correlation)

\[S(i,j)=(I*K)(i,j)=\sum_m \sum_n I(i+m,j+n)K(m,n)\]

这个是不可交换的。

在数学定义上,矩阵的卷积(convolution)操作为首先将卷积核同时在水平和垂直方向上进行翻转,构成一个卷积核的镜像,然后使用该镜像再和前面的矩阵进行移动相乘求和操作。如下面例子所示:

在深度学习中,我们称为的卷积运算实则没有卷积核变换为镜像的这一步操作,因为在权重学习的角度,变换是没有必要的。深度学习的卷积操作在数学上准确度来说称为互相关(cross-correlation)。

一般来说,图像领域的卷积用的就是互相关函数。参考文献CNN笔记:通俗理解卷积神经网络对卷积的定义是:对图像(不同的数据窗口数据)和滤波矩阵(一组固定的权重:因为每个神经元的多个权重固定,所以又可以看做一个恒定的滤波器filter)做内积(逐个元素相乘再求和)的操作就是所谓的『卷积』操作,也是卷积神经网络的名字来源。


卷积计算层

举个具体的例子。比如下图中,图中左边部分是原始输入数据,图中中间部分是滤波器filter(有个高大上的名字叫卷积核),图中右边是输出的新的二维数据。

即将下面 两个矩阵对应位置先相乘,后相加:

\[*\] \[=\]

在CNN中,滤波器filter(带着一组固定权重的神经元)对局部输入数据进行卷积计算。每计算完一个数据窗口内的局部数据后,数据窗口不断平移滑动,直到计算完所有数据。这个过程中,有这么几个参数:

Depth-深度

神经元个数,决定输出的depth厚度。同时代表滤波器个数。

Stride-步长

决定滑动多少步可以到边缘。

以s表示stride的大小,那么在进行卷积运算后,图片的变化为:\(n×n –> ⌊\frac{n+2p−f}{s}+1⌋×⌊\frac{n+2p−f}{s}+1⌋\)

注意,在当padding≠1时,若移动的窗口落在图片外面时,则不要再进行相乘的操作,丢弃边缘的数值信息,所以输出图片的最终维度为向下取整

Padding-填充值

在外围边缘补充若干圈0,方便从初始位置以步长为单位可以刚好滑到末尾位置,通俗地讲就是为了总长能被步长整除。

没有Padding的缺点 :

  1. 每次卷积操作,图片会缩小; 就前面的例子来说,6×6 大小的图片,经过 3×3 大小的 filter,缩小成了 4×4 大小 。图片:n×n –> (n−f+1)×(n−f+1)
  2. 角落和边缘位置的像素进行卷积运算的次数少,可能会丢失有用信息。 其中,n表示图片的长或宽的大小,f表示filter的长或宽的大小。

有Padding :

  1. 为了解决上面的两个缺点,我们在进行卷积运算前为图片加padding,包围角落和边缘的像素,使得通过filter的卷积运算后,图片大小不变,也不会丢失角落和边沿的信息。

以p表示 Padding 的值,则输入n×n大小的图片,最终得到的图片大小为 (n+2p−f+1)×(n+2p−f+1),为使图片大小保持不变,需根据filter的大小调整p的值。

卷积计算的过程

对于灰色图像中,卷积核和图像均是二维的。而应用于彩色图像中,因为图片有R、G、B三个颜色通道,所以此时的卷积核应为三维卷积核

单个卷积核应用于图片时,提取图片特定的特征,不同的卷积核提取不同的特征。如两个大小均为3×3×3 的卷积核分别提取图片的垂直边缘和水平边缘。

由图可知,最终提取到彩色图片的垂直特征图和水平特征图,得到有2个通道的4×4大小的特征图片。

这张gif诠释了三维、两个卷积核的卷积过程:

输入图像:三维

卷积核W0和W1:分别都是三维

可以看到:

  • 两个神经元,即depth=2,意味着有两个滤波器。
  • 数据窗口每次移动两个步长取3*3的局部数据,即stride=2。
  • zero-padding=1。

然后分别以两个滤波器filter为轴滑动数组进行卷积计算,得到两组不同的结果。

​ 如果初看上图,可能不一定能立马理解啥意思,但结合上文的内容后,理解这个动图已经不是很困难的事情:

  • 蓝色输入(**7*7*3**中,7*7代表图像的像素/长宽,3代表R、G、B 三个颜色通道)
  • 红色是两个不同的滤波器Filter w0、Filter w1
  • 绿色则是两个不同的输出,每一个格子都等于一次滤波窗口内的内积和(RGB三通道相加)

​ 随着左边数据窗口的平移滑动,滤波器Filter w0 / Filter w1对不同的局部数据进行卷积计算。如果这一部分看不明白,可以继续看CNN笔记:通俗理解卷积神经网络,这里讲的非常详细。

单层卷积网络

和普通的神经网络单层前向传播的过程类似,卷积神经网络也是一个先由输入和权重及偏置做线性运算,然后得到的结果输入一个激活函数中,得到最终的输出:

\[z^{[1]}=w^{[1]}a^{[0]}+b^{[1]}\]

\[a^{[1]}=g(z^{[1]})\]

不同点是在卷积神经网络中,权重和输入进行的是卷积运算。

单层卷积的参数个数:

在一个卷积层中,如果我们有10个 3×3×3 大小的卷积核,那么加上每个卷积核对应的偏置,则对于一个卷积层,我们共有的参数个数为:

\[(3×3×3+1)×10=280\]

无论图片大小是多少,该例子中的卷积层参数个数一直都是280个,相对于普通的神经网络,卷积神经网络的参数个数要少很多。

多层卷积网络

多层卷积构成卷积神经网络,下面是一个卷积神经网络的例子:

激励层

几种不同的激活函数\(g(x)\)

  • sigmoid:

    \[a = \frac{1}{1+e^{-z}}\]

    导数:\(a′=a(1−a)\)

  • tanh:

    \[a = \frac{e^z - e^{-z}}{e^z + e{-z}}\]

    导数:\(a′=1−a2\)

  • ReLU(修正线性单元): \[a=max(0,z)\]

  • Leaky ReLU: \[a=max(0.01z,z)\]

激活函数的选择

sigmoid函数和tanh函数比较:

  • 隐藏层:tanh函数的表现要好于sigmoid函数,因为tanh取值范围为[−1,+1],输出分布在0值的附近,均值为0,从隐藏层到输出层数据起到了归一化(均值为0)的效果。
  • 输出层:对于二分类任务的输出取值为{0,1},故一般会选择sigmoid函数。

然而sigmoid和tanh函数在当|z|很大的时候,梯度会很小,在依据梯度的算法中,更新在后期会变得很慢。在实际应用中,要使|z|尽可能的落在0值附近。

ReLU弥补了前两者的缺陷,当z>0时,梯度始终为1,从而提高神经网络基于梯度算法的运算速度。然而当z<0时,梯度一直为0,但是实际的运用中,该缺陷的影响不是很大。

Leaky ReLU保证在z<0的时候,梯度仍然不为0。

在选择激活函数的时候,如果在不知道该选什么的时候就选择ReLU,当然也没有固定答案,要依据实际问题在交叉验证集合中进行验证分析。

池化层

池化函数

使用某一位置的相邻输出的总体统计特征来代替网络再该位置的输出。

最大池化函数:给出相邻矩形区域内的最大值。

在最大池化中,有一组超参数需要进行调整,其中,$f \(表示池化的大小,\)s$表示步长。

  • 池化前:\(n×n\)
  • 池化后:\(⌊\frac{n+2p−f}{s}+1⌋×⌊\frac{n+2p−f}{s}+1⌋\)

此外还有平均池化、最小池化等等。注意,池化层没有需要学习的参数。

池化的用途

池化函数帮助输入近似不变:即当我们对输入进行少量平移时,经过池化函数后的大多数输出并不会发生改变。通俗地说就是——为了让我们的网络具有平移不变形(我的理解是无论输入轻微旋转或平移,输出都不变),我们引入池化这个骚操作来达到这个目的。

例如下图所示的例子。上图是一个网络,下图是一个网络,每个网络的下层是非线性输出,上层是最大池化输出。下图的非线性输出是通过向右平移一个像素得到的。我们可以发现,池化层的输出只有一半发生了改变,这是因为最大池化单元只对周围的最大值较敏感,而不是精确的位置。

当我们只关心某个特征是否出现(比如是否有眼睛),而不关心它的具体位置时,局部平移不变性是一个非常有用的性质。

对空间区域进行池化产生了平移不变性。下图是一种学习不变性的实例:反映的是池化的旋转不变性,对于输入手写5,有三个滤波器分别检测选择不同角度的手写5。当滤波器和对应的手写5匹配时,滤波器会得到一个较大的激活值,然后池化会选择得到最大的激活值,无论手写5是怎样的旋转的。

总的来说就是:通过卷积后,为了引入不变性,同时防止过拟合问题或欠拟合问题、降低计算量,我们常进行池化处理。

卷积网络的优势

我们先说一下卷积网络的概念:卷积神经网络(Convolutional Neural Networks)的卷积操作是通过可训练的滤波器对上一层的输出进行卷积求和,然后添加上偏移量经过激活函数,得到了特征映射图作为下一层的输入。卷积操作相对于传统神经网络主要有稀疏链接、权值共享和等变表达的特性。

卷积通过三个重要思想来帮助改进机器学习系统:稀疏交互、参数共享、等变表示。

与普通的全连接神经网络相比,卷积神经网络的参数更少。如图中的例子,卷积神经网络仅有6×(5×5+1)=156个参数,而普通的全连接网络有3072×4704≈14M个参数。

稀疏交互

对于一张图像来说,输入图像可能包含上千万像素点。但我们可以通过只占用几十或几百的核来检测一些小的但有意义的特征,例如图像边缘。即——在每一层中,每个输出值只取决于少量的输入,也就是稀疏交互。

下图是一种稀疏连接的例子,从下往上看,我们强调了一个输入单元\(x_3\)以及\(s\)中受该单元影响的输出单元。这个是当\(s\)由核宽度为3的卷积产生的,只有3个输出受到了\(x\)的影响:

从另一个角度,从上往下看,这次我们抢到了一个输出单元\(s_3\)以及\(x\)中影响该单元的输入单元。这些单元被称为\(s_3\)接受域

从深层的网络来看,我们可以看到尽管连接稀疏,但处在更深层的单元可以间接地连接到全部或大部分输入图像中。

参数共享

一个特征检测器(filter)对图片的一部分有用的同时也有可能对图片的另外一部分有用。

因为核的每一个元素都作用在输入的每一个位置上,因此卷积运算会导致用于一个输入的权重也会被绑定在其它权重上。这样的参数共享保证了我们只需要学习一个参数集合,而不是对每一个位置都需要学习一个单独的参数集合。如下图所示,黑色箭头表示两个不同模型中使用了特殊的参数连接。灰色箭头表示它用了黑色箭头的参数。其实就是一个\(x\)只有一个参数,但这个参数被用于了多个下一层。

对于卷积,参数共享的特殊形式使得神经网络层具有对平移等变的性质:先平移后卷积=先卷积后平移。

边缘检测的例子

如图所示,我们使用每个像素减去左边相邻像素形成的。这其实就是一种最简单的卷积。

训练卷积神经网络

我们将训练集输入到卷积神经网络中,对网络进行训练。利用梯度下降(Adam、momentum等优化算法)最小化代价函数来寻找网络最优的参数。

复杂度

卷积网络的时间复杂度

单个卷积层的时间复杂度

\[O(M^2 K^2 C_{in}C_{out})\]

  • M : 每个卷积核输出特征图(feature map)的边长
    • 输出特征图尺寸本身又由输入矩阵尺寸 X 、卷积核尺寸K、填充大小Padding、步长Stride 这四个参数所决定,表示如下: \[M = (X - K + 2 * Padding) / Stride + 1\]
  • K : 每个卷积核的边长
  • Cin : 每个卷积核的通道数,也即输入通道数,也即上一层的输出通道数
  • Cout : 本卷积层具有的卷积核个数,也即输出通道数

卷积神经网络整体的时间复杂度

\[O\sum_{l = 1}^D(M^2_l K_l ^2 C_{l-1}C_{l})\]

  • D : 网络深度

卷积网络的空间复杂度

\[O(\sum_{l = 1}^D K_l^2 C_{l - 1}C_l)\]

  • 与输入数据大小无关
  • 当我们需要裁剪模型时,由于卷积核的尺寸通常已经很小,而网络的深度又与模型的能力紧密相关,不宜过多削减,因此模型裁剪通常最先下手的地方就是通道数

参考文献

  1. 深度学习
  2. 达观数据深度学习
  3. 深度学习笔记(一)卷积神经网络(Convolutional Neural Networks)
  4. CNN笔记:通俗理解卷积神经网络
  5. 卷积神经网络的复杂度分析

其实在之前的博客朴素贝叶斯的理解一文中曾经提到过最大似然估计。

极大似然估计的核心思想是:我们已知\(x\)已发生,我们再根据实际情况写出\(x\)发生的概率\(p(x;θ)\)。目标函数是使得这个概率\(p(x;θ)\)最大,然后求得\(θ\)

\[\theta_{ML}=arg max_\theta p(X;\theta)=arg max_\theta ∏_{i=1}^mp(x^{i};\theta)\] 上公式中: \(p(X;\theta)\)是样本集\(X\)出现的概率; \(p(x^{i};\theta)\)是某个样本出现的概率; 左式等于右式原因是每个样本出现的概率独立;

多个数的连乘容易溢出,我们可以将它转化为log运算: \[\theta_{ML}=arg max_\theta \sum{i=1}^mlog p(x^{i};\theta)\] 将上式除以\(m\),得到一种期望: \[\theta_{ML}=arg max_\theta E_{x\text{~}p'_{data}}log p(x^{i};\theta) \tag{公式1}\] 这个式子的含义是:在经验分布\(x\text{~}p^`\)上,求得一个\(\theta\),使得模型分布的期望最大化。

此时我们暂时先不看上面这个公式。我们从另一个角度——误差来衡量。训练集上的经验分布\(p'_{data}​\)和模型之间的分布差异可以用KL散度衡量:

\[D_{KL}(p'_{data}||p_{model}) = E_{x\text{~}p'_{data}}[logp'_{data}(x)-logp_{model}(x)]\]

我们的目标是使上式最小化。减号左边是在训练集上的,是一个常数;我们只关心右边:

\[\theta_{KL}=argmin_{\theta} -E_{x\text{~}p'_{data}}[logp_{model}(x)]\tag{公式2}\]

很明显我们可以看到,公式2与公式1其实是一样的。我们从这个角度证明了这种度量下的误差与极大似然是相同的。

之前我们在神经网络简介中曾经提到过误差函数。这一节我们总结一下误差函数。

误差函数一般有两种来源:

  1. 如果参数模型定义了一个分布\(p(y|x;\theta)\),我们采用最大似然原理得到代价函数:训练数据和模型预测间的交叉熵。这个在之后会详细解释。
  2. 如果不预测y的完整概率分布,仅仅预测在给定x条件下y的某种统计量,那就用某些专门的损失函数来计算。

方法1,使用最大似然学习条件分布

可以在博客最大似然估计中看到,参数模型定义了一个分布\(p(y|x;\theta)\),为了求得参数,我们使用最大似然原理,得到最终的目标函数是最小化代价函数J——训练数据模型预测间的交叉熵:

\[J(\theta)=-E_{x,y\sim p'_{data}}log p_{model}(y|x)\]

上式的意义是:在\(x,y\)服从训练数据$ p'{data}\(分布下,使得模型的\)-Elog p{model}(y|x)$最小。

用似然解决问题带来的好处:

  1. 当明确了一个模型\(p(y|x)\)时,就自动地确定了代价函数\(logp(y|x)\)
  2. 对数函数能帮我们避免梯度过小(例如有的输出单元有一个指数函数,取对数后梯度就不那么小了)

方法2,简单学习条件统计量

我们用历史的数据,计算出特征x下y发生的概率:\(f(x)=p(y|x)\),将它作为x特征下y的预测。学习这个条件统计量的过程就是我们这节介绍的方法。

均方误差

通过解优化问题:

\[f^*=arg min_f E_{x,y\sim p_{data}}||y-f(x)||^2 \tag{均方误差最小化时的f^{※}}\]

得到\(f^*\),我们用它来进行预测得到:

\[f^*(x)=E_{y\sim p_{data}(y|x)}[y]\tag{将所有服从p_{data}(y|x)的y的y均值作为x特征下y的预测}\]

可以看出,这样得到的函数是可以用来对每个x的值预测出y的均值

平均绝对误差

还有另一种误差叫平均绝对误差,通过解优化问题:

\[f^*=arg min_f E_{x,y \sim p_{data}}||y-f(x)||_1\]

得到的函数,可以对每个x预测y取值的中位数

比较

一般,均方误差和平均绝对误差在梯度下降法表现不好,因为饱和的输出单元梯度非常小。所以一般来说交叉熵代价函数更受欢迎。

之前的博客中简单介绍了Learning to Rank的基本原理,也讲到了Learning to Rank的几类常用的方法:pointwise,pairwise,listwise。这篇博客就pairwise中的RankSVM、GBRank和LambdaRank做简要介绍。

RankSVM是2000年提出的;GBRank是2007年提出的;LambdaMART是2008年提出的。因此我们按照提出顺序来讲解这三种算法。

引言

机器学习一般都是解决分类问题。而在Rank中我们遇到的是排序问题。那么如何将排序问题转化为分类问题成了当下的关键。

如何将排序问题转化为分类问题?

对于一个query-doc pair(检索-文档结果对),我们可以将其用一个feature vector表示:x。而排序函数为f(x),我们根据f(x)的大小来决定哪个doc排在前面,哪个doc排在后面。即如果f(xi) > f(xj),则xi应该排在xj的前面,反之亦然。可以用下面的公式表示:

理论上,f(x)可以是任意函数,为了简单起见,我们假设其为线性函数: 如果这个排序函数f(x)是一个线性函数,那么我们便可以将一个排序问题转化为一个二元分类问题。理由如下: 首先,对于任意两个feature vector xi和 xj,在f(x)是线性函数的前提下,下面的关系都是存在的: 然后,便可以对xi和 xj的差值向量考虑二元分类问题。特别地,我们可以对其赋值一个label:

有一个很好的例子说明了如何将排序问题转化为分类问题,在L2R的笔记中已提到过,此处不再多加阐述。

将排序问题转化为分类问题之后, 我们就可以使用常用的机器学习方法解决该问题。

RankSVM

RankSVM的基本思想是,将排序问题转化为pairwise的分类问题,然后使用SVM分类模型进行学习并求解。Ranking SVM使用SVM来进行分类:

其中w为参数向量, x为文档的特征,y为文档对之间的相对相关性, ξ为松弛变量。

对这个公式,知乎上有一个很好的解释: 之前svm为实现软间隔最大化,约束条件里有 。而rank-svm是典型的pairwise方法,考虑两个有偏序关系的文档对,训练样本是xi(1)-xi(2),所以要把约束条件改成 ,由于相减不再需要偏置b。而优化问题中的目标函数和其他约束项不变。

使用Clikthrough数据作为训练数据

T. Joachims提出了一种非常巧妙的方法, 来使用Clickthrough数据作为Ranking SVM的训练数据。

假设给定一个查询"Support Vector Machine", 搜索引擎的返回结果为 其中1, 3, 7三个结果被用户点击过, 其他的则没有。因为返回的结果本身是有序的, 用户更倾向于点击排在前面的结果, 所以用户的点击行为本身是有偏(Bias)的。为了从有偏的点击数据中获得文档的相关信息, 我们认为: 如果一个用户点击了a而没有点击b, 但是b在排序结果中的位置高于a, 则a>b。 所以上面的用户点击行为意味着: 3>2, 7>2, 7>4, 7>5, 7>6。

Ranking SVM的开源实现

Joachims的主页上有Ranking SVM的开源实现。

数据的格式与LIBSVM的输入格式比较相似, 第一列代表文档的相关性, 值越大代表越相关, 第二列代表查询, 后面的代表特征: qid:1 1:1 2:1 3:0 4:0.2 5:0 # 1A qid:1 1:0 2:0 3:1 4:0.1 5:1 # 1B qid:1 1:0 2:1 3:0 4:0.4 5:0 # 1C qid:1 1:0 2:0 3:1 4:0.3 5:0 # 1D
qid:2 1:0 2:0 3:1 4:0.2 5:0 # 2A
qid:2 1:1 2:0 3:1 4:0.4 5:0 # 2B qid:2 1:0 2:0 3:1 4:0.1 5:0 # 2C qid:2 1:0 2:0 3:1 4:0.2 5:0 # 2D
qid:3 1:0 2:0 3:1 4:0.1 5:1 # 3A qid:3 1:1 2:1 3:0 4:0.3 5:0 # 3B qid:3 1:1 2:0 3:0 4:0.4 5:1 # 3C qid:3 1:0 2:1 3:1 4:0.5 5:0 # 3D

GBRank

参考文献GBRank:一种基于回归的学习排序算法 对GBRank做出了较好的解释。原论文是[A Regression Framework for Learning Ranking Functions Using Relative Relevance Judgments]。下面对这个博客和论文进行摘录和整理。

算法原理

一般来说在搜索引擎里面,相关性越高的越应该排在前面。现在 query-doc 的特征使用向量x或者y表示,假设现在有一个文档对<xi,yi>,当xi排在yi前面时,我们使用xi>yi来表示。我们含顺序的 pair 对用如下集合表示(也就是真的xi真的排在yi前面): 现假设学习的排序函数为h,我们希望当h(xi)>h(yi)时,满足xi>yi的数量越多越好。那么如何来评价这个h到底好不好呢,那么我们可以定义h的风险函数为: 对于这个风险函数,我们可以做如下解释。我们的目标是:对于集合S中的某个文档对<xi,yi>来说,h要符合我们之前的设定,也就是:

当h(xi)>h(yi)时,h是正确的,不造成损失; 当h(xi)<h(yi)时,h是不符合预期的,会造成损失,并且损失的大小成残差的平方级别;

将R(h)与每个 pair 对<xi,yi>的cost画成图可表示为:

上述风险函数直接优化比较困难,这里一个巧妙的解决方案:也就是首先固定h(xi)或者h(yi)当中其中的一个,然后再通过回归的方式来解决问题。 为了避免优化函数h是一个常量,我们对风险函数加入一个平滑项τ(0<τ≤10): 其实加上了这个平滑项之后,一来可以防止h变为常数,二来还对损失函数给了更严格的条件:如果希望xi>yi,就得有h(xi)>h(yi)+τ,也就是更为严格了。

接下来我们用Functional Gradient Descent法来求解h.

参考文献Learning to Rank算法介绍:GBRank对这个求解方法做了扼要的介绍:

在GBDT中,Functional Gradient Descent的使用为:将需要求解的F(x)表示成一个additive model,即将一个函数分解为若干个小函数的加和形式,而这每个小函数的产生过程是串行生成的,即每个小函数都是在拟合 loss function在已有的F(x)上的梯度方向(由于训练数据是有限个数的,所以F(x)是离散值的向量,而此梯度方向也表示成一个离散值的向量),然后将拟合的结果函数进一步更新到F(x)中,形成一个新的F(x)。

  1. 将h(xi)和h(xi)作为未知数。梯度下降使得R最小,来求得这些未知数。
  2. 对R计算h的负梯度:
  3. 当pair对<xi,yi>符合条件时,上述梯度为0;反之,他们对应的梯度为:
  4. 接下来,还需要知道如何将梯度作用到h的更新上。通过设定xi的目标值为h(yi)+τ。yi的目标值为h(xi)−τ(这一步我的理解就是首先固定x/y中的某一个,然后去计算另一个)。因此在每轮迭代中,当h不满足<xi,yi>会产生一组数据:

我们需要拟合本轮产生的所有负例。

下面形式化本算法: 可以看到step3里面每轮都拟合误判的结果,在迭代中这个集合会越来越小。还有一种做法是将曾经误判的集合维持在训练集中,那么训练集就会始终增长。在这个步骤中使用GBDT模型进行回归预测,当然其他的回归方法也可以使用。

LambdaMART

在learn to rank的成长过程中,2000提出了SVMRank,2006年提出GBrank,2008年提出lambdaMART。看了几个比较大的框架,我发现现在市面上最常见的learn to rank算法就是LambdaMART了。接下来先介绍一下LambdaMART的原理,然后再介绍xgboost中是怎么写的LambdaMart。

LambdaMart的原理

这一小节主要参考资料LambdaMART 不太简短之介绍

Pointwise和Pairwise类型的LTR算法,将排序问题转化为回归、分类或者有序分类问题。Listwise 类型的 LTR 算法则另辟蹊径,将用户查询(Query)所得的结果作为整体,作为训练用的实例(Instance)。

LambdaMART 是一种 Listwise 类型的 LTR 算法,它基于 LambdaRank 算法和 MART(Multiple Additive Regression Tree)算法,将搜索引擎结果排序问题转化为回归决策树问题。MART实际就是梯度提升决策树(GBDT, Gradient Boosting Decision Tree)算法。GBDT 的核心思想是在不断的迭代中,新一轮迭代产生的回归决策树模型拟合损失函数的梯度,最终将所有的回归决策树叠加得到最终的模型。LambdaMART使用一个特殊的 Lambda 值来代替上述梯度,也就是将LambdaRank算法与MART算法加和起来。考虑到LambdaRank是基于RankNet算法的,所以在搞清楚LambdaMART算法之前,我们首先需要了解 MART、RankNet 和 LambdaRank 是怎么回事。

MART其实也是GBDT。此处对GBDT不再做过多的介绍。值得一提的是,MART并不对损失函数的形式做具体规定。实际上,损失函数几乎只需要满足可导这一条件就可以了。这一点非常重要,意味着我们可以把任何合理的可导函数安插在 MART 模型中。LambdaMART 就是用一个 λ 值代替了损失函数的梯度,将 λ和 MART 结合起来罢了。

LambdaMART是怎么来的

Lambda 的设计,最早是由 LambdaRank 从 RankNet 继承而来。因此,我们先要从 RankNet 讲起。 后记:其实这些文章都是到处整理得来,远不及直接看论文的好。如果有兴趣的话,可以读一读微软整理的这篇论文https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/MSR-TR-2010-82.pdf

RankNet的创新

Ranking常见的评价指标都无法求梯度,因此没法直接对评价指标做梯度下降。

RankNet 的创新之处在于,它将不适宜用梯度下降求解的Ranking问题,转化为对概率的交叉熵损失函数的优化问题,从而适用梯度下降方法。

RankNet的终极目标是得到一个带参的算分函数s = f(x;w)

参考文献机器学习---RankNet.md](https://github.com/MangoLiu/mangoliu.github.io/blob/master/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0---RankNet.md)给了一个非常好的例子:那么如何通过pair来训练,并最终作用到针对point的算分函数上,请看下面的简单例子。假设有以下同一个query下的4个文档,并且有3个特征维度,并对它们进行了人工打分,我们就利用它们来训练model。

point       特征f1  特征f2  特征f3  label     
文档doc1      3       2        1      3(很好) 
文档doc2      1       2        1      2(好) 
文档doc3      1       1        2      1(一般) 
文档doc4      1       0        3      0(不好) 

于是,根据这个算分函数,我们可以根据特征来计算文档xi和xj的得分si和sj: Si = f(xi;w),sj = f(xj,w)

我们并不知道每个特征维度的具体权值w[i]。但我们感觉到要训练出这样的结果,就是使label得分比较高的样本得分尽量大,让label低的样本得分尽量小。 即本例中,我们应该让f(doc1)得分尽量大,f(doc4)得分尽量小。其实也就是f(doc1)-f(doc4)的结果尽量大,因为这个分差实际就包含了前者尽量大后者尽量小的含义。并且之前那个常量b通过作差后,不见了。 这样两两文档即可组成一个文档对,我们把前者好于后者的成为正向文档对;反之称之为负向文档对。正向对表示前者好于后者,负向对表示后者劣于前者。其实表述的是同样的意义。因此,我们只需要正向的对就好了。因为负向对不能再额外提供有意义的信息了。那么我们提取出上面的正向对:

Pair  作差       特征f1 特征f2  特征f3
P12 (doc1-doc2)    2      0       0
P13 (doc1-doc3)    2      1      -1
P14 (doc1-doc4)    2      2      -2
P23 (doc2-doc3)    0      1      -1
P24 (doc2-doc4)    0      2      -2
P34 (doc3-doc4)    0      1      -1

需要注意的是,这里的特征也进行了相减!

我们再引入一个文档对概率,表示文档i好于文档j的概率。我们将它称为两者的偏序概率:(这个东西我理解为是将偏序关系转化为概率的函数,类似于lr里面的sigmod函数)

那么,现在这个问题就转化成了使所有正向对的概率和最大。我们现在已经知道了目标函数,那么接下来就需要一个损失函数来将这个目标函数最优化。 再将以上的交叉熵定义为损失函数: 然后对这个损失函数进行梯度下降: 在以上的方法中,我们将偏序关系转化为目标函数,然后再定义目标函数的损失函数,再通过梯度下降法求参数使得损失函数最小,得到目标函数。那么我们能不能直接定义梯度呢?

LambdaRank

参考文献Learning To Rank之LambdaMART的前世今生对下面这个图有非常详细的解释。此处对一些重点内容进行摘录。 如图所示,每个线条表示文档,蓝色表示相关文档,灰色表示不相关文档。 RankNet以pairwise error的方式计算cost,左图的cost为13,右图通过把第一个相关文档下调3个位置,第二个文档上条5个位置,将cost降为11,但是像NDCG或者ERR等评价指标只关注top k个结果的排序,在优化过程中下调前面相关文档的位置不是我们想要得到的结果。图 1右图左边黑色的箭头表示RankNet下一轮的调序方向和强度,但我们真正需要的是右边红色箭头代表的方向和强度,即更关注靠前位置的相关文档的排序位置的提升。

LambdaRank正是基于这个思想演化而来,其中Lambda指的就是红色箭头,代表下一次迭代优化的方向和强度,也就是梯度。 受LambdaNet的启发,LambdaRank对RankNet的梯度做因式分解: 注意有下面对称性 也就是说:每条文档移动的方向和趋势取决于其他所有与之 label 不同的文档。 现在回过头来看,看看我们做了些什么? - 分析了梯度的物理意义; - 绕开损失函数,直接定义梯度。 当然,我们可以反推一下 LambdaRank 的损失函数:

LambdaMART 现在的情况变成了这样: - MART 是一个框架,缺一个「梯度」; - LambdaRank 定义了一个「梯度」。 于是,就有了 LambdaMART:

Xgboost中的Learning to rank

为了后续方便后续小伙伴们的使用,我将官方文档xgboost的learning to rank文档进行扼要的翻译,并在此贴出。 Xgboost的rank模型是基于lambdaRank的。 XGBoost支持以ranking为目标的学习。在ranking的情况下,数据集一般都需要被格式化为group input: 在ranking中,数据是根据不同的真实场景被分为groups的。例如,在学习web pages的rank场景下,rank page数据是根据不同的queries分到各groups的。

数据形式 基本数据形式train.txt Xgboost接受像libSVM格式数据,例如:

1 101:1.2 102:0.03
0 1:2.1 10001:300 10002:400
0 0:1.3 1:0.3
1 0:0.01 1:0.3
0 0:0.2 1:0.3

每行表示:

label   特征1索引:值 特征2索引:值

groups索引文件train.txt.group 除了group input format,XGboost需要一个索引group信息的文件,索引文件train.txt.group格式如下:

2
3

这意味着,数据集包含5个实例,前两个是一个group,后三个是一个group。

实例权重文件train.txt.weight

XGboost还支持每个实例的权重调整,数据格式如下:

1
0.5
0.5
1
0.5

初始margin文件train.txt.base_margin

XGBoost还可以支持每个实例的初始化margin prediction.例如我们对train.txt可以有一个initial margin file:

-0.4
1.0
3.4

XGBoost will take these values as initial margin prediction and boost from that. An important note about base_margin is that it should be margin prediction before transformation, so if you are doing logistic loss, you will need to put in value before logistic transformation. If you are using XGBoost predictor, use pred_margin=1 to output margin values.

使用Demo:

https://github.com/stegben/kaggle_outbrain/blob/990f5e1cc18ff0f6156a56b9d919ac0d52222268/train_xgb.py

xgboost的pairwiseRank实现

****如何构造pair对? xgboost/src/objective/rank_obj.cc,75行开始构造pair对。如上理论所说,每条文档移动的方向和趋势取决于其他所有与之 label 不同的文档。因此我们只需要构造不同label的“正向文档对”。其方法主要为:遍历所有的样本,从与本样本label不同的其他label桶中,任意取一个样本,构造成正样本; 如何定义梯度? xgboost/src/objective/rank_obj.cc中,写到了它是使用lambdaWeight. 然后将梯度和文档对输入GBDT训练即可。 ****输出是什么?** 根据labdaMart原理,输出是模型对每个文档的打分。

# 参考文献

Learning to Rank之Ranking SVM 简介 GBRank:一种基于回归的学习排序算法 gbRank & logsitcRank自顶向下

本文是吴恩达老师的课程总结。主要参考了大树先生PilgrimHui 的笔记。如有不当之处,还请各位指出。

本课程附带编程作业。本文的编程作业有:

  1. 实现浅层神经网络 :深度学习实践-1-3-构建浅层神经网络

  2. 实现深层神经网络: 深度学习实践-1-4-1-构建深层神经网络

  3. 用神经网络做猫脸识别:深度学习实践-1-4-2-用神经网络识别猫

  4. 待续

神经网络概念

所谓神经网络就是将许多个单一“神经元”联结在一起,这样,一个“神经元”的输出就可以是另一个“神经元”的输入。神经网络就是按照一定规则将多个神经元连接起来的网络。

我们使用圆圈来表示神经网络的输入,标上“+1”的圆圈被称为偏置节点,也就是截距项。神经网络最左边的一层叫做输入层,最右的一层叫做输出层(本例中,输出层只有一个节点)。中间所有节点组成的一层叫做隐藏层,因为我们不能在训练样本集中观测到它们的值。同时可以看到,以上神经网络的例子中有3个输入单元(偏置单元不计在内),3个隐藏单元及一个输出单元

记: \(a^{[0]} = X\),表示输入特征,也表示“acitive value” \(a^{[1]}\),表示隐藏层的“active value” \(a^{[2]} = y^{\text{~}}\),表示输出层

刚才提到的是一种最简单的神经网络,叫深度前馈(feedforword)网络,又称前馈神经网络、多层感知机,是典型的深度学习模型。

前馈网络的目标是近似某个函数\(f^*(x)\),将输入\(x\)映射到输出\(y\)

而映射$ f(x;)\(,并且学习参数\)\(的值,使它能够得到最佳函数\)f^*(x) $的近似。

前馈是因为没有反馈连接。如果有反馈的话,叫循环(recurrent)神经网络。

全连接:第N层的每个神经单元和第N-1层的所有神经元相连。

阅读全文 »

ArrayList : 是一个采用类型参数(type parameter)的泛型类(generic class)(泛型类指数组内的元素可以是任意类型)。

新建:

ArrayList<Employee> staff = new ArrayList<Employee>();

增:

add();

若调用add且内部数组已经满了,数组列表就自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。

指定数组大小:

test.ensureCapacity(100);

ArrayList<Employee> staff = new ArrayList<>(100);

一旦确定数组大小不再发生变化,即可调用trimeToSize方法,使得存储区域的大小调整为当前元素数量所需的存储空间数目。垃圾回收器将回收多余的存储空间。

其它操作:

get(index);
set(index,element);
add(index,element);
remove(index);

本节是吴恩达老师在deepLearning.ai第二周课程的笔记。

本节以逻辑回归的梯度下降法为例,讲了我们究竟如何使用梯度下降法。

LR的梯度下降

在LR中,我们想要得到z=wx+b,并且这个z在样本上,损失函数L(a,y)最小。那么,我们可以不断地改变w和b,找到一个合适的w和b,达到我们上述的目的。

如何改变w和b,能更快地得到最优的w和b呢?那么我们就要使用梯度下降法。

上图中,从左到右的计算过程就是前向传播法。

一般来说,我们都用后向传播法来计算这个过程:

在单个样本中, 想要计算\(L(a,y)\)的导数: 1. 先向前一步,计算损失函数\(L(a,y)\)关于\(a\)的导数\(da = \frac{dL(a,y)}{da} = -\frac{y}{a} + \frac{1-y}{1-a}\)。 2. 再向前一步,计算\(dz = \frac{dL}{dz} = a - y\) 3. 再向前一步,计算\(dw = \frac{dL}{dw} = ...=x(a-y), db = ...=a-y\) 4. 用 $ w = w - dw,b = b - db$

在m个训练集中, \(J(w,b) = \frac{1}{m} \sum{_i^m L(a^{(i)},y^{(i)})}\) 那么: \(\frac{d(J(w,b))}{w1} = \frac{1}{m}\sum_i^m\frac{d(L(w^{(i)},y^{(i)}))}{w_i}\)

也就是说,m个训练样本的损失函数的导数 = 每个训练样本损失函数导数的均值

伪代码:

J = 0; dw1 = 0; dw2 = 0 ; db = 0;
for i = 1 to m :
    z = w1x1[i] + w2x2[i] + b ;
    y = sigmod(z) ;
    a = get(i) ;
    J += ylog(a) + (1-y)log(1-a);
    dz = a - y; # 先算dz
    dw1+= x1dz; # 后算dw,db
    dw2 += x2dz;
    db+= dz;
J/= m;
dw1 /= m;
dw2 /= m;
db /= m; 

此时就已经得到了全部样本的dw1,dw2,db,J

然后应用梯度下降:

w1 = w1 - sdw1
w2 = w2 - sdw2
b = b - sb

其中,s是步长。

向量化

一般来说,for循环是很不好的。可以使用向量化来摆脱for循环,加速运算。接下来我们来讲一讲向量化。

一般来说,如果我们想计算\(z = w^T x + b\),其中,w和x都是一个n维的列向量。在非向量化实现中,我们会用:

z = 0;
for i in range(n):
    z += w[i]*x[i];
z += b ;

在向量化(例如numpy中),我们用:

z = np.dot(w,x) + b

向量运算非常快(主要原因是并行运算)。因此我们尽量将loop运算转换为向量运算。

向量化的LR

x是m维向量

import numpy as np
J = 0; dw1 = 0; dw2 = 0 ; db = 0;
z = np.dot(w.T,x) + b; # m维列向量
y = sigmod(z) ;# m维列向量
a = label;# m维列向量
J = np.dot(y.T,log(a)) + np.dot((1-y).T,log(1-a));
J/= m;
dz = a - y; # 先算dz
dw1 = np.dot(x1.T,dz) /m ; # 后算dw,db
dw2 = np.dot(x2.T,dz) /m ;
db= np.sum(dz) /m ;

然后应用梯度下降:

w1 = w1 - sdw1
w2 = w2 - sdw2
b = b - sb

总结

这一节主要讲了我们如何将梯度下降法应用到LR中,以及强调了Nuppy。应该只是为了后续的学习做一些准备。

一定一定要看这个作业通过神经网络mindset实现简单的Logistic Regression