为了确保您成功将‘Warp-CTC’和Torch绑定,请在warp-ctc根目录中运行“luarocks make torch_binding/rocks/warp-ctc-scm-1.rockspec”。
现在,您可以非常容易的通过torch_binding来测试CTC。
假如你的编译没有GPU支持,请用torch.Tensor(...):float()
替代torch.Tensor(...):cuda()
,用cpu_ctc
取代gpu_ctc
CTC是一种计算输入序列与目标输出序列之间相似程度的目标函数。由于CTC普遍运用于神经网络,我们称输入序列为激活序列。目标输出序列是从一个固定的字母表中得出的。
为了在此讨论, 我们选择了四个字母a,b,c,d
. 算法需要一个<空白>
符号区别于字母。这就意味着激活序列会是一个五维向量序列(字母的数量加<空白>)。这个向量(通过softmax函数)将会被转化成字母以及<空白>上的概率分布。
比如CTC可以用来衡量一个长度为7的激活序列和标签 dacba
之间的误差。
一个长度为7的激活序列就会是(向量的组成部分是任意的)
{<2,0,0,0,0>, <1,3,0,0,0>, <1,4,1,0,0>, <1,1,5,6,0>, <1,1,1,1,1>, <1,1,7,1,1>, <9,1,1,1,1>}
得到的有效输出序列即daceba
.
一开始我们会举一个非常简单的例子。在这个例子中我们会用一个长度为1的激活序列,以及一个长度为1的目标输出序列。
为了指定这个激活序列,我们必须写下每一个五维向量的组成部分。我们使用<0,0,0,0,0>
作为激活序列的单一向量,得到的概率分布及0.2,0.2,0.2,0.2,0.2
.
对于目标输出,我们会用一个单一标签a
.
首先,我们如何将数据展现给算法? 像平时使用Torch一样,激活表示要在一个2维张量中放入行。目标标签需要放入lua table, 每个目标标签序列都有一个对应的表。
我们每一个标签仅有一个序列,因此当标签a
有指数1时,表即{{1}}
(指数0预留给空白符号)。因为我们允许输入不同长度的激活序列的可能性,我们需要指定
输入激活序列的长度,在这个例子即包涵一个lua table{1}
的1.
为了计算以上问题(单一元素输入序列,单一输出标签)的CTC损失函数的价值, 只有一种可能的对齐方式,所以符号必须在第一个时间步(time step)发出。
发出符号的概率为0.2
。 算法返回的负对数似然值为-ln(0.2)=1.6094
.
现在让我们通过代码来做计算。先从Torch部分开始,需要代码库。
假如你有GPU的支持
th>require 'cutorch'
如果仅有CPU
th>require 'warp_ctc'
请将激活输入行-- 注意用两个大括号
th>acts = torch.Tensor({{0,0,0,0,0}}):cuda()
假如输入为空,梯度计算则不能完成。
th>grads = torch.Tensor():cuda()
对于目标标签以及输入序列的大小
th>labels = {{1}}
th>sizes ={1}
如果你有CUDA支持,请使用gpu_ctc
,否则请使用cpu_ctc
th> gpu_ctc(acts, grads, labels, sizes)
{
1 : 1.6094379425049
}
对每一组序列,函数会返回CTC损失的一个lua table.
现在,我们来看一个更有意思的例子。假如我们有一个长度为3的输入序列,激活后:
<1,2,3,4,5>
,<6,7,8,9,10>
and <11,12,13,14,15>
.
对应这些帧的概率则为
0.0117, 0.0317, 0.0861, 0.2341, 0.6364
(在这个特殊例子中,每一帧的概率都一样)
对于目标符号,我们将使用序列c,c
.
th>acts = torch.Tensor({{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}):cuda()
th>labels = {{3,3}}
th>sizes = {3}
CTC计算了所有可能对齐的概率。请注意目标包涵了重复的符号c
.CTC不能在连续的时间步上发出重复的符号(更多细节,请见)。对于重复的符号必须用一个空白分开,所以唯一可能的对齐序列为c <空白> c
.
CTC假设,在给定数据的情况下,标签概率是有条件独立的,所以我们期待的答案即Pr(c at frame 1)*Pr(<空白> at frame 2)*Pr(c at frame 3) = 0.2341*0.0117*0.2341
and -ln(0.2341*0.0117*0.2341) = 7.3522
.
th> gpu_ctc(acts, grads, labels, sizes)
{
1 : 7.355742931366
}
小的数值差由于其中一个计算人工完成。
假设目标序列为b,c
,激活序列则为
<-5,-4,-3,-2,-1>
,<-10,-9,-8,-7,-6>
and <-15,-14,-13,-12,-11>
.
对应这些帧的概率则为
0.0117, 0.0317, 0.0861, 0.2341, 0.6364
.
由于重复的符号被清空,空白被取消,现在有五种可能的对齐
<空白> b c
, b <空白> c
, b c <空白>
, b b c
and b c c
.
结果应当是
-ln(3*0.0117*0.0861*0.2341 + 0.0861*0.0861*0.2341 + 0.0861*0.2341*0.2341) = 4.9390
th>acts = torch.Tensor({{-5,-4,-3,-2,-1},{-10,-9,-8,-7,-6},{-15,-14,-13,-12,-11}}):cuda()
th>labels = {{2,3}}
th>sizes = {3}
th>gpu_ctc(acts, grads, labels, sizes)
{
1 : 4.938850402832
}
因此,我们有三个例子。最后一个例子显示如果通过算法将3个例子做迷你批处理 (minibatch). 标签现在是{{1}, {3,3}, {2,3}}
,输入序列的长度是{1,3,3}
.
我们必须将输入序列放入一个单独的两维矩阵。通过交织输入序列的元素,我们的输入矩阵如下:
为了清楚起见,我们从前两个输入序列开始
entries | col1 | col2 | col3 | col4 | col5 |
---|---|---|---|---|---|
seq1 item 1 | 0 | 0 | 0 | 0 | 0 |
seq2 item 1 | 1 | 2 | 3 | 4 | 5 |
seq1 item 2 | P | P | P | P | P |
seq2 item 2 | 6 | 7 | 8 | 9 | 10 |
seq1 item 3 | P | P | P | P | P |
seq2 item 3 | 11 | 12 | 13 | 14 | 15 |
由于第一个序列没有第二个或第三个元素,我们用0填入矩阵(在上面一个表格中显示为P
)。 现在我们将第三个序列放入表格中
entries | col1 | col2 | col3 | col4 | col5 |
---|---|---|---|---|---|
seq1 item 1 | 0 | 0 | 0 | 0 | 0 |
seq2 item 1 | 1 | 2 | 3 | 4 | 5 |
seq3 item 1 | -5 | -4 | -3 | -2 | -1 |
seq1 item 2 | P | P | P | P | P |
seq2 item 2 | 6 | 7 | 8 | 9 | 10 |
seq3 item 2 | -10 | -9 | -8 | 7 | -6 |
seq1 item 3 | P | P | P | P | P |
seq2 item 3 | 11 | 12 | 13 | 14 | 15 |
seq3 item 3 | -15 | -14 | -13 | -12 | -11 |
在Torch中完整的例子如下
th>acts = torch.Tensor({{0,0,0,0,0},{1,2,3,4,5},{-5,-4,-3,-2,-1},
{0,0,0,0,0},{6,7,8,9,10},{-10,-9,-8,-7,-6},
{0,0,0,0,0},{11,12,13,14,15},{-15,-14,-13,-12,-11}}):cuda()
th>labels = {{1}, {3,3}, {2,3}}
th>sizes = {1,3,3}
th>gpu_ctc(acts, grads, labels, sizes)
{
1 : 1.6094379425049
2 : 7.355742931366
3 : 4.938850402832
}
为了获取接下来激活的梯度,传递和激活张量同样大小的张量即可。
如果想看更多例子,请见torch_binding/tests/test.lua
。