Sora技术解析与实战-02

前言

这一章来学习剩下的两小节

第三小节: 基于Transformers,diffusion技术解析+实战

代码地址: sora-tutorial/docs/chapter2/chapter2_3

视频地址:录播回放

论文地址:

2010.11929.pdf (arxiv.org)

2103.15691.pdf (arxiv.org)

2209.12152.pdf (arxiv.org)

第四小节: 声音生成TTS技术解析与实战

视频链接: 【【AI+X组队学习】Sora原理与技术实战:声音生成TTS技术解析与实战】https://www.bilibili.com/video/BV1py421i7Ha?vd_source=d3b670bff794e5b65640845289f35e5f

模型地址: 语音合成-中文-多情感领域-16k-发音人Zhizhe · 模型库 (modelscope.cn)

一、基于Transformers,diffusion技术解析+实战

关于Transformer还有diffusion在前面的学习中已经讲过了,这里主要来学习一下关于ViT、UViT、VViT和Latte的相关知识以及它的最佳实践。这里会在每一个技术学习之后进行代码实践。

1. ViT技术学习

ViT(Vision Transformer)是一种应用于视觉任务的Transformer模型,该模型将标准的Transformer结构直接应用于图像,并对整个图像分类流程进行最少的修改 。具体来讲,ViT会将整幅图像拆分成小图像块,然后把这些小图像块的线性嵌入序列作为Transformer的输入送入网络,最后使用监督学习的方式进行图像分类的训练。

根据论文摘要来看,在视觉中,attention要么与卷积网络(CNN)结合使用,要么用于替换卷积网络的某些组件,同时保持其整体结构到位。他们发现,这种对CNN的依赖是不必要的,直接应用于图像patch序列的纯transformer可以在图像分类任务中表现出色。

当对大量数据进行预训练并传输到多个中型或小型图像识别基准(ImageNet、CIFAR-100、VTAB 等)时,与最先进的卷积网络相比,Vision Transformer (ViT) 获得了出色的结果,同时需要更少的计算资源来训练。

那么其实ViT就是将图像分为多个patch(16x16),再将每个patch投影为固定长度的向量送入Transformer的这样的运行方式,但是这里因为需要对图片进行分类所以需要在输入序列中增加一个特殊的token,该token对应的输出就是最后的类别预测。这部分在Sora技术原理学习里面也介绍过了。

那我们下面就来进行代码实践吧,因为代码里面对每段代码都做了解释所以我们只来看一看结果。

1.1 patch代码实践

这段代码实践我放在这里了,首先是将图像分为patches的代码,我们看一下原图像

image-20240307112510559

然后是经过缩放和裁剪后的图像

image-20240307112641405

因为采用了双线性插值方法 (ResizeMethod.BILINEAR) 进行缩放,并且不保留原始宽高比 (preserve_aspect_ratio=False)。而且在读取图像的时候使用了Interpolation='nearest'所以在裁剪后的图像中可以看到明显的锯齿状边缘。

" Interpolation='nearest'" 是一种插值方法,也称为最近邻插值算法(Nearest Neighbour Interpolation)

这种插值方法是在不生成像素的情况下增加图像像素大小的一种方法,它根据中心像素点的颜色参数模拟出周边像素值,是数码相机特有的放大数码照片的软件手段。当图片放大时,缺少的像素通过直接使用与之最接近的原有的像素的颜色生成,也就是说照搬旁边的像素,这样做的结果是产生了明显可见的锯齿状边缘。

最后看一下patch后的图像

image-20240307113144765

image-20240307113200406

1.2 ViT代码实践

首先下载vit-base-patch16-224模型然后通过模型加载ViTForImageClassification这个顶层容器,

from transformers import ViTForImageClassification
import torch
from modelscope import snapshot_download
​
model_dir = snapshot_download('AI-ModelScope/vit-base-patch16-224')
​
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
​
model = ViTForImageClassification.from_pretrained(model_dir)
model.to(device)

然后加载图片(这里先使用默认图片进行测试,后面找一个网络图片再来识别一下)

from PIL import Image
import requests
​
# url = 'https://ts1.cn.mm.bing.net/th/id/R-C.ef0949ff0d08fea8fc5561b62ae98b3f?rik=QOUvMQbG8MjGtQ&riu=http%3a%2f%2fpic23.nipic.com%2f20120808%2f6608733_131101157000_2.jpg&ehk=L2CnOgjEFTWm1pFf8H29uLRG2ZfxyW%2b9d3ArOjcph3o%3d&risl=&pid=ImgRaw&r=0'
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
image

然后使用ViTImageProcessor预处理器对输入的图像进行处理,以便将其输入到ViT模型中。处理后的数据被存储在inputs变量中,并且其像素值被提取到pixel_values变量中。

最后使用ViT模型对pixel_values进行推断,并获取模型的原始输出(logits)及其形状,然后从模型的输出(即logits)中找出得分最高的类别,并打印出该类别的标签。

import torch
​
with torch.no_grad():
  outputs = model(pixel_values)
logits = outputs.logits
​
prediction = logits.argmax(-1)
print("Predicted class:", model.config.id2label[prediction.item()])

最后查看一下预测的结果: Predicted class: Egyptian cat(埃及猫)

那我们在使用网络图片看一下结果

image-20240307163452953

结果是Predicted class: Eskimo dog, husky可以看出这个预测结果还是可以的。

2. U-ViT技术学习

U-ViT是一种基于Transformer的架构,用于参数化噪声预测网络 。U-ViT将所有的输入都视作token,并在Transformer块之间加入了U型连接。U-ViT的特点是将包括时间、条件和噪声图像patch在内的所有输入视为token,并在浅层和深层之间采用长跳跃连接。

目前U-ViT的相关代码已经开源https://github.com/baofff/U-ViT

那么U-ViT是怎么诞生的呢?

他们发现ViT在各种视觉任务中显示出前景,其中ViT与基于CNN的方法相当甚至更好。所以问题就出现了:在扩散模型中是否有必要依赖基于CNN的U-Net?然后它们就设计了一种简单且通用的基于 ViT 的架构,称为 U-ViT

image-20240307165754710

它遵循Transformer的设计方法,U-ViT将所有输入(包括时间、条件和噪声图像patches)视为token。至关重要的是,U-ViT在受U-Net启发的浅层和深层之间采用了长跳跃连接。直观地说,低级特征对于扩散模型中的像素级预测目标很重要,这种连接可以简化相应预测网络的训练。

此外,U-ViT 还可以在输出前添加额外的 3×3 卷积块,以获得更好的视觉质量。

image-20240307165939093

其他的关于U-ViT的相关实现细节可以看一下论文,因为内容实在太多了。

下面就来进行代码实践

2.1 U-ViT代码实践

首先是克隆代码和安装eniops库,然后设置环境变量并导入所需要的库

einops是一个Python库,用于处理和重塑多维数组,旨在改善深度学习中Tensor操作的可读性和灵活性

然后下载并加载一个预训练的U-ViT模型,针对ImageNet数据集进行训练的,并根据指定的图像大小(256或512)来选择合适的模型。

  1. 设置图像大小:

image_size = "256" #@param [256, 512]
image_size = int(image_size)

这里首先设置了一个字符串image_size为"256",然后通过int()函数将其转换为整数。

  1. 根据图像大小下载相应的模型:

if image_size == 256:
    model_file_download(model_id='thu-ml/imagenet256_uvit_huge',file_path='imagenet256_uvit_huge.pth', cache_dir='/mnt/workspace')
    !mv /mnt/workspace/thu-ml/imagenet256_uvit_huge/imagenet256_uvit_huge.pth /mnt/workspace/U-ViT
else:
    model_file_download(model_id='thu-ml/imagenet512_uvit_huge',file_path='imagenet512_uvit_huge.pth', cache_dir='/mnt/workspace')
    !mv /mnt/workspace/thu-ml/imagenet512_uvit_huge/imagenet512_uvit_huge.pth /mnt/workspace/U-ViT

如果image_size为256,则下载模型thu-ml/imagenet256_uvit_huge,否则下载thu-ml/imagenet512_uvit_huge

  1. 设置模型的参数:

z_size = image_size // 8
patch_size = 2 if image_size == 256 else 4
device = 'cuda' if torch.cuda.is_available() else 'cpu'
  • z_size:图像大小的1/8,用于U-ViT模型中的某些参数。

  • patch_size:如果图像大小为256,则patch大小为2,否则为4。

  1. 创建U-ViT模型:

nnet = UViT(img_size=z_size,
       patch_size=patch_size,
       in_chans=4,
       embed_dim=1152,
       depth=28,
       num_heads=16,
       num_classes=1001,
       conv=False)

这里创建了一个U-ViT模型的实例nnet,并设置了其参数。

  1. 加载预训练模型的权重:

nnet.load_state_dict(torch.load(f'imagenet{image_size}_uvit_huge.pth', map_location='cpu'))

使用.load_state_dict()方法加载预训练的模型权重。权重文件的名字是基于image_size来确定的。

然后使用我们下载的预训练的U-ViT模型进行图像预测

  1. 参数设定

seed = 4321 #@param {type:"number"}
steps = 25 #@param {type:"slider", min:0, max:1000, step:1}
cfg_scale = 3 #@param {type:"slider", min:0, max:10, step:0.1}
class_labels = 207, 360, 387, 974, 88, 979, 417, 279 #@param {type:"raw"}
samples_per_row = 4 #@param {type:"number"}
torch.manual_seed(seed)

这里设定了模型的参数,包括随机种子(seed),生成步骤数(steps),配置缩放因子(cfg_scale),类别标签(class_labels)和每行的样本数(samples_per_row)。torch.manual_seed(seed)用于设置PyTorch的随机种子,以确保结果的可重复性。

  1. 定义扩散模型的β调度

def stable_diffusion_beta_schedule(linear_start=0.00085, linear_end=0.0120, n_timestep=1000):
    _betas = (
        torch.linspace(linear_start ** 0.5, linear_end ** 0.5, n_timestep, dtype=torch.float64) ** 2
    )
    return _betas.numpy()
​
_betas = stable_diffusion_beta_schedule()  # set the noise schedule
noise_schedule = NoiseScheduleVP(schedule='discrete', betas=torch.tensor(_betas, device=device).float())

定义了一个函数stable_diffusion_beta_schedule,用于生成一个从linear_startlinear_end的线性β调度。这个β调度被用于扩散模型的噪声调度。

  1. 准备模型输入

y = torch.tensor(class_labels, device=device)
y = einops.repeat(y, 'B -> (B N)', N=samples_per_row)

这里,class_labels被转换为一个PyTorch张量,并使用einops.repeat进行扩展,以匹配每行的样本数。

  1. 定义模型函数

def model_fn(x, t_continuous):
    t = t_continuous * len(_betas)
    _cond = nnet(x, t, y=y)
    _uncond = nnet(x, t, y=torch.tensor([1000] * x.size(0), device=device))
    return _cond + cfg_scale * (_cond - _uncond)  # classifier free guidance

这个函数定义了模型的前向传播。它接受输入x和连续时间t_continuous,并使用nnet(可能是一个神经网络)进行预测。这里还使用了“无分类器引导”(classifier free guidance)技术,这是一种提高生成样本质量的技术。

无分类器引导是在标记和未标记数据的混合上训练生成模型,在推理过程中,可以控制生成受标签或标题影响的程度

  1. 初始化并采样

z_init = torch.randn(len(y), 4, z_size, z_size, device=device)
dpm_solver = DPM_Solver(model_fn, noise_schedule, predict_x0=True, thresholding=False)
​
with torch.no_grad():
    with torch.cuda.amp.autocast():  # inference with mixed precision
        z = dpm_solver.sample(z_init, steps=steps, eps=1. / len(_betas), T=1.)
        samples = autoencoder.decode(z)

这里首先初始化了噪声z_init,然后创建了一个DPM_Solver对象,用于执行扩散模型的采样过程。使用with torch.no_grad():确保在采样过程中不计算梯度,从而节省内存。torch.cuda.amp.autocast()用于混合精度推理。

  1. 后处理和保存结果

samples = 0.5 * (samples + 1.)
samples.clamp_(0., 1.)
save_image(samples, "sample.png", nrow=samples_per_row * 2, padding=0)
samples = Image.open("sample.png

我们来看一下结果

image-20240307185305314

3. ViViT技术学习

ViViT是利用纯Transformer结构进行视频分类的模型,是ViT在视频输入上的应用

ViViT针对视频长距离依赖的问题,提出一系列变体模型。由于视频的输入是图像输入的数多倍,而Transformer的性能和输入token之间是平方关系,处理性能是一个很大的问题。所以ViViT通过挖掘spatio- temporal tokens,进而encode一系列的transformer层。

它的代码仓库已开源: https://github.com/google-research/scenic

他们提出了用于视频分类的纯Transformer模型。在此架构中执行的主要操作是自注意力,它根据从输入视频中提取的一系列时空标记进行计算的。为了有效地处理视频中可能遇到的大量spatio-temporal tokens,他们提出了几种沿空间和时间维度分解模型的方法(下图右边),以提高效率和可扩展性。

  • 空间-时间注意力机制

  • 因子分解编码器

  • 因子分解自我注意力机制

  • 因子分解点积注意力机制

image-20240307194233461

这里简单介绍一下ViViT因为内容太多建议大家去看原论文: 2103.15691.pdf (arxiv.org)

下面进行代码实践

3.1 ViViT代码实践

我们看一下代码内容,首先是超参数设置

DATA

  1. DATASET_NAME = "organmnist3d": 设置数据集

  2. BATCH_SIZE = 32: 批处理大小设置为32,意味着在每次前向和反向传播中,模型将处理32个样本。

  3. AUTO = tf.data.AUTOTUNE: 使用TensorFlow的自动调整功能来确定数据预处理和加载的最佳并行度。

  4. INPUT_SHAPE = (28, 28, 28, 1): 输入数据的形状是(28, 28, 28, 1)。这表示每个样本是一个28x28x28的3D立方体,并且只有一个通道(例如灰度图像)。

  5. NUM_CLASSES = 11: 数据集中有11个类别。

OPTIMIZER

  1. LEARNING_RATE = 1e-4: 学习率设置为0.0001。在训练过程中更新权重的步长大小。

  2. WEIGHT_DECAY = 1e-5: 权重衰减设置为0.00001。防止模型过拟合。

TRAINING

  1. EPOCHS = 60: 训练周期(或迭代次数)设置为60。

TUBELET EMBEDDING

  1. PATCH_SIZE = (8, 8, 8): 补丁大小设置为8x8x8。

  2. NUM_PATCHES = (INPUT_SHAPE[0] // PATCH_SIZE[0]) ** 2: 计算补丁的数量。这里只计算了x和y方向上的补丁数量,然后取平方。但注意,由于输入数据是3D的,所以还需要考虑z方向上的补丁数量。

ViViT ARCHITECTURE

这些是与ViViT模型架构相关的参数:

  1. LAYER_NORM_EPS = 1e-6: 层归一化的epsilon值设置为0.000001。epsilon是一个小值,用于防止除以零的情况。

  2. PROJECTION_DIM = 128: 投影维度设置为128。这可能是模型内部某些层的输出维度。

  3. NUM_HEADS = 8: 头数设置为8。在多头注意力机制中,这表示模型将同时处理8个不同的注意力“头”。

  4. NUM_LAYERS = 8: 层数设置为8。这表示模型由8个相同的层(或块)组成。

然后下载organmnist3dnumpy形式的数据集

MedMNIST v2:一个用于二维和三维生物医学图像分类的大规模轻量级基准

然后定义了一个名为 download_and_prepare_dataset 的函数,从一个预定义的路径加载一个数据集,并返回训练、验证和测试的视频和标签。定义一个函数:preprocess ,用来处理和准备视频数据和标签,prepare_dataloader 创建三个数据加载器:trainloader(用于训练),validloader(用于验证),和 testloader(用于测试)。这些加载器分别用训练、验证和测试视频数据和标签进行初始化。

然后TubeletEmbedding 层从输入的视频中提取管状(Tubelet)特征,并将其转换为适合进一步处理的二维张量形式。

在ViTs(视觉转换器)中,一幅图像会被划分为多个patch,随后进行空间维度上的展平并投影作为token化方案。对于视频,可以对单个帧重复这一过程。正如作者所建议的,均匀帧采样是一种token化方案,即从视频片段中抽样出帧,然后执行简单的ViT token化操作。

image-20240307200704153

Tubelet Embedding则在捕捉时间信息方面有所不同。从视频中,我们提取出包含多个连续帧的体积。这些体积不仅包含了帧的patch,还包含了时间信息。接着,将这些体积进行展平和投影,以便构建视频tokens。

image-20240307200731667

定义了一个名为PositionalEncoder的类,用于为输入数据添加位置信息的编码器,以便模型可以更好地理解序列的上下文。然后创建一个 空间-时间注意力机制 模型,代码参考Image classification with Vision Transformer (keras.io)

最后进行训练并输出结果,因为加载widget可能比较慢所以耐心等待一下,从训练中测试的准确率可以达到

Test accuracy: 78.36%
Test top 5 accuracy: 97.05%

4. Latte:用于视频生成的潜伏扩散Transformer

这里代码仓库文档里面有介绍如果想读原论文的可以点开2401.03048.pdf (arxiv.org)以及Latte:用于视频生成的潜伏扩散变压器 (maxin-cn.github.io)

这里也再简单了解一下Latte,Latte是一种基于潜在扩散转换器的视频生成模型

Latte首先从输入视频中提取时空tokens,然后采用一系列Transformer模块来建模在潜在空间中的视频分布。为了建模从视频中提取的大量tokens,Latte从输入视频的空间和时间维度分解的角度,引入了四个高效的变体。此外,Latte还通过严谨的实验分析,包括视频片段补丁嵌入、模型变体、时间步长类信息注入、时间位置嵌入和学习策略等,确定了最佳实践。Latte在四个标准视频生成数据集上实现了最先进的性能,并且还可以扩展到文本到视频生成任务中。

二、声音生成TTS技术解析与实战

关于TTS技术解析大家可以看一下视频以及它的模型文档,我们直接跟着文档在魔搭社区上面进行实践,这里最佳实践文档可以参考SambertHifigan个性化语音合成-中文-预训练-16k · 模型库 (modelscope.cn)

1. 准备工作

我们先将下载好的训练声音文件上传到notebook上面

image-20240308132501560

然后创建一个代码块安装最新版tts-autolabel

import sys
!{sys.executable} -m pip install -U tts-autolabel -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html

如果由于网络问题安装失败,可以尝试使用国内镜像源, 在Notebook中新建一个代码块,输入如下代码并运行

!{sys.executable} -m pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

2. 运行TTS-AutoLabel自动标注

首先在Notebook左侧新建TTS-AutoLabel工作文件夹output_training_data

然后运行代码

from modelscope.tools import run_auto_label
​
input_wav = "./ptts/" # 根据自己的声音文件夹修改
output_data = "./output_training_data/"
​
ret, report = run_auto_label(input_wav=input_wav, work_dir=output_data, resource_revision="v1.0.7")

二十条音频标注时间大约30秒。标注完成后在output_training_data中可以获取音频对应的TTS训练数据。

image-20240308133849038

3. 基于PTTS-basemodel微调

获得标注好的训练数据后,进行模型微调

from modelscope.metainfo import Trainers
from modelscope.trainers import build_trainer
from modelscope.utils.audio.audio_utils import TtsTrainType
​
pretrained_model_id = 'damo/speech_personal_sambert-hifigan_nsf_tts_zh-cn_pretrain_16k'
​
dataset_id = "./output_training_data/"
pretrain_work_dir = "./pretrain_work_dir/"
        
# 训练信息,用于指定需要训练哪个或哪些模型,这里展示AM和Vocoder模型皆进行训练
# 目前支持训练:TtsTrainType.TRAIN_TYPE_SAMBERT, TtsTrainType.TRAIN_TYPE_VOC
# 训练SAMBERT会以模型最新step作为基础进行finetune
train_info = {
    TtsTrainType.TRAIN_TYPE_SAMBERT: {  # 配置训练AM(sambert)模型
        'train_steps': 202,               # 训练多少个step 
        'save_interval_steps': 200,       # 每训练多少个step保存一次checkpoint
        'log_interval': 10               # 每训练多少个step打印一次训练日志
    }
}
​
# 配置训练参数,指定数据集,临时工作目录和train_info
kwargs = dict(
    model=pretrained_model_id,                  # 指定要finetune的模型
    model_revision = "v1.0.6",
    work_dir=pretrain_work_dir,                 # 指定临时工作目录
    train_dataset=dataset_id,                   # 指定数据集id
    train_type=train_info                       # 指定要训练类型及参数
)
​
trainer = build_trainer(Trainers.speech_kantts_trainer,
                        default_args=kwargs)
​
trainer.train()

4. 体验效果

import os
from modelscope.models.audio.tts import SambertHifigan
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
​
model_dir = os.path.abspath("./pretrain_work_dir")
​
custom_infer_abs = {
    'voice_name':
    'F7',
    'am_ckpt':
    os.path.join(model_dir, 'tmp_am', 'ckpt'),
    'am_config':
    os.path.join(model_dir, 'tmp_am', 'config.yaml'),
    'voc_ckpt':
    os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan', 'ckpt'),
    'voc_config':
    os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan',
             'config.yaml'),
    'audio_config':
    os.path.join(model_dir, 'data', 'audio_config.yaml'),
    'se_file':
    os.path.join(model_dir, 'data', 'se', 'se.npy')
}
kwargs = {'custom_ckpt': custom_infer_abs}
​
model_id = SambertHifigan(os.path.join(model_dir, "orig_model"), **kwargs)
​
inference = pipeline(task=Tasks.text_to_speech, model=model_id)
output = inference(input="欢迎来到Datawhale的Sora开源学习课程")
​
import IPython.display as ipd
ipd.Audio(output["output_wav"], rate=16000)

我们来听一下最后效果,效果还可以

最后还有一个KAN-TTS的模型大家也可以去试试。