TechNOTE

[논문리뷰] 간단한 EATS 리뷰 본문

음성

[논문리뷰] 간단한 EATS 리뷰

JU1234 2020. 10. 15. 22:53

딥마인드에서 음성 합성 관련해서 새 논문이 나왔다!

바로 END-TO-END ADVERSARIAL TEXT-TO-SPEECH 인데..

사실 End to End 라는 말보다 (말의 정의가 너무 애매하다) 1 stage speech synthsis 라고 하는게 더 맞지 않나 싶다.

여튼 이번 뉴립스에는 떨어지고 ICLR 에 다시 낸 것 같다.

deepmind.com/research/publications/End-to-End-Adversarial-Text-to-Speech

 

End-to-End Adversarial Text-to-Speech

Modern text-to-speech synthesis pipelines typically involve multiple processing stages, each of which is designed or learnt independently from the rest. In this work, we take on the challenging task of learning to synthesise speech from normalised text or

deepmind.com

이 논문을 한번 리뷰해보도록 하겠다 두둥!

Contribution

- 기존의 text-to-speech (음성 합성) 모델들은 2 stage로 이루어져서 두 개의 모델로 두번 학습이 필요했었는데, (text -> mel(or linguistic feature -> speech) 이를 (text -> speech) 한 개의 모델로 한번 학습만에 해 낼수 있다!

- 2 stage로 학습을 진행할 때 오류 발생으로 인한 어떤 추상적인 정보의 손실이 일어날 것이기 때문에 1 stage로 진행했을 때 최적화가 더 잘되어 성능이 더 좋을 것이다.

Limitation

- 실제 성능은 2 stage 모델보다 별로 안 좋다 ㅜ <- 이게 아마 크리티컬한 점이 아니었을까..

- 딥마인드 내부 데이터를 사용했기 때문에.. (아마 좋은 데이터였을 것) 직접 구현한 것으로는 저 정도 퀄리티가 안 나온다.

대충 이 정도의 Limitation과 Contribution을 안고 논문 내용을 읽어보자!

Model - Aligner + Generator 

EATS 전체 모델 구조 

모델 구조는 다음과 같다.

먼저, cat sat on the mat 이라는 text 가 왔을 때 이를 음성 기호인 Phonem으로 바꾸어 준다!

이 부분이다.. phonemizer 를 통해 phonem으로 바꾸어줌 

그리고 여러 개의 Convolution Block 에 이를 넣어서 각 Phonem의 Length를 예측한다.

이 부분은 Pesudo Code에 잘 나와 있다.

# Learn embeddings of the input tokens and speaker IDs.
embedded_tokens = Embed(input_vocab_size=token_vocab_size, # -> [N, 600, 256]
output_dim=256)(token_sequences)
embedded_speaker_ids = Embed(input_vocab_size=num_speakers, # -> [N, 128]
output_dim=128)(speaker_ids)
# Make the "class-conditioning" inputs for class-conditional batch norm (CCBN)
# using the embedded speaker IDs and the noise.
ccbn_condition = Concat([embedded_speaker_ids, noise], axis=1) # -> [N, 256]
# Add a dummy sequence axis to ccbn_condition for broadcasting.
ccbn_condition = ccbn_condition[:, None, :] # -> [N, 1, 256]
# Use `lengths` to make a mask indicating valid entries of token_sequences.
sequence_length = token_sequences.shape[1] # = 600
mask = Range(sequence_length)[None, :] < lengths[:, None] # -> [N, 600]
# Dilated 1D convolution stack.
# 10 blocks * 6 convs per block = 60 convolutions total.
x = embedded_tokens
conv_mask = mask[:, :, None] # -> [N, 600, 1]; dummy axis for broadcast.
for _ in range(10):
  for a, b in [(1, 2), (4, 8), (16, 32)]:
    block_inputs = x
    x = ReLU(ClassConditionalBatchNorm(x, ccbn_condition))
    x = MaskedConv1D(output_channels=256, kernel_size=3, dilation=a)(
    x, conv_mask)
    x = ReLU(ClassConditionalBatchNorm(x, ccbn_condition))
    x = MaskedConv1D(output_channels=256, kernel_size=3, dilation=b)(
    		x, conv_mask)
x += block_inputs # -> [N, 600, 256]
# Save dilated conv stack outputs as unaligned_features.
unaligned_features = x # [N, 600, 256]

설명도 너무 친절하게 적혀 있어서 다른게 필요 없을 듯. 

여기서 써져 있는 unaligned_feature 은, "안녕하세요" 라는 말을 발화한다고 했을 때 각 phonem별로 가지는 벡터값이다.. 

이 unaligned feature 를 가지고 각 phonem별로 실제 발화에서 길이가 얼마나 될지 예측한다 

# Map to predicted token lengths.
x = ReLU(ClassConditionalBatchNorm(x, ccbn_condition))
x = Conv1D(output_channels=256, kernel_size=1)(x)
x = ReLU(ClassConditionalBatchNorm(x, ccbn_condition))
x = Conv1D(output_channels=1, kernel_size=1)(x) # -> [N, 600, 1]

token_lengths = ReLU(x[:, :, 0]) # -> [N, 600]
token_ends = CumSum(token_lengths, axis=1) # -> [N, 600]
token_centres = token_ends - (token_lengths / 2.) # -> [N, 600]

 

 

 

짠 됐다. 각 token_length를 cumsum 하면 각 token의 end 를 할수 있고 얘를 저렇게 잘 나누면 각 token의 중간 위치를 알 수 있다. 

여기서부터가 중요하다..! 저 token_centres 정보와 unaligned_features를 가지고 실제 wav 를 생성해내는 GAN-TTS 전에 들어가는 Input을 만들어 내야 한다. 근데 모든 토큰 정보를 활용하여 전체 wav 길이를 생성해 내는 걸 하면은 학습이 너무 힘들고.. batch 마다 패딩조절하기도 어려울 것이므로 slicing 을 해 준다. 특정 음성 부분만 generation 해 내도록 하는 것이다..! 

일단 token_centres 를 이용하여 각 unaligned_feature가 실제 wav 에 어떻게 attention 해 내야 하는지 계산을 해 준다., 
설명이 잘 될지는 모르겠지만 다음과 같다 

output timestep과 token centre 사이의 diff matrix를 구해 준다. 

논문에서의 이 부분이다. 그리고 Softmax를 해 주면 각 token centre가 어디 output timestep에 어텐션을 해줘야하는지가 나온다.! 그리고 이 나온 값을 Unaligned feature와곱해준다면  의도한 output sequence 길이만큼의 벡터값들이 나온다!! (전체모델구조 참고) 

휴 다왔다... 이제 이 값들을 Gan-TTS Generator 에 넣어서 Adversarial Training 만 해주면 된다. 

GAN-TTS Generator 파라미터 파란 숫자는 무시..

GAN-TTS 구조와 파라미터는 다음과 같다. 휴.. 이제 generator 부분은 끝났다. Text 를 넣어서 Wav 가 나오는 것이다! 

Model - Discriminator 

두 가지 Discriminator를 사용한다. 
Random window Discriminator 와 Spectrogram Discriminator 이다. Random Window Discriminator는 GAN-TTS 에서 사용한 그대로이므로 설명을 생략한다. 
Spectrogram Discriminator는 generating 된 wav를 다시 mel 로 바꾸어서 진짜인지 아닌지 판별하는 discriminator이다. 

Loss 

세 가지 loss를 사용하였다. 

1. Spectrogram Prediction Loss 

    애초에 Generator 에서 나온 output이 time step에 따라 정렬되어 나온 output이 아니기 때문에 실제 음성 정보와 align이 다를 수 있다. 그래서 원래는 



이렇게 생긴 mel prediction loss 를 써야 하지만, (align이 확실하다고 가정할 때) 실제는 그렇지 않으므로.. 
Mel 을 이용한 Dynamic Time Warping 을 사용한다. 
Dynamic Time Warping은 time step이 다른 것들을 비교할 때 많이 쓰이는 건데.. 추후에 다뤄보도록 하겠다.. 

여튼 이 그림과 같이 align이 다른 S_gt 와 S_gen 을 가지고 최적의 align을 찾아 loss 를 계산한다. (근데 내 생각에는 실제로는 시작점과 끝점이 완전히 일치하지 않을것이라는 생각이다.. 그래서 이걸 쓰는것같긴한데 흠..)
Dynamic Programming을 사용한다면 최적의 경로를 찾아 그걸로만 backpropagation해도 되지만 논문에서 말하기는 이렇게 하면 최적화가 약간 느리다고 한다. 그래서 soft-dynamic warping을 사용한다고 한다. (이번에 ICLR version을 새로 공개하면서 코드를 붙여주었으니 고대로 구현하면 된다.) soft-dynamic warping은 이 논문에서 제시된 개념은 아니고 다른 논문에서 먼저 제시한 개념이다. 

이렇게 soft dynamic warping을 사용해서 spectrogram loss를 계산해 준다. 

2. Aligner Length Loss

Aligner 가 전체 text seq에 해당하는 길이를 제대로 예측할수 있도록 하는 loss 이다. 

 

Experiment Result 

다음은 EATS의 실험 결과이다. 각 Component 별로 Ablation Study를 잘 진행했다. 

하지만 Multi Speaker Dataset으로 나온 MOS가 다른 2 stage 모델에 비해서는 떨어지는 것으로 보아 현업에서 사용하기엔 무리가 있지 않나 싶기도 하다.. 
하지만 기존의 2-stage 모델의 한계를 지적하고 1-stage로 모델을 학습시킬 새로운 방법을 제시했다는 점에서 의미가 있는 것같다!! 

 

다음에 다른 논문도 리뷰해야지 ㅎㅎ 

반응형

'음성' 카테고리의 다른 글

Mel spectrogram 설명  (1) 2020.11.20
Comments