1. 语言模型
给定一个长度为T的词的序列,语言模型计算该序列的概率为
,依据乘法定理,我们可以展开为
。
例如T=4,
1.1 n元语法
n元语法,是基于n-1阶马尔可夫链【只是对语言模型的一种简化,并不一定成立,不过现实中确实可行】的概率语言模型,即假设一个词的出现仅与前面的n-1个词有关。例如
一元语法(n=1),每一个词的出现仅与前面0个词相关,其概率语言模型可以直接频率相乘,
二元语法(n=2),每一个词的出现仅与前面的1个词相关,
这种方法一旦n确定了,即强制的限定了一个词的出现与前面的多少个词相关,可以理解为硬性的记忆了固定长度的序列,且一旦n过大,会导致高维稀疏以及计算性能的问题,导致我们无法考虑过长的序列。循环神经网络通过隐藏状态来记忆之前的信息,尽管bptt也是限制序列的长度的【参数的数量不随时间步的增长而增长,但是计算复杂度却是和时间步相关的】,但是n元语法的n通常不会很大,一般不会超过4。
2. 循环神经网络
简单的循环神经网络的结构
我们若把该序列的每个元素直接送入DNN,则参数矩阵会随着序列长度【时间步】的增长而增长,且不能体现序的概念。
按普通的DNN来看,可以看作
和
拼接后作为输入,其权重矩阵自然就是
和
的拼接,以及一个偏置向量。但是这边有序的概念【时间步】,
不仅依赖于本时间步的输入
,还依赖上一时间步的隐藏状态
,实现的时候有循环的概念,当前的输入是该时间步输入的一部分,另一部分是之前所有时间步的影响【体现为上一时间步的隐藏状态,这就是序】,所有时间步共享权重矩阵,这种设计参数矩阵不会随着序列长度的增长而增长。但是由于
的关系,按照链式求导法则,计算复杂度却和序列长度有关系,在这边做的是通过时间反向传播bptt。
3. 关于采样
3.1 相邻采样
相邻采样,令相邻的两个随机小批量在原始序列上的位置相毗邻。这时候我们可以用前一个小批量最终时间步的隐藏状态初始化下一个小批量的隐藏状态【使得下一个小批量的输出也取决于当前小批量的输入,我们输入的一个个样本是原始序列上连续的一段,依常识看,当前的输出确实和当前的输入以及之前的语境有关,所以这也是加入隐藏状态的意义,那么两个小批量在原始序列上的位置相邻,当前小批量的最后一个字符和下一个小批量的第一个字符也是相邻的,那么它的输出和之前的语境相关,也就是和之前的小批量相关,前一个小批量最终时间步的隐藏状态可以理解为记忆了之前的语境】。1.我们只需要在每一个迭代周期开始的时候初始化隐藏状态;2.多个相邻小批量通过隐藏状态串联起来,若不做特殊处理,我们做bptt的时候,也将会依赖于之前所有的小批量,计算梯度的开销会比较大,也容易撑爆内存;这里我们可以只使用前一个小批量最终时间步的隐藏状态的值,即当前小批量的第一个输入作为起点,做bptt的时候不需要依赖之前的小批量,也就是每次读取小批量之前将隐藏状态从计算图中分离出来,pytorch中借助detach()方法【相当于只保留了值,没有保留之前所有节点的关系,在反向传播的时候,就是起点了,而不是前面】。
3.2 随机采样
这个就没啥好说的了,完全随机,相邻的两个随机小批量在原始序列上的位置不一定相邻。那么前一个小批量的最后一个字符和当前小批量的第一个字符不一定相邻,自然当前小批量的输出不会依赖前一个小批量,也就不可以用前一个小批量最终时间步的隐藏状态初始化下一个小批量的隐藏状态,在每个小批量更新前需要初始化隐藏状态。
4. 通过时间反向传播
通过时间反向传播【back-propagation through time】,其实就是反向传播在循环神经网络中应用,我们知道由于隐藏状态的引入,在实现上有循环的概念,正向传播的时候当前时间步的输出不仅依赖于当前时间步的输入还依赖于上一时间步的隐藏状态,那么反向传播的时候自然也需要按时间步展开。
为简化,考虑不带偏置的循环神经网络,且激活函数为恒等函数【即】
为了可视化循环神经网络中模型变量和参数在计算中的依赖关系,我们可以绘制模型计算图,如下所示。例如,时间步3的隐藏状态的计算依赖模型参数
、
、上一时间步隐藏状态
以及当前时间步输入
。
,表示时间步数为T的该样本的损失,其中
是在时间步t的损失。
输出层不涉及循环的概念,目标函数有关各时间步输出层变量的梯度
求解的梯度,每个时间步输出层均共享参数,意味着L通过
依赖于
,则我们需要L对每个时间步的输出求偏导,再由各时间步的输出对各时间步的隐藏状态求偏导(正向传播的时候记住每一时间步的隐藏状态,反向传播的时候就可以直接用了)
ps:
之前一直很难理解循环神经网络,为啥图要这样画,为啥有些又说最后计算是要和整个词表的词都算softmax(图上并没有体现),现在发现抓住前向传播和损失函数才是王道,图只是辅助理解。代码实现上抓住前向传播 & 损失函数,梯度的反向传播对于大多数人而言,现有的框架可以帮你做的很好,我们看反向传播知识为了更好的了解循环神经网络。
至于参数的形状那就更无所谓了,例如输入x的形状(n,d),那么,则
是(n,h),那么
必定是(d,h),但是如果输入x形状是(d,n),那么
,则
是(h,n),那么
必定是(h,d)。我们只要保证自己实现的时候形状对应就好啦~。
未完待续