1. 符号定义
    $w_{jk}^L$ 表示第$L-1$层的第$j$个神经元到第$L$层的第$k$个神经元映射的权值。
    $b_k^L$ 表示第$L$层的第$k$个神经元的偏置量。
    $z_k^L=\sum_j w_{jk}^L a_j^{L-1}+b_k^L​$ 表示第$L​$层的未经激活函数的输出。
    $a_k^{L}=\sigma(z_j^L)$ 表示第$L$层经过sigmoid函数后的输出。

  2. 损失函数定义二次代价函数:($x$代表输入的样本,$y(x)$代表标签值)
    当只关注某一个样本$x_i​$的时候,有:

  3. 反向传播推导
    计算最后一层神经网络产生的错误
    对于每一个$L$层的神经元有:

​ 则整个$L​$层可以用矩阵的Hadamard积(矩阵行行对应相乘)来进行计算:

反向传播
计算每一层的每个神经元产生的误差,推广到每一层的每个神经元有:
推广到整个一层有:

计算权重的梯度

计算偏置的梯度

4.总结反向传播四个公式:

输出层误差:

反向传播每一层误差:

权重梯度:

偏置梯度:

  1. Softmax Loss 推导
    (1) $N$为类别数
    (2) $a$为输出向量,$a_j$为向量$a$的第$j$个值

参考:

1.1 Softmax

1.2 Cross-entropy Loss1.3 Softmax Loss
当Cross-entropy的$P_j=S_i$ ,即Softmax输出的时候。

1.4 Softmax对Softmax输入的导数
$S_i$对$a_j$求导:

这里求导有两种情况

1)当$i=j$时:

2)当$i≠j$时:

1.5 Softmax Loss对softmax输入的导数
第③个等号就用到了上面$S_i$对$a_j$求导的结论,第三个等号结果的左半部分是$i=k$的时候$S_i$对$a_j$求导的导数,右半部分是$i≠k$的时候S_i$对​$a_j$求导的导数。
第⑥、⑦个等号是将$y_iS_i​$合并到∑里面。最后一个等号的成立是建立在假设$∑y_k=1​$的前提下,这个对于常见的单标签分类任务而言都是成立的。

1.6 总结因此假设一个5分类任务,经过Softmax层后得到的概率向量$S$是$[0.1,0.2,0.25,0.4,0.05]$,真实标签$y$是$[0,0,1,0,0]$,那么损失回传时该层得到的梯度就是$p-y=[0.1,0.2,-0.75,0.4,0.05]$。这个梯度就指导网络在下一次forward的时候更新该层的权重参数。

参考资料:

  1. 随机梯度下降
    批梯度下降(Gradient Descent)
    随机批梯度下降(Stotastic Gradient Descent)
    每次梯度计算只使用一个随机样本(可能是噪声样本)
    避免在类似的样本上进行冗余计算
    增加了跳出当前局部最小值的可能
    可以通过减小学习率,来使其能够与GD有相同的收敛速度

小批量随机梯度下降(Mini batch SGD)
每次梯度计算使用小批量的样本
梯度计算比单样本计算更加稳定
便于使用矩阵计算
适当的batch size训练效率高

  1. 随机梯度下降的困难
    局部梯度的反方向不一定是函数整体的下降方向。
    学习率衰减法,难以根据数据进行自适应。
    对不同的参数采取不同的学习率(数据稀疏,不平衡)。
    容易困在局部最小点,甚至是鞍点。

  2. 动量方法(Momentum)
    目的: 解决随机梯度的局部梯度的反方向不一定是函数整体的下降方向问题。

方法:

动量法:(Momentum)(适用于隧道型曲面)

方法:
每次更新都吸收上一次更新的余势。使得主体方向得到了更好的保留,使得效果被不断的放大。

缺点:
在前期下降比较快,收敛速度较好,但到最优值附近时容易由于动量过大而导致优化过度。

改进动量法:(Nesterov)

方法:利用主体的下降方向,预判下一步优化的位置,根据预判的位置计算优化的梯度。momentum首先计算一个梯度(短的蓝色向量),然后在加速更新梯度的方向进行一个大的跳跃(长的蓝色向量),nesterov项首先在之前加速的梯度方向进行一个大的跳跃(棕色向量),计算梯度然后进行校正(绿色梯向量)

  1. 自适应梯度方法(Ada)
    目的:

解决学习率衰减法,难以根据数据进行自适应的问题。
更新频繁的参数使用较小的学习率。
更新较少的参数使用较大的学习率。

方法:

Adagrad方法:

思路:Adagrad对每个参数的历史梯度更新进行叠加,并以此作为下一次更新的惩罚系数。(约束学习率)

算法:

梯度:$g_{t,i}=\nabla_{\theta}J(\theta_i)$
梯度历史矩阵: $G_t$是对角阵,其中$G_{t,ii}=\sum_{k}g_{k,i}^2$
参数更新:(历史梯度大,则$\eta$项越小)

存在的问题:

随着训练的进行,学习率衰减过快。

梯度与参数单位不匹配

RMSprop(Adadelta方法第一版):

目的:解决随着训练的进行,学习率衰减过快。

思路:使用梯度平方的移动平均来取代全部的历史平方和。

算法:

梯度:$g_{t,i}=\nabla_{\theta}J(\theta_i)$

移动平均: $\mathbb{E}{t}[g^2]=\gamma \mathbb{E}{t-1}[g^2] + (1-\gamma) g_{t}^2$

参数更新:(更新系数分母换了)

特点:

其实RMSprop依然依赖于全局学习率
RMSprop算是Adagrad的一种发展,和Adadelta的变体,效果趋于二者之间
适合处理非平稳目标
对于RNN效果很好

Adadelta方法第二版:

目的:梯度与参数单位不匹配

思路:使用参数更新的移动平均来取代学习率$\eta$

算法:

参数更新: (学习率换成参数的移动平均自适应)

特点:

训练初中期,加速效果不错,很快
训练后期,反复在局部最小值附近抖动

  1. 动量+自适应方法(Adam)
    Adam (带动量项的RMSprop)

思路:在Adadelta的梯度平方和(二阶矩)的基础上引入动量方法的的一阶矩(梯度)

算法:

一阶矩(动量): $m_t=\beta_1 m_{t-1}+(1-\beta_1)g_t$ (保持下降速度)
二阶矩(Adadelta):$v_t=\beta_2 v_{t-1}+(1-\beta_2)g_t^2$ (保持参数自适应)
参数更新:

特点:

经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
适用于大数据集和高维空间

NAdam (引入Nesterov)

对学习率有了更强的约束

  1. 小结
    对于稀疏数据,尽量使用学习率可自适应的优化方法,不用手动调节,而且最好采用默认值
    SGD通常训练时间更长,容易陷入鞍点,但是在好的初始化和学习率调度方案的情况下,结果更可靠
    如果在意更快的收敛,并且需要训练较深较复杂的网络时,推荐使用学习率自适应的优化方法。
    Adadelta,RMSprop,Adam是比较相近的算法,在相似的情况下表现差不多。
    在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果

鞍点:

  1. [Leetcode 200]
    解法:
DFS:一次遍历,遇到1则进行深搜,遍历过的1设为2,遇到非1或者越界则深搜停止,统计深搜的次数,即为岛屿数量。(这里采用)
BFS: 循环遍历每个点,如果该点是0,则跳过,如果是1,岛屿数目加1,并加入队列,并将该点改为0,避免重复访问,然后进行广搜,取出队首元素,搜索该点周围四个点,如果有1就加入队列,并将1改为0,否则就跳过,当队列空时,一块岛屿搜索完毕,进入下一块搜索。(代码可参考:)
将二维数组重新编号,从0开始,从左到右,从上到下,直到nm-1(其中n为行数,m为列数),对于位置(i,j)则编号为im+j,那么相邻(左右,上下)的为同一个值,则认为他们相通。那么最终只要统计一下father[i]==i且对应值为1的个数即可。(代码可参考: )

代码:

  1. [Leetcode 77]
    模板题:重点在于dfs中的startPos=i+1

代码:

  1. [Leetcode 39]
    基于模板加一些改动即可,由于可重复选择数字,将startPos=i+1 改成startPos=i,判停条件满足目标和就可以了,(前提是有序数组,先拍下序)
    代码:

  2. [Leetcode 40]
    解法:在上一题的基础上使用Set进行去重

代码:

5.[Leetcode 216]
解法:
代码

6.[Leetcode 131]
解法:
按解组合的方式解,解的过程中判断回文串,组合的元素集合是按照从子串的第一个元素开始,到第i个元素截断作为一个新子串进行回文判断,

代码:

规规矩矩没啥说的, 主要注意边界条件

代码:

[Leetcode 141, 142] 环形链表,环形链表 II (剑指Offer面试题23)
解法:(链表遍历,快慢指针)

判环:快慢指针,快指针+2, 慢指针+1,若快指针在到达链表尾(不带环的才有链表尾)之前与慢指针相遇,则有环。
找入口:快慢指针第一次相遇节点与头结点之间的节点数与环中节点数相同,两个指针一个从链表头开始,一个从相遇节点开始遍历,两个指针再次相遇节点为入口节点。

代码:

[Leetcode 206] 反转链表 (剑指Offer面试题24)
解法:

代码:

[Leetcode 21] 合并两个有序链表 (剑指Offer面试题 25)
解法:

优先队列重排 (可参考 提交记录99%代码)

比较插入列表 (这里使用)

代码:

[Leetcode23] 合并K个排序链表
解法:

优先队列或列表遍历插入,优先队列效率相对高一些,这里列出的是列表遍历比较插入的代码,优先队列可参考提交记录99%代码。

代码:

[Leetcode 141, 142] , (剑指Offer面试题23)
解法:(链表遍历,快慢指针)

判环:快慢指针,快指针+2, 慢指针+1,若快指针在到达链表尾(不带环的才有链表尾)之前与慢指针相遇,则有环。
找入口:快慢指针第一次相遇节点与头结点之间的节点数与环中节点数相同,两个指针一个从链表头开始,一个从相遇节点开始遍历,两个指针再次相遇节点为入口节点。

代码:

[Leetcode 206] (剑指Offer面试题24)
解法:

代码:

[Leetcode 21] (剑指Offer面试题 25)
解法:

优先队列重排 (可参考 提交记录99%代码)

比较插入列表 (这里使用)

代码:

[Leetcode23]
解法:

优先队列或列表遍历插入,优先队列效率相对高一些,这里列出的是列表遍历比较插入的代码,优先队列可参考提交记录99%代码。

代码:

[Leetcode 141, 142] 环形链表,环形链表 II (剑指Offer面试题23)
解法:(链表遍历,快慢指针)

判环:快慢指针,快指针+2, 慢指针+1,若快指针在到达链表尾(不带环的才有链表尾)之前与慢指针相遇,则有环。
找入口:快慢指针第一次相遇节点与头结点之间的节点数与环中节点数相同,两个指针一个从链表头开始,一个从相遇节点开始遍历,两个指针再次相遇节点为入口节点。

代码:

[Leetcode 206] 反转链表 (剑指Offer面试题24)
解法:

代码:

[Leetcode 21] 合并两个有序链表 (剑指Offer面试题 25)
解法:

优先队列重排 (可参考 提交记录99%代码)

比较插入列表 (这里使用)

代码:

[Leetcode23] 合并K个排序链表
解法:

优先队列或列表遍历插入,优先队列效率相对高一些,这里列出的是列表遍历比较插入的代码,优先队列可参考提交记录99%代码。

代码:

  1. [Leetcode 232] (剑指OFFER面试题 9)
    解法:一个栈(push栈)用于接收push,一个栈(pop栈)用于top(peek)和pop
    当pop栈为空,且push栈不为空时,将push栈的元素转移到pop栈中
    当pop栈不为空时,将pop栈的数据pop出去
    push操作只在push栈进行

注意:
leetcode上可以不进行异常处理,能a过,但是面试时候最好还是加上空栈的异常处理。
泛型支持。
线程安全。

2.[Leetcode 155] 最小栈 (剑指Offer面试题30)
解法:双栈实现,一个栈用于正常的入栈出栈,一个栈用于记录最小值

代码:

  1. [Leetcode 946] (剑指Offer面试题31)
    解法:双栈模拟,或者通过判断输入栈栈顶元素是否与验证栈当前指针所指元素相等。
    代码:

参考

  1. [Leetcode 105] (剑指OFFER面试题 7)
    解法:递归,由先序序列确定子树根节点,由中序序列确定当前节点下的子树范围。
    主要参数(递归函数):先序序列的起点idx与终点idx, 中序序列的起点idx与终点idx。
    递归停止条件: 先序序列或中序序列的起点idx > 终点idx。
    递归参数更新方法:
    左子树更新:
    先序起点idx+1。 preL + 1
    先序终点为中序确定左子树节点数量num + 先序起点idx。 preL + num
    中序起点为之前递归层中的中序起点。 inL
    中序终点为根节点在中序序列中的idx - 1。 inRoot - 1

右子树更新:
先序起点idx+1 + 中序确定左子树节点数量num。 preL + num + 1
先序终点为之前递归层中的先序终点。 preR
中序起点为根节点在中序序列中的idx + 1。 inRoot + 1
中序终点为为之前递归层中的中序终点。 inR

  1. [Leetcode 572] (剑指Offer面试题 26)
    解法:递归寻根相同,根相同后判断子树是否相同。

注意:停止条件,空指针处理

代码:

  1. [LeetCode 144]
    解法:

递归,先输出再递归

循环,用栈模拟,节点右子节点入栈,左节点直接输出。

代码:

  1. [LeetCode 94]
    解法:

递归:遍历左节点后输出值

循环:

代码:

  1. [Leetcode 145]
    解法:递归/循环,循环解,先左插,再右插,遇到空节点出栈,出栈元素插在结果数组的最前面。

代码:

  1. [LeetCode 226] (剑指Offer面试题27)
    解法:递归换序

代码:

6.[LeetCode 101] (剑指Offer面试题28)
解法1: 递归,

解法2:BFS循环

  1. [Leetcode 102] (剑指Offer面试题32)
    思路:用一个队列nodeQ维护二叉树BFS顺序,由于题目中需要分层记录,所以需要维护每一层的数据vectorlayerRes以及每一层可扩展节点队列layerQ。

代码:

  1. [Leetcode 230]
    解法: 循环模拟中序遍历

代码:

2017~2019年度我在Github上面收藏的一些优质干货Repo其中有一些很荣幸作为Repo的贡献者,在issue中也结识了不少好友,共同学习

计算机软件工程类
(44638 star)

刷题类

深度学习类

随着整理,持续更新

0%