社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

从零复现Google Veo 3:从数据预处理到视频生成的完整Python代码实现指南(下)

机器学习研究组订阅 • 2 月前 • 78 次点击  

Veo 3 模型架构


第二阶段是 Veo 3 模型架构的编码实现。此阶段是整个项目的核心与难点所在,涉及 Veo 3 模型具体架构的设计与代码实现。


Veo 3 支持通过以下两种类型的输入生成高质量视频:

  1. 文本提示 (Text prompts):对期望视频内容的自然语言描述,例如“向日葵绽放的延时摄影 (A timelapse of a sunflower blooming)。

  2. 图像提示 + 文本提示 (Image prompts + Text prompts):图像与文本描述的组合,例如,提供一张向日葵图片,并配合文本“向日葵绽放的延时摄影 (A timelapse of a sunflower blooming)”。


其核心流程可图示如下:


图片


流程概述:

  1. 视频与音频分别通过视频编码器 (Video Encoder)与音频编码器 (Audio Encoder)进行编码。

  2. 图像与文本则利用 Google 提供的 UL2 编码器进行编码。

  3. 编码后的视频与音频(通常会加入一定的噪声),连同嵌入式图像与文本信息,共同输入到一个基于 Transformer 架构的联合去噪器 (Joint Denoiser)。

  4. 该联合去噪器处理这些多模态输入,并生成视频与音频的联合表示。


接下来将逐步实现这些组件。首先,导入此阶段所需的 Python 库。


import imageio  # For reading/writing images  # 用于读写图像from PIL import Image  # For image manipulation  # 用于图像处理import torch  # Core PyTorch library  # PyTorch 核心库import torch.nn as nn  # For neural network modules  # PyTorch 神经网络模块import torch.nn.functional as F  # Functional API (e.g., activation functions)  # PyTorch 函数式 API (如激活函数)import torch.optim as optim  # Optimizers (e.g., Adam, SGD)  # PyTorch 优化器 (如 Adam, SGD)from torch.optim.lr_scheduler import CosineAnnealingLR  # Learning rate scheduler  # 学习率调度器from torch.utils.data import Dataset, DataLoader  # For creating datasets and loading data  # 用于创建数据集和数据加载器import torchaudio  # For audio processing  # 用于音频处理


    
from torchvision import transforms  # For common image transformations  # 用于常见图像变换from diffusers import DDPMScheduler  # For diffusion models scheduling  # Diffusers 库中的 DDPMSchedulerfrom transformers import CLIPProcessor, CLIPModel  # For CLIP model and processor  # Transformers 库中的 CLIP 模型和处理器from einops import rearrange  # For tensor reshaping and repetition  # einops 库,用于张量重排和重复import numpy as np  # For numerical operations  # NumPy 库,用于数值运算import skimage.transform # For image transformations (e.g., resizing, cropping)  # scikit-image 库,用于图像变换 (如缩放、裁剪)# Set computation device to GPU if available, otherwise CPU  # 设置计算设备,优先使用 GPU (如果可用),否则使用 CPUDEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")


数据重组


在数据预处理阶段,最终的数据集仅包含视频文件。然而,Veo 3 模型支持图像与文本结合的输入方式,并且音频数据需要单独处理。因此,首要任务是对数据集进行重组,为每种数据类型建立清晰的结构。


目标数据结构如下:    📁 dataset/     ├── 📁 t2v_pairs/            # dir containing text to videos pairs training data  # 包含文本到视频对训练数据的目录     │    ├── 📄 video_1.mp4      # video file  # 视频文件     │    ├── 📄 video_1.wav      # audio file (extracted from video_1.mp4)  # 音频文件 (从 video_1.mp4 提取)     │    ├── 📄 video_1.txt      # text prompt or annotation for video_1.mp4  # video_1.mp4 的文本提示或标注     │    └── ...     └── 📁 i2v_pairs/            # dir containing image to videos pairs training data  # 包含图像到视频对训练数据的目录          ├── 📄 image_1.jpg      # input image  # 输入图像          ├── 📄 video_1.mp4      # generated video file  # 生成的视频文件          ├── 📄 video_1.wav      # audio file (extracted from video_1.mp4)  # 音频文件 (从 video_1.mp4 提取)          └── ...


此步骤主要涉及常规的 Python 文件操作与数据组织,其具体代码实现已整合至 transform_data.py 文件中,可用于将前述处理得到的修剪后视频数据转换为上述 dataset/ 目录结构。此处不再赘述其实现细节。


dataset/ 目录准备就绪后,需进一步依据音频、图像和视频文件的路径,创建结构化的数据集变量。这将便于在模型训练和推理阶段高效地访问数据。


首先,定义包含成对数据集的目录路径:    # Paths    # 路径定义    i2v_dir = 'dataset/i2v_pairs'    t2v_dir = 'dataset/t2v_pairs'


# Collect files  # 收集文件列表i2v_files = os.listdir(i2v_dir)  t2v_files = os.listdir(t2v_dir)


构建文本到视频 (Text-to-Video, T2V) 的数据对信息。


# Build T2V_INFO  # 构建 T2V_INFO 列表t2v_info = []  t2v_videos = [f for f in t2v_files if f.endswith('.mp4')]  for idx, video_file in enumerate(t2v_videos, start=1):      base_name = os.path.splitext(video_file)[0]      audio_file = f"{base_name}.wav"      # Find matching annotation      # 查找匹配的标注信息    annotation = next((a for a in video_annotations if a['video_path'].split('\\')[-1] == video_file), None)      caption = annotation['summary'if annotation else ''      t2v_info.append({          'id'f"t2v_{idx:03d}",          'video_path': video_file,          'audio_path': audio_file,          'initial_caption': caption      })


类似地,构建图像加文本到视频 (Image+Text-to-Video, I2V) 的数据对信息。


# Build I2V_INFO  # 构建 I2V_INFO 列表i2v_info = []  i2v_images = [f for f in i2v_files if f.endswith('.jpg')]  for idx, image_file in enumerate(i2v_images, start=1):      base_name = os.path.splitext(image_file)[0]      video_file = f"{base_name}.mp4"      audio_file = f"{base_name}.wav"      # Find matching annotation      # 查找匹配的标注信息    annotation = next((a for a in video_annotations if a['video_path'].split('\\')[-1] == video_file), None)      prompt = annotation['summary'if annotation else ''      i2v_info.append({          'id'f"i2v_{idx:03d}",          'image_path': image_file,          'target_video_path': video_file,          'target_audio_path': audio_file,          'prompt': prompt      })


检查生成的数据对数量。


len(i2v_info), len(t2v_info)  # Check the lengths of the info lists  # 检查信息列表的长度#### OUTPUT ####  (517)


为进行模型训练,需将训练数据(包括视频、音频、图像)转换为张量 (tensors)。张量是可供模型处理并在训练过程中优化的多维数组。本实现将使用 torch 库完成数据到张量的转换。


定义两个核心的预处理函数:一个用于视频数据,另一个用于音频数据。同时,声明一些后续流程中将使用的常量与参数。


# Base video frames (meaning the number of frames to extract from each video)  # 基础视频帧数 (即从每个视频中提取的帧数)BASE_VIDEO_FRAMES = 16  # Function to preprocess video files into tensors  # 将视频文件预处理为张量的函数def preprocess_video(path, target_height, target_width):      # Read video frames using imageio      # 使用 imageio 读取视频帧    reader = imageio.get_reader(path, 'ffmpeg')      frames = []      for i, f in enumerate(reader):          if i >= BASE_VIDEO_FRAMES:              break          # Resize each frame to (target_height, target_width) and convert to tensor (C, H, W)          # 将每帧调整大小至 (target_height, target_width) 并转换为张量 (通道, 高, 宽)        resized = skimage.transform.resize(f, (target_height, target_width), anti_aliasing=True)          tensor = torch.from_numpy(resized).permute(201).float()          frames.append(tensor)      # Pad with black frames if not enough frames are present      # 如果帧数不足,则用黑色帧进行填充    if len(frames) < BASE_VIDEO_FRAMES and frames:          pad_frame = torch.zeros_like(frames[0])          frames.extend([pad_frame] * (BASE_VIDEO_FRAMES - len(frames)))      # Stack frames into a tensor of shape (C, T, H, W)      # 将帧堆叠为形状为 (通道, 帧数, 高, 宽) 的张量    return torch.stack(frames, dim=1if frames else None


视频预处理函数定义完毕,其中包含一个关键参数 BASE_VIDEO_FRAMES。接下来定义音频文件的预处理函数,并设定音频相关的特定参数。


# Audio parameters   # 音频参数AUDIO_SAMPLE_RATE = 16000 # Standard sample rate for audio processing (meaning the number of samples per second)  # 音频处理的标准采样率 (即每秒采样数)AUDIO_DURATION_SEC = 2 # Duration of audio clips in seconds (meaning how long each audio clip should be)  # 音频片段时长 (秒) (即每个音频片段的长度)AUDIO_SAMPLES = AUDIO_SAMPLE_RATE * AUDIO_DURATION_SEC # Total number of audio samples (meaning the total number of audio samples in the clip)  # 音频样本总数 (即片段中的总音频采样点数)# This function preprocesses audio files to ensure they are in the correct format and length  # 此函数对音频文件进行预处理,以确保其格式和长度符合要求def preprocess_audio(path):      w, sr = torchaudio.load(path)      


    
# Keep only the first channel if multi-channel      # 如果是多声道音频,则仅保留第一个声道    if w.shape[0] > 1:          w = w[:1, :]      # Resample if needed      # 如果采样率不匹配,则进行重采样    if sr != AUDIO_SAMPLE_RATE:          w = torchaudio.transforms.Resample(sr, AUDIO_SAMPLE_RATE)(w)      # Pad or trim to fixed length      # 填充或裁剪至固定长度    if w.shape[1] < AUDIO_SAMPLES:          w = F.pad(w, (0, AUDIO_SAMPLES - w.shape[1]))      else:          w = w[:, :AUDIO_SAMPLES]      # Normalize      # 归一化处理    max_val = torch.max(torch.abs(w))      if max_val > 0:          w = w / max_val      return w


训练过程关注两种视频张量:基础视频张量 (base video tensor) 与上采样视频张量 (upsampled video tensor)。基础视频张量由原始视频帧构成,而上采样视频张量则通过重复帧以匹配目标帧数来创建。


为统一处理图像、视频和音频数据,需在现有预处理函数的基础上构建两个新的数据加载函数。


# Define constants for video dimensions  # 定义视频尺寸常量UPSAMPLED_VIDEO_HEIGHT = 64 # Height of the upsampled video frames (meaning the height of the video frames after upsampling)  # 上采样视频帧的高度 (即上采样后视频帧的高度)UPSAMPLED_VIDEO_WIDTH = 64 # Width of the upsampled video frames (meaning the width of the video frames after upsampling)  # 上采样视频帧的宽度 (即上采样后视频帧的宽度)BASE_VIDEO_HEIGHT = 32 # Height of the base video frames (meaning the height of the original video frames before upsampling)  # 基础视频帧的高度 (即上采样前原始视频帧的高度)BASE_VIDEO_WIDTH = 32 # Width of the base video frames (meaning the width of the original video frames before upsampling)  # 基础视频帧的宽度 (即上采样前原始视频帧的宽度)T2V_DATA_DIR = 'dataset/t2v_pairs' # Directory for T2V pairs (meaning the directory where the text-to-video pairs are stored)  # T2V 数据对目录 (即存储文本到视频数据对的目录)I2V_DATA_DIR = 'dataset/i2v_pairs' # Directory for I2V pairs (meaning the directory where the image-to-video pairs are stored)  # I2V 数据对目录 (即存储图像到视频数据对的目录)


参数定义完成后,编写 T2V 和 I2V 数据集的加载逻辑。


def load_i2v_item(info):      # Load a base and upsampled version of the input image      # 加载输入图像的基础分辨率版本和上采样版本    img_pil = Image.open(          os.path.join(I2V_DATA_DIR, info['image_path'])      ).convert('RGB')      img_base = transforms.ToTensor()(          img_pil.resize((BASE_VIDEO_WIDTH, BASE_VIDEO_HEIGHT))      )      img_upsampled = transforms.ToTensor()(          img_pil.resize((UPSAMPLED_VIDEO_WIDTH, UPSAMPLED_VIDEO_HEIGHT))      )      # Preprocess target video at base and upsampled resolutions      # 在基础分辨率和上采样分辨率下预处理目标视频    vid_base = preprocess_video(          os.path.join(I2V_DATA_DIR, info['target_video_path']),          BASE_VIDEO_HEIGHT, BASE_VIDEO_WIDTH      )      vid_upsampled = preprocess_video(          os.path.join(I2V_DATA_DIR, info['target_video_path']),          UPSAMPLED_VIDEO_HEIGHT, UPSAMPLED_VIDEO_WIDTH      )      # Preprocess target audio      # 预处理目标音频    aud = preprocess_audio(          os.path.join(I2V_DATA_DIR, info['target_audio_path'])      )      # Return a dictionary containing all modalities and metadata      # 返回一个包含所有模态数据和元数据的字典    return {          'modality''i2v',          'input_image_base': img_base,          'input_image_upsampled': img_upsampled,          'target_video_base': vid_base,          'target_video_upsampled': vid_upsampled,          'target_audio': aud,          'caption': info['prompt'],          'id': info['id']      }


以及 T2V 数据项的加载逻辑:


def load_t2v_item(info):      base_vid = preprocess_video(          os.path.join(T2V_DATA_DIR, info['video_path']),          BASE_VIDEO_HEIGHT, BASE_VIDEO_WIDTH      )      upsampled_vid = preprocess_video(          os.path.join(T2V_DATA_DIR, info['video_path']),          UPSAMPLED_VIDEO_HEIGHT, UPSAMPLED_VIDEO_WIDTH      )      audio = preprocess_audio(          os.path.join(T2V_DATA_DIR, info['audio_path'])      )      if base_vid is not None and upsampled_vid is not None and audio is not None:          return {              'modality''t2v',              'video_base': base_vid,              'video_upsampled': upsampled_vid,              'audio': audio,              'caption'


    
: info['initial_caption'],              'id': info['id']          }      return None


至此,数据重组的最后一步——将数据转换为适合训练的张量格式——已准备就绪。


# Load Text-to-Video (T2V) data:  # 加载文本到视频 (T2V) 数据:# - For each entry in t2v_info, call load_t2v_item to preprocess video, audio, and caption.  # - 对 t2v_info 中的每个条目,调用 load_t2v_item 函数预处理视频、音频和字幕。# - Only keep non-None results.  # - 仅保留非 None 的结果。t2v_data = [      d for d in [          load_t2v_item(i) for i in tqdm(t2v_info, desc="Loading T2V data")      ] if d  ]  # Load Image-to-Video (I2V) data:  # 加载图像到视频 (I2V) 数据:# - For each entry in i2v_info, call load_i2v_item to preprocess image, video, audio, and prompt.  # - 对 i2v_info 中的每个条目,调用 load_i2v_item 函数预处理图像、视频、音频和提示。# - Only keep non-None results.  # - 仅保留非 None 的结果。i2v_data = [      d for d in [          load_i2v_item(i) for i in tqdm(i2v_info, desc="Loading I2V data")      ] if d  ]


将更新后的张量数据合并为一个统一的数据变量。


raw_data = t2v_data + i2v_data  # Combine both datasets  # 合并两个数据集


原始数据已成功转换为适合训练的格式。在进入视频或音频编码器实现之前,需创建一个 MultiModalDataset 类,用于统一管理数据加载与批处理。该类将以结构化方式封装所有转换后的数据,便于访问不同模态(视频、音频、图像、文本)及其对应的字幕信息。


创建多模态数据集 (MultiModalDataset)


采用面向对象编程(OOP)是组织训练数据的恰当方式。此处将创建一个 MultiModalDataset 类,负责 T2V 和 I2V 数据集的数据加载与处理。


BATCH_SIZE = 1  # Batch size for DataLoader (how many samples to process in one batch)  # DataLoader 的批处理大小 (即单批次处理的样本数量)class MultiModalDataset(Dataset):      def __init__(self, data_list):          # Store the list of data samples (each sample is a dict for t2v or i2v)          # 存储数据样本列表 (每个样本是一个用于 t2v 或 i2v 的字典)        self.data = data_list      def __len__(self):          # Return the number of samples in the dataset          # 返回数据集中的样本总数        return len(self.data)      def __getitem__(self, idx):          # Get the sample at the given index          # 获取指定索引处的样本        item = self.data[idx]          # Use the caption (should be set in your data)          # 使用样本中的字幕信息        caption_string = item['caption']          if item['modality'] == 't2v':              # For text-to-video: return video, audio, and a dummy image tensor              # 对于文本到视频 (t2v) 模态:返回视频、音频和一个占位图像张量            return {                  'modality''t2v',                  'video_base': item['video_base'],                  'video_upsampled': item['video_upsampled'],                  'audio': item['audio'],                  'raw_caption': caption_string,                  # Dummy image tensor (all zeros) for t2v, shape matches a single video frame                  # 为 t2v 提供的占位图像张量 (全零),其形状与单帧视频匹配                'input_image_base': torch.zeros_like(item['video_base'][:0]),              }          else:  # For image-to-video  # 对于图像到视频 (i2v) 模态            return {                  'modality''i2v',                  'video_base': item['target_video_base'],                  'video_upsampled': item['target_video_upsampled'],                  'audio': item['target_audio'],                  'raw_caption': caption_string,                  # Use the actual input image tensor for i2v                  # 为 i2v 使用实际的输入图像张量                


    
'input_image_base': item['input_image_base'],              }


通过在原始数据之上实例化 MultiModalDataset 类,即可构建数据集对象。该类继承自 torch.utils.data.Dataset,后者是 PyTorch 提供的标准接口,用于以批处理方式加载和处理数据。 


# Create the dataset and dataloader for training  # 为训练创建数据集和数据加载器# - train_dataset: wraps the raw_data list using the MultiModalDataset class  # - train_dataset: 使用 MultiModalDataset 类封装 raw_data 列表# - train_dataloader: DataLoader for batching and shuffling during training  # - train_dataloader: 用于训练时进行批处理和数据打乱的 DataLoadertrain_dataset = MultiModalDataset(raw_data)  train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)


至此,数据已准备就绪。接下来,我们将着手实现 Veo 3 模型的具体架构,包括视频编码器、音频编码器、条件编码器、联合去噪器等多个核心组件。


视频变分自编码器 (Video VAE)


原始数据经过预处理并重组为 PyTorch Dataset 后,为训练视频生成模型奠定了基础。现在,我们开始构建模型的第一个组件:视频变分自编码器 (Video Variational Autoencoder, Video VAE)。


变分自编码器(VAE)是一种广泛应用于无监督学习,特别是生成模型领域的神经网络架构。它主要由两部分构成:

  1. 编码器 (Encoder):将输入数据(如图像或视频)映射到一个低维的潜在空间 (Latent Space) 表示。

  2. 解码器 (Decoder):将潜在空间表示映射回原始数据空间,从而实现对输入数据的重建。


其基本架构如下图所示:


图片


其工作流程如下:

  1. 编码器接收输入张量,并将其编码为一个潜在向量 (Latent Vector)。该向量捕获了输入数据的关键特征(例如,图像或视频帧的形状、颜色、纹理等)。

  2. 编码器通常由一系列卷积层组成,这些卷积层在提取特征的同时,逐步对输入张量进行下采样。

  3. 解码器接收此潜在向量,并重建原始输入数据。它通常采用转置卷积层 (Transposed Convolutional Layers) 将潜在表示上采样回原始维度。


以下是 VideoVAE 的代码实现


VIDEO_LATENT_CHANNELS = 4 # Number of channels in the latent space for video (meaning the number of feature maps in the latent representation)  # 视频潜在空间中的通道数 (即潜在表示中特征图的数量)class VideoVAE(nn.Module):      def __init__(self):          super().__init__()          # Encoder: Input shape (B, 3, F, H, W) -> Latent (B, C_lat, F/2, H/8, W/8)          # 编码器: 输入形状 (批量大小, 3, 帧数, 高, 宽) -> 潜在表示 (批量大小, 潜在通道数, 帧数/2, 高/8, 宽/8)        self.e = nn.Sequential(              # First 3D conv: downsample F, H, W by 2, output channels 32              # 第一个3D卷积层: 帧数、高、宽均下采样2倍,输出通道数为32            nn.Conv3d(332, kernel_size=3, stride=(222), padding=1), nn.SiLU(),              # Second 3D conv: downsample H, W by 2, output channels 64              # 第二个3D卷积层: 高、宽下采样2倍,输出通道数为64            nn.Conv3d(3264, kernel_size=3, stride=(122), padding=1), nn.SiLU(),              # Third 3D conv: downsample H, W by 2, output channels 128              # 第三个3D卷积层: 高、宽下采样2倍,输出通道数为128            nn.Conv3d(64128, kernel_size=3, stride=(122), padding=1), nn.SiLU(),              # Final 3D conv: output latent channels, no downsampling              # 最后一个3D卷积层: 输出潜在通道,无下采样            nn.Conv3d(128VIDEO_LATENT_CHANNELS, kernel_size=3, stride=1, padding=1)          )          # Decoder: Latent -> Output (reverse of encoder)          # 解码器: 潜在表示 -> 输出 (编码器的逆过程)        self.d = nn.Sequential(              # First transpose conv: keep shape, output channels 128              # 第一个转置卷积层: 保持形状,输出通道数为128            nn.ConvTranspose3d(VIDEO_LATENT_CHANNELS128, kernel_size=3, stride=1, padding=1), nn.SiLU(),              # Second transpose conv: upsample H, W by 2, output channels 64              # 第二个转置卷积层: 高、宽上采样2倍,输出通道数为64            nn.ConvTranspose3d(12864, kernel_size=3, stride=(122), padding=1, output_padding=(011)), nn.SiLU(),              # Third transpose conv: upsample H, W by 2, output channels 32              # 第三个转置卷积层: 高、宽上采样2倍,输出通道数为32            nn.ConvTranspose3d(6432, kernel_size=3, stride=(122), padding=1, output_padding=(011


    
)), nn.SiLU(),              # Final transpose conv: upsample F, H, W by 2, output channels 3 (RGB)              # 最后一个转置卷积层: 帧数、高、宽均上采样2倍,输出通道数为3 (RGB)            nn.ConvTranspose3d(323, kernel_size=3, stride=(222), padding=1, output_padding=1), nn.Sigmoid()          )      def encode(self, x):          # Pass input through encoder          # 将输入数据传递给编码器        return self.e(x)      def decode(self, x):          # Pass latent through decoder          # 将潜在表示传递给解码器        return self.d(x)


所设计的 VideoVAE 包含一个编码器(encoder)和一个解码器(decoder)。编码器负责将输入视频压缩为低维的潜在表示(latent representation),而解码器则从该潜在表示重建原始视频。编码器采用 3D 卷积处理时间维度(帧)以及空间维度(高和宽),解码器则利用转置卷积将潜在表示上采样回原始视频的形状。


Veo 3 同样具备音频生成能力,因此,我们接下来为音频数据构建一个类似的 VAE。


音频变分自编码器 (Audio VAE)


音频数据的处理方式与 VideoVAE 类似,但由于音频是一维时序数据,因此采用 1D 卷积。编码器接收音频输入并将其压缩为潜在表示,解码器则从该潜在表示重建音频。


定义 AudioVAE 类,其功能与 VideoVAE 类似,但专用于处理音频数据。


AUDIO_LATENT_CHANNELS = 16  # Number of channels in the latent space for audio (meaning the number of feature maps in the latent representation)  # 音频潜在空间中的通道数 (即潜在表示中特征图的数量)class AudioVAE(nn.Module):      def __init__(self):          super().__init__()          # Encoder: Input shape (B, 1, 32000) -> Latent (B, AUDIO_LATENT_CHANNELS, 1000)          # 编码器: 输入形状 (批量大小, 1, 32000) -> 潜在表示 (批量大小, 音频潜在通道数, 1000)        self.e = nn.Sequential(              # First 1D conv: downsample audio, output 16 channels              # 第一个1D卷积层: 下采样音频,输出16个通道            nn.Conv1d(116, kernel_size=32, stride=8, padding=12), nn.SiLU(),              # Second 1D conv: further downsample, output latent channels              # 第二个1D卷积层: 进一步下采样,输出潜在通道            nn.Conv1d(16AUDIO_LATENT_CHANNELS, kernel_size=32, stride=4, padding=14)          )          # Decoder: Latent -> Output (reverse of encoder)          # 解码器: 潜在表示 -> 输出 (编码器的逆过程)        self.d = nn.Sequential(              # First transpose conv: upsample, output 16 channels              # 第一个转置卷积层: 上采样,输出16个通道            nn.ConvTranspose1d(AUDIO_LATENT_CHANNELS16, kernel_size=32, stride=4, padding=14), nn.SiLU(),              # Second transpose conv: upsample to original length, output 1 channel              # 第二个转置卷积层: 上采样至原始长度,输出1个通道            nn.ConvTranspose1d(161, kernel_size=32, stride=8, padding=12),              nn.Tanh()  # Output in range [-1, 1]  # 输出范围在 [-1, 1] 之间        )      def encode(self, x):          # Pass input through encoder          # 将输入数据传递给编码器        return self.e(x)      def decode(self, x):          # Pass latent through decoder          # 将潜在表示传递给解码器        return self.d(x)


AudioVAE 类专为处理音频数据而设计,特别是针对采样率为 16kHz、时长为 2 秒的音频文件。该模型将音频编码为潜在表示,并能将其解码回原始音频格式。


至此,Veo 3 的 VideoVAE 和 AudioVAE 两个核心组件已实现。接下来,需要实现负责处理文本和图像输入的编码器。


条件编码器  Conditional Encoder   (采用 CLIP替代 UL2)


Google Veo 3 采用 UL2 模型进行条件编码。考虑到 UL2 模型规模较大,不易在本地设备运行,本实现将采用 OpenAI 的 CLIP 模型作为替代。CLIP 模型相对轻量,且同样具备强大的文本与图像编码能力。


图片


训练过程中的文本与图像数据将输入条件编码器,该编码器负责将文本和图像编码为统一的联合表示 (Joint Representation)。此联合表示随后将作为条件,引导视频生成模型的行为。


创建一个条件编码器类,能够接收字幕和图像作为输入,并输出编码后的文本与图像特征。





    
TEXT_MAX_LENGTH = 77 # Maximum length for text inputs (meaning the maximum number of tokens in the text)  # 文本输入的最大长度 (即文本中的最大词元数量)class ConditioningEncoder(nn.Module):      def __init__(self, model_name="openai/clip-vit-large-patch14"):          super().__init__()          # Load the pretrained CLIP model and processor          # 加载预训练的 CLIP 模型和处理器        self.model = CLIPModel.from_pretrained(model_name)          self.processor = CLIPProcessor.from_pretrained(model_name)      def get_text_embeds(self, text):          # Preprocess the input text and move tensors to the configured device          # 预处理输入文本并将张量移至配置的设备        inputs = self.processor(              text=text,              return_tensors="pt",              padding=True,              truncation=True,              max_length=TEXT_MAX_LENGTH          ).to(DEVICE)          # Get text embeddings from the CLIP model          # 从 CLIP 模型获取文本嵌入        return self.model.get_text_features(**inputs)      def get_image_embeds(self, image):          # If input is a torch tensor, avoid rescaling (already preprocessed)          # 如果输入是 PyTorch 张量,则避免重新缩放 (假设已预处理)        if isinstance(image, torch.Tensor):              inputs = self.processor(                  images=image,                  return_tensors="pt",                  do_rescale=False              ).to(DEVICE)          else:              # Otherwise, preprocess the image normally              # 否则,正常预处理图像            inputs = self.processor(                  images=image,                  return_tensors="pt"              ).to(DEVICE)          # Get image embeddings from the CLIP model          # 从 CLIP 模型获取图像嵌入        return self.model.get_image_features(**inputs)


处理不同类型数据(视频、音频、文本、图像)的编码器类均已定义完毕。根据 Google 的技术报告,Veo 3 架构中采用了一个基于 Transformer 的去噪组件。接下来,我们将构建一个基于 Transformer 的模型,负责对视频、音频和文本嵌入进行去噪处理。该模型将接收来自 VideoVAE、AudioVAE 和 ConditioningEncoder 的潜在表示,并通过一系列 Transformer 层进行处理。


Transformer 模块 (Transformer Block)


Transformer 是一种基于自注意力机制的神经网络架构,最初为处理序列数据(如文本)而设计,现已广泛应用于图像、视频等多种模态。它通过自注意力机制权衡输入序列中不同部分的重要性,从而有效捕捉数据内部的长程依赖关系。


Transformer 的具体实现可以非常复杂,也可以相对简化,具体取决于应用场景和可用训练数据量。考虑到本项目为学习性质,我们将为多模态模型实现一个简化版的 Transformer 架构。


# It is a function that applies adaptive layer normalization modulation  # 该函数应用自适应层归一化调制def modulate(x, shift, scale):      # Applies adaptive layer normalization modulation: scales and shifts normalized input      # 应用自适应层归一化调制:对归一化后的输入进行缩放和移位    return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)  class TransformerBlock(nn.Module):      def __init__(self, dim, heads):          super().__init__()          # Layer normalization before attention          # 注意力之前的层归一化        self.norm1 = nn.LayerNorm(dim)          # Multi-head self-attention          # 多头自注意力机制        self.attn = nn.MultiheadAttention(dim, heads, batch_first=True)          # Layer normalization before MLP          # MLP之前的层归一化        self.norm2 = nn.LayerNorm(dim)          # Feed-forward MLP          # 前馈多层感知机        self.mlp = nn.Sequential(              nn.Linear(dim, dim * 4),              nn.GELU(),              nn.Linear(dim * 4, dim)          )          # Adaptive LayerNorm modulation for attention and MLP (6 outputs)          # 用于注意力和MLP的自适应层归一化调制(6个输出)        self.adaLN_modulation = nn.Sequential(              nn.SiLU(),              nn.Linear(dim, 6 * dim, bias=True)          )      def forward(self, x, c):          # Compute modulation parameters from conditioning vector c          # 从条件向量 c 计算调制参数        shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1)          # Apply modulated attention with gating          # 应用带门控的调制注意力        x = x + gate_msa.unsqueeze(1) * self.attn(              modulate(self.norm1(x), shift_msa, scale_msa), x, x          )[0]          # Apply modulated MLP with gating          # 应用带门控的调制MLP        x = x + gate_mlp.unsqueeze(1) * self.mlp(              modulate(self.norm2(x), shift_mlp, scale_mlp)          )          return x  class FinalLayer(nn.Module):      def __init__(self, hidden_size, patch_size, out_channels):          super().__init__()          # Final layer normalization (no affine params)          # 最终层归一化(无仿射参数)        self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)          # Linear projection to patch size          # 线性投影到补丁大小        self.linear = nn.Linear(hidden_size, patch_size, bias=True)          # Adaptive LayerNorm modulation for final layer (2 outputs)          # 用于最终层的自适应层归一化调制(2个输出)        self.adaLN_modulation = nn.Sequential(              nn.SiLU(),              nn.Linear(hidden_size, 2 * hidden_size, bias=True)          )      def forward(


    
self, x, c):          # Compute shift and scale from conditioning vector c          # 从条件向量 c 计算移位和缩放参数        shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)          # Apply modulation and final linear projection          # 应用调制和最终的线性投影        x = modulate(self.norm_final(x), shift, scale)          x = self.linear(x)          return x


本实现的 Transformer 模型包含以下核心组件:

  1. ConditioningEncoder (条件编码器):已在前述章节定义,负责将文本和图像输入编码为嵌入向量。

  2. TransformerBlock (Transformer 模块):实现多头自注意力机制和带有自适应层归一化(Adaptive LayerNorm)调制的前馈网络。

  3. FinalLayer (最终层):对 Transformer 模块的输出进行归一化处理,并通过线性投影将其映射到期望的输出维度(例如,补丁大小),同样应用调制。


这是一个相对简化的 Transformer 实现,但已涵盖了处理文本和图像输入所需的基本要素。


时间步嵌入生成 (Timestep Embedding Generation)


在扩散模型中,时间步嵌入 (Timestep Embedding) 至关重要,它负责对扩散过程中的各个时间步进行编码,使模型能够感知和利用去噪过程的进展信息。


该模块通常基于时间步索引生成正弦位置编码(Sinusoidal Positional Encoding),然后通过一个小型多层感知机(MLP)将其投影到一个更高维度的空间,以便有效地作为模型的条件输入。


class TimestepEmbedding(nn.Module):      def __init__(self, dim):          super().__init__()          self.dim = dim          # MLP to project the embedding to a higher dimension          # MLP 将嵌入投影到更高维度        self.mlp = nn.Sequential(              nn.Linear(dim, dim * 4),              nn.SiLU(),              nn.Linear(dim * 4, dim * 4)          )      def forward(self, t):          # Compute sinusoidal timestep embedding (like in transformers/diffusion models)          # 计算正弦时间步嵌入 (类似于 Transformer/扩散模型中的做法)        half_dim = self.dim // 2          # Exponential decay for frequencies          # 频率的指数衰减        emb = torch.exp(              torch.arange(half_dim, device=t.device) * -(np.log(10000.0) / (half_dim - 1))          )          # Outer product: shape (batch, half_dim)          # 外积: 形状 (批量大小, half_dim)        emb = t.float()[:, None] * emb[None, :]          # Concatenate sin and cos embeddings          #拼接 sin 和 cos 嵌入        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)          # Pass through MLP for further projection          #通过 MLP 进行进一步投影        return self.mlp(emb)


TimestepEmbedding 类实现了计算时间步正弦嵌入的功能。其 forward 方法负责生成嵌入向量,并通过一个 MLP 将其投影到更高维度。


联合去噪模型 (Joint Denoising Model, JDM)


接下来,将实现 JointDenoisingTransformer 类,它将集成前述定义的所有组件。


JointDenoisingTransformer 的核心功能是接收带噪声的视频和音频输入、当前的时间步信息以及上下文(文本或图像嵌入),并输出去噪后的视频和音频。


图片


JointDenoisingTransformer 的实现将分解为以下几个关键组件:

  1. InputProjector:负责将带噪声的输入(视频和音频)投影为补丁 (Patches) 并进行嵌入,即将其转换为适合后续处理的格式。

  2. TokenBuilder:基于投影得到的补丁构建词元 (Tokens) 序列,其中包含代表视频和音频补丁的词元,以及特殊的模态指示词元。

  3. PatchTransformer:将 Transformer 架构应用于这些词元序列,通过自注意力机制捕捉序列内部的关系与依赖。

  4. OutputProjector:将经过 Transformer 处理的词元序列投影回视频和音频的输出空间,即将处理后的词元转换回期望的去噪后视频和音频格式。

  5. JointDenoisingTransformer:作为主类,集成上述所有组件并定义完整的前向传播逻辑,协调整个去噪过程。


首先定义 InputProjector 类,负责处理带噪声视频和音频输入的补丁化与嵌入,以及时间步和上下文信息的嵌入。


TEXT_EMBED_DIM = 768  # Dimension of text embeddings (meaning the size of the text feature vector)  # 文本嵌入的维度 (即文本特征向量的大小)VIDEO_PATCH_SIZE_F = 2 # Video patch size in frames (meaning how many frames each video patch contains)  # 视频补丁在帧维度的大小 (即每个视频补丁包含的帧数)VIDEO_PATCH_SIZE_H = 2 # Video patch size in height (meaning how many pixels each video patch contains in height)  # 视频补丁在高度维度的大小 (即每个视频补丁在高度上包含的像素数)VIDEO_PATCH_SIZE_W = 2 # Video patch size in width (meaning how many pixels each video patch contains in width)  # 视频补丁在宽度维度的大小 (即每个视频补丁在宽度上包含的像素数)AUDIO_PATCH_SIZE = 16 # Audio patch size (meaning how many audio samples each audio patch contains)  # 音频补丁大小 (即每个音频补丁包含的音频样本数)# This class projects noisy video and audio inputs into patches, embeds them, and combines them with timestep and context embeddings.  # 此类将带噪声的视频和音频输入投影为补丁,对其进行嵌入,并与时间步和上下文嵌入相结合。class InputProjector(nn.Module):      def __init__(self, is_upsampler, embed_dim):          super().__init__()          self.is_upsampler = is_upsampler          self.embed_dim = embed_dim          # Timestep embedding: encodes diffusion timestep and projects to embed_dim          # 时间步嵌入:编码扩散时间步并投影到 embed_dim        self.t_embedder = nn.Sequential(TimestepEmbedding(embed_dim), nn.Linear(embed_dim * 4, embed_dim))          # Context embedding: projects text/image embedding to embed_dim          # 上下文嵌入:将文本/图像嵌入投影到 embed_dim        self.c_embedder = nn.Linear(TEXT_EMBED_DIM, embed_dim)          # Video patching: patch size and patcher for video latent tensor          # 视频分块:视频潜在张量的分块大小和分块器        self.video_patch_size = VIDEO_PATCH_SIZE_F * VIDEO_PATCH_SIZE_H * VIDEO_PATCH_SIZE_W * VIDEO_LATENT_CHANNELS          self.video_patcher = nn.Conv3d(              VIDEO_LATENT_CHANNELS, embed_dim,              kernel_size=(VIDEO_PATCH_SIZE_F, VIDEO_PATCH_SIZE_H, VIDEO_PATCH_SIZE_W),              stride=(VIDEO_PATCH_SIZE_F, VIDEO_PATCH_SIZE_H, VIDEO_PATCH_SIZE_W)          )          # Audio patching: patch size and patcher for audio latent tensor          # 音频分块:音频潜在张量的分块大小和分块器        self.audio_patch_size = AUDIO_PATCH_SIZE * AUDIO_LATENT_CHANNELS          self.audio_patcher = nn.Conv1d(              AUDIO_LATENT_CHANNELS, embed_dim,              kernel_size=AUDIO_PATCH_SIZE, stride=AUDIO_PATCH_SIZE          )          # If upsampler, add patcher for low-res video input          # 如果是上采样器,则为低分辨率视频输入添加分块器        if self.is_upsampler:              self.low_res_patcher = nn.Conv3d(                  VIDEO_LATENT_CHANNELS, embed_dim,                  kernel_size=(VIDEO_PATCH_SIZE_F, VIDEO_PATCH_SIZE_H, VIDEO_PATCH_SIZE_W),                  stride=(VIDEO_PATCH_SIZE_F, VIDEO_PATCH_SIZE_H, VIDEO_PATCH_SIZE_W)              )      def forward(self, noisy_video, noisy_audio, t, context, low_res_video=None):          # Patch and embed the noisy video: (B, embed_dim, F', H', W') -> (B, N_patches, embed_dim)          


    
# 对带噪视频进行分块和嵌入:(批量大小, embed_dim, F', H', W') -> (批量大小, 补丁数量, embed_dim)        video_patches = rearrange(self.video_patcher(noisy_video), 'b c f h w -> b (f h w) c')          # Patch and embed the noisy audio: (B, embed_dim, L') -> (B, N_audio_patches, embed_dim)          # 对带噪音频进行分块和嵌入:(批量大小, embed_dim, L') -> (批量大小, 音频补丁数量, embed_dim)        audio_patches = rearrange(self.audio_patcher(noisy_audio), 'b c l -> b l c')          # Embed timestep and context, then sum for conditioning          # 嵌入时间步和上下文,然后求和作为条件信息        t_emb = self.t_embedder(t)          c_emb = self.c_embedder(context)          cond_emb = t_emb + c_emb          # If upsampler, patch and embed the low-res video          # 如果是上采样器,则对低分辨率视频进行分块和嵌入        low_res_patches = None          if self.is_upsampler and low_res_video is not None:              low_res_patches = rearrange(self.low_res_patcher(low_res_video), 'b c f h w -> b (f h w) c')          # Return all projected patches and conditioning embedding          # 返回所有投影后的补丁和条件嵌入        return video_patches, audio_patches, cond_emb, low_res_patches


InputProjector 类实现了以下功能:

  • 将带噪声的视频和音频输入投影为补丁。

  • 对补丁进行嵌入,并将其与时间步和上下文嵌入相结合。


接下来定义 TokenBuilder 类,负责从投影的补丁创建词元序列。该类将处理视频和音频的特殊模态词元的创建,并在上采样模式下可选地为低分辨率视频创建特殊词元。 


class TokenBuilder(nn.Module):      def __init__(self, is_upsampler, embed_dim):          super().__init__()          self.is_upsampler = is_upsampler          self.embed_dim = embed_dim          # Learnable token for video modality          # 视频模态的可学习词元        self.video_token = nn.Parameter(torch.randn(11, embed_dim))          # Learnable token for audio modality          # 音频模态的可学习词元


    
        self.audio_token = nn.Parameter(torch.randn(11, embed_dim))          # Learnable token for low-res video (only if upsampler)          # 低分辨率视频的可学习词元 (仅当是上采样器时)        if self.is_upsampler:              self.low_res_token = nn.Parameter(torch.randn(11, embed_dim))      def forward(self, B, video_patches, audio_patches, low_res_patches=None):          # Start with video token, video patches, audio token, audio patches          # 以视频词元、视频补丁、音频词元、音频补丁开始构建序列        tokens = [              self.video_token.repeat(B, 11),  # (批量大小, 1, embed_dim)              video_patches,                     # (批量大小, 视频补丁数量, embed_dim)              self.audio_token.repeat(B, 11),  # (批量大小, 1, embed_dim)              audio_patches                      # (批量大小, 音频补丁数量, embed_dim)          ]          # If upsampler, prepend low-res token and low-res patches          # 如果是上采样器,则在序列前端添加低分辨率词元和低分辨率补丁        if self.is_upsampler and low_res_patches is not None:              tokens = [                  self.low_res_token.repeat(B, 11),  # (批量大小, 1, embed_dim)                  low_res_patches                      # (批量大小, 低分辨率补丁数量, embed_dim)              ] + tokens          # Concatenate all tokens along the sequence dimension          # 沿序列维度拼接所有词元        return torch.cat(tokens, dim=1)


TokenBuilder 类实现了以下功能:

  • 为视频和音频补丁创建特殊的模态指示词元。

  • 在上采样模式下,可选择为低分辨率视频补丁创建特殊的模态指示词元。


现在定义 PatchTransformer 类,它将 Transformer 架构应用于 TokenBuilder 创建的词元序列。该类负责处理位置嵌入以及对词元序列进行 Transformer 模块的堆叠处理。


# --- Latent Space Dimensions ---  # --- 潜在空间维度 ---VIDEO_LATENT_FRAMES = 8 # Number of frames in the video latent space (meaning how many frames are represented in the video latent)  # 视频潜在空间中的帧数 (即视频潜在表示中包含的帧数)BASE_VIDEO_LATENT_H = 4 # Base video latent height (meaning the height of the video latent representation before upsampling)  # 基础视频潜在表示的高度 (即上采样前视频潜在表示的高度)BASE_VIDEO_LATENT_W = 4 # Base video latent width (meaning the width of the video latent representation before upsampling)  # 基础视频潜在表示的宽度 (即上采样前视频潜在表示的宽度)UPSAMPLED_VIDEO_LATENT_H = 8 # Upsampled video latent height (meaning the height of the video latent representation after upsampling)  # 上采样视频潜在表示的高度 (即上采样后视频潜在表示的高度)UPSAMPLED_VIDEO_LATENT_W = 8 # Upsampled video latent width (meaning the width of the video latent representation after upsampling)  # 上采样视频潜在表示的宽度 (即上采样后视频潜在表示的宽度)AUDIO_LATENT_SAMPLES = AUDIO_SAMPLES // 32 # Number of audio samples in the latent space (meaning how many audio samples are represented in the audio latent)  # 音频潜在空间中的样本数 (即音频潜在表示中包含的音频样本数)# --- Transformer (DiT) Architecture ---  # --- Transformer (DiT) 架构参数 ---DIT_EMBED_DIM = 256 # Main dimension for the transformer  # Transformer 的主要嵌入维度DIT_DEPTH = 4       # Number of transformer blocks  # Transformer 模块的层数DIT_HEADS = 4       # Number of attention heads  # 注意力头的数量# Patch PatchTransformer for processing video and audio patchesclass PatchTransformer(nn.Module):    def __init__(self, is_upsampler, embed_dim):        super().__init__()        self.is_upsampler = is_upsampler        self.embed_dim = embed_dim        # Determine video patch dimensions based on upsampler mode        video_h = UPSAMPLED_VIDEO_LATENT_H if is_upsampler else BASE_VIDEO_LATENT_H        video_w = UPSAMPLED_VIDEO_LATENT_W if is_upsampler else BASE_VIDEO_LATENT_W        # Calculate number of video patches        video_patches = (            (video_h // VIDEO_PATCH_SIZE_H) *            (video_w // VIDEO_PATCH_SIZE_W) *            (VIDEO_LATENT_FRAMES // VIDEO_PATCH_SIZE_F)        )        # Calculate number of audio patches        audio_patches = AUDIO_LATENT_SAMPLES // AUDIO_PATCH_SIZE        # Total number of tokens (patches + special tokens)        num_patches = video_patches + audio_patches + 2  # +2 for special tokens        if is_upsampler:            # Add low-res video patches and token if upsampler            low_res_video_patches = (                (BASE_VIDEO_LATENT_H // VIDEO_PATCH_SIZE_H) *                (BASE_VIDEO_LATENT_W // VIDEO_PATCH_SIZE_W) *                (VIDEO_LATENT_FRAMES // VIDEO_PATCH_SIZE_F)            )            num_patches += low_res_video_patches + 1  # +1 for low-res token        # Learnable positional embedding for all tokens        self.pos_embed = nn.Parameter(torch.randn(1int(num_patches), embed_dim))        # Stack of transformer blocks        self.transformer_blocks = nn.ModuleList([            TransformerBlock(embed_dim, DIT_HEADS)            for _ in range(DIT_DEPTH)        ])    def forward(self, x, cond_emb):        # Adjust positional embedding if input has more tokens than expected        if x.shape[1] > self.pos_embed.shape[1]:            extra_pos = torch.randn(1, x.shape[1] - self.pos_embed.shape[1], self.embed_dim, device=x.device)            pos_embed = torch.cat([self.pos_embed, extra_pos], dim=1)        else:            pos_embed = self.pos_embed[:, :x.shape[1]]        # Add positional embedding to input tokens        x += pos_embed        # Pass through each transformer block with conditioning        for block in self.transformer_blocks:            x = block(x, cond_emb)        return x


PatchTransformer 类实现了以下功能:

  • 将位置嵌入应用于词元序列。

  • 通过多个 Transformer 模块处理词元序列,以捕捉序列内部的关系和依赖性。

第四个组件是 OutputProjector 类,它负责将经过 Transformer 处理的词元序列投影回期望的去噪后视频和音频输出。该类将处理最终的层归一化和线性投影操作。

class OutputProjector(nn.Module):      def __init__(self, is_upsampler, embed_dim, video_patch_size, audio_patch_size):          super().__init__()          self.is_upsampler = is_upsampler          self.embed_dim = embed_dim          # Final projection layers for video and audio outputs          # 用于视频和音频输出的最终投影层        self.final_video = FinalLayer(embed_dim, video_patch_size, VIDEO_LATENT_CHANNELS)          self.final_audio = FinalLayer(embed_dim, audio_patch_size, AUDIO_LATENT_CHANNELS)      def forward(self, x, cond_emb, video_patches_shape, audio_patches_shape, noisy_audio_shape, low_res_patches_shape=None):          # Determine the starting index for video patches in the token sequence          # 确定词元序列中视频补丁的起始索引        start_idx = 1          if self.is_upsampler and low_res_patches_shape is not None:              # If upsampler, skip low-res token and its patches              # 如果是上采样器,则跳过低分辨率词元及其补丁            start_idx += 1 + low_res_patches_shape[1]          # Extract video patch tokens from the sequence          # 从序列中提取视频补丁词元        vid_out_patches = x[:, start_idx : start_idx + video_patches_shape[1]]          # Project video patch tokens to latent video patches          # 将视频补丁词元投影为潜在视频补丁        vid_pred = self.final_video(vid_out_patches, cond_emb)          # Rearrange the predicted video patches back to (B, C, F, H, W)          # 将预测的视频补丁重排回 (批量大小, 通道数, 帧数, 高, 宽) 的形状        vid_pred = rearrange(              vid_pred,              'b (f h w) (p1 p2 p3 c) -> b c (f p1) (h p2) (w p3)',              p1=VIDEO_PATCH_SIZE_F,              p2=VIDEO_PATCH_SIZE_H,              p3=VIDEO_PATCH_SIZE_W,              h=(UPSAMPLED_VIDEO_LATENT_H if self.is_upsampler else BASE_VIDEO_LATENT_H) // VIDEO_PATCH_SIZE_H,              w=(UPSAMPLED_VIDEO_LATENT_W if self.is_upsampler else BASE_VIDEO_LATENT_W) // VIDEO_PATCH_SIZE_W,              f=VIDEO_LATENT_FRAMES // VIDEO_PATCH_SIZE_F          )          # Extract audio patch tokens from the sequence          # 从序列中提取音频补丁词元        aud_out_patches = x[:, start_idx + video_patches_shape[1] + 1 : start_idx + video_patches_shape[1] + 1 + audio_patches_shape[1]]          # Project audio patch tokens to latent audio patches          # 将音频补丁词元投影为潜在音频补丁        aud_pred = self.final_audio(aud_out_patches, cond_emb)          # Rearrange the predicted audio patches back to (B, C, L)          # 将预测的音频补丁重排回 (批量大小, 通道数, 长度) 的形状        aud_pred = rearrange(              aud_pred,              'b l (p c) -> b c (l p)',              p=AUDIO_PATCH_SIZE,              c=AUDIO_LATENT_CHANNELS          )          # If needed, interpolate audio to match the original noisy audio length  


    
        # 如果需要,对音频进行插值以匹配原始带噪音频的长度        if aud_pred.shape[2] != noisy_audio_shape[2]:              aud_pred = F.interpolate(aud_pred, size=noisy_audio_shape[2], mode='linear', align_corners=False)          return vid_pred, aud_pred


OutputProjector 类实现了以下功能:

  • 将经过 Transformer 处理的词元序列投影回视频和音频的输出空间。

  • 执行最终的层归一化和线性投影,生成去噪后的输出。

最后,将上述所有组件整合到主类 JointDenoisingTransformer 中,该类将输入投影、词元构建、补丁转换和输出投影等步骤串联起来,形成完整的前向传播逻辑。


class JointDenoisingTransformer(nn.Module):      def __init__(self, is_upsampler=False):          super().__init__()          self.is_upsampler = is_upsampler          self.embed_dim = DIT_EMBED_DIM          # Projects noisy inputs into patch embeddings and combines with timestep/context          # 将带噪输入投影为补丁嵌入,并与时间步/上下文信息结合        self.input_proj = InputProjector(is_upsampler, self.embed_dim)          # Builds token sequence from patch embeddings (with special tokens)          # 从补丁嵌入构建词元序列 (包含特殊模态词元)        self.token_builder = TokenBuilder(is_upsampler, self.embed_dim)          # Processes tokens with transformer blocks and conditioning          # 使用 Transformer 模块和条件信息处理词元序列        self.patch_transformer = PatchTransformer(is_upsampler, self.embed_dim)          # Projects transformer output tokens back to video/audio latent space          # 将 Transformer 输出的词元投影回视频/音频潜在空间        self.output_proj = OutputProjector(              is_upsampler, self.embed_dim,              self.input_proj.video_patch_size,              self.input_proj.audio_patch_size          )      def forward(self, noisy_video, noisy_audio, t, context, low_res_video=None):          B = noisy_video.shape[0]          # Project noisy video/audio into patch embeddings and get conditioning embedding          # 将带噪视频/音频投影为补丁嵌入,并获取条件嵌入        video_patches, audio_patches, cond_emb, low_res_patches = self.input_proj(              noisy_video, noisy_audio, t, context, low_res_video          )          # Build token sequence from patches (and low-res patches if upsampler)          # 从补丁 (如果是上采样器,则包括低分辨率补丁) 构建词元序列


    
        x = self.token_builder(B, video_patches, audio_patches, low_res_patches)          # Process tokens through transformer blocks with conditioning          # 通过带条件的 Transformer 模块处理词元序列        x = self.patch_transformer(x, cond_emb)          # Project tokens back to denoised video/audio predictions          # 将词元投影回预测的去噪后视频/音频        vid_pred, aud_pred = self.output_proj(              x, cond_emb,              video_patches.shape, audio_patches.shape, noisy_audio.shape,              low_res_patches.shape if low_res_patches is not None else None          )          return vid_pred, aud_pred


JointDenoisingTransformer 类已定义完毕。它是一个用于对视频和音频数据进行去噪的神经网络模型,集成了输入投影、词元构建、补丁转换和输出投影等多个组件。该模型能够处理基础分辨率和上采样分辨率的任务,通过处理带噪声的视频和音频输入来生成去噪后的输出。


VideoVAE、AudioVAE、ConditioningEncoder 和 JointDenoisingTransformer 等核心类均已定义。接下来,将进入模型训练阶段,实例化这些组件,并利用先前准备的数据集来优化模型参数。


Veo 3 模型训练


第三阶段是基于前述架构进行模型训练。在详细介绍训练循环逻辑之前,首先实例化模型的各个组件,并为训练过程做好准备。


DENOISER_TIMESTEPS = 1000  # Number of denoising timesteps (the number of steps in the denoising process)  # 去噪过程的时间步总数# Instantiate the base denoiser (for base resolution) and move to device  # 实例化基础分辨率去噪器,并将其移至指定计算设备base_denoiser = JointDenoisingTransformer(is_upsampler=False).to(DEVICE)  # Instantiate the upsampler denoiser (for upsampled resolution) and move to device  # 实例化上采样分辨率去噪器,并将其移至指定计算设备


    
upsampler_denoiser = JointDenoisingTransformer(is_upsampler=True).to(DEVICE)  # Create a DDPM noise scheduler for the denoising process  # 为去噪过程创建一个 DDPM 噪声调度器noise_scheduler = DDPMScheduler(num_train_timesteps=DENOISER_TIMESTEPS, beta_schedule="linear")


上述代码实例化了用于基础分辨率视频和音频去噪的 base_denoiser,以及用于上采样分辨率去噪的 upsampler_denoiser。noise_scheduler 负责管理去噪过程中的噪声水平。


# --- Training Hyperparameters ---  # --- 训练超参数 ---NUM_EPOCHS = 10 # Number of epochs for training (meaning how many times the model will see the entire dataset)  # 训练的总轮数 (即模型完整遍历数据集的次数)LEARNING_RATE = 1e-4 # Learning rate for the optimizer (meaning how quickly the model learns)  # 优化器的学习率 (即模型学习速率的快慢)# Create Video and Audio VAEs and move them to the configured device  # 创建视频和音频 VAE,并将其移至配置的计算设备video_vae = VideoVAE().to(DEVICE)  audio_vae = AudioVAE().to(DEVICE)  # Create the Conditioning Encoder (CLIP-based) and move to device  # 创建条件编码器 (基于CLIP),并将其移至计算设备conditioning_encoder = ConditioningEncoder().to(DEVICE)  # Gather all parameters from VAEs, denoisers, and conditioning encoder for optimization  # 收集来自 VAE、去噪器和条件编码器的所有参数,以供优化器使用all_params = (      list(video_vae.parameters()) +      list(audio_vae.parameters()) +      list(base_denoiser.parameters()) +      list(upsampler_denoiser.parameters()) +      list(conditioning_encoder.parameters())  )  # Create AdamW optimizer for all model parameters  # 为所有模型参数创建 AdamW 优化器optimizer = optim.AdamW(all_params, lr=LEARNING_RATE)  # Define mean squared error loss for training  # 定义用于训练的均方误差损失函数loss_fn = nn.MSELoss()  # Cosine annealing learning rate scheduler for optimizer  # 为优化器配置余弦退火学习率调度器lr_scheduler = CosineAnnealingLR(      optimizer,      T_max=len(train_dataloader) * NUM_EPOCHS  )


实例化 VideoVAE、AudioVAE 和 ConditioningEncoder 模型,并配置了用于训练的优化器和学习率调度器。优化器选用 AdamW,学习率调度策略为 CosineAnnealingLR。损失函数采用均方误差 (MSE) 来训练去噪器。


训练循环的核心目标是训练两个去噪器(基础分辨率去噪器和上采样分辨率去噪器)来准确预测添加到潜在表示中的噪声,并通过最小化均方误差(MSE)损失来实现。在前向传播过程中:

  1. 首先,对低分辨率的 video_base 潜在表示添加噪声,并训练 base_denoiser 来预测此噪声,同时处理音频部分的去噪。

  2. 接着,对高分辨率的 video_upsampled 潜在表示添加噪声,并训练 upsampler_denoiser 来预测此噪声。在此阶段,带噪声的低分辨率视频潜在表示将作为额外的条件信号输入,以辅助模型在高分辨率重建中添加更多细节。


在反向传播过程中,计算来自两个去噪模型的组合损失,执行反向传播,并更新所有相关模型的参数。整个训练过程使用单一优化器,训练循环中包含针对基础阶段和上采样阶段的独立损失计算。


接下来定义模型的训练循环。该循环将迭代数据集,处理每个批次的数据,并根据计算得到的损失更新模型参数。根据每个批次的模态信息,模型将对视频和音频数据进行差异化处理,并应用相应的去噪模型(基础模型或上采样模型)。


# --- Training Hyperparameters ---  # --- 训练超参数 ---GRADIENT_CLIP_NORM = 1.0 # Gradient clipping norm (to prevent exploding gradients)  # 梯度裁剪范数 (用以防止梯度爆炸)# save loss and learning rate for plotting later  # 保存损失和学习率以便后续绘图loss_history = []  for epoch in range(NUM_EPOCHS):      total_loss = 0.0      for batch_idx, batch in enumerate(train_dataloader):          optimizer.zero_grad()          # --- Unpack and move data to device ---          # --- 解包数据并将数据移至计算设备 ---        modality = batch['modality']          video_base, video_upsampled, audio, img_base = (b.to(DEVICE) for b in (batch['video_base'], batch['video_upsampled'], batch['audio'], batch['input_image_base']))          raw_caption = batch['raw_caption'# This is a list of strings  # 这是一个字符串列表        # Sample random timesteps for diffusion process          # 为扩散过程采样随机时间步        timesteps = torch.randint(0, noise_scheduler.config.num_train_timesteps, (BATCH_SIZE,), device=DEVICE).long()          # --- VAE Encoding and Conditioning ---          # --- VAE 编码与条件化 ---        with torch.no_grad():              # Encode video and audio to latent space              # 将视频和音频编码到潜在空间            video_latents_base = video_vae.encode(video_base)  


    
            video_latents_upsampled = video_vae.encode(video_upsampled)              audio_latents = audio_vae.encode(audio)              # Get text embeddings for captions              # 获取字幕的文本嵌入            text_embeds = conditioning_encoder.get_text_embeds(raw_caption)          context = text_embeds          # If modality is image-to-video, also get image embeddings and add to context          # 如果模态是图像到视频,则同时获取图像嵌入并加入到上下文中        if modality[0] == 'i2v':              with torch.no_grad():                  img_embeds = conditioning_encoder.get_image_embeds(img_base)              context += img_embeds          # --- Base Model Training Stage (Video + Audio) ---          # --- 基础模型训练阶段 (视频 + 音频) ---        # Add noise to video and audio latents          # 向视频和音频的潜在表示添加噪声        noise_vid_base = torch.randn_like(video_latents_base)          noisy_video_base = noise_scheduler.add_noise(video_latents_base, noise_vid_base, timesteps)          noise_aud = torch.randn_like(audio_latents)          noisy_audio = noise_scheduler.add_noise(audio_latents, noise_aud, timesteps)          # Predict noise using base denoiser          # 使用基础去噪器预测噪声        pred_noise_vid_base, pred_noise_aud = base_denoiser(noisy_video_base, noisy_audio, timesteps, context)          # Compute loss for video and audio          # 计算视频和音频的损失        loss_base_vid = loss_fn(pred_noise_vid_base, noise_vid_base)          loss_aud = loss_fn(pred_noise_aud, noise_aud)          # --- Upsampler Model Training Stage (Video Only) ---          # --- 上采样模型训练阶段 (仅视频) ---        # Add noise to upsampled video latents          # 向上采样视频的潜在表示添加噪声        noise_vid_up = torch.randn_like(video_latents_upsampled)          noisy_video_upsampled = noise_scheduler.add_noise(video_latents_upsampled, noise_vid_up, timesteps)          # Predict noise using upsampler denoiser, conditioned on noisy low-res video          # 使用上采样去噪器预测噪声,以带噪声的低分辨率视频为条件        pred_noise_vid_up, _ = upsampler_denoiser(noisy_video_upsampled, torch.zeros_like(noisy_audio), timesteps, context, low_res_video=noisy_video_base)          # Compute loss for upsampled video          # 计算上采样视频的损失        loss_upsample_vid = loss_fn(pred_noise_vid_up, noise_vid_up)          # --- Combined Loss and Backward Pass ---          # --- 组合损失与反向传播 ---        # Combine all losses          # 组合所有损失        loss = loss_base_vid + loss_aud + loss_upsample_vid          # Backpropagate          # 执行反向传播        loss.backward()          # Clip gradients to prevent exploding gradients          # 裁剪梯度以防止梯度爆炸        torch.nn.utils.clip_grad_norm_(all_params, GRADIENT_CLIP_NORM)          # Update model parameters          # 更新模型参数        optimizer.step()          # Update learning rate          # 更新学习率        lr_scheduler.step()          total_loss += loss.item()          # In case you want to print the loss for each batch          # 如果需要,可以打印每个批次的损失值        # print(f"Batch {batch_idx+1}/{len(train_dataloader)}, Loss: {loss.item():.4f}")      # Average loss for the epoch      # 当前周期的平均损失    loss_history.append(total_loss / len(train_dataloader))      print(f"Epoch {epoch+1} Avg Loss: {total_loss / len(train_dataloader):.4f}")  print("--- Training Finished ---")Epoch 2 Avg Loss: 3.7529  Epoch 3 Avg Loss: 3.6651  Epoch 4 Avg Loss: 3.5488  Epoch 5 Avg Loss: 3.5301  Epoch 6 Avg Loss: 3.5549  Epoch 7 Avg Loss: 3.4246  Epoch 8 Avg Loss: 3.4193  Epoch 9 Avg Loss: 3.4146  Epoch 10 Avg Loss: 3.3345  --- Training Finished ---


训练循环执行完毕后,可以观察到每个周期的组合损失。为了更直观地评估模型训练过程,我们将训练损失随周期的变化进行可视化。


plt.figure(figsize=(163))  plt.plot(loss_history, label='Training Loss', marker='o')  plt.xlabel('Epoch')  plt.ylabel('Loss')  plt.title('Training Loss Over Epochs')  plt.legend()  plt.grid(True)  plt.show()

图片


从图中可见,损失值呈下降趋势,但下降幅度未达理想状态。这表明模型需要更多轮次的训练,并可能需要进一步调整超参数。由于计算资源限制,本演示在此暂停训练,重点在于展示代码结构与执行流程。


模型训练完成后(尽管本例中训练不充分),即可利用其从文本提示或图像生成视频。这是接下来的步骤。


基于级联逆向扩散的 Veo 3 推理


模型已完成数个周期的训练。尽管我们已实现了模型的各个组件,但当前模型的复杂度和训练程度尚不足以支撑一次完整的、高质量的训练运行。


尽管如此,我们仍可利用已训练(或部分训练)的模型执行推理过程。需要注意的是,由于模型未在足量的大规模数据上进行充分训练,预期输出的视频和音频将包含显著噪声。


推理过程并非简单的前向传播,它涉及额外的输入准备和输出处理步骤。


模型的推理过程采用级联逆向扩散(cascaded reverse diffusion)策略,分为两个主要步骤:

  1. 首先,生成低分辨率的基础潜在表示(视频和音频)。

  2. 然后,利用这些生成的基础潜在表示作为条件,输入到上采样器模型中,将视频细化至更高分辨率。

此逻辑对于文本到视频(T2V)和图像到视频(I2V)的推理均适用。若提供图像作为输入,其嵌入将被添加到基础模型和上采样器模型的上下文中。


最终,经过处理的“纯净”高分辨率视频潜在表示和基础音频潜在表示将分别通过其对应的 VAE 解码器,生成最终的视频和音频输出。


以下 generate_content 函数实现了上述两阶段逆向扩散的推理逻辑。


INFERENCE_STEPS = 50 # Number of steps for inference denoising (the number of steps in the inference process)  # 推理去噪的步数 (即推理过程中的迭代次数)def generate_content(prompt, input_image_path=None, steps=INFERENCE_STEPS):      # Set all models to evaluation mode      # 将所有模型设置为评估模式    for m in [video_vae, audio_vae, base_denoiser, upsampler_denoiser, conditioning_encoder]:          m.eval()      with torch.no_grad():          # --- 1. Prepare Conditioning ---          # --- 1. 准备条件信息 ---        # Get text embeddings for the prompt          # 获取提示文本的嵌入        text_embeds = conditioning_encoder.get_text_embeds(prompt)          context = text_embeds          # If an input image is provided, get its embedding and add to context          # 如果提供了输入图像,则获取其嵌入并添加到上下文中        if input_image_path:              img_pil = Image.open(input_image_path).convert("RGB").resize((BASE_VIDEO_WIDTH, BASE_VIDEO_HEIGHT))              img_tensor = transforms.ToTensor()(img_pil).unsqueeze(0).to(DEVICE)              context += conditioning_encoder.get_image_embeds(img_tensor)          # --- 2. Base Generation Stage ---          # --- 2. 基础分辨率生成阶段 ---        # Sample random noise for base video and audio latents          # 为基础视频和音频的潜在表示采样随机噪声        vid_latents_base = torch.randn(1, VIDEO_LATENT_CHANNELS, VIDEO_LATENT_FRAMES, BASE_VIDEO_LATENT_H, BASE_VIDEO_LATENT_W, device=DEVICE)          aud_latents = torch.randn(1, AUDIO_LATENT_CHANNELS, AUDIO_LATENT_SAMPLES, device=DEVICE)          # Set the scheduler timesteps for inference          # 为推理过程设置调度器的时间步        noise_scheduler.set_timesteps(steps)          # Denoising loop for base video and audio          # 基础视频和音频的去噪循环        for t in tqdm(noise_scheduler.timesteps, desc="Denoising (Base)"):              ts = t.unsqueeze(0).to(DEVICE)              pred_vid_noise, pred_aud_noise = base_denoiser(vid_latents_base, aud_latents, ts, context)              vid_latents_base = noise_scheduler.step(pred_vid_noise, t, vid_latents_base).prev_sample              aud_latents = noise_scheduler.step(pred_aud_noise, t, aud_latents).prev_sample          # --- 3. Upsampler Generation Stage ---          # --- 3. 上采样分辨率生成阶段 ---        # Sample random noise for upsampled video latents          # 为上采样视频的潜在表示采样随机噪声        vid_latents_upsampled = torch.randn(1, VIDEO_LATENT_CHANNELS, VIDEO_LATENT_FRAMES, UPSAMPLED_VIDEO_LATENT_H, UPSAMPLED_VIDEO_LATENT_W, device=DEVICE)          # Denoising loop for upsampled video, conditioned on clean base video latents          # 上采样视频的去噪循环,以纯净的基础视频潜在表示为条件        for t in tqdm(noise_scheduler.timesteps, desc="Denoising (Upsampler)"):              ts = t.unsqueeze(0).to(DEVICE)              pred_vid_noise_up, _ = upsampler_denoiser(                  vid_latents_upsampled, torch.zeros_like(aud_latents), ts, context, low_res_video=vid_latents_base  


    
            )              vid_latents_upsampled = noise_scheduler.step(pred_vid_noise_up, t, vid_latents_upsampled).prev_sample          # --- 4. Decode Final Latents ---          # --- 4. 解码最终的潜在表示 ---        # Decode the final video and audio latents to output          # 将最终的视频和音频潜在表示解码为输出        final_video = video_vae.decode(vid_latents_upsampled)          final_audio = audio_vae.decode(aud_latents)          return final_video, final_audio


generate_content 函数接收文本提示和可选的输入图像路径,基于提供的提示生成视频和音频内容。它利用训练好的模型,逐步对潜在表示进行去噪,首先处理基础分辨率,然后处理上采样分辨率。


现在,可以利用图像或文本提示进行视频生成。以下为文本到视频生成的测试示例。


# Example: Generate a video and audio from a text prompt using the trained model  # 示例: 使用训练好的模型从文本提示生成视频和音频t2v_prompt = "A mystical forest with glowing trees and soft ethereal music."  gen_video_t2v, gen_audio_t2v = generate_content(t2v_prompt)


为方便观察生成的视频和音频,定义一个简单的可视化函数。


def display_media(video, audio, title_prefix=""):      fig, axes = plt.subplots(12, figsize=(153))      if video is not None:          # Extract the middle frame from the upsampled video and display it          # 从上采样视频中提取中间帧并显示        frame = (video[0,:, BASE_VIDEO_FRAMES//2].permute(1,2,0).cpu().numpy()*255).astype(np.uint8)          axes[0].imshow(frame)          axes[0].set_title(f"{title_prefix} Video Frame (Upsampled)")          axes[0


    
].axis('off')      if audio is not None:          # Plot the audio waveform          # 绘制音频波形        axes[1].plot(audio[0,0].cpu())          axes[1].set_title(f"{title_prefix} Audio")          axes[1].set_xlabel('Sample')          axes[1].set_ylabel('Amplitude')      plt.tight_layout()      plt.show()  print("--- Displaying Generated Outputs ---")  display_media(gen_video_t2v, gen_audio_t2v, "T2V Output")


图片

从输出结果可以看出,生成的视频帧包含显著噪声,音频也表现为噪声。这符合预期,因为模型仅在小规模数据集上进行了有限周期的训练。尽管如此,该实验验证了模型能够根据文本提示生成视频和音频,这已达到本项目的主要演示目标。


一个重要的问题随之而来:这个初步的 Veo 3 模型已构建完成,那么 Google 是如何评估其模型性能的呢?这是项目接下来的探讨内容。


评估策略


Google 并未公开 Veo 3 评估所用的全部确切指标,但在其模型介绍中提及 Veo v3 是在 Meta 发布的 MovieGenBench(包含视频和视频+音频两种任务)基准数据集上进行评估的。这些数据集分别包含 1,003 个视频生成提示和 527 个视频+音频生成提示,并附带了由其他主流模型生成的对应视频,例如 Meta 的 MovieGen(视频及视频+音频)、Kling 2.0(仅视频)、Minimax(仅视频)以及 Sora Turbo(仅视频)。


除了使用特定的基准数据集进行评估外,从统计学角度衡量生成视频质量的方法也多种多样。其中,两个常用的客观评价指标是:

  1. CLIP Score (CLIP 分数): 此指标利用 CLIP 模型来评估生成的视频内容与输入文本提示之间的语义一致性。它通过计算文本嵌入与视频帧嵌入之间的余弦相似度来实现。

  2. LPIPS (Learned Perceptual Image Patch Similarity, 学习感知图像块相似度): 此指标衡量生成的视频帧与参考图像之间的感知相似度,常用于评估生成图像或视频的视觉质量。


这些指标是评估文本到视频生成模型(尤其是此类任务)质量的常用手段。


以下函数用于计算 CLIP 分数:


def calculate_clip_score(gen_video, prompt):      # Extract the middle frame from the generated video and convert to PIL image      # 从生成的视频中提取中间帧并转换为 PIL 图像    middle_frame = gen_video[0, :, BASE_VIDEO_FRAMES // 2].cpu()      frame_pil = transforms.ToPILImage()(middle_frame)      # Get CLIP embeddings for the prompt and the frame      # 获取提示文本和帧的 CLIP 嵌入    text_embed = conditioning_encoder.get_text_embeds(prompt)      image_embed = conditioning_encoder.get_image_embeds(frame_pil)      # Normalize embeddings      # 对嵌入进行归一化    text_embed = text_embed / text_embed.norm(dim=-1, keepdim=True)      image_embed = image_embed / image_embed.norm(dim=-1, keepdim=True)      # Compute cosine similarity (CLIP score)      # 计算余弦相似度 (CLIP 分数)


    
    clip_score = (text_embed @ image_embed.T).item()      return clip_score


calculate_clip_score 函数接收生成的视频和文本提示,从视频中提取中间帧,计算该帧与提示文本的 CLIP 嵌入,并返回它们之间的余弦相似度作为 CLIP 分数。


类似地,LPIPS 指标用于评估生成视频帧与参考图像之间的感知相似性。通常选用基于 VGG 网络的 LPIPS 模型(net='vgg'),因其在捕捉感知差异方面表现优异。


lpips_model = lpips.LPIPS(net='vgg').to(DEVICE)  def calculate_lpips(gen_video, img_path):      # Load and preprocess the reference image: resize, convert to tensor, scale to [-1, 1]      # 加载并预处理参考图像:调整大小、转换为张量、缩放到 [-1, 1] 范围    input_img = transforms.ToTensor()(          Image.open(img_path).convert('RGB').resize(              (UPSAMPLED_VIDEO_WIDTH, UPSAMPLED_VIDEO_HEIGHT)          )      ).unsqueeze(0) * 2 - 1      # Extract and preprocess the first frame from the generated video, scale to [-1, 1]      # 从生成的视频中提取第一帧并进行预处理,缩放到 [-1, 1] 范围    gen_frame = gen_video[0, :, 0].cpu().unsqueeze(0) * 2 - 1      # Compute LPIPS score between the reference image and generated frame      # 计算参考图像与生成帧之间的 LPIPS 分数    return lpips_model(input_img.to(DEVICE), gen_frame.to(DEVICE)).item()


对前述生成的文本到视频结果计算 CLIP 分数:


# Calculate the CLIP score for the generated video and prompt.  # 计算生成视频与提示之间的 CLIP 分数。# This measures how well the generated video matches the text prompt using CLIP embeddings.  # 此分数通过 CLIP 嵌入衡量生成视频与文本提示的匹配程度。clip_score = calculate_clip_score(gen_video_t2v, t2v_prompt)  print(f"T2V CLIP Score: {clip_score:.4f}")  #### OUTPUT ####  


    
T2V CLIP Score: 0.1389


计算得到的 CLIP 分数接近 0,表明生成质量不佳,这与训练不充分的预期相符。尽管如此,此示例演示了如何对 Veo 3 或类似视频扩散模型等文本到视频生成系统进行定性评估。类似地,也可以计算图像到视频任务的 LPIPS 分数或其他相关指标。


总结


本文详细介绍了一个简化版 Veo 3 文本到视频生成模型的构建过程。首先进行了数据预处理,涵盖了去重、不安全内容过滤、质量合规性检查以及数据标注等环节。


随后设计并实现了模型的整体架构,包括 VideoVAE、AudioVAE、ConditioningEncoder 和 JointDenoisingTransformer 等核心组件。最后,构建了训练循环,并执行了推理过程,以根据提示或图像生成视频。


本项目为理解文本到视频生成模型的工作原理以及如何使用 PyTorch 从零开始构建此类模型提供了一个基础框架。尽管本实现与 Google Veo 3 的复杂度和性能尚有差距,但它清晰地展示了此类先进模型所涉及的核心概念、关键组件及技术流程,为进一步探索和研究奠定了基础。


想要了解更多资讯,请扫描下方二维码,关注机器学习研究会


图片


转自:数据派THU

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/184819
 
78 次点击