KFalignerλ HTK(Hidden Markov Model Toolkit) κΈ°λ°μ νκ΅μ΄ κ°μ μ λ ¬(Forced Alignment) μμ€ν μ λλ€. μμ± νμΌκ³Ό μ μ¬ ν μ€νΈλ₯Ό μ λ ₯λ°μ μμ(phoneme) λ° λ¨μ΄(word) λ¨μμ μ νν μκ° μ λ ¬ μ 보λ₯Ό Praat TextGrid νμμΌλ‘ μΆλ ₯ν©λλ€.
- μ£Όμ κΈ°λ₯
- μ¬μ©λ²
- μμ€ν μꡬμ¬ν
- μ€μΉ
- νλ‘μ νΈ κ΅¬μ‘°
- μΉ μ ν리μΌμ΄μ
- κΈ°μ μΈλΆμ¬ν
- λ¬Έμ ν΄κ²°
- λΌμ΄μ μ€
- μλ μμ μ λ ¬: νκ΅μ΄ μμ±κ³Ό ν μ€νΈλ₯Ό μμ λ¨μλ‘ μ ννκ² μ λ ¬
- λ€μ€ μνλ μ΄νΈ μ§μ: 8kHz, 11.025kHz, 16kHz μμ± νμΌ μ²λ¦¬
- μλ 리μνλ§: Praat μ€ν¬λ¦½νΈλ₯Ό μ΄μ©ν 16kHz μλ λ³ν
- TextGrid μΆλ ₯: Praatμμ λ°λ‘ μ¬μ© κ°λ₯ν νμ€ νμ
- μ¬μ μλ νμ₯: λ―Έλ±λ‘ λ¨μ΄μ λν λ°μ μ¬μ μλ μμ±
- μΉ μΈν°νμ΄μ€: Flask κΈ°λ° μΉ μ ν리μΌμ΄μ μ 곡
- Short Pause (sp) μ²λ¦¬: λ¨μ΄ μ¬μ΄μ ν΄μ§λ₯Ό λ³λ ꡬκ°μΌλ‘ λΆλ¦¬
- λ©ν° Tier TextGrid: phone/word/syllable/utterance tier μμ± λ° νκΈ λΌλ²¨ μ§μ
python3 align.py <audio.wav> <transcript.lab> <output.TextGrid>μμ:
python3 align.py test/mv01_t01_s01.wav test/mv01_t01_s01.lab test/mv01_t01_s01.TextGrid- μνλ μ΄νΈ: 16000 Hz (κΆμ₯)
- 8000 Hz, 11025 Hzλ μ§μ (μλ λͺ¨λΈ μ ν)
- λ€λ₯Έ μνλ μ΄νΈλ μλμΌλ‘ 16kHzλ‘ λ¦¬μνλ§
- μ±λ: λͺ¨λ Έ (Mono)
- λΉνΈ κΉμ΄: 16-bit PCM
- μΈμ½λ©: UTF-8
- νμ: νκ΅μ΄ μ² μλ² ν μ€νΈ (ν μ€)
- μμ:
κΈ°μ°¨λ μ μ΄λ μμλ€
TextGrid νμΌ (Praat νΈν):
- phone tier: μμ λ¨μ μ λ ¬ (
gg,i,c,a,d,o, ...) - syllable tier: νκ΅μ΄ μμ κ·μΉ(CV(C)) κΈ°λ°μ μλ μμ κ²½κ³
- word tier: λ¨μ΄ λ¨μ μ λ ¬ λ° (νκΈ μ λ ₯ μ) μλ¬Έ λΌλ²¨ νμ
- utterance tier:
silμ¬μ΄ ꡬκ°μ λ¬Έμ₯ λ¨μλ‘ ν©μΉλ©° νκΈ λΌλ²¨ νμ - κ²½κ³ νμ:
sil(silence),sp(short pause)
- Python: 3.6 μ΄μ
- HTK: 3.4.1 (Hidden Markov Model Toolkit)
- Praat: μμ± λΆμ λ° λ¦¬μνλ§μ© (μ νμ¬ν)
- OS: Linux (Ubuntu 20.04+ κΆμ₯)
flask>=2.0.0
gunicorn>=20.0.0
numpygit clone https://github.com/exphon/kfaligner.git
cd kfalignerHTKλ λΌμ΄μ μ€ μ μ½μΌλ‘ μ§μ λ€μ΄λ‘λκ° νμν©λλ€:
- HTK 곡μ μ¬μ΄νΈμμ HTK-3.4.1 λ€μ΄λ‘λ
- μμΆ ν΄μ λ° μ»΄νμΌ:
tar -xvf HTK-3.4.1.tar.gz
cd htk
./configure --prefix=/usr/local
make all
sudo make install# κ°μνκ²½ μμ± (κΆμ₯)
python3 -m venv venv
source venv/bin/activate
# ν¨ν€μ§ μ€μΉ
pip install -r requirements.txt # requirements.txtκ° μλ κ²½μ°
# λλ
pip install flask gunicorn numpypython3 align.py <audio.wav> <transcript.lab> <output.TextGrid>μμ:
python3 align.py test/mv01_t01_s01.wav test/mv01_t01_s01.lab output.TextGrid- μνλ μ΄νΈ: 16000 Hz (κΆμ₯)
- 8000 Hz, 11025 Hzλ μ§μ (μλ λͺ¨λΈ μ ν)
- λ€λ₯Έ μνλ μ΄νΈλ μλμΌλ‘ 16kHzλ‘ λ¦¬μνλ§
- μ±λ: λͺ¨λ Έ (Mono)
- λΉνΈ κΉμ΄: 16-bit PCM
- μΈμ½λ©: UTF-8
- νμ: νκ΅μ΄ μ² μλ² ν μ€νΈ (ν μ€)
- μμ:
κΈ°μ°¨λ μ μ΄λ μμλ€
16kHzκ° μλ μμ± νμΌμ Praat μ€ν¬λ¦½νΈλ‘ λ³ν:
# Praat μ€ν¬λ¦½νΈ μ€ν
praat --run resampleTo16000.praat input.wav output_16k.wav# λ¬Έμ₯ 리μ€νΈ νμΌ μ€λΉ (flist.txt)
echo "μλ‘μ΄ λ¬Έμ₯λ€" > flist.txt
# μ¬μ μμ± λ° μΆκ°
./make_dict.sh flist.txtμ΄ μ€ν¬λ¦½νΈλ bin/ λλ ν 리μ λꡬλ₯Ό μ¬μ©νμ¬:
- νκΈμ μ λμ½λλ‘ λ³ν
- μμ μνμ€ μμ±
- κΈ°μ‘΄ μ¬μ μ λ³ν©
kfaligner/
βββ align.py # λ©μΈ μ λ ¬ μ€ν¬λ¦½νΈ
βββ __init__.py # Python ν¨ν€μ§ μ΄κΈ°ν
βββ restart.sh # μλ² κ΄λ¦¬ μ€ν¬λ¦½νΈ
βββ make_dict.sh # μ¬μ μμ± μ€ν¬λ¦½νΈ
βββ resampleTo16000.praat # 리μνλ§ Praat μ€ν¬λ¦½νΈ
βββ check_alignment.praat # μ λ ¬ κ²°κ³Ό κ²μ¦ μ€ν¬λ¦½νΈ
β
βββ bin/ # μ νΈλ¦¬ν° μ€ν¬λ¦½νΈ
β βββ make_kdict.py # νκ΅μ΄ μ¬μ μμ±κΈ°
β βββ add_dict.py # μ¬μ μΆκ° λꡬ
β βββ kdictmap.py # μμ λ§€ν
β βββ han2uniconversion.py # νκΈ-μ λμ½λ λ³ν
β βββ convert_sentences_unicode.py
β βββ dict # λ°μ μ¬μ
β βββ kdict0.txt # κΈ°λ³Έ νκ΅μ΄ μ¬μ
β βββ kdict1.txt # νμ₯ μ¬μ
β
βββ model/ # HTK μν₯ λͺ¨λΈ
β βββ dict # λ©μΈ λ°μ μ¬μ (5,589+ λ¨μ΄)
β βββ monophones # μμ λͺ©λ‘
β βββ 16000/ # 16kHz λͺ¨λΈ
β βββ hmmdefs # HMM μ μ
β βββ macros # λ§€ν¬λ‘ μ μ
β βββ config # HTK μ€μ
β
βββ test/ # ν
μ€νΈ νμΌ
β βββ *.wav # μν μμ± νμΌ
β βββ *.lab # μν μ μ¬ νμΌ
β βββ *.TextGrid # μΆλ ₯ μμ
β
βββ webapp/ # Flask μΉ μ ν리μΌμ΄μ
β βββ app.py # λ©μΈ μ ν리μΌμ΄μ
β βββ forms.py # μΉ νΌ μ μ
β βββ models.py # λ°μ΄ν°λ² μ΄μ€ λͺ¨λΈ
β βββ templates/ # HTML ν
νλ¦Ώ
β β βββ index.html
β β βββ login.html
β β βββ signup.html
β β βββ results.html
β βββ data/ # μ
λ‘λ λ° μμ
λ°μ΄ν°
β βββ users.db # μ¬μ©μ λ°μ΄ν°λ² μ΄μ€
β βββ uploads/ # μ
λ‘λλ νμΌ
β βββ jobs/ # μ²λ¦¬ μμ
λλ ν 리
β
βββ tmp/ # μμ μ²λ¦¬ νμΌ
β βββ tmp.mlf # μ
λ ₯ MLF
β βββ aligned.mlf # HVite μΆλ ₯
β βββ *.mfc # MFCC νΉμ§ νμΌ
β
βββ examples/ # μμ νμΌ
βββ code.scp # νμΌ λ¦¬μ€νΈ μμ
# κ°λ° μλ² (5010 ν¬νΈ)
cd webapp
python3 app.py
# νλ‘λμ
μλ² (Gunicorn + Systemd)
sudo systemctl start kfaligner
sudo systemctl enable kfaligner
# μλ² κ΄λ¦¬ μ€ν¬λ¦½νΈ μ¬μ©
./restart.sh start|stop|restart|reload|statushttp://localhost:5010
- νμΌ μ λ‘λ: WAV + LAB νμΌ λμ μ λ‘λ
- λ°°μΉ μ²λ¦¬: μ¬λ¬ νμΌ λμ μ λ ¬
- κ²°κ³Ό λ€μ΄λ‘λ: TextGrid νμΌ λ€μ΄λ‘λ
- μ¬μ©μ κ΄λ¦¬: νμκ°μ /λ‘κ·ΈμΈ μμ€ν
- μμ νμ€ν 리: κ³Όκ±° μ λ ¬ κ²°κ³Ό μ‘°ν
# webapp/app.py λλ systemd μλΉμ€μμ
export MAX_CONTENT_LENGTH_MB=64 # μ΅λ μ
λ‘λ ν¬κΈ°
export MAX_FILES_PER_REQUEST=100 # μ΅λ νμΌ μ
export ENABLE_CLAMAV_SCAN=1 # λ°μ΄λ¬μ€ μ€μΊ νμ±νKFalignerλ HViteλ₯Ό μ¬μ©ν Viterbi μ λ ¬μ μνν©λλ€:
HVite -T 1 -a -m \
-I tmp/tmp.mlf \
-H model/16000/macros \
-H model/16000/hmmdefs \
-S tmp/test.scp \
model/dict \
model/monophones \
> tmp/aligned.mlfμ£Όμ μ΅μ :
-T 1: νΈλ μ΄μ€ λ 벨 (λλ²κΉ )-a: μμ κ²½κ³ μΆλ ₯-m: λͺ¨λΈ μ¬μ©-I: μ λ ₯ MLF (Master Label File)-H: HMM μ μ νμΌ
readAlignedMLF()μμ λ¨μ΄ λspλ₯Ό λΆλ¦¬ν ν,writeTextGrid()μμ phone, syllable, word, utterance tierλ₯Ό μμ±ν©λλ€._build_syllable_intervals()λ λ‘λ§μ μμμ΄μ κΈ°λ°μΌλ‘ νκ΅μ΄ μμ κ²½κ³λ₯Ό μΆμ ν©λλ€._build_utterance_intervals()λsilμ¬μ΄ ꡬκ°μ νλμ λ°νλ‘ λ¬Άκ³ , νκΈ μ μ¬μμ λ³νλ μλ¬Έ λ¬Έμμ΄μ λΌλ²¨λ‘ μ¬μ©ν©λλ€.- νκΈ μ
λ ₯ μ
_build_display_map()μ΄ λ‘λ§μ/νκΈ λ§€νμ λ§λ€μ΄ wordΒ·utterance tierμμ νκΈ λΌλ²¨μ μΆλ ₯ν©λλ€.
λ¬Έμ : HTKμ spλ tee-model (1-state)λ‘ silμ μ€κ° μνλ₯Ό 곡μ ν©λλ€.
ν΄κ²° λ°©λ² (2025-10-09 μ λ°μ΄νΈ):
- μ¬μ : λͺ¨λ λ¨μ΄κ°
spλ‘ λλ¨ (5,589 λ¨μ΄) - MLF:
spμ½μ μμ,silλ§ λ¬Έμ₯ μ λμ - νμ²λ¦¬:
readAlignedMLF()μμ λ¨μ΄ λspλ₯Ό λ³λ νλͺ©μΌλ‘ λΆλ¦¬
# align.py Line 196-205
for wrd in ret:
if len(wrd) > 1 and wrd[-1][0] == 'sp':
sp_entry = wrd.pop() # spλ₯Ό λ¨μ΄μμ λΆλ¦¬
separated_ret.append(wrd) # λ¨μ΄λ§
separated_ret.append(['sp', sp_entry]) # sp λ³λκ²°κ³Ό: λ¨μ΄ μ¬μ΄μ pauseκ° λ³λ ꡬκ°μΌλ‘ TextGridμ νμλ©λλ€.
νκ΅μ΄ λ‘λ§μ νκΈ° κΈ°λ° μμ:
- μμ:
gg,dd,bb,jj,ss,k,t,p,c,h,g,d,b,j,m,n,ng,r,s - λͺ¨μ:
a,eo,o,u,eu,i,ae,e,wa,weo,wo,we,oe,wi,ui,ya,yeo,yo,yu,yae,ye - νΉμ:
sil(silence),sp(short pause)
| μνλ μ΄νΈ | λͺ¨λΈ λλ ν 리 | μλ λ³ν |
|---|---|---|
| 8000 Hz | model/8000/ | β |
| 11025 Hz | model/11025/ | β |
| 16000 Hz | model/16000/ | - |
| κΈ°ν | - | β 16000 Hz |
μ¦μ: HViteκ° ERROR [+8522] LatFromPaths: Align have dur<=0 μΆλ ₯
μμΈ: spκ° MLFμ μ¬μ μμͺ½μ μμ΄ tee-model μΆ©λ
ν΄κ²°:
between_token = Noneμ€μ (MLFμ sp μ½μ μ ν¨)- μ¬μ μλ§ sp μ μ§
μ¦μ: λ¬Έμ₯ μμ/λμ sil sil λνλ¨
μμΈ: -b sil μ΅μ
+ MLFμ sil = μ€λ³΅
ν΄κ²°: HViteμμ -b μ΅μ
μ κ±°
μ¦μ: νΉμ λ¨μ΄μμ μ λ ¬ μ€ν¨
ν΄κ²°:
# μλ μ¬μ μμ±
./make_dict.sh flist.txt
# λλ μλ μΆκ°
python3 bin/make_kdict.pyμ¦μ: μ λ ¬ νμ§ μ ν λλ μ€ν¨
ν΄κ²°:
# PraatμΌλ‘ 리μνλ§
praat --run resampleTo16000.praat input.wav output.wav
# λλ sox μ¬μ©
sox input.wav -r 16000 output.wavμ¦μ: "Maximum file size exceeded"
ν΄κ²°:
# app.py λλ νκ²½λ³μ μμ
export MAX_CONTENT_LENGTH_MB=128
# Nginx μ€μ (νλ‘μ μ¬μ© μ)
client_max_body_size 128M;μ΄ νλ‘μ νΈλ λ€μ κΈ°μ μ κΈ°λ°μΌλ‘ ν©λλ€:
- HTK (Hidden Markov Model Toolkit): Cambridge University Engineering Department
- Praat: Paul Boersma and David Weenink
- Flask: Pallets Projects
Tae-Jin Yoon
Sungshin Women's University
Β© January 2016-2025
νλ‘μ νΈ κ΄λ ¨ λ¬Έμμ¬νμ΄λ λ²κ·Έ 리ν¬νΈλ GitHub Issuesλ₯Ό μ΄μ©ν΄μ£ΌμΈμ:
https://github.com/exphon/kfaligner/issues
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€ νμ λ°°ν¬λ©λλ€. μμΈν λ΄μ©μ LICENSE νμΌμ μ°Έμ‘°νμΈμ.
β μ΄ νλ‘μ νΈκ° μ μ©νλ€λ©΄ Starλ₯Ό λλ¬μ£ΌμΈμ!
