diffusers 源码解析(二十八)
.\diffusers\pipelines\deprecated\repaint\__init__.py
# 导入类型检查工具
from typing import TYPE_CHECKING
# 从上层模块导入所需的工具和常量
from ....utils import DIFFUSERS_SLOW_IMPORT, _LazyModule
# 定义模块的导入结构,指定要导入的子模块及其内容
_import_structure = {"pipeline_repaint": ["RePaintPipeline"]}
# 检查是否为类型检查或慢速导入
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
# 从子模块导入 RePaintPipeline 类
from .pipeline_repaint import RePaintPipeline
else:
# 导入系统模块
import sys
# 使用延迟加载模块,替换当前模块为一个懒加载模块
sys.modules[__name__] = _LazyModule(
__name__,
globals()["__file__"],
_import_structure,
module_spec=__spec__,
)
.\diffusers\pipelines\deprecated\score_sde_ve\pipeline_score_sde_ve.py
# 版权声明,表明此代码的版权归 HuggingFace 团队所有
#
# 根据 Apache 许可证第 2.0 版(“许可证”)进行许可;
# 除非遵循许可证,否则您不得使用此文件。
# 可以通过以下网址获取许可证的副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则根据许可证分发的软件
# 是以“按现状”基础提供的,不提供任何形式的保证或条件。
# 请参见许可证了解特定语言所涉及的权限和
# 限制条款。
# 从 typing 模块导入类型注解
from typing import List, Optional, Tuple, Union
# 导入 PyTorch 库
import torch
# 从指定路径导入 UNet2DModel 模型
from ....models import UNet2DModel
# 从指定路径导入 ScoreSdeVeScheduler 调度器
from ....schedulers import ScoreSdeVeScheduler
# 从指定路径导入 randn_tensor 函数
from ....utils.torch_utils import randn_tensor
# 从指定路径导入 DiffusionPipeline 和 ImagePipelineOutput
from ...pipeline_utils import DiffusionPipeline, ImagePipelineOutput
# 定义 ScoreSdeVePipeline 类,继承自 DiffusionPipeline
class ScoreSdeVePipeline(DiffusionPipeline):
r"""
用于无条件图像生成的管道。
此模型继承自 [`DiffusionPipeline`]. 请查看超类文档以了解所有管道实现的通用方法
(下载、保存、在特定设备上运行等)。
参数:
unet ([`UNet2DModel`]):
一个用于去噪编码图像的 `UNet2DModel`。
scheduler ([`ScoreSdeVeScheduler`]):
一个与 `unet` 结合使用以去噪编码图像的 `ScoreSdeVeScheduler`。
"""
# 定义 unet 和 scheduler 属性,分别为 UNet2DModel 和 ScoreSdeVeScheduler 类型
unet: UNet2DModel
scheduler: ScoreSdeVeScheduler
# 初始化方法,接收 unet 和 scheduler 作为参数
def __init__(self, unet: UNet2DModel, scheduler: ScoreSdeVeScheduler):
# 调用父类的初始化方法
super().__init__()
# 注册 unet 和 scheduler 模块
self.register_modules(unet=unet, scheduler=scheduler)
# 使用 @torch.no_grad() 装饰器,指示在调用时不计算梯度
@torch.no_grad()
def __call__(
# 设置默认批大小为 1
batch_size: int = 1,
# 设置默认推理步骤数为 2000
num_inference_steps: int = 2000,
# 可选生成器,支持单个或多个 torch.Generator 实例
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 可选输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 可选返回字典,默认为 True
return_dict: bool = True,
# 接收额外的关键字参数
**kwargs,
) -> Union[ImagePipelineOutput, Tuple]:
r"""
生成的管道调用函数。
Args:
batch_size (`int`, *optional*, defaults to 1):
要生成的图像数量。
generator (`torch.Generator`, `optional`):
用于生成确定性结果的 [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html)。
output_type (`str`, `optional`, defaults to `"pil"`):
生成图像的输出格式。可以选择 `PIL.Image` 或 `np.array`。
return_dict (`bool`, *optional*, defaults to `True`):
是否返回 [`ImagePipelineOutput`] 而不是普通元组。
Returns:
[`~pipelines.ImagePipelineOutput`] 或 `tuple`:
如果 `return_dict` 为 `True`,则返回 [`~pipelines.ImagePipelineOutput`],否则返回一个元组,元组的第一个元素是生成图像的列表。
"""
# 获取 UNet 模型配置中的样本大小
img_size = self.unet.config.sample_size
# 定义输入张量的形状
shape = (batch_size, 3, img_size, img_size)
# 赋值 UNet 模型
model = self.unet
# 生成随机张量并乘以初始噪声标准差
sample = randn_tensor(shape, generator=generator) * self.scheduler.init_noise_sigma
# 将样本移动到指定设备
sample = sample.to(self.device)
# 设置调度器的时间步
self.scheduler.set_timesteps(num_inference_steps)
# 设置调度器的 sigma 值
self.scheduler.set_sigmas(num_inference_steps)
# 遍历调度器时间步的进度条
for i, t in enumerate(self.progress_bar(self.scheduler.timesteps)):
# 创建 sigma_t,形状为 (batch_size,)
sigma_t = self.scheduler.sigmas[i] * torch.ones(shape[0], device=self.device)
# 修正步骤
for _ in range(self.scheduler.config.correct_steps):
# 获取模型输出
model_output = self.unet(sample, sigma_t).sample
# 更新样本
sample = self.scheduler.step_correct(model_output, sample, generator=generator).prev_sample
# 预测步骤
model_output = model(sample, sigma_t).sample
# 更新输出
output = self.scheduler.step_pred(model_output, t, sample, generator=generator)
# 获取上一个样本及其均值
sample, sample_mean = output.prev_sample, output.prev_sample_mean
# 限制样本均值在 0 到 1 之间
sample = sample_mean.clamp(0, 1)
# 转换样本格式为 numpy 数组
sample = sample.cpu().permute(0, 2, 3, 1).numpy()
# 根据输出类型转换为 PIL 图像
if output_type == "pil":
sample = self.numpy_to_pil(sample)
# 如果不返回字典,则返回样本元组
if not return_dict:
return (sample,)
# 返回图像管道输出
return ImagePipelineOutput(images=sample)
.\diffusers\pipelines\deprecated\score_sde_ve\__init__.py
# 从 typing 模块导入 TYPE_CHECKING,用于类型检查
from typing import TYPE_CHECKING
# 从上层目录的 utils 模块导入 DIFFUSERS_SLOW_IMPORT 和 _LazyModule
from ....utils import DIFFUSERS_SLOW_IMPORT, _LazyModule
# 定义模块的导入结构,指定模块内可用的内容
_import_structure = {"pipeline_score_sde_ve": ["ScoreSdeVePipeline"]}
# 如果正在进行类型检查或 DIFFUSERS_SLOW_IMPORT 为真
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
# 从 pipeline_score_sde_ve 模块中导入 ScoreSdeVePipeline 类
from .pipeline_score_sde_ve import ScoreSdeVePipeline
# 否则
else:
import sys
# 使用 _LazyModule 将当前模块替换为一个延迟加载的模块
sys.modules[__name__] = _LazyModule(
__name__, # 模块名称
globals()["__file__"], # 当前文件的全局变量
_import_structure, # 导入结构
module_spec=__spec__, # 模块规格
)
.\diffusers\pipelines\deprecated\spectrogram_diffusion\continuous_encoder.py
# 版权信息,声明此文件的版权所有者及相关法律条款
# Copyright 2022 The Music Spectrogram Diffusion Authors.
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache License, Version 2.0 进行授权
# 仅在遵守许可证的情况下使用此文件
# 可以在以下网址获取许可证
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有规定,否则软件按“原样”分发
# 不提供任何形式的担保或条件
# 请参见许可证以获取特定语言的权限和限制
# 导入 PyTorch 库
import torch
# 导入 PyTorch 的神经网络模块
import torch.nn as nn
# 从 Transformers 库中导入模块工具混合类
from transformers.modeling_utils import ModuleUtilsMixin
# 从 Transformers 的 T5 模型导入相关组件
from transformers.models.t5.modeling_t5 import (
T5Block, # T5 模型中的块
T5Config, # T5 模型的配置类
T5LayerNorm, # T5 模型中的层归一化
)
# 从自定义配置工具导入混合类和注册到配置的装饰器
from ....configuration_utils import ConfigMixin, register_to_config
# 从自定义模型导入模型混合类
from ....models import ModelMixin
# 定义 SpectrogramContEncoder 类,继承自多个混合类
class SpectrogramContEncoder(ModelMixin, ConfigMixin, ModuleUtilsMixin):
# 注册初始化方法到配置
@register_to_config
def __init__(
self,
input_dims: int, # 输入维度
targets_context_length: int, # 目标上下文长度
d_model: int, # 模型维度
dropout_rate: float, # dropout 比率
num_layers: int, # 层数
num_heads: int, # 注意力头数
d_kv: int, # 键值维度
d_ff: int, # 前馈层维度
feed_forward_proj: str, # 前馈投影类型
is_decoder: bool = False, # 是否为解码器
):
# 调用父类初始化方法
super().__init__()
# 定义输入投影层,将输入维度映射到模型维度
self.input_proj = nn.Linear(input_dims, d_model, bias=False)
# 定义位置编码层,使用嵌入来表示位置
self.position_encoding = nn.Embedding(targets_context_length, d_model)
# 禁止位置编码权重更新
self.position_encoding.weight.requires_grad = False
# 定义前置 dropout 层
self.dropout_pre = nn.Dropout(p=dropout_rate)
# 创建 T5 模型的配置
t5config = T5Config(
d_model=d_model, # 模型维度
num_heads=num_heads, # 注意力头数
d_kv=d_kv, # 键值维度
d_ff=d_ff, # 前馈层维度
feed_forward_proj=feed_forward_proj, # 前馈投影类型
dropout_rate=dropout_rate, # dropout 比率
is_decoder=is_decoder, # 是否为解码器
is_encoder_decoder=False, # 是否为编码器解码器
)
# 创建编码器层的模块列表
self.encoders = nn.ModuleList()
# 根据层数循环创建 T5 块并添加到编码器列表中
for lyr_num in range(num_layers):
lyr = T5Block(t5config) # 创建 T5 块
self.encoders.append(lyr) # 添加到编码器列表
# 定义层归一化层
self.layer_norm = T5LayerNorm(d_model)
# 定义后置 dropout 层
self.dropout_post = nn.Dropout(p=dropout_rate)
# 定义前向传播函数,接收编码器输入和编码器输入的掩码
def forward(self, encoder_inputs, encoder_inputs_mask):
# 通过输入投影将编码器输入转换为模型内部表示
x = self.input_proj(encoder_inputs)
# 计算终端相对位置编码
max_positions = encoder_inputs.shape[1] # 获取输入序列的最大长度
input_positions = torch.arange(max_positions, device=encoder_inputs.device) # 生成位置索引的张量
# 计算输入序列的长度
seq_lens = encoder_inputs_mask.sum(-1) # 对掩码进行求和,得到每个序列的有效长度
# 将位置索引张量根据序列长度进行滚动
input_positions = torch.roll(input_positions.unsqueeze(0), tuple(seq_lens.tolist()), dims=0)
# 将位置编码添加到输入特征中
x += self.position_encoding(input_positions)
# 对输入特征应用 dropout 以防止过拟合
x = self.dropout_pre(x)
# 反转注意力掩码
input_shape = encoder_inputs.size() # 获取输入张量的形状
# 扩展注意力掩码以适应输入形状
extended_attention_mask = self.get_extended_attention_mask(encoder_inputs_mask, input_shape)
# 依次通过每个编码层
for lyr in self.encoders:
x = lyr(x, extended_attention_mask)[0] # 通过编码器层处理输入并获取输出
# 对输出应用层归一化
x = self.layer_norm(x)
# 返回处理后的输出和编码器输入掩码
return self.dropout_post(x), encoder_inputs_mask
.\diffusers\pipelines\deprecated\spectrogram_diffusion\midi_utils.py
# 版权声明,标明文件的版权信息
# Copyright 2022 The Music Spectrogram Diffusion Authors.
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 许可证信息,指出文件使用的许可证类型
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# 可以通过以下链接获取许可证的副本
# http://www.apache.org/licenses/LICENSE-2.0
#
# 在未适用或未书面同意的情况下,软件在“按现状”基础上分发,
# 不提供任何明示或暗示的保证或条件
# 查看许可证以获取特定的权限和限制
# 导入 dataclasses 模块,用于创建数据类
import dataclasses
# 导入 math 模块,提供数学函数
import math
# 导入 os 模块,用于操作系统功能
import os
# 从 typing 模块导入各种类型提示
from typing import Any, Callable, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Union
# 导入 numpy 库,常用于数值计算
import numpy as np
# 导入 PyTorch 库
import torch
# 从 PyTorch 中导入功能模块,提供各种函数
import torch.nn.functional as F
# 从 utils 模块导入 is_note_seq_available 函数,检查 note-seq 是否可用
from ....utils import is_note_seq_available
# 从 pipeline_spectrogram_diffusion 模块导入 TARGET_FEATURE_LENGTH 常量
from .pipeline_spectrogram_diffusion import TARGET_FEATURE_LENGTH
# 检查 note-seq 库是否可用,如果可用则导入,否则抛出导入错误
if is_note_seq_available():
import note_seq
else:
raise ImportError("Please install note-seq via `pip install note-seq`")
# 定义输入特征的长度
INPUT_FEATURE_LENGTH = 2048
# 定义音频的采样率
SAMPLE_RATE = 16000
# 定义每帧的跳跃大小
HOP_SIZE = 320
# 根据采样率和跳跃大小计算帧率
FRAME_RATE = int(SAMPLE_RATE // HOP_SIZE)
# 定义每秒的默认步骤数
DEFAULT_STEPS_PER_SECOND = 100
# 定义默认的最大偏移时间(秒)
DEFAULT_MAX_SHIFT_SECONDS = 10
# 定义默认的速度区间数量
DEFAULT_NUM_VELOCITY_BINS = 1
# 定义一个字典,映射乐器名称到其对应的程序编号
SLAKH_CLASS_PROGRAMS = {
"Acoustic Piano": 0,
"Electric Piano": 4,
"Chromatic Percussion": 8,
"Organ": 16,
"Acoustic Guitar": 24,
"Clean Electric Guitar": 26,
"Distorted Electric Guitar": 29,
"Acoustic Bass": 32,
"Electric Bass": 33,
"Violin": 40,
"Viola": 41,
"Cello": 42,
"Contrabass": 43,
"Orchestral Harp": 46,
"Timpani": 47,
"String Ensemble": 48,
"Synth Strings": 50,
"Choir and Voice": 52,
"Orchestral Hit": 55,
"Trumpet": 56,
"Trombone": 57,
"Tuba": 58,
"French Horn": 60,
"Brass Section": 61,
"Soprano/Alto Sax": 64,
"Tenor Sax": 66,
"Baritone Sax": 67,
"Oboe": 68,
"English Horn": 69,
"Bassoon": 70,
"Clarinet": 71,
"Pipe": 73,
"Synth Lead": 80,
"Synth Pad": 88,
}
# 定义一个数据类,用于配置音符表示
@dataclasses.dataclass
class NoteRepresentationConfig:
"""Configuration note representations."""
# 是否仅包含音符的开始时间
onsets_only: bool
# 是否包含连音符
include_ties: bool
# 定义一个数据类,表示音符事件数据
@dataclasses.dataclass
class NoteEventData:
pitch: int # 音高
velocity: Optional[int] = None # 音量,可选
program: Optional[int] = None # 乐器程序编号,可选
is_drum: Optional[bool] = None # 是否为鼓乐器,可选
instrument: Optional[int] = None # 乐器类型编号,可选
# 定义一个数据类,表示音符编码状态
@dataclasses.dataclass
class NoteEncodingState:
"""Encoding state for note transcription, keeping track of active pitches."""
# 存储活动音高及其对应的速度区间
active_pitches: MutableMapping[Tuple[int, int], int] = dataclasses.field(default_factory=dict)
# 定义一个数据类,表示事件范围
@dataclasses.dataclass
class EventRange:
type: str # 事件类型
min_value: int # 最小值
max_value: int # 最大值
# 定义一个数据类,表示事件
@dataclasses.dataclass
class Event:
type: str # 事件类型
value: int # 事件值
# 定义一个名为 Tokenizer 的类
class Tokenizer:
# 初始化方法,用于创建对象时传入正则 ID 的数量
def __init__(self, regular_ids: int):
# 特殊标记的数量:0=PAD,1=EOS,2=UNK
self._num_special_tokens = 3
# 存储正则 ID 的数量
self._num_regular_tokens = regular_ids
# 编码方法,接收一组 token ID
def encode(self, token_ids):
# 初始化一个空列表,用于存储编码后的 token
encoded = []
# 遍历传入的每个 token ID
for token_id in token_ids:
# 检查 token ID 是否在有效范围内
if not 0 <= token_id < self._num_regular_tokens:
# 如果不在范围内,抛出值错误异常
raise ValueError(
f"token_id {token_id} does not fall within valid range of [0, {self._num_regular_tokens})"
)
# 将有效的 token ID 加上特殊标记数量,并添加到编码列表中
encoded.append(token_id + self._num_special_tokens)
# 在编码列表末尾添加 EOS 标记
encoded.append(1)
# 将编码列表填充至 INPUT_FEATURE_LENGTH 长度,使用 PAD 标记
encoded = encoded + [0] * (INPUT_FEATURE_LENGTH - len(encoded))
# 返回最终编码后的列表
return encoded
# 编码和解码事件的类
class Codec:
"""Encode and decode events.
用于声明词汇的特定范围。打算在编码前或解码后使用GenericTokenVocabulary。此类更轻量,不包含诸如EOS或UNK令牌处理等内容。
为确保“shift”事件始终是词汇的第一个块并从0开始,该事件类型是必需的并单独指定。
"""
# 初始化方法,定义Codec
def __init__(self, max_shift_steps: int, steps_per_second: float, event_ranges: List[EventRange]):
"""定义Codec。
参数:
max_shift_steps: 可编码的最大移位步数。
steps_per_second: 移位步数将被解释为持续时间为1 / steps_per_second。
event_ranges: 其他支持的事件类型及其范围。
"""
# 设置每秒的步数
self.steps_per_second = steps_per_second
# 创建移位范围,最小值为0,最大值为max_shift_steps
self._shift_range = EventRange(type="shift", min_value=0, max_value=max_shift_steps)
# 将移位范围与其他事件范围合并
self._event_ranges = [self._shift_range] + event_ranges
# 确保所有事件类型具有唯一名称
assert len(self._event_ranges) == len({er.type for er in self._event_ranges})
# 计算事件类别的总数
@property
def num_classes(self) -> int:
return sum(er.max_value - er.min_value + 1 for er in self._event_ranges)
# 下面的方法是仅针对移位事件的简化特例方法,打算在autograph函数中使用
# 检查给定索引是否为移位事件索引
def is_shift_event_index(self, index: int) -> bool:
return (self._shift_range.min_value <= index) and (index <= self._shift_range.max_value)
# 获取最大移位步数
@property
def max_shift_steps(self) -> int:
return self._shift_range.max_value
# 将事件编码为索引
def encode_event(self, event: Event) -> int:
"""将事件编码为索引。"""
offset = 0 # 初始化偏移量
# 遍历事件范围
for er in self._event_ranges:
# 检查事件类型是否匹配
if event.type == er.type:
# 验证事件值是否在有效范围内
if not er.min_value <= event.value <= er.max_value:
raise ValueError(
f"Event value {event.value} is not within valid range "
f"[{er.min_value}, {er.max_value}] for type {event.type}"
)
# 返回编码后的索引
return offset + event.value - er.min_value
# 更新偏移量
offset += er.max_value - er.min_value + 1
# 抛出未识别事件类型的错误
raise ValueError(f"Unknown event type: {event.type}")
# 返回事件类型的范围
def event_type_range(self, event_type: str) -> Tuple[int, int]:
"""返回事件类型的[min_id, max_id]。"""
offset = 0 # 初始化偏移量
# 遍历事件范围
for er in self._event_ranges:
# 检查事件类型是否匹配
if event_type == er.type:
return offset, offset + (er.max_value - er.min_value)
# 更新偏移量
offset += er.max_value - er.min_value + 1
# 抛出未识别事件类型的错误
raise ValueError(f"Unknown event type: {event_type}")
# 解码事件索引,返回对应的 Event 对象
def decode_event_index(self, index: int) -> Event:
# 初始化偏移量为 0
offset = 0
# 遍历事件范围列表
for er in self._event_ranges:
# 检查索引是否在当前事件范围内
if offset <= index <= offset + er.max_value - er.min_value:
# 返回相应类型和解码后的值的 Event 对象
return Event(type=er.type, value=er.min_value + index - offset)
# 更新偏移量以包含当前事件范围
offset += er.max_value - er.min_value + 1
# 如果没有找到匹配的事件范围,则抛出值错误
raise ValueError(f"Unknown event index: {index}")
# 定义一个数据类 ProgramGranularity,用于描述程序粒度的相关函数
@dataclasses.dataclass
class ProgramGranularity:
# 声明两个函数,要求它们是幂等的(同样输入返回相同输出)
tokens_map_fn: Callable[[Sequence[int], Codec], Sequence[int]]
program_map_fn: Callable[[int], int]
# 定义一个函数 drop_programs,用于从令牌序列中删除程序变化事件
def drop_programs(tokens, codec: Codec):
"""从令牌序列中删除程序变化事件。"""
# 获取程序事件的最小和最大ID
min_program_id, max_program_id = codec.event_type_range("program")
# 返回所有不在程序ID范围内的令牌
return tokens[(tokens < min_program_id) | (tokens > max_program_id)]
# 定义一个函数 programs_to_midi_classes,用于将程序事件修改为 MIDI 类中的第一个程序
def programs_to_midi_classes(tokens, codec):
"""将程序事件修改为 MIDI 类中的第一个程序。"""
# 获取程序事件的最小和最大ID
min_program_id, max_program_id = codec.event_type_range("program")
# 确定哪些令牌是程序事件
is_program = (tokens >= min_program_id) & (tokens <= max_program_id)
# 根据程序事件的类别,将其映射到相应的 MIDI 类
return np.where(is_program, min_program_id + 8 * ((tokens - min_program_id) // 8), tokens)
# 定义一个字典 PROGRAM_GRANULARITIES,用于存储不同程序粒度的映射
PROGRAM_GRANULARITIES = {
# “平坦”粒度;删除程序变化令牌并将 NoteSequence 的程序设置为零
"flat": ProgramGranularity(tokens_map_fn=drop_programs, program_map_fn=lambda program: 0),
# 将每个程序映射到其 MIDI 类中的第一个程序
"midi_class": ProgramGranularity(
tokens_map_fn=programs_to_midi_classes, program_map_fn=lambda program: 8 * (program // 8)
),
# 保留程序不变
"full": ProgramGranularity(tokens_map_fn=lambda tokens, codec: tokens, program_map_fn=lambda program: program),
}
# 定义一个函数 frame,用于将信号分帧
def frame(signal, frame_length, frame_step, pad_end=False, pad_value=0, axis=-1):
"""
相当于 tf.signal.frame
"""
# 获取信号在指定轴的长度
signal_length = signal.shape[axis]
# 如果需要填充信号的结尾
if pad_end:
# 计算帧重叠的大小
frames_overlap = frame_length - frame_step
# 计算剩余样本数以确定需要填充的大小
rest_samples = np.abs(signal_length - frames_overlap) % np.abs(frame_length - frames_overlap)
pad_size = int(frame_length - rest_samples)
# 如果需要填充
if pad_size != 0:
# 创建填充轴的大小列表
pad_axis = [0] * signal.ndim
pad_axis[axis] = pad_size
# 在信号的指定轴上进行常量填充
signal = F.pad(signal, pad_axis, "constant", pad_value)
# 在指定轴上进行信号分帧
frames = signal.unfold(axis, frame_length, frame_step)
# 返回分帧后的信号
return frames
# 定义一个函数 program_to_slakh_program,用于将程序转换为 Slakh 程序
def program_to_slakh_program(program):
# 这个实现方法很黑客,可能应该使用自定义映射
# 遍历所有 Slakh 类程序,按照从大到小的顺序
for slakh_program in sorted(SLAKH_CLASS_PROGRAMS.values(), reverse=True):
# 如果输入程序大于或等于 Slakh 程序,返回该 Slakh 程序
if program >= slakh_program:
return slakh_program
# 定义一个函数 audio_to_frames,用于将音频样本转换为非重叠的帧和帧时间
def audio_to_frames(
samples,
hop_size: int,
frame_rate: int,
) -> Tuple[Sequence[Sequence[int]], torch.Tensor]:
"""将音频样本转换为非重叠的帧和帧时间。"""
# 将帧大小设置为跳跃大小
frame_size = hop_size
# 填充样本以确保长度为帧大小的整数倍
samples = np.pad(samples, [0, frame_size - len(samples) % frame_size], mode="constant")
# 将音频分割成帧
frames = frame(
torch.Tensor(samples).unsqueeze(0),
frame_length=frame_size,
frame_step=frame_size,
pad_end=False, # TODO 检查为什么在这里填充为 True 时会偏差 1
)
# 计算帧的数量
num_frames = len(samples) // frame_size
# 生成每帧的时间
times = np.arange(num_frames) / frame_rate
# 返回分帧和对应的时间
return frames, times
# 将音符序列转换为音符的起始和结束时间及程序信息
def note_sequence_to_onsets_and_offsets_and_programs(
ns: note_seq.NoteSequence,
) -> Tuple[Sequence[float], Sequence[NoteEventData]]:
"""从 NoteSequence 提取起始和结束时间以及音高和程序。
起始和结束时间不一定按顺序排列。
参数:
ns: 要提取起始和结束时间的 NoteSequence。
返回:
times: 音符的起始和结束时间列表。values: 包含音符数据的 NoteEventData 对象列表,其中音符结束的速度为零。
"""
# 按程序和音高排序,将结束时间放在起始时间之前,以便后续稳定排序
notes = sorted(ns.notes, key=lambda note: (note.is_drum, note.program, note.pitch))
# 创建一个列表,包含音符的结束时间和起始时间
times = [note.end_time for note in notes if not note.is_drum] + [note.start_time for note in notes]
# 创建一个包含音符数据的列表,其中音符结束的速度为零
values = [
NoteEventData(pitch=note.pitch, velocity=0, program=note.program, is_drum=False)
for note in notes
if not note.is_drum
] + [
NoteEventData(pitch=note.pitch, velocity=note.velocity, program=note.program, is_drum=note.is_drum)
for note in notes
]
# 返回起始和结束时间列表及音符数据列表
return times, values
# 从事件编码器获取速度分箱的数量
def num_velocity_bins_from_codec(codec: Codec):
"""从事件编码器获取速度分箱数量。"""
# 获取速度的最小和最大事件类型范围
lo, hi = codec.event_type_range("velocity")
# 计算并返回速度分箱的数量
return hi - lo
# 将数组分段为长度为 n 的段
def segment(a, n):
# 通过列表解析将数组分段
return [a[i : i + n] for i in range(0, len(a), n)]
# 将速度转换为相应的分箱
def velocity_to_bin(velocity, num_velocity_bins):
# 如果速度为零,返回第一个分箱
if velocity == 0:
return 0
else:
# 否则计算并返回相应的速度分箱
return math.ceil(num_velocity_bins * velocity / note_seq.MAX_MIDI_VELOCITY)
# 将音符事件数据转换为事件序列
def note_event_data_to_events(
state: Optional[NoteEncodingState],
value: NoteEventData,
codec: Codec,
) -> Sequence[Event]:
"""将音符事件数据转换为事件序列。"""
# 如果速度为 None,仅返回起始事件
if value.velocity is None:
return [Event("pitch", value.pitch)]
else:
# 从编码器获取速度分箱的数量
num_velocity_bins = num_velocity_bins_from_codec(codec)
# 将速度转换为相应的分箱
velocity_bin = velocity_to_bin(value.velocity, num_velocity_bins)
if value.program is None:
# 仅返回起始、结束和速度事件,不包含程序
if state is not None:
state.active_pitches[(value.pitch, 0)] = velocity_bin
return [Event("velocity", velocity_bin), Event("pitch", value.pitch)]
else:
if value.is_drum:
# 对于打击乐事件,使用单独的词汇
return [Event("velocity", velocity_bin), Event("drum", value.pitch)]
else:
# 返回程序、速度和音高的事件
if state is not None:
state.active_pitches[(value.pitch, value.program)] = velocity_bin
return [
Event("program", value.program),
Event("velocity", velocity_bin),
Event("pitch", value.pitch),
]
# 将音符编码状态转换为事件序列,输出活动音符的程序和音高事件,以及最后的延续事件
def note_encoding_state_to_events(state: NoteEncodingState) -> Sequence[Event]:
# 创建一个空列表以存储事件
events = []
# 遍历所有活动音高及其对应的程序,按程序排序
for pitch, program in sorted(state.active_pitches.keys(), key=lambda k: k[::-1]):
# 如果当前音高和程序处于活动状态
if state.active_pitches[(pitch, program)]:
# 将程序和音高事件添加到事件列表中
events += [Event("program", program), Event("pitch", pitch)]
# 在事件列表中添加一个延续事件,值为 0
events.append(Event("tie", 0))
# 返回生成的事件列表
return events
# 编码并索引事件到音频帧时间
def encode_and_index_events(
state, event_times, event_values, codec, frame_times, encode_event_fn, encoding_state_to_events_fn=None
):
"""编码一个定时事件序列并索引到音频帧时间。
将时间偏移编码为重复的单步偏移,以便后续运行长度编码。
可选地,还可以编码一个“状态事件”序列,跟踪每个音频帧的当前编码状态。
这可以用于在目标段前添加表示当前状态的事件。
参数:
state: 初始事件编码状态。
event_times: 事件时间序列。
event_values: 事件值序列。
encode_event_fn: 将事件值转换为一个或多个事件对象的函数。
codec: 将事件对象映射到索引的 Codec 对象。
frame_times: 每个音频帧的时间。
encoding_state_to_events_fn: 将编码状态转换为一个或多个事件对象序列的函数。
返回:
events: 编码事件和偏移量。event_start_indices: 每个音频帧对应的起始事件索引。
注意:由于采样率的差异,一个事件可能对应多个音频索引。这使得拆分序列变得棘手,因为同一个事件可能出现在一个序列的结尾和另一个序列的开头。
event_end_indices: 每个音频帧对应的结束事件索引。用于确保切片时一个块结束于下一个块的开始。应始终满足 event_end_indices[i] = event_start_indices[i + 1]。
state_events: 编码的“状态”事件,表示每个事件之前的编码状态。
state_event_indices: 每个音频帧对应的状态事件索引。
"""
# 对事件时间进行排序,返回排序后的索引
indices = np.argsort(event_times, kind="stable")
# 将事件时间转换为与 codec 的步进对应的步进值
event_steps = [round(event_times[i] * codec.steps_per_second) for i in indices]
# 根据排序的索引重新排列事件值
event_values = [event_values[i] for i in indices]
# 创建空列表以存储事件、状态事件和索引
events = []
state_events = []
event_start_indices = []
state_event_indices = []
# 当前步进、事件索引和状态事件索引初始化为 0
cur_step = 0
cur_event_idx = 0
cur_state_event_idx = 0
# 定义填充当前步进对应的事件起始索引的函数
def fill_event_start_indices_to_cur_step():
# 当事件起始索引列表的长度小于帧时间的长度且当前帧时间小于当前步进转换的时间
while (
len(event_start_indices) < len(frame_times)
and frame_times[len(event_start_indices)] < cur_step / codec.steps_per_second
):
# 将当前事件索引添加到事件起始索引列表中
event_start_indices.append(cur_event_idx)
# 将当前状态事件索引添加到状态事件索引列表中
state_event_indices.append(cur_state_event_idx)
# 遍历事件步骤和事件值的组合
for event_step, event_value in zip(event_steps, event_values):
# 当当前步骤小于事件步骤时,持续添加“shift”事件
while event_step > cur_step:
events.append(codec.encode_event(Event(type="shift", value=1)))
# 当前步骤增加
cur_step += 1
# 填充当前步骤的事件开始索引
fill_event_start_indices_to_cur_step()
# 记录当前事件的索引
cur_event_idx = len(events)
# 记录当前状态事件的索引
cur_state_event_idx = len(state_events)
# 如果有编码状态到事件的函数
if encoding_state_to_events_fn:
# 在处理下一个事件前,转储状态到状态事件
for e in encoding_state_to_events_fn(state):
state_events.append(codec.encode_event(e))
# 编码事件并添加到事件列表中
for e in encode_event_fn(state, event_value, codec):
events.append(codec.encode_event(e))
# 在最后一个事件后,继续填充事件开始索引数组
# 不严格的比较是因为当前步骤与音频帧的起始重合时需要额外的“shift”事件
while cur_step / codec.steps_per_second <= frame_times[-1]:
events.append(codec.encode_event(Event(type="shift", value=1)))
# 当前步骤增加
cur_step += 1
# 填充当前步骤的事件开始索引
fill_event_start_indices_to_cur_step()
# 记录当前事件的索引
cur_event_idx = len(events)
# 填充事件结束索引,确保每个切片结束于下一个切片的开始
event_end_indices = event_start_indices[1:] + [len(events)]
# 将事件转换为整型数组
events = np.array(events).astype(np.int32)
# 将状态事件转换为整型数组
state_events = np.array(state_events).astype(np.int32)
# 将事件开始索引分段
event_start_indices = segment(np.array(event_start_indices).astype(np.int32), TARGET_FEATURE_LENGTH)
# 将事件结束索引分段
event_end_indices = segment(np.array(event_end_indices).astype(np.int32), TARGET_FEATURE_LENGTH)
# 将状态事件索引分段
state_event_indices = segment(np.array(state_event_indices).astype(np.int32), TARGET_FEATURE_LENGTH)
# 初始化输出列表
outputs = []
# 遍历每组开始索引、结束索引和状态事件索引
for start_indices, end_indices, event_indices in zip(event_start_indices, event_end_indices, state_event_indices):
# 将输入和索引信息添加到输出中
outputs.append(
{
"inputs": events,
"event_start_indices": start_indices,
"event_end_indices": end_indices,
"state_events": state_events,
"state_event_indices": event_indices,
}
)
# 返回最终的输出列表
return outputs
# 从特征中提取与音频令牌段对应的目标序列
def extract_sequence_with_indices(features, state_events_end_token=None, feature_key="inputs"):
# 创建特征的副本以避免修改原始数据
features = features.copy()
# 获取事件开始索引的第一个值
start_idx = features["event_start_indices"][0]
# 获取事件结束索引的最后一个值
end_idx = features["event_end_indices"][-1]
# 根据开始和结束索引截取特征中的目标序列
features[feature_key] = features[feature_key][start_idx:end_idx]
# 如果给定了状态事件结束标记
if state_events_end_token is not None:
# 获取与音频开始令牌对应的状态事件开始索引
state_event_start_idx = features["state_event_indices"][0]
# 状态事件结束索引初始为开始索引加一
state_event_end_idx = state_event_start_idx + 1
# 遍历状态事件,直到遇到结束标记
while features["state_events"][state_event_end_idx - 1] != state_events_end_token:
state_event_end_idx += 1
# 将状态事件与目标序列连接在一起
features[feature_key] = np.concatenate(
[
features["state_events"][state_event_start_idx:state_event_end_idx],
features[feature_key],
],
axis=0,
)
# 返回包含处理后特征的字典
return features
# 将 MIDI 程序映射应用于令牌序列
def map_midi_programs(
feature, codec: Codec, granularity_type: str = "full", feature_key: str = "inputs"
) -> Mapping[str, Any]:
# 获取与给定粒度类型对应的粒度映射
granularity = PROGRAM_GRANULARITIES[granularity_type]
# 使用粒度映射函数处理特征的令牌序列
feature[feature_key] = granularity.tokens_map_fn(feature[feature_key], codec)
# 返回处理后的特征
return feature
# 返回一个函数,用于对给定编码器的变化进行行程编码
def run_length_encode_shifts_fn(
features,
codec: Codec,
feature_key: str = "inputs",
state_change_event_types: Sequence[str] = (),
) -> Callable[[Mapping[str, Any]], Mapping[str, Any]]:
"""返回一个函数,用于对给定编码器的变化进行行程编码。
参数:
codec: 用于变化事件的编码器。
feature_key: 要进行行程编码的特征键。
state_change_event_types: 表示状态变化的事件类型列表;
对应这些事件类型的令牌将被解释为状态变化,冗余的将被移除。
返回:
一个预处理函数,用于对单步变化进行行程编码。
"""
# 为每种状态变化事件类型获取事件类型范围
state_change_event_ranges = [codec.event_type_range(event_type) for event_type in state_change_event_types]
# 定义一个函数,进行运行长度编码处理
def run_length_encode_shifts(features: MutableMapping[str, Any]) -> Mapping[str, Any]:
"""合并前导/内部移位,修剪尾部移位。
Args:
features: 要处理的特征字典。
Returns:
特征字典。
"""
# 从特征字典中提取事件
events = features[feature_key]
# 初始化移位步骤计数器
shift_steps = 0
# 初始化总移位步骤计数器
total_shift_steps = 0
# 创建空的输出数组,用于存储结果
output = np.array([], dtype=np.int32)
# 初始化当前状态数组,大小与状态变化事件范围相同
current_state = np.zeros(len(state_change_event_ranges), dtype=np.int32)
# 遍历所有事件
for event in events:
# 检查当前事件是否为移位事件
if codec.is_shift_event_index(event):
# 增加当前移位步骤计数
shift_steps += 1
# 增加总移位步骤计数
total_shift_steps += 1
else:
# 如果当前事件是状态变化且与当前状态相同,标记为冗余
is_redundant = False
# 遍历状态变化事件范围
for i, (min_index, max_index) in enumerate(state_change_event_ranges):
# 检查当前事件是否在范围内
if (min_index <= event) and (event <= max_index):
# 如果当前状态与事件相同,标记为冗余
if current_state[i] == event:
is_redundant = True
# 更新当前状态
current_state[i] = event
# 如果标记为冗余,跳过该事件
if is_redundant:
continue
# 一旦遇到非移位事件,进行之前移位事件的 RLE 编码
if shift_steps > 0:
# 设置当前移位步骤为总移位步骤
shift_steps = total_shift_steps
# 处理所有移位步骤
while shift_steps > 0:
# 计算当前可输出的步骤数,不能超过最大移位步骤
output_steps = np.minimum(codec.max_shift_steps, shift_steps)
# 将当前输出步骤添加到结果数组
output = np.concatenate([output, [output_steps]], axis=0)
# 减少剩余移位步骤
shift_steps -= output_steps
# 添加当前事件到输出数组
output = np.concatenate([output, [event]], axis=0)
# 将处理后的输出数组存回特征字典
features[feature_key] = output
# 返回更新后的特征字典
return features
# 调用函数并返回结果
return run_length_encode_shifts(features)
# 定义一个处理音符表示的处理链函数,接受特征、编解码器和音符表示配置
def note_representation_processor_chain(features, codec: Codec, note_representation_config: NoteRepresentationConfig):
# 编码一个 "tie" 事件,值为 0
tie_token = codec.encode_event(Event("tie", 0))
# 如果配置包含连音符,则将其作为状态事件结束的标记,否则为 None
state_events_end_token = tie_token if note_representation_config.include_ties else None
# 从特征中提取序列,指定状态事件结束标记和特征键
features = extract_sequence_with_indices(
features, state_events_end_token=state_events_end_token, feature_key="inputs"
)
# 映射 MIDI 程序到特征
features = map_midi_programs(features, codec)
# 对特征进行游程编码,指定状态变化事件类型
features = run_length_encode_shifts_fn(features, codec, state_change_event_types=["velocity", "program"])
# 返回处理后的特征
return features
# 定义一个 MIDI 处理类
class MidiProcessor:
# 初始化 MIDI 处理器
def __init__(self):
# 创建一个编解码器,设置最大移动步数、每秒步数和事件范围
self.codec = Codec(
max_shift_steps=DEFAULT_MAX_SHIFT_SECONDS * DEFAULT_STEPS_PER_SECOND,
steps_per_second=DEFAULT_STEPS_PER_SECOND,
event_ranges=[
EventRange("pitch", note_seq.MIN_MIDI_PITCH, note_seq.MAX_MIDI_PITCH),
EventRange("velocity", 0, DEFAULT_NUM_VELOCITY_BINS),
EventRange("tie", 0, 0),
EventRange("program", note_seq.MIN_MIDI_PROGRAM, note_seq.MAX_MIDI_PROGRAM),
EventRange("drum", note_seq.MIN_MIDI_PITCH, note_seq.MAX_MIDI_PITCH),
],
)
# 创建一个标记器,传入编解码器的类别数量
self.tokenizer = Tokenizer(self.codec.num_classes)
# 配置音符表示,设置为仅包含起始音符并包括连音符
self.note_representation_config = NoteRepresentationConfig(onsets_only=False, include_ties=True)
# 定义调用函数,接受 MIDI 数据
def __call__(self, midi: Union[bytes, os.PathLike, str]):
# 检查输入是否为字节,如果不是则读取文件内容
if not isinstance(midi, bytes):
with open(midi, "rb") as f:
midi = f.read()
# 将 MIDI 数据转换为音符序列
ns = note_seq.midi_to_note_sequence(midi)
# 应用延音控制变化到音符序列
ns_sus = note_seq.apply_sustain_control_changes(ns)
# 遍历音符序列中的音符
for note in ns_sus.notes:
# 如果音符不是打击乐器,则将程序号转换为相应的程序
if not note.is_drum:
note.program = program_to_slakh_program(note.program)
# 创建一个零数组以存储样本,长度为总时间乘以采样率
samples = np.zeros(int(ns_sus.total_time * SAMPLE_RATE))
# 将音频样本转换为帧,获取帧时间
_, frame_times = audio_to_frames(samples, HOP_SIZE, FRAME_RATE)
# 从音符序列提取开始和结束时间及程序号
times, values = note_sequence_to_onsets_and_offsets_and_programs(ns_sus)
# 编码和索引事件
events = encode_and_index_events(
state=NoteEncodingState(),
event_times=times,
event_values=values,
frame_times=frame_times,
codec=self.codec,
encode_event_fn=note_event_data_to_events,
encoding_state_to_events_fn=note_encoding_state_to_events,
)
# 对每个事件应用音符表示处理链
events = [
note_representation_processor_chain(event, self.codec, self.note_representation_config) for event in events
]
# 对处理后的事件进行编码,生成输入标记
input_tokens = [self.tokenizer.encode(event["inputs"]) for event in events]
# 返回输入标记
return input_tokens
.\diffusers\pipelines\deprecated\spectrogram_diffusion\notes_encoder.py
# 版权所有 2022 The Music Spectrogram Diffusion Authors.
# 版权所有 2024 The HuggingFace Team。保留所有权利。
#
# 根据 Apache 许可证第 2.0 版(“许可证”)进行许可;
# 除非遵循许可证,否则不得使用此文件。
# 您可以在以下地址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面同意,否则根据许可证分发的软件是以“原样”基础分发的,
# 不提供任何形式的担保或条件,无论是明示还是暗示。
# 有关许可证的特定语言,见于许可证的许可和限制。
# 导入 PyTorch 库
import torch
# 导入 PyTorch 的神经网络模块
import torch.nn as nn
# 从 transformers 库导入模块工具类
from transformers.modeling_utils import ModuleUtilsMixin
# 从 transformers 库导入 T5 模型相关类
from transformers.models.t5.modeling_t5 import T5Block, T5Config, T5LayerNorm
# 从父级目录导入配置混合类和注册配置的装饰器
from ....configuration_utils import ConfigMixin, register_to_config
# 从父级目录导入模型混合类
from ....models import ModelMixin
# 定义 SpectrogramNotesEncoder 类,继承多个混合类
class SpectrogramNotesEncoder(ModelMixin, ConfigMixin, ModuleUtilsMixin):
# 注册构造函数到配置
@register_to_config
def __init__(
self,
max_length: int, # 输入序列的最大长度
vocab_size: int, # 词汇表的大小
d_model: int, # 模型的维度
dropout_rate: float, # dropout 比率
num_layers: int, # 编码器的层数
num_heads: int, # 多头注意力机制的头数
d_kv: int, # 键和值的维度
d_ff: int, # 前馈网络的维度
feed_forward_proj: str, # 前馈网络的投影类型
is_decoder: bool = False, # 是否为解码器
):
# 调用父类构造函数
super().__init__()
# 创建词嵌入层
self.token_embedder = nn.Embedding(vocab_size, d_model)
# 创建位置编码层
self.position_encoding = nn.Embedding(max_length, d_model)
# 设置位置编码的权重不需要梯度
self.position_encoding.weight.requires_grad = False
# 创建 dropout 层以在前向传播前进行正则化
self.dropout_pre = nn.Dropout(p=dropout_rate)
# 配置 T5 模型的参数
t5config = T5Config(
vocab_size=vocab_size, # 词汇表大小
d_model=d_model, # 模型维度
num_heads=num_heads, # 注意力头数
d_kv=d_kv, # 键值维度
d_ff=d_ff, # 前馈网络维度
dropout_rate=dropout_rate, # dropout 比率
feed_forward_proj=feed_forward_proj, # 前馈网络类型
is_decoder=is_decoder, # 是否为解码器
is_encoder_decoder=False, # 是否为编码器-解码器架构
)
# 创建模块列表以存储编码器层
self.encoders = nn.ModuleList()
# 循环添加每个编码器层
for lyr_num in range(num_layers):
lyr = T5Block(t5config) # 创建 T5Block 实例
self.encoders.append(lyr) # 将编码器层添加到列表
# 创建层归一化层
self.layer_norm = T5LayerNorm(d_model)
# 创建 dropout 层以在输出后进行正则化
self.dropout_post = nn.Dropout(p=dropout_rate)
# 定义前向传播函数
def forward(self, encoder_input_tokens, encoder_inputs_mask):
# 通过词嵌入层获取嵌入表示
x = self.token_embedder(encoder_input_tokens)
# 获取输入序列的长度
seq_length = encoder_input_tokens.shape[1]
# 创建输入位置的张量
inputs_positions = torch.arange(seq_length, device=encoder_input_tokens.device)
# 将位置编码添加到嵌入表示
x += self.position_encoding(inputs_positions)
# 应用前向 dropout
x = self.dropout_pre(x)
# 反转注意力掩码
input_shape = encoder_input_tokens.size()
# 获取扩展的注意力掩码
extended_attention_mask = self.get_extended_attention_mask(encoder_inputs_mask, input_shape)
# 遍历每个编码器层,执行前向传播
for lyr in self.encoders:
x = lyr(x, extended_attention_mask)[0]
# 应用层归一化
x = self.layer_norm(x)
# 返回经过后向 dropout 的输出和输入掩码
return self.dropout_post(x), encoder_inputs_mask
.\diffusers\pipelines\deprecated\spectrogram_diffusion\pipeline_spectrogram_diffusion.py
# 版权所有 2022 The Music Spectrogram Diffusion Authors.
# 版权所有 2024 The HuggingFace Team。保留所有权利。
#
# 根据 Apache License, Version 2.0 (以下称为“许可证”)进行许可;
# 除非遵守许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,软件
# 按“原样”分发,不附带任何明示或暗示的担保或条件。
# 请参见许可证以获取有关权限的具体说明和
# 限制。
import math # 导入数学库以进行数学运算
from typing import Any, Callable, List, Optional, Tuple, Union # 导入类型提示
import numpy as np # 导入 NumPy 库以进行数值计算
import torch # 导入 PyTorch 库以进行深度学习
from ....models import T5FilmDecoder # 从模型模块导入 T5FilmDecoder 类
from ....schedulers import DDPMScheduler # 从调度器模块导入 DDPMScheduler 类
from ....utils import is_onnx_available, logging # 导入实用函数和日志功能
from ....utils.torch_utils import randn_tensor # 从 PyTorch 实用程序导入随机张量函数
if is_onnx_available(): # 如果 ONNX 可用
from ...onnx_utils import OnnxRuntimeModel # 导入 ONNX 运行时模型类
from ...pipeline_utils import AudioPipelineOutput, DiffusionPipeline # 导入音频管道输出和扩散管道类
from .continuous_encoder import SpectrogramContEncoder # 从当前模块导入连续编码器
from .notes_encoder import SpectrogramNotesEncoder # 从当前模块导入音符编码器
logger = logging.get_logger(__name__) # 创建当前模块的日志记录器,禁用 pylint 名称检查
TARGET_FEATURE_LENGTH = 256 # 定义目标特征长度常量
class SpectrogramDiffusionPipeline(DiffusionPipeline): # 定义一个继承自 DiffusionPipeline 的类
r""" # 开始类文档字符串
用于无条件音频生成的管道。
此模型继承自 [`DiffusionPipeline`]. 检查超类文档以获取所有管道实现的通用方法
(下载、保存、在特定设备上运行等)。
参数:
notes_encoder ([`SpectrogramNotesEncoder`]): # 音符编码器参数
continuous_encoder ([`SpectrogramContEncoder`]): # 连续编码器参数
decoder ([`T5FilmDecoder`]): # 解码器参数
用于去噪编码音频潜在特征的 [`T5FilmDecoder`]。
scheduler ([`DDPMScheduler`]): # 调度器参数
与 `decoder` 一起使用以去噪编码音频潜在特征的调度器。
melgan ([`OnnxRuntimeModel`]): # MELGAN 参数
""" # 结束类文档字符串
_optional_components = ["melgan"] # 定义可选组件列表
def __init__( # 构造函数定义
self,
notes_encoder: SpectrogramNotesEncoder, # 音符编码器
continuous_encoder: SpectrogramContEncoder, # 连续编码器
decoder: T5FilmDecoder, # 解码器
scheduler: DDPMScheduler, # 调度器
melgan: OnnxRuntimeModel if is_onnx_available() else Any, # MELGAN,如果可用则为 OnnxRuntimeModel
) -> None: # 返回类型为 None
super().__init__() # 调用父类构造函数
# 从 MELGAN
self.min_value = math.log(1e-5) # 设置最小值,与 MelGAN 训练匹配
self.max_value = 4.0 # 设置最大值,适用于大多数示例
self.n_dims = 128 # 设置维度数
self.register_modules( # 注册模块,包括编码器和调度器
notes_encoder=notes_encoder, # 注册音符编码器
continuous_encoder=continuous_encoder, # 注册连续编码器
decoder=decoder, # 注册解码器
scheduler=scheduler, # 注册调度器
melgan=melgan, # 注册 MELGAN
)
# 定义特征缩放函数,将输入特征线性缩放到网络输出范围
def scale_features(self, features, output_range=(-1.0, 1.0), clip=False):
# 解包输出范围的最小值和最大值
min_out, max_out = output_range
# 如果启用剪辑,将特征限制在指定的最小值和最大值之间
if clip:
features = torch.clip(features, self.min_value, self.max_value)
# 将特征缩放到[0, 1]的范围
zero_one = (features - self.min_value) / (self.max_value - self.min_value)
# 将特征缩放到[min_out, max_out]的范围并返回
return zero_one * (max_out - min_out) + min_out
# 定义反向缩放函数,将网络输出线性缩放到特征范围
def scale_to_features(self, outputs, input_range=(-1.0, 1.0), clip=False):
# 解包输入范围的最小值和最大值
min_out, max_out = input_range
# 如果启用剪辑,将输出限制在指定的范围
outputs = torch.clip(outputs, min_out, max_out) if clip else outputs
# 将输出缩放到[0, 1]的范围
zero_one = (outputs - min_out) / (max_out - min_out)
# 将输出缩放到[self.min_value, self.max_value]的范围并返回
return zero_one * (self.max_value - self.min_value) + self.min_value
# 定义编码函数,将输入令牌和连续输入编码为网络的表示
def encode(self, input_tokens, continuous_inputs, continuous_mask):
# 创建令牌掩码,标识有效输入
tokens_mask = input_tokens > 0
# 使用令牌编码器编码输入令牌和掩码
tokens_encoded, tokens_mask = self.notes_encoder(
encoder_input_tokens=input_tokens, encoder_inputs_mask=tokens_mask
)
# 使用连续输入编码器编码连续输入和掩码
continuous_encoded, continuous_mask = self.continuous_encoder(
encoder_inputs=continuous_inputs, encoder_inputs_mask=continuous_mask
)
# 返回编码后的令牌和连续输入
return [(tokens_encoded, tokens_mask), (continuous_encoded, continuous_mask)]
# 定义解码函数,根据编码和掩码生成输出
def decode(self, encodings_and_masks, input_tokens, noise_time):
# 将噪声时间赋值给时间步长
timesteps = noise_time
# 如果时间步长不是张量,则转换为张量
if not torch.is_tensor(timesteps):
timesteps = torch.tensor([timesteps], dtype=torch.long, device=input_tokens.device)
# 如果时间步长是张量但维度为0,则增加一个维度
elif torch.is_tensor(timesteps) and len(timesteps.shape) == 0:
timesteps = timesteps[None].to(input_tokens.device)
# 通过广播使时间步长适应批处理维度,确保与ONNX/Core ML兼容
timesteps = timesteps * torch.ones(input_tokens.shape[0], dtype=timesteps.dtype, device=timesteps.device)
# 使用解码器生成 logits
logits = self.decoder(
encodings_and_masks=encodings_and_masks, decoder_input_tokens=input_tokens, decoder_noise_time=timesteps
)
# 返回生成的 logits
return logits
# 使用torch.no_grad()装饰器,避免计算梯度
@torch.no_grad()
def __call__(
# 定义调用方法的参数,包括输入令牌、生成器和推理步骤等
input_tokens: List[List[int]],
generator: Optional[torch.Generator] = None,
num_inference_steps: int = 100,
return_dict: bool = True,
output_type: str = "np",
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
callback_steps: int = 1,
.\diffusers\pipelines\deprecated\spectrogram_diffusion\__init__.py
# flake8: noqa # 忽略 flake8 的检查
from typing import TYPE_CHECKING # 从 typing 模块导入 TYPE_CHECKING,用于类型检查
from ....utils import ( # 从 utils 模块导入必要的工具函数和常量
DIFFUSERS_SLOW_IMPORT, # 导入 DIFFUSERS_SLOW_IMPORT 常量
_LazyModule, # 导入 _LazyModule 类
is_note_seq_available, # 导入检测 note_seq 可用性的函数
OptionalDependencyNotAvailable, # 导入表示可选依赖不可用的异常
is_torch_available, # 导入检测 PyTorch 可用性的函数
is_transformers_available, # 导入检测 transformers 可用性的函数
get_objects_from_module, # 导入从模块获取对象的函数
)
_dummy_objects = {} # 初始化一个空字典,用于存储虚拟对象
_import_structure = {} # 初始化一个空字典,用于存储导入结构
try: # 尝试执行以下代码块
if not (is_transformers_available() and is_torch_available()): # 检查 transformers 和 torch 是否可用
raise OptionalDependencyNotAvailable() # 如果不可用,抛出异常
except OptionalDependencyNotAvailable: # 捕获可选依赖不可用的异常
from ....utils import dummy_torch_and_transformers_objects # 导入虚拟对象模块
_dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects)) # 更新虚拟对象字典
else: # 如果没有异常,执行以下代码
_import_structure["continous_encoder"] = ["SpectrogramContEncoder"] # 添加连续编码器到导入结构
_import_structure["notes_encoder"] = ["SpectrogramNotesEncoder"] # 添加音符编码器到导入结构
_import_structure["pipeline_spectrogram_diffusion"] = [ # 添加谱图扩散管道到导入结构
"SpectrogramContEncoder", # 添加连续编码器
"SpectrogramDiffusionPipeline", # 添加谱图扩散管道
"T5FilmDecoder", # 添加 T5 电影解码器
]
try: # 再次尝试执行代码块
if not (is_transformers_available() and is_torch_available() and is_note_seq_available()): # 检查所有三个依赖项
raise OptionalDependencyNotAvailable() # 如果不可用,抛出异常
except OptionalDependencyNotAvailable: # 捕获可选依赖不可用的异常
from ....utils import dummy_transformers_and_torch_and_note_seq_objects # 导入所有虚拟对象模块
_dummy_objects.update(get_objects_from_module(dummy_transformers_and_torch_and_note_seq_objects)) # 更新虚拟对象字典
else: # 如果没有异常,执行以下代码
_import_structure["midi_utils"] = ["MidiProcessor"] # 添加 MIDI 工具到导入结构
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT: # 如果正在类型检查或慢导入标志为真
try: # 尝试执行以下代码块
if not (is_transformers_available() and is_torch_available()): # 检查 transformers 和 torch 是否可用
raise OptionalDependencyNotAvailable() # 如果不可用,抛出异常
except OptionalDependencyNotAvailable: # 捕获可选依赖不可用的异常
from ....utils.dummy_torch_and_transformers_objects import * # 导入所有虚拟对象
else: # 如果没有异常,执行以下代码
from .pipeline_spectrogram_diffusion import SpectrogramDiffusionPipeline # 从谱图扩散管道导入 SpectrogramDiffusionPipeline
from .pipeline_spectrogram_diffusion import SpectrogramContEncoder # 从谱图扩散管道导入 SpectrogramContEncoder
from .pipeline_spectrogram_diffusion import SpectrogramNotesEncoder # 从谱图扩散管道导入 SpectrogramNotesEncoder
from .pipeline_spectrogram_diffusion import T5FilmDecoder # 从谱图扩散管道导入 T5FilmDecoder
try: # 再次尝试执行代码块
if not (is_transformers_available() and is_torch_available() and is_note_seq_available()): # 检查所有三个依赖项
raise OptionalDependencyNotAvailable() # 如果不可用,抛出异常
except OptionalDependencyNotAvailable: # 捕获可选依赖不可用的异常
from ....utils.dummy_transformers_and_torch_and_note_seq_objects import * # 导入所有虚拟对象
else: # 如果没有异常,执行以下代码
from .midi_utils import MidiProcessor # 从 MIDI 工具模块导入 MidiProcessor
else: # 如果不是类型检查且慢导入标志为假
import sys # 导入 sys 模块
sys.modules[__name__] = _LazyModule( # 将当前模块替换为懒加载模块
__name__, # 模块名称
globals()["__file__"], # 模块文件路径
_import_structure, # 导入结构
module_spec=__spec__, # 模块规格
)
for name, value in _dummy_objects.items(): # 遍历虚拟对象字典
setattr(sys.modules[__name__], name, value) # 将虚拟对象设置到当前模块
.\diffusers\pipelines\deprecated\stable_diffusion_variants\pipeline_cycle_diffusion.py
# 版权声明,表明版权归 HuggingFace 团队所有
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache 2.0 许可证授权(“许可证”);
# 除非遵守许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有规定,
# 否则根据许可证分发的软件是按“原样”提供的,
# 不附带任何形式的保证或条件,无论是明示或暗示的。
# 请参见许可证以了解管理权限和限制的具体条款。
# 导入 inspect 模块,用于获取对象的信息
import inspect
# 从 typing 模块导入类型提示所需的类型
from typing import Any, Callable, Dict, List, Optional, Union
# 导入 numpy 库,用于数值计算和数组操作
import numpy as np
# 导入 PIL.Image,用于图像处理
import PIL.Image
# 导入 torch 库,提供深度学习功能
import torch
# 从 packaging 导入版本控制功能
from packaging import version
# 从 transformers 导入 CLIP 相关的类
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer
# 从相对路径导入 FrozenDict 类
from ....configuration_utils import FrozenDict
# 从相对路径导入图像处理相关类
from ....image_processor import PipelineImageInput, VaeImageProcessor
# 从相对路径导入加载器类
from ....loaders import StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin
# 从相对路径导入模型类
from ....models import AutoencoderKL, UNet2DConditionModel
# 从相对路径导入 Lora 调整函数
from ....models.lora import adjust_lora_scale_text_encoder
# 从相对路径导入调度器类
from ....schedulers import DDIMScheduler
# 从相对路径导入常用工具函数和变量
from ....utils import PIL_INTERPOLATION, USE_PEFT_BACKEND, deprecate, logging, scale_lora_layers, unscale_lora_layers
# 从相对路径导入随机数生成相关的工具函数
from ....utils.torch_utils import randn_tensor
# 从相对路径导入扩散管道类
from ...pipeline_utils import DiffusionPipeline
# 从相对路径导入稳定扩散的输出类
from ...stable_diffusion.pipeline_output import StableDiffusionPipelineOutput
# 从相对路径导入稳定扩散的安全检查器类
from ...stable_diffusion.safety_checker import StableDiffusionSafetyChecker
# 创建一个日志记录器实例,用于记录模块中的日志
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img 中复制的预处理函数
def preprocess(image):
# 定义弃用信息,提示用户该方法将在未来版本中被移除
deprecation_message = "The preprocess method is deprecated and will be removed in diffusers 1.0.0. Please use VaeImageProcessor.preprocess(...) instead"
# 调用弃用函数,记录警告信息
deprecate("preprocess", "1.0.0", deprecation_message, standard_warn=False)
# 如果输入是一个张量,则直接返回
if isinstance(image, torch.Tensor):
return image
# 如果输入是 PIL 图像,则将其放入列表中
elif isinstance(image, PIL.Image.Image):
image = [image]
# 如果列表中的第一个元素是 PIL 图像
if isinstance(image[0], PIL.Image.Image):
# 获取图像的宽和高
w, h = image[0].size
# 将宽和高调整为 8 的整数倍
w, h = (x - x % 8 for x in (w, h)) # resize to integer multiple of 8
# 对每个图像进行调整大小,并转换为数组格式
image = [np.array(i.resize((w, h), resample=PIL_INTERPOLATION["lanczos"]))[None, :] for i in image]
# 将图像数组沿第一个维度拼接
image = np.concatenate(image, axis=0)
# 将像素值归一化到 [0, 1] 范围,并转换为 float32 类型
image = np.array(image).astype(np.float32) / 255.0
# 调整数组维度顺序,从 (N, H, W, C) 转换为 (N, C, H, W)
image = image.transpose(0, 3, 1, 2)
# 将像素值从 [0, 1] 线性映射到 [-1, 1]
image = 2.0 * image - 1.0
# 将 NumPy 数组转换为 PyTorch 张量
image = torch.from_numpy(image)
# 如果列表中的第一个元素是张量,则在第 0 维拼接这些张量
elif isinstance(image[0], torch.Tensor):
image = torch.cat(image, dim=0)
# 返回处理后的图像
return image
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img 中复制的检索潜在变量函数
def retrieve_latents(
# 输入的编码器输出,类型为 torch.Tensor
encoder_output: torch.Tensor,
# 可选的随机数生成器
generator: Optional[torch.Generator] = None,
# 采样模式,默认为 "sample"
sample_mode: str = "sample"
):
# 检查 encoder_output 是否具有 "latent_dist" 属性,并且采样模式为 "sample"
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
# 从 latent_dist 中随机采样并返回生成器生成的样本
return encoder_output.latent_dist.sample(generator)
# 检查 encoder_output 是否具有 "latent_dist" 属性,并且采样模式为 "argmax"
elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
# 返回 latent_dist 的众数作为结果
return encoder_output.latent_dist.mode()
# 检查 encoder_output 是否具有 "latents" 属性
elif hasattr(encoder_output, "latents"):
# 返回 latents 属性的值
return encoder_output.latents
# 如果以上条件都不满足,抛出属性错误
else:
raise AttributeError("Could not access latents of provided encoder_output")
# 后验采样函数,生成前一时刻的潜变量
def posterior_sample(scheduler, latents, timestep, clean_latents, generator, eta):
# 1. 获取前一步的时间值(t-1)
prev_timestep = timestep - scheduler.config.num_train_timesteps // scheduler.num_inference_steps
# 如果前一步时间值小于等于0,返回干净的潜变量
if prev_timestep <= 0:
return clean_latents
# 2. 计算 alpha 和 beta 值
alpha_prod_t = scheduler.alphas_cumprod[timestep] # 当前时间步的累积 alpha
alpha_prod_t_prev = (
scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else scheduler.final_alpha_cumprod
) # 前一步时间步的累积 alpha
# 获取当前和前一步的方差
variance = scheduler._get_variance(timestep, prev_timestep)
std_dev_t = eta * variance ** (0.5) # 计算标准差
# 指向 x_t 的方向
e_t = (latents - alpha_prod_t ** (0.5) * clean_latents) / (1 - alpha_prod_t) ** (0.5)
dir_xt = (1.0 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * e_t # 计算 x_t 的方向
# 生成噪声
noise = std_dev_t * randn_tensor(
clean_latents.shape, dtype=clean_latents.dtype, device=clean_latents.device, generator=generator
)
# 计算前一步的潜变量
prev_latents = alpha_prod_t_prev ** (0.5) * clean_latents + dir_xt + noise
# 返回前一步的潜变量
return prev_latents
# 计算噪声的函数
def compute_noise(scheduler, prev_latents, latents, timestep, noise_pred, eta):
# 1. 获取前一步的时间值(t-1)
prev_timestep = timestep - scheduler.config.num_train_timesteps // scheduler.num_inference_steps
# 2. 计算 alpha 和 beta 值
alpha_prod_t = scheduler.alphas_cumprod[timestep] # 当前时间步的累积 alpha
alpha_prod_t_prev = (
scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else scheduler.final_alpha_cumprod
) # 前一步时间步的累积 alpha
beta_prod_t = 1 - alpha_prod_t # 计算 beta 值
# 3. 根据预测的噪声计算预测的原始样本
pred_original_sample = (latents - beta_prod_t ** (0.5) * noise_pred) / alpha_prod_t ** (0.5)
# 4. 对预测的原始样本进行剪辑
if scheduler.config.clip_sample:
pred_original_sample = torch.clamp(pred_original_sample, -1, 1) # 限制值范围在[-1, 1]之间
# 5. 计算方差
variance = scheduler._get_variance(timestep, prev_timestep) # 获取方差
std_dev_t = eta * variance ** (0.5) # 计算标准差
# 6. 计算指向 x_t 的方向
pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * noise_pred
# 计算噪声
noise = (prev_latents - (alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction)) / (
variance ** (0.5) * eta
)
return noise
# 文本引导图像生成的扩散管道类
class CycleDiffusionPipeline(DiffusionPipeline, TextualInversionLoaderMixin, StableDiffusionLoraLoaderMixin):
r"""
基于文本引导的图像生成管道,使用稳定扩散模型。
此模型继承自 [`DiffusionPipeline`]。请查看超类文档以获取所有管道实现的通用方法
(下载、保存、在特定设备上运行等)。
# 管道继承以下加载方法:
# - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] 用于加载文本反转嵌入
# - [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
# - [`~loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
# 参数说明:
# vae ([`AutoencoderKL`]):变分自编码器模型,用于将图像编码和解码为潜在表示。
# text_encoder ([`~transformers.CLIPTextModel`]):冻结的文本编码器,使用 [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)。
# tokenizer ([`~transformers.CLIPTokenizer`]):用于文本标记化的 `CLIPTokenizer`。
# unet ([`UNet2DConditionModel`]):用于去噪编码图像潜在的 `UNet2DConditionModel`。
# scheduler ([`SchedulerMixin`]):与 `unet` 结合使用以去噪编码图像潜在的调度器。只能是 [`DDIMScheduler`] 的实例。
# safety_checker ([`StableDiffusionSafetyChecker`]):分类模块,估计生成的图像是否可能被认为是冒犯性或有害的。
# 请参考 [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) 获取更多关于模型潜在危害的详细信息。
# feature_extractor ([`~transformers.CLIPImageProcessor`]):用于从生成图像中提取特征的 `CLIPImageProcessor`;作为输入用于 `safety_checker`。
# 定义模型在 CPU 上的卸载顺序
model_cpu_offload_seq = "text_encoder->unet->vae"
# 定义可选组件列表
_optional_components = ["safety_checker", "feature_extractor"]
# 初始化方法,定义构造函数的参数
def __init__(
self,
vae: AutoencoderKL, # 变分自编码器
text_encoder: CLIPTextModel, # 文本编码器
tokenizer: CLIPTokenizer, # 文本标记器
unet: UNet2DConditionModel, # 去噪模型
scheduler: DDIMScheduler, # 调度器
safety_checker: StableDiffusionSafetyChecker, # 安全检查器
feature_extractor: CLIPImageProcessor, # 特征提取器
requires_safety_checker: bool = True, # 是否需要安全检查器的布尔值
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt 复制
def _encode_prompt(
self,
prompt, # 输入的提示信息
device, # 设备信息(CPU/GPU)
num_images_per_prompt, # 每个提示生成的图像数量
do_classifier_free_guidance, # 是否使用无分类器引导
negative_prompt=None, # 可选的负面提示
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面提示嵌入
lora_scale: Optional[float] = None, # 可选的 LoRA 缩放因子
**kwargs, # 其他关键字参数
# 定义函数结束的括号
):
# 设置弃用警告信息,提示用户该方法将在未来版本中被移除
deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple."
# 调用弃用函数,记录弃用信息,版本为 1.0.0,且不使用标准警告
deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False)
# 调用 encode_prompt 方法,生成提示嵌入元组
prompt_embeds_tuple = self.encode_prompt(
# 传入提示内容
prompt=prompt,
# 传入设备信息
device=device,
# 每个提示生成的图像数量
num_images_per_prompt=num_images_per_prompt,
# 是否执行无分类器引导
do_classifier_free_guidance=do_classifier_free_guidance,
# 传入负提示内容
negative_prompt=negative_prompt,
# 传入提示嵌入(可选)
prompt_embeds=prompt_embeds,
# 传入负提示嵌入(可选)
negative_prompt_embeds=negative_prompt_embeds,
# 传入 LoRA 比例(可选)
lora_scale=lora_scale,
# 传入额外参数(可变参数)
**kwargs,
)
# 将提示嵌入元组中的两个部分进行连接,以兼容旧版
prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]])
# 返回最终的提示嵌入
return prompt_embeds
# 定义 encode_prompt 方法,来自稳定扩散管道
# 该方法用于编码提示信息
def encode_prompt(
self,
# 提示内容
prompt,
# 设备信息
device,
# 每个提示生成的图像数量
num_images_per_prompt,
# 是否执行无分类器引导
do_classifier_free_guidance,
# 负提示内容(可选)
negative_prompt=None,
# 提示嵌入(可选)
prompt_embeds: Optional[torch.Tensor] = None,
# 负提示嵌入(可选)
negative_prompt_embeds: Optional[torch.Tensor] = None,
# LoRA 比例(可选)
lora_scale: Optional[float] = None,
# 跳过的剪辑(可选)
clip_skip: Optional[int] = None,
# 定义 check_inputs 方法,检查输入参数
def check_inputs(
self,
# 提示内容
prompt,
# 强度参数
strength,
# 回调步骤
callback_steps,
# 负提示内容(可选)
negative_prompt=None,
# 提示嵌入(可选)
prompt_embeds=None,
# 负提示嵌入(可选)
negative_prompt_embeds=None,
# 在步骤结束时的回调张量输入(可选)
callback_on_step_end_tensor_inputs=None,
):
# 检查 strength 的值是否在 [0.0, 1.0] 范围内,若不在则引发 ValueError
if strength < 0 or strength > 1:
raise ValueError(f"The value of strength should in [0.0, 1.0] but is {strength}")
# 检查 callback_steps 是否为正整数,若不是则引发 ValueError
if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0):
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
# 检查 callback_on_step_end_tensor_inputs 中的所有键是否都在 _callback_tensor_inputs 中
if callback_on_step_end_tensor_inputs is not None and not all(
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
):
raise ValueError(
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
)
# 检查是否同时提供了 prompt 和 prompt_embeds,若是则引发 ValueError
if prompt is not None and prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
# 检查是否两个都未提供,若是则引发 ValueError
elif prompt is None and prompt_embeds is None:
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
# 检查 prompt 的类型是否为 str 或 list,若不是则引发 ValueError
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
# 检查是否同时提供了 negative_prompt 和 negative_prompt_embeds,若是则引发 ValueError
if negative_prompt is not None and negative_prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
# 检查 prompt_embeds 和 negative_prompt_embeds 的形状是否一致,若不一致则引发 ValueError
if prompt_embeds is not None and negative_prompt_embeds is not None:
if prompt_embeds.shape != negative_prompt_embeds.shape:
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# 复制自 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
# 准备调度器步骤的额外参数,因为并不是所有调度器都有相同的参数签名
def prepare_extra_step_kwargs(self, generator, eta):
# eta (η) 仅用于 DDIMScheduler,其他调度器将忽略该参数
# eta 对应于 DDIM 论文中的 η:https://arxiv.org/abs/2010.02502
# 并且应该在 [0, 1] 范围内
# 检查调度器步骤是否接受 eta 参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化额外步骤参数的字典
extra_step_kwargs = {}
# 如果接受 eta 参数,则将其添加到字典中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器步骤是否接受 generator 参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果接受 generator 参数,则将其添加到字典中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回包含额外参数的字典
return extra_step_kwargs
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker 复制
def run_safety_checker(self, image, device, dtype):
# 如果没有安全检查器,则设置 NSFW 概念为 None
if self.safety_checker is None:
has_nsfw_concept = None
else:
# 如果图像是张量格式
if torch.is_tensor(image):
# 将张量后处理为 PIL 格式以供特征提取器使用
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
else:
# 如果图像不是张量,将其转换为 PIL 格式
feature_extractor_input = self.image_processor.numpy_to_pil(image)
# 将处理后的图像输入特征提取器,并转移到指定设备
safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
# 使用安全检查器对图像进行检查并获取 NSFW 概念
image, has_nsfw_concept = self.safety_checker(
images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
)
# 返回检查后的图像和 NSFW 概念的状态
return image, has_nsfw_concept
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents 复制
def decode_latents(self, latents):
# 提示:decode_latents 方法已弃用,并将在 1.0.0 中删除,请使用 VaeImageProcessor.postprocess(...) 代替
deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead"
# 发出弃用警告
deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False)
# 将潜变量按缩放因子进行调整
latents = 1 / self.vae.config.scaling_factor * latents
# 解码潜变量以获得图像,并返回第一个元素
image = self.vae.decode(latents, return_dict=False)[0]
# 将图像值归一化到 [0, 1] 范围内
image = (image / 2 + 0.5).clamp(0, 1)
# 将图像转换为 float32 格式以确保兼容性
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
# 返回解码后的图像
return image
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.StableDiffusionImg2ImgPipeline.get_timesteps 复制
# 定义获取时间步长的方法,接收推理步数、强度和设备作为参数
def get_timesteps(self, num_inference_steps, strength, device):
# 计算初始时间步长,取 num_inference_steps 和 strength 的乘积的最小值
init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
# 计算开始时间步,确保不小于零
t_start = max(num_inference_steps - init_timestep, 0)
# 从调度器中获取时间步,基于 t_start 和调度器的顺序
timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :]
# 如果调度器有设置开始索引的方法,则调用该方法
if hasattr(self.scheduler, "set_begin_index"):
self.scheduler.set_begin_index(t_start * self.scheduler.order)
# 返回时间步和剩余推理步数
return timesteps, num_inference_steps - t_start
# 准备潜在向量,进行图像处理
def prepare_latents(self, image, timestep, batch_size, num_images_per_prompt, dtype, device, generator=None):
# 将图像移动到指定设备并转换为指定数据类型
image = image.to(device=device, dtype=dtype)
# 获取图像的批次大小
batch_size = image.shape[0]
# 检查图像的通道数是否为4
if image.shape[1] == 4:
# 如果是4通道图像,初始化潜在向量为图像本身
init_latents = image
else:
# 检查生成器是否是列表且其长度与批次大小不匹配
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
# 抛出值错误,提示生成器长度与请求的批次大小不匹配
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果生成器是列表
if isinstance(generator, list):
# 使用每个生成器和对应图像获取潜在向量
init_latents = [
retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i])
for i in range(image.shape[0])
]
# 将所有潜在向量拼接成一个张量
init_latents = torch.cat(init_latents, dim=0)
else:
# 使用单个生成器获取潜在向量
init_latents = retrieve_latents(self.vae.encode(image), generator=generator)
# 对初始化的潜在向量进行缩放
init_latents = self.vae.config.scaling_factor * init_latents
# 检查批次大小是否大于初始化潜在向量的数量且可以整除
if batch_size > init_latents.shape[0] and batch_size % init_latents.shape[0] == 0:
# 扩展初始化潜在向量以匹配批次大小
deprecation_message = (
# 创建弃用消息,提醒用户初始图像的数量与文本提示不匹配
f"You have passed {batch_size} text prompts (`prompt`), but only {init_latents.shape[0]} initial"
" images (`image`). Initial images are now duplicating to match the number of text prompts. Note"
" that this behavior is deprecated and will be removed in a version 1.0.0. Please make sure to update"
" your script to pass as many initial images as text prompts to suppress this warning."
)
# 发出弃用警告
deprecate("len(prompt) != len(image)", "1.0.0", deprecation_message, standard_warn=False)
# 计算每个提示所需的附加图像数量
additional_image_per_prompt = batch_size // init_latents.shape[0]
# 扩展潜在向量以匹配文本提示的数量
init_latents = torch.cat([init_latents] * additional_image_per_prompt * num_images_per_prompt, dim=0)
# 如果批次大小大于初始化潜在向量的数量且不能整除
elif batch_size > init_latents.shape[0] and batch_size % init_latents.shape[0] != 0:
raise ValueError(
# 抛出值错误,提示无法将图像复制到文本提示
f"Cannot duplicate `image` of batch size {init_latents.shape[0]} to {batch_size} text prompts."
)
else:
# 如果批次大小小于等于初始化潜在向量数量,则复制潜在向量
init_latents = torch.cat([init_latents] * num_images_per_prompt, dim=0)
# 使用时间步长向潜在向量添加噪声
shape = init_latents.shape
# 生成与潜在向量相同形状的随机噪声
noise = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
# 获取清晰的潜在向量
clean_latents = init_latents
# 将噪声添加到初始化潜在向量中
init_latents = self.scheduler.add_noise(init_latents, noise, timestep)
# 结果潜在向量
latents = init_latents
# 返回处理后的潜在向量和清晰的潜在向量
return latents, clean_latents
# 禁用梯度计算,节省内存
@torch.no_grad()
# 定义可调用的类方法,用于生成图像
def __call__(
self,
# 输入提示,可以是字符串或字符串列表
prompt: Union[str, List[str]],
# 源提示,可以是字符串或字符串列表
source_prompt: Union[str, List[str]],
# 输入图像,可选参数,默认为 None
image: PipelineImageInput = None,
# 强度参数,默认为 0.8
strength: float = 0.8,
# 推理步骤数量,默认为 50
num_inference_steps: Optional[int] = 50,
# 指导比例,默认为 7.5
guidance_scale: Optional[float] = 7.5,
# 源指导比例,默认为 1
source_guidance_scale: Optional[float] = 1,
# 每个提示生成的图像数量,默认为 1
num_images_per_prompt: Optional[int] = 1,
# 噪声参数,默认为 0.1
eta: Optional[float] = 0.1,
# 随机数生成器,可以是单个或多个生成器
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 预计算的提示嵌入,可选参数
prompt_embeds: Optional[torch.Tensor] = None,
# 输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 是否返回字典格式,默认为 True
return_dict: bool = True,
# 回调函数,可选参数,接收进度信息
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调调用的步数,默认为 1
callback_steps: int = 1,
# 交叉注意力的额外参数,可选
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 跳过的剪辑层数,可选
clip_skip: Optional[int] = None,
.\diffusers\pipelines\deprecated\stable_diffusion_variants\pipeline_onnx_stable_diffusion_inpaint_legacy.py
# 导入inspect模块,用于获取当前的堆栈信息和函数参数
import inspect
# 导入类型提示相关的类型,包含可调用对象、列表、可选项和联合类型
from typing import Callable, List, Optional, Union
# 导入numpy库,用于数组操作和数值计算
import numpy as np
# 导入PIL库中的Image模块,用于图像处理
import PIL.Image
# 导入torch库,用于深度学习相关操作
import torch
# 从transformers库导入CLIP图像处理器和分词器
from transformers import CLIPImageProcessor, CLIPTokenizer
# 从配置工具导入FrozenDict,用于处理不可变字典
from ....configuration_utils import FrozenDict
# 从调度器导入不同类型的调度器
from ....schedulers import DDIMScheduler, LMSDiscreteScheduler, PNDMScheduler
# 从工具模块导入弃用装饰器和日志记录功能
from ....utils import deprecate, logging
# 从onnx工具导入ORT到NumPy类型映射和OnnxRuntime模型
from ...onnx_utils import ORT_TO_NP_TYPE, OnnxRuntimeModel
# 从管道工具导入DiffusionPipeline类
from ...pipeline_utils import DiffusionPipeline
# 从稳定扩散输出模块导入StableDiffusionPipelineOutput类
from ...stable_diffusion.pipeline_output import StableDiffusionPipelineOutput
# 创建一个日志记录器,用于记录当前模块的日志信息
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 定义图像预处理函数
def preprocess(image):
# 获取输入图像的宽度和高度
w, h = image.size
# 将宽度和高度调整为32的整数倍
w, h = (x - x % 32 for x in (w, h)) # resize to integer multiple of 32
# 按指定大小调整图像,使用LANCZOS重采样
image = image.resize((w, h), resample=PIL.Image.LANCZOS)
# 将图像转换为NumPy数组并归一化到[0, 1]区间
image = np.array(image).astype(np.float32) / 255.0
# 调整数组的维度,将通道维移到第二个位置
image = image[None].transpose(0, 3, 1, 2)
# 将数组值从[0, 1]范围缩放到[-1, 1]
return 2.0 * image - 1.0
# 定义掩膜预处理函数
def preprocess_mask(mask, scale_factor=8):
# 将掩膜图像转换为灰度模式
mask = mask.convert("L")
# 获取掩膜的宽度和高度
w, h = mask.size
# 将宽度和高度调整为32的整数倍
w, h = (x - x % 32 for x in (w, h)) # resize to integer multiple of 32
# 按比例缩放掩膜图像并使用最近邻重采样
mask = mask.resize((w // scale_factor, h // scale_factor), resample=PIL.Image.NEAREST)
# 将掩膜转换为NumPy数组并归一化到[0, 1]区间
mask = np.array(mask).astype(np.float32) / 255.0
# 将掩膜数组复制四次以匹配通道数
mask = np.tile(mask, (4, 1, 1))
# 调整数组的维度
mask = mask[None].transpose(0, 1, 2, 3) # what does this step do?
# 将掩膜值反转,将白色变为黑色,黑色保持不变
mask = 1 - mask # repaint white, keep black
# 返回处理后的掩膜
return mask
# 定义一个类,用于稳定扩散的图像修复管道,继承自DiffusionPipeline
class OnnxStableDiffusionInpaintPipelineLegacy(DiffusionPipeline):
r"""
使用稳定扩散进行文本引导图像修复的管道。此功能为*遗留功能*,用于ONNX管道,以
提供与StableDiffusionInpaintPipelineLegacy的兼容性,未来可能会被移除。
该模型继承自[`DiffusionPipeline`]。有关库为所有管道实现的通用方法(例如下载或保存、在特定设备上运行等),请查看超类文档。
"""
# 参数说明部分,描述每个参数的用途
Args:
vae ([`AutoencoderKL`]):
# 变分自编码器模型,用于对图像进行编码和解码,将图像转化为潜在表示
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
text_encoder ([`CLIPTextModel`]):
# 冻结的文本编码器,Stable Diffusion 使用 CLIP 的文本部分,具体为 clip-vit-large-patch14 变体
Frozen text-encoder. Stable Diffusion uses the text portion of
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
tokenizer (`CLIPTokenizer`):
# 用于将文本转化为标记的 CLIPTokenizer 类
Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
unet ([`UNet2DConditionModel`]):
# 条件 U-Net 架构,用于去噪编码后的图像潜在表示
Conditional U-Net architecture to denoise the encoded image latents.
scheduler ([`SchedulerMixin`]):
# 与 unet 结合使用的调度器,用于去噪编码后的图像潜在表示,可以是 DDIMScheduler、LMSDiscreteScheduler 或 PNDMScheduler
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
safety_checker ([`StableDiffusionSafetyChecker`]):
# 分类模块,用于评估生成图像是否可能被视为冒犯或有害,详情请参考模型卡
Classification module that estimates whether generated images could be considered offensive or harmful.
Please, refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for details.
feature_extractor ([`CLIPImageProcessor`]):
# 从生成图像中提取特征的模型,用于作为安全检查器的输入
Model that extracts features from generated images to be used as inputs for the `safety_checker`.
"""
# 定义可选组件,包含安全检查器和特征提取器
_optional_components = ["safety_checker", "feature_extractor"]
# 标记该模型为 ONNX 格式
_is_onnx = True
# 声明各个模型组件的类型
vae_encoder: OnnxRuntimeModel
vae_decoder: OnnxRuntimeModel
text_encoder: OnnxRuntimeModel
tokenizer: CLIPTokenizer
unet: OnnxRuntimeModel
scheduler: Union[DDIMScheduler, PNDMScheduler, LMSDiscreteScheduler]
safety_checker: OnnxRuntimeModel
feature_extractor: CLIPImageProcessor
# 构造函数,初始化模型组件
def __init__(
self,
vae_encoder: OnnxRuntimeModel,
vae_decoder: OnnxRuntimeModel,
text_encoder: OnnxRuntimeModel,
tokenizer: CLIPTokenizer,
unet: OnnxRuntimeModel,
scheduler: Union[DDIMScheduler, PNDMScheduler, LMSDiscreteScheduler],
safety_checker: OnnxRuntimeModel,
feature_extractor: CLIPImageProcessor,
requires_safety_checker: bool = True,
# 从 diffusers.pipelines.stable_diffusion.pipeline_onnx_stable_diffusion.OnnxStableDiffusionPipeline._encode_prompt 复制的函数
# 用于编码输入提示的函数
def _encode_prompt(
self,
prompt: Union[str, List[str]], # 输入提示,可以是字符串或字符串列表
num_images_per_prompt: Optional[int], # 每个提示生成的图像数量
do_classifier_free_guidance: bool, # 是否使用无分类器引导
negative_prompt: Optional[str], # 可选的负面提示
prompt_embeds: Optional[np.ndarray] = None, # 输入提示的嵌入表示,可选
negative_prompt_embeds: Optional[np.ndarray] = None, # 负面提示的嵌入表示,可选
# 检查输入有效性的函数
def check_inputs(
self,
prompt, # 输入提示
callback_steps, # 回调步骤
negative_prompt=None, # 可选的负面提示
prompt_embeds=None, # 输入提示的嵌入表示,可选
negative_prompt_embeds=None, # 负面提示的嵌入表示,可选
# 函数定义结束,开始处理参数验证
):
# 检查回调步骤是否为 None 或者为负值或非整数
if (callback_steps is None) or (
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
):
# 如果不符合条件,抛出 ValueError 异常
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
# 检查同时提供 prompt 和 prompt_embeds 的情况
if prompt is not None and prompt_embeds is not None:
# 如果两者都存在,抛出 ValueError 异常
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
# 检查是否同时未提供 prompt 和 prompt_embeds
elif prompt is None and prompt_embeds is None:
# 如果都未提供,抛出 ValueError 异常
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
# 检查 prompt 的类型
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
# 如果类型不正确,抛出 ValueError 异常
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
# 检查同时提供 negative_prompt 和 negative_prompt_embeds 的情况
if negative_prompt is not None and negative_prompt_embeds is not None:
# 如果两者都存在,抛出 ValueError 异常
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
# 检查 prompt_embeds 和 negative_prompt_embeds 是否都存在
if prompt_embeds is not None and negative_prompt_embeds is not None:
# 检查这两个数组的形状是否一致
if prompt_embeds.shape != negative_prompt_embeds.shape:
# 如果形状不一致,抛出 ValueError 异常
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# 定义 __call__ 方法,以便该类的实例可以被调用
def __call__(
# 定义 prompt 参数,支持字符串或字符串列表类型
prompt: Union[str, List[str]],
# 定义可选的图像输入
image: Union[np.ndarray, PIL.Image.Image] = None,
# 定义可选的掩膜图像输入
mask_image: Union[np.ndarray, PIL.Image.Image] = None,
# 定义强度参数,默认值为 0.8
strength: float = 0.8,
# 定义推理步骤数,默认值为 50
num_inference_steps: Optional[int] = 50,
# 定义引导比例,默认值为 7.5
guidance_scale: Optional[float] = 7.5,
# 定义可选的负向提示
negative_prompt: Optional[Union[str, List[str]]] = None,
# 定义每个提示生成的图像数量,默认值为 1
num_images_per_prompt: Optional[int] = 1,
# 定义 eta 参数,默认值为 0.0
eta: Optional[float] = 0.0,
# 定义可选的随机数生成器
generator: Optional[np.random.RandomState] = None,
# 定义可选的提示嵌入
prompt_embeds: Optional[np.ndarray] = None,
# 定义可选的负向提示嵌入
negative_prompt_embeds: Optional[np.ndarray] = None,
# 定义输出类型,默认值为 "pil"
output_type: Optional[str] = "pil",
# 定义是否返回字典,默认值为 True
return_dict: bool = True,
# 定义可选的回调函数
callback: Optional[Callable[[int, int, np.ndarray], None]] = None,
# 定义回调步骤,默认值为 1
callback_steps: int = 1,
.\diffusers\pipelines\deprecated\stable_diffusion_variants\pipeline_stable_diffusion_inpaint_legacy.py
# 版权所有 2024 HuggingFace 团队。保留所有权利。
#
# 根据 Apache 许可证,版本 2.0(“许可证”)许可;
# 除非遵循许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证的副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则根据许可证分发的软件是按“现状”提供的,
# 不提供任何形式的担保或条件,无论是明示或暗示的。
# 有关许可证下特定权限和限制的详细信息,请参见许可证。
# 导入 inspect 模块以检查对象
import inspect
# 从 typing 模块导入类型提示相关的类
from typing import Any, Callable, Dict, List, Optional, Union
# 导入 numpy 库用于数值计算
import numpy as np
# 导入 PIL.Image 以处理图像
import PIL.Image
# 导入 torch 库以进行深度学习计算
import torch
# 导入 version 模块以处理版本比较
from packaging import version
# 导入 HuggingFace 变换器的相关类
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer
# 导入 FrozenDict 类用于不可变字典
from ....configuration_utils import FrozenDict
# 导入 VaeImageProcessor 类处理变分自编码器图像
from ....image_processor import VaeImageProcessor
# 导入不同的加载器混合类
from ....loaders import FromSingleFileMixin, StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin
# 导入自编码器和条件 UNet 模型
from ....models import AutoencoderKL, UNet2DConditionModel
# 导入用于调整 Lora 规模的函数
from ....models.lora import adjust_lora_scale_text_encoder
# 导入 Karras Diffusion 调度器
from ....schedulers import KarrasDiffusionSchedulers
# 导入工具函数和常量
from ....utils import PIL_INTERPOLATION, USE_PEFT_BACKEND, deprecate, logging, scale_lora_layers, unscale_lora_layers
# 导入用于生成随机张量的函数
from ....utils.torch_utils import randn_tensor
# 导入 DiffusionPipeline 类
from ...pipeline_utils import DiffusionPipeline
# 导入 StableDiffusionPipelineOutput 类
from ...stable_diffusion import StableDiffusionPipelineOutput
# 导入稳定扩散的安全检查器
from ...stable_diffusion.safety_checker import StableDiffusionSafetyChecker
# 获取当前模块的日志记录器
logger = logging.get_logger(__name__)
# 定义图像预处理函数
def preprocess_image(image, batch_size):
# 获取图像的宽和高
w, h = image.size
# 调整宽和高为8的整数倍
w, h = (x - x % 8 for x in (w, h)) # resize to integer multiple of 8
# 根据新的尺寸重新调整图像大小,使用 Lanczos 重采样
image = image.resize((w, h), resample=PIL_INTERPOLATION["lanczos"])
# 将图像转换为浮点数数组并归一化到[0, 1]范围
image = np.array(image).astype(np.float32) / 255.0
# 创建一个大小为 batch_size 的图像堆叠
image = np.vstack([image[None].transpose(0, 3, 1, 2)] * batch_size)
# 将 NumPy 数组转换为 PyTorch 张量
image = torch.from_numpy(image)
# 将图像值缩放到 [-1, 1] 范围
return 2.0 * image - 1.0
# 定义掩膜预处理函数
def preprocess_mask(mask, batch_size, scale_factor=8):
# 如果掩膜不是张量,则进行转换
if not isinstance(mask, torch.Tensor):
# 将掩膜转换为灰度图像
mask = mask.convert("L")
# 获取掩膜的宽和高
w, h = mask.size
# 调整宽和高为8的整数倍
w, h = (x - x % 8 for x in (w, h)) # resize to integer multiple of 8
# 根据缩放因子调整掩膜大小,使用最近邻重采样
mask = mask.resize((w // scale_factor, h // scale_factor), resample=PIL_INTERPOLATION["nearest"])
# 将掩膜转换为浮点数数组并归一化到[0, 1]范围
mask = np.array(mask).astype(np.float32) / 255.0
# 将掩膜扩展到4个通道
mask = np.tile(mask, (4, 1, 1))
# 创建一个大小为 batch_size 的掩膜堆叠
mask = np.vstack([mask[None]] * batch_size)
# 将白色部分重绘为黑色,黑色部分保持不变
mask = 1 - mask # repaint white, keep black
# 将 NumPy 数组转换为 PyTorch 张量
mask = torch.from_numpy(mask)
# 返回处理后的掩膜
return mask
else:
# 定义有效的掩码通道大小
valid_mask_channel_sizes = [1, 3]
# 如果掩码通道是第四个张量维度,调整维度顺序为 PyTorch 标准 (B, C, H, W)
if mask.shape[3] in valid_mask_channel_sizes:
mask = mask.permute(0, 3, 1, 2)
# 如果掩码的第二个维度大小不在有效通道大小中,抛出异常
elif mask.shape[1] not in valid_mask_channel_sizes:
raise ValueError(
f"Mask channel dimension of size in {valid_mask_channel_sizes} should be second or fourth dimension,"
f" but received mask of shape {tuple(mask.shape)}"
)
# (可能)将掩码通道维度从 3 减少到 1,以便广播到潜在形状
mask = mask.mean(dim=1, keepdim=True)
# 获取掩码的高度和宽度
h, w = mask.shape[-2:]
# 将高度和宽度调整为 8 的整数倍
h, w = (x - x % 8 for x in (h, w)) # resize to integer multiple of 8
# 使用插值调整掩码大小到目标尺寸
mask = torch.nn.functional.interpolate(mask, (h // scale_factor, w // scale_factor))
# 返回调整后的掩码
return mask
# 定义一个名为 StableDiffusionInpaintPipelineLegacy 的类,继承多个混入类以扩展功能
class StableDiffusionInpaintPipelineLegacy(
# 继承 DiffusionPipeline 和其他混入类以获取其功能
DiffusionPipeline, TextualInversionLoaderMixin, StableDiffusionLoraLoaderMixin, FromSingleFileMixin
):
# 文档字符串,描述此管道的用途和特性
r"""
Pipeline for text-guided image inpainting using Stable Diffusion. *This is an experimental feature*.
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
In addition the pipeline inherits the following loading methods:
- *Textual-Inversion*: [`loaders.TextualInversionLoaderMixin.load_textual_inversion`]
- *LoRA*: [`loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`]
- *Ckpt*: [`loaders.FromSingleFileMixin.from_single_file`]
as well as the following saving methods:
- *LoRA*: [`loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`]
Args:
vae ([`AutoencoderKL`]):
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
text_encoder ([`CLIPTextModel`]):
Frozen text-encoder. Stable Diffusion uses the text portion of
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
tokenizer (`CLIPTokenizer`):
Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents.
scheduler ([`SchedulerMixin`]):
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
safety_checker ([`StableDiffusionSafetyChecker`]):
Classification module that estimates whether generated images could be considered offensive or harmful.
Please, refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for details.
feature_extractor ([`CLIPImageProcessor`]):
Model that extracts features from generated images to be used as inputs for the `safety_checker`.
"""
# 定义一个字符串,表示模型的 CPU 卸载顺序
model_cpu_offload_seq = "text_encoder->unet->vae"
# 定义一个可选组件列表,包含 feature_extractor
_optional_components = ["feature_extractor"]
# 定义一个不包含在 CPU 卸载中的组件列表,包含 safety_checker
_exclude_from_cpu_offload = ["safety_checker"]
# 初始化方法,定义构造函数接受的参数
def __init__(
# 接受一个 AutoencoderKL 实例作为 VAE 模型
self,
vae: AutoencoderKL,
# 接受一个 CLIPTextModel 实例作为文本编码器
text_encoder: CLIPTextModel,
# 接受一个 CLIPTokenizer 实例作为分词器
tokenizer: CLIPTokenizer,
# 接受一个 UNet2DConditionModel 实例作为去噪模型
unet: UNet2DConditionModel,
# 接受一个调度器,通常是 KarrasDiffusionSchedulers 的实例
scheduler: KarrasDiffusionSchedulers,
# 接受一个 StableDiffusionSafetyChecker 实例作为安全检查器
safety_checker: StableDiffusionSafetyChecker,
# 接受一个 CLIPImageProcessor 实例作为特征提取器
feature_extractor: CLIPImageProcessor,
# 定义一个布尔值,表示是否需要安全检查器,默认值为 True
requires_safety_checker: bool = True,
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt 复制的代码
def _encode_prompt(
self, # 定义一个方法,接受多个参数
prompt, # 输入的提示文本
device, # 设备类型(例如 CPU 或 GPU)
num_images_per_prompt, # 每个提示生成的图像数量
do_classifier_free_guidance, # 是否使用无分类器引导
negative_prompt=None, # 可选的负面提示文本
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面提示嵌入
lora_scale: Optional[float] = None, # 可选的 Lora 比例
**kwargs, # 其他任意参数
):
# 设置弃用消息,告知用户该方法将在未来版本中移除
deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple."
# 调用弃用函数,记录弃用信息
deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False)
# 调用新的 encode_prompt 方法,并获取提示嵌入的元组
prompt_embeds_tuple = self.encode_prompt(
prompt=prompt, # 传递提示文本
device=device, # 传递设备
num_images_per_prompt=num_images_per_prompt, # 传递每个提示的图像数量
do_classifier_free_guidance=do_classifier_free_guidance, # 传递无分类器引导标志
negative_prompt=negative_prompt, # 传递负面提示
prompt_embeds=prompt_embeds, # 传递提示嵌入
negative_prompt_embeds=negative_prompt_embeds, # 传递负面提示嵌入
lora_scale=lora_scale, # 传递 Lora 比例
**kwargs, # 传递其他任意参数
)
# 将提示嵌入元组中的两个部分连接起来,兼容旧版本
prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]])
# 返回连接后的提示嵌入
return prompt_embeds
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt 复制的代码
def encode_prompt(
self, # 定义一个方法,接受多个参数
prompt, # 输入的提示文本
device, # 设备类型(例如 CPU 或 GPU)
num_images_per_prompt, # 每个提示生成的图像数量
do_classifier_free_guidance, # 是否使用无分类器引导
negative_prompt=None, # 可选的负面提示文本
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面提示嵌入
lora_scale: Optional[float] = None, # 可选的 Lora 比例
clip_skip: Optional[int] = None, # 可选的剪辑跳过参数
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker 复制的代码
def run_safety_checker(self, image, device, dtype): # 定义安全检查方法,接受图像、设备和数据类型
if self.safety_checker is None: # 如果安全检查器未定义
has_nsfw_concept = None # 设置无敏感内容概念为 None
else: # 否则
if torch.is_tensor(image): # 如果输入图像是张量
# 将图像后处理为 PIL 格式
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
else: # 如果输入图像不是张量
# 将 NumPy 数组转换为 PIL 格式
feature_extractor_input = self.image_processor.numpy_to_pil(image)
# 提取特征并将其转移到指定设备
safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
# 运行安全检查器,检查图像和提取的特征
image, has_nsfw_concept = self.safety_checker(
images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
)
# 返回处理后的图像和是否存在敏感内容的概念
return image, has_nsfw_concept
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents 复制的代码
# 解码潜在表示
def decode_latents(self, latents):
# 设置弃用警告信息,提示用户该方法将在1.0.0版本中被移除
deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead"
# 调用弃用函数,记录弃用信息
deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False)
# 按照缩放因子调整潜在表示的值
latents = 1 / self.vae.config.scaling_factor * latents
# 解码潜在表示,返回图像的第一个元素
image = self.vae.decode(latents, return_dict=False)[0]
# 将图像值归一化到[0, 1]区间
image = (image / 2 + 0.5).clamp(0, 1)
# 将图像转换为float32格式,以确保兼容bfloat16
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
# 返回处理后的图像
return image
# 从diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs复制而来
def prepare_extra_step_kwargs(self, generator, eta):
# 准备调度器步骤的额外参数,因为并非所有调度器具有相同的参数签名
# eta (η) 仅在DDIMScheduler中使用,对于其他调度器将被忽略。
# eta对应于DDIM论文中的η: https://arxiv.org/abs/2010.02502
# 应该在[0, 1]之间
# 检查调度器的步骤函数是否接受eta参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化额外参数字典
extra_step_kwargs = {}
# 如果接受eta参数,则将其添加到字典中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器的步骤函数是否接受generator参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果接受generator参数,则将其添加到字典中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回包含额外参数的字典
return extra_step_kwargs
# 检查输入参数
def check_inputs(
self,
prompt, # 输入的提示信息
strength, # 强度参数
callback_steps, # 回调步骤
negative_prompt=None, # 可选的负面提示信息
prompt_embeds=None, # 可选的提示嵌入
negative_prompt_embeds=None, # 可选的负面提示嵌入
callback_on_step_end_tensor_inputs=None, # 可选的回调在步骤结束时的张量输入
):
# 检查 strength 是否在 [0, 1] 范围内,若不在则抛出 ValueError
if strength < 0 or strength > 1:
raise ValueError(f"The value of strength should in [0.0, 1.0] but is {strength}")
# 检查 callback_steps 是否为正整数,若不是则抛出 ValueError
if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0):
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
# 检查 callback_on_step_end_tensor_inputs 中的键是否在 _callback_tensor_inputs 中,若不在则抛出 ValueError
if callback_on_step_end_tensor_inputs is not None and not all(
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
):
raise ValueError(
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
)
# 检查 prompt 和 prompt_embeds 是否同时定义,若是则抛出 ValueError
if prompt is not None and prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
# 检查 prompt 和 prompt_embeds 是否都未定义,若是则抛出 ValueError
elif prompt is None and prompt_embeds is None:
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
# 检查 prompt 的类型是否为 str 或 list,若不是则抛出 ValueError
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
# 检查 negative_prompt 和 negative_prompt_embeds 是否同时定义,若是则抛出 ValueError
if negative_prompt is not None and negative_prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
# 检查 prompt_embeds 和 negative_prompt_embeds 是否都已定义,并检查它们的形状是否相同,若不相同则抛出 ValueError
if prompt_embeds is not None and negative_prompt_embeds is not None:
if prompt_embeds.shape != negative_prompt_embeds.shape:
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# 定义获取时间步的函数,参数包括推理步骤数量、strength 和设备
def get_timesteps(self, num_inference_steps, strength, device):
# 计算初始时间步,取 num_inference_steps 和 strength 的乘积的最小值
init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
# 计算起始时间步,确保不小于 0
t_start = max(num_inference_steps - init_timestep, 0)
# 获取调度器中的时间步,基于 t_start 和调度器的顺序
timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :]
# 返回时间步和剩余推理步骤
return timesteps, num_inference_steps - t_start
# 准备潜在向量的函数,输入图像、时间步、每个提示生成的图像数量、数据类型、设备和随机生成器
def prepare_latents(self, image, timestep, num_images_per_prompt, dtype, device, generator):
# 将输入图像转换为指定设备和数据类型
image = image.to(device=device, dtype=dtype)
# 使用变分自编码器 (VAE) 编码图像,获取潜在分布
init_latent_dist = self.vae.encode(image).latent_dist
# 从潜在分布中采样潜在向量
init_latents = init_latent_dist.sample(generator=generator)
# 将潜在向量缩放,以适应 VAE 配置的缩放因子
init_latents = self.vae.config.scaling_factor * init_latents
# 扩展初始潜在向量,以匹配批处理大小和每个提示生成的图像数量
init_latents = torch.cat([init_latents] * num_images_per_prompt, dim=0)
# 保存原始的初始潜在向量
init_latents_orig = init_latents
# 使用时间步向潜在向量添加噪声
noise = randn_tensor(init_latents.shape, generator=generator, device=device, dtype=dtype)
# 调度器添加噪声到初始潜在向量中
init_latents = self.scheduler.add_noise(init_latents, noise, timestep)
# 将潜在向量赋值给变量 latents
latents = init_latents
# 返回潜在向量、原始潜在向量和噪声
return latents, init_latents_orig, noise
# 装饰器表示在计算梯度时不会记录该函数的操作
@torch.no_grad()
# 定义调用函数,允许多种参数输入
def __call__(
# 提示文本,可以是单个字符串或字符串列表
prompt: Union[str, List[str]] = None,
# 输入图像,可以是张量或 PIL 图像
image: Union[torch.Tensor, PIL.Image.Image] = None,
# 掩码图像,可以是张量或 PIL 图像
mask_image: Union[torch.Tensor, PIL.Image.Image] = None,
# 强度参数,控制图像的强度
strength: float = 0.8,
# 推理步骤的数量
num_inference_steps: Optional[int] = 50,
# 指导比例,影响生成结果的引导强度
guidance_scale: Optional[float] = 7.5,
# 负提示,可以是单个字符串或字符串列表
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量
num_images_per_prompt: Optional[int] = 1,
# 是否添加预测的噪声
add_predicted_noise: Optional[bool] = False,
# eta 参数,用于控制生成过程
eta: Optional[float] = 0.0,
# 随机生成器,可以是单个或多个生成器
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 提示嵌入,允许预先计算的嵌入传入
prompt_embeds: Optional[torch.Tensor] = None,
# 负提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 是否返回字典格式的结果
return_dict: bool = True,
# 回调函数,用于在推理步骤中进行额外操作
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 每多少步调用一次回调
callback_steps: int = 1,
# 跨注意力的额外参数
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 跳过的剪辑层数
clip_skip: Optional[int] = None,