Spaces:
Runtime error
Runtime error
File size: 9,605 Bytes
de2377a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | # 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):,}")
|