File size: 9,725 Bytes
37c61d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# MoFlow β€” edge-index 버그 μˆ˜μ •κ³Ό μƒ˜ν”Œλ§ λ°œμ‚° ν•΄κ²°

**ν•œ 쀄 μš”μ•½**
edge-index reshape λ²„κ·Έλ‘œ κ·Έλž˜ν”„κ°€ 씬을 λ’€μ„žκ³  μžˆμ—ˆλ‹€(`permute` 둜 μˆ˜μ •). 그런데
**정상 κ·Έλž˜ν”„λ₯Ό λ„£μž MoFlow 만 λ°œμ‚°**ν–ˆλ‹€ β€” ν•™μŠ΅μ΄ μ•„λ‹ˆλΌ **μƒ˜ν”Œλ§**μ—μ„œ. λ…Έλ“œλ³„
**residual-norm cap** 으둜 μŠ€ν…λ‹Ή 섭동을 μ œν•œν•΄ ν•΄κ²°ν–ˆλ‹€.

μˆ˜μ • λŒ€μƒ: `models/graph_interaction_nba.py`(`_make_batched_edge_index`),
`models/graph_interaction_nba_v6.py`(SRA κ·Έλž˜ν”„ 좜λ ₯λΆ€).
λͺ¨λ“  변경은 **ν™˜κ²½λ³€μˆ˜ 토글이고 κΈ°λ³Έκ°’ off** β€” μΌœμ§€ μ•ŠμœΌλ©΄ 원본과 λ™μΌν•˜κ²Œ λ™μž‘ν•΄
κΈ°μ‘΄ 체크포인트·기쑴(버그) 결과의 μž¬ν˜„μ„±μ΄ λ³΄μ‘΄λœλ‹€.

---

## 1. 버그: edge-index κ°€ 씬을 λ’€μ„žμŒ

```python
batched = single.unsqueeze(0).expand(S, -1, -1) + offsets.view(-1, 1, 1)  # [S, 2, E0]
return batched.reshape(2, -1)     # ← 버그
```

`[S, 2, E0]` 의 λ©”λͺ¨λ¦¬ μˆœμ„œλŠ” `s0_src, s0_dst, s1_src, s1_dst, …` 이닀.
`reshape(2,-1)` 은 이 버퍼λ₯Ό **μ•ž/λ’€ 절반**으둜 자λ₯΄λ―€λ‘œ row0 에 씬0 블둝,
row1 에 씬1 블둝이 λ“€μ–΄κ°„λ‹€ β€” src/dst κ°€ μ•„λ‹ˆλΌ **μ”¬μœΌλ‘œ λ‚˜λ‰œλ‹€**.

μ‹€μΈ‘ (A=11, S=4):

| | 같은-씬 edge | 이웃 0인 κ³ μ•„ λ…Έλ“œ | λ…Έλ“œ degree |
|---|---|---|---|
| 버그 | **0 %** | **50 %** | 0 λ˜λŠ” 20 (정상 10) |
| μˆ˜μ • | **100 %** | 0 % | μ „λΆ€ 10 |

`scores.view(B*K*A, A-1)` 의 top-N 이웃 그룹핑도 λ²„κ·Έμ—μ„œ **33 %** 만 μ‹€μ œ νƒ€κ²Ÿκ³Ό
μΌμΉ˜ν–ˆκ³ , μˆ˜μ • ν›„ **100 %** κ°€ 됐닀. 즉 이웃 선택 μžμ²΄λ„ 망가져 μžˆμ—ˆλ‹€.

인덱슀 λ²”μœ„λŠ” 정상(0~43)이라 PyG κ°€ μ—λŸ¬λ₯Ό λ‚΄μ§€ μ•ŠλŠ”λ‹€ β†’ **ν¬λž˜μ‹œ 없이 쑰용히 ν‹€λ¦°
κ·Έλž˜ν”„**λ₯Ό μ“°κ³  μžˆμ—ˆκ³ , κ·Έλž˜μ„œ 였래 λ°œκ²¬λ˜μ§€ μ•Šμ•˜λ‹€.

### μˆ˜μ • (`SRA_EDGE_FIX=1`)

```python
if os.environ.get('SRA_EDGE_FIX', ...):
    return batched.permute(1, 0, 2).reshape(2, -1)   # src/dst 좕을 λ¨Όμ € β†’ 씬 λ‚΄λΆ€ κ·Έλž˜ν”„
return batched.reshape(2, -1)                        # κΈ°λ³Έ: μ›λž˜(버그) λ™μž‘
```

**baseline 은 κ·Έλž˜ν”„λ₯Ό μΈμŠ€ν„΄μŠ€ν™”ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ 이 버그와 λ¬΄κ΄€ν•˜λ‹€** β€” λΉ„κ΅μ˜ ν•œμͺ½
좕은 μ˜¨μ „ν•˜λ‹€. 영ν–₯을 받은 것은 SRA/ablation μͺ½λΏμ΄λ‹€.

---

## 2. 증상: ν•™μŠ΅μ€ 정상, μƒ˜ν”Œλ§λ§Œ λ°œμ‚°

edge λ₯Ό 고치자 MoFlow-NBA 의 eval ADE κ°€ ν­λ°œν–ˆλ‹€. κ²Œμ΄νŠΈΒ·μ΄ˆκΈ°ν™” 처방이 연달아
μ‹€νŒ¨ν•œ λ’€, train loss 와 eval 을 **μ‹œκ°„ μ •λ ¬**ν•΄μ„œ 원인을 νŠΉμ •ν–ˆλ‹€:

```
train loss : 96.5 β†’ 35.6 β†’ 20.1 β†’ 18.0 β†’ 16.6    단쑰 κ°μ†Œ (μ™„μ „ 정상)
eval  ADE  : 1.13 β†’ 1.07 β†’ 1.52 β†’ 1.85 β†’ 3.49    λ°œμ‚°
```

train loss κ°€ λ§€λ„λŸ½κ²Œ λ‚΄λ €κ°€λŠ” **λ°”λ‘œ κ·Έ μˆœκ°„** eval 이 ν„°μ§„λ‹€. 이 관찰이 처방의
λ°©ν–₯을 λ°”κΏ¨λ‹€ β€” λ¬Έμ œλŠ” ν•™μŠ΅μ΄ μ•„λ‹ˆλΌ **μƒ˜ν”Œλ§**이닀.

---

## 3. 원인: train/sample mismatch (MoFlow 고유)

- **ν•™μŠ΅**: 랜덀 timestep ν•˜λ‚˜μ—μ„œ κ·Έλž˜ν”„κ°€ **ν•œ 번** μ μš©λœλ‹€ β†’ 섭동이 λˆ„μ λ  κ²½λ‘œκ°€ μ—†λ‹€.
- **μƒ˜ν”Œλ§**: MoFlow 의 flow 적뢄은 10 μŠ€ν…. κ·Έλž˜ν”„κ°€ **λ§€ μŠ€ν…** 적용되고 κ·Έ 좜λ ₯이
  λ‹€μŒ μŠ€ν…μ˜ μž…λ ₯(`y_abs`)으둜 λ˜λ¨Ήμž„λœλ‹€ β†’ 섭동이 **κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ λˆ„μ **λœλ‹€.

λ²„κ·ΈνŒμ΄ μ•ˆ ν„°μ§„ μ΄μœ λ„ 이걸둜 μ„€λͺ…λœλ‹€: λ…Έλ“œ 절반이 κ³ μ•„μ—¬μ„œ κ·Έλž˜ν”„ 섭동이 사싀상
절반만 ν˜λ €κ³ , 그만큼 μƒ˜ν”Œλ§ λˆ„μ μ΄ μ•½ν–ˆλ‹€. **버그가 μš°μ—°νžˆ μ •κ·œν™” μ—­ν• **을 ν•˜κ³  μžˆμ—ˆλ‹€.

MIDΒ·LED λŠ” iterative denoiser 라 κ·Έλž˜ν”„λ₯Ό 반볡 μ •μ œ κ³Όμ •μ˜ μΌλΆ€λ‘œ ν‘μˆ˜ν•œλ‹€ β€”
κ·Έλž˜μ„œ 같은 V6 λ₯Ό 써도 λ°œμ‚°ν•˜μ§€ μ•ŠλŠ”λ‹€. (이전에 κ΄€μ°°ν•œ "MoFlow λŠ” plug-in λͺ¨λ“ˆμ—
μœ λ… λ―Όκ°ν•˜κ³  faithful GameFormer/C2F 도 μ „λΆ€ λ°œμ‚°"κ³Ό μ •ν™•νžˆ 같은 νŒ¨ν„΄.)

μΈ‘μ •: κ·Έλž˜ν”„ residual λ…Έλ“œ norm β‰ˆ **6.3** vs 호슀트 μž„λ² λ”© λ…Έλ“œ norm β‰ˆ **16**
β†’ 섭동이 μž„λ² λ”©μ˜ **μ•½ 40 %**. ν•™μŠ΅μ΄ μ§„ν–‰λ˜λ©΄ 이 λΆ„ν¬μ˜ 꼬리(outlier)κ°€ 컀지고,
κ·Έ outlier κ°€ μƒ˜ν”Œλ§ λˆ„μ μ„ μ΄‰λ°œν•œλ‹€.

---

## 4. μ‹€νŒ¨ν•œ μ²˜λ°©λ“€κ³Ό κ·Έ 이유

| 처방 | κ±΄λ“œλ¦° λŒ€μƒ | κ²°κ³Ό |
|---|---|---|
| `SRA_GATE_SCALE=0.1` (게이트 μ „μ—­ μΆ•μ†Œ) | ν•™μŠ΅ | eval 5νšŒμ°¨λΆ€ν„° 단쑰 μ•…ν™” |
| `SRA_SOFT_START` (out_proj zero-init + gate bias βˆ’4) | ν•™μŠ΅ μ΄ˆκΈ°κ°’ | eval **3회차** λ°œμ‚° (였히렀 더 빠름) |
| 게이트 κΈ°λ³Έκ°’ μœ μ§€ | β€” | eval 4회차 λ°œμ‚° |

μ…‹ λ‹€ **ν•™μŠ΅**을 μ‘°μ •ν–ˆλ‹€. 그런데 ν•™μŠ΅μ€ μ›λž˜ λ©€μ©‘ν–ˆμœΌλ―€λ‘œ νš¨κ³Όκ°€ μ—†μ—ˆλ‹€.
`SRA_SOFT_START` λŠ” 초기 좜λ ₯을 μ™„μ „ identity(β€–Ξ”β€–=0)둜 λ§Œλ“€μ—ˆλŠ”λ°λ„, ν•™μŠ΅ λͺ‡ μŠ€ν…
λ§Œμ— `out_proj` κ°€ 자라자 λ˜‘κ°™μ΄ ν„°μ‘Œλ‹€ β€” **초기 좩격 가섀이 반증된** μ…ˆμ΄λ‹€.

---

## 5. 해법: λ…Έλ“œλ³„ residual-norm cap (`SRA_RES_CAP`)

μƒ˜ν”Œλ§ μ‹œ **μŠ€ν…λ‹Ή λ…Έλ“œ μ„­λ™μ˜ 크기**λ₯Ό 직접 μ œν•œν•œλ‹€. μ „μ—­ μΆ•μ†Œ(gate scale)와 달리
정상 크기의 섭동은 κ·ΈλŒ€λ‘œ 두고 **λˆ„μ μ„ μœ λ°œν•˜λŠ” 큰 outlier 만** μž˜λΌλ‚Έλ‹€.

```python
res  = _gs * gate * self.out_proj(nodes)              # [N, D] κ·Έλž˜ν”„ 섭동
_cap = float(os.environ.get('SRA_RES_CAP', 0.0))      # 0 = 끔(κΈ°λ³Έ)
if _cap > 0:
    rn  = res.norm(dim=-1, keepdim=True)              # [N, 1] λ…Έλ“œλ³„ norm
    res = res * (rn.clamp(max=_cap) / (rn + 1e-6))    # norm μƒν•œ
out  = orig + res
```

검증: `SRA_RES_CAP=3.0` μ—μ„œ λ…Έλ“œ norm 이 μ •ν™•νžˆ `≀3.0` 으둜 잘리고, gradient λŠ”
μœ μ§€λ˜λ©°(ν•™μŠ΅ 계속 κ°€λŠ₯), κ°€λ³€ A(SDD) 도 톡과.

**gate scale 과의 결정적 차이**: gate scale 은 μ‹ ν˜Έλ₯Ό 영ꡬ히 쀄여 κ·Έλž˜ν”„ νš¨κ³ΌκΉŒμ§€
같이 죽인닀. cap 은 λΆ„ν¬μ˜ 꼬리만 자λ₯΄λ―€λ‘œ κ·Έλž˜ν”„μ˜ ν‘œν˜„λ ₯을 λŒ€λΆ€λΆ„ λ³΄μ‘΄ν•œλ‹€.

---

## 6. κ²°κ³Ό

### λ°œμ‚° 지점(eval 3~5) 톡과 μ—¬λΆ€

| eval # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 게이트 κΈ°λ³Έ | 1.141 | 1.052 | 1.048 | **1.828** | 1.429 | 1.716 | 1.821 | β€” |
| soft-start | 1.134 | 1.075 | **1.516** | 1.852 | 1.697 | 1.862 | 1.765 | β€” |
| gate 0.1 | 1.143 | 1.104 | 1.118 | 1.107 | **1.355** | 1.468 | 1.521 | 1.622 |
| **cap 3.0** | 1.145 | 1.053 | 1.007 | 0.969 | 0.942 | 0.988 | 0.927 | **0.894** |
| **cap 6.0** | 1.138 | 1.018 | 0.999 | 0.976 | 0.950 | 0.947 | 0.962 | 0.895 |
| *baseline(μ°Έκ³ )* | 1.163 | 1.021 | 0.961 | 0.964 | 0.952 | 0.955 | 0.953 | 0.895 |

cap 두 κ°’ λͺ¨λ‘ λ°œμ‚° 지점을 ν†΅κ³Όν•˜κ³  **baseline ꢀ적을 κ·ΈλŒ€λ‘œ 따라간닀**.

### μž₯κΈ° 수렴 (2026-07-28 κΈ°μ€€, 58회 평가 Β· 10029/25500 = 39 %)

| μ„€μ • | ν˜„μž¬ best ADE/FDE | λΉ„κ³  |
|---|---|---|
| **cap 6.0** | **0.7373 / 0.9080** | λ°œμ‚° 없이 58회 |
| **cap 3.0** | **0.7399 / 0.9300** | λ°œμ‚° 없이 58회 |
| *κΈ°μ‘΄(λ²„κ·ΈνŒ) SRA* | *0.695* | μ™„μ£Όκ°’ |
| *baseline* | *0.703* | μ™„μ£Όκ°’ |

cap 3.0 κ³Ό 6.0 의 차이가 **0.0026** 에 λΆˆκ³Όν•˜λ‹€ β†’ 이 μ²˜λ°©μ€ cap 값에 λ―Όκ°ν•˜μ§€ μ•Šλ‹€
(ν•˜μ΄νΌνŒŒλΌλ―Έν„° νŠœλ‹μ— μ˜μ‘΄ν•˜λŠ” μž„μ‹œλ°©νŽΈμ΄ μ•„λ‹ˆλΌλŠ” κ·Όκ±°).

### λ‹€λ₯Έ μ„€μ •μ—μ„œμ˜ μž¬ν˜„

같은 처방(`EDGE_FIX + SOFT_START + RES_CAP=3.0`)으둜 돌린 MoFlow-NBA ablation 2건도
λ°œμ‚° 없이 ν•˜κ°• 쀑 β€” cap 의 νš¨κ³Όκ°€ full SRA ν•œ μ„€μ •μ—λ§Œ κ΅­ν•œλ˜μ§€ μ•ŠλŠ”λ‹€.

| μ‹€ν–‰ | 평가 수 | ν˜„μž¬ best |
|---|---|---|
| sparse selection (no Οƒ) | 16 | 0.8034 |
| encoding (all nbrs, no Οƒ) | 9 | 0.8648 |

---

## 7. ν•œκ³„μ™€ λ―Έν™•μ • (μ •μ§ν•˜κ²Œ)

**β‘  SOFT_START κ°€ κ΅λž€ λ³€μˆ˜λ‹€.** cap μ‹€ν–‰ 4건 λͺ¨λ‘ `SRA_SOFT_START=1` 을 ν•¨κ»˜ μΌ°λ‹€.
λ”°λΌμ„œ "cap λ‹¨λ…μœΌλ‘œ μΆ©λΆ„ν•œκ°€"λŠ” **아직 κ²€μ¦λ˜μ§€ μ•Šμ•˜λ‹€**. soft-start λŠ” λ‹¨λ…μœΌλ‘œλŠ”
μ‹€νŒ¨ν–ˆμœΌλ―€λ‘œ cap 이 핡심인 것은 λΆ„λͺ…ν•˜μ§€λ§Œ, λ‘˜μ˜ κΈ°μ—¬λ₯Ό λΆ„λ¦¬ν•˜λ €λ©΄
`RES_CAP` 만 μΌ  λŒ€μ‘° 싀행이 ν•„μš”ν•˜λ‹€.

**β‘‘ μ΅œμ’… μˆ˜λ ΄κ°’ λ―Έν™•μ •.** 39 % μ§€μ μ—μ„œ 0.737 이고, κΈ°μ‘΄(λ²„κ·ΈνŒ) 0.695 / baseline
0.703 보닀 아직 μœ„λ‹€. 남은 61 % 와 cosine LR κ°μ‡ μ—μ„œ 더 λ‚΄λ €κ°ˆ μ—¬μ§€λŠ” μžˆμœΌλ‚˜,
**0.695 λ₯Ό μž¬ν˜„ν•˜μ§€ λͺ»ν•  κ°€λŠ₯성도 μ—΄λ € μžˆλ‹€.**

**β‘’ κ·Έ 경우의 결둠도 보고할 κ°€μΉ˜κ°€ μžˆλ‹€.** "정상 씬-λ‚΄λΆ€ κ·Έλž˜ν”„λŠ” μ•ˆμ •μ μœΌλ‘œ ν•™μŠ΅λ˜λ‚˜
MoFlow 의 one-step flow matching μ—μ„œλŠ” 이득이 μ œν•œμ "μ΄λΌλŠ” 것 μžμ²΄κ°€, λ²„κ·ΈνŒμ˜
0.695 κ°€ **μ˜λ„ν•œ λ©”μ»€λ‹ˆμ¦˜μ΄ μ•„λ‹ˆλΌ μš°μ—°ν•œ μ •κ·œν™” 효과**μ˜€μ„ 수 μžˆμŒμ„ μ‹œμ‚¬ν•˜λŠ”
λ°œκ²¬μ΄λ‹€.

**β‘£ MID / LED λŠ” λ‹€λ₯Έ μ²˜λ°©μ„ μ“΄λ‹€.** RES_CAP 은 MoFlow μ „μš©μ΄λ‹€. MID λŠ”
`graph_gate_init` 0.01β†’0.001 + warmup 100β†’200 으둜 λΆ•κ΄΄λ₯Ό λ§‰μ•˜κ³ , LED λŠ” 별도
처방 없이 회볡 쀑이닀(LED sparse no-Οƒ λŠ” 이미 κΈ°μ‘΄κ°’ 0.886 β†’ **0.800** 으둜 μ•žμ„¬).

---

## 8. μž¬ν˜„

```bash
# MoFlow-NBA full SRA (ν˜„μž¬ μ‹€ν–‰ μ„€μ •)
SRA_EDGE_FIX=1 SRA_SOFT_START=1 SRA_RES_CAP=3.0 \
CUDA_VISIBLE_DEVICES=0 python fm_nba_graph_v6.py \
  --cfg cfg/nba/cor_fm.yml --exp edgefix_cap3 \
  --batch_size 192 --epochs 150 --fm_in_scaling --tied_noise \
  --top_n_neighbors 5 --uncertainty_weight 0.01 --data_dir ./data/nba

# no-Οƒ ablation 은 μ „μš© 슀크립트 μ‚¬μš© (use_sigma_gating=False ν•˜λ“œμ½”λ”©)
SRA_EDGE_FIX=1 SRA_SOFT_START=1 SRA_RES_CAP=3.0 \
python fm_nba_graph_v6_nosigma.py --cfg cfg/nba/cor_fm.yml --exp ef_sparse_nosig \
  --batch_size 192 --epochs 150 --fm_in_scaling --tied_noise --top_n_neighbors 5
```

| ν† κΈ€ | κΈ°λ³Έ | μ—­ν•  |
|---|---|---|
| `SRA_EDGE_FIX` | off | edge-index 씬 ν˜Όν•© 버그 μˆ˜μ • (**핡심**) |
| `SRA_RES_CAP` | 0 (off) | λ…Έλ“œλ³„ residual norm μƒν•œ β€” MoFlow μƒ˜ν”Œλ§ μ•ˆμ •ν™” (**핡심**) |
| `SRA_SOFT_START` | off | out_proj zero-init + gate bias βˆ’4 (보쑰, λ‹¨λ…μœΌλ‘œλŠ” μ‹€νŒ¨) |
| `SRA_GATE_SCALE` | 1.0 | 게이트 μ „μ—­ μΆ•μ†Œ (μ‹€νŒ¨ν•œ 처방, μ½”λ“œλ§Œ μž”μ‘΄) |