2019년 7월 15일 월요일

음성인식 메모(kaldi) 26 - parameter update, NG-SGD Dan's DNN(nnet2)

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/10/06/124604


이전 글의 연속 논문과 함께 보도록 하자.

파라미터 업데이트의 과정에서 하고 있는 것은 「Natural Gradient for Stochastic Gradient Descent (NG-SGD)」이라는 이름이 붙어있다.

용어의 복습

「Stochastic Gradient Descent」는 shuffle한 학습 데이터로 gradient를 계산, 파라미터 업데이트를 반복하는 방법. 하나의 학습 데이터를 사용하는 것이 온라인 학습, 여러개의 학습 데이터를 사용하는 것이 미니배치 학습.

「Natural Gradient method」는 그들 연구에서 쓰이는 용어로 fisher information 행렬의 역행렬(근사)을 learning rate matrix(???)로서 사용하는 방법이다.

previous work has used the term “Natural Gradient” to describe methods like ours which use an approximated inverse-Fisher matrix as the learning rate matrix, so we follow their precedent in calling our method “Natural Gradient”.

핵심이 되는 계산은 N행으로 구성된 행렬 X가 있을 때 i번째의 행 벡터 「xi」에 fisher information (Fi)의 역수를 곱하는 것이다.

여기에서 fisher information「Fi」은 i번째의 행을 뺀 다른 행「xj」으로부터 구할 수 있다.

이 아이디어를 기반으로, 아래 2개의 확장을 추가했다고 한다.

  1. smoothing of Fi with the identity matrix (단위 행렬에 의한 Fi의 equalization)
  2. scaling the output to have the same Frobenius norm as the input(input과 같은 frobenius norm을 가지도록 output을 스케일링)

여기서, N행 x D열의 행렬 「Xt」가 있을 때 「Xt」를 column-wise(열 방향)으로 생각하면 fisher information matrix 「F_i」은 D행 x D열이 된다(X^T X)

fisher information matrix를 R차원의 낮은 랭크로 근사하여,

t : 미니배치 index

F_t : D행 x D열

R_t : R행 x D행

D_t : R행 x R행

I : D행 x D열、identity matrix (단위행렬)

rhot : 0 < rhot

역행렬 (근사한 것)을 구한다.

G_t : D행 x D열

E_t : R행 x R행

beta_t : scalar

column-wise(열방향)이므로, 곱셈은 「Xt」의 우측에서 곱하여 「Xt」을 업데이트한다.

이는 이전 글의 아래 부분에 해당한다.

이어서 스케일링.

이 부분은 이전 글의 「gamma_t」에 해당한다.

좀 더 자세히 보도록 하자.

업데이트 대상인 파라미터의 차원(D)가 376, 미니배치 사이즈(N)이 128, 낮은 랭크로 근사한 차원(R)이 30이라 하자.

행렬 X_t(N행 x D열)은, transpose 행렬을 곱하여 대칭행렬로 만든 후 계산한다.

또한 논문에서는

파라미터의 차원(D) < 미니배치의 차원(N)

을 상정하여 column-wise(열방향)으로 되어 있다.

(학습이 진행됨에 따라, 미니배치 사이즈는 점점 커진다(예를 들어 512))

계산은 D차원(=full-rank)의 대칭행렬 「T_t」(D행 x D열)에 대해서가 아니라,

S_t : D행 x D열

eta : forgetting factor、0 < η < 1

R차원(=row-rank approximation)의 대칭행렬 「Z_t」(R행 x R열)에 대해 수행한다.

Y_t : R행 x D열

R_t : R행 x D행

위의 「Rt」을 스케일링 한것을 「Wt」로 두어,

W_t : R행 x D행

이것을 weight 행렬로써 적절한 값이 되도록 업데이트하는 것이 목적이 된다.

일단은 「R_t」를 초기화하자.

이것은 R차원의 직교행렬을 가로로 늘어놓은 형태가 된다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::InitDefault()

// after the next line, W_t_ will store the orthogonal matrix R_t.
InitOrthonormalSpecial(&W_t_);

R_t (R x D)

「E_t」의 초기값을 구하여, 그것의 root를 곱한다.

BaseFloat E_tii = 1.0 / ( 2.0 + (D + rank_) * alpha_ / D );

// W_t =(def) E_t^{0.5} R_t.
W_t_.Scale(sqrt(E_tii));

다음의 미니배치에 대해 「W_t」를 업데이트한다.

업데이트 식은 아래와 같다.

W_t1 : R행 x D열
E_t1 : R행 x R행、diagonal matrix
R_t1 : R행 x D열
C_t : R행 x R행、diagonal matrix
U_t : R행 x R열、Orthogonal matrix
Y_t : R행 x D열
J_t : R행 x D열
D_t : R행 x R행
eta : forgetting factor、0 < η < 1

「Ut」(직교행렬)와 「Ct」(특이값)은, 「Z_t」를 SVD하여 얻는다.

아래 「Z_t」를 구하는 과정

원래의 input 「Xt」(N행 x D열)에, 「Wt」(R행 x D열)의 전치행렬을 오른쪽에서부터 곱하여, 「H_t」(N행 x R열)을 구한다(「N」은 미니배치사이즈)

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

H_t.AddMatMat(1.0, *X_t, kNoTrans, W_t, kTrans, 0.0);  // H_t = X_t W_t^T

H_t (N x R)

열 수 「D」(376차원)의 input (X_t)가, 열 수「R」(30차원)의 행렬이 된다.

이어서, 「J_t」를 구한다.

원래의 input 「Xt」(N행 x D열)에 「Ht」(N행 x R열)의 전치행렬을 왼쪽에서부터 곱하여 「J_t」(R행 x D열)을 구한다.

(「W_t」와 같은 행 수, 열 수가 된다)

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

J_t.AddMatMat(1.0, H_t, kTrans, *X_t, kNoTrans, 0.0);  // J_t = H_t^T X_t

「J_t」의 식을 변형하면

J_t = H_t^T X_t
    = (X_t W_t^T)^T X_t
    = W_t X_t^T X_t

「X_t」(N행 x D열)의 「uncentered covariance matrix」(D행 x D열)에, Weight Matrix(R행 x D열)를 왼쪽에서 곱한 것이라고 할 수도 있다.

J_t (R x D)

이어서 「K_t」를 구한다.

「J_t」(R행 x D열)에 대하여, 전치행렬을 오른쪽에서부터 곱한 것에 해당한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

K_t.SymAddMat2(1.0, J_t, kNoTrans, 0.0);  // K_t = J_t J_t^T

K_t (R x R、symmetric)

이어서 「L_t」을 구한다.

「H_t」(N행 x R열)에 대해, 전치행렬을 왼쪽에서 곱한 것에 해당한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

L_t.SymAddMat2(1.0, H_t, kTrans, 0.0);  // L_t = H_t^T H_t 

L_t (R x R、symmetric)

「Kt」과 「Lt」을 사용하여, 「Z_t」를 구한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

 SpMatrix<double> Z_t_double(R);
 ComputeZt(N, rho_t, d_t, inv_sqrt_e_t, K_t_cpu, L_t_cpu, &Z_t_double);

Z_t (R x R、symmetric)

스케일 변환 후, SVD한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

Matrix<BaseFloat> U_t(R, R);
Vector<BaseFloat> c_t(R);
// do the symmetric eigenvalue decomposition Z_t = U_t C_t U_t^T.
Z_t_scaled.Eig(&c_t, &U_t);
SortSvd(&c_t, &U_t);
c_t.Scale(z_t_scale);

C_t (30차원)

0.957  0.164  0.119  0.090  ...   0.00019  0.00016

U_t (R x R)

「W_t」를 업데이트한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::PreconditionDirectionsInternal()

CuMatrix<BaseFloat> W_t1(R, D);  // W_{t+1}
ComputeWt1(N,
           d_t,
           d_t1,
           rho_t,
           rho_t1,
           U_t,
           sqrt_c_t,
           inv_sqrt_e_t,
           W_t,
           &J_t,
           &W_t1);

「B_t」를 구한다.

B_t (R x D)

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::ComputeWt1()

// B_t = J_t + (1-eta)/(eta/N) (D_t + rho_t I) W_t
J_t->AddDiagVecMat(1.0, w_t_coeff_gpu, W_t, kNoTrans, 1.0);

wtcoeffgpu : R행 x R행, 대각행렬, Wt의 각 행에 대한 계수

식의 변형은 아래와 같다.

B_t = J_t + ( W_t계수 * W_t )
    = ( W_t X_t^T X_t ) + ( W_t계수 * W_t )

「A_t」를 구한다.

A_t (R x R)

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::ComputeWt1()

// A_t = (eta/N) E_{t+1}^{0.5} C_t^{-0.5} U_t^T E_t^{-0.5} B_t
Matrix<BaseFloat> A_t(U_t, kTrans);
for (int32 i = 0; i < R; i++) {
    BaseFloat i_factor = (eta / N) * sqrt_e_t1(i) * inv_sqrt_c_t(i);
    for (int32 j = 0; j < R; j++) {
        BaseFloat j_factor = inv_sqrt_e_t(j);
        A_t(i, j) *= i_factor * j_factor;
    }
}

「W_t1」를 구한다.

[ nnet2/nnet-precondition-online.cc ] OnlinePreconditioner::ComputeWt1()

// W_{t+1} = A_t B_t
CuMatrix<BaseFloat> A_t_gpu(A_t);
W_t1->AddMatMat(1.0, A_t_gpu, kNoTrans, *J_t, kNoTrans, 0.0);

「Wt1」를 사용하여 「Xt」를 업데이트한다.

이것을 반복하여, 업데이트 된 「Xt」는 이전 글에서 보았던 「invaluetemp」、「outderiv_temp」에 해당한다.

[ nnet2/nnet-component.cc ] AffineComponentPreconditionedOnline::Update()

preconditioner_in_.PreconditionDirections(&in_value_temp,
                                          &in_row_products,
                                          &in_scale);

preconditioner_out_.PreconditionDirections(&out_deriv_temp,
                                           &out_row_products,
                                           &out_scale);

파라미터를 수정하는 벡터로 사용하여 모델의 파라미터를 업데이트.

[ nnet2/nnet-component.cc ] AffineComponentPreconditionedOnline::Update()

bias_params_.AddMatVec(local_lrate, out_deriv_temp, kTrans, precon_ones, 1.0);
linear_params_.AddMatMat(local_lrate, out_deriv_temp, kTrans, in_value_precon_part, kNoTrans, 1.0);

음성인식 메모(kaldi) 25 - 내부에서 사용하는 데이터의 형태

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/09/15/121225


프로그램 내에서 사용하는 데이터의 자릿수에 관한 메모.

디폴트로 컴파일하면 「float」형이 된다.

kaldi/configure at master · kaldi-asr/kaldi · GitHub

configure 내용 중

# Default configuration
double_precision=false

(snip)

if $double_precision; then
  echo "DOUBLE_PRECISION = 1" >> kaldi.mk
else
  echo "DOUBLE_PRECISION = 0" >> kaldi.mk
fi

DOUBLE_PRECISION이 「0」이라면, 템플릿 부분은 float형으로 컴파일된다.

matrix/kaldi-matrix.cc

template<typename Real>
void Matrix<Real>::Read(std::istream & is, bool binary, bool add) {

    Real r;
    is >> r;

사용하는 머신에 double형 연산을 지원하는 회로가 있다면 double형으로 하는 쪽이 빨라질지도 모른다.

아래는 데이터의 읽기에 관한 메모.

아래와 같은 지수표기로 쓰여진 텍스트 파일이 있다면,

-6.93889018e-18 -5.55112e-17 ...

아래의 코드를 통해 읽을 수 있다.

float r;
is >> r;

음성인식 메모(kaldi) 24 - parameter update Dan's DNN(nnet2)

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/09/04/165212


「AffineComponentPreconditionedOnline」 컴포넌트의 파라미터 업데이트 과정을 따라가보자.

모델은 「nnet4c」 기반, mixup을 수행하기 전의 상태

이번에 확인하려는 것은 아래 그림의 점선 부분의 파라미터

BackPropagation에서는 아래 2개의 값을 사용하여 업데이트한다.

또한 여기서 minibatch-size는 「128」로 한다.

  • 「invaluetemp」 : Propagation에서 Tanh 컴포넌트를 통과한 이후의 값(128row x 375col)에 대해, 각 행의 마지막에 「1.0」을 추가한 것(128row x 376col)
  • 「outderivtemp」 : Backpropagation에서 Softmax 컴포넌트를 통과한 이후의 값의 복사본(128row x 192col)

AffineComponentPreconditionedOnline 클래스의 멤버인 2개의 OnlinePreconditioner 클래스에 위 2개를 각각 전달한다.

nnet2/nnet-component.cc

void AffineComponentPreconditionedOnline::Update(
 CuMatrixBase<BaseFloat> &in_value,
 const CuMatrixBase<BaseFloat> &out_deriv)
{
 ...
 preconditioner_in_.PreconditionDirections(&in_value_temp,
                                              &in_row_products,
                                              &in_scale);
 preconditioner_out_.PreconditionDirections(&out_deriv_temp,
                                               &out_row_products,
                                               &out_scale);
 ...

여기에서의 처리 내용의 세부사항은 Daniel povey의 논문에 쓰여 있다.

업데이트 전의 invaluetemp(128row x 376col、 논문의 「X_t」에 해당

0.265  0.362  -0.999  -0.999  -0.946  ... 1
...

t : minibatch index

여기에서, Xhatt를 구한다.

R=30(rankIn(낮은 랭크 근사)의 차원)

「W_t」는 R x D의 Weight Matrix(D=376)

N=128(minibatch size)

업데이트 후의 invaluetemp(128row x 376col、 논문의 「Xhatt」에 해당

0.035  0.562  -0.010  0.040  -0.575  ...
...

해당하는 소스코드 (nnet-precondition-online.cc)

// X_hat_t = X_t - H_t W_t
X_t->AddMatMat(-1.0, H_t, kNoTrans, W_t, kNoTrans, 1.0);  

「Xhatt」의 각 행의 inner product ( (0.035 * 0.035) + (0.562 * 0.562) + (-0.010 * -0.010) + …)를 구한다.

이것이 「inrowproducts」에 해당한다.

inrowproducts (128dim)

62.02  44.57  66.93  81.97  59.58 ...

게다가, 아래 식에 따라 gamma_t를 구한다.

이것이 「in_scale」에 해당한다.

in_scale

2.04739451

「preconditionerout」에 대해서도 같은 처리를 수행한다.

업데이트 전의 outderivtemp(128row x 192col、논문의 「X_t」에 해당)

-0.000  -6.5e-06, -9.9e-06, -1.2-06, -7.5e-07 ...
...

업데이트 후의 outderivtemp(128row x 192col、 논문의 「Xhatt」에 해당)

0.048  -5.5e-06  -0.000  5.9e-08  -0.000  ...
...

outrowproducts(128dim)

0.136  0.163  0.000  0.072  0.008  ...

out_scale

2.1778152

「invaluetemp」(128row x 376dim)로, 「invaluepreconpart」(128row x 375dim)、「preconones」(128dim)를 구한다.

AffineComponentPreconditionedOnline::Update()의 내부 처리

CuSubMatrix<BaseFloat> in_value_precon_part(in_value_temp,
                                            0, in_value_temp.NumRows(),
                                            0, in_value_temp.NumCols() - 1);

CuVector<BaseFloat> precon_ones(in_value_temp.NumRows());
precon_ones.CopyColFromMat(in_value_temp, in_value_temp.NumCols() - 1);

「inscale」、「outscale」、「inrowproducts」、「outrowproducts」으로 해당 미니배치의 「scale」 및 「minibatch_scale」을 구한다.

이상 언급한 데이터를 가지고 파라미터를 업데이트한다.

AffineComponentPreconditionedOnline::Update()의 내부처리

BaseFloat local_lrate = scale * minibatch_scale * learning_rate_;

bias_params_.AddMatVec(local_lrate, out_deriv_temp, kTrans,
                                    precon_ones, 1.0);
linear_params_.AddMatMat(local_lrate, out_deriv_temp, kTrans,
                                      in_value_precon_part, kNoTrans, 1.0);

2019년 7월 14일 일요일

음성인식 메모(kaldi) 23 - training 시의 모델 업데이트 Dan's DNN(nnet2)

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/08/12/123334


음성인식 메모(kaldi) 20 - Training Dan's DNN(nnet2)의 연장.

mixup 후에 생성된 12개의 모델(14.mdl〜25.mdl)에서 「final.mdl」을 생성하는 과정을 추적해보자.

nnet2에는 결과가 좋은 모델을 하나만 고르지 않고 (여러 모델에?) 스케일을 곱한 다음 더하는 방식을 취한다.

생성은 「nnet2bin/nnet-combine-fast」 커맨드를 사용하여 모델을 combine(합성)한다.

nnet-combine-fast \
    --minibatch-size=129 \ 
    exp/nnet4c/14.mdl \
    exp/nnet4c/15.mdl \
    <snip> \
    exp/nnet4c/25.mdl \
    ark:exp/nnet4c/egs/combine.egs \    #  <valid-examples-in>
    exp/nnet4c/final.mdl

이번 케이스에선 모델의 Component 수가 「9」、Updatable-Component 수가 「3」

info 커맨드(nnet2bin/nnet-am-info exp/nnet4c/14.mdl )

num-components 9
num-updatable-components 3
input-dim 40
output-dim 192
component 0 : SpliceComponent, input-dim=40, output-dim=360, ... 
component 1 : FixedAffineComponent, input-dim=360, output-dim=360, ...
component 2 : AffineComponentPreconditionedOnline, input-dim=360, output-dim=375, ...
component 3 : TanhComponent, input-dim=375, output-dim=375
component 4 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=375, ...
component 5 : TanhComponent, input-dim=375, output-dim=375
component 6 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=453, ...
component 7 : SoftmaxComponent, input-dim=453, output-dim=453
component 8 : SumGroupComponent, input-dim=453, output-dim=192

index이 2、4、6인 「AffineComponentPreconditionedOnline」 컴포넌트 (Updatable-Component에 해당)가 합성 대상이 된다.

결과를 먼저 쓰자면, 각 모델, 각 컴포넌트에 대해 아래와 같은 스케일이 할당된다.

각 모델, 컴포넌트마다의 스케일

스케일을 구하는 과정을 따라가보자.

업데이트에 있어서의 평가 척도는 Propagate의 값(Log값)이 된다.(값이 작을수록 좋다)

같은 데이터(exp/nnet4c/egs/combine.egs)에 대한 모델의 값은 아래와 같이 되어 있다.

14.mdl : -0.0753596
15.mdl : -0.0679429
16.mdl : -0.0373454
17.mdl : -0.0325585
18.mdl : -0.0286209
19.mdl : -0.0237487
20.mdl : -0.0209156
21.mdl : -0.00755152
22.mdl : -0.0101945
23.mdl : -0.00779286
24.mdl : -0.00615664
25.mdl : -0.00456561

이번 케이스에서는 「25.mdl」의 「-0.00456561」가 가장 좋은 값이므로, 그 값을 기준으로 더욱 좋은 값을 얻을 수 있는 스케일을 구해보자.

스케일의 최적해는 뉴턴 메소드 (L-BFGS)를 사용하여 구한다.

소스 코드의 내용(nnet2/combine-nnet-fast.cc)

OptimizeLbfgs<double> lbfgs(params_,
                            lbfgs_options);

// Loop 10 times
for (int32 i = 0; i < config_.num_lbfgs_iters; i++) {

    // 스케일을 세팅
    params_.CopyFromVec(lbfgs.GetProposedValue());

    // Propagate의 값과 gradient를 구한다
    objf = ComputeObjfAndGradient(&gradient, ®ularizer_objf);

    if (i == 0) {
        initial_objf = objf;
        initial_regularizer_objf = regularizer_objf;
    }
    // 판정과 스케일을 업데이트
    lbfgs.DoStep(objf, gradient);

} // end of for(i)

각 루프 단계에서 Propagate의 값과 판정은 아래와 같았다.

(i=0) -0.00456561                  
(i=1) -0.310829      action = decrease 
(i=2) -0.0201834     action = decrease 
(i=3) -0.00512826    action = decrease 
(i=4) -0.00410415    action = accept   
(i=5) -0.00352887    action = accept
(i=6) -0.00315388    action = accept
(i=7) -0.00228854    action = accept
(i=8) -0.000916785   action = accept
(i=9) -0.000539656   action = accept

루프 카운터가 「9」일 때의 값 「-0.000539656」가 가장 좋았기 때문에, 이 때의 스케일이 최적해가 된다.

params_ = lbfgs.GetValue(&objf);

음성인식 메모(kaldi) 22 - alignment

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/07/29/104925


decode의 과정을 파헤쳐보자.

alignment로 출력되는 수치(input인 MFCC feature 각 프레임에 일대일로 대응한다)는 무엇을 나타내고 있는 것일까?

이번엔 egs/wsj/s5/steps/decode.sh의 내부에서 호출되는 lattice 생성 커맨드의 output을 보도록 하자.

(추가 옵션으로, 「words-wspecifier」와 「alignments-wspecifier」를 지정)

gmm-latgen-faster \
--max-active=7000 \
--beam=13.0 \
--lattice-beam=6.0 \
--acoustic-scale=0.083333 \
--allow-partial=true \
--determinize-lattice=true \
--word-symbol-table=exp/mono/graph/words.txt \
exp/mono/final.mdl \        # model-in
exp/mono/graph/HCLG.fst \   # fst-in
ark:/tmp/utter_053.ark \    # features-rspecifier
ark:/tmp/lat.ark \          # lattice-wspecifier
ark:/tmp/word.ark \         # words-wspecifier
ark:/tmp/ali.ark            # alignments-wspecifier

입력

음성(/tmp/utter_053.ark)

이전 글에서 테스트용으로 사용한 ”禁煙席お願いします(킨넨 세키 오네가이 시 마스: 금연석 부탁합니다)”라는 발화

(프레임 수는 「323」、식별자는 "utteranceid053"、39차원)

아래 커맨드로 생성한다.

apply-cmvn \
--utt2spk=ark:data/test/split1/1/utt2spk \
scp:data/test/split1/1/cmvn.scp \
scp:data/test/split1/1/feats.scp \
ark:- | \
add-deltas \
ark:- \
ark:/tmp/hoge.ark

모델

이전 글에서 생성했던 Monophone 모델(언어 모델은 2-gram으로 생성)

exp/mono/graph/words.txt

<eps> 0
!SIL 1
<UNK> 2
あり(아리) 3 
お(오) 4
お願い(오네가이) 5
し(시) 10
ます(마스) 23
席(세키) 45
禁煙(킨넨) 53
#0 62
<s> 63
</s> 64

exp/mono/graph/phones/align_lexicon.txt

<eps> <eps> sil
禁煙(킨넨) 禁煙(킨넨) k_B i_I N_I e_I N_E
席(세키) 席(세키) s_B e_I k_I i_E
お願い(오네가이) お願い(오네가이) o_B n_I e_I g_I a_I i_E
し(시) し(시) sh_B i_E
ます(마스) ます(마스) m_B a_I s_I u_E

output

words(tmp/word.ark)

utterance_id_053 53 45 5 10 23 

symbol로 바꾸면, 「禁煙(킨넨)(53) 席(세키)(45) お願い(오네가이)(5) し(시)(10) ます(마스)(23)」

lattice (/tmp/lat.ark) ※설명용으로 일부 가공

utterance_id_053                                              
0   1   53  9.81125, 7750.02, 2_1_1_1_8_5_5_5_18_17_17_17_17_17_...
1   2   45  2.38995, 4952.66, 197_197_197_197_197_197_197_197_...
2   3   5   4.61863, 8155.52, 527_527_527_527_527_527_527_527_...
3   4   10  2.22007, 3096.31, 527_926_925_925_925_925_925_925_...
4   5   23  5.81478, 6656.75, 523_526_528_638_637_640_639_639_...
5                 0,       0, 917_917_917_917_917_917_917_917_...

alignment 결과(/tmp/ali.ark)

utterance_id_053 2 1 1 1 8 5 5 5 18 17 17 17 17 17 17 17 17 17 17 ...

MFCC feature 프레임과 같은 수의 「323」개

alignment를 음소로 변환한다.

ali-to-phones \
--write-lengths \
exp/mono/final.mdl \
ark:/tmp/ali.ark \
ark:/tmp/ali2phone.ark

alignment 결과(/tmp/ali2phone.ark) ※설명용으로 음소를 symbol로 치환

utterance_id_053 sil 31 ; k_B 5 ; i_I 12 ; N_I 39 ; e_I 6 ; N_E 3 ; s_B 3 ; e_I 22 ; k_I 3 ; i_E 12 ; o_B 28 ; n_I 14 ; e_I 3 ; g_I 13 ; a_I 4 ; i_E 14 ; sh_B 28 ; i_E 5 ; m_B 38 ; a_I 14 ; s_I 21 ; u_E 5 

symbol의 뒤는 출현 수. 예를 들어 「sil 31」은 "sil"이 31회 이어졌다는 것을 뜻함

다시 오늘의 주제 alignment 시 출력되고 있는 것은 무엇인지 알아보자.

「ali-to-phones」 커맨드에 건넨 input을 봤다면 모델(*.mdl)의 정보로부터 유추 가능하다.

(alignment로부터 음소로 변환하는 것뿐이라면, FST의 그래프는 사용하지 않는다)

모델 생성 시의 input이 되는 「phones.txt」의 내부는 전부 「171」개가 있다.

exp/mono/phones.txt

<eps> 0
sil 1
sil_B 2
sil_E 3
sil_I 4
sil_S 5
spn 6
spn_B 7
spn_E 8
spn_I 9
spn_S 10
N_B 11
N_E 12
N_I 13
N_S 14
a_B 15
a_E 16
a_I 17
a_S 18
...
z_E 164
z_I 165
z_S 166
#0 167
#1 168
#2 169
#3 170

여기에서 모델의 「TopologyEntry」로 정의되는 것은 「166」개.

<ForPhones> 1 2 3 4 5 6 7 8 9 10 </ForPhones> 
<ForPhones> 11 12 13 14 15 16 17 18  ...  164 165 166 </ForPhones> 

phone-id가 1에서 10까지 (silence phone)은 「5」 상태, 11에서 166까지(non silence phone)는 「3」 상태가 된다.

3 state HMM

pdf-class수는 「3」、transition 수는 「6」

5 state HMM

pdf-class수는 「5」、transition 수는 「18」

음소 수 x 상태의 총 수는 「518」(5상태 x 10음소 + 3상태 x 156음소)

이 「518」 개의 하나하나에 pdf를 정의하는 것이 아니라, 닮은 음소 간 상태 pdf를 공유시킨다.

상태 간에 pdf를 공유하여, pdf의 총 수는 「127」이 된다.

<NUMPDFS> 127

상태 transition의 총 수는 「1116」이 된다(18transition x 10음소 + 6transition x 156음소)

(「LogProbs」엔트리와 같은 수)

<LogProbs> 
 [ 0 -0.3281818 -1.378509 -4.027719 -4.60517 ... -1.386294 -0.2876821 -1.386294 ]
</LogProbs> 

alignment로 출력되는 것은 상태 transition의 식별자(transition-id)에 해당한다.

예를 들어, 말하기 시작하는 부분의 「sil」에 대해서는 「2 1 1 1 8 5 5 5 18 17 17 17 17 17 17 17 17 17 17 …」로 나열된다.

번호를 부여하는 방법을 생각해보면, self-loop의 transition을 뒤에서부터 추가하는 것 같다.

이는 어느 state를 봤을 때 self-loop 쪽이 transition-id가 크게 되어있기 때문이다.