본문 바로가기
Personal Projects/Toy Projects

[토이 프로젝트 1] 내 목소리로 동화책 읽어주는 AI 만들기 - 학습 & TEST

by muns91 2024. 11. 30.
동화책 읽어주는 AI 만들기 - 학습 & TEST

 

 이번 글은 토이 프로젝트 1에 대한 내용으로 '내 목소리로 동화책 읽어주는 AI 만들기'의 학습 & TEST를 해본 후기에 대한 글입니다. 지난 글에서는 동영상 파일로부터 오디오 데이터를 추출하고 이를 슬라이싱한 다음에 Whisper 모델을 통해서 스크립트와 함께 슬라이싱 데이터를 추출해보았습니다. 따라서 이번에는 추출된 입력 데이터를 가지고 Coqui에서 제공하는 xTTS라는 인공지능 모델에 학습하고 이를 테스트해보는 것을 확인하도록 하겠습니다. 

 

  • 사용 데이터 : 개인 발표 녹화 영상 (약 13분)
  • 프로그래밍 환경 : 개인 컴퓨터 (GPU : NVIDIA TITAN RTX , RAM 128)
  • 사용 프레임 워크 : Coqui TTS
  • 모델 : XTTS v2
  • 총 학습 시간 : 약 46시간

코드 https://github.com/Muns91/Toy-Project1_XTTS

 

GitHub - Muns91/Toy-Project1_XTTS

Contribute to Muns91/Toy-Project1_XTTS development by creating an account on GitHub.

github.com

 

참고 영상 : https://www.youtube.com/watch?v=UKNzKyTLDGo

 

학습 데이터 추출 관련 링크 :

2024.11.25 - [Personal Projects/Toy Projects] - [토이 프로젝트 1] 내 목소리로 동화책 읽어주는 AI 만들기 - 데이터

 


구현 코드

 

 본인의 목소리로 구현되는 TTS 모델을 구현하기 위해서는 먼저 위의 참고 링크를 통해서 빵형님께서 구현하신 Coqui TTS 모델에 대한 코드를 활용하셔야 합니다. 그리고 컴퓨팅 환경의 경우, 저는 소속했던 연구실의 컴퓨터 중 한 대를 양해를 구하고 사용하였습니다. 그리고 install과 관련해서 정말 다사다난(?)한 일들이 정말 많아서, 학습을 시키는 것보다 학습을 위한 준비 과정이 더 힘들고 복잡했던 것 같습니다. 아무튼 이러한 우열곡절 끝에 해당 코드를 돌릴 수 있는 가상 환경을 구축할 수 있었고, 이를 토대로 저의 목소리 파일을 기반으로 학습을 수행할 수 있었습니다.  

 

- 사용전 설치 사항

저는 CUDA 11.8v 버전에 호환되는 Pytorch GPU 버전으로 설치하였습니다.

conda update -n base conda
conda update --all

conda create -n torch_env python=3.9
conda activate torch_env

pip install TTS #numpy 버전을 호환에 대한 에러가 발생하면 해당 버전에 맞춰서 업데이트
pip uninstall torch torchvision torchaudio # 기존 CPU 버전 없애기

conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia # GPU 버전 설치

 

설치 확인

# 설치 확인
import torch

USE_CUDA = torch.cuda.is_available()
device = torch.device('cuda:0' if USE_CUDA else 'cpu')

print('CUDA 사용 가능 여부 :', USE_CUDA)
print('현재 사용 device :', device)
print('CUDA Index :', torch.cuda.current_device())
print('GPU 이름 :', torch.cuda.get_device_name())
print('GPU 개수 :', torch.cuda.device_count())

 

구현 코드

import os

from trainer import Trainer, TrainerArgs
from TTS.config.shared_configs import BaseDatasetConfig
from TTS.tts.datasets import load_tts_samples
from TTS.tts.layers.xtts.trainer.gpt_trainer import GPTArgs, GPTTrainer, GPTTrainerConfig, XttsAudioConfig
from TTS.utils.manage import ModelManager

 

# Logging parameters
RUN_NAME = "GPT_XTTS_v2.0_BBANGHYONG_FT"
PROJECT_NAME = "XTTS_trainer"
DASHBOARD_LOGGER = "tensorboard"
LOGGER_URI = None
__file__ = "C:/Users/Server1/Desktop/Toy_Project"


# Set here the path that the checkpoints will be saved. Default: ./run/training/
OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "run", "training")

# Training Parameters
OPTIMIZER_WD_ONLY_ON_WEIGHTS = True  # for multi-gpu training please make it False
START_WITH_EVAL = True  # if True it will star with evaluation
BATCH_SIZE = 3  # set here the batch size
GRAD_ACUMM_STEPS = 84  # set here the grad accumulation steps
# Note: we recommend that BATCH_SIZE * GRAD_ACUMM_STEPS need to be at least 252 for more efficient training. You can increase/decrease BATCH_SIZE but then set GRAD_ACUMM_STEPS accordingly.

# Define here the dataset that you want to use for the fine-tuning on.
config_dataset = BaseDatasetConfig(
    formatter="ljspeech",
    dataset_name="bbanghyong",
    path="C:/Users/Server1/Desktop/Toy_Project/bbanghyong/content",
    meta_file_train="C:/Users/Server1/Desktop/Toy_Project/bbanghyong/content/metadata.txt",
    language="ko",
)

# Add here the configs of the datasets
DATASETS_CONFIG_LIST = [config_dataset]

# Define the path where XTTS v2.0.1 files will be downloaded
CHECKPOINTS_OUT_PATH = os.path.join(OUT_PATH, "XTTS_v2.0_original_model_files/")
os.makedirs(CHECKPOINTS_OUT_PATH, exist_ok=True)


# DVAE files
DVAE_CHECKPOINT_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/dvae.pth"
MEL_NORM_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/mel_stats.pth"

# Set the path to the downloaded files
DVAE_CHECKPOINT = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(DVAE_CHECKPOINT_LINK))
MEL_NORM_FILE = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(MEL_NORM_LINK))

# download DVAE files if needed
if not os.path.isfile(DVAE_CHECKPOINT) or not os.path.isfile(MEL_NORM_FILE):
    print(" > Downloading DVAE files!")
    ModelManager._download_model_files([MEL_NORM_LINK, DVAE_CHECKPOINT_LINK], CHECKPOINTS_OUT_PATH, progress_bar=True)


# Download XTTS v2.0 checkpoint if needed
TOKENIZER_FILE_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/vocab.json"
XTTS_CHECKPOINT_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/model.pth"

# XTTS transfer learning parameters: You we need to provide the paths of XTTS model checkpoint that you want to do the fine tuning.
TOKENIZER_FILE = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(TOKENIZER_FILE_LINK))  # vocab.json file
XTTS_CHECKPOINT = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(XTTS_CHECKPOINT_LINK))  # model.pth file

# download XTTS v2.0 files if needed
if not os.path.isfile(TOKENIZER_FILE) or not os.path.isfile(XTTS_CHECKPOINT):
    print(" > Downloading XTTS v2.0 files!")
    ModelManager._download_model_files(
        [TOKENIZER_FILE_LINK, XTTS_CHECKPOINT_LINK], CHECKPOINTS_OUT_PATH, progress_bar=True
    )
    
# Training sentences generations
SPEAKER_REFERENCE = [
    "C:/Users/Server1/Desktop/Toy_Project/bbanghyong/content/wavs/audio2.wav"  # speaker reference to be used in training test sentences
]
LANGUAGE = config_dataset.language

 

def main():
    # init args and config
    model_args = GPTArgs(
        max_conditioning_length=132300,  # 6 secs
        min_conditioning_length=66150,  # 3 secs
        debug_loading_failures=False,
        max_wav_length=255995,  # ~11.6 seconds
        max_text_length=200,
        mel_norm_file=MEL_NORM_FILE,
        dvae_checkpoint=DVAE_CHECKPOINT,
        xtts_checkpoint=XTTS_CHECKPOINT,  # checkpoint path of the model that you want to fine-tune
        tokenizer_file=TOKENIZER_FILE,
        gpt_num_audio_tokens=1026,
        gpt_start_audio_token=1024,
        gpt_stop_audio_token=1025,
        gpt_use_masking_gt_prompt_approach=True,
        gpt_use_perceiver_resampler=True,
    )
    # define audio config
    audio_config = XttsAudioConfig(sample_rate=22050, dvae_sample_rate=22050, output_sample_rate=24000)
    # training parameters config
    config = GPTTrainerConfig(
        output_path=OUT_PATH,
        model_args=model_args,
        run_name=RUN_NAME,
        project_name=PROJECT_NAME,
        run_description="""
            GPT XTTS training
            """,
        dashboard_logger=DASHBOARD_LOGGER,
        logger_uri=LOGGER_URI,
        audio=audio_config,
        batch_size=BATCH_SIZE,
        batch_group_size=48,
        eval_batch_size=BATCH_SIZE,
        num_loader_workers=8,
        eval_split_max_size=256,
        print_step=50,
        plot_step=100,
        log_model_step=1000,
        save_step=10000,
        save_n_checkpoints=1,
        save_checkpoints=True,
        # target_loss="loss",
        print_eval=False,
        # Optimizer values like tortoise, pytorch implementation with modifications to not apply WD to non-weight parameters.
        optimizer="AdamW",
        optimizer_wd_only_on_weights=OPTIMIZER_WD_ONLY_ON_WEIGHTS,
        optimizer_params={"betas": [0.9, 0.96], "eps": 1e-8, "weight_decay": 1e-2},
        lr=5e-06,  # learning rate
        lr_scheduler="StepLR",
        # it was adjusted accordly for the new step scheme
        lr_scheduler_params={"step_size": 50, "gamma": 0.5, "last_epoch": -1},
        test_sentences=[
            {
                "text": "나에게는 그들보다 이 점등인이 더 나은 사람이야. 적어도 점등인은 그들과는 달리, 남을 위해 일하기 때문이야. 너는 나에게 이 세상에 단 하나뿐인 존재가 되는 거고, 나도 너에게 세상에 하나뿐인 존재가 되는 거야.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "넌 네가 길들인 것에 영원히 책임이 있어. 누군가에게 길들여진다는 것은 눈물을 흘릴 일이 생긴다는 뜻일지도 몰라. 네 장미꽃이 소중한 이유는 그 꽃을 위해 네가 애쓴 시간 때문이야. 다른 사람에게는 열어주지 않는 문을 당신에게만 열어주는 사람이 있다면 그 사람은 당신의 진정한 친구이다.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "세상에서 가장 어려운 일은 사람이 사람의 마음을 얻는 일이야. 내가 좋아하는 사람이 나를 좋아해 주는 건 기적이야.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "질문을 하지 않으면 세상 일을 어떻게 알겠어요?",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "세상엔 재미있는 일이 참 많아요. 우리가 모든 걸 다 안다면 사는 재미가 반으로 줄어들 거예요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "또 다른 걱정거리들이 생길 거예요. 항상 골치 아픈 일들은 새롭게 일어나니까요. 한 가지가 해결되면 또 다른 문제가 이어지죠. 나이를 먹으니 생각할 것도, 결정해야 할 일도 많아져요. 뭐가 옳은지 곰곰이 생각하고 결정하느라 늘 바빠요. 어른이 된다는 건 쉬운 일이 아니에요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "이 세상에서 무언가를 얻거나 이루려면 반드시 그만한 대가를 치러야 한다. 야망을 품는 것은 가치 있는 일이지만 합당한 노력과 절제와 불안과 좌절 없이 얻어지지는 않는 법이다.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "앤은 발 앞에 높인 길이 아무리 좁더라도 그 길을 따라 잔잔한 행복의 꽃이 피어난다는 걸 알고 있었다. 정직한 일과 훌륭한 포부와 마음 맞는 친구가 있다는 기쁨은 온전히 앤의 것이었다.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "아무리 황량하고 따분해도 다른 아름다운 곳보다 고향에서 살고 싶어 하는 게 사람이에요. 세상에 집보다 좋은 곳은 없거든요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "바보들은 심장이 있어도 그걸로 무엇을 해야 하는지 몰라요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "살아 있는 존재라면 누구나 위험 앞에서 두려움을 느껴. 진정한 용기란 두려움에도 불구하고 위험에 맞서는 것인데, 너는 이미 그런 용기를 충분히 갖고 있어.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "너는 작가가 되지 않아도, 배우가 되지 않아도, 그저 너이기에 사랑스럽고 완전한 존재란다. 다른 무엇이 되려고 애쓰지 않아도 좋아. 너는 그저 너, 너다운 너이기만 하면 된단다.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "소설을 쓰며 사는 삶보다 소설처럼 살아가는 삶이 훨씬 더 재미있을 거예요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "저는 매 순간 제가 행복하다는 사실을 온전히 느껴요. 아무리 속상한 일이 생겨도 그 사실을 잊지 않을 거예요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "저는 인간에게 가장 필요한 자질은 상상력이라고 생각합니다. 상상력이 있어야 타인을 이해할 수 있고, 그래야 친절할 수도, 남을 이해할 수도, 또 동정할 수도 있으니까요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "자신에게 찾아오는 기회를 붙잡을 의지만 있다면 세상은 행복으로 가득 차 있고, 가볼 곳도 많아요. 비법은 바로 유연한 마음이에요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "정말 소중한 것은 커다란 기쁨이 아니에요. 사소한 곳에서 얻는 기쁨이 더 중요해요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "큰 시련이 닥쳤을 때만 인격이 필요한 게 아니에요. 위기에 대처하거나, 치명적인 비극에 맞서는 건 누구나 할 수 있지만, 그날그날의 사소한 불운들을 웃음으로 넘기는 일은 '정신력'이 없다면 불가능한 일이에요. 제가 키워나가야 할 게 바로 이런 종류의 인격이에요.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "눈이 보이지 않으면 눈이 보이는 코끼리와 살을 맞대고 걸으면 되고, 다리가 불편하면 다리가 튼튼한 코끼리에게 기대서 걸으면 돼. 같이 있으면 그런 건 큰 문제가 아니야.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
            {
                "text": "누구든 너를 좋아하게 되면, 네가 누구인지 알아볼 수 있어. 아마 처음에는 호기심으로 너를 관찰하겠지. 하지만 점 너를 좋아하게 되어서 너를 눈여겨보게 되고, 네가 가까이 있을 때는 어떤 냄새가 나는지 알게 될 거고, 네가 걸을 때는 어떤 소리가 나는지에도 귀 기울이게 될 거야. 그게 바로 너야.",
                "speaker_wav": SPEAKER_REFERENCE,
                "language": LANGUAGE,
            },
        ],
    )

    # init the model from config
    model = GPTTrainer.init_from_config(config)

    # load training samples
    train_samples, eval_samples = load_tts_samples(
        DATASETS_CONFIG_LIST,
        eval_split=True,
        eval_split_max_size=config.eval_split_max_size,
        eval_split_size=config.eval_split_size,
    )

    # init the trainer and 🚀
    trainer = Trainer(
        TrainerArgs(
            restore_path=None,  # xtts checkpoint is restored via xtts_checkpoint key so no need of restore it using Trainer restore_path parameter
            skip_train_epoch=False,
            start_with_eval=START_WITH_EVAL,
            grad_accum_steps=GRAD_ACUMM_STEPS,
        ),
        config,
        output_path=OUT_PATH,
        model=model.to(device),
        train_samples=train_samples,
        eval_samples=eval_samples,
    )
    trainer.fit()
    
if __name__ == "__main__":
    main()

 

 main() 에서 주의 하실 사항은 Epoch 1에서 더 이상 변동되는 학습이 진행되지 않길래.. 왜 그러지 하는 과정에서 코드를 파헤쳐본 결과 # init The Trainer ~~ 코드 구간에서 'skip_train_epoch'를 True로 해두시면 Epoch가 얼만큼 진행되고 있는 지를 아실 수 있습니다. 이것 때문에 model 부분에 to(device)도 넣고 별 방법을 다 했었는데... 결국 이것 때문이라는 것을 알게 되었습니다. 역시 코드에서 파라미터 부분은 꼼꼼히 살펴보아야 되는 것 같습니다.

 

★ 학습이 끝난 후 어떻게 결과를 확인 해야될까?

 위 코드에 따라 학습이 끝난 이후에 쥬피터 노트북 화면에서는 아무것도 확인하실 수 없습니다. (이것 때문에 엄청 당황했었습니다.) 따라서 이걸 어떻게 해야될까? 생각하는 과정에서 빵형님의 영상을 다시 본 결과, Tensorboard를 활용하셔서 오디오 파일을 확인하셨다는 것을 알 수 있었습니다. 그냥 넘어갔으면 학습만 시켜본 사기꾼이 될 뻔했습니다;;; 

 

 따라서 학습을 통해 저장시킨 로그 데이터를 텐서보드를 통해 다음과 같은 과정을 통해 확인하도록 하겠습니다.

 

1. Jupyter Notebook >> Terminal

2. Terminal >> 경로 변경 >> 텐서보드 실행

3. 텐서보드 >> Audido

 

 

 일단 로그 파일이 저장된 폴더로 가시면 해당 쥬피터 노트북 화면에서 오른쪽에 New를 누르시면 터미널을 여실 수 있습니다. 해당 터미널을 여시면 아래와 같은 화면을 확인하실 수 있는 데, 여기서 경로를 run 폴더가 있는 곳으로 바꿔주시고 다음과 같은 명령어를 입력해주세요. 참고로 저는 run 폴더 안에 trining 폴더가 있고 그 안에 로그 파일들이 있는 폴더들이 있습니다. 

 

cd 'run 폴더가 있는 경로'

tensorboard --logdir=./로그파일이 있는 폴더 --port=6006

 

 

 위 사진과 같이 명령어를 입력하셨다면, 텐서보드가 실행이 됩니다. 그리고 상단의 메뉴 중에서 'Audio'를 통해서 본인의 목소리로 학습 시킨 TEST Audio 들을 최종적으로 확인하실 수 있습니다. 

 

 

텐서보드 사용 참고 링크 : https://gooopy.tistory.com/98


마무리

 여기까지 빵형의 개발 도상국을 보고 따라해본 저의 첫 번째 AI 토이 프로젝트입니다. 개인적으로 해당 과정을 따라해보면서 동영상으로부터 오디오를 추출하는 과정을 확인할 수 있었고, SKT FLY AI 이후에 처음으로 STT 모델인 Whisper를 활용하여 오디오로부터 스크립트를 만들어볼 수 있었습니다. 또한, TTS 기능 구현을 위해서 Coqui TTS을 활용하여 적은 데이터로부터 저의 목소리와 거의 흡사한 AI 목소리를 만들어내서 이를 동화를 구현하는 데, 사용해 볼 수 있었습니다. 이후에는 데이터 수집 과정과 사용된 모델 등에 대해서 조금 깊게 알아보면서 두 번째 토이 프로젝트를 수행할 수 있도록 해야겠습니다. 

 

 

 

참 고 

참고 링크 1 : https://docs.coqui.ai/en/latest/index.html

참고 링크 2 : https://bradjobs.notion.site/TTS-85a62e9706fe49a3876208f749ce8c35

 

동화 구절 TTS 파인튜닝 - 빵형의 개발도상국 | Notion

유튜브 빵형의 개발도상국

bradjobs.notion.site

 

 

 

 

 

반응형