yolozyk's picture
update sample tools
de2377a
Raw
History Blame Contribute Delete
9.61 kB
# 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):,}")