概率分布|语音识别第一课:基于Tensorflow的端到端语音识别技术( 二 )


空白标记的作用:CTC矩阵中的时间步长通常比较小 , 如10毫秒 。 因此 , 句子中的一个字符会横跨多个时间步长 。 如 , C-A-T会变成C-C-C-A-A-T-T 。 所以 , 需要将CTC矩阵中出现该问题的字符串中的重复部分折叠 , 消除重复 。 那么像FUNNY这种本来就有两个重复字符(N)的词要怎么办呢?在这种情况下 , 就可以使用空白标记 , 将其插入两个N中间 , 就可以防止N被折叠 。 而这么做实际上并没有在文本中添加任何东西 , 也就不会影响其内容或形式 。 因此 , F-F-U-N-[空白]-N-N-Y最终会变成FUNNY 。
结束标记的作用:字符串的结束表示着一句话的结束 。 对字符串结束标记后的时间步长进行解码不会给候选字符串增加任何内容 。
概率分布|语音识别第一课:基于Tensorflow的端到端语音识别技术文章插图
步骤
初始化
· 准备一个初始列表 。 列表包括多个候选字符串 , 一个空白字符串 , 以及各个字符串在不同时间步长以空白标记结束的概率 , 和以非空白标记结束的概率 。 在时刻0 , 空白字符串以空白标记结束的概率为1 , 以非空白标记结束的概率则为0 。
迭代
· 选择一个候选字符串 , 将字符一个一个添加进去 。 计算拓展后的字符串在时刻1以空白标记和非空白标记结束的概率 。 将拓展字符串及其概率记录到列表中 。 将拓展字符串作为新的候选字符串 , 在下一时刻重复上述步骤 。
· 情况A:如果添加的字符是空白标记 , 则保持候选字符串不变 。
· 情况B:如果添加的字符是空格符 , 则根据语言模型将概率与和候选字符串的概率成比例的数字相乘 。 这一步可以防止错误拼写变成最佳候选字符串 。 如 , 避免COOL被拼成KUL输出 。
· 情况C:如果添加的字符和候选字符串的最后一个字符相同 , (以候选字符串FUN和字符N为例) , 则生成两个新的候选字符串 , FUNN和FUN 。 生成FUN的概率取决于FUN以空白标记结束的概率 。 生成FUNN的概率则取决于FUN以非空白标记结束的概率 。 因此 , 如果FUN以非空白标记结束 , 则去除额外的字符N 。
输出
经过所有时间步长迭代得出的最佳候选字符串就是输出 。
为了加快这一过程 , 可作出如下两个修改 。
1.在每一个时间步长 , 去除其他字符串 , 仅留下最佳的K个候选字符串 。 具体操作为:根据字符串以空白和非空白标记结束的概率之和 , 对候选字符串进行分类 。
2.去除矩阵中概率之和低于某个阈值(如0.001)的字符 。
具体操作细节可参考如下代码 。
def prefix_beam_search(ctc,alphabet,blank_token,end_token,space_token,lm,k=25,alpha=0.30,beta=5,prune=0.001): ''' function to perform prefix beam search on output ctc matrix and return the best string :param ctc: output matrix :param alphabet: list of strings in the order their probabilties are present in ctc output :param blank_token: string representing blank token :param end_token: string representing end token :param space_token: string representing space token :param lm: function to calculate language model probability of given string :param k: threshold for selecting the k best prefixes at each timestep :param alpha: language model weight (b/w 0 and 1) :param beta: language model compensation (should be proportional to alpha) :param prune: threshold on the output matrix probability of a character.If the probability of a character is less than this threshold, we do not extend the prefix with it :return: best string ''' zero_pad = np.zeros((ctc.shape[0]+1,ctc.shape[1])) zero_pad[1:,:] = ctc ctc = zero_pad total_timesteps = ctc.shape[0] # #### Initialization #### null_token = '' Pb, Pnb = Cache(), Cache() Pb.add(0,null_token,1) Pnb.add(0,null_token,0) prefix_list = [null_token]# #### Iterations #### for timestep in range(1, total_timesteps): pruned_alphabet = [alphabet[i] for i in np.where(ctc[timestep] > prune)[0]] for prefix in prefix_list: if len(prefix) > 0 and prefix[-1] == end_token: Pb.add(timestep,prefix,Pb.get(timestep - 1,prefix))Pnb.add(timestep,prefix,Pnb.get(timestep - 1,prefix)) continuefor character in pruned_alphabet: character_index = alphabet.index(character) # #### Iterations : Case A #### if character == blank_token: value = http://kandian.youth.cn/index/Pb.get(timestep,prefix) + ctc[timestep][character_index] * (Pb.get(timestep - 1,prefix) + Pnb.get(timestep - 1,prefix)) Pb.add(timestep,prefix,value) else: prefix_extended = prefix + character # #### Iterations : Case C #### if len(prefix)> 0 and character == prefix[-1]: value = http://kandian.youth.cn/index/Pnb.get(timestep,prefix_extended) + ctc[timestep][character_index] * Pb.get(timestep-1,prefix) Pnb.add(timestep,prefix_extended,value) value = Pnb.get(timestep,prefix) + ctc[timestep][character_index] * Pnb.get(timestep-1,prefix) Pnb.add(timestep,prefix,value) # #### Iterations : Case B #### elif len(prefix.replace(space_token,'')) > 0 and character in (space_token, end_token): lm_prob = lm(prefix_extended.strip(space_token + end_token)) ** alpha value = http://kandian.youth.cn/index/Pnb.get(timestep,prefix_extended) + lm_prob * ctc[timestep][character_index] * (Pb.get(timestep-1,prefix) + Pnb.get(timestep-1,prefix)) Pnb.add(timestep,prefix_extended,value)else: value = Pnb.get(timestep,prefix_extended) + ctc[timestep][character_index] * (Pb.get(timestep-1,prefix) + Pnb.get(timestep-1,prefix)) Pnb.add(timestep,prefix_extended,value) if prefix_extended not in prefix_list: value = Pb.get(timestep,prefix_extended) + ctc[timestep][-1] * (Pb.get(timestep-1,prefix_extended) + Pnb.get(timestep-1,prefix_extended)) Pb.add(timestep,prefix_extended,value) value = Pnb.get(timestep,prefix_extended) + ctc[timestep][character_index] * Pnb.get(timestep-1,prefix_extended) Pnb.add(timestep,prefix_extended,value) prefix_list = get_k_most_probable_prefixes(Pb,Pnb,timestep,k,beta) # #### Output #### return prefix_list[0].strip(end_token)