为了账号安全,请及时绑定邮箱和手机立即绑定

动手体验语音克隆:来自StyleTTS 2和Tortoise-TTS的代码示例与见解

近年来,文本转语音(TTS)技术领域取得了显著的进步,这得益于深度学习技术的发展和大规模数据集的可用性。现代TTS系统能够生成高度逼真且表达丰富的语音,模糊了两者之间的界限。本指南将深入探讨这两个引领革命的杰出模型:StyleTTS 2 和 Tortoise-TTS。这两个模型代表了语音克隆和语音合成的创新方法,各自利用不同的技术手段取得了出色的结果。

本指南旨在作为一个起点,帮助您探索这些强大的TTS系统,简要介绍了这些系统的核心原则、方法和关键特性。此外,我们还将通过代码示例展示如何使用每个项目。请注意,这些示例仅供教学和研究使用,我不会对这些技术的任何非法或不当使用负责。虽然本指南着重介绍了这两个项目,但需要认识到,开源社区提供了丰富的TTS模型生态,每个模型都有其独特的优点和局限性。建议读者探索其他项目,并比较它们的性能和功能,以更全面地了解语音克隆技术不断发展的现状。

本项目指导包含以下几个部分:

  • StyleTTS 2: 本部分探讨了StyleTTS 2的架构及其能力,重点介绍了其新颖的风格扩散技术和对抗训练方法。我们将深入探讨其关键组件,包括风格扩散、大规模语音语言模型(SLMs)的对抗训练、可微分时长建模以及端到端训练。

  • Tortoise-TTS: 本部分研究了Tortoise-TTS的独特方法,突出了其基于自回归解码器和受图像生成技术启发的扩散模型(DDPMs)。我们将探讨其核心特性,包括使用CLVP(条件长度变分程序)进行重排序、条件输入、“Tortoise技巧”及其技术效果。
StyleTTS 2: 运用风格扩散技术和SLM对抗训练方法

StyleTTS 2 因其创新地应用风格扩散和对抗训练与大型语音语言模型(SLMs),因此与众不同。该模型能够捕捉并合成多种语音风格,无需参考音频素材,从而生成高度表现力且自然流畅的语音。

图1:StyleTTS 2在单说话人情况下的训练和推理方案。在处理多说话人的情况时,声学和韵律编码器(标记为E)首先接收目标说话人的参考音频xref并生成参考风格向量c = E(xref),然后。然后,风格扩散模型以此为参考,采样出与xref中说话人相对应的sp和sa。

关键组件包括TTS 2:

  • 风格扩散技术: 这种新颖的技术将说话风格建模为一个潜在的随机变量,从而能够高效地采样和操纵不同风格的语音,这些语音都是根据输入文本条件生成的。通过扩散模型,StyleTTS 2 可以生成多种多样的且富有表现力的语音,无需参考音频。
  • SLM 对抗性训练: 预训练的 SLM(如 WavLM)充当判别器,为模型提供反馈,从而增强合成语音的自然度。这种方法利用了 SLM 中内含的丰富声学和语义信息,显著提高了语音质量。
  • 可微持续时间建模: 这种创新的方法使得模型能够端到端地与 SLM 判别器一起训练,解决先前方法中的不稳定性问题。这使得模型可以直接从 SLM 反馈中学习,从而实现更准确和自然的持续时间控制。
  • 端到端训练: 整个 StyleTTS 2 架构是联合训练的,优化了所有组件,如波形生成。这种整体方法进一步增强了合成语音的整体质量和一致性,使得合成的语音更加自然流畅。
StyleTTS 2 代码示例
准备环境和依赖项

这个初始块将所有准备工作完成。它克隆了StyleTTS2代码库,安装了所需的Python库,下载了一个预训练模型,并准备了参考音频文件。

    %%shell  
    git clone https://github.com/yl4579/StyleTTS2.git  # 克隆代码仓库到本地
    cd StyleTTS2  # 进入克隆下来的文件夹
    pip install SoundFile torchaudio munch torch pydub pyyaml librosa nltk matplotlib accelerate transformers phonemizer einops einops-exts tqdm typing-extensions git+https://github.com/resemble-ai/monotonic_align.git  # 安装依赖库
    sudo apt-get install espeak-ng  # 安装espeak-ng,用于文本转语音
    git-lfs clone https://huggingface.co/yl4579/StyleTTS2-LibriTTS  # 使用git-lfs克隆大型文件,这是为了下载较大的预训练模型文件
    mv StyleTTS2-LibriTTS/Models .  # 将模型文件移动到当前目录
    mv StyleTTS2-LibriTTS/reference_audio.zip .  # 将参考音频文件移动到当前目录
    unzip reference_audio.zip  # 解压参考音频文件
    mv reference_audio Demo/reference_audio  # 将解压后的文件移动到指定目录
    pip install yt_dlp  # 安装yt_dlp,用于从YouTube下载视频和音频
完成了上述步骤后,你已经成功地设置了项目环境,并下载了必要的模型文件和参考音频。下一步可以开始使用这个项目进行语音合成相关的任务。
导入库、设定随机种子及定义辅助工具

这一部分导入了所需的库,设置了随机种子以确保结果的一致性和可重复性,并定义了一些实用工具函数。这些函数包括音频预处理、计算风格嵌入和文本到音位的转换等任务。

    import nltk  
    nltk.download('punkt')  

    %cd StyleTTS2  

    import torch  
    torch.manual_seed(0)  
    torch.backends.cudnn.benchmark = False  
    torch.backends.cudnn.deterministic = True  

    import random  
    random.seed(0)  

    import numpy as np  
    np.random.seed(0)  

    # 加载所需的库  
    import time  
    import random  
    import yaml  
    from munch import Munch  
    import torch  
    from torch import nn  
    import torch.nn.functional as F  
    import torchaudio  
    import librosa  
    from nltk.tokenize import word_tokenize  

    from models import *  
    from utils import *  
    from text_utils import TextCleaner  
    textcleaner = TextCleaner()
加载预训练模型及配置

这里,我们加载了StyleTTS 2所依赖的各种预训练模型,包括一个ASR模型、一个音高提取模型和一个BERT模型。我们还要加载一个配置文件,其中包括模型所需的重要参数。

    %matplotlib inline  

    to_mel = torchaudio.transforms.MelSpectrogram(  
        n_mels=80, n_fft=2048, win_length=1200, hop_length=300)  
    mean, std = -4, 4  

    def length_to_mask(lengths):  
        mask = torch.arange(lengths.max()).unsqueeze(0).expand(lengths.shape[0], -1).type_as(lengths)  
        mask = torch.gt(mask+1, lengths.unsqueeze(1))  
        return mask  

    def preprocess(wave):  
        wave_tensor = torch.from_numpy(wave).float()  
        mel_tensor = to_mel(wave_tensor)  
        mel_tensor = (torch.log(1e-5 + mel_tensor.unsqueeze(0)) - mean) / std  
        return mel_tensor  

    def compute_style(path):  
        wave, sr = librosa.load(path, sr=24000)  
        audio, index = librosa.effects.trim(wave, top_db=30)  
        if sr != 24000:  
            audio = librosa.resample(audio, sr, 24000)  
        mel_tensor = preprocess(audio).to(device)  

        with torch.no_grad():  
            ref_s = model.style_encoder(mel_tensor.unsqueeze(1))  
            ref_p = model.predictor_encoder(mel_tensor.unsqueeze(1))  

        return torch.cat([ref_s, ref_p], dim=1)  

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

    # 加载音素生成器模块  
    import phonemizer  
    global_phonemizer = phonemizer.backend.EspeakBackend(language='en-us', preserve_punctuation=True, with_stress=True)  

    config = yaml.safe_load(open("Models/LibriTTS/config.yml"))  

    # 加载预训练的ASR模型文件  
    ASR_config = config.get('ASR_config', False)  
    ASR_path = config.get('ASR_path', False)  
    text_aligner = load_ASR_models(ASR_path, ASR_config)  

    # 加载预训练的F0模型文件  
    F0_path = config.get('F0_path', False)  
    pitch_extractor = load_F0_models(F0_path)  

    # 加载BERT模型文件  
    from Utils.PLBERT.util import load_plbert  
    BERT_path = config.get('PLBERT_dir', False)  
    plbert = load_plbert(BERT_path)  

    model_params = recursive_munch(config['model_params'])  
    model = build_model(model_params, text_aligner, pitch_extractor, plbert)  
    _ = [model[key].eval() for key in model]  
    _ = [model[key].to(device) for key in model]  

    params_whole = torch.load("Models/LibriTTS/epochs_2nd_00020.pth", map_location='cpu')  
    params = params_whole['net']  

    for key in model:  
        if key in params:  
            print('%s模型加载完成' % key)  
            try:  
                model[key].load_state_dict(params[key])  
            except:  
                from collections import OrderedDict  
                state_dict = params[key]  
                new_state_dict = OrderedDict()  
                for k, v in state_dict.items():  
                    name = k[7:] # 移除前缀 `module.`  
                    new_state_dict[name] = v  
                # 加载模型参数  
                model[key].load_state_dict(new_state_dict, strict=False)  
    #             except:  
    #                 _load(params[key], model[key])  
    _ = [model[key].eval() for key in model]
设置扩散采样工具并进行推理功能

这一步设置了扩散采样器(Diffusion Sampler),它是StyleTTS 2中的一个关键组件。它还定义了我们用来生成语音的主要推理函数(inferenceLFinferenceSTinference),这就像配置驱动语音合成过程的核心引擎。

    from Modules.diffusion.sampler import DiffusionSampler, ADPM2Sampler, KarrasSchedule  

    sampler = DiffusionSampler(  
        model.diffusion.diffusion,  
        sampler=ADPM2Sampler(),  
        sigma_schedule=KarrasSchedule(sigma_min=0.0001, sigma_max=3.0, rho=9.0), # 经验性的参数  
        clamp=False  
    )  

    def inference(text, ref_s, alpha = 0.3, beta = 0.7, diffusion_steps=5, embedding_scale=1):  
        text = text.strip()  
        ps = global_phonemizer.phonemize([text])  
        ps = word_tokenize(ps[0])  
        ps = ' '.join(ps)  
        tokens = text_cleaner(ps)  
        tokens.insert(0, 0)  
        tokens = torch.LongTensor(tokens).to(device).unsqueeze(0)  

        with torch.no_grad():  
            input_lengths = torch.LongTensor([tokens.shape[-1]]).to(device)  
            text_mask = length_to_mask(input_lengths).to(device)  

            t_en = model.text_encoder(tokens, input_lengths, text_mask)  
            bert_dur = model.bert(tokens, attention_mask=(~text_mask).int())  
            d_en = model.bert_encoder(bert_dur).transpose(-1, -2)  

            s_pred = sampler(noise = torch.randn((1, 256)).unsqueeze(1).to(device),  
                                              embedding=bert_dur,  
                                              embedding_scale=embedding_scale,  
                                                features=ref_s, # 与嵌入的说话者相同的参考  
                                                 num_steps=diffusion_steps).squeeze(1)  

            s = s_pred[:, 128:]  
            ref = s_pred[:, :128]  

            ref = alpha * ref + (1 - alpha)  * ref_s[:, :128]  
            s = beta * s + (1 - beta)  * ref_s[:, 128:]  

            d = model.predictor.text_encoder(d_en,  
                                             s, input_lengths, text_mask)  

            x, _ = model.predictor.lstm(d)  
            duration = model.predictor.duration_proj(x)  

            duration = torch.sigmoid(duration).sum(axis=-1)  
            pred_dur = torch.round(duration.squeeze()).clamp(min=1)  

            pred_aln_trg = torch.zeros(input_lengths, int(pred_dur.sum().data))  
            c_frame = 0  
            for i in range(pred_aln_trg.size(0)):  
                pred_aln_trg[i, c_frame:c_frame + int(pred_dur[i].data)] = 1  
                c_frame += int(pred_dur[i].data)  

            # 编码韵律  
            en = (d.transpose(-1, -2) @ pred_aln_trg.unsqueeze(0).to(device))  
            if model_params.decoder.type == "hifigan":  
                asr_new = torch.zeros_like(en)  
                asr_new[:, :, 0] = en[:, :, 0]  
                asr_new[:, :, 1:] = en[:, :, 0:-1]  
                en = asr_new  

            F0_pred, N_pred = model.predictor.F0Ntrain(en, s)  

            asr = (t_en @ pred_aln_trg.unsqueeze(0).to(device))  
            if model_params.decoder.type == "hifigan":  
                asr_new = torch.zeros_like(asr)  
                asr_new[:, :, 0] = asr[:, :, 0]  
                asr_new[:, :, 1:] = asr[:, :, 0:-1]  
                asr = asr_new  

            out = model.decoder(asr,  
                                    F0_pred, N_pred, ref.squeeze().unsqueeze(0))  

        return out.squeeze().cpu().numpy()[..., :-50] # 模型末尾出现异常脉冲,需要后续修复  

    def LFinference(text, s_prev, ref_s, alpha = 0.3, beta = 0.7, t = 0.7, diffusion_steps=5, embedding_scale=1):  
      text = text.strip()  
      ps = global_phonemizer.phonemize([text])  
      ps = word_tokenize(ps[0])  
      ps = ' '.join(ps)  
      ps = ps.replace('``', '"')  
      ps = ps.replace("''", '"')  

      tokens = text_cleaner(ps)  
      tokens.insert(0, 0)  
      tokens = torch.LongTensor(tokens).to(device).unsqueeze(0)  

      with torch.no_grad():  
          input_lengths = torch.LongTensor([tokens.shape[-1]]).to(device)  
          text_mask = length_to_mask(input_lengths).to(device)  

          t_en = model.text_encoder(tokens, input_lengths, text_mask)  
          bert_dur = model.bert(tokens, attention_mask=(~text_mask).int())  
          d_en = model.bert_encoder(bert_dur).transpose(-1, -2)  

          s_pred = sampler(noise = torch.randn((1, 256)).unsqueeze(1).to(device),  
                                            embedding=bert_dur,  
                                            embedding_scale=embedding_scale,  
                                              features=ref_s, # 与嵌入的说话者相同的参考  
                                                num_steps=diffusion_steps).squeeze(1)  

          if s_prev is not None:  
              # 前一个和当前风格的凸线性组合  
              s_pred = t * s_prev + (1 - t) * s_pred  

          s = s_pred[:, 128:]  
          ref = s_pred[:, :128]  

          ref = alpha * ref + (1 - alpha)  * ref_s[:, :128]  
          s = beta * s + (1 - beta)  * ref_s[:, 128:]  

          s_pred = torch.cat([ref, s], dim=-1)  

          d = model.predictor.text_encoder(d_en,  
                                            s, input_lengths, text_mask)  

          x, _ = model.predictor.lstm(d)  
          duration = model.predictor.duration_proj(x)  

          duration = torch.sigmoid(duration).sum(axis=-1)  
          pred_dur = torch.round(duration.squeeze()).clamp(min=1)  

          pred_aln_trg = torch.zeros(input_lengths, int(pred_dur.sum().data))  
          c_frame = 0  
          for i in range(pred_aln_trg.size(0)):  
              pred_aln_trg[i, c_frame:c_frame + int(pred_dur[i].data)] = 1  
              c_frame += int(pred_dur[i].data)  

          # 编码韵律  
          en = (d.transpose(-1, -2) @ pred_aln_trg.unsqueeze(0).to(device))  
          if model_params.decoder.type == "hifigan":  
              asr_new = torch.zeros_like(en)  
              asr_new[:, :, 0] = en[:, :, 0]  
              asr_new[:, :, 1:] = en[:, :, 0:-1]  
              en = asr_new  

          F0_pred, N_pred = model.predictor.F0Ntrain(en, s)  

          asr = (t_en @ pred_aln_trg.unsqueeze(0).to(device))  
          if model_params.decoder.type == "hifigan":  
              asr_new = torch.zeros_like(asr)  
              asr_new[:, :, 0] = asr[:, :, 0]  
              asr_new[:, :, 1:] = asr[:, :, 0:-1]  
              asr = asr_new  

          out = model.decoder(asr,  
                                  F0_pred, N_pred, ref.squeeze().unsqueeze(0))  

      return out.squeeze().cpu().numpy()[..., :-100], s_pred # 模型末尾出现异常脉冲,需要后续修复  

    def STinference(text, ref_s, ref_text, alpha = 0.3, beta = 0.7, diffusion_steps=5, embedding_scale=1):  
        text = text.strip()  
        ps = global_phonemizer.phonemize([text])  
        ps = word_tokenize(ps[0])  
        ps = ' '.join(ps)  

        tokens = text_cleaner(ps)  
        tokens.insert(0, 0)  
        tokens = torch.LongTensor(tokens).to(device).unsqueeze(0)  

        ref_text = ref_text.strip()  
        ps = global_phonemizer.phonemize([ref_text])  
        ps = word_tokenize(ps[0])  
        ps = ' '.join(ps)  

        ref_tokens = text_cleaner(ps)  
        ref_tokens.insert(0, 0)  
        ref_tokens = torch.LongTensor(ref_tokens).to(device).unsqueeze(0)  

        with torch.no_grad():  
            input_lengths = torch.LongTensor([tokens.shape[-1]]).to(device)  
            text_mask = length_to_mask(input_lengths).to(device)  

            t_en = model.text_encoder(tokens, input_lengths, text_mask)  
            bert_dur = model.bert(tokens, attention_mask=(~text_mask).int())  
            d_en = model.bert_encoder(bert_dur).transpose(-1, -2)  

            ref_input_lengths = torch.LongTensor([ref_tokens.shape[-1]]).to(device)  
            ref_text_mask = length_to_mask(ref_input_lengths).to(device)  
            ref_bert_dur = model.bert(ref_tokens, attention_mask=(~ref_text_mask).int())  
            s_pred = sampler(noise = torch.randn((1, 256)).unsqueeze(1).to(device),  
                                              embedding=bert_dur,  
                                              embedding_scale=embedding_scale,  
                                                features=ref_s, # 与嵌入的说话者相同的参考  
                                                 num_steps=diffusion_steps).squeeze(1)  

            s = s_pred[:, 128:]  
            ref = s_pred[:, :128]  

            ref = alpha * ref + (1 - alpha)  * ref_s[:, :128]  
            s = beta * s + (1 - beta)  * ref_s[:, 128:]  

            d = model.predictor.text_encoder(d_en,  
                                             s, input_lengths, text_mask)  

            x, _ = model.predictor.lstm(d)  
            duration = model.predictor.duration_proj(x)  

            duration = torch.sigmoid(duration).sum(axis=-1)  
            pred_dur = torch.round(duration.squeeze()).clamp(min=1)  

            pred_aln_trg = torch.zeros(input_lengths, int(pred_dur.sum().data))  
            c_frame = 0  
            for i in range(pred_aln_trg.size(0)):  
                pred_aln_trg[i, c_frame:c_frame + int(pred_dur[i].data)] = 1  
                c_frame += int(pred_dur[i].data)  

            # 编码韵律  
            en = (d.transpose(-1, -2) @ pred_aln_trg.unsqueeze(0).to(device))  
            if model_params.decoder.type == "hifigan":  
                asr_new = torch.zeros_like(en)  
                asr_new[:, :, 0] = en[:, :, 0]  
                asr_new[:, :, 1:] = en[:, :, 0:-1]  
                en = asr_new  

            F0_pred, N_pred = model.predictor.F0Ntrain(en, s)  

            asr = (t_en @ pred_aln_trg.unsqueeze(0).to(device))  
            if model_params.decoder.type == "hifigan":  
                asr_new = torch.zeros_like(asr)  
                asr_new[:, :, 0] = asr[:, :, 0]  
                asr_new[:, :, 1:] = asr[:, :, 0:-1]  
                asr = asr_new  

            out = model.decoder(asr,  
                                    F0_pred, N_pred, ref.squeeze().unsqueeze(0))  

        return out.squeeze().cpu().numpy()[..., :-50] # 模型末尾出现异常脉冲,需要后续修复

从YouTube下载参考音轨。

这里我们创建一个从YouTube下载音频的函数,我们可以用它作为声音克隆的参考声音。

    from yt_dlp import YoutubeDL  

    def download_audio(youtube_link, output_path="./content"):  
      """从YouTube视频下载音频。  

      参数:  
        youtube_link: YouTube视频的URL。  
        output_path: 保存音频文件的路径。默认为 "./content"。  
      """  

      ydl_opts = {  
          'format': 'bestaudio/best',  
          'postprocessors': [{  
              'key': 'FFmpegExtractAudio',  
              'preferredcodec': 'wav',  
              'preferredquality': '192',  
          }],  
          'outtmpl': f'{output_path}/%(NA)s.%(ext)s'  
      }  

      with YoutubeDL(ydl_opts) as ydl:  
          ydl.download([youtube_link])  

      print(f"音频从 {youtube_link} 下载并保存到 {output_path}")  

    download_audio("https://www.youtube.com/watch?v=VID") # 请将 'VID' 替换成你自己的视频ID  
简单使用StyleTTS 2

现在我们可以使用模型了……我们指定一个参考音频文件和要转换的文字内容,然后使用inference函数来生成语音。我们还会计算实时因子(RTF,Real-Time Factor)来看看合成的速度有多快。

    reference_dicts = {}  
    reference_dicts['Voice_NAME'] = "/content/NA.wav" # 修改语音参考名称(例如:特朗普,或其他你喜欢的名字),以及你的参考文件路径(应为wav格式)  
    text = """  
    StyleTTS 2 是一种利用风格扩散和对抗训练的大规模语音语言模型的文本到语音合成模型,能够生成接近人类水平的语音  
    """  
    # 简单的用法   
    noise = torch.randn(1,1,256).to(device)  
    for k, path in reference_dicts.items():  
        ref_s = compute_style(path)  
        start = time.time()  
        wav = inference(text, ref_s, alpha=0.3, beta=0.7, diffusion_steps=5, embedding_scale=1)  
        rtf = (time.time() - start) / (len(wav) / 24000)  
        print(f"RTF = {rtf:5f}")  
        import IPython.display as ipd  
        print(k + ' 合成:')  
        display(ipd.Audio(wav, rate=24000, normalize=False))  
        print('参考音频:')  
        display(ipd.Audio(path, rate=24000, normalize=False)) # 播放原始参考音频文件
探索不同的情感类型

本部分演示了如何通过使用带有不同情感提示的文本来控制生成语音的情感风格。

    texts = {}  
    ref_s = compute_style("/content/NA.wav")  
    texts['Happy'] = "我们很高兴邀请您加入我们前往过去的旅程,在那里我们将参观人类历史上建造的最令人惊叹的纪念碑之一。"  
    texts['Sad'] = "不得不告诉您一个坏消息,我们在努力恢复繁荣和信心方面遭遇了严重的挫折。"  
    texts['Angry'] = "天文学领域简直就是个大笑话!它的理论基于错误的观察和偏颇的解释!"  
    texts['Surprised'] = "您不是在开玩笑吧?真的在这个池塘里发现了一种新的细菌物种?"  

    for k,v in texts.items():  
        wav = inference(v, ref_s, diffusion_steps=10, alpha=0.3, beta=0.7, embedding_scale=1)  
        print(k + ": ")  
        display(ipd.Audio(wav, rate=24000, normalize=False))

Alpha 和 Beta 决定了我们根据文本选择基于样本的风格而不是参考风格的程度。Alpha 和 Beta 的值越高,风格就越适合文本,但与参考的相似度越低。使用更高的 Beta 值会让合成语音更加有情感,但与参考的相似度会下降。Alpha 决定了音色,而 Beta 决定了韵律。

使用StyleTTS 2的长篇叙述功能:

下面的代码展示了如何使用LFinference函数生成较长文本的语音,这在有声书朗读或创建更长的旁白任务中特别有用。

    # 长篇叙述  
    passage =  """  
    如果水果的供应量超过了家庭的需求,可以通过将其新鲜水果送到附近的市场来作为收入来源,或者通过保存、罐装和制作果酱进行销售。要使这样的事业取得成功,水果和工作质量都必须是顶级的。“家庭自制”这个词具有魔力,当产品既美观又美味时尤其如此;然而,许多粗心和能力不足的人发现,当他们的次品无法在市场上销售时,这个词并没有足够的魔力来挽救它们。通常,大型罐头和保存工厂不仅非常干净,而且还配备了最好的设备,他们雇佣化学家和技术熟练的工人。家庭自制的产品必须非常好才能与这些工厂生产出来的吸引人的商品竞争。然而,对于顶级的家庭自制产品来说,在所有大城市中都有市场。所有顶级杂货店都有购买此类商品的顾客。  
    """  

    path = "/content/NA.wav"  
    s_ref = compute_style(path)  
    sentences = passage.split('.') # 简单地按句点分割  
    wavs = []  
    s_prev = None  
    for text in sentences:  
        if text.strip() == "": continue  
        text += '.' # 再加上去  

        wav, s_prev = LFinference(text,  
                                  s_prev,  
                                  s_ref,  
                                  alpha = 0.3,  
                                  beta = 0.9,  # 使其更好地适应文本  
                                  t = 0.7,  
                                  diffusion_steps=10, embedding_scale=1.5)  
        wavs.append(wav)  
    print('合成音频:')  
    display(ipd.Audio(np.concatenate(wavs), rate=24000, normalize=False))  
    print('参考音频:')  
    display(ipd.Audio(path, rate=24000, normalize=False))

StyleTTS 2 展示了最先进的性能,在各种基准测试中,在主观和客观的测评中都取得了令人印象深刻的结果。它能够生成多样且富有表现力的语音,无需依赖参考音频,为声音克隆和个人化语音合成应用带来了令人兴奋的新机会。

结合自回归解码器和扩散模型Tortoise-TTS

Tortoise-TTS 受到图像生成技术进展的启发,结合了自回归解码器和扩散模型(如DDPM)。这种架构使得 Tortoise-TTS 能够利用这两种方法的优势,实现高质量的语音合成。

Tortoise-TTS的主要特点包括:,

  • 自回归解码器和扩散模型(DDPM): 自回归Transformer将文本转换成一系列“语音标记”,随后由扩散模型(DDPM)解码成高质量的MEL频谱图。这种组合实现了高效且准确的文本到语音转换。
  • 对比语言-语音预训练(CLVP)模型: 受DALL-E启发,Tortoise-TTS采用了类似于CLIP的模型,该模型在文本和语音配对上进行训练,用于评估自回归解码器生成的输出。这使得在进行计算成本高昂的DDPM解码之前,能够选择最高质量的候选者。
  • 条件性输入: 为了改善音色特征并减少搜索空间,Tortoise-TTS引入了来自目标发言者的参考音频片段作为额外输入,对自回归和扩散模型进行条件化。
  • Tortoise技巧: 在自回归潜在空间上而不是离散标记上微调扩散模型(DDPM),显著提升了效率和质量。
  • 大规模训练: Tortoise-TTS从一个包含LibriTTS、HiFiTTS和49,000小时独立收集的有声书和播客数据集的大型数据集中受益。
龟TTS 语音复制代码示例
先设置一下环境

我们这里正在安装必要的软件包,克隆Tortoise-TTS仓库代码,并安装其特定的依赖。

This text contains commands and should not be translated.

%%capture
!pip3 install -U scipy
!git clone https://github.com/jnordberg/tortoise-tts.git
%cd tortoise-tts
!pip3 install -r requirements.txt
!pip3 install transformers==4.19.0 einops==0.5.0 rotary_embedding_torch==0.1.5 unidecode==1.3.5
!python3 setup.py install
!pip install yt_dlp


## 导入所需库并初始化Tortoise-TTS

现在我们导入处理音频和使用Tortoise-TTS所需的代码库。我们还创建了一个`TextToSpeech`对象,这是与模型交互的主要方式。这会从互联网下载必要的模型文件,可能需要一点时间。
import torch  
import torchaudio  
import torch.nn as nn  
import torch.nn.functional as F  
import IPython  
from tortoise.api import TextToSpeech  
from tortoise.utils.audio import load_audio, load_voice, load_voices  
# 这会从Hugging Face Hub 下载Tortoise所需的所有模型。  
tts = TextToSpeech()

## 定义: 文本和语音参数
import os  
text = """  
这种提高性能的方式并不局限于图像。本文描述了一种方法,将图像生成领域的进步应用于语音合成。结果是TorToise——一个表达丰富、多音色的文本转语音系统。  
"""  
# 语音名称  
CUSTOM_VOICE_NAME = "ANY" # 例如:特朗普或其他名称  
# preset 是克隆语音的预设速度  
preset = "standard"  
custom_voice_folder = f"tortoise/voices/{CUSTOM_VOICE_NAME}"  
os.makedirs(custom_voice_folder)

在这里,我们指定要转换成语音的文本,设置一些参数来定义我们的自定义语音。我们创建一个文件夹并命名,用于存储语音数据。我们还定义了`preset`,它影响生成语音的速度。

## 获取语音数据(可以从YouTube或上传文件获得)

这一部分给我们提供了两种获取音频数据训练我们自定义语音的方法:要么使用 `yt-dlp` 从 YouTube 视频下载,要么直接上传 WAV 音频文件。

**选项一:从YouTube上下载,**
从 yt_dlp 导入 YoutubeDL  

def download_audio(youtube_link):  
  """从 YouTube 视频下载音频。  
  参数:  
    youtube_link: YouTube 视频的 URL。  
  """  
  ydl_opts = {  
      'format': 'bestaudio/best',  
      'postprocessors': [{  
          'key': 'FFmpegExtractAudio',  
          'preferredcodec': 'wav',  
          'preferredquality': '192',  
      }],  
      'outtmpl': f'{custom_voice_folder}/%(0)s.%(ext)s'  
  }  
  with YoutubeDL(ydl_opts) as ydl:  
      ydl.download([youtube_link])  
  print(f"从 {youtube_link} 下载音频并保存在 {custom_voice_folder}")  
download_audio("https://www.youtube.com/watch?v=VID") # 替换 VID 为您的视频 ID

**选项 2:从电脑上传**:
从 google.colab import files  
for i, file_data in enumerate(files.upload().values()):  
  with open(os.path.join(自定义语音文件夹路径, f'{i}.wav'), 'wb') as f:  
    f.write(file_data)

## 使用自定义声音生成语音

最后,神奇的部分来了。我们加载之前收集的语音数据,使用 `tts_with_preset` 函数根据我们输入的文本和选择的语音生成语音,然后将其保存为 WAV 文件。我们甚至可以直接在笔记本中听一听。
# 使用自定义声音生成语音代码。
voice_samples, conditioning_latents = load_voice(CUSTOM_VOICE_NAME)  
gen = tts.tts_with_preset(text, voice_samples=voice_samples, conditioning_latents=conditioning_latents,  
                          preset=preset)  
torchaudio.save(f'生成-{CUSTOM_VOICE_NAME}.wav', gen.squeeze(0).cpu(), 24000)  
# 播放生成的音频文件。
IPython.display.Audio(f'生成-{CUSTOM_VOICE_NAME}.wav')


**通过结合这些创新技术和大规模训练,Tortoise-TTS 在语音质量和自然度方面取得了显著成果。它能利用其他领域(如图像生成)的成就,展示了跨领域学习在推动语音合成技术发展方面的潜力。**

所以,我们已经探讨了龟兔-TTS和StyleTTS 2这两项技术的复杂运作机制,它们是文本转语音技术发展的杰出例子。它们提供了强大的工具,用于生成逼真且充满表现力的合成语音,为各种应用场景打开了无限可能。

记住,这只是众多声音克隆示例中的两个。开源社区不断突破界限,根据您的需求,可能还有更合适的模型。

**真正的魔法往往在于定制。你可以利用这些预训练模型,并用你自己的数据进行微调,从而创造出更加独特且符合你需求的语音。这尤其有助于捕捉特定的俚语、口音或其他这些原始训练数据中可能没有的细节。**

最适合你的声音克隆模型取决于你的个人需求以及你愿意投入的训练和定制努力。探索这些技术的旅程是不断进行的,通过不断学习和实验,你可以最大限度地利用声音克隆在项目中的潜力。

**愉快合成啦!😉**

**我的领英主页** : <https://www.linkedin.com/in/ayoub-kirouane3>

**我的Hugging Face页面** : <https://huggingface.co/ayoubkirouane>
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消