---
# System prepended metadata

title: "\U0001F916 AI Motion 模型開發與訓練全指南 (完整版)"
tags: [Houdini, Machine Learning]

---

# 🤖 AI Motion 模型開發與訓練全指南 (完整版)

本文件整合了從數據預處理、序列預測（Predictor）到隨機生成（VAE）的完整實作路徑。

---

## 📂 第一部分：動作序列預測模型 (Motion Predictor)

**目標**：輸入前 60 幀，預測下一幀（第 61 幀）。適用於動作補完或即時互動。

### 1. 模型架構

```python
import torch
import torch.nn as nn

class MotionPredictor(nn.Module):
    def __init__(self, input_dim=120, model_dim=256, nhead=8, num_layers=3):
        super().__init__()
        # 位置編碼與投影
        self.pos_encoder = nn.Parameter(torch.zeros(1, 60, model_dim))
        self.input_projection = nn.Linear(input_dim, model_dim)
        
        # Transformer Encoder
        enc_layer = nn.TransformerEncoderLayer(d_model=model_dim, nhead=nhead, batch_first=True)
        self.transformer_encoder = nn.TransformerEncoder(enc_layer, num_layers=num_layers)
        
        # 輸出層：將特徵轉回原始維度
        self.decoder = nn.Linear(model_dim, input_dim)

    def forward(self, src):
        # src: (Batch, 60, 120)
        x = self.input_projection(src) + self.pos_encoder
        x = self.transformer_encoder(x)
        
        # 【預測邏輯】：取最後一幀的特徵來預測未來
        out = self.decoder(x[:, -1, :]) 
        return out # 返回 (Batch, 120)

```

### 2. 預測模型訓練代碼

```python
def train_predictor(model, dataloader, epochs=50):
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
    criterion = nn.MSELoss() # 預測任務通常使用均方誤差

    model.train()
    for epoch in range(epochs):
        for batch_x, batch_y in dataloader:
            # batch_x: (Batch, 60, 120)
            # batch_y: (Batch, 120) -> 真實的第 61 幀
            optimizer.zero_grad()
            output = model(batch_x)
            loss = criterion(output, batch_y)
            loss.backward()
            optimizer.step()
        print(f"Epoch {epoch+1}, Loss: {loss.item():.6f}")

```

---

## 🎨 第二部分：隨機動作生成模型 (Motion VAE)

**目標**：學習動作的「隱藏空間」，並從中隨機抽樣產生全新的動畫變體。

### 1. 模型架構 (含 Encoder 與 Decoder)

```python
class MotionVAE(nn.Module):
    def __init__(self, input_dim=120, model_dim=256, latent_dim=64, seq_len=60):
        super().__init__()
        self.latent_dim = latent_dim
        self.seq_len = seq_len
        self.pos_encoder = nn.Parameter(torch.zeros(1, seq_len, model_dim))
        
        # Encoder 支柱
        self.enc_embed = nn.Linear(input_dim, model_dim)
        self.encoder = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(d_model=model_dim, nhead=8, batch_first=True), num_layers=3)
        self.fc_mu = nn.Linear(model_dim, latent_dim)
        self.fc_logvar = nn.Linear(model_dim, latent_dim)

        # Decoder 支柱
        self.latent_to_model = nn.Linear(latent_dim, model_dim)
        self.dec_transformer = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(d_model=model_dim, nhead=8, batch_first=True), num_layers=3)
        self.final_out = nn.Linear(model_dim, input_dim)

    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std # 此處梯度會回傳至 mu 與 logvar

    def forward(self, x):
        # 壓縮過程
        x_enc = self.encoder(self.enc_embed(x) + self.pos_encoder)
        z_feat = x_enc[:, -1, :] # 取序列精華
        mu, logvar = self.fc_mu(z_feat), self.fc_logvar(z_feat)
        
        # 抽樣與還原
        z = self.reparameterize(mu, logvar)
        recon_x = self.decode(z)
        return recon_x, mu, logvar

    def decode(self, z):
        """隨機抽樣後調用此函數"""
        z_emb = self.latent_to_model(z).unsqueeze(1).expand(-1, self.seq_len, -1)
        z_emb = z_emb + self.pos_encoder
        return self.final_out(self.dec_transformer(z_emb))

```

### 2. VAE 訓練代碼 (含雙重 Loss)

```python
def train_vae(model, dataloader, epochs=100):
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

    for epoch in range(epochs):
        for data in dataloader: # data: (Batch, 60, 120)
            optimizer.zero_grad()
            recon_x, mu, logvar = model(data)
            
            # 1. 重建誤差：確保動作正確
            recon_loss = F.mse_loss(recon_x, data, reduction='sum')
            # 2. KL 散度：確保隱藏空間整齊（梯度會校正 mu 和 logvar）
            kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
            
            loss = recon_loss + kl_loss
            loss.backward()
            optimizer.step()
        print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")

```

---

## 🛠️ 第三部分：Houdini 與推理應用 (Inference)

### 1. 預測未來模式

* **用法**：將當前 60 幀餵入 Predictor，得到第 61 幀。
* **特性**：適合做物理連動或即時反應。

### 2. 隨機生成模式 (VAE Decode)

* **用法**：
```python
z = torch.randn(1, 64) # 從常態分佈抽樣（數值多在 -2 到 2）
new_motion = model.decode(z)

```


* **特性**：每次點擊按鈕，都會產生一段全新的類似動作。
* **Houdini 整合**：利用 **Slider** 控制 `latent_dim` 中的數值，你可以像調色盤一樣「滑動」出不同的動作風格。

---

## 💡 總結檢查清單 (Development Checklist)

1. **維度一致性**：`latent_dim` 是隱藏空間的維度，數值範圍在訓練後應趨向 。
2. **位置編碼**：在 Predictor 的 Encoder 以及 VAE 的 Decoder 中都是必須的。
3. **梯度傳導**：均值與對數方差透過 `reparameterize` 參與梯度更新，從而定義了有意義的隱藏空間。
