首先我们需要明确
c
t
c
l
o
s
s
ctcloss
ctcloss是用来做什么的。比如说我们要生成的目标字符串长度为
l
l
l,而这个字符串包含
k
k
k个字符,字符串允许的最大长度为
L
L
L,这里我们认为一个位置是一个时间步,就是一拍,记为
T
T
T。
对于这个允许最大长度,需要做出一些解释,我们需要定义一个生成字符串的规则,因为训练的时候,这个标签的长度是不一样的,所以我们需要引入空格来生成字符串,那么相应的,关于空格定义以下两条规则:
- 空格与空格之间的字符串是可以去掉重复字母的
- 使用空格间隔的两个部分串不能去重,比如说这个串长成:
cc cc
,在运用上述两条规则之后应该变成c c
举例来说,对于目标生成串如果是
C
A
T
CAT
CAT的话,那么在时间拍为
5
5
5拍的情况下,他有这以下
28
28
28条路径可以生成
C
A
T
CAT
CAT
注:上述图片引自这里,博主对这篇文章加以致谢,还好有这个文章让我对
c
t
c
l
o
s
s
ctcloss
ctcloss有了初步的认识。
因此我们首先拿在手里的是一个随机矩阵为
y
y
y,这个矩阵的形状是
[
k
,
T
]
[k,T]
[k,T],其中
y
[
i
,
j
]
y[i,j]
y[i,j]表示的是在第
j
j
j个时间步,该字符为
i
i
i的概率,而我们需要做的是训练这个
y
y
y矩阵,让他最终产生指定字符串的概率
p
p
p最大,所以我们设置
−
l
n
(
p
)
-ln(p)
−ln(p)为损失函数,我们的目标就是让这个损失函数最小。
那么我们应该怎么做呢?你可以枚举出这28条路径的全部概率,
l
i
k
e
t
h
i
s
like\space this
like this,然后把他们相加之后求损失和。
但是我知道你一定不想这么做。
所以呢,我们需要使用一种更加简洁的方法来求这个概率,咋做呢?这里就要放上耳熟能详的图了?
通过这个图,你可以看出来,我们对字符串进行了插入空格的操作,没错,因为我一开始的时候不给他插入空格行不行,但是你必须得考虑每一步能不能取到空格,并且状态转移的时候,还要考虑从第几个空格转移过来,非常麻烦,不如直接插入空格。
没错就是动态规划,我们来分析以下状态转移方程:
首先在
t
t
t时刻,能取到
s
s
s字符,那么这个字符可以由
t
−
1
t-1
t−1时刻的
s
s
s字符转移过来,两个一样的字符消去就行了呗。与此同时,该字符是不是还可以由
s
−
1
s-1
s−1字符转移过来
?
?
?就是
_
C
_
A
_
T
_
\_C\_A\_T\_
_C_A_T_的
T
T
T接在
_
\_
_后面的情况。我们再考虑从第
s
−
2
s-2
s−2个字符转移过来,这个时候第
s
s
s个字符和第
s
−
2
s-2
s−2个字符必须不能相同,否则的话就是
_
C
_
A
_
A
_
T
_
\_C\_A\_A\_T\_
_C_A_A_T_的两个
A
A
A越过中间的空格连在一起,这不铁定消去了。
所以状态转移使用如下的方法实现:
alpha[s, t] = alpha[s, t - 1]
if s - 1 >= 0:
alpha[s, t] += alpha[s - 1, t - 1]
if s - 2 >= 0 and blank_label[s] != '0' and blank_label[s] != blank_label[s - 2]:
alpha[s, t] += alpha[s - 2, t - 1]
alpha[s, t] *= y[map_dict[blank_label[s]], t]
但是你一定会问弄这个动态规划矩阵有个锤子用,我们来看看:
对于
a
l
p
h
a
alpha
alpha矩阵当中的任意一个元素来说,我们可以得到以下的表达式,其中
l
l
l是任意一个可能产生字符串的路径,
π
\pi
π是全部路径,
l
t
l_t
lt代表这个路径上在第
t
t
t拍上的字符,
P
P
P为概率。
a
l
p
h
a
[
i
,
t
]
=
P
(
l
t
)
∑
l
∈
π
∏
t
′
=
1
t
−
1
P
(
l
t
′
)
alpha[i,t] = P(l_t)\sum_{l\in \pi} \prod_{t'=1}^{t-1} P(l_{t'})
alpha[i,t]=P(lt)l∈π∑t′=1∏t−1P(lt′)
那么我们如果拿到一个后向传播的矩阵
b
e
t
a
beta
beta,是不是就能得到一个:
b
e
t
a
[
i
,
t
]
=
P
(
l
t
)
∑
l
∈
π
∏
t
′
=
t
+
1
T
P
(
l
t
′
)
beta[i,t] = P(l_t)\sum_{l\in \pi} \prod_{t'=t+1}^{T} P(l_{t'})
beta[i,t]=P(lt)l∈π∑t′=t+1∏TP(lt′)
然后就有:
a
l
p
h
a
[
i
,
t
]
∗
b
e
t
a
[
i
,
t
]
=
P
(
l
t
)
∑
l
∈
π
∏
t
′
=
1
T
P
(
l
t
′
)
alpha[i,t]*beta[i,t] = P(l_t)\sum_{l\in \pi} \prod_{t'=1}^{T} P(l_{t'})
alpha[i,t]∗beta[i,t]=P(lt)l∈π∑t′=1∏TP(lt′)
所以我们非常想求的总概率
p
=
∑
l
∈
π
∏
t
′
=
1
T
P
(
l
t
′
)
p=\sum_{l\in \pi} \prod_{t'=1}^{T} P(l_{t'})
p=∑l∈π∏t′=1TP(lt′)就可以使用
a
l
p
h
a
[
i
,
t
]
∗
b
e
t
a
[
i
,
t
]
P
(
l
t
)
\frac{alpha[i,t]*beta[i,t]}{P(l_t)}
P(lt)alpha[i,t]∗beta[i,t]来表示。
公式推导鸣谢:这里
注:这只是我大概的理解,不能十分完备的使用原文章中的符号
接下来就到了非常鸡冻人心的训练过程,差点没给我训练死了。因为改了一天这个梯度公式,并且我体会了什么是梯度消失,
s
o
f
t
m
a
x
softmax
softmax的作用,接下来将详细记录我训练的这个过程,应该只是我记得的了,其中非常感谢这几篇文章的帮助,尤其是在晚上八点还是没有结果的时候看到的这篇文章,但是当时通过死亡调试梯度矩阵已经反应过来是梯度问题了hh。
首先先声明一下:我不能完全保证我的梯度求解没有问题,但是的确训练出了结果,并且参考多篇博客,梯度的结果全都不一样,因此我只能找到一个我认为最合理的梯度来进行梯度下降。
那么我们再来捋一下思路:首先我们想的是要最小化
−
l
n
(
p
)
-ln(p)
−ln(p),而前面我们又求出
p
=
a
l
p
h
a
[
i
,
t
]
∗
b
e
t
a
[
i
,
t
]
P
(
l
t
)
p=\frac{alpha[i,t]*beta[i,t]}{P(l_t)}
p=P(lt)alpha[i,t]∗beta[i,t],这里我们将
P
(
l
t
)
P(l_t)
P(lt)换成
y
k
t
y_{k}^{t}
ykt来表示,就是在
t
t
t时刻的第
k
k
k个字符的概率。我们这里是想对
y
y
y求偏导,但是这里有一个
b
u
g
bug
bug就是这个
y
y
y他不一定是每一列的和都是
1
1
1,所以我们需要对他进行
s
o
f
t
m
a
x
softmax
softmax操作,因此,我们用
x
k
t
x_{k}^{t}
xkt代表经过
s
o
f
t
m
a
x
softmax
softmax之后的。困了,明天再写