여기에서는 Kaldi 툴킷에서 FF 알고리즘에 대해 설명합니다. OpenFst의 것과 다르거나 다른 알고리즘입니다 (많은 알고리즘에 OpenFst 코드 자체를 사용합니다).

 

이러한 알고리즘은 디렉토리 fstext/에 있으며 해당 명령 행 프로그램은 존재하는 경우 fstbin/에 있습니다. 이 코드는 OpenFst 라이브러리를 사용합니다. 여기서는 현재 Kaldi 레시피에 실제로 사용되는 알고리즘에 대해서만 설명합니다.

 

Determinization

OpenFst의 알고리즘과 다른 결정 알고리즘을 사용합니다. 우리는 이것을 DeterminizeStar(); 해당 명령 행 프로그램의 이름은 fstdeterminizestar입니다. 우리의 결정 알고리즘은 실제로 결정과 함께 엡실론 제거를 수행한다는 점에서 OpenFst의 알고리즘보다 표준 FST 결정 알고리즘에 더 가깝습니다. 따라서 많은 다른 FST 알고리즘과 마찬가지로 엡실론을 "실제 심볼"로 간주하지 않습니다.

 

우리의 결정 알고리즘은 초기 결정 출력의 전이에 하나 이상의 출력 심볼이있을 때 발생하는 일을 처리하는 다른 방법을 가지고 있습니다. OpenFst 결정 알고리즘은 하나 이상의 (인코딩 된) 출력 심볼이없는 아크를 보장하기 위해 등가를 유지하면서 출력 심볼 (가중으로 인코딩 된) 주위를 이동하는 FactorWeights라는 함수를 사용합니다. 입력에 엡실론 기호가있는 새로운 상태를 도입하지는 않습니다. 그러나 FactorWeights 알고리즘은 DeterminizeStar의 출력에 대해 실패 할 수 있습니다. 사이클의 상태보다 출력이 더 많은 사이클이있을 수 있기 때문입니다 (엡실론 제거를 수행하지 않기 때문에 일반 결정 알고리즘의 출력에는 불가능 함). 대신, 둘 이상의 출력 심볼이있는 링크가 발생할 때마다 모든 출력 심볼을 수용 할 수있는 충분한 수의 중간 상태로 체인을 만듭니다. 무게와 입력 기호는이 체인의 첫 번째 링크에 있습니다. DeterminizeStar 알고리즘의 출력은 OpenFst가 사용하는 정의, 즉 엡실론을 일반 기호로 취급하는 정의에 따라 결정적입니다. 출력에는 입력 측에 엡실론이 있으며, 이는 결정론의 일반적인 정의에 위배되지만 링크에서 하나 이상의 출력 심볼을 허용하는 인코딩 메커니즘으로 간주되며 어떤 경우에도 매우 구체적으로 발생합니다. 상황 (엡실론 아크는 항상 상태에서 유일한 아크)입니다.

 

또 다른 차이점은 우리의 프로그램 fstdeterminizestar는 입력 FST에 출력 심볼이 가중치로 인코딩되도록 요구하지 않는다는 것입니다.

 

Debugging determinization

프로세스 디버깅에 오랜 시간이 걸리면 종료 여부를 말하기가 어렵 기 때문에 일반적으로 디버깅 결정은 매우 어렵습니다. fstdeterminizestar 프로그램에는 "kill -SIGUSR1 \ <its processid \>"신호를 보내면 결정을 디버깅하는 데 유용한 일부 정보가 중지되고 인쇄됩니다.

 

Determinization in the log semiring

DeterminizeInLog () 함수를 제공하여 결정하기 전에 일반 (열대) 반올림에서 Fst를 로그 반올림으로 캐스팅 한 다음 다시 변환합니다. 이것은 확률론을 보존하기 때문에 알고리즘에서 사용되는 결정의 형태입니다 (확률론 보존 및 테스트 참조).

 

Removing epsilons

우리는 FST를 절대로 날려 버리지 않을 것이라고 보장되는 RemoveEpsLocal ()이라는 엡실론 제거 알고리즘을 제공하지만 반면에 모든 엡실론을 제거하는 것은 아닙니다. 기본적으로 그래프를 크게 만들지 않고도 쉽게 제거 할 수있는 엡실론을 제거합니다. 어려운 문제이므로 여기에 최적 성이 보장되지 않습니다. RemoveEpsLocal () 함수는 OpenFst의 RemoveEps () 함수와 약간 다른 동작을합니다. 하나는 입력 epsilon과 하나는 출력 epsilon 인 경우 두 개의 호를 결합하기 때문입니다. RemoveEpsLocal () 함수는 FST 동등성을 유지합니다.

 

로그 세미 링의 확률을 유지하면서 열대성 반올림의 동등성을 유지하는 RemoveEpsLocalSpecial () 함수도 있습니다 (확률성에 대한 자세한 내용은 다음 섹션 참조). 우리는 두 개의 반고리를 동시에 고려해야하기 때문에 반 반복 형식주의의 유용성이 약간 저하되는 경우 인 것으로 보인다.

 

Preserving stochasticity and testing it

우리는 확률 적 FST를 FST로 정의하는데, 여기서 FST의 반고리에서 주어진 상태 (및 최종 가중치)에서 나온 호의 가중치의 합은 (반고리에서) 1과 같습니다. 이 개념은 로그 세미 링에서 가장 유용하고 자연 스럽습니다. 본질적으로 확률 론적 FST는 주어진 호에서 가중치의 합이 1 인 것입니다 (예를 들어, 적절히 정규화 된 HMM은 확률 론적 FST에 해당합니다).

 

IsStochasticFst () 함수는 확률을 테스트합니다. 선택적으로 FST가 확률 적으로 실패한 정도를 사용자에게 알리기 위해 최소 및 최대 무게를 출력 할 수 있습니다. 이것의 명령 행 버전은 fstisstochastic입니다. 우리는 확률 론적 입력을 고려하여 확률 론적 출력을 생성한다는 의미에서 확률론을 보존하기 위해 사용하는 대부분의 FST 알고리즘을 목표로합니다. 비 확률 입력의 경우 가중치의 최소 및 최대 범위가 더 커지지 않는 것을 목표로합니다. 기본적으로 isstochasticfst 프로그램은 로그 반올림으로 캐스트 한 후 확률을 테스트합니다. 이것이 가장 유용한 경우입니다 (옵션 –test-in-log = false를 제공하여이를 중지 할 수 있음).

 

확률 성을 유지하기 위해 우리가 구성하는 FST에는 특정 속성이 있어야합니다. 이는 L, C 및 H에 적용되어야합니다. 예를 들어 L을 고려하십시오. G를 통과하는 경로에 해당하는 모든 선형 FST에 대해이 FST F를 호출하면 제품 L o F는 확률 적이어야합니다. 이것은 기본적으로 L이 발음 확률을 올바르게 표준화했음을 의미합니다. 공식적으로 요구되는 실제 재산은 이것보다 약간 강할 수 있습니다 (이것은 확률이 "적시"에 나타나도록하는 것과 관련이 있습니다). 실제로 그래프 작성의 각 단계마다 isstochasticfst 프로그램을 실행하여 확률을 확인합니다.

 

Minimization

OpenFst에서 제공하는 최소화 알고리즘을 사용하지만 결정적이지 않은 FST에 최소화를 적용 할 수 있도록 OpenFst를 컴파일하기 전에 패치를 적용합니다. 그 이유는 최소화하기 전에 명확성 기호를 제거 할 수 있기 때문에 더 최적입니다 (최소화로 더 많은 상태를 결합 할 수 있음). 패치는 데이터 구조 관련 문제를 해결합니다. 기본적으로 OpenFst의 최소화 알고리즘은 비 결정적 FST에 적용 할 수 있습니다. 입력 심볼의 일부로 가중치 및 출력 심볼을 인코딩 한 후 FST를 최소화하는 fstminimizeencoded라는 명령 줄 프로그램을 제공하므로 FST가 수락자가됩니다. 이것은 fstminimize 프로그램이 수행하는 것과 동일합니다. 우리가 확률론을 보장하는 방식은 무게 추를 피하기 때문에 바람직합니다.

 

Composition

대부분의 경우 OpenFst 자체의 컴포지션 알고리즘을 사용하지만 TableCompose () 함수와 해당 명령 줄 프로그램 fsttablecompose를 사용합니다. 이는 특정 경우에보다 효율적인 컴포지션 알고리즘입니다. OpenFst의 "Matcher"개념을 사용합니다. Matcher는 특정 입력 또는 출력 기호가있는 호를 찾기 위해 상태에서 호를 조회하는 컴포지션 중에 사용되는 일종의 도우미 클래스입니다. OpenFst가 사용하는 일반적인 매처는 SortedMatcher입니다.이 레이블은 관련 레이블에서 정렬되는 호에 의존하며 이진 검색을 수행합니다. TableMatcher는 레이블로 인덱스 된 테이블을 작성하는 것이 효율적인 경우를 감지하고이 상태에서는 2 진 검색의 오버 헤드를 피합니다. 이것은 매우 높은 정도의 어휘집으로 구성 할 때 속도가 빨라집니다.

 

Adding and removing disambiguation symbols

FST 레시피 (다른 트랜스 듀서 기반 레시피와 마찬가지로)는 명확성 기호를 사용합니다. 일반적인 레시피에서는 어휘집 FST (L)의 입력 측에 추가되어 결정 가능합니다. 또한 명확성 기호를 G 및 C에 추가합니다 (음성 기호 참조). 컴포지션을 수행하고 오른쪽의 FST에 입력에 명확성 기호가있을 때마다 이론적으로 왼쪽 FST의 각 상태에 각 명확성 기호에 대한 자체 루프를 추가합니다. 입력 및 출력. 왼쪽과 오른쪽의 명확성 기호에 대한 실제 정수 기호 ID는 동일하지 않을 수 있습니다. 예를 들어 G에는 특수 기호 # 0이 있습니다 (epsilon은 일반적으로 사용됨). 이것에 대한 symbol-id는 일반적으로 가장 높은 번호의 단어에 1을 더한 것입니다. 그러나이 기호를 L을 통해 전달하려면 L의 입력 기호 테이블 (주로 전화를 포함)에 # 0을 나타내는 기호가 필요합니다. 우리는 가변 FST와 레이블의 두 벡터를 취하는 AddSelfLoops () 함수를 가지고 있습니다 (라벨은 심볼의 정수 ID입니다). 벡터는 크기가 동일하며 명확성 기호에 대한 해당 입력 및 출력 레이블을 나타냅니다. 이 기능은 각각의 최종 상태와 그 밖의 하나 이상의 아크에 엡실론이 아닌 출력 기호가있는 각 상태에 자체 루프를 추가합니다.

 

명령 행에서 fstrmsymbols 프로그램으로 액세스 할 수있는 DeleteISymbols () 함수를 사용하여 명확성 기호를 제거합니다.

 


[1] https://kaldi-asr.org/doc/fst_algo.html

  • 음성인식기 음향 모델 중 뛰어난 성능을 내고 있는 FSMN 에 대한 논문들을 리뷰해 보았다. 
  • 신호처리 이론 중 IIR filter는 High order FIR filter로 근사가 가능하다.
  • RNN 계통에서 recurrent layer는 개념적으로 first order IIR filter와 유사하다고 볼 수 있다.
  • 핵심 아이디어는 recurrent layer를 대신할 수 있는 High order FIR filter와 같은 DNN 구조를 제시한다는 것
  • feedforward neural network (FNN)에서 Memory block을 둬서 현재 프레임의 앞뒤의 long context information을 인코딩 해서 그 정보를 사용하여 현재의 FNN을 update 해 나간다.
  • RNN 계통보다 모델이 light하고, 학습 시 안정적이다.
  • scalar FSMN, vector FSMN, compact (vector) FSMN, Deep-FSMN 

 

 


 

[1] https://arxiv.org/abs/1803.05030

 

 

1) VTLP based data augmentation

- vocal tract length perturbation (VTLP) [3], has shown gains on the TIMIT phoneme recognition task. VTLP was further extended to large vocabulary continuous speech recognition (LVCSR) in [4]. In [3] the VTLP warping factors for each utterance is randomly chosen from a range (e.g. [0.9, 1.1]). Using these sampled warping factors, improvement was reported on TIMIT phoneme recognition task. In [4], VTLP was used in large vocabulary continuous speech recognition (LVCSR) tasks, and an observation was made that selecting VTLP warping factors from a limited set of perturbation factors, was better.

 

2) Tempo perturbation based data augmentaion

- Speech rate perturbation, where the speech rate of the audio was modified by randomly selected factor, was investigated in [6]. In speech rate modification, the tempo of the signal is modified while ensuring that the pitch and spectral evelope of the signal does not change. The WSOLA [16] based implementation in the tempo command of the SoX tool was used to achieve this perturbation.

 

3) Speed perturbation based data augmentation

- To modify the speed of a signal we just resample the signal. The speed function of Sox was used for this. Two additional copies of the original training data were created by modifying the speed to 90% and 110% of the original rate.

 

4) SpecAugment

 

**In order to implement speed perturbation, we resample the signal using the speed function of the Sox audio manipulation tool; SoX, audio manipulation tool, (accessed March 25, 2015). [Online]. Available: http://sox.sourceforge.net/

 


 

[1] T. Ko, V. Peddinti, D. Povey, and S. Khudanpur, “Audio augmentation for speech recognition.” in INTERSPEECH, 2015, pp. 3586–3589.

 

 

 

RNN-T for ASR 은 크게 Audio Encoder, Test Predictor 및 Joiner의 세 가지로 구성되어 있다.

 

1) Audio Encoder는 audio frames을 time t까지 input으로 받아서 high-level acoustic feature a_t를 인코딩한다. 2) Text predictor은 과거 text 의 과거정보를 h index까지 받아서, high-level lexical feature t_h를 인코딩한다. 3) 이 high-level acoustic and lexical features은 Joiner 모듈을 태우는데, 이 모듈은 두 feature을 결합하여, output unit에 대한 probability distribution, y_t,h를 내놓는다.

 

RNN-T는 CTC based 모델과 다르게, output symbols에 대한 확률을 생성하기 위해 audio, text 두 정보를 모두 사용함으로써, CTC 모델의 조건부 독립 가정을 극복할 수 있다는 장점이 있다.

 

Loss는 RNN-Transducer forward-backward 알고리즘을 사용하며 디테일은 [1] 논문을 참고하면 된다.

 

Test 할 때는, decoding 과정이 필요하며, 관련 메모는 [2,3]을 참고하면 된다.

 


[1] Alex Graves, "Sequence Transduction with Recurrent Neural Networks", 2012

[2] https://sequencedata.tistory.com/3?category=1129285

[3] https://sequencedata.tistory.com/4?category=1129285

 

 

 

 

 

앞서 메모한 #RNN-T Beam search [1] 글에 이어, 최근 facebook AI 팀에서 ICASSP 2020에 제출한 것처럼 보이는 아카이브에 올린 [2] 논문에 대해서 메모하고자 한다. [2]에서는 Latency Controlled RNN과 improved RNNT Beam Search를 제안했지만, 이 글은 후자인 RNNT improved Beam Search 부분만을 위한 글이다.

 

먼저 간단하게 기본 beam search for RNNT를 리마인드 해보면 이렇다. RNN-T에서는 다음 joiner call을 할 때, 다음 time frame t+1로 넘어갈 것인지, 같은 time frame t에서의 output units을 더 emit할 것인지 결정하기 위해서, output units은 특별한 symbol인 blank symbol을 포함한다. 매번 joiner call 마다, 1) 우리는 time axis (t) 에서 다음 audio frame t+1로 넘어가거나, 2) hypothesis를 업데이트하고 같은 time frame t 에서 계속해서 symbols를 emit 할 수 있다. 전자는 Joiner가 blank symbol을 가장 큰 확률로 emit할 때이고, 후자는 blank가 아닌 chracter들 중 하나를 가장 큰 확률로 outputting 할 때이다. 기존 RNNT Beam Search는 다음과 같다.

사진 삭제

RNN-T Beam Search [3]

일반적으로 beam search를 computationally efficient하게 개선하기 위해 hypothesis pruning을 많이 시도한다. 이 논문의 beam search의 목표도 그러하다. RNNT beam search에서 hypothesis set A의 hypothesis들의 확장을 제한한다는 것이 이 논문 beam search 알고리즘의 핵심이다. pesudo code는 다음과 같다.

대표사진 삭제

Improved RNN-T Beam Search [2]

 

 

간단하다. 두 가지만 확인하면 된다.

 

1) state_beam을 도입했다. log space에서 hypothesis setB의 best hypothesis보다 (state_beam + hypothesis setA의 best hypothesis)이 더 확률이 낮은 경우, "hypothesis set B의 이미 존재하는 hypos"들이, "hypothesis setA 로부터 확장될 hypothesises들"보다 이미 더욱 좋은 hypothesises이라고 가정해서 while loop 를 끝내 버려서 그 때까지의 hypothesis로 결과를 낸다.

 

2) 또한 A에 추가되는 hypothesises의 개수를 제한하는 expand_beam을 도입했다. hypothesis set B가, hypothesis set A에서 가장 가능성있는 hypothesis 보다 probability가 높은 W(beam width) 개의 hypothesis를 갖자마자 beam search criterion은 시간 t에서 충족되며, 프레임 t + 1 처리를 시작할 수 있다.y_t, h를 생성하는 (t, h)에서의 Joiner 호출의 경우, 먼저 Hypothesis set A 와 B 에서의 최고의 확률 값 (non-blank output unit (yt, h) 중에서 최고의 확률과, 최고의 확률)을 계산한다. 그 후, log (best prob)보다 log (Pr (k | y ∗, t)가 더 높은 output, k만 추출하여 hypothesis set A에 추가한다.

 

** hypothesis set A는 여전히 시간 t에 대해 고려되고 있는 hypothesis를 포함하고 있으며, hypothesis set B는 시간 t에서 이미 blank symbol을 emit했으며, 현재 시간 프레임인 t+1에 있는 hypothesis를 포함한다.

 


코드를 작성해보면 다음과 같다.

 

 

def recognize_beam_facebook(self, h, recog_args, rnnlm=None, state_beam, expand_beam):
    """facebook Beam search implementation for rnn-transducer.
    Args:
        h (torch.Tensor): encoder hidden state sequences (maxlen_in, Henc)
        recog_args (Namespace): argument Namespace containing options
        rnnlm (torch.nn.Module): language model module
        state_beam: ...
        expand_beam: ...
    Returns:
        nbest_hyps (list of dicts): n-best decoding results
    """
    beam = recog_args.beam_size
    k_range = min(beam, self.odim)
    nbest = recog_args.nbest
    normscore = recog_args.score_norm_transducer

    B_hyps = [{'score': 0.0, 'yseq': [self.blank], 'cache': None}]

    for i, hi in enumerate(h):
        A_hyps = B_hyps
        B_hyps = []

        while True:
            new_hyp = max(A_hyps, key=lambda x: x['score'])
            a_best_hyp = max(A_hyps, key=lambda x: x['score'])
            b_best_hyp = max(B_hyps, key=lambda x: x['score'])
            
            if log(b_best_hyp) >= state_beam + log(a_best_hyp):
                break
            
            A_hyps.remove(new_hyp)

            ys = to_device(self, torch.tensor(new_hyp['yseq']).unsqueeze(0))
            ys_mask = to_device(self, subsequent_mask(len(new_hyp['yseq'])).unsqueeze(0))
            y, c = self.forward_one_step(ys, ys_mask, new_hyp['cache'])

            ytu = torch.log_softmax(self.joint(hi, y[0]), dim=0)

            best_prob = max(ytu[1:])
            
            for k in six.moves.range(self.odim):

                if k == self.blank:
                    beam_hyp = {'score': new_hyp['score'] + float(ytu[k]),
                            'yseq': new_hyp['yseq'][:],
                            'cache': new_hyp['cache']}

                    B_hyps.append(beam_hyp)
    
                else:
                    if float(ytu[k]) >= log(best_prob) - expand_beam :
                        beam_hyp = {'score': new_hyp['score'] + float(ytu[k]),
                            'yseq': new_hyp['yseq'][:],
                            'cache': new_hyp['cache']}

                        beam_hyp['yseq'].append(int(k))
                        beam_hyp['cache'] = c

                        A_hyps.append(beam_hyp)

            if len(B_hyps) >= k_range: // beam_size (W)
                break

    if normscore:
        nbest_hyps = sorted(
            B_hyps, key=lambda x: x['score'] / len(x['yseq']), reverse=True)[:nbest]
    else:
        nbest_hyps = sorted(
            B_hyps, key=lambda x: x['score'], reverse=True)[:nbest]

    return nbest_hyps

 


[1] https://sequencedata.tistory.com/3?category=1129285

[2] M. Jain, al "RNN-T FOR LATENCY CONTROLLED ASR WITH IMPROVED BEAM SEARCH", 2020

[3] Alex Graves, "Sequence Transduction with Recurrent Neural Networks", 2012

 

 

몇 년 전, Alex Graves가 길이가 다른 input/output sequences 를 잘 mapping 할 수 있는 RNN-T 모델을 Sequence Transduction with RNN [1] 논문에서 제안했다. 여러 가지 sequence transduction 문제 중 ASR 문제에 집중을 했는데, 구글이 작년에 이 모델로 모바일 상에서 steamable 음성인식 성과를 소개해서인지, 최근들어 이 모델에 대한 관심이 급격하게 높아졌다. ASR 문제에서 input sequence는 audio signal, output sequence는 텍스트이다. RNN-T에 대한 설명은 생략하고, 이 글의 주제인 beam search for RNN-T 알고리즘에 대해서 메모를 남겨보고자 한다. 이 알고리즘은 [1] 논문에서 처음 제안되었다. pseudo code는 다음과 같다.

 

대표사진 삭제

사진 설명을 입력하세요.

 

 

RNN-T 모델을 위한 beam search를 위해 우선 time t에서 hypothesis h 는 frame embedding a_t 와 text embedding t_h 를 가진다고 가정한다. 이 두 embedding들은 결합되고 Joiner를 태워서 y_t,h output units(eg, 256) 에 대한 확률을 생성한다. while 문 안에 있는 for문에서의 y는 한 시점의 output units 이다.

 

RNN-T의 output units 은 우리가 모델링하고자 하는 output character들 외에 특별한 symbol 인 blank (∅) 가 존재한다. 이 blank symbol 은 디코딩 진행시, lattice 상에서 t 시점에 있을 때, 다음 time frame인 t+1 으로 진행할 것인지 혹은 t 시점에서의 output unit 과 같은 문자를 emit할 것인지 결정하기 위해 존재한다.

 

Beam search는 두 가지 hypothesis set A, B 를 사용하여 수행이 된다. 이것은 t + 1로 이동되는 최소 W (beam size) hypothesis가 t에서 여전히 생성 될 수있는 것보다 높은 확률을 갖도록하기 위함이다.

 

알고리즘을 보면 크게 세 부분으로 나눌 수 있다.

 

1. 첫번째는 특정시점까지 찾아진 hypothesis set A에 포함된 모든 hypothesis에 대한 probability를 그 hypothesis 의 적절한 prefix의 set을 사용하여 계산해 내는, 첫번째 for-loop에 대한 부분이다. 모든 time-step마다 그 시점까지 찾아진 hypothesis set B(=A) 에 포함된 모든 hypothesis에 대한 확률을 계산해낸다.

 

2. 두번째는 Beam search 를 진행하는 중에, hypothesis set A에서 최고 확률이 높은 hypothesis를 선택하고 blank 또는 non-blank symbol 로 확장하는 부분이다. while 를 돌며 Joiner(softmax)를 태워서 나온 y_t,h output units(eg, 256) 에서 모든 dimension output(probability)에 대해서, 기존 score(prob)에 곱한 score(prob)를 업데이트 하고, 해당 chracter(정확히는 index)를 append 시킨 새로운 hypothesis를 hypothesis set A 혹은 B에 append 한다. 이 때, blank에 해당하는 output dimension에 대해서는 score 계산 후, hypothesis set B에 append 하고, 다른 dimension에 해당하는 값(다른 캐릭터) 들에 대해서는 socre 계산 후, hypothesis set A에 append한다. While 문은 beam width 만큼 hypothesis가 hypothesis set B에 존재할 때까지 진행된다. 다시 말하면, blank가 마지막에 사용되는 expansion hypothesis는 를 hypo set B에 삽입된다. 한편, blank가 사용되지 않는 다른 모든 symbols을 사용하는 expansion hypothesis들은 hypothesis set A에 다시 삽입되어 t에서 확장 된 hypothesis set A가 계속해서 재 정의 된다.

 

3. return 할 때, hypothesis set B에 속한 W 개 미만의 hypothesis 중에 "각각의 hypotesis의 확률 값을 chracter들의 숫자로 나눈 최종 score"을 비교하여 가장 높은 score 값이 되는 hypothesis를 return 한다.

 

** 참고로 hypothesis set A, B에 저장 할 때는 {score(probability), character sequence(hypothesis)} 가 사전 형태로 함께 저장된다는 것을 함께 생각해야, 헷갈리지 않는다.


espnet에 구현되어 있는 beam search 코드[2]이다. 참고하면 더 이해하기 편하다. pseudo code와는 다르게 코드 상에선 blank output 에 대해서 해당 hypothesis를 hypothesis set B 에 append 하는 부분을 for 문 안으로 넣어놓았다.

 

 

def recognize_beam(self, h, recog_args, rnnlm=None):
    """Beam search implementation for transformer-transducer.
    Args:
        h (torch.Tensor): encoder hidden state sequences (maxlen_in, Henc)
        recog_args (Namespace): argument Namespace containing options
        rnnlm (torch.nn.Module): language model module
    Returns:
        nbest_hyps (list of dicts): n-best decoding results
    """
    beam = recog_args.beam_size
    k_range = min(beam, self.odim)
    nbest = recog_args.nbest
    normscore = recog_args.score_norm_transducer

    B_hyps = [{'score': 0.0, 'yseq': [self.blank], 'cache': None}]

    for i, hi in enumerate(h):
        A_hyps = B_hyps
        B_hyps = []

        while True:
            new_hyp = max(A_hyps, key=lambda x: x['score'])
            A_hyps.remove(new_hyp)

            ys = to_device(self, torch.tensor(new_hyp['yseq']).unsqueeze(0))
            ys_mask = to_device(self, subsequent_mask(len(new_hyp['yseq'])).unsqueeze(0))
            y, c = self.forward_one_step(ys, ys_mask, new_hyp['cache'])

            ytu = torch.log_softmax(self.joint(hi, y[0]), dim=0)

            for k in six.moves.range(self.odim):
                beam_hyp = {'score': new_hyp['score'] + float(ytu[k]),
                            'yseq': new_hyp['yseq'][:],
                            'cache': new_hyp['cache']}

                if k == self.blank:
                    B_hyps.append(beam_hyp)
                else:
                    beam_hyp['yseq'].append(int(k))
                    beam_hyp['cache'] = c

                    A_hyps.append(beam_hyp)

            if len(B_hyps) >= k_range: // beam_size (W)
                break

    if normscore:
        nbest_hyps = sorted(
            B_hyps, key=lambda x: x['score'] / len(x['yseq']), reverse=True)[:nbest]
    else:
        nbest_hyps = sorted(
            B_hyps, key=lambda x: x['score'], reverse=True)[:nbest]

    return nbest_hyps

 


[1] Alex Graves, "Sequence Transduction with Recurrent Neural Networks", 2012

[2] ESPnet; https://github.com/espnet/espnet/blob/master/espnet/nets/pytorch_backend/transducer/transformer_decoder.py

 

 

Smart speaker or Voice Assistant 를 만들기 위해서 필요한 알고리즘을 정리해보자. 필요한 컴포넌트 중심으로 Flow를 간단하게 적어보면 다음과 같을 수 있다.

 

Mic -> Audio Processing -> KWS -> ASR -> NLU -> knowledge/Skill/Action -> TTS -> Speaker


각 모듈에 대한 간략한 설명

  • Audio Processing includes Acoustic Echo Cancellation, Beamforming, Noise Suppression (NS).
  • Keyword Spotting (KWS) detects a keyword (okay google) to start a conversation.
  • Speech To Text (STT or ASR)
  • Natural Language Understanding (NLU) converts raw text into structured data.
  • Knowledge/Skill/Action- Knowledge-based model provide an answer.
  • Text To Speech(TTS)

각 모듈에 대한 알고리즘을 오픈소스 중심으로 생각나는대로 간단하게 정리해보자.

Audio Processing

Several Basic Filters for sound and speech processing

https://github.com/voidqk/sndfilter

reverb, dynamic range compression, lowpass, highpass, notch

Automatic Gain Control

TF AGC: https://github.com/jorgehatccrma/pyagc

Acoustic Echo Cancellation

Removes echoes that can occur when a microphone picks up audio from a speaker, preventing feedback loops.

SpeexDSP

https://github.com/xiph/speexdsp

Daemon based on SpeexDSP AEC for the devices running Linux. https://github.com/voice-engine/ec

Residual Echo Cancellation (RES) - SpeexDSP 에 같이 구현되어 있음

Direction Of Arrival (DOA)- Most used DOA algorithms is GCC-PHAT

DOA (SRP-PHAT and GCC-PHAT)

https://github.com/wangwei2009/DOA

TDOA

https://github.com/xiongyihui/tdoa

ODAS

https://github.com/introlab/odas

ODAS stands for Open embeddeD Audition System. This is a library dedicated to perform sound source localization, tracking, separation and post-filtering.

Beamforming

Involves using multiple microphones to focus on sounds from a specific direction, enhancing the signal from the desired source while suppressing noise. Common algorithms include GCC-PHAT, MVDR, GSC, and DNN-based methods.

  • Direction Of Arrival (DOA): Estimates the direction of the incoming sound. This is important for beamforming and source localization. Algorithms like SRP-PHAT, GCC-PHAT, and systems like ODAS are used.

Beamformlt - delay & sum beamforming

https://github.com/xanguera/BeamformIt

CGMM Beamforming

https://github.com/funcwj/CGMM-MVDR

MVDR Beamforming

https://github.com/DistantSpeechRecognition/mcse(mvdr + postfilter)

GSC Beamforming

그외 DNN-based 방법들

https://github.com/fgnt/nn-gev

Voice Activity Detection

Detects whether the input signal contains speech, helping to reduce unnecessary processing when there is no speech. Common tools include Sohn VAD and WebRTC VAD.

Sohn VAD

https://github.com/eesungkim/Voice_Activity_Detector

WebRTC VAD

https://github.com/wiseman/py-webrtcvad

DNN VAD

Noise Suppresion

Reduces background noise to improve the clarity of the spoken input.

MMSE-STSA
https://github.com/eesungkim/Speech_Enhancement_MMSE-STSA

NS of WebRTC audio processing
https://github.com/xiongyihui/python-webrtc-audio-processing

KWS

  • Mycroft Precise - A lightweight, simple-to-use, RNN wake word listener
  • Snowboy - DNN based hotword and wake word detection toolkit
  • Honk - PyTorch reimplementation of Google's TensorFlow CNNs for keyword spotting
  • ML-KWS-For-MCU - Maybe the most promise for resource constrained devices such as ARM Cortex M7 microcontroller
  • Porcupine - Lightweight, cross-platform engine to build custom wake words in seconds

ASR

NeMo | https://github.com/NVIDIA/NeMo
ESPNET | https://github.com/espnet
Speechbrain | https://github.com/speechbrain
Kaldi | https://github.com/kaldi-asr/kaldi

NLU

  • Rasa NLU
  • Snips NLU - a Python library that allows to parse sentences written in natural language and extracts structured information.

TTS

Audio I/O

  • portAudio, pyaudio
  • libsoundio
  • ALSA
  • pulseAudio

+ Recent posts