我已经在 C++ 中实现了 LSTM,它的错误稳步减少,但在某个错误值处减慢。它似乎也可以预测大多数字符,但即使经过 5000 次反向传播迭代,它也会卡住并且无法纠正一些错误(或者会非常缓慢地纠正它们)。例如,要求它一个一个地预测字符可能会导致
abcdefg jff klmnopqrstu x wxyz
或类似的东西。请注意,网络几乎把事情做好了。此外,它在犯错后永远不会“迷失”,例如在上面的例子中,在jff之后它产生k并回到正轨,就好像它从未犯过那个错误一样。结果总是不同的——有时网络会学习所有的字母。但是,误差仍保持在相同的值。
错误从 7.0 左右开始下降到 2.35,之后它随着每次迭代而减慢,这似乎达到了一个平台期。
如果我的字母表仅由a,b组成,那么网络几乎立即意识到它应该生成abababababab,但是错误从 0.8 开始,现在总是在 0.2 到 0.28 左右
如果使用a,b,我们设置 4 个时间步长,网络会学习生成abab,但在 50 000 个反向道具之后(即使在 25 000 次之后仍然被卡住)它仅以 85% 的确定性预测“a”,即使我预计为 99.999%;必须预测“b”时的类似值。一旦卡住,它就会保持与这些类似的值。因此,在使用a,b数据集时,它可能会一遍又一遍地猜测相同的值;
奇怪的是,在大多数情况下使用a,b数据集时,我观察到最终学习的概率为:
- a=[0.68, 0.31] b=[0.15, 0.85]
- 有时,在重新初始化网络后,它会学习最终概率为 a=[0.8205, 0.1794] 和 b=[0.1795, 0.8205]
禁用动量(前一帧的 grads 乘以零)仍然对a、b产生相同的影响
网络没有爆炸,但它的梯度似乎消失了。
问题:
通常会陷入这些价值观吗?在 26 个时间步后进行反向支撑,完成 200 000 次,到那时,变化变得缓慢。误差在 2.35 左右,不值得等待另一个 0.000001 的误差变化。
尝试使用较小的学习率 (0.0004) 可使误差降至 2.28 - 这是我所拥有的最好的。此外,使用 0.2 的动量系数;它应用于前一帧的渐变。我不会在程序执行时增加动量,而是将其保持在恒定的 0.2;
newgradient = newgradientMatrix + prevFrameGradientMatrix*0.2
我没有使用任何形式的 dropout 等。我只是希望网络过度拟合,但对于 26 字符的 aphabet,它会卡在 2.35。
当整个字母表由单个字符组成时,我只会收到 0 错误。在这种情况下,NN 将预测 aaaaaaaaaaaaaaaaa,误差将为 0
转发道具:
一切都在 CPU 中的单个线程上完成。对向量的分量使用“float”。
- dataGate 上的 Tanh 和构建“单元”之后(在“单元”乘以输出门之前)
输入、遗忘和输出门上的 Sigmoid
每个门都有权重矩阵,其中每一列是从当前 LSTM 单元中的神经元到前一个 LSTM 单元中所有神经元的权重。最后一列被忽略,因为没有任何东西会影响我们的偏见。此外,偏置值(但不是它的权重!)手动设置为 1.0 只是为了确定。
每个门都有一个单独的 NxN 矩阵,其循环权重(U 矩阵)在 [time-1] 时对当前 LSTM 单元的结果进行操作
W 和 U 都保留最后一行,因此它们都采样了 lower-LSTM 的偏差。这不应该产生问题,因为这两种偏见都得到了正确的反向传播。事实上,最后一行已从 U-Matrix 中完全删除 - 只是为了确定,但无论如何,误差仍会保持在相同的 2.35 数量上。
权重初始化: 具有均匀分布的Xavier-Benjio第 253 页,右下角。统一分布的边界定义如here所述,如下所示:
low = -4*np.sqrt(6.0/(fan_in + fan_out)); // use |4| for sigmoid gates, |1| for tanh gates
high = 4*np.sqrt(6.0/(fan_in + fan_out));
成本函数
LSTM 单元的结果是 softmaxed(忽略偏差),然后通过交叉熵函数得到单个浮点值。
交叉熵是正确的,用于多类分类。
float cost = 0;
for(each vector component){
if(predictedVal == 0){ continue; }
cost += -(targetVec[i])*naturalLog(predictedVec[i]);
}
return cost;
然后将所有时间步的成本相加,并在我们进行反向传播之前返回其平均值。这是我在 2.3 处获得 26 个字符字母表的平台
顺便说一句,单元格(又名 c)和结果(又名 h)在最后一个(第 26 个时间步)之后被缓存。在反向传播之后,它们被 timestep0 使用。这可能(并且在一段时间内)被禁用,但结果是相似的。
反向传播
将列出我处理的几个重要问题和关键点:
de_dh就是 (targetVec - predictVec),按这个顺序。这是因为收集的梯度将从W 和 U 矩阵中减去。这是因为在 forward prop 期间同时使用 softmax 和 crossEntropy 时,导数很好地抵消了。
de_dh从 t+1添加一个额外的梯度。添加的数量是所有 4 个门的总和。为了更好地解释,回想一下这样的门之一是向前推动的,如下所示:
dataGate = tanh(W * incomingVal + U * lstmResultPrevT + bias_ComesFrom_W_and_U); //四个门之一
在上面的公式中,粗体表示从 [t+1] 处的这四个门之一获取梯度的数量。然后将这样的梯度在这 4 个门上相加,并添加到de_dh中,如最初所述。必须这样做,因为在前向支撑期间,[t] 的 'H' 影响了 [t+1] 的所有 4 个门
在计算 [t] 处的 Cell 的梯度时,会添加来自 [t+1] 的单元的梯度。之后,我们计算 [t-1] 的 C 的梯度,以便在我们到达较早的时间步时能够重复此过程。
在 [t-1] 处导致偏差的 U 权重的梯度是在记住偏差的原始值为 1.0 的情况下计算的;此外,它经过双重检查,以确保梯度不会从我们在 [t] 处的偏差流向 [t-1] 处的神经元。那是因为最初没有任何东西会影响我们的偏见。如下,整个U-梯度矩阵的最后一列总是0.0;
对于 W 矩阵的这种偏置列也进行了类似的操作——整列为零。
最后,为四个门中的每一个计算 [t-1] 的每个 H 的梯度。这样做是为了使 '2. key-point' 是可能的(将 4-grads 添加到de_dh),当我们到达这个 back-prop 中的较早时间步时。
单元测试和调试:
在 20 000 次反向传播(每 26 个时间步完成一次)之后,将收集一个文件。观察到所有 4 个门的梯度都非常小,尤其是在通过每个门的激活函数之后。这是引入 Xavier init(上图)的原因之一,以防止权重太大(通过激活推回后缩小 grad)或太小(通过权重推回后缩小 grad)。
使用“规范裁剪”后观察到了显着的改进,即使使用了 56 个唯一字符并且在 56 个时间步后完成反向传播,我的 LSTM 似乎也能学习到正确的序列。与原始示例(使用 26 个字符)类似,只有几个字符被错误预测。然而,误差始终处于稳定状态,值较高(约 4.5)
再一次,这是传统的行为,我只需要依靠 dropout 和平均多个网络的结果吗?然而,似乎我的网络甚至无法过度拟合......
编辑:
我发现了一件事——LSTM的结果是向量,它的分量不能小于-1或大于1(tanh和sigmoid的curtesy)因此,不能小于~0.36或大于~ 2.71
所以概率总是有一些“沉淀”悬空,网络总是“担心”不能达到100%的置信度?试图在这里澄清