이번에 리뷰를 진행할 논문은 Parrtron 이라는 논문인데, Google에서 작성한 논문이다. 이전에 리뷰한 논문에서의 저자가 1저자로 작성한 논문이다. https://arxiv.org/abs/1904.04169 여기에서 확인할 수 있다.
Abstract & Intro
이 논문은 Translatotron처럼 중간 이산 표현(text)를 통하지 않고, 입력 spectrum을 다른 spectrum으로 직접 매핑하는 end-to-end 음성 변환 모델이다. 인코더, 스펙트로그램 및 음소 디코더로 구성 되며, 보코더가 음성으로 변환하는 역할을 맡는다. 악센트, 프로소디 및 배경 소음에 관계없이 모든 입력 스피커의 음성을 고정 된 억양과 일관된 조음 및 단일 표준 대상 스피커의 음성으로 정규화하도록 학습하였고, 또한 청각 스피커(목소리에 장애가 있는)로부터의 음성을 표준화하도록 조정하였으며, 이는 성공하고, 이를 통한 비정형 목소리를 음성 인식기를 통한 WER measurement및 무작위 대상으로부터의 Mean Opinion Score (MOS) 측정을 통해 제안한 모델이 명료도 및 자연스러움이 얼만큼 개선되었는지 보여주고 있다. 마지막으로 화자 8명에 대하여, 1명과 나머지 7명 중 1개를 합성하고, 잡음(배경음)을 합성한 뒤 분리하는 것을 보여준다.
Model Architecture
먼저 이 모델의 목적은, "소스 음성을 입력으로 넣고, 대상 음성을 출력으로 생성/합성하는 end-to-end Seq2Seq model architecture"를 사용하여 타겟 신호를 직접 생성하며 처음부터 합성하는 end-to-end architecture이다. 즉, 화자 특성을 포함하여 모든 비언어적 정보를 투사하고 누가, 어디서, 어떻게 말하는지가 아니라 말하는 내용만 유지 하는 것이다. 이렇기 위해서는 텍스트로부터 indenpendent한, N:1 음성 변환 작업에 해당한다. 다르게 말하면 화자는 여러명이고 target 음성의 화자는 1명이라는 것이다. 사용되는 데이터는 쌍을 이루는 입력/출력 음성 발화의 병렬 모음이 필요하다.
Google에서는 Parallel WaveNet 기반 TTS 시스템을 이용하여 손으로 작성한 script로 부터 TTS를 합성하여 target set을 만들었다. TTS를 사용함으로서 다음을 보장 할 수 있다고 밝히는데, 1) 대상은 항상 일관된 스피커 및 악센트로 말하고, 2) 배경 소음이나 불쾌감이 없으며, 3)마지막으로, 필요한 만큼의 데이터셋을 합성할 수 있다.
Google은 2천 2백만 건의 영어 음성으로 구성된 30,000시간의 교육 세트에서 모델을 훈련시켰다고 한다. 이 모음을 사용하여 TTS 시스템을 통한 여성 음성으로 대상 발화를 생성했다고 한다. (엄청난 데이터 양...)
이제부터 모델 아키텍처에 대해 설명한다.
전체 모델 아키텍처는 이렇다. Encoder, Spectrogram decoder, ASR decoder가 사용되고 있다.
1) Encoder
빨간색 표시한 부분이 Encoder이다. Encoder에서 소개드릴 내용이 많다. 우선 이 모델에서 사용되는 핵심 아키텍처는 최근의 Attention 기반 end-to-end ASR 모델(1: Listen, Attend and Spell, 2: Spectral and Prosodic Transformations of Hearing-impaired Mandarin Speech)과 Tacotron(1: Tacotron2-Natural TTS synthesis by conditioning WaveNet on mel spectrogram predictions, 2: Tacotron-Towards end-to-end speech synthesis) 과 같은 TTS 모델을 기반으로 한다.
Parrotron의 기본 encoder 구성은 "Sequence-to-sequence models can directly translate foreign speech" 와 유사하며, 일부 변형이 되어있다. 위에 언급한 reference 논문에 대해 간단히 모델 부분만 체크하겠다. (https://arxiv.org/abs/1703.08581)
Sequence-to-sequence models can directly translate foreign speech
- Encoder는 8개의 layer로 이루어져있으며, Time X 80 X 3 tensor형태이다.
- 그 후, 2개의 CNN + ReLU 활성화를 통과한다. 여기에서 각 CNN은 32kernels로 이루어진 3 X 3 X에서 time X frequency이다. 이 CNN의 stride 는 2 X 2를 사용하였고, 결국 total factor는 4로 downsampling된다.
- 이후 Batch Normalization을 적용한다. CNN~Batch까지 다른 관점에서 바라보면 vgg랑 비슷하다고 볼 수 있지 않을까?
- 그 후, single bidrectional convolution LSTM (CLSTM)을 통과하는데, 이 때 filter 가 1 X 3 으로 통과한다.
- 마지막으로, 각 방향당 256크기의 LSTM을 갖는 BiLSTM을 통과하는데, 이 후 512차원의 Dense를 통과한다. Dense를 통해 Linear Projection을 하고 (512), Batch normalization과 ReLU가 함께 쓰인다.
- 즉 최종 Encoder의 ouptut은 512-dimensonal의 인코더 representation으로 나온다.
1-2> Decoder
- 64-dimensional의 word embedding된 y_{k-1}가 입력인데, 즉 text가 입력이다. 음성~ -> 인식 이므로 음성이 text로 바뀜. 심볼은 이전 time-step을 이용하며, 512-dimensional의 attention context vector를 사용한다.
- 128개의 Single hidden layer (attention)으로 이루어져있다.
- 이것들이 256-dimensional의 4개의 Unidirectional LSTM으로 통과한다.
- 마지막의 attention context와 LSTM output은 concatenation되어 출력 단어(vocabulary)를 예측하는 softmax로 통과한다.
1-3> Etc
- 9.8 parameter들을 가지고 있다. 생각보다 크지 않다.
- 전형적인 Seq2Seq으로, Teacher forcing을 사용하여 batch 당 64개의 utterances들을 구성하여 사용하였다.
- Adam optimizer를 사용하여 10개의 copy에서 비동기적 확률론적 경사하강(Stochastic gradient descent)를 사용하였다.
- 초기 lr은 1e-3이고, 백만 step 지나고 factor of 10을 decay한다.
- Beam search를 사용하였으며, Beam width는 3이며, 어떤 Language Model도 사용하지 않았다.
여기까지가 "Sequence-to-sequence models can directly translate foreign speech"의 Model Architecture와 Experimental Setup 부분을 정리한 내용이다.
다시 Parrotron으로 돌아가서,
16 kHz의 샘플링 된 waveform으로부터, 125-7600 Hz의 범위에서 80-channel log mel-spectrogram을 추출하였으며, 이 때, Hanning 윈도우를 사용하였고, 50ms 프레임 길이, 12.5ms 프레임을 겹쳤고, 1024-STFT 계수를 사용하여 추출하였다. (기존 위에 ref 삼은 논문보다 겹치는것을 줄였다.)
- 입력: 80channel log mel-spectrogram
- 2-layers CNN with 32 kernels (ReLU + BatchNormalization)
- Convolutional Bidirectional LSTM with 1x3 filter ==> 이를 통해 time step 마다 주파수 축을 통해서 뭉친다.
- 256-dimensional 3-layers LSTM
- 512 Dense + ReLU + Batchnormalization
- Get representation of Encoder input
위의 6줄이 Encoder의 구성이다.
2) Decoder (Spectrogram decoder)
최대한 쉽게 설명하기 위해 구분하여 진행해보자면,
- 디코더 타겟은 1025-dim STFT이며, 왜 이러냐면... NFFT를 1024으로 사용하고 hop을 25%사용했기 때문에 1025차원의 Spectrum을 얻는다. 마지막 1은 허수부분이다.
- Autoregressive RNN을 사용하여 decoder를 구성하였으며, 1번의 decoder step당 1 frame씩 인코딩 된 입력 sequence에서 출력 spectrogram으로 predict를 진행한다.
- previous(이전의) decoder time step으로부터의 예측은 256 ReLU가 연결된 2개의 layer를 포함하는 Pre-Net을 통과하는 것이 attention이 잘 먹힌다고 밝혔다. ***여기에서 Pre-Net은 이전에 review한 논문에 자세하게 나와 있음***
- LSTM 출력과 attention context vector을 concatenation하여 Linear transformation을 통해 target spectrogram을 예측한다.
- 마지막으로, ***마찬가지로 이전 논문에서의 Post-Net이 나옴*** 5-layers CNN Post-Net을 통과한다. 각각의 Post-Net은 5 x 1 형태의 512 filter를 갖고, BatchNormalization도 있으며, tanh activation 을 통해 출력 된다.
- 예측된 spectrogram에서 오디오 신호를 합성하기 위해 Griffin-Lim 알고리즘을 사용하여 예측된 magnitude와 일치하는 phase를 추정한 뒤 Inverse Short-Time Fourier Transform을 통해 waveform으로 변환한다.
- Griffin-Lim은 음질이 그닥 좋지 않으므로, Griffin-Lim보다 시간이 오래걸리지만 음성의 품질이 더 좋은 WaveRNN을 사용하여 사람들에게 평가하였다.
3) ASR 디코더를 이용한 Multitask Learning
앞서 Translatotron에서의 구조와 비슷하다. 이 ASR decoder의 목적은 기본 언어의 높은 수준의 표현을 동시에 배우기 위해 인코더 네트워크에 multitask learning 기법을 사용하여 공동으로 train을 하면, encoder 의 음성의 feature를 더 잘 배운다고 한다. 쉽게 말해서, Signal-to-Signal로의 변환이 어렵기 때문에 중간에 ASR 디코더를 달아서 음성 부분을 학습시키는 것이다. 인코더 latent 표현에 따라 출력 음성의 (grapheme:자소 or phoneme:음소) transcript를 예측하는 역할을 맡는다. 이러한 multitask learning으로 train된 인코더는 기초적인 transcript에 대한 정보를 유지하는 입력의 latent 표현을 학습하는 것으로 생각할 수 있다.
ASR decoder의 input은 이전 step의 grapheme에 대한 64-dimensional 임베딩과(ASR decoder part) 512-dimensional의 attention context를 concatenate하여 생성된다. 그 후 256 unit LSTM layer로 전달 되고, 마지막으로 attention context와 LSTM의 output은 concatenated되어 softmax로 전달되고, 그 후 phoneme으로 출력 될 확률을 예측한다.
Application
1) 음성 정규화
이 실험은 임의의 speaker로 부터 사전 정의 된 target의 음성으로 변환된 음성을 정규화하는 목적이다. 임의의 speaker는, 크게 말해서 화자 별 특성에 따라 같은 script를 말해도 신호의 크기 및 세기, 잡음, 명료도가 다를 수 있다. Google은 input speaker가 누구든지 mapping된 target data로의 변환에 정규화하는 것을 목적으로 실험을 진행했다.
제안한 model의 output을 원래 입력 신호의 lingustic information을 보존하는지 평가하기 위해 음성 명료도를 측정하고(MOS), ASR 엔진을 사용하여 단어 오류율인 Word Error Rate (WER)를 평가했다. ASR 엔진은 WaveRNN으로 진행한 결과에 대해서만 평가했다.
보유한 음성의 ASR WER은 8.3%이라고 한다. 표에는 기입이 안되어 있다. 고품질 TTS 모델을 사용하여 ASR 엔진으로 평가하면 WER이 7.4%이라고 논문에 밝혀져있다.
위의 Table 1에서 맨 윗줄은 보조 ASR decoder가 없는 결과이다. 27.1%의 비교적 낮은 WER을 갖고 있다. 두번 째 줄인 Grapheme 단위로 예측하는 ASR decoder를 추가할 경우 19.9%로 약 7.3%가량 크게 개선되었다. Phoneme 단위로 ASR decoder를 달아도 맨 윗줄과는 확연한 차이가 보이는데, CLSTM 대신 decay(parameter 미세 조정)를 사용하며 LSTM을 2개 추가할 경우 가장 좋은 성능인 WER 17.6%을 달성하였다.
논문에서, CLSTM을 제거하여 매개 변수 수를 줄이면 성능이 크게 저하되며, 그러나 CLSTM 대신 2개의 additonal BLSTM을 사용하면 약간 향상 되는 동시에 모델이 단순화 된다고 한다. 이것이 마지막 줄의 결과이다.
위의 Table 2는 강력한 악센트와 배경 소음이 포함 된 보다 까다로운 테스트 셋에 대한 MOS 평가 와 ASR 성능의 평가 결과이다. 이 표에서 WER이 높은것이 중요한 것이 아니라, Real speech와 Parrotron의 결과에 대한 WER의 큰 차이가 없음으로 인해 언어적 내용을 보존하고 있음을 확인한다. 라고 주장하고있다.
정규화 실험 중 마지막으로, 모델이 동일한 TTS 음성으로 정규화 된 음성을 지속적으로 생성하는지에 대한 증명을 Table3을 통해 하였다. 8명의 영어 native들에게 발화된 model output 음성을 주고 평가하였는데, 질문 내용은
- 출력 음성에서 불일치가 포함되어 있는가? (Transcript와 다른지에 대한 평가인듯) ==> 0%
- 출력 언어는 일관된 조음, 표준 억양 및 prosody를 사용하는가? ==> 83.3%
위와 같고, 위와 같은 결과를 얻었다.
2) 청각 장애 연설의 정규화
위의 정규화 모델을 사용하여 standard 음성이 아닌, 청각 장애인을 유창한 음성으로 변환 할 수 있는지에 대한 실험이다. 이를 통해 사람들의 음성을 통한 의사 소통을 향상시키는 것으로 사용 될 수 있다고 주장한다.
실험은 러시아에서 태어난 10대에 청각장애를 소유한채로 영어를 배운 사람을 대상으로 진행된다.
요약하면, 표준어로부터 학습된 Parrotron 은 위의 청각 장애 언어르 완전히 정규화 하지 못하지만, 이를 해결 하기 위해 Google은 모든 매개 변수를 조정하고 모든 파라미터를 청각 장애 음성으로 적응(인코더만 미세 조정, 디코더와 음소 디코더 파라미터 모두 hold)시켰다. 그리하여 아래 표와 같은 결과를 얻었다.
첫째 줄의 Real speech는 청각 장애의 언어이며, MOS가 매우 낮고, 위에서 사용된 ASR 엔진의 WER은 89.2이다. 즉 워드 단위로 100개가 입력 될경우 10.8개 밖에 못맞춘 것이다.
두번째 줄의 Parrotron (male)은 위의 정규화 시스템을 그대로 사용했을 경우, MOS는 조금 나아지지만 마찬가지로 음성 인식 결과는 더 나빠졌다.
2)에서 제안한 fine tuning 기법(적응)으로 뽑은 결과는 MOS에서 1점 가량의 상승을 보였으며, WER도 일반인과 같은 성능을 내고 있다. 놀라운 결과라고 볼 수 있다.
위의 실험을 통해, 모든 fine tuning 전략이 음성을 이해하기 쉽고 훨씬 더 자연스러운 speech로 이어진다는 것을 밝혀냈다. 최고의 fine tuning은 모든 매개 변수를 조정하는 것이라고 논문에서 언급하고있다.
3) 음성 분리
이 실험은 Parrotron이 다양한 음성 응용에 사용될 수 있음을 설명하기 위해 만든 섹션이라고 한다. 중첩 된 혼합 음성 중 magnitude가 가장 큰 스피커의 신호를 재구성하는 음성 분리 작업이다. 최대 8개의 서로 다른 스피커의 즉각적인 혼합과 잡음(배경)을 넣어서 평가한다.
이 섹션은 SOTA separation 모델을 말하는 것이 아니다. 일반화 된 개방형 speaker set으로부터 speech를 생성할 수 있는지 평가하기 위해 분리를 진행한다고 한다. 솔직히 잘 이해가 안된다.
Google은 음성 신호의 즉각적인 혼합을 인위적으로 진행하기 위해, train set의 각 target 음성에 대해 1~7개의 발화를 무작위로 선택하여 배경 소음으로 혼합하였다. 백그라운드 발화 수도 임의로 선택된다. 혼합하기 전, 모든 발화를 비슷한 세기로 정규화한다.
배경에 대해 무작위로 샘플링 된 가중치 w(0.1~0.5)와 목표 발화에 대한 1-w로 두 신호를 평균화하여 target 발화와 배경 잡음을 혼합하였다. 이로 인해 인위적으로 구성된 모든 발화에서 12.15dB의 평균 SNR이 발생하고, 이에 대한 표준 편차는 4.7이라고 한다.
Parrotron이 분리 작업을 할 수 있는지 평가하기 위해 위의 정규화 파트 부분의 학습된 것을 가져와서 혼합 발화를 입력으로 넣고 모델을 훈련 시켜 해당하는 원래의 발화를 생성하는 식으로 진행하였다고 한다. 이를 ASR 엔진을 사용하여 분리 모델의 영향을 평가하였는데, 1) 배경 잡음을 넣기 전의 원래 깨끗한 발화, 2) 시끄러운 잡음인 배경 음과 음성을 혼합 한 후, 3) Parrotron을 실행하여 생성된 출력
형식으로 평가하였다고 한다.
위의 표를 보면, 잡음이 많은 세트인 "Noisy"에서의 WER은 33.2%이다. 이를 Parrotron을 사용하여(분리) denoising하면 17.3%의 WER를 얻었다고 한다. (음 근데 사실 이부분은 real time demonstration을 좀 보고 싶다.) 이를 통해 모델이 target speaker의 음성을 보존하고 다른 스피커로부터 분리 할 수 있음을 보여준다고 한다.
Conclusion
위 논문에선 이전 논문처럼 End-to-End 로 음성 변환을 진행하였는데, 필수적으로 script가 필요하지 않았다. 하지만 signal-to-signal 간의 음성 변환을 위하여 multitask learning 관점에서 음성 인식 디코더를 auxiliary part로 사용하였고, 이는 성능 개선에 도움이 되었다고 밝혔다. 위 논문의 목적은 "언어적 콘텐츠를 보존하고, 다른 스피커의 음성을 단일 대상 스피커의 음성으로 표준화하는 것" 이다. 청각 장애인의 음성이 model을 거쳐 단일 대상 스피커 (TTS)로 나올 경우, 제안 된 모델의 방법이 WER 개선에 영향을 미쳤다고 밝혔다. 또한 악센트가 많은 음성을 정규화를 통하여 표준 음성으로 변환하여 명료성을 향상시켰다. 추후 input 화자의 특성을 보존하며 정규화 및 개선하는 것을 목표로 두고 있다.
가장 큰 novelty는 N:1의 음성 변환을 정규화로 시키면서 좋은 성능을 내고 있다는 점, ASR 엔진을 이용한 명확한 평가를 하였다는 점, 그리고 청각 장애인 데이터셋으로부터 fine tuning을 통해 성능 개선을 했다는 점이라고 뽑고 싶다.
추가적으로, Google의 ASR엔진을 쉽게 사용할 수 있도록 API나 weight를 공유해주면 연구용으로 비교 및 사용하기 편할텐데... 아쉽다.
이번에 진행할 논문은 Translatotron: Direct speech-to-speech translation with a sequence-to-sequence model 이다. Google Research에서 published 하였으며, https://arxiv.org/abs/1904.06037 에서 확인해볼 수 있다.
(그림을 업로드하다가 중간에 렉이먹어서 도중에 쓴 것들이 다 날아갔다... 다시 씁니다ㅜ.ㅜ)
Intro, Related Work
한국인과 미국인이 서로 대화를 나누려 한다. 하지만 이 둘은 서로간의 언어 정보에 대해 하나도 모른다고 가정하겠다. 즉 한국인은 영어를 하나도 할 줄 모르고, 미국인도 마찬가지라고 가정한다.
한국인이 미국인에게 대화를 걸려면 1) 말을 하고, 2)이를 음성 인식하여, 3)인식 된 text를 번역하고, 4)얻은 text를 음성으로 합성해야 대화가 가능하다. 너무나 복잡하다. 사실 우리는 이 방법을 사용하여 외국인들과 대화를 하고 있지 않은가? 그만큼 우리는 대단한 존재일 수 있다.
어찌됐든, 위의 복잡한 단계를 해결할 중요한 이유가 하나 더 있다. 바로 중간에서 잘못 된 오류를 범할 경우, 마지막으로 음성 합성 된 결과값이 미국인에게 잘못 전달 될 수 있다. 예를 들어,
한국인: 제가 저기에 가려면 어떻게 해야하나요?
1) 음성 인식: 제가 저기에 가려면 어떻게 해야하나요?
2) 번역: How can I get there?
3) 음성 합성: ~~
4) 미국인에게 전달
이렇게 잘 전달되면 문제가 없다. 하지만 가령,
2) 번역: Can I go there?
라고 번역이 잘못 되었을 경우, 미국인은 yes라 할 것이고, 한국인은 pardon?을 외칠 것이다.
이러한 복잡한 문제를 해결한 연구 결과가 있다. 바로 지금 review할 논문인데, translatotron이다. 내가 생각한 이 논문의 novelty는 서로 다른 언어간의 음성 번역을 한 번에 진행 한 시발점(starting research) 이라고 생각한다.
딥러닝은 많은 발전이 이루어 졌고, 음성 인식, 번역, 음성 합성에서 굉장한 성능을 내고 있다. 하지만 위의 사례처럼 이용하려면 음성 인식 -> 번역 -> 합성이 이루어져야 한다. 하지만 이 논문은 이것을 한 번에 다 해내고 있다. 심지어 목소리까지 바꿀 수 있다.
이 논문의 실험은 스페인어-영어 데이터셋으로 진행하였다.
자 그럼 이 논문에 대해 알아 가 봅시다.
Method
모델 구조는 별도로 훈련 된 여러 구성 요소로 구성된다. 즉 이 모델은 end-to-end가 아니고, pretrain된 것들도 사용한다.
전체 모델 아키텍처는 아래 그림과 같다.
- Primary task: target spectrogram을 생성하는 attention based Seq2Seq
- Speaker Encoder: 이 부분은 선택적으로 쓰이는 부분인데, 대상의 목소리를 원할 경우 바꿔서 번역할 수 있음
- Vocoder: target spectrogram을 시간도메인인 waveform형태로(음성) 바꾸는 부분
- Auxiliary tasks: source와 target의 phoneme sequence 예측 하는 부분
차례차례 보도록 해봅시다.
- Primary task, Encoder
먼저 Translatotron의 Encoder부분은 아래 빨간 표시한 곳이다.
Encoder의 input은 80channel log-mel-spectrogram이 사용된다. 여기에서 80channel은 mel을 80 dimension으로 뽑았단 얘기이다. librosa의 n_mels와 같다. 그렇게 뽑은 mel-spectrogram에 log값을 취해준 것이다.
그 다음 8-layer Stacked Bidrectional LSTM을 사용한 것으로 나와있다. BLSTM을 8개 쌓은것이다. 이 최종 output은 speaker encoder를 사용했을 경우 i-vector와 concat하여 decoder ouptut과 transformer 에서 사용되는 multi-head attention을 통해 encoder-decoder간 scaled-dot product attention이 이루어진다. speaker encoder가 사용되지 않았을 경우엔 저 concat 부분은 사용되지 않는다. 중간에 Auxiliary recogniton tasks로 가는 화살표가 있는데 이것은 마지막에 설명할 예정
- Primary task, Decoder
Spectrogram Decoder는 Text-to-Speech에서 좋은 평가를 받고 있는, 마찬가지로 구글에서 published 한 Tacotron2 구조와 유사하다. Pre-net, Autoregressive LSTM, Post-net 을 포함하고 있다. 여기에서,
Pre-net: 2-layer FC
Autoregressive LSTM: Bi-directiona LSTM(Tacotron2 encoder), LSTM(2-Uni-directional layers in Tacotron2 decoder)
Post-net: 5-layer CNN with residual connections 으로서, mel scaled filter bank spectrogram을 개선하는 역할
로 정의할 수 있다.
Tacotron2의 Encoder는 text를 character 단위의 embedding을 하고, Pre-net을 거친다. Pre-net은 정확히,
Pre-net: (Dense -> ReLu -> Dropout) X 2
로 이루어져 있다. 그 후, CBHG를 통과하는데,
CBHG: Conv1D Bank -> Max pooling -> Conv1D projection -> Conv1D layer, Conv1D layer + First input(이 부분이 residual connections) -> Highway Network(4 layers of Dense - ReLu) -> Bidirectional RNN
으로 이루어져 있다. Encoder의 최종 결과는 embedding result가 나온다. 위의 모든 process를 mel-spectrogram과 최적의 alignment를 하기 위해서라고 생각하면 좋을 것 같다. 너무 복잡하지만..
Tacotron2의 Decoder는 Encoder와 거의 비슷한데, RNN의 구조를 띄고 있다. 즉 각 step마다 output (1개의 spectrogram frame)을 출력한다. 그리고 autoregressive 한 형태이므로, 이전 step을 사용하여 현재 step의 frame을 예측한다. 매 step마다 encoder의 context vector와 attention을 통해 output 1개를 얻는 것이다. 최종적으로 projection 하는 decoder의 hidden layer의 output은 2개의 256으로 이루어진 FC가 를 가장 먼저 통과한다. 즉 Dense 형태로, (256, 1)의 형태를 얻는다.
그 후 LSTM과 이루어진 context vector는 80개의 neurons을 갖는 FC로 통과한다. 이게 바로 mel-spectrogram의 channel인 n_mel와 동일하다! 이 최종 부분이 spectrogram을 frame by frame으로 예측한다. 그 후 PreNet으로 다시 들어가고, linear scale spectrogram을 얻는다. 아마 Reduction Factor 를 3으로 사용했던 것 같은데, 그렇다면 3개의 predicted spectrogram을 사용하여 1개의 linear scale spectrogram을 얻는다.
Attention 부분에 대해 조금 더 언급하고 싶어서 수식을 캡쳐했다. context vector Ci 는 encoder의 output(h)와 attention weights(alpha)의 product로 연산되는데, alpha_ij는 아래의 식에 의해 연산된다.
자 많이 보던거 나왔죠? exp/exp합은 softmax이다. 대충 눈 도장 익히면 아~확률 구하는구나 라고 생각하면 편하다.
e_ij = vaT tanh(Wsi-1 + Vhj + Ufi,j + b) 인데,
si-1: decoder LSTM의 이전 hidden state
alpha_i-1 : 이전 attention weight
hj: j번째 encoder hidden state
W, V, U, va 그리고 b: 학습시 사용된 parameter들
fi, j: 공식에 의한 계산된 위치
fi: F * alpha_i -1
F: CNN 연산
이다.. 복잡한데, 제 설명이 부족하시다 생각하신 분께서는 Tacotron2 와 attentive-attention mechanism을 읽어보시는 것을 추천드립니다.
정리하면, Tacotron2는 encoder에서 text를 입력받아 Pre-net, CBHG를 통해 character embedding result를 얻고, decoder에서는 80channel의 log-mel-spectrogram 입력을 받아 encoder-decoder attention을 통해, 256 dense로 1개를 예측하고, 다시 그 값이 80 dense를 통해 80channel의 spectrogram이 통과한다. 이 때, frame by frame, autoregressive방식으로 진행되고, 위의 output은 Post-Net을 통과하여 linear scale spectrogram으로 얻는다. 이게 다 ~~끝나고 vocoder를 통해 음성이 복원된다.
Translatotron도 유사하다. 이 떄, 4 or 6 LSTM이 성능이 좋다고 언급되어있다.
Translatotron의 decoder에서의 예측은 1025dimension의 log spectrogram의 frame을 예측한다. 각 디코딩 단계마다 2개의 스펙트로그램 frame을 예측한다. 이렇게 사용하는 이유는 음성 신호가 연속성을 띄고 있기 때문이고, decoder의 부하를 줄여주기 때문이다.
쓰고 보니 Tacotron2 리뷰를 따로 안해도 될듯 ㅎㅎ
- Vocoder
아래 부분의 빨간색 표시가 Vocoder 부분이다.
Griffin-Lim을 기본적으로 사용하였다. 하지만 MOS테스트에서 음성의 자연성을 평가할 때 WaveRNN을 사용하였다. Griffin-Lim은 CPU로 돌릴 수 있지만, WaveRNN은 시간이 더 오래걸리고, GPU 기반이다. 그러나 음성 복원 품질은 WaveRNN이 더 좋기때문에 이를 이용하여 테스트한 듯.
또한 Vocoder에서 reduction factor를 사용하여 감소 계수 2로 frame을 계수만큼 예측한다. 이 계수만큼의 log-spectrogram을 생성한다. 이렇게 복원된 음성을 직접 들을 수 있다.
- Speaker Encoder
이 부분에서는, D-벡터를 뽑아서 사용한다. D-벡터는 Speaker Independent System의 특징인데, 화자별 차이의 벡터값 정도로 생각하면 될 것 같다. Speaker vefirication를 위한 pretrained 된 것을 여기에서 사용한다.
위 논문에서, 851k 스피커, 8개 언어에 대한 정보를 사용했다고 밝혔다. training 시에는 사용하지 않았고, inference에 합성할때에만 썼다고 한다. 256차원으로 값이 나오면, 이를 linear projection을 통해 16으로 줄이고, 마지막 encoder BLSTM layer의 output과 concat했다고 밝혔다. 오직 tanfer task에만 사용되었다.
- Auxiliary recognition tasks
각각 attention이 구성되어 있는 소스 및 대상 음소 시퀀스를 예측하는 파트이다. BLSTM의 마지막 값이 여기에 연결된다. single head attention이 있는 2 layer LSTM으로 구성되어 있다. 여기에서, 한 입력은 소스 음소 (스페인어) 이고 다른 입력은 대상 (영어) 이다. signal-to-signal의 변환이 모델에서 버거워 하기 때문에, multitask learning 기법을 사용하여 음성을 phoneme 단위로 인식하여, 이 정보를 decoder 단에 전해주어 음성 변환을 도운 task라고 보면 되겠다. 3개의 loss가 사용되었다. 이 task를 통해 BLEU score가 개선되었다고 밝히고 있다.
- training하는 동안 speech transcripts를 사용하는것이 중요하다고 함
아마도 signal-to-signal간의 학습이 잘 안되었기 때문이지 않았을까 싶다.
내 느낌점은, 처음 이 논문이 나오고, demonstration을 들어보고나서 정말 놀라웠었다. 작년 5월에 처음 봤는데 나는 무슨 연구를 하고 있는 건가 싶었다. 새로운 길을 개척한 논문이라고 평가하고 싶다. 벌써 8개월 정도가 지났는데 어떤 성능을 갖는 end-to-end 가 나올지 기대된다.
Duplicate plugins for name projector 라는 에러가 생기면서 안된다.
3. 완전 삭제가 안됨
tensorflow-gpu, tensorboard 를 지우고, 재설치하여도 안될때가 있다.
** 해결법
아래의 코드를 그대로 복사하여 하나의 .py 로 만들고, 실행하면 다음과 같은 step을 알려준다.
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Self-diagnosis script for TensorBoard.
Instructions: Save this script to your local machine, then execute it in
the same environment (virtualenv, Conda, etc.) from which you normally
run TensorBoard. Read the output and follow the directions.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# This script may only depend on the Python standard library. It is not
# built with Bazel and should not assume any third-party dependencies.
import collections
import errno
import functools
import hashlib
import inspect
import logging
import os
import pipes
import shlex
import socket
import subprocess
import sys
import tempfile
import textwrap
import traceback
# A *check* is a function (of no arguments) that performs a diagnostic,
# writes log messages, and optionally yields suggestions. Each check
# runs in isolation; exceptions will be caught and reported.
CHECKS = []
# A suggestion to the end user.
# headline (str): A short description, like "Turn it off and on
# again". Should be imperative with no trailing punctuation. May
# contain inline Markdown.
# description (str): A full enumeration of the steps that the user
# should take to accept the suggestion. Within this string, prose
# should be formatted with `reflow`. May contain Markdown.
Suggestion = collections.namedtuple("Suggestion", ("headline", "description"))
def check(fn):
"""Decorator to register a function as a check.
Checks are run in the order in which they are registered.
Args:
fn: A function that takes no arguments and either returns `None` or
returns a generator of `Suggestion`s. (The ability to return
`None` is to work around the awkwardness of defining empty
generator functions in Python.)
Returns:
A wrapped version of `fn` that returns a generator of `Suggestion`s.
"""
@functools.wraps(fn)
def wrapper():
result = fn()
return iter(()) if result is None else result
CHECKS.append(wrapper)
return wrapper
def reflow(paragraph):
return textwrap.fill(textwrap.dedent(paragraph).strip())
def pip(args):
"""Invoke command-line Pip with the specified args.
Returns:
A bytestring containing the output of Pip.
"""
# Suppress the Python 2.7 deprecation warning.
PYTHONWARNINGS_KEY = "PYTHONWARNINGS"
old_pythonwarnings = os.environ.get(PYTHONWARNINGS_KEY)
new_pythonwarnings = "%s%s" % (
"ignore:DEPRECATION",
",%s" % old_pythonwarnings if old_pythonwarnings else "",
)
command = [sys.executable, "-m", "pip", "--disable-pip-version-check"]
command.extend(args)
try:
os.environ[PYTHONWARNINGS_KEY] = new_pythonwarnings
return subprocess.check_output(command)
finally:
if old_pythonwarnings is None:
del os.environ[PYTHONWARNINGS_KEY]
else:
os.environ[PYTHONWARNINGS_KEY] = old_pythonwarnings
def which(name):
"""Return the path to a binary, or `None` if it's not on the path.
Returns:
A bytestring.
"""
binary = "where" if os.name == "nt" else "which"
try:
return subprocess.check_output([binary, name])
except subprocess.CalledProcessError:
return None
def sgetattr(attr, default):
"""Get an attribute off the `socket` module, or use a default."""
sentinel = object()
result = getattr(socket, attr, sentinel)
if result is sentinel:
print("socket.%s does not exist" % attr)
return default
else:
print("socket.%s = %r" % (attr, result))
return result
@check
def autoidentify():
"""Print the Git hash of this version of `diagnose_tensorboard.py`.
Given this hash, use `git cat-file blob HASH` to recover the relevant
version of the script.
"""
module = sys.modules[__name__]
try:
source = inspect.getsource(module).encode("utf-8")
except TypeError:
logging.info("diagnose_tensorboard.py source unavailable")
else:
# Git inserts a length-prefix before hashing; cf. `git-hash-object`.
blob = b"blob %d\0%s" % (len(source), source)
hash = hashlib.sha1(blob).hexdigest()
logging.info("diagnose_tensorboard.py version %s", hash)
@check
def general():
logging.info("sys.version_info: %s", sys.version_info)
logging.info("os.name: %s", os.name)
na = type("N/A", (object,), {"__repr__": lambda self: "N/A"})
logging.info("os.uname(): %r", getattr(os, "uname", na)(),)
logging.info(
"sys.getwindowsversion(): %r",
getattr(sys, "getwindowsversion", na)(),
)
@check
def package_management():
conda_meta = os.path.join(sys.prefix, "conda-meta")
logging.info("has conda-meta: %s", os.path.exists(conda_meta))
logging.info("$VIRTUAL_ENV: %r", os.environ.get("VIRTUAL_ENV"))
@check
def installed_packages():
freeze = pip(["freeze", "--all"]).decode("utf-8").splitlines()
packages = {line.split(u"==")[0]: line for line in freeze}
packages_set = frozenset(packages)
# For each of the following families, expect exactly one package to be
# installed.
expect_unique = [
frozenset([
u"tensorboard",
u"tb-nightly",
u"tensorflow-tensorboard",
]),
frozenset([
u"tensorflow",
u"tensorflow-gpu",
u"tf-nightly",
u"tf-nightly-2.0-preview",
u"tf-nightly-gpu",
u"tf-nightly-gpu-2.0-preview",
]),
frozenset([
u"tensorflow-estimator",
u"tensorflow-estimator-2.0-preview",
u"tf-estimator-nightly",
]),
]
found_conflict = False
for family in expect_unique:
actual = family & packages_set
for package in actual:
logging.info("installed: %s", packages[package])
if len(actual) == 0:
logging.warning("no installation among: %s", sorted(family))
elif len(actual) > 1:
logging.warning("conflicting installations: %s", sorted(actual))
found_conflict = True
if found_conflict:
preamble = reflow(
"""
Conflicting package installations found. Depending on the order
of installations and uninstallations, behavior may be undefined.
Please uninstall ALL versions of TensorFlow and TensorBoard,
then reinstall ONLY the desired version of TensorFlow, which
will transitively pull in the proper version of TensorBoard. (If
you use TensorBoard without TensorFlow, just reinstall the
appropriate version of TensorBoard directly.)
"""
)
packages_to_uninstall = sorted(
frozenset().union(*expect_unique) & packages_set
)
commands = [
"pip uninstall %s" % " ".join(packages_to_uninstall),
"pip install tensorflow # or `tensorflow-gpu`, or `tf-nightly`, ...",
]
message = "%s\n\nNamely:\n\n%s" % (
preamble,
"\n".join("\t%s" % c for c in commands),
)
yield Suggestion("Fix conflicting installations", message)
@check
def tensorboard_python_version():
from tensorboard import version
logging.info("tensorboard.version.VERSION: %r", version.VERSION)
@check
def tensorflow_python_version():
import tensorflow as tf
logging.info("tensorflow.__version__: %r", tf.__version__)
logging.info("tensorflow.__git_version__: %r", tf.__git_version__)
@check
def tensorboard_binary_path():
logging.info("which tensorboard: %r", which("tensorboard"))
@check
def addrinfos():
sgetattr("has_ipv6", None)
family = sgetattr("AF_UNSPEC", 0)
socktype = sgetattr("SOCK_STREAM", 0)
protocol = 0
flags_loopback = sgetattr("AI_ADDRCONFIG", 0)
flags_wildcard = sgetattr("AI_PASSIVE", 0)
hints_loopback = (family, socktype, protocol, flags_loopback)
infos_loopback = socket.getaddrinfo(None, 0, *hints_loopback)
print("Loopback flags: %r" % (flags_loopback,))
print("Loopback infos: %r" % (infos_loopback,))
hints_wildcard = (family, socktype, protocol, flags_wildcard)
infos_wildcard = socket.getaddrinfo(None, 0, *hints_wildcard)
print("Wildcard flags: %r" % (flags_wildcard,))
print("Wildcard infos: %r" % (infos_wildcard,))
@check
def readable_fqdn():
# May raise `UnicodeDecodeError` for non-ASCII hostnames:
# https://github.com/tensorflow/tensorboard/issues/682
try:
logging.info("socket.getfqdn(): %r", socket.getfqdn())
except UnicodeDecodeError as e:
try:
binary_hostname = subprocess.check_output(["hostname"]).strip()
except subprocess.CalledProcessError:
binary_hostname = b"<unavailable>"
is_non_ascii = not all(
0x20 <= (ord(c) if not isinstance(c, int) else c) <= 0x7E # Python 2
for c in binary_hostname
)
if is_non_ascii:
message = reflow(
"""
Your computer's hostname, %r, contains bytes outside of the
printable ASCII range. Some versions of Python have trouble
working with such names (https://bugs.python.org/issue26227).
Consider changing to a hostname that only contains printable
ASCII bytes.
""" % (binary_hostname,)
)
yield Suggestion("Use an ASCII hostname", message)
else:
message = reflow(
"""
Python can't read your computer's hostname, %r. This can occur
if the hostname contains non-ASCII bytes
(https://bugs.python.org/issue26227). Consider changing your
hostname, rebooting your machine, and rerunning this diagnosis
script to see if the problem is resolved.
""" % (binary_hostname,)
)
yield Suggestion("Use a simpler hostname", message)
raise e
@check
def stat_tensorboardinfo():
# We don't use `manager._get_info_dir`, because (a) that requires
# TensorBoard, and (b) that creates the directory if it doesn't exist.
path = os.path.join(tempfile.gettempdir(), ".tensorboard-info")
logging.info("directory: %s", path)
try:
stat_result = os.stat(path)
except OSError as e:
if e.errno == errno.ENOENT:
# No problem; this is just fine.
logging.info(".tensorboard-info directory does not exist")
return
else:
raise
logging.info("os.stat(...): %r", stat_result)
logging.info("mode: 0o%o", stat_result.st_mode)
if stat_result.st_mode & 0o777 != 0o777:
preamble = reflow(
"""
The ".tensorboard-info" directory was created by an old version
of TensorBoard, and its permissions are not set correctly; see
issue #2010. Change that directory to be world-accessible (may
require superuser privilege):
"""
)
# This error should only appear on Unices, so it's okay to use
# Unix-specific utilities and shell syntax.
quote = getattr(shlex, "quote", None) or pipes.quote # Python <3.3
command = "chmod 777 %s" % quote(path)
message = "%s\n\n\t%s" % (preamble, command)
yield Suggestion("Fix permissions on \"%s\"" % path, message)
@check
def source_trees_without_genfiles():
roots = list(sys.path)
if "" not in roots:
# Catch problems that would occur in a Python interactive shell
# (where `""` is prepended to `sys.path`) but not when
# `diagnose_tensorboard.py` is run as a standalone script.
roots.insert(0, "")
def has_tensorboard(root):
return os.path.isfile(os.path.join(root, "tensorboard", "__init__.py"))
def has_genfiles(root):
sample_genfile = os.path.join("compat", "proto", "summary_pb2.py")
return os.path.isfile(os.path.join(root, "tensorboard", sample_genfile))
def is_bad(root):
return has_tensorboard(root) and not has_genfiles(root)
tensorboard_roots = [root for root in roots if has_tensorboard(root)]
bad_roots = [root for root in roots if is_bad(root)]
logging.info(
"tensorboard_roots (%d): %r; bad_roots (%d): %r",
len(tensorboard_roots),
tensorboard_roots,
len(bad_roots),
bad_roots,
)
if bad_roots:
if bad_roots == [""]:
message = reflow(
"""
Your current directory contains a `tensorboard` Python package
that does not include generated files. This can happen if your
current directory includes the TensorBoard source tree (e.g.,
you are in the TensorBoard Git repository). Consider changing
to a different directory.
"""
)
else:
preamble = reflow(
"""
Your Python path contains a `tensorboard` package that does
not include generated files. This can happen if your current
directory includes the TensorBoard source tree (e.g., you are
in the TensorBoard Git repository). The following directories
from your Python path may be problematic:
"""
)
roots = []
realpaths_seen = set()
for root in bad_roots:
label = repr(root) if root else "current directory"
realpath = os.path.realpath(root)
if realpath in realpaths_seen:
# virtualenvs on Ubuntu install to both `lib` and `local/lib`;
# explicitly call out such duplicates to avoid confusion.
label += " (duplicate underlying directory)"
realpaths_seen.add(realpath)
roots.append(label)
message = "%s\n\n%s" % (preamble, "\n".join(" - %s" % s for s in roots))
yield Suggestion("Avoid `tensorboard` packages without genfiles", message)
# Prefer to include this check last, as its output is long.
@check
def full_pip_freeze():
logging.info("pip freeze --all:\n%s", pip(["freeze", "--all"]).decode("utf-8"))
def set_up_logging():
# Manually install handlers to prevent TensorFlow from stomping the
# default configuration if it's imported:
# https://github.com/tensorflow/tensorflow/issues/28147
logger = logging.getLogger()
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
def main():
set_up_logging()
print("### Diagnostics")
print()
print("<details>")
print("<summary>Diagnostics output</summary>")
print()
markdown_code_fence = "``````" # seems likely to be sufficient
print(markdown_code_fence)
suggestions = []
for (i, check) in enumerate(CHECKS):
if i > 0:
print()
print("--- check: %s" % check.__name__)
try:
suggestions.extend(check())
except Exception:
traceback.print_exc(file=sys.stdout)
pass
print(markdown_code_fence)
print()
print("</details>")
for suggestion in suggestions:
print()
print("### Suggestion: %s" % suggestion.headline)
print()
print(suggestion.description)
print()
print("### Next steps")
print()
if suggestions:
print(reflow(
"""
Please try each suggestion enumerated above to determine whether
it solves your problem. If none of these suggestions works,
please copy ALL of the above output, including the lines
containing only backticks, into your GitHub issue or comment. Be
sure to redact any sensitive information.
"""
))
else:
print(reflow(
"""
No action items identified. Please copy ALL of the above output,
including the lines containing only backticks, into your GitHub
issue or comment. Be sure to redact any sensitive information.
"""
))
if __name__ == "__main__":
main()
최근 tensorflow 2.0이 release 된 이후로, cuda 설치가 매우 간단해졌다.
(저는 cuda+cudnn 설치하는 시간 5분 걸렸습니다.)
Ubuntu 16.04 (CUDA 10) + cudnn + driver 설치
# Add NVIDIA package repositories
# Add HTTPS support for apt-key
sudo apt-get install gnupg-curl
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_10.0.130-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1604_10.0.130-1_amd64.deb
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
sudo apt-get update
wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64/nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt install ./nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt-get update
# Install NVIDIA driver
# Issue with driver install requires creating /usr/lib/nvidia
sudo mkdir /usr/lib/nvidia
sudo apt-get install --no-install-recommends nvidia-driver-418
# Reboot. Check that GPUs are visible using the command: nvidia-smi
# Install development and runtime libraries (~4GB)
sudo apt-get install --no-install-recommends \
cuda-10-0 \
libcudnn7=7.6.2.24-1+cuda10.0 \
libcudnn7-dev=7.6.2.24-1+cuda10.0
# Install TensorRT. Requires that libcudnn7 is installed above.
sudo apt-get install -y --no-install-recommends libnvinfer5=5.1.5-1+cuda10.0 \
libnvinfer-dev=5.1.5-1+cuda10.0
Ubuntu 16.04 (CUDA 9.0 for TensorFlow < 1.13.0) + cudnn + driver 설치
# Add NVIDIA package repository
sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_9.1.85-1_amd64.deb
sudo apt install ./cuda-repo-ubuntu1604_9.1.85-1_amd64.deb
wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64/nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt install ./nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt update
# Install the NVIDIA driver
# Issue with driver install requires creating /usr/lib/nvidia
sudo mkdir /usr/lib/nvidia
sudo apt-get install --no-install-recommends nvidia-410
# Reboot. Check that GPUs are visible using the command: nvidia-smi
# Install CUDA and tools. Include optional NCCL 2.x
sudo apt install cuda9.0 cuda-cublas-9-0 cuda-cufft-9-0 cuda-curand-9-0 \
cuda-cusolver-9-0 cuda-cusparse-9-0 libcudnn7=7.2.1.38-1+cuda9.0 \
libnccl2=2.2.13-1+cuda9.0 cuda-command-line-tools-9-0
# Optional: Install the TensorRT runtime (must be after CUDA install)
sudo apt update
sudo apt install libnvinfer4=4.1.2-1+cuda9.0
위에는 cuda-10.0, 아래는 cuda-9.0 인데, 2개 모두 동시 설치 가능하다.
본인 계정 -> basrhc 에 들어가서 아래와 같이 사용할 버전을 typing 한다.
vi ~/.bashrc
export PATH=/mnt/junewoo/bin:/mnt/junewoo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# Path for cuda-9.0
export PATH=$PATH:/usr/local/cuda-9.0/bin
# Path for cuda-10.0
#export PATH=$PATH:/usr/local/cuda-10.0/bin
# Path for cudnn with cuda-9.0
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/junewoo/utils/cuda-9.0/lib64
# Path for cudnn with cuda-10.0
export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:/mnt/junewoo/utils/cuda-10.0/lib64
만약 특정 폴더에 cudnn이 있을 경우(하지만 위의 명령어대로 설치할 경우 usr/local/cuda-n.m에 설치된다.
# Path for cudnn with cuda-10.0
export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:/where/your_dir/cuda-10.0/lib64