Kaldi Tutorial for Korean Model Part 4
칼디로 한국어 음성인식 구현하기 파트 4
- 본 튜토리얼은 칼디를 막 시작하는 초보자들을 대상으로 하고 있으므로 칼디를 어느정도 공부하신 분들에게는 적합하지 않을 수 있습니다. 모든 설명은 깃헙에 올려진 run.sh 스크립트를 기준으로 진행되며 추가적으로 궁금한 사항은 칼디 공식 홈페이지나 칼디 구글 헬프를 이용하길 바랍니다.
- 본 튜토리얼에서 사용하는 스크립트는 맥과 리눅스(우분투) 환경에서 준비해왔기 때문에 그 외에 환경 (예: 윈도우)에서는 제대로 진행되지 않을수도 있습니다. 따라서 다소 불편하시더라도 이 부분은 최대한 맞춰주시고 튜토리얼을 진행하여 주시기 바랍니다.
- 튜토리얼에 사용되는 튜토리얼용 서울말 낭독체 코퍼스는 다음 링크를 통해 받으실 수 있으며, 튜토리얼 진행 도중 훈련에 필수적인 파일들이 제대로 생성되지 않을 상황에 대비해서 몇개의 파일을 미리 생성하여 준비하였으니 필요한 데이터는 그때마다 다운 받으시고 계속해서 진행해 나가시면 되겠습니다.
- 궁금한 사항이 있으실 경우 개인적인 메일을 보내시기 보다는 아래에 댓글을 다셔서 대부분의 질문이 이 튜토리얼을 보는 모든 분들과 공유되게 해주시기 바랍니다.
Monophone Training and Decoding
-
steps/train_mono.sh
- 음향모델을 만들기 위한 첫 단계가 바로 monophone 훈련인데, 이곳에서는 우리가 준비한 오디오와 텍스트 파일을 이용해서 각 개별 음소에 대한 확률값을 훈련한다.
- 참고로 훈련에 사용하는 음소들은 연구자의 주관에 따라 그 개수와 모양이 달라질 수 있으며, 현재 본 튜토리얼에서는 총 195개의 음소를 사용하고 있다.
- phones.txt는 nonsilence.txt와 silence.txt에 정의된 음소들을 모두 가져온 뒤, 그 뒤에 B, I, E, S를 붙여서 4가지 타입의 음소를 새롭게 생성하며, 이들은 각각 시작(Begin), 중간(Intermediate), 끝(End), 단독(Singleton)으로 발생하는 음소들이다. 예를들어 aa_B는 '안녕(aa nf nn yv ng)'처럼 단어 맨 앞에 등장하는 음소를 말하고, aa_S는 "아(aa)"처럼 홀로 등장하는 음소를 말한다.
- 본 튜토리얼에서는 실제 훈련을 위해 사용되는 GMM-HMM 기법과 EM 알고리즘등에 대한 설명은 생략하지만 적어도 kaldi에서 어떠한 방식으로 음소가 훈련되는지 그 과정을 간략하게 설명하겠다.
- 먼저 “음성” 이라는 짧은 소리를 훈련한다고 생각해보자. 이 때, 소리를 담은 텍스트 정보를 다음과 같이 “xx mf s0 vv ng” 총 5개의 음소로 이루어진 음소셋으로 변환할 수 있다.
- 그리고 소리와 음소를 등간격으로 잘라서 그 구간이 이 음소가 발생한 구간일 것이라고 정의한다.
- 이렇게 초반에 정의한 각 음소의 등간격은 다양한 소리를 훈련함에 따라 그 간격을 점차 학습하면서 음소에 적합한 구간만 지정하도록 다음과 같이 변화한다.
- 훈련이 완료되면 final.mdl이라는 음향모델이 생성된다.
- 음향모델을 만들기 위한 첫 단계가 바로 monophone 훈련인데, 이곳에서는 우리가 준비한 오디오와 텍스트 파일을 이용해서 각 개별 음소에 대한 확률값을 훈련한다.
-
steps/align_si.sh
- monophone 훈련을 완료하여 최종적으로 음향모델(final.mdl)을 완성하였다면 바로 테스트 데이터를 이용해 디코딩(음성인식) 성능을 얻을 수 있다. 하지만 monophone 훈련으로 얻은 음향모델은 가장 기본적인 훈련 단계만 거친 모델이기 때문에 우리가 원하는 수준에 도달한 모델이라고 할 수 없다. 그러므로 추가적으로 훈련을 진행해야 하는데, 이를 위해서는 먼저 monphone 훈련을 통해 얻은 음향모델로 훈련 데이터를 정리해야 한다.
- 여기서 정리란 (위에서 했던 것처럼) 각 소리 파일에 대해서 음소별로 구간을 잘라주는 작업을 말한다. 우리가 처음 훈련했을때는 어떠한 정보도 없었기에 모든 음소를 등간격으로 잘랐지만, 지금은 훈련된 모델이 있으므로 어느정도 의미있게 음소 구간을 자를 수 있다. 이처럼 훈련으로 얻은 음향모델을 통해 훈련 데이터에 음소 구간을 의미있게 잘라주는 작업을 align이라 하며, 이는 필자의 블로그에서 설명한 자동강제정렬(forced alignment) 작업과 같다고 할 수 있다. (자동강제정렬이 궁금하다면 본 블로그의 한국어 자동강제정렬 글을 참조하도록 하자)
- align 작업을 진행하면 mono_ali 폴더가 생성되며 이 안에 있는 align.*.gz가 바로 모든 훈련데이터에 대한 align 정보를 담고 있다.
- 만약 monophone 훈련으로 얻은 음향모델로 만족하여 다음 훈련을 진행하지 않겠다면 이 작업은 진행할 필요가 없다.
-
utils/mkgraph.sh
- kaldi는 음성인식에 사용할 최종 모델을 fst(finite state transducer) 형태로 변환하여 사용하므로 이제까지 생성한 모델들을 최종적으로 결합해주는 과정을 거쳐야한다. 결합을 위해서는 크게 음향모델(final.mdl)과 언어모델(lm.arpa)이 필요하며 이 둘을 결합하면서 2가지의 부수적인 모델(context model, lexicon model)들도 함께 결합된다. 즉 최종 결합을 위해서는 총 4가지의 모델들이 필요하며 이 모델들을 다 결합하면 음성인식에 사용가능한 HCLG.fst가 완성된다. 그럼 각 모델에 대해 간략히 알아보도록 하겠다.
- 음향모델: 각 음소들의 확률정보를 담은 모델로 최종 훈련이 끝나면 final.mdl로 저장되고, 나중에 H.fst로 변환이되어 HCLG 결합에 사용된다.
- context모델: 음향모델에서 음소들의 연쇄를 훈련하였다면 그 연쇄를 하나의 음소로 연결해주는 모델로, monophone처럼 하나의 음소를 독립적인 음소로 훈련한 모델의 경우 의미가 없지만 triphone처럼 음소의 연쇄를 훈련한 경우 이를 하나의 음소로 연결해주는 것이 필요하며 이러한 정보를 담은 것이 C.fst이다. 이에 대한 설명은 추후 triphone 모델 훈련과정에서 더 자세히 하도록 하겠다.
- lexicon모델: 각 단어의 발음 정보를 담은 모델로 단어에 대한 발음 음소열로 구성되어있다. 이는 prepare_lang.sh을 통해 최종적으로 L.fst로 변환된다.
- 언어모델: 각 단어들의 연쇄 확률정보(ngram)를 담은 모델로 srilm 툴을 사용하여 훈련하면 lm.arpa로 저장되며 이를 G.fst로 변환하여 최종 결합에 사용한다.
- graph 생성 과정을 거치면, 최종적으로 mono/graph 폴더가 생성되며 이 곳에 HCLG.fst가 저장된다.
- kaldi는 음성인식에 사용할 최종 모델을 fst(finite state transducer) 형태로 변환하여 사용하므로 이제까지 생성한 모델들을 최종적으로 결합해주는 과정을 거쳐야한다. 결합을 위해서는 크게 음향모델(final.mdl)과 언어모델(lm.arpa)이 필요하며 이 둘을 결합하면서 2가지의 부수적인 모델(context model, lexicon model)들도 함께 결합된다. 즉 최종 결합을 위해서는 총 4가지의 모델들이 필요하며 이 모델들을 다 결합하면 음성인식에 사용가능한 HCLG.fst가 완성된다. 그럼 각 모델에 대해 간략히 알아보도록 하겠다.
-
steps/decode.sh
- 훈련을 통해 모델을 최종적으로 생성하였다면 그에 대한 성능평가가 이루어져하는데, 이를 위해서는 실제로 발화하면서 디코딩 결과를 눈으로 볼 수도 있지만 테스트 데이터를 이용하여 객관적인 성능을 추려 볼 수도 있다. 그렇다면 테스트 데이터를 이용하여 객관적인 성능을 볼 때 얻는 장점은 어떤것이 있는지 알아보도록 하자.
- 다른 모델과의 성능을 객관적으로 비교할 수 있다.
- 정해진 테스트 셋을 이용해서 모델의 성능을 평가한다면 이후의 얻은 다른 모델과의 성능을 비교하기가 용이할 것이다. 예를들어 A와 B 모델을 생성해서 똑같은 테스트 셋에 적용하여 성능을 얻었는데 A의 경우 80점이 나오고 B의 경우 90점이 나왔다면 B가 A보다 더 좋은 모델이라고 쉽게 평가할 수 있을것이다.
- 제대로된 음성인식 모델을 얻기 위해서는 다양한 실험을 진행해야 하는데, 이 때 고정된 테스트 셋을 이용한다면 여러개의 모델 성능을 비교하면서 모델 성능 향상에 가장 중요한 요소들이 무엇인지 쉽게 파악할 수가 있다.
- 모델의 문제점을 파악하기가 쉽다.
- A라는 모델을 이용해서 테스트 셋을 디코딩 했을 때와 B라는 모델을 이용했을 때 각각 80점과 90점이 나왔을 때, 왜 A라는 모델이 더 낮은 점수를 얻었는지 비교할 수가 있다. 예를들어 테스트 셋에 "나는 학교를 간다"라는 음성이 있었고 이를 A는 “나는 학교를 건다” 라고 디코딩 하였고, B는 "나는 학교를 간다"라고 디코딩 하였다고 가정하자. 이럴 경우 A 모델이 "학교를 간다"라는 단어의 연쇄를 제대로 학습하지 못하였다는 것을 확인하고 언어모델을 보강하기 위해 관련 데이터를 보충하여 모델을 재학습 시킬수가 있을 것이다.
- 다른 모델과의 성능을 객관적으로 비교할 수 있다.
- 디코딩을 돌리고 나면 mono/decode 폴더가 생성되며, 이 안에는 디코딩 관련 결과물이 쌓이게된다. 이 때, 살펴볼만한 결과물에 대해서 알아보도록 하자.
- scoring_kaldi/best_wer
- 현재 모델로 테스트 셋 디코딩을 진행했을 때 얻은 결과를 담고 있다. wer(word error rate)로 성능을 표시해주는데 이는 낮을 수록 좋은 성능이며 100-wer 할 경우 우리에게 익숙한 점수가 된다. best_wer에 적힌 값들을 좀 더 자세히 보도록 하자.
%WER 11.08 [ 120 / 1083, 57 ins, 3 del, 60 sub ] exp/mono/decode/wer_18_0.0
- WER 11.08은 바꿔 말하면 88.92%(100-11.08)이므로 모델의 성능이 약 89%의 정확도를 갖는다고 할 수 있다.
- 괄호안에 있는 120 / 1083은 테스트 셋에 존재하는 단어가 총 1083개이고 이 중 120개가 틀렸다는 것을 의미하며, 이 120개 단어의 오류는 57번의 insertion, 3번의 deletion과 60번의 substitution 으로 구성된다는 것을 나타낸다.
- insertion은 정답지엔 없는데 단어를 삽입한 오류다.
- 정답지: “나는 학교를 간다”
- 디코딩: “오늘 나는 학교를 간다”
- deletion은 정답지엔 있는데 단어를 생략한 오류다.
- 정답지: “나는 학교를 간다”
- 디코딩: " 나는 *** 간다"
- substitution은 정답지에 있는 단어를 다르게 표기한 오류다.
- 정답지: “나는 학교를 간다”
- 디코딩: “나는 집을 간다”
- insertion은 정답지엔 없는데 단어를 삽입한 오류다.
- 현재 모델로 테스트 셋 디코딩을 진행했을 때 얻은 결과를 담고 있다. wer(word error rate)로 성능을 표시해주는데 이는 낮을 수록 좋은 성능이며 100-wer 할 경우 우리에게 익숙한 점수가 된다. best_wer에 적힌 값들을 좀 더 자세히 보도록 하자.
- scoring_kaldi/wer_details/ops
- 디코딩이 진행된 단어들에 대해서 1) 올바르게 디코딩한 경우, 2) deletion이 발생한 경우, 3) insertion이 발생한 경우, 4) substitution이 발생한 경우에 대한 세부사항을 적은 결과물로, 이를 통해 전체적인 디코딩 추이를 확인해 볼 수 있다.
correct 소년은 소년은 13 correct 이 이 12 correct 한 한 12 ... deletion 등 *** 1 deletion 감고 *** 1 deletion 송아지가 *** 1 insertion *** 요행 48 insertion *** 실컷 2 insertion *** 주기도 2 ... substitution 질색이었다 요행 1 substitution 허리띠처럼 얼굴이라도 1 substitution 토요일이었다 탄 1
- 첫번째 열은 단어의 결과를 CSID(correct, substitution, insertion, deletion)로 보여준다.
- 두번째 열은 정답 단어를 보여준다.
- 세번째 열은 디코딩 단어를 보여준다.
- 네번째 열은 발생한 단어의 회수를 보여준다.
- 디코딩이 진행된 단어들에 대해서 1) 올바르게 디코딩한 경우, 2) deletion이 발생한 경우, 3) insertion이 발생한 경우, 4) substitution이 발생한 경우에 대한 세부사항을 적은 결과물로, 이를 통해 전체적인 디코딩 추이를 확인해 볼 수 있다.
- scoring_kaldi/wer_details/per_utt
- 전반적인 디코딩 결과를 세세하게 보여주는 자료로, 본 필자는 주로 이 자료를 통해 디코딩 성능을 검토한다.
fv03_t01_s01 ref *** 기차도 전기도 없었다 fv03_t01_s01 hyp 요행 기차도 전기도 없었다 fv03_t01_s01 op I C C C fv03_t01_s01 #csid 3 0 1 0 ...
- per_utt는 한 오디오 파일에 대해 총 4개의 라인을 이용하여 결과물을 보여주는데, 여기서 ref는 정답지, hyp는 디코딩 결과물, op는 CSID 정보, 그리고 #csid는 각 CSID가 발생한 회수 정보를 나타낸다.
- 위의 결과물을 가지고 설명하자면 정답지는 "기차도 전기도 없었다"이며, 이에 대한 디코딩 결과는 "요행 기차도 전기도 없었다"가 된다. 여기서 “요행” 이란 단어가 디코딩 결과물에 추가되었으므로 I가 발생한 부분이고 나머지는 정답과 디코딩이 같으므로 C로 처리되었다. 이를 계산하면, I는 1번 C는 3번 발생하였으므로 csid 순서에 따라 3 0 1 0이라는 결과가 나온다.
- 전반적인 디코딩 결과를 세세하게 보여주는 자료로, 본 필자는 주로 이 자료를 통해 디코딩 성능을 검토한다.
- scoring_kaldi/best_wer
- 훈련을 통해 모델을 최종적으로 생성하였다면 그에 대한 성능평가가 이루어져하는데, 이를 위해서는 실제로 발화하면서 디코딩 결과를 눈으로 볼 수도 있지만 테스트 데이터를 이용하여 객관적인 성능을 추려 볼 수도 있다. 그렇다면 테스트 데이터를 이용하여 객관적인 성능을 볼 때 얻는 장점은 어떤것이 있는지 알아보도록 하자.
Hyungwon Yang
댓글
댓글 쓰기