NVIDIA NeMo를 활용하여 세계 정상급 AI 모델 구축하기

by NVIDIA Korea

말은 가장 자연스러운 형태의 의사 소통 방식입니다. 따라서 우리가 음성을 사용하여 기계에 명령을 내리고 상호 작용하기를 원하는 것도 당연합니다. 그러나 대화형 AI가 매끄럽고 자연스러우면서 인간에 가까운 경험을 제공하려면, 해당 모델이 해결하고자 하는 문제를 대표하는 대규모 데이터로 훈련을 거쳐야 합니다. 여기에서 머신 러닝 담당팀이 직면하는 난관은 영역별로 특화된 고품질 데이터가 희소하다는 점이죠.

각 기업은 이 문제를 해결하고자 노력하는 한편, 모델의 확장성과 국제성을 보장하는 혁신적 솔루션과 함께 대화형 AI의 광범위한 도입을 가속화하는 중입니다. 대표적인 사례로 NVIDIA와 디파인드크라우드(DefinedCrowd)를 들 수 있는데요. 머신 러닝 엔지니어들은 NVIDIA의 모델 구축용 툴킷과 디파인드크라우드의 고품질 훈련 데이터를 통합하여 세계 정상급 AI를 보다 간편하고 쉽고 빠르게 구축합니다.

AI 훈련용 데이터 일체를 제공하는 디파인드크라우드

저는 디파인드크라우드의 머신 러닝 부문 책임자 크리스토퍼 셜비(Christopher Shulby)입니다. 우리 업무의 핵심은 세계 정상급 AI 솔루션을 구축하는 기업에 고품질 AI 훈련용 데이터를 제공하는 것입니다. 이 데이터는 언어와 영역, 리코딩(recording) 유형별로 규격화된 AI 훈련용 데이터의 온라인 마켓플레이스인 디파인드데이터(DefinedData)를 통해 제공됩니다.

원하는 데이터를 디파인드데이터에서 찾을 수 없는 경우, 디파인드클라우드의 워크플로우를 독립적 또는 엔드-투-엔드 데이터 서비스로 활용하면 음성이나 텍스트로 지원되는 AI 아키텍처를 완전히 새로 구축하거나 기존의 솔루션을 개선하고, 생산 단계의 모델을 평가할 수도 있습니다. 이 과정 일체의 품질을 디파인드클라우드가 보증합니다.

대화형 AI 애플리케이션을 구축하는 손쉬운 방법

NVIDIA NeMo는 대화형 AI 애플리케이션 구축용으로 개발된 툴킷입니다. 자동 음성 인식(ASR)과 자연어 처리(NLP), 문자 음성 변환(TTS)을 위해 사전 훈련된 모듈의 컬렉션을 제공하죠. 이에 따라 연구자와 데이터 과학자들은 복잡한 신경망 아키텍처를 손쉽게 구성하고 독자적인 애플리케이션의 설계에 집중할 수 있게 됩니다.

영상: NVIDIA NeMo와 디파인드크라우드로 세계 정상급 대화형 AI를 빠르고 간편하게 구축하는 방법을 확인하세요. NeMo 툴킷으로 세계적 수준의 모델을 쉽고 빠르게 구축한 뒤 디파인드크라우드의 고품질 데이터로 훈련을 진행해 성능을 강화하세요.

NeMo와 디파인드크라우드의 통합

다음은 NVIDIA NeMo를 활용하는 ASR 모델에 디파인드크라우드의 음성 워크플로우를 연결하여 훈련과 개선 작업을 진행하는 방법입니다. 이 코드는 구글 코랩(Google Colab) 링크로도 액세스할 수 있습니다.

Step 1: NeMo 툴킷과 종속성 설치

# First, install NeMo Toolkit and dependencies to run this notebook
!apt-get install -y libsndfile1 ffmpeg
!pip install Cython

## Install NeMo dependencies in the correct versions
!pip install torchtext==0.8.0 torch==1.7.1 pytorch-lightning==1.2.2

## Install NeMo
!python -m pip install nemo_toolkit[all]==1.0.0b3

Step 2: 디파인드크라우드 API로 데이터 확보

디파인드크라우드 API에 연결하여 음성 데이터를 확보하는 방법은 다음과 같습니다. 더 자세한 내용은 디파인드크라우드 API(v2)를 참고하세요.

# For the demo, use a sandbox environment
auth_url = "https://sandbox-auth.definedcrowd.com"
api_url = "https://sandbox-api.definedcrowd.com"

# These variables should be obtained at the DefinedCrowd Enterprise Portal for your account.
client_id = "<INSERT YOUR CLIENT ID>"
client_secret = "<INSERT YOUR SECRET ID>"
project_id = "<INSERT YOUR PROJECT ID>"

인증(Authentication)

payload = {
    "client_id": client_id,
    "client_secret": client_secret,
    "grant_type": "client_credentials",
    "scope": "PublicAPIv2",
}
files = []
headers = {}

# request the Auth 2.0 access token
response = requests.request(
    "POST", f"{auth_url}/connect/token", headers=headers, data=payload, files=files
)
if response.status_code == 200:
    print("Authentication success!")
    access_token = response.json()["access_token"]
else:
    print("Authentication Failed")

인증이 완료됐습니다!

산출물(deliverables)의 목록

# GET /projects/{project-id}/deliverables
headers = {"Authorization": "Bearer " + access_token}
response = requests.request(
    "GET", f"{api_url}/projects/{project_id}/deliverables", headers=headers
)

if response.status_code == 200:
    # Pretty print the response
    print(json.dumps(response.json(), indent=4))

    # Get the first deliverable ID
    deliverable_id = response.json()[0]["id"]

[
    {
        "projectId": "eb324e45-c4f9-41e7-b5cf-655aa693ae75",
        "id": "258f9e15-2937-4846-b9c3-3ae1164b7364",
        "type": "Flat",
        "fileName": "data_Flat_eb324e45-c4f9-41e7-b5cf-655aa693ae75_258f9e15-2937-4846-b9c3-3ae1164b7364_2021-03-22-14-34-37.zip",
        "createdTimestamp": "2021-03-22T14:34:37.8037259",
        "isPartial": false,
        "downloadCount": 2,
        "status": "Downloaded"
    }
]

음성 데이터 수집을 위한 최종 산출물

# Name to give to the deliverable file
filename = "scripted_monologue_en_GB.zip"

# GET /projects/{project-id}/deliverables/{deliverable-id}/download
headers = {"Authorization": "Bearer " + access_token}
response = requests.request(
    "GET",
    f"{api_url}/projects/{project_id}/deliverables/{deliverable_id}/download/",
    headers=headers,
)

if response.status_code == 200:
    # save the deliverable file
    with open(filename, "wb") as fp:
        fp.write(response.content)
    print("Deliverable file saved with success!")

산출물 파일이 성공적으로 저장됐습니다!

# Extract the contents from the downloaded file
!unzip  scripted_monologue_en_GB.zip &> /dev/null
!rm -f en-gb_single-scripted_Dataset.zip

Step 3: 음성 데이터세트 분석

다음은 디파인드크라우드에서 받은 데이터를 분석하는 방법입니다. 이 데이터는 디파인드크라우드의 니보(Neevo) 플랫폼이 영국 내 화자(디파인드크라우드의 회원)를 대상으로 수집한 음성 스크립트 데이터로 구축됐습니다.

데이터세트의 각 열에는 음성 프롬프트(prompt), 회원, 사용된 기기, 리코딩과 관련한 정보가 포함되어 있습니다. 전송되는 데이터의 세부 내용은 다음과 같습니다.

  • 리코딩:
    • 리코딩Id(RecordingId)
    • 프롬프트Id(PromptId)
    • 프롬프트
  • 오디오 파일:
    • 관련 파일명(RelativeFileName)
    • 길이(Duration)
    • 샘플률(SampleRate)
    • 비트 심도(BitDepth)
    • 오디오 통신용 대역(AudioCommunicationBand)
    • 리코딩 환경(RecordingEnvironment)
  • 크라우드 회원:
    • 화자 Id
    • 성별
    • 연령
    • 발음의 강세
    • 거주국가
  • 리코딩 기기:
    • 제조사
    • 기기 유형
    • 도메인

이 데이터는 여러 용도로 사용 가능하지만, 본 튜토리얼에서는 영국인 화자를 위한 기존 ASR 모델의 개선 작업에 활용해보겠습니다.

import pandas as pd

# Look in the metadata file
dataset = pd.read_csv("metadata.tsv", sep="\t", index_col=[0])

# Check the data for the first row
dataset.iloc[0]

RecordingId                               165559628
PromptId                                   64977250
RelativeFileName                Audio/165559628.wav
Prompt                    The Avengers' extinction.
Duration                               00:00:02.815
SpeakerId                                    128209
Gender                                       Female
Age                                              26
Manufacturer                                  Apple
DeviceType                                iPhone 6s
Accent                                      Suffolk
Domain                                      generic
SampleRate                                    16000
BitDepth                                         16
AudioCommunicationBand                    Broadband
LivingCountry                        United Kingdom
Native                                         True
RecordingEnvironment                         silent
Name: 0, dtype: object

# How many rows do you have?
len(dataset)

50000

# Check some examples from the dataset
import librosa
import IPython.display as ipd

for index, row in dataset.sample(4, random_state=1).iterrows():

    print(f"Prompt: {dataset.iloc[index].Prompt}")
    audio_file = dataset.iloc[index].RelativeFileName

    # Load and listen to the audio file
    audio, sample_rate = librosa.load(audio_file)
    ipd.display(ipd.Audio(audio, rate=sample_rate))

오디오 샘플은 구글 코랩의 디파인드크라우드 X NeMo – ASR 훈련 튜토리얼을 참고하세요.

Step 4: 데이터 준비

디파인드크라우드 API에서 음성 데이터를 다운로드한 다음에는 ASR 훈련을 위해 NeMo가 요구하는 형식에 맞춰 조정해야 합니다. 이를 위해 각 오디오 파일의 메타데이터를 비롯한 훈련과 평가 데이터의 매니페스트(manifests)를 생성합니다.

NeMo를 사용하려면 해당 데이터를 특정한 매니페스트 형식으로 조정해야 합니다. 각 행은 하나의 오디오 샘플에 해당하므로 행수는 매니페스트가 표시하는 샘플의 수와 일치합니다. 하나의 행에는 오디오 파일에의 경로, 파일의 전사(transcript), 오디오 샘플 길이가 반드시 포함되어야 합니다. 다음은 NeMo 호환 매니페스트의 한 행이 어떤 형식으로 구성되는지 보여주는 예입니다.

{"audio_filepath": "path/to/audio.wav", "duration": 3.45, "text": "this is a nemo tutorial"}

매니페스트의 생성을 위해 파일의 전사도 표준화합니다.

import os

# Function to build a manifest
def build_manifest(dataframe, manifest_path):
    with open(manifest_path, "w") as fout:
        for index, row in dataframe.iterrows():
            transcript = row["Prompt"]

            # The model uses lowercased data for training/testing
            transcript = transcript.lower()

            # Removing linguistic marks (they are not necessary for this demo)
            transcript = (
                transcript.replace("<s>", "")
                .replace("</s>", "")
                .replace("[b_s/]", "")
                .replace("[uni/]", "")
                .replace("[v_n/]", "")
                .replace("[filler/]", "")
                .replace('"', "")
                .replace("[n_s/]", "")
            )

            audio_path = row["RelativeFileName"]

            # Get the audio duration
            try:
                duration = librosa.core.get_duration(filename=audio_path)
            except Exception as e:
                print("An error occurred: ", e)

            if os.path.exists(audio_path):
                # Write the metadata to the manifest
                metadata = {
                    "audio_filepath": audio_path,
                    "duration": duration,
                    "text": transcript,
                }
                json.dump(metadata, fout)
                fout.write("\n")
            else:
                continue

Step 5: 훈련과 검증 스플릿(splits)

모델의 품질을 검증하려면 모델 검증용으로 일부 데이터를 따로 보관해야 합니다. 이 데이터로 해당 모델의 성능을 평가하세요.

import json
from sklearn.model_selection import train_test_split
# Split 10% for testing (500 prompts) and 90% for training (4500 prompts)
trainset, testset = train_test_split(dataset, test_size=0.1, random_state=1)
# Build the manifests
build_manifest(trainset, "train_manifest.json")
build_manifest(testset, "test_manifest.json")

Step 6: 모델 구성

다음은 QuartzNet15x5 모델을 기초 모델로 삼고 데이터를 활용해 미세 조정을 진행하는 방법입니다. 데이터세트의 인식을 강화하기 위해 기초 모델과 이후 미세 조정을 마친 버전에서의 모델 성능 벤치마크를 각각 실시합니다. 다음 함수의 일부는 Nemo Tutorial on ASR에서 가져왔습니다.

# Import Nemo and the functions for ASR
import torch
import nemo
import nemo.collections.asr as nemo_asr
import logging
from nemo.utils import _Logger
# Set up the log level by NeMo
logger = _Logger()
logger.set_verbosity(logging.ERROR)

Step7: 훈련 파라미터 설정

NeMo는 데이터 구조로 파이썬(Python)의 딕셔너리(dictionary)를 사용하여 파라미터 일체를 유지합니다. 보다 자세한 정보는 NeMo ASR Config 사용자 가이드를 확인하세요.

이번 튜토리얼에서는 표준 ASR 구성으로 기존 파일을 로드하고 필요한 필드만 변경합니다.

## Download the config to use in this example
!mkdir configs
!wget -P configs/ https://raw.githubusercontent.com/NVIDIA/NeMo/main/examples/asr/conf/config.yaml &> /dev/null

# --- Config Information ---#
from ruamel.yaml import YAML

config_path = "./configs/config.yaml"

yaml = YAML(typ="safe")
with open(config_path) as f:
    params = yaml.load(f)

Step 8: 기초 모델 다운로드

ASR 모델의 경우, NGC 카탈로그에서 QuartzNet15x5의 사전 훈련 모델을 활용합니다.

QuartzNet15x5 모델은 리브리스피치(LibriSpeech)와 모질라 커먼 보이스(Mozilla Common Voice, en_1488h_2019-12-10에서 인증된 클립), 월스트리트저널(WSJ), 피셔(Fisher), 스위치보드(Switchboard), NSC 싱가포르 잉글리시(NSC Singapore English)의 6개 데이터세트로 훈련했습니다. Apex/Amp 최적화 레벨 O1로 600에포크(epochs)의 훈련을 진행했습니다. 이 모델은 리브리스피치 dev-clean에서는 오류율(WER) 3.79%를, dev-other에서는 오류율 10.05%를 달성합니다.

# This line downloads the pretrained QuartzNet15x5 model from NGC and instantiates it for you
quartznet = nemo_asr.models.EncDecCTCModel.from_pretrained(model_name="QuartzNet15x5Base-En", strict=False)

Step 9: 기초 모델의 성능 평가

단어 오류율(WER)은 서로 다른 ASR 모델을 비교하고 하나의 시스템 내에서 개선의 정도를 평가하는 데 유용한 도구입니다. 결과를 얻으려면 검증용 세트를 활용하여 해당 모델의 성능을 평가하세요.

# Configure the model parameters for testing

# Parameters for training, validation, and testing are specified using the 
# train_ds, validation_ds, and test_ds sections of your configuration file

# Bigger batch-size = bigger throughput
params["model"]["validation_ds"]["batch_size"] = 8

# Set up the test data loader and make sure the model is on GPU
params["model"]["validation_ds"]["manifest_filepath"] = "test_manifest.json"
quartznet.setup_test_data(test_data_config=params["model"]["validation_ds"])

# Comment out this line if you don't want to use GPU acceleration
_ = quartznet.cuda()

# Compute the WER metric between the hypothesis and predictions.

wer_numerators = []
wer_denominators = []

# Loop over all test batches.
# Iterating over the model's `test_dataloader` gives you:
# (audio_signal, audio_signal_length, transcript_tokens, transcript_length)
# See the AudioToCharDataset for more details.
with torch.no_grad():
    for test_batch in quartznet.test_dataloader():
        input_signal, input_signal_length, targets, targets_lengths = [x.cuda() for x in test_batch]
                
        log_probs, encoded_len, greedy_predictions = quartznet(
            input_signal=input_signal, 
            input_signal_length=input_signal_length
        )
        # The model has a helper object to compute WER
        quartznet._wer.update(greedy_predictions, targets, targets_lengths)
        _, wer_numerator, wer_denominator = quartznet._wer.compute()
        wer_numerators.append(wer_numerator.detach().cpu().numpy())
        wer_denominators.append(wer_denominator.detach().cpu().numpy())

# First, sum all numerators and denominators. Then, divide.
print(f"WER = {sum(wer_numerators)/sum(wer_denominators)*100:.2f}%")

오류율(WER) = 39.70%

Step 10: 모델의 미세 조정

기초 모델의 오류율은 39.7%로, 크게 훌륭한 수준은 아닙니다. 동일 영역과 방언으로 구성된 데이터를 제공하면 ASR 모델을 개선할 수 있습니다. 단순화를 위해 디파인드크라우드의 데이터를 사용하여 훈련을 1에포크만 진행하세요.

import pytorch_lightning as pl
from omegaconf import DictConfig
import copy

# Before training, you must provide the train manifest for training
params["model"]["train_ds"]["manifest_filepath"] = "train_manifest.json"

# Use the smaller learning rate for fine-tuning
new_opt = copy.deepcopy(params["model"]["optim"])
new_opt["lr"] = 0.001
quartznet.setup_optimization(optim_config=DictConfig(new_opt))

# Batch size depends on the GPU memory available
params["model"]["train_ds"]["batch_size"] = 8

# Point to the data to be used for fine-tuning as the training set
quartznet.setup_training_data(train_data_config=params["model"]["train_ds"])

# Clean the torch cache
torch.cuda.empty_cache()

# Now you can create a PyTorch Lightning trainer.
trainer = pl.Trainer(gpus=1, max_epochs=1)

# The fit function starts the training
trainer.fit(quartznet)

Step 11: 모델 성능 비교

최종 모델의 성능과 여러분이 추가 데이터로 훈련을 진행해 미세 조정한 모델의 성능을 비교합니다.

# Configure the model parameters for testing
params["model"]["validation_ds"]["batch_size"] = 8

# Set up the test data loader and make sure the model is on GPU
params["model"]["validation_ds"]["manifest_filepath"] = "test_manifest.json"
quartznet.setup_test_data(test_data_config=params["model"]["validation_ds"])
_ = quartznet.cuda()

# Compute the WER metric between the hypothesis and predictions.

wer_numerators = []
wer_denominators = []

# Loop over all test batches.
# Iterating over the model's `test_dataloader` gives you:
# (audio_signal, audio_signal_length, transcript_tokens, transcript_length)
# See the AudioToCharDataset for more details.
with torch.no_grad():
    for test_batch in quartznet.test_dataloader():
        input_signal, input_signal_length, targets, targets_lengths = [x.cuda() for x in test_batch]
                
        log_probs, encoded_len, greedy_predictions = quartznet(
            input_signal=input_signal, 
            input_signal_length=input_signal_length
        )
        # The model has a helper object to compute WER
        quartznet._wer.update(greedy_predictions, targets, targets_lengths)
        _, wer_numerator, wer_denominator = quartznet._wer.compute()
        wer_numerators.append(wer_numerator.detach().cpu().numpy())
        wer_denominators.append(wer_denominator.detach().cpu().numpy())

# First, sum all numerators and denominators. Then, divide.
print(f"WER = {sum(wer_numerators)/sum(wer_denominators)*100:.2f}%")

오류율(WER) = 24.36%

신경망 ASR 아키텍처로 여러 에포크에 걸쳐 훈련을 진행한 후 24.36%의 오류율을 달성했습니다. 이는 1에포크의 훈련만을 진행했던 기초 모델이 처음 달성한 39.7%보다 개선된 수치입니다. 더 나은 결과를 얻으려면 더 많은 에포크로 훈련을 진행하는 방안을 고려해보세요.

결론

이번 튜토리얼에서는 디파인드크라우드가 수집한 음성 데이터를 로드하고 이를 활용해 ASR 모델의 훈련과 성능 측정을 실시하는 방법을 살펴봤습니다. NVIDIA와 디파인드크라우드로 세계 정상급 AI 솔루션을 얼마나 간편하게 구축할 수 있는지 경험하는 시간이 되셨기를 바랍니다.