HTS Training

Sittipong Saychum
NECTEC
Published in
4 min readSep 21, 2021

บทความที่แล้วพูดถึง Tacotron ในการสร้างโมเดลสำหรับการสังเคราะห์เสียงพูด ใครยังไม่ได้อ่านเชิญ link ด้านล่างเลยครับ

มาบทความนี้เราจะ train ด้วย HTS กัน ซึ่งเป็นเทคโนโลยีที่เก่ากว่า แต่ถึงแม้จะเก่าแต่ก็เก๋านะ เพราะเจ้า HTS นี้สามารถนำไปใช้งานแบบ standalone ได้เกือบทุก platfrom เลยนะ ไม่ว่าจะเป็น embedded อย่างบน android บน SAPI ใน window หรือจะทำงานบนเครื่อง linux server ก็ได้ ถึงว่าว่าคุณภาพเสียงจะสู้ไม่ได้แต่ความเร็วในการประมวลผลรวดเร็วเลยทีเดียว ขั้นตอนการ train ดูจะยุ่งยากกว่าตัว tacotron มากเลยทีเดียว ผมจะไม่ลงรายละเอียดนะครับ เพราะความตั้งใจในการเขียนบทความนี้เพียงต้องการเขียนเป็น tr กันลืมให้ตัวเอง ใครอยากสอบถามเพิ่มเติมก็หลังไมค์มานะครับ

ก่อนอื่นเราต้องเตรียมเครื่อง server เราด้วยการลง festival-2.4 ,HTS-2.3 ,hts_engine_API-1.10 ,SPTK-3.11 และให้ Download Demonstrations HTS_2.3.2(English) ด้วย สามารถเข้าไป download ได้ที่ link ด้านล่างนี้เลยครับ

http://hts.sp.nitech.ac.jp/?Download

หลังจากนั้นเป็นขั้นตอนการเตรียมข้อมูลสำหรับการ train เริ่มต้นด้วยการ down sampling rate

import glob
import os
import tqdm
def ConvSampRate(inputPath,outputPath,samp):
files=glob.glob(inputPath+"/*")
for fileName in tqdm.tqdm(files, total=len(files)):
base_name = os.path.basename(fileName)
os.system('sox '+inputPath+'/'+base_name+' -r '+ str(samp)+' -c 1 -b 16 -e signed-integer -L '+outputPath+'/'+base_name)

เราต้อง convert ให้เป็น raw ไฟล์ด้วย เพราะเราต้องใช้ raw นี้แหละในการ train

def wav2raw(wavPath,rawPath):
files=glob.glob(wavPath+"/*")
for fileName in tqdm.tqdm(files, total=len(files)):
base_name = os.path.basename(fileName)
os.system('sox '+wavPath+'/'+base_name+' -b 16 -e signed-integer -c 1 -L '+rawPath+'/'+base_name.replace('wav','raw'))

ในที่นี้เราจะใช้ World vocoder แทนที่ STRAIGHT ดังนั้นเราจึงต้องทำงาน Extract feature ก่อน สามารถที่เราใช้ world เพราะ มันฟรีแถมคุณภาพเสียงสังเคราะห์ก็ออกมาดีพอๆกัน

ขั้นแรกเราต้องลง merlin ก่อน

!cd /opt/HTS_World
!apt-get update
!apt-get install git-all realpath autotools-dev automake
!git clone https://github.com/CSTR-Edinburgh/merlin.git
!cd merlin
!tools/compile_tools.sh
!pip install -r requirements.txt

ขั้นตอนต่อไปเป็นการ Extract feature โดยที่ Input เป็น folder ไฟล์ wav และ output เป็น feature ซึ่งจะมี 3 folder bap ,lf0 และ mgc

sampRate = 22050
nameDir = 'pung/'
wavPath = '/opt/speechData/wav/Tab/'+nameDir+downSampDir
merlinDir = 'opt/HTS_World/merlin'
worldFeat = '/opt/HTS_World/World_Workspace/features/'+nameDir+downSampDir
!mkdir $nameDir
!mkdir $worldFeat
!$merlinDir/misc/scripts/vocoder/world/extract_features_for_merlin.sh $merlinDir $wavPath $worldFeat $sampRate

ขั้นตอนต่อไปเป็นขั้นตอนเตรียมไฟล์ label เรามาเริ่มกันที่ Mono label กันก่อนโดยจะใช้ MontrealFaorcedAligner สามารถเข้าไปอ่านบทความเก่าของผมได้ที่ link ด้านล่างนี้นะครับ

หลังจากได้ mono label แล้ว ต่อไปเป็นการทำ full label แต่ก่อนที่จะทำ full label เราจะต้อง เตรียม phoneme เพื่อที่จะนำไป gen เนื่องจาก Monteal Forced Aligner จะ tag short pause(sp) ให้เราโดยอัตโนมัติ เราจึงจำเป็นต้อง Aligner phoneme กับ mono label ให้ตรงกันด้วย เวลานำไปทำ full label จะได้ออกมาตรงกันกับ mono

ขั้นตอนนี้ ผมจะใช้ tool จาก vaja ในการ gen ไฟล์ต่างๆ vaja จะเตรียมมาให้พร้อมแล้ว เราแค่เรียกไฟล์แล้วใส่เข้าไป โดยเราสามารถที่จะปรับแก้ไข full label ของเราได้จากไฟล์ fullLabel.def ในที่นี้ผมทำการ tiedTone เข้าไปกับตัว phone vowel และ final consonant ด้วย

fileLiveSyl = '/opt/Vaja_AEC/VajaTTS/vaja-data/hts/liveSyl.def'
fileDef = '/opt/HTS_World/BuildQst/input/bi_tiedTone/fullLabel.def'
fileRep = '/opt/Vaja_AEC/VajaTTS/vaja-data/hts/pung/fileRep_thai.txt'
fileTran = '/opt/Corpus/Tab/pung/tran.thai.txt'
tiedTone = 'V-T,Cf-T'
outputThai_FullLabel = '/opt/speechData/labels/full/pung_thai.tiedTone/'
!mkdir $outputFullLabel
!/opt/Vaja_AEC/Makefile/fullLabel -l $fileLiveSyl -r $fileRep -d $fileDef -t $fileTran -tt $tiedTone -o $outputThai_FullLabel

จากคำสั่งด้วนบน fileTran หรือ file Transcription ซึ่งเป็น input file จะมีหน้าตาแบบรูปด้านล่าง

จากรูปด้านบน โครงสร้างประกอบด้วย fileName ,addSil และ phoneme โดยที่ addSil คือสถานะ ในการเพิ่ม sill เข้าไปใน full label หัว และท้าย โดยค่าสถานะจะเป็นตัวเลขมีความหมายดังนี้

0 ไม่เพิ่ม sil ทั้งหัวและท้าย
1 เพิ่ม sil เฉพาะหัว
2 เพิ่ม sil ทั้งหัวและท้าย
3 เพิ่ม sil เฉพาะท้าย

หลังจาก ใช้คำสั่ง gen full label แล้วโปรแกรมจะหาค่าต่างๆมาให้เราด้วยเช่น จำนวนที่มากที่สุดของพยางค์ในคำ(maxSyl_W) หรือ จำนวนที่มากที่สุดของคำในวลี(maxW_Phr) ดังเช่นตัวอย่างด้านล่างนี้ สำหรับนำไปเป็นข้อมูลในการ gen question file ต่อไป

#maxP_Syl:7
#maxP_W:14
#maxSyl_W:7
#maxP_Phr:55
#maxW_Phr:13
#maxSyl_Phr:20
#maxP_Utt:76
#maxSyl_Utt:34
#maxWord_Utt:21
#maxPhr_Utt:15

ข้อมูลตัวเลขที่ได้จาก การ gen full label นำมาแก้ใขในไฟล์ fullLabel.def แบบรูปด้านล่างโดยกำหนดค่าให้มากกว่า

เสร็จแล้วใช้คำสั่งด้านล่างนี้ในการ gen โดยจะใช้ tool BuildQst.jar ซึ่งเป็น ภาษา java ใส่ input เป็น fullLabel.def

BuildQstDir =  '/opt/HTS_World/BuildQst'
DefDir = BuildQstDir+'/input/bi_tiedTone/'
DefFile = 'fullLabel.def'
!java -jar $BuildQstDir/BuildQst.jar -d $DefFile -p $DefDir -o $questDir/questions_qst001.hed
!cp -rf $DefDir$DefFile $questDir
!cp -rf $questDir/questions_qst001.hed $questDir/questions_utt_qst001.hed

ขั้นตอนต่อไปเป็นการสุ่มไฟล์บางไฟล์จาก full label ที่เรา gen ได้มาไว้ใน folder gen สำหรับเป็น test ไฟล์

import glob
import os
from shutil import copyfile
from sklearn.model_selection import train_test_split
DIRFULL = '/opt/speechData/labels/full/pung_bi.tiedTone'
DIRGEN = '/opt/speechData/labels/gen/pung_bi.tiedTone'
!mkdir $DIRGEN
dataSet=glob.glob(DIRFULL+'/*')
trainSet, testSet = train_test_split(dataSet, test_size=0.005, random_state=42)
print("Count trainSet: ", len(trainSet))
print("Count testSet: ", len(testSet))
for fileName in testSet:
copyfile(fileName, fileName.replace('/full/','/gen/'))

ขั้นตอนการ train

หลังจากที่เรา Download Demonstrations HTS_2.3.2(English) มาแล้วให้เราเข้าไปแก้ไขดังนี้ครับ

% vi Makefile
- ใส่ path เต็ม Training.pl และ Config.pm
- ลบ 2>&1 & ออกจาก /usr/bin/perl scripts/Training.pl scripts/Config.pm > log 2>&1 &

% vi data/Makefile
- ลบ lab ,question ออก
#labels: lab mlf list scp question
labels:mlf list scp

% vi scripts/Config.pm
#@slnt = ('pau','h#','brth'); # silent and pause phoneme
@slnt = ('sil','sp1','sp2');
% vi script/Training.pl
# HHEd (converting mmfs to the HTS voice format)
# if ( $CONVM && !$usestraight ) {
if ( $CONVM ) {

หลังจากนั้นสร้าง link ทุกอย่างที่เราเตรียมไว้ก่อนหน้านี้

DIRRAW = '/opt/speechData/raw/Tab/pung/bi_22k_NORM'
DIRFULL = '/opt/speechData/labels/full/pung_bi.tiedTone'
DIRMONO = '/opt/speechData/labels/mono/pung_bi.tiedTone'
DIRGEN = '/opt/speechData/labels/gen/pung_bi.tiedTone'
DIRQST = '/opt/speechData/questions/pung_bi.tiedTone'
!ln -s $DIRRAW HTS_2.3.2/data/raw
!ln -s $DIRQST HTS_2.3.2/data/questions
!ln -s $DIRFULL HTS_2.3.2/data/labels/full
!ln -s $DIRGEN HTS_2.3.2/data/labels/gen
!ln -s $DIRMONO HTS_2.3.2/data/labels/mono
DIRFEST = '/opt/HTS_World/World_Workspace/features/pung/bi_22k_NORM'
!mkdir $DIRFEST/cmp
!ln -s $DIRFEST/mgc HTS_2.3.2/data/mgc
!ln -s $DIRFEST/lf0 HTS_2.3.2/data/lf0
!ln -s $DIRFEST/cmp HTS_2.3.2/data/cmp
!ln -s $DIRFEST/bap HTS_2.3.2/data/bap

ขั้นตอนต่อมาเป็นการ config ให้กำหนดค่าตาม sampling rate จากด้านล่าง

FRAMELEN: Frame length in point (551 = 22050 * 0.025) \
FRAMELEN: Frame length in point (1102= 44100* 0.025) \
FRAMESHIFT: Frame shift in point (110 = 22050 * 0.005) \
FRAMESHIFT: Frame shift in point (220= 44100* 0.005)
MGCORDER=5916000 FREQWARP=0.58 \
22050 FREQWARP=0.65 \
44100 FREQWARP=0.76 \
48000 FREQWARP=0.77
16000 FFTLEN=1024 \
22050 FFTLEN=1024 \
44100 FFTLEN=2048 \
48000 FFTLEN=2048
22050 BAPORDER=1 \
44100 BAPORDER=4

ค่า BAPORDER สามารหาได้ดังนี้

BAPORDER = (nLine_bap/nLine_lf0) - 1 
(Number line of bap divide by Number line of lf0) minus 1
x2x +fa lf0/1.lf0 | wc -l
x2x +fa bap/1.bap | wc -l

ใช้คำสั่ง configure และ make เพื่อ train

!cd HTS_2.3.2
!./configure --with-fest-search-path=/opt/tools/festival-2.4/festival/examples \
--with-sptk-search-path=/opt/tools/SPTK-3.11/bin \
--with-hts-search-path=/opt/tools/HTS-2.3/bin \
--with-hts-engine-search-path=/opt/tools/hts_engine_API-1.10/bin \
FULLCONTEXT_FORMAT=HTS_TTS_THAI \
DATASET=tab \
SPEAKER=pung \
QNAME=qst001 \
USEUTT=0 \
USESTRAIGHT=1 \
FRAMELEN=551 \
FRAMESHIFT=110 \
SAMPFREQ=22050 \
FREQWARP=0.65 \
MGCORDER=59 \
BAPORDER=1 \
FFTLEN=1024
!make

หลังจากใช้เวลา train ประมาณ 4–5 วันแล้วแต่ขนาดฐานข้อมูลและ performance ของเครื่อง server จะได้ไฟล์ model อยู่ใน folder voices/ver1 ซึ่งไฟล์ model จะมีนามสกุล .htsvoice เราสามารถทดสอบสังเคราะห์เสียงได้ด้วยคำสั่ง

./hts_engine -m voices/ver1/tab_pung.htsvoice -or output/test.raw -ow output/test.wav -ot output/test.trace input/fullLabel.lab

เป็นไงบ้างครับ เหนื่อยกันเลยทีเดียว จะเห็นว่า HTS จะมีขั้นตอนยุ่งยากกว่า Tacotron เยอะเลย และคุณภาพเสียงก็สู้ไม่ได้ด้วย คุณภาพเสียงที่สังเคราะห์ได้จะดีมากน้อยขึ้นอยู่กับ ฐานข้อมูลเสียงด้วยนะครับ ทั้งจำนวนและคุณภาพเสียงที่อัดมา สำหรับ HTS คุณภาพเสียงจะดีขึ้นอย่างมาก ถ้าเราทำ human label ด้วย แต่ด้วยอุปสรรคที่ต้องให้เงินทุนและเวลาเยอะ รวมถึงจำเป็นต้องใช้ผู้เชี่ยวชาญโดยเฉพาะ ฐานข้อมูลเสียงที่ทำ human label จึงเกิดขึ้นได้ยาก

--

--