Upload 11 files
Browse files- 000001.npy +3 -0
- 000012.npy +3 -0
- Mean.npy +3 -0
- README.md +102 -3
- Std.npy +3 -0
- TestRVQ.py +144 -0
- TrainRVQ.py +68 -0
- config.json +23 -0
- motion_rvq_weights.safetensors +3 -0
- rvq_humanml_dataset.py +42 -0
- rvq_model.py +140 -0
000001.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0c737af5a59d4fc9667092ce8bd31a4830dc20ec00444123c1ecabaed5715abd
|
| 3 |
+
size 36948
|
000012.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1ddd5759d18e10a7fbafd4f5ce49a18d7fffd65706d3c020145b4b1b0c357228
|
| 3 |
+
size 84288
|
Mean.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:26e136555dab04c94a129d446c26e6b9939cbf045fbf77bcf5462c1fb5a2001c
|
| 3 |
+
size 1180
|
README.md
CHANGED
|
@@ -1,3 +1,102 @@
|
|
| 1 |
-
---
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
tags:
|
| 4 |
+
- motion
|
| 5 |
+
- rvq
|
| 6 |
+
- vector-quantization
|
| 7 |
+
- human-motion
|
| 8 |
+
- safetensors
|
| 9 |
+
- humanML
|
| 10 |
+
- motion-reconstructor
|
| 11 |
+
license: cc-by-nc-4.0
|
| 12 |
+
datasets:
|
| 13 |
+
- Wojtekb30/HumanML3D-500ms-FPP-descriptions-CoTs-1
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Motion RVQ (Move Reconstruction)
|
| 17 |
+
|
| 18 |
+
This model uses Residual Vector Quantization (RVQ) to reconstruct motion represented as 263-dimensional frame vectors.
|
| 19 |
+
It is a custom PyTorch model (not a Transformers `AutoModel`) and is loaded from `safetensors` with `rvq_model.py`.
|
| 20 |
+
|
| 21 |
+
## Model Summary
|
| 22 |
+
|
| 23 |
+
- Architecture: encoder -> 4-level RVQ -> decoder
|
| 24 |
+
- Input shape: `(T, 263)` per sequence (frame-major)
|
| 25 |
+
- Training window: 100 frames (with crop/pad in dataset loader)
|
| 26 |
+
- Output: reconstructed motion sequence in the same 263-dim representation
|
| 27 |
+
|
| 28 |
+
## Repository Files
|
| 29 |
+
|
| 30 |
+
- `motion_rvq_weights.safetensors` - main published checkpoint
|
| 31 |
+
- `config.json` - model configuration metadata
|
| 32 |
+
- `rvq_model.py` - model architecture (`MotionRVQ_VAE`)
|
| 33 |
+
- `TestRVQ.py` - inference + 3-panel visualization
|
| 34 |
+
- `TrainRVQ.py` - training script
|
| 35 |
+
- `rvq_humanml_dataset.py` - training dataset loader
|
| 36 |
+
- `Mean.npy`, `Std.npy` - normalization statistics
|
| 37 |
+
- `000001.npy`, `000012.npy` - sample motion files
|
| 38 |
+
|
| 39 |
+
`motion_rvq_weights.pth` can be treated as a legacy artifact; code uses `motion_rvq_weights.safetensors`.
|
| 40 |
+
|
| 41 |
+
## Install
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
pip install torch safetensors numpy matplotlib
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
## Inference
|
| 48 |
+
|
| 49 |
+
Run the provided visualization script:
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
python TestRVQ.py
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
By default, `TestRVQ.py` uses `000001.npy`. You can change `FILE_TO_TEST` in `TestRVQ.py` to another sequence.
|
| 56 |
+
|
| 57 |
+
Minimal loading example:
|
| 58 |
+
|
| 59 |
+
```python
|
| 60 |
+
from pathlib import Path
|
| 61 |
+
import torch
|
| 62 |
+
from safetensors.torch import load_file
|
| 63 |
+
from rvq_model import MotionRVQ_VAE
|
| 64 |
+
|
| 65 |
+
base = Path(".")
|
| 66 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 67 |
+
|
| 68 |
+
model = MotionRVQ_VAE().to(device)
|
| 69 |
+
state_dict = load_file(str(base / "motion_rvq_weights.safetensors"), device=str(device))
|
| 70 |
+
model.load_state_dict(state_dict)
|
| 71 |
+
model.eval()
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## Training From Scratch
|
| 75 |
+
|
| 76 |
+
Expected layout:
|
| 77 |
+
|
| 78 |
+
```text
|
| 79 |
+
rvq/
|
| 80 |
+
TrainRVQ.py
|
| 81 |
+
rvq_model.py
|
| 82 |
+
rvq_humanml_dataset.py
|
| 83 |
+
Mean.npy
|
| 84 |
+
Std.npy
|
| 85 |
+
new_joint_vecs/
|
| 86 |
+
*.npy
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
Run training:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
python TrainRVQ.py
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Output checkpoint:
|
| 96 |
+
|
| 97 |
+
- `motion_rvq_weights.safetensors`
|
| 98 |
+
|
| 99 |
+
## Limitations
|
| 100 |
+
|
| 101 |
+
- This model reconstructs motion vectors; it is not a text-to-motion generator.
|
| 102 |
+
- Input format must match the same 263-dim representation and normalization scheme used during training.
|
Std.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6565a65ed9b31e23c328829a309e1c482be8b85fd23b43d65451a9b19a917f40
|
| 3 |
+
size 1180
|
TestRVQ.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
import matplotlib.animation as animation
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from safetensors.torch import load_file
|
| 8 |
+
|
| 9 |
+
# Import model architecture
|
| 10 |
+
from rvq_model import MotionRVQ_VAE
|
| 11 |
+
|
| 12 |
+
# ==========================================
|
| 13 |
+
# 1. Configuration
|
| 14 |
+
# ==========================================
|
| 15 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 16 |
+
FILE_TO_TEST = BASE_DIR / "000001.npy"
|
| 17 |
+
WEIGHTS_PATH = BASE_DIR / "motion_rvq_weights.safetensors"
|
| 18 |
+
|
| 19 |
+
# ==========================================
|
| 20 |
+
# 2. Model Initialization
|
| 21 |
+
# ==========================================
|
| 22 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 23 |
+
model = MotionRVQ_VAE().to(device)
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
state_dict = load_file(str(WEIGHTS_PATH), device=str(device))
|
| 27 |
+
model.load_state_dict(state_dict)
|
| 28 |
+
print(f"Successfully loaded weights from {WEIGHTS_PATH}!")
|
| 29 |
+
except FileNotFoundError:
|
| 30 |
+
print(f"ERROR: Could not find file {WEIGHTS_PATH}.")
|
| 31 |
+
exit()
|
| 32 |
+
|
| 33 |
+
model.eval()
|
| 34 |
+
|
| 35 |
+
# ==========================================
|
| 36 |
+
# 3. Forward Pass (With Padding and Normalization)
|
| 37 |
+
# ==========================================
|
| 38 |
+
original_data = np.load(FILE_TO_TEST)
|
| 39 |
+
T_orig = original_data.shape[0]
|
| 40 |
+
|
| 41 |
+
# Load normalization vectors
|
| 42 |
+
mean = np.load(BASE_DIR / "Mean.npy")
|
| 43 |
+
std = np.load(BASE_DIR / "Std.npy")
|
| 44 |
+
|
| 45 |
+
# Padding for stride=4 compression
|
| 46 |
+
pad_len = (4 - (T_orig % 4)) % 4
|
| 47 |
+
if pad_len > 0:
|
| 48 |
+
last_frame = original_data[-1:]
|
| 49 |
+
padded_data = np.concatenate([original_data, np.repeat(last_frame, pad_len, axis=0)], axis=0)
|
| 50 |
+
else:
|
| 51 |
+
padded_data = original_data
|
| 52 |
+
|
| 53 |
+
padded_data = (padded_data - mean) / std
|
| 54 |
+
input_tensor = torch.from_numpy(padded_data).float().unsqueeze(0).permute(0, 2, 1).to(device)
|
| 55 |
+
|
| 56 |
+
with torch.no_grad():
|
| 57 |
+
# 1. Get tokens from all levels
|
| 58 |
+
z = model.encoder(input_tensor)
|
| 59 |
+
_, tokens, _ = model.rvq(z)
|
| 60 |
+
|
| 61 |
+
# 2. Function for "partial" decoding
|
| 62 |
+
def decode_from_levels(num_levels):
|
| 63 |
+
z_q_partial = 0
|
| 64 |
+
for i in range(num_levels):
|
| 65 |
+
# Get indices only for level "i"
|
| 66 |
+
indices = tokens[:, i, :]
|
| 67 |
+
quantizer = model.rvq.quantizers[i]
|
| 68 |
+
|
| 69 |
+
# Convert token id (e.g. 145) into its 1024-codebook vector
|
| 70 |
+
level_z_q = F.embedding(indices, quantizer.embedding)
|
| 71 |
+
level_z_q = level_z_q.permute(0, 2, 1) # Shape expected by decoder
|
| 72 |
+
|
| 73 |
+
# Add residual vector to the running latent
|
| 74 |
+
z_q_partial = z_q_partial + level_z_q
|
| 75 |
+
|
| 76 |
+
return model.decoder(z_q_partial)
|
| 77 |
+
|
| 78 |
+
# Generate motion using only level 1 and all 4 levels
|
| 79 |
+
recon_tensor_1_lvl = decode_from_levels(1)
|
| 80 |
+
recon_tensor_4_lvl = decode_from_levels(4)
|
| 81 |
+
|
| 82 |
+
# Back to NumPy
|
| 83 |
+
recon_1_lvl = recon_tensor_1_lvl.squeeze(0).permute(1, 0).cpu().numpy()[:T_orig, :]
|
| 84 |
+
recon_4_lvl = recon_tensor_4_lvl.squeeze(0).permute(1, 0).cpu().numpy()[:T_orig, :]
|
| 85 |
+
|
| 86 |
+
# De-normalization
|
| 87 |
+
recon_1_lvl = (recon_1_lvl * std) + mean
|
| 88 |
+
recon_4_lvl = (recon_4_lvl * std) + mean
|
| 89 |
+
|
| 90 |
+
# ==========================================
|
| 91 |
+
# 4. Skeleton extraction utility
|
| 92 |
+
# ==========================================
|
| 93 |
+
def get_3d_joints(data_263):
|
| 94 |
+
num_frames = data_263.shape[0]
|
| 95 |
+
joints = np.zeros((num_frames, 22, 3))
|
| 96 |
+
for i in range(num_frames):
|
| 97 |
+
root_y = data_263[i, 3]
|
| 98 |
+
joints[i, 0] = [0, root_y, 0]
|
| 99 |
+
local_positions = data_263[i, 4:67].reshape(21, 3)
|
| 100 |
+
joints[i, 1:] = local_positions + [0, root_y, 0]
|
| 101 |
+
return joints
|
| 102 |
+
|
| 103 |
+
joints_orig = get_3d_joints(original_data)
|
| 104 |
+
joints_1_lvl = get_3d_joints(recon_1_lvl)
|
| 105 |
+
joints_4_lvl = get_3d_joints(recon_4_lvl)
|
| 106 |
+
|
| 107 |
+
# ==========================================
|
| 108 |
+
# 5. Three-panel visualization
|
| 109 |
+
# ==========================================
|
| 110 |
+
kinematic_tree = [
|
| 111 |
+
[0, 1, 4, 7, 10], [0, 2, 5, 8, 11], [0, 3, 6, 9, 12, 15],
|
| 112 |
+
[9, 13, 16, 18, 20], [9, 14, 17, 19, 21]
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
fig = plt.figure(figsize=(15, 5))
|
| 116 |
+
ax1 = fig.add_subplot(131, projection='3d')
|
| 117 |
+
ax2 = fig.add_subplot(132, projection='3d')
|
| 118 |
+
ax3 = fig.add_subplot(133, projection='3d')
|
| 119 |
+
|
| 120 |
+
def update(frame):
|
| 121 |
+
for ax in [ax1, ax2, ax3]:
|
| 122 |
+
ax.clear()
|
| 123 |
+
ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(0, 2)
|
| 124 |
+
ax.view_init(elev=10., azim=-90)
|
| 125 |
+
ax.axis('off')
|
| 126 |
+
|
| 127 |
+
ax1.set_title(f"ORIGINAL\n(Frame {frame})")
|
| 128 |
+
ax2.set_title("RECONSTRUCTION: 1 LEVEL\n(Coarse tokens only)")
|
| 129 |
+
ax3.set_title("RECONSTRUCTION: 4 LEVELS\n(Full RVQ detail)")
|
| 130 |
+
|
| 131 |
+
for chain in kinematic_tree:
|
| 132 |
+
# Original (Blue)
|
| 133 |
+
ax1.plot(joints_orig[frame, chain, 0], joints_orig[frame, chain, 2], joints_orig[frame, chain, 1],
|
| 134 |
+
linewidth=3, marker='o', markersize=4, color='blue')
|
| 135 |
+
# 1 Level (Orange)
|
| 136 |
+
ax2.plot(joints_1_lvl[frame, chain, 0], joints_1_lvl[frame, chain, 2], joints_1_lvl[frame, chain, 1],
|
| 137 |
+
linewidth=3, marker='o', markersize=4, color='orange')
|
| 138 |
+
# 4 Levels (Red)
|
| 139 |
+
ax3.plot(joints_4_lvl[frame, chain, 0], joints_4_lvl[frame, chain, 2], joints_4_lvl[frame, chain, 1],
|
| 140 |
+
linewidth=3, marker='o', markersize=4, color='red')
|
| 141 |
+
|
| 142 |
+
ani = animation.FuncAnimation(fig, update, frames=T_orig, interval=50, repeat=True)
|
| 143 |
+
plt.tight_layout()
|
| 144 |
+
plt.show()
|
TrainRVQ.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import multiprocessing
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
import torch.optim as optim
|
| 7 |
+
from safetensors.torch import save_file
|
| 8 |
+
|
| 9 |
+
from rvq_model import MotionRVQ_VAE
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
if __name__ == "__main__":
|
| 13 |
+
multiprocessing.freeze_support()
|
| 14 |
+
|
| 15 |
+
from rvq_humanml_dataset import DataLoader, HumanML3DDataset
|
| 16 |
+
|
| 17 |
+
base_dir = Path(__file__).resolve().parent
|
| 18 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 19 |
+
print(f"Training on: {device}")
|
| 20 |
+
|
| 21 |
+
model = MotionRVQ_VAE().to(device)
|
| 22 |
+
|
| 23 |
+
dataset = HumanML3DDataset(data_dir=str(base_dir / "new_joint_vecs"), window_size=100)
|
| 24 |
+
dataloader = DataLoader(
|
| 25 |
+
dataset,
|
| 26 |
+
batch_size=32,
|
| 27 |
+
shuffle=True,
|
| 28 |
+
num_workers=4,
|
| 29 |
+
drop_last=True,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
optimizer = optim.AdamW(model.parameters(), lr=2e-4, weight_decay=1e-4)
|
| 33 |
+
num_epochs = 5
|
| 34 |
+
|
| 35 |
+
model.train()
|
| 36 |
+
for epoch in range(num_epochs):
|
| 37 |
+
epoch_loss = 0.0
|
| 38 |
+
|
| 39 |
+
for batch_idx, batch in enumerate(dataloader):
|
| 40 |
+
batch = batch.to(device)
|
| 41 |
+
optimizer.zero_grad()
|
| 42 |
+
|
| 43 |
+
reconstructed, _, commit_loss = model(batch)
|
| 44 |
+
pos_loss = F.mse_loss(reconstructed, batch)
|
| 45 |
+
|
| 46 |
+
vel_orig = batch[:, :, 1:] - batch[:, :, :-1]
|
| 47 |
+
vel_recon = reconstructed[:, :, 1:] - reconstructed[:, :, :-1]
|
| 48 |
+
vel_loss = F.mse_loss(vel_recon, vel_orig)
|
| 49 |
+
|
| 50 |
+
reconstruction_loss = pos_loss + (1.5 * vel_loss)
|
| 51 |
+
loss = reconstruction_loss + commit_loss
|
| 52 |
+
loss.backward()
|
| 53 |
+
optimizer.step()
|
| 54 |
+
|
| 55 |
+
epoch_loss += loss.item()
|
| 56 |
+
|
| 57 |
+
if batch_idx % 50 == 0:
|
| 58 |
+
print(
|
| 59 |
+
f"Epoch [{epoch + 1}/{num_epochs}] Batch [{batch_idx}/{len(dataloader)}] "
|
| 60 |
+
f"MSE: {reconstruction_loss.item():.4f} | Commit: {commit_loss.item():.4f}"
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
print(f"--- End of epoch {epoch + 1} | Avg loss: {epoch_loss / len(dataloader):.4f} ---")
|
| 64 |
+
|
| 65 |
+
weights_path = base_dir / "motion_rvq_weights.safetensors"
|
| 66 |
+
state_dict = {k: v.detach().cpu() for k, v in model.state_dict().items()}
|
| 67 |
+
save_file(state_dict, str(weights_path))
|
| 68 |
+
print(f"Training complete and model saved to: {weights_path}")
|
config.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"MotionRVQ_VAE"
|
| 4 |
+
],
|
| 5 |
+
"model_type": "motion_rvq_vae",
|
| 6 |
+
"library_name": "pytorch",
|
| 7 |
+
"torch_dtype": "float32",
|
| 8 |
+
"in_channels": 263,
|
| 9 |
+
"out_channels": 263,
|
| 10 |
+
"latent_dim": 512,
|
| 11 |
+
"encoder_hidden_channels": 512,
|
| 12 |
+
"decoder_hidden_channels": 512,
|
| 13 |
+
"rvq_num_levels": 4,
|
| 14 |
+
"rvq_num_embeddings": 1024,
|
| 15 |
+
"rvq_embedding_dim": 512,
|
| 16 |
+
"rvq_commitment_cost": 0.25,
|
| 17 |
+
"rvq_decay": 0.99,
|
| 18 |
+
"rvq_epsilon": 1e-5,
|
| 19 |
+
"downsample_stride": 4,
|
| 20 |
+
"window_size": 100,
|
| 21 |
+
"input_representation": "263-dim frame vectors",
|
| 22 |
+
"task": "motion_reconstruction"
|
| 23 |
+
}
|
motion_rvq_weights.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7bbee9c0dfbe42875ace86db0f584f8634e83711f231bbaca551eb65a3e504f8
|
| 3 |
+
size 74587252
|
rvq_humanml_dataset.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import glob
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from torch.utils.data import Dataset, DataLoader
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class HumanML3DDataset(Dataset):
|
| 10 |
+
def __init__(self, data_dir='./new_joint_vecs', window_size=100):
|
| 11 |
+
self.data_dir = str(data_dir)
|
| 12 |
+
self.window_size = window_size
|
| 13 |
+
self.file_paths = glob.glob(os.path.join(self.data_dir, '*.npy'))
|
| 14 |
+
|
| 15 |
+
data_dir_path = Path(self.data_dir).resolve()
|
| 16 |
+
base_dir = data_dir_path.parent
|
| 17 |
+
self.mean = torch.from_numpy(np.load(base_dir / 'Mean.npy')).float()
|
| 18 |
+
self.std = torch.from_numpy(np.load(base_dir / 'Std.npy')).float()
|
| 19 |
+
|
| 20 |
+
print(f"Dataset initialized: found {len(self.file_paths)} files.")
|
| 21 |
+
|
| 22 |
+
def __len__(self):
|
| 23 |
+
return len(self.file_paths)
|
| 24 |
+
|
| 25 |
+
def __getitem__(self, idx):
|
| 26 |
+
data = np.load(self.file_paths[idx])
|
| 27 |
+
tensor_data = torch.from_numpy(data).float()
|
| 28 |
+
|
| 29 |
+
tensor_data = (tensor_data - self.mean) / self.std
|
| 30 |
+
|
| 31 |
+
t_len, _ = tensor_data.shape
|
| 32 |
+
if t_len > self.window_size:
|
| 33 |
+
start = torch.randint(0, t_len - self.window_size + 1, (1,)).item()
|
| 34 |
+
tensor_data = tensor_data[start : start + self.window_size, :]
|
| 35 |
+
elif t_len < self.window_size:
|
| 36 |
+
padding_size = self.window_size - t_len
|
| 37 |
+
last_pose = tensor_data[-1].unsqueeze(0)
|
| 38 |
+
padding = last_pose.repeat(padding_size, 1)
|
| 39 |
+
tensor_data = torch.cat([tensor_data, padding], dim=0)
|
| 40 |
+
|
| 41 |
+
tensor_data = tensor_data.permute(1, 0)
|
| 42 |
+
return tensor_data
|
rvq_model.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class EMAVectorQuantizer(nn.Module):
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
num_embeddings=512,
|
| 10 |
+
embedding_dim=256,
|
| 11 |
+
commitment_cost=0.25,
|
| 12 |
+
decay=0.99,
|
| 13 |
+
epsilon=1e-5,
|
| 14 |
+
):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self.num_embeddings = num_embeddings
|
| 17 |
+
self.embedding_dim = embedding_dim
|
| 18 |
+
self.commitment_cost = commitment_cost
|
| 19 |
+
self.decay = decay
|
| 20 |
+
self.epsilon = epsilon
|
| 21 |
+
|
| 22 |
+
embed = torch.randn(num_embeddings, embedding_dim)
|
| 23 |
+
self.register_buffer("embedding", embed)
|
| 24 |
+
self.register_buffer("cluster_size", torch.zeros(num_embeddings))
|
| 25 |
+
self.register_buffer("ema_w", embed.clone())
|
| 26 |
+
|
| 27 |
+
def forward(self, z):
|
| 28 |
+
z = z.permute(0, 2, 1).contiguous()
|
| 29 |
+
z_flattened = z.view(-1, self.embedding_dim)
|
| 30 |
+
|
| 31 |
+
distances = (
|
| 32 |
+
torch.sum(z_flattened**2, dim=1, keepdim=True)
|
| 33 |
+
+ torch.sum(self.embedding**2, dim=1)
|
| 34 |
+
- 2 * torch.matmul(z_flattened, self.embedding.t())
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
min_encoding_indices = torch.argmin(distances, dim=1)
|
| 38 |
+
z_q = F.embedding(min_encoding_indices, self.embedding)
|
| 39 |
+
|
| 40 |
+
if self.training:
|
| 41 |
+
encodings = F.one_hot(min_encoding_indices, self.num_embeddings).float()
|
| 42 |
+
self.cluster_size.data.mul_(self.decay).add_(encodings.sum(0), alpha=1 - self.decay)
|
| 43 |
+
|
| 44 |
+
n = self.cluster_size.sum()
|
| 45 |
+
cluster_size = (self.cluster_size + self.epsilon) / (
|
| 46 |
+
n + self.num_embeddings * self.epsilon
|
| 47 |
+
) * n
|
| 48 |
+
|
| 49 |
+
dw = torch.matmul(encodings.t(), z_flattened)
|
| 50 |
+
self.ema_w.data.mul_(self.decay).add_(dw, alpha=1 - self.decay)
|
| 51 |
+
self.embedding.data.copy_(self.ema_w / cluster_size.unsqueeze(1))
|
| 52 |
+
|
| 53 |
+
loss = self.commitment_cost * F.mse_loss(z_q.detach(), z_flattened)
|
| 54 |
+
z_q = z_flattened + (z_q - z_flattened).detach()
|
| 55 |
+
z_q = z_q.view(z.shape).permute(0, 2, 1).contiguous()
|
| 56 |
+
|
| 57 |
+
return z_q, min_encoding_indices.view(z.shape[0], z.shape[1]), loss
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class RVQ(nn.Module):
|
| 61 |
+
def __init__(self, num_levels=3, num_embeddings=512, embedding_dim=256):
|
| 62 |
+
super().__init__()
|
| 63 |
+
self.num_levels = num_levels
|
| 64 |
+
self.quantizers = nn.ModuleList(
|
| 65 |
+
[EMAVectorQuantizer(num_embeddings, embedding_dim) for _ in range(num_levels)]
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
def forward(self, z):
|
| 69 |
+
quantized_out = 0
|
| 70 |
+
residual = z
|
| 71 |
+
all_indices = []
|
| 72 |
+
total_loss = 0
|
| 73 |
+
|
| 74 |
+
for quantizer in self.quantizers:
|
| 75 |
+
z_q, indices, loss = quantizer(residual)
|
| 76 |
+
quantized_out = quantized_out + z_q
|
| 77 |
+
residual = residual - z_q
|
| 78 |
+
all_indices.append(indices)
|
| 79 |
+
total_loss += loss
|
| 80 |
+
|
| 81 |
+
return quantized_out, torch.stack(all_indices, dim=1), total_loss
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class ResBlock1D(nn.Module):
|
| 85 |
+
def __init__(self, channels):
|
| 86 |
+
super().__init__()
|
| 87 |
+
self.net = nn.Sequential(
|
| 88 |
+
nn.Conv1d(channels, channels, kernel_size=3, padding=1),
|
| 89 |
+
nn.LeakyReLU(0.2, inplace=True),
|
| 90 |
+
nn.Conv1d(channels, channels, kernel_size=3, padding=1),
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def forward(self, x):
|
| 94 |
+
return x + self.net(x)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class MotionEncoder(nn.Module):
|
| 98 |
+
def __init__(self, in_channels=263, latent_dim=512):
|
| 99 |
+
super().__init__()
|
| 100 |
+
self.net = nn.Sequential(
|
| 101 |
+
nn.Conv1d(in_channels, 512, kernel_size=3, padding=1),
|
| 102 |
+
nn.LeakyReLU(0.2, inplace=True),
|
| 103 |
+
ResBlock1D(512),
|
| 104 |
+
ResBlock1D(512),
|
| 105 |
+
ResBlock1D(512),
|
| 106 |
+
nn.Conv1d(512, latent_dim, kernel_size=8, stride=4, padding=2),
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
def forward(self, x):
|
| 110 |
+
return self.net(x)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class MotionDecoder(nn.Module):
|
| 114 |
+
def __init__(self, latent_dim=512, out_channels=263):
|
| 115 |
+
super().__init__()
|
| 116 |
+
self.net = nn.Sequential(
|
| 117 |
+
nn.ConvTranspose1d(latent_dim, 512, kernel_size=8, stride=4, padding=2),
|
| 118 |
+
nn.LeakyReLU(0.2, inplace=True),
|
| 119 |
+
ResBlock1D(512),
|
| 120 |
+
ResBlock1D(512),
|
| 121 |
+
ResBlock1D(512),
|
| 122 |
+
nn.Conv1d(512, out_channels, kernel_size=3, padding=1),
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
def forward(self, z_q):
|
| 126 |
+
return self.net(z_q)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class MotionRVQ_VAE(nn.Module):
|
| 130 |
+
def __init__(self):
|
| 131 |
+
super().__init__()
|
| 132 |
+
self.encoder = MotionEncoder(in_channels=263, latent_dim=512)
|
| 133 |
+
self.rvq = RVQ(num_levels=4, num_embeddings=1024, embedding_dim=512)
|
| 134 |
+
self.decoder = MotionDecoder(latent_dim=512, out_channels=263)
|
| 135 |
+
|
| 136 |
+
def forward(self, x):
|
| 137 |
+
z = self.encoder(x)
|
| 138 |
+
z_q, token_indices, commitment_loss = self.rvq(z)
|
| 139 |
+
x_recon = self.decoder(z_q)
|
| 140 |
+
return x_recon, token_indices, commitment_loss
|