Spaces:
Runtime error
Runtime error
| # motion_generation/lib/model/unet1d.py | |
| import torch | |
| import torch.nn as nn | |
| import math | |
| class PositionalEncoding(nn.Module): | |
| """ | |
| 用于编码时间步 t 的标准 Transformer 位置编码 | |
| """ | |
| def __init__(self, dim): | |
| super().__init__() | |
| self.dim = dim | |
| self.register_buffer('inv_freq', 1. / (10000 ** (torch.arange(0, dim, 2).float() / dim))) | |
| def forward(self, x): | |
| # x 形状: (B,) 时间步索引 | |
| sinusoid_inp = torch.einsum('i, j -> i j', x.float(), self.inv_freq) | |
| emb = torch.cat((sinusoid_inp.sin(), sinusoid_inp.cos()), dim=-1) | |
| return emb | |
| class ResBlock1D(nn.Module): | |
| """ | |
| 一维残差块,包含 time embedding 的融合 | |
| """ | |
| def __init__(self, in_channels, out_channels, time_dim, kernel_size=3): | |
| super().__init__() | |
| padding = kernel_size // 2 | |
| self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, padding=padding) | |
| self.bn1 = nn.BatchNorm1d(out_channels) | |
| self.act1 = nn.GELU() | |
| self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding=padding) | |
| self.bn2 = nn.BatchNorm1d(out_channels) | |
| self.act2 = nn.GELU() | |
| # 时间步嵌入层 | |
| self.time_proj = nn.Linear(time_dim, out_channels) | |
| # 确保输入/输出通道匹配 | |
| self.residual_conv = nn.Conv1d(in_channels, out_channels, 1) if in_channels != out_channels else nn.Identity() | |
| def forward(self, x, time_emb): | |
| # x: (B, C_in, L) L=72, C_in=通道数 | |
| h = self.conv1(x) | |
| h = self.bn1(h) | |
| h = self.act1(h) | |
| # 融合时间嵌入:沿特征维度广播并相加 | |
| time_emb_proj = self.time_proj(time_emb).unsqueeze(-1) # (B, C_out) -> (B, C_out, 1) | |
| h = h + time_emb_proj | |
| h = self.conv2(h) | |
| h = self.bn2(h) | |
| h = self.act2(h) | |
| return h + self.residual_conv(x) | |
| class SelfAttention1D(nn.Module): | |
| """ | |
| 一维自注意力模块,包含 time embedding 的融合 | |
| """ | |
| def __init__(self, channels, time_dim, num_heads=4): | |
| super().__init__() | |
| self.channels = channels | |
| self.num_heads = num_heads | |
| assert channels % num_heads == 0, "channels must be divisible by num_heads" | |
| # Group normalization for better stability | |
| self.norm = nn.GroupNorm(num_groups=8, num_channels=channels) | |
| # Multi-head attention components | |
| self.qkv = nn.Conv1d(channels, channels * 3, 1) | |
| self.proj = nn.Conv1d(channels, channels, 1) | |
| # Time embedding projection | |
| self.time_proj = nn.Linear(time_dim, channels) | |
| def forward(self, x, time_emb): | |
| # x: (B, C, L) | |
| B, C, L = x.shape | |
| # Normalize input | |
| h = self.norm(x) | |
| # Add time embedding | |
| time_emb_proj = self.time_proj(time_emb).unsqueeze(-1) # (B, C, 1) | |
| h = h + time_emb_proj | |
| # Compute Q, K, V | |
| qkv = self.qkv(h) # (B, 3*C, L) | |
| qkv = qkv.reshape(B, 3, self.num_heads, C // self.num_heads, L) | |
| qkv = qkv.permute(1, 0, 2, 4, 3) # (3, B, num_heads, L, head_dim) | |
| q, k, v = qkv[0], qkv[1], qkv[2] | |
| # Attention | |
| scale = (C // self.num_heads) ** -0.5 | |
| attn = torch.matmul(q, k.transpose(-2, -1)) * scale # (B, num_heads, L, L) | |
| attn = torch.softmax(attn, dim=-1) | |
| # Apply attention to values | |
| out = torch.matmul(attn, v) # (B, num_heads, L, head_dim) | |
| out = out.permute(0, 1, 3, 2).reshape(B, C, L) # (B, C, L) | |
| # Project and add residual | |
| out = self.proj(out) | |
| return out + x | |
| class UNet1D(nn.Module): | |
| def __init__(self, pose_dim=72, base_channels=128, channel_multipliers=[1, 2, 4], | |
| time_emb_dim=256, mid_structure='conv', mid_num_heads=4): | |
| """ | |
| UNet1D model for 1D sequence processing | |
| Args: | |
| pose_dim: Dimension of input pose (default: 72) | |
| base_channels: Base number of channels (default: 128) | |
| channel_multipliers: Channel multipliers for each level (default: [1, 2, 4]) | |
| time_emb_dim: Time embedding dimension (default: 256) | |
| mid_structure: Structure for middle layer, either 'conv' or 'attention' (default: 'conv') | |
| mid_num_heads: Number of attention heads for mid layer when using attention (default: 4) | |
| """ | |
| super().__init__() | |
| self.mid_structure = mid_structure | |
| assert mid_structure in ['conv', 'attention'], "mid_structure must be 'conv' or 'attention'" | |
| # 1. Time Embedding | |
| self.time_mlp = nn.Sequential( | |
| PositionalEncoding(base_channels), | |
| nn.Linear(base_channels, time_emb_dim), | |
| nn.GELU(), | |
| nn.Linear(time_emb_dim, time_emb_dim) | |
| ) | |
| # 调整输入通道:姿态向量 (B, 72) -> (B, 1, 72) | |
| # 我们将特征维度 (72) 视为长度 L,将 1 视为通道 C | |
| in_channels = 1 | |
| # 2. 编码器 (Downsampling) | |
| channels = [in_channels] + [base_channels * m for m in channel_multipliers] | |
| self.downs = nn.ModuleList() | |
| for i in range(len(channel_multipliers)): | |
| in_c = channels[i] | |
| out_c = channels[i+1] | |
| self.downs.append(nn.ModuleList([ | |
| ResBlock1D(in_c if i == 0 else in_c, out_c, time_emb_dim), # 输入是 1, L 或 C_in, L | |
| nn.MaxPool1d(2) if i < len(channel_multipliers) - 1 else nn.Identity() | |
| ])) | |
| # 3. 中间层 - 根据 mid_structure 选择使用卷积或自注意力 | |
| mid_c = channels[-1] | |
| if mid_structure == 'conv': | |
| self.mid = ResBlock1D(mid_c, mid_c, time_emb_dim) | |
| elif mid_structure == 'attention': | |
| # 确保通道数能被注意力头数整除 | |
| assert mid_c % mid_num_heads == 0, f"mid_c ({mid_c}) must be divisible by mid_num_heads ({mid_num_heads})" | |
| self.mid = SelfAttention1D(mid_c, time_emb_dim, num_heads=mid_num_heads) | |
| # 4. 解码器 (Upsampling) | |
| self.ups = nn.ModuleList() | |
| reversed_channels = list(reversed(channels)) | |
| for i in range(len(channel_multipliers)): | |
| in_c = reversed_channels[i] # 来自上一层的通道数 | |
| # 最后一层输出 base_channels,而不是 in_channels (1) | |
| out_c = reversed_channels[i+1] if i < len(channel_multipliers) - 1 else base_channels | |
| skip_c = in_c # 跳跃连接通道数(来自对应编码器层) | |
| self.ups.append(nn.ModuleList([ | |
| # ResBlock 接收拼接后的通道: in_c(来自上层) + skip_c(来自编码器) | |
| # 输出为 out_c 通道 | |
| ResBlock1D(in_c + skip_c, out_c, time_emb_dim), | |
| # 上采样到下一层的空间尺寸 | |
| nn.ConvTranspose1d(out_c, out_c, kernel_size=2, stride=2) if i < len(channel_multipliers) - 1 else nn.Identity(), | |
| ])) | |
| # 5. 输出层 (回到 1 个通道) | |
| self.out_conv = nn.Conv1d(base_channels, in_channels, kernel_size=1) | |
| def forward(self, x, t): | |
| # import pdb; pdb.set_trace() | |
| # x: (B, 72) 归一化姿态,t: (B,) 时间步索引 | |
| x = x.unsqueeze(1) # (B, 1, 72) | |
| # 1. Time Embedding | |
| time_emb = self.time_mlp(t) | |
| # 2. 编码器 | |
| skips = [] | |
| for resblock, downsample in self.downs: | |
| x = resblock(x, time_emb) | |
| skips.append(x) | |
| x = downsample(x) | |
| # 3. 中间层 | |
| x = self.mid(x, time_emb) | |
| # 4. 解码器 | |
| for i, (resblock, upsample) in enumerate(self.ups): | |
| skip = skips.pop() | |
| # 跳跃连接 | |
| # 检查维度是否匹配,如果 MaxPool 导致了奇数/偶数长度不匹配,需要裁剪 | |
| if x.shape[-1] != skip.shape[-1]: | |
| x = nn.functional.pad(x, (0, skip.shape[-1] - x.shape[-1])) | |
| # 拼接跳跃连接 | |
| x = torch.cat((x, skip), dim=1) # 沿通道维度拼接 | |
| # 处理拼接后的特征 | |
| x = resblock(x, time_emb) | |
| # 上采样到下一层的空间尺寸(最后一层不上采样) | |
| if i < len(self.ups) - 1: | |
| x = upsample(x) # (B, C, L) -> (B, C, L*2) | |
| # 5. 输出层 | |
| x = self.out_conv(x) # (B, 1, 72) | |
| return x.squeeze(1) # (B, 72) | |
| if __name__ == "__main__": | |
| # 测试 UNet1D 模型 | |
| print("Testing UNet1D with conv mid structure...") | |
| model_conv = UNet1D(mid_structure='conv') | |
| x = torch.randn(4, 72) # 模拟输入 (B, 72) | |
| t = torch.randint(0, 1000, (4,)) # 模拟时间步索引 (B,) | |
| output_conv = model_conv(x, t) | |
| print(f"Input shape: {x.shape}") | |
| print(f"Output shape (conv): {output_conv.shape}") | |
| print(f"Expected output shape: (4, 72)") | |
| print(f"Test passed (conv): {output_conv.shape == torch.Size([4, 72])}") | |
| print("\nTesting UNet1D with attention mid structure...") | |
| model_attn = UNet1D(mid_structure='attention', base_channels=128, mid_num_heads=8) | |
| output_attn = model_attn(x, t) | |
| print(f"Output shape (attention): {output_attn.shape}") | |
| print(f"Test passed (attention): {output_attn.shape == torch.Size([4, 72])}") | |
| # 统计参数量 | |
| conv_params = sum(p.numel() for p in model_conv.parameters()) | |
| attn_params = sum(p.numel() for p in model_attn.parameters()) | |
| print(f"\nParameters (conv): {conv_params:,}") | |
| print(f"Parameters (attention): {attn_params:,}") | |
| print(f"Difference: {abs(attn_params - conv_params):,}") | |