Deep learning CTC语音识别中常用的前缀波束搜索能否以这样简单的方式实现?
我最近在学习语音识别,我了解到,前缀波束搜索的思想是合并具有相同前缀的路径,例如Deep learning CTC语音识别中常用的前缀波束搜索能否以这样简单的方式实现?,deep-learning,speech-recognition,ctc,beam-search,Deep Learning,Speech Recognition,Ctc,Beam Search,我最近在学习语音识别,我了解到,前缀波束搜索的思想是合并具有相同前缀的路径,例如[1,1,和[\u1,\u](如您所见,\u表示空白标记) 基于这种理解,我实现了我的一个版本,可以使用如下伪代码简化: def _prefix_beam_decode(y, beam_size, blank): T, V = y.shape log_y = np.log(y) beam = [(tuple(), (0, ninf))] for t in range(T):
[1,1,
和[\u1,\u]
(如您所见,\u
表示空白标记)
基于这种理解,我实现了我的一个版本,可以使用如下伪代码简化:
def _prefix_beam_decode(y, beam_size, blank):
T, V = y.shape
log_y = np.log(y)
beam = [(tuple(), (0, ninf))]
for t in range(T):
new_beam = defaultdict(lambda: (ninf, ninf))
for prefix, (p_b, p_nb) in beam:
for i in range(V):
p = log_y[t, i]
if i == blank:
new_p_b, new_p_nb = new_beam[prefix]
new_p_b = logsumexp(new_p_b, p_b + p, p_nb + p)
new_beam[prefix] = (new_p_b, new_p_nb)
continue
end_t = prefix[-1] if prefix else None
new_prefix = prefix + (i,)
new_p_b, new_p_nb = new_beam[new_prefix]
if i != end_t:
new_p_nb = logsumexp(new_p_nb, p_b + p, p_nb + p)
else:
new_p_nb = logsumexp(new_p_nb, p_b + p)
new_beam[new_prefix] = (new_p_b, new_p_nb)
if i == end_t:
new_p_b, new_p_nb = new_beam[prefix]
new_p_nb = logsumexp(new_p_nb, p_nb + p)
new_beam[prefix] = (new_p_b, new_p_nb)
beam = sorted(new_beam.items(), key=lambda x: logsumexp(*x[1]), reverse=True)
beam = beam[:beam_size]
return beam
def prefix_beam_搜索(y,beam_大小,空白):
seq_len,n_class=y形
logY=np.log(y)
梁=[([],0)]
对于范围内的t(如下所示):
buff=[]
对于梁中的前缀p:
对于范围内的i(n_类):
新前缀=列表(前缀)+[i]
新p=p+logY[t][i]
buff.append((新的前缀,新的前缀))
#合并具有相同前缀的路径'
新光束=defaultdict(λ:ninf)
对于前缀,buff中的p:
#“norm_prefix”可以简化路径[1,1,2]===>[1,2]
#但是,结尾的“空白”被保留,[1,1,]==>[1,]
前缀=标准前缀(前缀,空白)
新波束[前缀]=logsumexp(新波束[前缀],p)
#选择最佳路径
new_beam=已排序(new_beam.items(),key=lambda x:x[1],reverse=True)
梁=新梁[:梁尺寸]
回程光束
但我在网上找到的大多数版本(根据报纸)都是这样的:
def _prefix_beam_decode(y, beam_size, blank):
T, V = y.shape
log_y = np.log(y)
beam = [(tuple(), (0, ninf))]
for t in range(T):
new_beam = defaultdict(lambda: (ninf, ninf))
for prefix, (p_b, p_nb) in beam:
for i in range(V):
p = log_y[t, i]
if i == blank:
new_p_b, new_p_nb = new_beam[prefix]
new_p_b = logsumexp(new_p_b, p_b + p, p_nb + p)
new_beam[prefix] = (new_p_b, new_p_nb)
continue
end_t = prefix[-1] if prefix else None
new_prefix = prefix + (i,)
new_p_b, new_p_nb = new_beam[new_prefix]
if i != end_t:
new_p_nb = logsumexp(new_p_nb, p_b + p, p_nb + p)
else:
new_p_nb = logsumexp(new_p_nb, p_b + p)
new_beam[new_prefix] = (new_p_b, new_p_nb)
if i == end_t:
new_p_b, new_p_nb = new_beam[prefix]
new_p_nb = logsumexp(new_p_nb, p_nb + p)
new_beam[prefix] = (new_p_b, new_p_nb)
beam = sorted(new_beam.items(), key=lambda x: logsumexp(*x[1]), reverse=True)
beam = beam[:beam_size]
return beam
两者的结果是不同的,我的版本倾向于返回更长的字符串。我不太了解主要的两个方面:
new\u prefix=prefix+(i,)
生成新前缀时的通用版本,无论前一个前缀的结尾是否与给定的“s”相同。例如,旧的前缀是[a,a,b]
,当添加新字符s时,[a,a,b]
和[a,a,b,b]
都被保存这样做的目的是什么?这会导致重复计算吗?期待您的回答,提前谢谢 当您在代码中选择最佳路径时,您不想区分[1、[1]和[1],因为它们都对应于相同的前缀[1] 例如,如果您有: [1] ,[1,1],[1,2] 那么你想让[1]和[1,u]的概率都是这两者的和 概率([1])=概率([1])+概率([1,u]) 概率([1,])=概率([1])+概率([1,])) 在使用这些概率进行排序之后,您可能希望保留如此多的前缀,以使真正前缀的数量为beam_size 例如,您有[1]、[1]、[2]、[3] 其中概率为:0.1,0.08,0.11,0.15 然后,您要对其进行排序的概率为: 分别为0.18,0.18,0.11,0.15(0.18=0.1+0.08) 排序:[1]:0.18,[1,_]:0.18,[3]:0.15,[2]:0.11 例如,如果您有尺寸为2的梁,那么您可能希望保留 [1] ,[1,u]和[3],因此波束中有两个前缀,因为[1]和[1,u]算作相同的前缀(只要下一个字符不是1-这就是我们分别跟踪[1]和[1,]的原因)