음성인식모델로 음성합성 데이터 만들기 (kaldi 음성 인식 모델 환경 구현)
예전에 multi-speaker-tacotron 을 가지고 음성합성 개발 환경을 구현하는 방법을 소개한적이 있었습니다.
처음에 https://github.com/carpedm20/multi-speaker-tacotron-tensorflow
에 소개된 가이드대로 구글 STT 를 통해 음성에 대한 text 를 자동 인식 시켰는데, 그 결과 정확도가
워낙 좋지 않아 처음에는 재대로 들리는 음성 자체를 얻을 수가 없었습니다.
그래서 그다마 정확도가 높았던 특정 길이 문장 데이터만 선별해서 학습을 하고, 이를 통한 결과로 학습에 사용했던 유사 길이의 문장들을 다시 음성으로 합성하여 학습데이터를 보충하면서 나름 효과를 보긴 했지만,
결국은 최종 원했던 성능의 TTS를 만들기 위해서는 정확하고, 가급적 많은 학습데이터를 확보하는 것이
중요했습니다.
애초 multi-speaker-tacotron-tensorflow deploy 를 시작했던 이유가 개발자께서 세메나에 언급했던
“유인나 목소리로 책 읽어주기” 를 구현해보자는 거였고, 이를 위해 기존 유튜브등에서 유인나 오디오북
음성 구하고, 책 구매하여 열심히 매주말마다 노가다로 audio/text 매핑 작업을 3000~4000 개 정도
학습데이터를 만들어서 이전 글에 소개한 샘플 정도의 결과를 얻을 수 있었습니다.
하지만, 다양하고 복잡한 어휘로 구성된 문장들을 넣었을때 ( 시, 독립선언문, 성경 등등.. ) 많은 한계가
존재하였습니다. 역시 결론은 정확하고 아주 많은 학습데이터가 답이였습니다.
1~2 달 동안 “도 닦는 마음”으로 주말마다 수작업을 하다가 음성 인식 모델로 정확한 TEXT를 뽑아보는데
도전을 하게 되었습니다. 최초 구글 STT 에 워낙 실망을 한터라 이 작업은 크게 생각도 안했었는데
우연한 기회에 음성인식모델인 Kaldi 를 deploy 하고, 음성합성에 사용했던 손석희 데이터를 가지고
음성인식 모델 학습을 시키니 해당 화자의 목소리는 거의 95% 이상의 정확도를 나타내었습니다.
(똑같은 데이터를 구글 STT 에 돌렸을때 70~80% 수준이였는데..)
음성합성과 음성인식 모두 학습데이터를 생성하기 위해 음성과 Text 데이터가 필요한데
일반적으로 범용적인 음성인식기를 만들기 여러명의 화자의 데이터가 필요하지만, 특정인의 음성 합성을 위한 TEXT 데이터를 얻기 위해서는 해당 화자의 음성/텍스트 데이터가 (1000개)정도만 있어도 이것으로 음성인식
학습을 시킨 결과로 훌륭한 STT 결과를 얻을 수 있었습니다.
최종적으로 kaldi 라는 음성인식 모델에 음성합성 학습용으로 만들었던 4000개 정도의 유인나 오디오북 데이터로 음성인식 학습을 시킨 후,
인터넷에서 확보 가능한 모든 유인나 오디오북 음성의 TEXT 를 자동으로 생성하여, 총 30000개 이상의 학습데이터를 만들 수 있었습니다.
아래는 학습 결과로 만든 음성 합성기에 성경책 창세기 부분을 통째로 입력하여 생성한 결과의 일부 입니다.
[유인나 오디오북 음성 합성 결과-성경 창세기 전반부]
특정 화자의 음성, 텍스트 데이터를 일부(N백개~천개정도)가지고 있을 경우, 범용 음성인식기는 아니지만,
해당 화자의 음성 인식률은 충분히 확보 가능하다는 것을 확인하였고, 이를 통해 더 많은 학습데이터 확보가 가능 하다는 것을 경험하였습니다.
아래는 유인나나 손석희 목소리 인식에 사용했던 한국어 음성인식 모델인 kaldi (kaldi-zeroth project)를
deploy 하면서 kaldi 설치 및 학습 과정을 정리했던 내용입니다. 적은 노가다로 많은 음성합성 데이터를 얻고자 하시는 여러분들께서 참고하시면 도움이 되실듯 합니다.
=====================================================================================
KALDI 음성 인식 개발 환경 구현하기
작성자 : 서진우 (alang@clunix.com)
운영체제 버전 : CentOS 7.x (g++ 버전은 4.8.x)
Python 버전 : python 3.6.1
// 운영체제 레벨에서 필요한 라이브러리나 유틸 패키지를 설치 한다.
# yum install atlas atlas-devel flac sox unzip parallel curl
// 사용하고자 하는 python 환경 설정 적용하시고, 필요한 python package module 을 설치합니다.
# source /APP/DeepLn/profile.d/python36.sh
# python -m pip install JPype1-py3
;; python 2.7 일 경우 pip install JPype1
python -m pip install konlpy
python -m pip install morfessor
// kaldi 소스를 다운 받습니다.
# mkdir -p /APP/DeepLn
# cd /APP/DeepLn
# git clone https://github.com/kaldi-asr/kaldi.git
# mv kaldi kaldi-5.3
# cd kaldi-5.3/tools
# ./extras/check_dependencies.sh
# ./extras/check_dependencies.sh: all OK.
// kaldi 의 기본 python 버전은 2.7 임. 기본 python 버전을 3.x 변경하고 싶은 경우
# touch /APP/DeepLn/kaldi-5.3/tools/python/.use_default_python
# make -j 4
kaldi 에 필요한 tools 을 자동으로 다운 받고 compile 한다. 다만,
srilm 의 경우 라이선스 문제가 있어 직접 다운로드를 받아 설치해야 한다.
http://www.speech.sri.com/projects/srilm/download.html
# cp srilm-1.7.2.tar.gz ~kaldi/tools
# cd ~kaldi/tools
# mv srilm-1.7.2.tar.gz srilm.tgz
# ./install_srilm.sh
# git clone https://github.com/irstlm-team/irstlm.git
# tar czvf irstlm.tgz irstlm/
# rm -rf irstlm/
# extras/install_irstlm.sh
환경 설정 profile 생성
# vi env.sh
———————————————–
export PATH=/APP/DeepLn/kaldi-5.3/tools/python:${PATH}
export IRSTLM=/APP/DeepLn/kaldi-5.3/tools/irstlm
export PATH=${PATH}:${IRSTLM}/bin
export LIBLBFGS=/APP/DeepLn/kaldi-5.3/tools/liblbfgs-1.10
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}:${LIBLBFGS}/lib/.libs
export SRILM=/APP/DeepLn/kaldi-5.3/tools/srilm
export PATH=${PATH}:${SRILM}/bin:${SRILM}/bin/i686-m64
##추가
export PATH=${PATH}:/APP/DeepLn/kaldi-5.3/tools/openfst-1.6.5/bin
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}:/APP/DeepLn/kaldi-5.3/tools/openfst-1.6.5/lib
————————————————-
cp env.sh /APP/DeepLn/profile.d/kaldi_env.sh
source env.sh
# cd ../src
// 사용할 cuda 버전과 맞아야 함.
./configure –shared –cudatk-dir=/usr/local/cuda-<version>
vi kaldi.mk
//debug level을 변화
-g O0 -DKALDI_PARANOID”를 enable 하면 가장 디버깅하기 쉬운 바이너리가 생성
-O0를 -O1로 바꾸면 디버깅이 가능하면서도 빠른 속도의 바이너리가 생성
“-g -O0 -DKALDI_PARANOID” 옵션은 디버깅은 되지 않지만 더 속도가 빠름
“-g -O0 -DKALDI_PARANOID”를 “-O2 -DNDEBUG”나 “-O3 -DNDEBUG”로
//정밀도 변경
결과의 정밀성을 위해 double precision으로 바꾸고 싶으면, -DKALDI_DOUBLEPRECISION=0 옵션을 1
//경고 감춤
OpenFst 코드에서 signed-unsigned 비교 경고를 내는데, CXXFLAGS에 -Wno-sign-compare를 추가
make clean
make depend -j 2
make -j 2
// valgrind를 통해 테스트를 실행하여 메모리 누수를 확인
make valgrind
// cuda memory 누수 확인
make cudavalgrind
// yesno 테스트 (기본 테스트)
kaldi 측에서 튜토리얼 용도로 ‘yes’와 ‘no’만 구분하는 음성인식 예제를 제공한다.
DB도 따로 준비할 필요 없이 인식 실험 스크립트를 실행하면 DB 다운로드를 자동으로
진행하여 언어모델을 생성히고, 음향모델 생성, 인식 실험까지 진행한다.
cd ~kaldi/egs
cd yesno
tree
———-
.
|– README.txt
|– s5
| |– conf
| | |– mfcc.conf
| | `– topo_orig.proto
| |– input
| | |– lexicon.txt
| | |– lexicon_nosil.txt
| | |– phones.txt
| | `– task.arpabo
| |– local
| | |– create_yesno_txt.pl
| | |– create_yesno_wav_scp.pl
| | |– create_yesno_waves_test_train.pl
| | |– prepare_data.sh
| | |– prepare_dict.sh
| | |– prepare_lm.sh
| | `– score.sh -> ../steps/score_kaldi.sh
| |– path.sh
| |– run.sh
| |– steps -> ../../wsj/s5/steps
| `– utils -> ../../wsj/s5/utils
`– tree_1.txt
6 directories, 17 files
—-
cd s5
./run.sh
.
.
local/score.sh: scoring with word insertion penalty=0.0,0.5,1.0
%WER 0.00 [ 0 / 232, 0 ins, 0 del, 0 sub ] exp/mono0a/decode_test_yesno/wer_10_0.0
// 홈디렉토리에서 작업하기
kaldi 의 egs에 있는 기본 example 을 이용할 경우 예제 별 실행 scripts 가
대부분 kaldi 설치 경로를 기존으로 상대 경로로 지정되어 있다.
만일 root 권한이 없이 관리자가 설치한 kaldi 를 이용해야 하는 경우 자신의 홈디렉토리에 작업 모델을
놓고, 몇가지 환경을 맞춰야 한다.
mkdir -p $HOME/kaldi_example/egs
cd kaldi_example
ln -sf /APP/DeepLn/kaldi/tools .
ln -sf /APP/DeepLn/kaldi/src .
cp -a /APP/DeepLn/kaldi/egs/yesno egs
cd egs/yesno/s5
ln -sf /APP/DeepLn/kaldi/egs/wsj/s5/steps steps
ln -sf /APP/DeepLn/kaldi/egs/wsj/s5/utils utils
sh run.sh
// 다른 예제 실습 – small_krs
자세한 설명은..
https://hyungwonsnotebook.blogspot.kr/2017/07/kaldi-tutorial-for-korean-model-part-1.html
/home 밑에 small_krs.zip 데이터파일 download ..
https://drive.google.com/open?id=0B9lwe_GFwe2oY196NUJ4NFlPb0k
cd /home/alang
unzip small_krs.zip
# cd $HOME/kaldi_example/egs
# git clone https://github.com/hyung8758/kaldi_tutorial
# cd kaldi_tutorial/
# mkdir generated/
# local/krs_prep_data.sh /home/small_krs/test/ generated/
# ls generated
segments spk2utt text textraw utt2spk wav.scp
;; 데이터 디렉토리 구조는는 아래와 같아야함.
data_dir/train/<화자>/<wav파일>
# vi run.sh
###
kaldi=/APP/DeepLn/kaldi-5.3
source=/home/alang/kaldi_example/small_krs
.
.
### Number of jobs.
# 화자수에 따라 결정
# small_krs 는 화자수가 2명밖에 없는 작은 모델임.
train_nj=2
decode_nj=2
### CMD
train_cmd=utils/run.pl
decode_cmd=utils/run.pl
#train_cmd=utils/queue.pl
#decode_cmd=utils/queue.pl
### train
train_mono=1
train_tri1=1
train_tri2=1
train_tri3=1
train_dnn=0 -> dnn 적용할려면 1
114 번째줄 ..
. path.sh $kaldi
change to
. ./path.sh $kaldi
# sh run.sh
정상적으로 완료되면 아래와 같이 종료된다.
.
.
steps/decode.sh –scoring-opts –num-threads 1 –skip-scoring false –acwt 0.083333 –nj 2 –cmd utils/run.pl –beam 10.0 –model exp/tri3/final.alimdl –max-active 2000 exp/tri3/graph data/train exp/tri3/decode.si
decode.sh: feature type is lda
local/score.sh –cmd utils/run.pl data/train exp/tri3/graph exp/tri3/decode.si
local/score.sh: scoring with word insertion penalty=0.0,0.5,1.0
steps/decode_fmllr.sh: feature type is lda
steps/decode_fmllr.sh: getting first-pass fMLLR transforms.
steps/decode_fmllr.sh: doing main lattice generation phase
steps/decode_fmllr.sh: estimating fMLLR transforms a second time.
steps/decode_fmllr.sh: doing a final pass of acoustic rescoring.
local/score.sh –cmd utils/run.pl data/train exp/tri3/graph exp/tri3/decode
local/score.sh: scoring with word insertion penalty=0.0,0.5,1.0
END TIME: (수)
PROCESS TIME: linux 00:01:26 sec
======================================================================
RESULTS
======================================================================
Displaying results
Displaying results
result.txt is newly generated in log.
Reporting results…
RESULT REPORT ON… 2019. 05. 08. (수) 16:08:48 KST
TRIPHONE3 (LDA + MLLT + SAT)
======================================================================
DECODE
– BEST : WER 0.28 [ 3 / 1082, 0 ins, 2 del, 1 sub ]
– PATH : exp/tri3/decode/wer_15_0.0
======================================================================
결과는 exp 디렉토리 내에 저장된다.
일단 small_krs 데이터의 경우 데이터가 너무 작아서 gpu 나 후속 decoding 작업들을 모두 수행하기에
한계가 있음.
아래는 음성 인식 학습을 완료한 결과로 음성 인식기를 만들때 필요한 decoder 생성 연습을 할때 사용한
예제입니다.
// voxforge 예제 테스트 하기
$ cp <kaldi_path>/egs/voxforge $HOME/kadi_example/egs
$ cd $HOME/kadi_example/egs/voxforge
$ vi path.sh
export DATA_ROOT=”$HOME/kadi_example/egs/voxforge/s5/voxforge”
sh path.sh
mkdir $HOME/kadi_example/egs/voxforge/s5/voxforge
./getdata.sh
du -sh voxforge/
25G voxforge/
which readlink
cd /APP/DeepLn/kaldi-5.3/toos
./extras/install_sequitur.sh
cd ~/egs/voxforge $HOME/kadi_example/egs/voxforge
vi ./local/voxforge_prepare_dict.sh
sequitur=$KALDI_ROOT/tools/sequitur-g2p
vi cmd.sh
export train_cmd=”run.pl –mem 2G”
export decode_cmd=”run.pl –mem 4G”
export mkgraph_cmd=”run.pl –mem 8G”
./path.sh && ./run.sh
최종 특정화자의 음성 인식 학습에 사용한 모델인 kaldi-zeroth 입니다.
https://github.com/goodatlas/zeroth
한국어 음성인식 프로젝트로 유명하더군요.
// kaldi-zeroth 테스트
$ cp -a $KALDI_ROOT/egs/zeroth_korean .
$ cd zeroth_korean/s5
$ mkdir zeroth_db
$ cd zeroth_db
$ wget https://storage.googleapis.com/zeroth_project/zeroth_korean.tar.gz .
$ tar xzvf zeroth_korean.tar.gz
$ mkdir ../data/local/lm -p
$ mv zeroth* ../data/local/lm
$ cd ..
$ ln -sf /APP/DeepLn/kaldi-5.3/egs/wsj/s5/steps .
$ ln -sf /APP/DeepLn/kaldi-5.3/egs/wsj/s5/utils .
$ vi path.sh
// 전처리 과정에서 python3 을 사용함.
export KALDI_ROOT=/APP/DeepLn/kaldi-5.3
[ -f $KALDI_ROOT/tools/env.sh ] && . $KALDI_ROOT/tools/env.sh
vi run.sh
db_dir=<path>/zeroth_korean/s5/zeroth_db
.
# 아래주석
#if [ $stage -le 0 ]; then
# download the data.
# local/download_and_untar.sh $db_dir
#fi
zeroth 는 기본적으로 GE스케줄링 분산처리(queue.pl)로 작업이 수행된다.
기본 설정에 ram_free 란 complex를 이용하는데, 이는 현재 Linux 환경의 SGE에서
기본적으로 사용하지 않는 complex 이다. 아래와 같이 mem_free 로 변경한
queue.conf 파일을 생성한다.
vi conf/queue.conf
# Default configuration
command qsub -V -v PATH -cwd -S /bin/bash -j y
option mem=* -l mem_free=$0
option mem=0 # Do not add anything to qsub_opts
option num_threads=* -pe smp $0
option num_threads=1 # Do not add anything to qsub_opts
option max_jobs_run=* -tc $0
default gpu=0
option gpu=0
option gpu=* -l gpu=$0 -q gpu.q
일반 기본적으로 해석 장비의 core 수는 최소 16core 이상 보유한 단일 서버 1대 이상이 필요하며,
그보다 자원이 작을 경우는
nj=16 부분을 줄여준다. (nj=8)
sh run.sh
해석 과정이 워낙 길다 보니 (2~3일) 중간에 한번 오류가 발생되어 재시작 할려면 시간 소비가 심히다.
특히 전처리 부분이..로그를 보고 정상적으로 완료된 과정을 재시작 시 넘어갈 수 있게 해야한다.
run.sh 스크립트를 열어보면 최 상단에 stage=0 이 있다. 이 부분을 재시작할 stage 단계에 맞게 조정한다.
// 완료된 결과로 decoder 만들기
$ mkdir -p online_demo/ ; cd online_demo
$ mkdir bin
$ mkdir -p model/chain
$ cd modle
$ cp -a <kaldi_zeroth_s5_path>/exp/chain/tdnn1a_sp_online chain
$ cp -a <kaldi_zeroth_s5_path>/exp/chain/tree_a chain
$ cd chain/tdnn1a_sp_online
$ rm -rf decode_*test_clean ivector_extractor
$ cd ../tree_a
$ rm -rf *.gz q log
$ cd online_demo/bin
$ vi speech2txt
—
#!/bin/sh
MODEL_PATH=/home/alang/kaldi_example/egs/zeroth_korean/online_demo
WAV_FILE=”$1″
cd $PWD
echo ${1} $1 > .decode.scp
/APP/DeepLn/kaldi-5.3/src/online2bin/online2-wav-nnet3-latgen-faster –online=true –frame-subsampling-factor=3 –config=${MODEL_PATH}/model/chain/tdnn1a_sp_online/conf/online.conf –min-active=200 –max-active=7000 –beam=15.0 –lattice-beam=6.0 –acoustic-scale=1.0 –word-symbol-table=${MODEL_PATH}/model/chain/tree_a/graph_tgsmall/words.txt ${MODEL_PATH}/model/chain/tdnn1a_sp_online/final.mdl ${MODEL_PATH}/model/chain/tree_a/graph_tgsmall/HCLG.fst ‘ark:cat .decode.scp|’ ‘scp:cat .decode.scp|’ ‘ark:/dev/null’ &> .decode.log && grep “^$1” .decode.log | sed -e “s/$1/$1 : /g”
—
$ chmod 755 speech2txt
// speech2txt 명령 뒤에 wav 파일을 arg 로 입력하면 해당 wav 음성파일의 TEXT 를 확인할 수 있다.
$ speech2txt <wavfile_path>
========================================================================================
아래는 최종적으로 개발한 kaldi decoder 로 유인나 음성 파일에 대한 TEXT 를 생성하는 영상입니다.
참고로 오른쪽 터미널창 내용은 정확한 실제 wav 파일의 TEXT 내용입니다.
아무래도 kaldi-zeroth 에서 이용한 형태소 분석과 언어모델등의 한계로 띄어쓰기등이 이상한 부분은
있지만, 대략 10여개의 패턴에 대해 자동으로 치환하는 후처리를 해주니 크게 문제되진 않았습니다.
이상입니다.
안녕하세요 kaldi를 설치하고 사용하기 위해 여러 글을 찾아보다가 여기까지 오게되었습니다.
지난번 tacotron도 설명을 너무 잘해주셔서 결국 서진우님 글을 다시 찾게됩니다.
kaldi 설치 및 셋팅 중 잘 이해가 가지 않는 부분이 있어서 글을 남깁니다.
제가 설치한 kaldi 의 kaldi/egs/zeroth_korean/s5 폴더 안에는 exp라는 폴더가 없는데
따로 설정 하신것인지 궁금합니다.
kaldi를 설치하기로는 똑같은 github에서 설치하였습니다.
안녕하세요. exp 디렉토리는 kaldi 를 실행했을때 자동으로 생성되며 그 결과들이 생성되었던거 같네요. 애초 설치만 한 상태에서는 없을거예요.
안녕하세요
ubuntu에서 상기 안내에 따라 진행오다가 다음과 같은 에러가 발생하였습니다.
마지막에 speech2txt 을 만들고 speech2txt 0.wav command을 날리니
LOG (online2-wav-nnet3-latgen-faster[5.5.746~1-8df25]:ComputeDerivedVars():ivector-extractor.cc:183) Computing derived variables for iVector extractor
LOG (online2-wav-nnet3-latgen-faster[5.5.746~1-8df25]:ComputeDerivedVars():ivector-extractor.cc:204) Done.
ERROR (online2-wav-nnet3-latgen-faster[5.5.746~1-8df25]:ExpectToken():io-funcs.cc:209) Expected token “”, got instead “”.
해결 방법을 알 수 있을런지요?
딱히 경험해 보지 못한 문제네요. 상시 딥러닝 플랫폼 관련 업무하고 있진 않아서..당장 재현해서 문제를 살펴보기 힘들듯 하네요. ^^;;;
relaxing
Hey there You have done a fantastic job I will certainly digg it and personally recommend to my friends Im confident theyll be benefited from this site
Wow, what a fantastic blog style. How long have you been blogging for? You made it seem so simple. Your website looks beautiful overall, and the information is even better.
Hi, Neat post. There’s an issue together with your web site in internet explorer, may test this·IE still is the marketplace chief and a good component of people will pass over your fantastic writing due to this problem.
I have been surfing online more than 3 hours today yet I never found any interesting article like yours It is pretty worth enough for me In my opinion if all web owners and bloggers made good content as you did the web will be much more useful than ever before
I was lucky enough to find this phenomenal website recently, a jewel providing value to subscribers. The clever owner really understands how to crank out relevant content. I’m pumped about this find and hopeful the excellent content keeps coming!
I have been struggling with this issue for a while and your post has provided me with much-needed guidance and clarity Thank you so much
This is exactly what I needed to read today Your words have provided me with much-needed reassurance and comfort
Your blog has become a part of my daily routine Your words have a way of brightening up my day and lifting my spirits
As a fellow blogger, I can appreciate the time and effort that goes into creating well-crafted posts You are doing an amazing job
Your posts always provide me with a new perspective and encourage me to look at things differently Thank you for broadening my horizons
I am not sure where youre getting your info but good topic I needs to spend some time learning much more or understanding more Thanks for magnificent info I was looking for this information for my mission
Nice blog here Also your site loads up very fast What host are you using Can I get your affiliate link to your host I wish my site loaded up as quickly as yours lol
I loved as much as youll receive carried out right here The sketch is attractive your authored material stylish nonetheless you command get bought an nervousness over that you wish be delivering the following unwell unquestionably come more formerly again as exactly the same nearly a lot often inside case you shield this hike
Excellent blog here Also your website loads up very fast What web host are you using Can I get your affiliate link to your host I wish my web site loaded up as quickly as yours lol
Wonderful beat I wish to apprentice while you amend your web site how could i subscribe for a blog web site The account aided me a acceptable deal I had been a little bit acquainted of this your broadcast provided bright clear idea
hiI like your writing so much share we be in contact more approximately your article on AOL I need a specialist in this area to resolve my problem Maybe that is you Looking ahead to see you
Nice blog here Also your site loads up very fast What host are you using Can I get your affiliate link to your host I wish my site loaded up as quickly as yours lol
Fantastic site A lot of helpful info here Im sending it to some buddies ans additionally sharing in delicious And naturally thanks on your sweat
Simply desire to say your article is as surprising The clearness in your post is simply excellent and i could assume you are an expert on this subject Fine with your permission let me to grab your feed to keep up to date with forthcoming post Thanks a million and please carry on the gratifying work
Simply desire to say your article is as surprising The clearness in your post is simply excellent and i could assume you are an expert on this subject Fine with your permission let me to grab your feed to keep up to date with forthcoming post Thanks a million and please carry on the gratifying work
I was recommended this website by my cousin I am not sure whether this post is written by him as nobody else know such detailed about my difficulty You are wonderful Thanks
Wonderful web site Lots of useful info here Im sending it to a few friends ans additionally sharing in delicious And obviously thanks to your effort
I simply could not go away your web site prior to suggesting that I really enjoyed the standard info a person supply on your guests Is going to be back incessantly to investigate crosscheck new posts
I simply could not go away your web site prior to suggesting that I really enjoyed the standard info a person supply on your guests Is going to be back incessantly to investigate crosscheck new posts
fun88 เป็นเว็บไซต์อย่างเป็นทางการที่มีชื่อเสียงซึ่งอุทิศให้กับตลาดเวียดนามโดยเฉพาะ fun88 เปิดประตูสู่การแข่งขันการเดิมพันที่น่าตื่นเต้น เว็บไซต์ไม่เพียงแค่อัปเดตข้อมูลเกี่ยวกับกิจกรรม โปรโมชั่น และโอกาสในการเดิมพันใหม่ๆ อย่างต่อเนื่องเท่านั้น แต่ยังให้การฝากและถอนเงินที่ง่ายดาย พร้อมคำแนะนำทีละขั้นตอนสำหรับเงินทุนของคุณ
I have been surfing online more than 3 hours today yet I never found any interesting article like yours It is pretty worth enough for me In my opinion if all web owners and bloggers made good content as you did the web will be much more useful than ever before
Nice blog here Also your site loads up very fast What host are you using Can I get your affiliate link to your host I wish my site loaded up as quickly as yours lol
Nice blog here Also your site loads up very fast What host are you using Can I get your affiliate link to your host I wish my site loaded up as quickly as yours lol
I do not even know how I ended up here but I thought this post was great I do not know who you are but certainly youre going to a famous blogger if you are not already Cheers
Attractive section of content I just stumbled upon your blog and in accession capital to assert that I get actually enjoyed account your blog posts Anyway I will be subscribing to your augment and even I achievement you access consistently fast
Thank you I have just been searching for information approximately this topic for a while and yours is the best I have found out so far However what in regards to the bottom line Are you certain concerning the supply
Hello i think that i saw you visited my weblog so i came to Return the favore Im trying to find things to improve my web siteI suppose its ok to use some of your ideas
Hi my loved one I wish to say that this post is amazing nice written and include approximately all vital infos Id like to peer more posts like this