·

【机器学习】ChatTTS:开源文本转语音(text-to-speech)大模型天花板

Published at 2024-08-27 23:14:40Viewed 227 times
Please reprint with source link

一、引言

我很愿意推荐一些小而美、高实用模型,比如之前写的YOLOv10霸榜百度词条,很多人搜索,仅需100M就可以完成毫秒级图像识别与目标检测,相关的专栏也是CSDN付费专栏中排行最靠前的。今天介绍有一个小而美、高实用性的模型:ChatTTS。

二、TTS(text-to-speech)模型原理

2.1 VITS 模型架构

由于ChatTTS还没有公布论文,我们也不好对ChatTTS的底层原理进行武断。这里对另一个TTS里程碑模型VITS原理进行简要介绍,让大家对TTS模型原理有多认知。VITS详细论文见链接

VITS论文对训练和推理两个环节分别进行讲述:

2.2 VITS 模型训练

VITS模型训练:在训练阶段,音素(Phonemes)可以被简单理解为文字对应的拼音或音标。它们经过文本编码(Text Encode)和映射(Projection)后,生成了文本的表示形式。左侧的线性谱(Linear Sepctrogram)是从用于训练的音频中提取的 wav 文件的音频特征。这些特征通过后验编码器(Posteritor)生成音频的表示,然后通过训练对齐这两者(在模块 A 中)。节奏也是表达的重要因素,因此还加入了一个随机持续时间预测器(Stochasitic Duration Predictor)模块,根据音素和对齐结果对输出音频长度进行调整。

2.3 VITS 模型推理

VITS模型推理:在推理过程中,输入是文本对应的音素。将映射和对长度采样输入模型,将其转换为语音表示流,然后通过解码器将其转换为音频格式。

根据论文中描述的逻辑,文本数据被转换为音素(即词的拼音)并输入模型。模型学习了音素与音频之间的关系,包括说话者的音质、音高、口音和发音习惯等。

三、ChatTTS 模型实战

3.1 ChatTTS 简介

ChatTTS 是一款专门为对话场景(例如 LLM 助手)设计的文本转语音模型。

3.2 ChatTTS 亮点

1. 对话式 TTS: ChatTTS 针对对话式任务进行了优化,能够实现自然且富有表现力的合成语音。它支持多个说话者,便于生成互动式对话。
2. 精细的控制: 该模型可以预测和控制精细的韵律特征,包括笑声、停顿和插入语。
3. 更好的韵律: ChatTTS 在韵律方面超越了大多数开源 TTS 模型。我们提供预训练模型以支持进一步的研究和开发。

3.3 ChatTTS 数据集

1. 主模型使用了 100,000+ 小时的中文和英文音频数据进行训练。
2. HuggingFace 上的开源版本是一个在 40,000 小时数据上进行无监督微调的预训练模型。

3.4 ChatTTS 部署

3.4.1 创建conda环境

conda create -n chattts
conda activate chattts

3.4.2 拉取源代码

git clone https://github.com/2noise/ChatTTS
cd ChatTTS

3.4.3 安装环境依赖

pip install -r requirements.txt

3.4.4 启动WebUI

export CUDA_VISIBLE_DEVICES=3 #指定显卡
nohup   python examples/web/webui.py --server_name 0.0.0.0 --server_port 8888 > chattts_20240624.out 2>&1 & #后台运行

执行后会自动跳转出webui,地址为server_name:server_port

3.4.5 WebUI推理

个人感觉:其中夹杂着“那个”、“然后”、“嗯...”等口头禅,学的太逼真了,人类说话不就是这样么。。

1. [uv_break]、[laugh]等符号进行断句、微笑等声音控制。
2. Audio Seed:用于初始化随机数生成器的种子值。设置相同的 Audio Seed 可以确保重复生成一致的语音,便于实验和调试。推荐 Seed: 3798-知性女、462-大舌头女、2424-低沉男。
3. Text Seed:类似于 Audio Seed,在文本生成阶段用于初始化随机数生成器的种子值。
4. Refine Text:勾选此选项可以对输入文本进行优化或修改,提升语音的自然度和可理解性。
5. Audio Temperature️:控制输出的随机性。数值越高,生成的语音越可能包含意外变化;数值较低则趋向于更平稳的输出。
6. Top_P:核采样策略,定义概率累积值,模型将只从这个累积概率覆盖的最可能的词中选择下一个词。
7. Top_K:限制模型考虑的可能词汇数量,设置为一个具体数值,模型将只从这最可能的 K 个词中选择下一个词。

3.5 ChatTTS 代码

import os, sys
 
if sys.platform == "darwin":
    os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
 
now_dir = os.getcwd()
sys.path.append(now_dir)
 
import random
import argparse
 
import torch
import gradio as gr
import numpy as np
 
from dotenv import load_dotenv
load_dotenv("sha256.env")
 
import ChatTTS
 
# 音色选项:用于预置合适的音色
voices = {
    "默认": {"seed": 2},
    "音色1": {"seed": 1111},
    "音色2": {"seed": 2222},
    "音色3": {"seed": 3333},
    "音色4": {"seed": 4444},
    "音色5": {"seed": 5555},
    "音色6": {"seed": 6666},
    "音色7": {"seed": 7777},
    "音色8": {"seed": 8888},
    "音色9": {"seed": 9999},
    "音色10": {"seed": 11111},
}
 
def generate_seed():
    new_seed = random.randint(1, 100000000)
    return {
        "__type__": "update",
        "value": new_seed
        }
 
# 返回选择音色对应的seed
def on_voice_change(vocie_selection):
    return voices.get(vocie_selection)['seed']
 
def generate_audio(text, temperature, top_P, top_K, audio_seed_input, text_seed_input, refine_text_flag):
 
    torch.manual_seed(audio_seed_input)
    rand_spk = chat.sample_random_speaker()
    params_infer_code = {
        'spk_emb': rand_spk,
        'temperature': temperature,
        'top_P': top_P,
        'top_K': top_K,
        }
    params_refine_text = {'prompt': '[oral_2][laugh_0][break_6]'}
 
    torch.manual_seed(text_seed_input)
 
    if refine_text_flag:
        text = chat.infer(text,
                          skip_refine_text=False,
                          refine_text_only=True,
                          params_refine_text=params_refine_text,
                          params_infer_code=params_infer_code
                          )
 
    wav = chat.infer(text,
                     skip_refine_text=True,
                     params_refine_text=params_refine_text,
                     params_infer_code=params_infer_code
                     )
 
    audio_data = np.array(wav[0]).flatten()
    sample_rate = 24000
    text_data = text[0] if isinstance(text, list) else text
 
    return [(sample_rate, audio_data), text_data]
 
 
def main():
 
    with gr.Blocks() as demo:
        gr.Markdown("# ChatTTS Webui")
        gr.Markdown("ChatTTS Model: [2noise/ChatTTS](https://github.com/2noise/ChatTTS)")
 
        default_text = "四川美食确实以辣闻名,但也有不辣的选择。[uv_break]比如甜水面、赖汤圆、蛋烘糕、叶儿粑等,这些小吃口味温和,甜而不腻,也很受欢迎。[laugh]"
        text_input = gr.Textbox(label="Input Text", lines=4, placeholder="Please Input Text...", value=default_text)
 
        with gr.Row():
            refine_text_checkbox = gr.Checkbox(label="Refine text", value=True)
            temperature_slider = gr.Slider(minimum=0.00001, maximum=1.0, step=0.00001, value=0.3, label="Audio temperature")
            top_p_slider = gr.Slider(minimum=0.1, maximum=0.9, step=0.05, value=0.7, label="top_P")
            top_k_slider = gr.Slider(minimum=1, maximum=20, step=1, value=20, label="top_K")
 
        with gr.Row():
            voice_options = {}
            voice_selection = gr.Dropdown(label="音色", choices=voices.keys(), value='默认')
            audio_seed_input = gr.Number(value=2, label="Audio Seed")
            generate_audio_seed = gr.Button("\U0001F3B2")
            text_seed_input = gr.Number(value=42, label="Text Seed")
            generate_text_seed = gr.Button("\U0001F3B2")
 
        generate_button = gr.Button("Generate")
 
        text_output = gr.Textbox(label="Output Text", interactive=False)
        audio_output = gr.Audio(label="Output Audio")
 
        # 使用Gradio的回调功能来更新数值输入框
        voice_selection.change(fn=on_voice_change, inputs=voice_selection, outputs=audio_seed_input)
 
        generate_audio_seed.click(generate_seed,
                                  inputs=[],
                                  outputs=audio_seed_input)
 
        generate_text_seed.click(generate_seed,
                                 inputs=[],
                                 outputs=text_seed_input)
 
        generate_button.click(generate_audio,
                              inputs=[text_input, temperature_slider, top_p_slider, top_k_slider, audio_seed_input, text_seed_input, refine_text_checkbox],
                              outputs=[audio_output, text_output])
 
        gr.Examples(
            examples=[
                ["四川美食确实以辣闻名,但也有不辣的选择。比如甜水面、赖汤圆、蛋烘糕、叶儿粑等,这些小吃口味温和,甜而不腻,也很受欢迎。", 0.3, 0.7, 20, 2, 42, True],
                ["What is [uv_break]your favorite english food?[laugh][lbreak]", 0.5, 0.5, 10, 245, 531, True],
                ["chat T T S is a text to speech model designed for dialogue applications. [uv_break]it supports mixed language input [uv_break]and offers multi speaker capabilities with precise control over prosodic elements [laugh]like like [uv_break]laughter[laugh], [uv_break]pauses, [uv_break]and intonation. [uv_break]it delivers natural and expressive speech,[uv_break]so please[uv_break] use the project responsibly at your own risk.[uv_break]", 0.2, 0.6, 15, 67, 165, True],
            ],
            inputs=[text_input, temperature_slider, top_p_slider, top_k_slider, audio_seed_input, text_seed_input, refine_text_checkbox],
        )
    
    parser = argparse.ArgumentParser(description='ChatTTS demo Launch')
    parser.add_argument('--server_name', type=str, default='0.0.0.0', help='Server name')
    parser.add_argument('--server_port', type=int, default=8080, help='Server port')
    parser.add_argument('--root_path', type=str, default=None, help='Root Path')
    parser.add_argument('--custom_path', type=str, default=None, help='the custom model path')
    args = parser.parse_args()
 
    print("loading ChatTTS model...")
    global chat
    chat = ChatTTS.Chat()
 
    if args.custom_path == None:
        chat.load_models()
    else:
        print('local model path:', args.custom_path)
        chat.load_models('custom', custom_path=args.custom_path)
 
    demo.launch(server_name=args.server_name, server_port=args.server_port, root_path=args.root_path, inbrowser=True)
 
 
if __name__ == '__main__':
    main()

通过import ChatTTS和chat = ChatTTS.chat()以及chat.infer对ChatTTS类进行引用,通过装载多个配置项进行不同语音类型的生成。

四、总结

本文首先以VITS为例,对TTS基本原理进行简要讲解,让大家对TTS模型有基本的认知,其次对ChatTTS模型进行step by step实战教学,个人感觉4万小时语音数据开源版本还是被阉割的很严重,可能担心合规问题吧。其次就是没有特定的角色与种子值对应关系,需要人工去归类,期待更多相关的工作诞生。

实用性上来讲,对于语音聊天助手,确实是一种技术上的升级,不需要特别多的GPU资源就可以搭建语音聊天服务,比LLM聊天上升了一个档次。最近好忙,主要在做一个人工智能助手,3天涨了1.3万粉丝。最近计划把ChatTTS应用于这个人工智能助手(微博:面子小行家)的私信回复中,涉及到音频文件与业务相结合。期待我的成果吧!


原文链接:https://blog.csdn.net/weixin_48007632/article/details/139929395

0 人喜欢

Comments

There is no comment, let's add the first one.

弦圈热门内容

最近有人反馈网站卡、打不开,我自己也试过这种情况,已再次对弦圈进行优化

最近这段时间,由于使用弦圈的人数比以前多了,尤其是同一时间使用的人数,这就导致网站加载起来会比较卡,甚至有时候出现502错误,也就是打不开网站。遇到这种问题,一般等一等刷新一下就过去了,但是还是对用户体验造成了不好的影响。昨天开始受到反馈后,我再次进行了测试,发现打开文章的速度确实比打开首页的速度要慢,也就是说弦圈部分页面的加载速度确实有问题,需要优化。接着首页打开的速度其实也慢了一些了。于是我重新优化了一下那些加载比较慢的页面的代码,然后还重新优化了一下后端的一些代码。经过今天的再次优化,目前我打开弦圈的页面,感觉也比之前顺畅了一些。不过目前服务器的配置确实有点吃不消了,CPU倒还好,主要是内存真的不够用了,可能卡主要也是内存不够的原因吧,我目前也在考虑换个内存更高的服务器。18:51更新:刚刚已经将弦圈的前端代码放到另一个内存更多的服务器上,在这个过程中一不小心让网站中断了一会儿。现在迁移已经全部完成!目前弦圈使用了两台服务器,一台2核4G用于前端,另一台2核2G用于后端,还有一台1核1G的云数据库。另外我还给域名挂上了CDN加速,目前来看应该比之前顺畅不少。

在已经掌握大学本科微积分、线代、概率的基础上怎么更进一步学习数学?

知乎提问:如题,我在准备考研数学中终于领略到了一些数学的美妙之处了,现在特别感兴趣,想等着考完试有时间再精进一下也为下一步的学习打打基础,那么请教各位大佬们我该去先去学哪些书呢?数学分析?高等代数?我作为一个普通工科生不太了解这些书的入门顺序,请各位数学大佬指点下,谢谢啦!!!我的回答:如果对微分几何感兴趣,可以尝试读 Loring W Tu微分几何经典入门教材:An Introduction to Manifolds 和 Loring W Tu微分几何教材:Differential Geometry Connections, Curvature, and Characteristic Classes。先读第一本,第一本最简单你的基础完全够了。有了第一本的基础后,可以读第二本。把这两本读懂后可以读 Jürgen Jost黎曼几何与几何分析教材:Riemannian Geometry and Geometric Analysis。如果对代数几何感兴趣,那就先入门交换代数,可以读Zariski交换代数经典教材Commutative Algebra系列(pdf可复制版)、Atiyah交换代数 ...

作为一个年轻的数学工作者,你们是如何独立于导师选定问题的?

知乎提问:感觉有意思的我做不动,我会做的又无趣(且无人关注)。那请问你们是如何自己选定一个有意思且做的动的问题的?我的回答:想要找问题,首先需要阅读很多相关的文献,但是这一步其实很多人都会做了,他们的问题是哪怕读了再多的文献,似乎也不知道有啥问题可做。其实在我看来原因无非那么几个第一是他们自以为自己掌握了正确的学习方法,看过的文献每一个细节都彻底弄懂弄透了,实际上他们连自己学的东西都没搞明白;第二是他们好高骛远,瞧不起一些比较基本的小问题,却不知道很多重要且有趣的理论往往来自于不经意间一些最简单的问题;第三就是科研能力问题了,这方面要展开太多可讲了,如数学成熟度不够、计算能力不足导致无法将脑海里的理论实现,或是想象力不足无法构想出一个一般性的理论,等等。这三点能做好,基本上就能脱离导师,独立自主做出研究成果了。其中第三点是最体现一个人数学天赋的地方了,不过其也是建立在第一点跟第二点的基础之上的。关于如何学习达到research level的程度,不是一两句话就能简单讲完的,可以参考我之前的文章和帖子,其实只要把第一点做好,且做到极致,第二点只要你调整好自己的心态就能做到。想学好数学不是 ...

基础数学几何方向应该如何学习?

前辈们好,本人是一名大二数学系学生,目前大致了解点集拓扑基本概念(但还没怎么做题),代数拓扑看过基本群和同伦型,复变和抽代这学期正在学。之所以问这个问题是因为之前看到中科大梁永祺老师的主页看到了这样一句话:让我感觉非常奇妙,也想见识一下这精华的部分(希望在大四毕业前能做到吧!😭),也激发了我学习代数与几何方向的想法。其中代数方向其实学习路径了解的差不多了,大致就是学完抽象代数后同调、交换和lie代数都可以学了,但几何方向还不甚了解,很多几何方向的课学校都是大三大四才有,甚至开不出来,因此只能自行学习。这个问题其实之前也问了不少前辈,但发现每个人的学习路径(有的是从微分几何上同调那边学,有的是先接触的代数拓扑等)都不一样,而几何方向又十分繁杂,理不清学习顺序,手头上有很多纸质书、电子书、网课等也无从下手;或者有些内容可能比较难且深入某个具体方向,以后不做这个方向可能根本不会用到,不知道该学多少合适。所以想多听取一点建议以便自己之后逐一尝试,例如:学习路径、参考书目、课程视频等等。谢谢各位!😘

雪的不遇者

作为过来人,迎接新客,咳嗽声总在暗地里起伏,瓷勺跌在了地上和空气一样冷淡,我的第二个爱人忘记了今天是什么名字,书啊迷惘的盐,是否意味我站在早晨之外向你们投来无主的目光,那颗心早晚都会走,像以前一样寻章摘句,获得古代传承的快感当一回宾客,接触那些表象的伪装丰富的伪装,六点钟的爱情喧哗这就是我们的日子,我们定义了爱既然你不存在,我同意你的消失在我们谈话的节奏趋于平稳之后,公交也已驶来,多好的机会,我想象着网状碎片藏在脸颊里,人总在落雪之后独立,而你不能独立我们去西伯利亚的天空,俄罗斯的忧郁丛生,深灰色必将成为主的语言一点一滴,在我的血脉里横流,小人曾梦忆,却不知城市有多衰老霜花,彩灯,麦克风,只占世界的诸多分之一,你总爱权衡可秤怎么能装下喜欢走丢的心?还没到来,远山也未拾起衣冠我们只是一个相册,城市深陷其中反复圈定,养一头温良的房子接纳雪的受难,你一直都很坚定这些如同名字一样模糊的骗局引诱我们在云层里分崩离析

关于目前各大平台引流实践的总结:如今各大平台都在封锁流量,在这么一个垄断的大背景下,小平台只能在夹缝中生存......

本文修改自我今天发推的几篇内容。以后我推特也懒得发再英文了,之前一直想搞国际化,国际化个der,我之前一直听信所谓的国外好赚钱的言论。其实真的尝试过才知道,国外也封锁你。现在弦圈的注册用户和流量仍然都是来自国内的,全靠社媒支撑着,SEO零流量,去tm的SEO,以后我也学小红书那样,把所有搜索引擎给屏蔽掉。 现在大家都在封锁自己的流量,不让你将流量引走,推特更是如此,发外链几乎零点击。现在各大社交平台,对引流管得最宽的唯有知乎了,可以给你随意发外链,而且对流量影响不大。 其他平台,如小红书、公众号,连外链都不能发,你只能发文本链接,公众号倒是可以填那个阅读原文,但谁会点? 不过即便是知乎,你发链接也仅仅只是为了引流罢了,知乎的外链有跳转页面,实测相当于屏蔽SEO。所以想要靠知乎发外链搞SEO的省省力吧,用处不大,而且外链本身就是引流的价值大于SEO,与其费时费力搞SEO,不如好好运营社媒。而在知乎上疯狂发外链引流,也不是高枕无忧的。偶尔在某些问题下回答,会引来某些无聊的人的恶意举报,一举报一个准,申诉都没用。 像那种“有什么有趣的网站推荐”、“有什么有深度的网站”,看似绝佳的网站宣传的问 ...

学习应该先追求深度还是广度?

知乎提问:学习应该先追求深度还是广度?我的回答:在我看来应该先追求广度,有了一定的广度再开始追求深度。因为选择深入哪个领域进行学习,是先需要广泛涉猎,对各个领域先有个初步的理解,接着再在这些领域中挑选一个进行深度学习。我当初学数学的时候,也是先大量的看各个数学分支的教材,广泛涉猎。然后挑选其中几个感兴趣的领域:微分几何和代数几何,开始着重学习。最后有了一定的数学成熟度,才开始全力追求深度,决定不仅是做代数几何,而且是代数几何中的算术几何。因此,比起一上来就追求深度,我认为先追求广度更加有效。因为任何一个领域都有成熟度这个概念,你没有一定的成熟度,过早的追求深度看似少走了很多弯路,但不过是拔苗助长。

Linus Kramer之拓扑群notes:Locally Compact Groups and Lie Groups

本notes顾名思义是关于局部紧致群和李群的,开篇先从最基本的拓扑群开始讲起,我当初就是靠这些内容补充拓扑群相关的基础的。为啥没有进一步往下学这个notes,一来是我不需要,二来是这个notes是残缺的,只写到第二章就没有了😅,即只有下图中画圈的部分。目前这本notes在网上已经绝迹,我今天倒是找到另一份残缺版,不过标题改成了Locally Compact Groups,内容倒是比之前的残缺版多一些。既然是属于稀缺资源,因此本notes除了学习价值以外,还有一定的收藏价值,因此我在此将该notes的两个版本都分享给有需要的人。PS:作者不再提供附件下载。

Charles Rezk拓扑学notes:Compactly Generated Spaces

本notes主要讲的是拓扑学中$k$-spaces与$k$-Hausdorff space的相关概念,之所以保存这份notes是因为我当初学习高阶范畴的时候,刚好需要用到这些概念。比如说,无穷范畴的定义就需要用到他们:A topological category is a category which is enriched over $\mathcal{C}\mathcal{G}$, the category of compactly generated (and weakly Hausdorff) topological spaces. The category of topological categories will be denoted by $\mathcal{C}at_{top}$.而抛开它与无穷范畴的联系,仅仅考虑它在拓扑学本身的意义,我觉得这也是本拓扑学方面有趣的notes,不仅是因为有趣的概念如$k$-空间、$k$-豪斯多夫空间,还有紧致生成的空间,还包括一些有趣的结论。总之,对高阶范畴、或者更深入的拓扑学感兴趣的人,可以看看。PS:作者不再提供附件下载。