텍스트-음성 생성

텍스트-음성 생성 (Text to speech)

텍스트-음성 변환(TTS)은 텍스트에서 자연스러운 음성을 만들어 내는 작업이에요. 음성은 여러 언어로, 그리고 여러 화자(speaker)에 대해 생성될 수 있어요. 🤗 Transformers에는 Dia, CSM, Bark, MMS, VITS, SpeechT5 같은 여러 텍스트-음성 모델이 준비돼 있습니다.

"text-to-audio" 파이프라인(별칭 "text-to-speech")을 쓰면 쉽게 오디오를 생성할 수 있어요. CSM과 함께 "text-to-speech" 파이프라인을 쓰는 방법 예시를 들어 볼게요:

>>> from transformers import pipeline

>>> pipe = pipeline("text-to-audio", model="sesame/csm-1b")
>>> output = pipe("Hello from Sesame.")

노트북에서 결과 오디오를 들어 보려면 이런 코드 조각을 쓰면 됩니다:

>>> from IPython.display import Audio
>>> Audio(output["audio"], rate=output["sampling_rate"])

기본적으로 CSM은 임의의 음성을 사용해요. 채팅 템플릿 사전(dictionary)의 일부로 참조 오디오를 제공하면 음성 복제(voice cloning)를 할 수 있습니다:

>>> import soundfile as sf
>>> import torch
>>> from datasets import Audio, load_dataset
>>> from transformers import pipeline

>>> pipe = pipeline("text-to-audio", model="sesame/csm-1b")

>>> ds = load_dataset("hf-internal-testing/dailytalk-dummy", split="train")
>>> ds = ds.cast_column("audio", Audio(sampling_rate=24000))
>>> conversation = [
...     {
...         "role": "0",
...         "content": [
...             {"type": "text", "text": "What are you working on?"},
...             {"type": "audio", "path": ds[0]["audio"]["array"]},
...         ],
...     },
...     {"role": "0", "content": [{"type": "text", "text": "How much money can you spend?"}]},
... ]
>>> output = pipe(conversation)

Dia 같은 일부 모델은 웃음, 한숨, 울음 같은 비언어적 의사소통을 생성하도록 조건화하거나, 심지어 음악을 더할 수도 있어요. 아래는 그런 예시입니다:

>>> from transformers import pipeline

>>> pipe = pipeline("text-to-speech", model="nari-labs/Dia-1.6B-0626")
>>> text = "[S1] (clears throat) Hello! How are you? [S2] I'm good, thanks! How about you?"
>>> output = pipe(text)

Dia는 서로 다른 고유한 목소리로 대화를 생성하기 위해 [S1], [S2] 같은 화자 태그도 받아들인다는 점을 기억하세요.

CSM과 다른 사전 훈련된 TTS 모델이 무엇을 할 수 있는지 더 많은 예시는 Audio course를 참고하세요.

TTS 모델을 파인튜닝하려고 한다면, 현재 🤗 Transformers에서 쓸 수 있는 텍스트-음성 모델은 SpeechT5, FastSpeech2Conformer, Dia, CSM뿐이에요 (앞으로 더 추가될 예정입니다). SpeechT5는 음성-텍스트와 텍스트-음성 데이터의 조합으로 사전 훈련되어, 텍스트와 음성이 공유하는 은닉 표현(representation)의 통합 공간을 학습할 수 있어요. 즉 동일한 사전 훈련된 모델을 다양한 작업에 파인튜닝할 수 있다는 뜻이에요. 게다가 SpeechT5는 x-vector 화자 임베딩을 통해 여러 화자를 지원합니다.

이 가이드의 나머지 부분은 다음을 보여 줍니다:

  1. 원래 영어 음성으로 훈련된 SpeechT5VoxPopuli 데이터셋의 네덜란드어(nl) 하위 집합으로 파인튜닝하기.
  2. 파인튜닝한 모델을 파이프라인을 쓰거나 직접 쓰는 두 방식으로 추론에 사용하기.

시작하기 전에 필요한 라이브러리가 모두 설치돼 있는지 확인하세요:

pip install transformers datasets soundfile librosa torchcodec speechbrain accelerate

🤗Transformers를 소스로부터 설치하세요. 아직 공식 릴리스에 SpeechT5 기능 전부가 병합되진 않았기 때문이에요:

pip install git+https://github.com/huggingface/transformers.git

이 가이드를 따르려면 GPU가 필요해요. 노트북에서 작업한다면 다음 줄을 실행해 GPU를 쓸 수 있는지 확인하세요:

!nvidia-smi

또는 AMD GPU의 경우:

!rocm-smi

모델을 커뮤니티와 공유하고 업로드할 수 있도록 Hugging Face 계정에 로그인하길 권장해요. 요청이 오면 토큰을 입력해 로그인합니다:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

데이터셋 불러오기

VoxPopuli는 2009-2020년 유럽 의회 이벤트 녹음에서 얻은 데이터로 구성된 대규모 다국어 음성 말뭉치예요. 15개 유럽 언어에 대한 라벨이 붙은 오디오-전사(transcription) 데이터를 담고 있습니다. 이 가이드에서는 네덜란드어 하위 집합을 쓰고 있어요. 다른 하위 집합을 골라도 괜찮습니다.

VoxPopuli나 다른 자동 음성 인식(ASR) 데이터셋은 TTS 모델 훈련에 가장 적합한 선택이 아닐 수 있다는 점을 기억하세요. ASR에 유용하게 만드는 특징(예: 과도한 배경 노이즈)은 보통 TTS에서는 바람직하지 않아요. 하지만 고품질의 다국어·다화자 TTS 데이터셋을 찾는 건 꽤 어려운 일입니다.

데이터를 불러와 볼게요:

>>> from datasets import load_dataset, Audio

>>> dataset = load_dataset("qmeeus/voxpopuli", "nl", split="train")
>>> len(dataset)
20968

파인튜닝에는 20968개 예시로 충분할 거예요. SpeechT5는 오디오 데이터가 16kHz 샘플링 레이트를 가질 것으로 기대하므로, 데이터셋의 예시가 이 요구 사항을 충족하는지 확인하세요:

dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))

데이터 전처리

먼저 사용할 모델 체크포인트를 정의하고 적절한 프로세서를 불러올게요:

>>> from transformers import SpeechT5Processor

>>> checkpoint = "microsoft/speecht5_tts"
>>> processor = SpeechT5Processor.from_pretrained(checkpoint)

SpeechT5 토크나이제이션을 위한 텍스트 정리

먼저 텍스트 데이터를 정리해 볼게요. 텍스트를 처리하려면 프로세서의 토크나이저 부분이 필요합니다:

>>> tokenizer = processor.tokenizer

데이터셋 예시에는 전사(transcription)가 담긴 text 피처(feature)가 있어요. SpeechT5 토크나이저에는 숫자용 토큰이 없으므로, 텍스트의 어떤 숫자든 <unk> 토큰으로 변환된다는 점을 유의하세요. 데이터셋에 숫자가 들어 있다면, 숫자를 문어(文語) 형태로 정규화하는 것을 고려해 보세요.

SpeechT5는 영어로 훈련됐기 때문에 네덜란드어 데이터셋의 특정 문자를 인식하지 못할 수 있어요. 그대로 두면 이런 문자들은 <unk> 토큰으로 변환됩니다. 하지만 네덜란드어에서 à 같은 특정 문자는 음절에 강세를 표시할 때 써요. 텍스트의 의미를 보존하려면 이 문자를 일반 a로 대체할 수 있습니다.

지원되지 않는 토큰을 식별하려면 문자를 토큰으로 다루는 SpeechT5Tokenizer를 이용해 데이터셋의 모든 고유 문자를 추출하세요. 이를 위해 모든 예시의 전사를 하나의 문자열로 이어 붙여 문자 집합으로 변환하는 extract_all_chars 매핑 함수를 작성합니다. dataset.map()에서 모든 전사를 매핑 함수에 한 번에 사용할 수 있도록 batched=Truebatch_size=-1을 설정해야 합니다.

>>> def extract_all_chars(batch):
...     all_text = " ".join(batch["text"])
...     vocab = list(set(all_text))
...     return {"vocab": [vocab], "all_text": [all_text]}


>>> vocabs = dataset.map(
...     extract_all_chars,
...     batched=True,
...     batch_size=-1,
...     keep_in_memory=True,
...     remove_columns=dataset.column_names,
... )

>>> dataset_vocab = set(vocabs["vocab"][0])
>>> tokenizer_vocab = {k for k, _ in tokenizer.get_vocab().items()}

이제 데이터셋 어휘와 토크나이저 어휘 두 가지 문자 집합이 있어요. 데이터셋의 지원되지 않는 문자를 식별하려면 이 두 집합의 차집합을 구하면 됩니다. 결과 집합에는 토크나이저에는 없지만 데이터셋에는 있는 문자가 담겨요.

>>> dataset_vocab - tokenizer_vocab
{' ', 'à', 'ç', 'è', 'ë', 'í', 'ï', 'ö', 'ü'}

이전 단계에서 식별한 지원되지 않는 문자를 처리하려면, 이 문자들을 유효한 토큰으로 매핑하는 함수를 정의하세요. 공백은 토크나이저에서 이미 로 대체되므로 따로 처리할 필요는 없습니다.

>>> replacements = [
...     ("à", "a"),
...     ("ç", "c"),
...     ("è", "e"),
...     ("ë", "e"),
...     ("í", "i"),
...     ("ï", "i"),
...     ("ö", "o"),
...     ("ü", "u"),
... ]


>>> def cleanup_text(inputs):
...     for src, dst in replacements:
...         inputs["text"] = inputs["text"].replace(src, dst)
...     return inputs


>>> dataset = dataset.map(cleanup_text)

이제 텍스트의 특수 문자를 처리했으니, 오디오 데이터로 초점을 옮길 차례예요.

화자(Speakers)

VoxPopuli 데이터셋에는 여러 화자의 음성이 포함돼 있는데, 데이터셋에는 몇 명의 화자가 있을까요? 이를 파악하려면 고유 화자 수와 각 화자가 데이터셋에 기여하는 예시 수를 세어 볼 수 있어요. 데이터셋에 총 20,968개 예시가 있으니, 이 정보는 데이터에서 화자와 예시의 분포를 더 잘 이해하게 해 줍니다.

>>> from collections import defaultdict

>>> speaker_counts = defaultdict(int)

>>> for speaker_id in dataset["speaker_id"]:
...     speaker_counts[speaker_id] += 1

히스토그램을 그리면 각 화자에 대해 얼마나 많은 데이터가 있는지 가늠할 수 있어요.

>>> import matplotlib.pyplot as plt

>>> plt.figure()
>>> plt.hist(speaker_counts.values(), bins=20)
>>> plt.ylabel("Speakers")
>>> plt.xlabel("Examples")
>>> plt.show()
Speakers histogram

히스토그램은 데이터셋의 화자 약 3분의 1이 100개 미만의 예시를 갖고 있고, 약 10명의 화자는 500개 이상의 예시를 갖고 있음을 보여 줘요. 훈련 효율을 높이고 데이터셋 균형을 맞추기 위해, 데이터를 100~400개 예시를 갖는 화자로 제한할 수 있습니다.

>>> def select_speaker(speaker_id):
...     return 100 <= speaker_counts[speaker_id] <= 400


>>> dataset = dataset.filter(select_speaker, input_columns=["speaker_id"])

남은 화자 수를 확인해 볼게요:

>>> len(set(dataset["speaker_id"]))
42

남은 예시 수를 확인해 볼게요:

>>> len(dataset)
9973

약 40명의 고유 화자에서 10,000개 미만의 예시가 남았는데, 이 정도면 충분할 거예요.

예시가 길다면 예시가 적은 일부 화자도 실제로는 오디오가 더 많을 수 있다는 점을 기억하세요. 하지만 각 화자의 총 오디오 양을 파악하려면 전체 데이터셋을 훑어봐야 하는데, 이는 각 오디오 파일을 로드하고 디코딩하는 시간이 많이 걸리는 과정이에요. 그래서 여기서는 이 단계를 건너뛰기로 했습니다.

화자 임베딩 (Speaker embeddings)

TTS 모델이 여러 화자를 구분할 수 있게 하려면 각 예시에 대해 화자 임베딩을 만들어야 해요. 화자 임베딩은 특정 화자의 목소리 특성을 담아내는 추가 입력입니다. 이 화자 임베딩을 생성하려면 SpeechBrain의 사전 훈련된 spkrec-xvect-voxceleb 모델을 사용하세요.

입력 오디오 파형을 받아 해당 화자 임베딩이 담긴 512-요소 벡터를 출력하는 create_speaker_embedding() 함수를 만듭니다.

>>> import os
>>> import torch
>>> from speechbrain.inference.classifiers import EncoderClassifier
>>> from accelerate import Accelerator

>>> spk_model_name = "speechbrain/spkrec-xvect-voxceleb"
>>> device = Accelerator().device
>>> speaker_model = EncoderClassifier.from_hparams(
...     source=spk_model_name,
...     run_opts={"device": device},
...     savedir=os.path.join("/tmp", spk_model_name),
... )


>>> def create_speaker_embedding(waveform):
...     with torch.no_grad():
...         speaker_embeddings = speaker_model.encode_batch(torch.tensor(waveform))
...         speaker_embeddings = torch.nn.functional.normalize(speaker_embeddings, dim=2)
...         speaker_embeddings = speaker_embeddings.squeeze().cpu().numpy()
...     return speaker_embeddings

speechbrain/spkrec-xvect-voxceleb 모델은 VoxCeleb 데이터셋의 영어 음성으로 훈련된 반면, 이 가이드의 훈련 예시는 네덜란드어라는 점에 유의하세요. 이 모델이 네덜란드어 데이터셋에 대해서도 여전히 합리적인 화자 임베딩을 생성할 것이라고 믿지만, 이 가정이 항상 유효한 것은 아닙니다.

최적의 결과를 얻으려면 먼저 대상 음성으로 X-vector 모델을 훈련할 것을 권장해요. 그래야 모델이 네덜란드어에 존재하는 고유한 목소리 특성을 더 잘 포착할 수 있습니다.

데이터셋 처리하기

마지막으로, 모델이 기대하는 형식으로 데이터를 처리해 볼게요. 단일 예시를 받아 SpeechT5Processor 객체로 입력 텍스트를 토크나이즈하고 타깃 오디오를 log-mel 스펙트로그램으로 로드하는 prepare_dataset 함수를 만듭니다. 화자 임베딩도 추가 입력으로 더해야 합니다.

>>> def prepare_dataset(example):
...     audio = example["audio"]

...     example = processor(
...         text=example["text"],
...         audio_target=audio["array"],
...         sampling_rate=audio["sampling_rate"],
...         return_attention_mask=False,
...     )

...     # strip off the batch dimension
...     example["labels"] = example["labels"][0]

...     # use SpeechBrain to obtain x-vector
...     example["speaker_embeddings"] = create_speaker_embedding(audio["array"])

...     return example

단일 예시를 살펴 처리 과정이 올바른지 확인해 볼게요:

>>> processed_example = prepare_dataset(dataset[0])
>>> list(processed_example.keys())
['input_ids', 'labels', 'stop_labels', 'speaker_embeddings']

화자 임베딩은 512-요소 벡터여야 합니다:

>>> processed_example["speaker_embeddings"].shape
(512,)

라벨은 80개의 멜 빈(mel bins)을 가진 log-mel 스펙트로그램이어야 합니다.

>>> import matplotlib.pyplot as plt

>>> plt.figure()
>>> plt.imshow(processed_example["labels"].T)
>>> plt.show()
Log-mel spectrogram with 80 mel bins

참고: 이 스펙트로그램이 헷갈린다면, 낮은 주파수를 플롯 아래쪽에, 높은 주파수를 위쪽에 배치하는 관례에 익숙하기 때문일 수도 있어요. 하지만 matplotlib 라이브러리로 스펙트로그램을 이미지로 그릴 때는 y축이 뒤집혀 스펙트로그램이 거꾸로 보입니다.

이제 처리 함수를 전체 데이터셋에 적용합니다. 이 과정은 5~10분이 걸릴 거예요.

>>> dataset = dataset.map(prepare_dataset, remove_columns=dataset.column_names)

데이터셋의 일부 예시가 모델이 처리할 수 있는 최대 입력 길이(600토큰)보다 길다는 경고가 표시될 거예요. 그 예시들을 데이터셋에서 제거하세요. 여기서는 더 나아가 더 큰 배치 크기를 허용하기 위해 200토큰이 넘는 것은 모두 제거합니다.

>>> def is_not_too_long(input_ids):
...     input_length = len(input_ids)
...     return input_length < 200


>>> dataset = dataset.filter(is_not_too_long, input_columns=["input_ids"])
>>> len(dataset)
8259

다음으로 기본적인 훈련/테스트 분할을 만듭니다:

>>> dataset = dataset.train_test_split(test_size=0.1)

데이터 콜레이터

여러 예시를 배치로 묶으려면 커스텀 데이터 콜레이터를 정의해야 해요. 이 콜레이터는 더 짧은 시퀀스를 패딩 토큰으로 패딩해 모든 예시가 동일한 길이를 갖도록 보장합니다. 스펙트로그램 라벨의 경우 패딩된 부분은 특수 값 -100으로 대체됩니다. 이 특수 값은 모델이 스펙트로그램 손실을 계산할 때 해당 부분을 무시하도록 지시합니다.

>>> from dataclasses import dataclass
>>> from typing import Any, Dict, List, Union


>>> @dataclass
... class TTSDataCollatorWithPadding:
...     processor: Any

...     def __call__(self, features: list[dict[str, Union[list[int], torch.Tensor]]]) -> dict[str, torch.Tensor]:
...         input_ids = [{"input_ids": feature["input_ids"]} for feature in features]
...         label_features = [{"input_values": feature["labels"]} for feature in features]
...         speaker_features = [feature["speaker_embeddings"] for feature in features]

...         # collate the inputs and targets into a batch
...         batch = processor.pad(input_ids=input_ids, labels=label_features, return_tensors="pt")

...         # replace padding with -100 to ignore loss correctly
...         batch["labels"] = batch["labels"].masked_fill(batch.decoder_attention_mask.unsqueeze(-1).ne(1), -100)

...         # not used during fine-tuning
...         del batch["decoder_attention_mask"]

...         # round down target lengths to multiple of reduction factor
...         if model.config.reduction_factor > 1:
...             target_lengths = torch.tensor([len(feature["input_values"]) for feature in label_features])
...             target_lengths = target_lengths.new(
...                 [length - length % model.config.reduction_factor for length in target_lengths]
...             )
...             max_length = max(target_lengths)
...             batch["labels"] = batch["labels"][:, :max_length]

...         # also add in the speaker embeddings
...         batch["speaker_embeddings"] = torch.tensor(speaker_features)

...         return batch

SpeechT5에서 모델 디코더 부분의 입력은 인수 2로 줄어듭니다. 다시 말해 타깃 시퀀스에서 매 다른 시점(timestep)을 버리는 거예요. 그러면 디코더는 두 배 길이의 시퀀스를 예측합니다. 원본 타깃 시퀀스 길이는 홀수일 수 있으므로, 데이터 콜레이터는 배치의 최대 길이를 2의 배수로 내림하는 것을 보장합니다.

>>> data_collator = TTSDataCollatorWithPadding(processor=processor)

모델 훈련하기

프로세서 로드에 쓴 것과 동일한 체크포인트에서 사전 훈련된 모델을 불러옵니다:

>>> from transformers import SpeechT5ForTextToSpeech

>>> model = SpeechT5ForTextToSpeech.from_pretrained(checkpoint)

use_cache=True 옵션은 그래디언트 체크포인팅과 호환되지 않아요. 훈련을 위해 비활성화하세요.

>>> model.config.use_cache = False

훈련 인자를 정의해 볼게요. 여기서는 훈련 과정 중 평가 지표를 계산하지 않습니다. 대신 손실(loss)만 살펴볼게요:

>>> from transformers import Seq2SeqTrainingArguments

>>> training_args = Seq2SeqTrainingArguments(
...     output_dir="speecht5_finetuned_voxpopuli_nl",  # change to a repo name of your choice
...     per_device_train_batch_size=4,
...     gradient_accumulation_steps=8,
...     learning_rate=1e-5,
...     warmup_steps=500,
...     max_steps=4000,
...     gradient_checkpointing=True,
...     fp16=True,
...     eval_strategy="steps",
...     per_device_eval_batch_size=2,
...     save_steps=1000,
...     eval_steps=1000,
...     logging_steps=25,
...     report_to="trackio",
...     load_best_model_at_end=True,
...     greater_is_better=False,
...     label_names=["labels"],
...     push_to_hub=True,
... )

Trainer 객체를 인스턴스화하고 모델, 데이터셋, 데이터 콜레이터를 전달합니다.

>>> from transformers import Seq2SeqTrainer

>>> trainer = Seq2SeqTrainer(
...     args=training_args,
...     model=model,
...     train_dataset=dataset["train"],
...     eval_dataset=dataset["test"],
...     data_collator=data_collator,
...     processing_class=processor,
... )

그리고 이제 훈련을 시작할 준비가 됐어요! 훈련은 몇 시간이 걸릴 거예요. GPU에 따라 훈련 시작 시 CUDA "out-of-memory" 오류가 발생할 가능성도 있어요. 이 경우 per_device_train_batch_size를 2의 인수로 점진적으로 줄이고 gradient_accumulation_steps를 2배로 늘려 보완할 수 있습니다.

>>> trainer.train()

체크포인트를 파이프라인과 함께 쓸 수 있게 하려면, 체크포인트와 함께 프로세서를 저장해야 합니다:

>>> processor.save_pretrained("YOUR_ACCOUNT_NAME/speecht5_finetuned_voxpopuli_nl")

최종 모델을 🤗 Hub에 push합니다:

>>> trainer.push_to_hub()

추론

파이프라인으로 추론하기

좋아요, 이제 모델을 파인튜닝했으니 추론에 쓸 수 있어요! 먼저 해당 파이프라인과 함께 이 모델을 어떻게 쓰는지 살펴볼게요. 여러분의 체크포인트로 "text-to-speech" 파이프라인을 만들어 볼게요:

>>> from transformers import pipeline

>>> pipe = pipeline("text-to-speech", model="YOUR_ACCOUNT_NAME/speecht5_finetuned_voxpopuli_nl")

내레이션하고 싶은 네덜란드어 텍스트 한 조각을 골라 보세요, 예:

>>> text = "hallo allemaal, ik praat nederlands. groetjes aan iedereen!"

파이프라인과 함께 SpeechT5를 쓰려면 화자 임베딩이 필요해요. 테스트 데이터셋의 예시에서 가져와 볼게요:

>>> example = dataset["test"][304]
>>> speaker_embeddings = torch.tensor(example["speaker_embeddings"]).unsqueeze(0)

이제 텍스트와 화자 임베딩을 파이프라인에 전달할 수 있습니다. 나머지는 파이프라인이 처리할 거예요:

>>> forward_params = {"speaker_embeddings": speaker_embeddings}
>>> output = pipe(text, forward_params=forward_params)
>>> output
{'audio': array([-6.82714235e-05, -4.26525949e-04,  1.06134125e-04, ...,
        -1.22392643e-03, -7.76011671e-04,  3.29112721e-04], dtype=float32),
 'sampling_rate': 16000}

그다음 결과를 들어 볼 수 있습니다:

>>> from IPython.display import Audio
>>> Audio(output['audio'], rate=output['sampling_rate'])

직접 추론 실행하기

파이프라인을 쓰지 않고도 동일한 추론 결과를 얻을 수 있지만, 단계가 더 필요합니다.

🤗 Hub에서 모델을 불러옵니다:

>>> model = SpeechT5ForTextToSpeech.from_pretrained("YOUR_ACCOUNT/speecht5_finetuned_voxpopuli_nl")

테스트 데이터셋에서 예시를 골라 화자 임베딩을 얻습니다.

>>> example = dataset["test"][304]
>>> speaker_embeddings = torch.tensor(example["speaker_embeddings"]).unsqueeze(0)

입력 텍스트를 정의하고 토크나이즈합니다.

>>> text = "hallo allemaal, ik praat nederlands. groetjes aan iedereen!"
>>> inputs = processor(text=text, return_tensors="pt")

모델로 스펙트로그램을 만듭니다:

>>> spectrogram = model.generate_speech(inputs["input_ids"], speaker_embeddings)

원한다면 스펙트로그램을 시각화해 볼게요:

>>> plt.figure()
>>> plt.imshow(spectrogram.T)
>>> plt.show()
Generated log-mel spectrogram

마지막으로 보코더(vocoder)를 사용해 스펙트로그램을 소리로 변환합니다.

>>> with torch.no_grad():
...     speech = vocoder(spectrogram)

>>> from IPython.display import Audio

>>> Audio(speech.numpy(), rate=16000)

우리 경험상, 이 모델에서 만족스러운 결과를 얻는 건 어려울 수 있어요. 화자 임베딩의 품질이 중요한 요소로 보입니다. SpeechT5는 영어 x-vector로 사전 훈련됐기 때문에 영어 화자 임베딩을 쓸 때 가장 잘 수행됩니다. 합성 음성이 나쁘게 들린다면 다른 화자 임베딩을 시도해 보세요.

훈련 기간을 늘리는 것도 결과 품질을 높일 가능성이 높아요. 그래도 음성은 영어가 아닌 네덜란드어임이 분명하고, 화자의 목소리 특성도 (예시의 원본 오디오와 비교해 보면) 잘 담아냅니다. 또 다른 실험거리는 모델의 설정(configuration)이에요. 예를 들어 config.reduction_factor = 1을 시도해 결과가 개선되는지 확인해 보세요.

마지막으로 윤리적 고려 사항을 검토하는 것이 필수적이에요. TTS 기술은 유용한 응용 분야가 많지만, 다른 사람의 지식이나 동의 없이 목소리를 사칭하는 것 같은 악의적인 용도로 쓰일 수도 있습니다. TTS를 분별력 있고 책임감 있게 사용해 주세요.

더 알아보기 (Learn more)