# MoNaVLA 주간 작업 기록
**기간: 2026-04-03 (목) ~ 2026-04-10 (금)**

---

## 1. 핵심 요약

이번 주는 **V4 모델의 한계를 확인하고, V5 데이터셋 수집 완료 및 Visual Grounding 검증 인프라를 전면 구축**한 한 주였다.

- Git 커밋 4개 + 미커밋 주요 코드 변경 다수
- V5 데이터 **150 에피소드** 수집 완료 → 교수님 서버 전송
- `inference_server.py`, `nav_h5_dataset_impl.py` 2DOF → **3DOF** 전면 확장
- 그라운딩 검증 파이프라인 구축 (820 프레임 전수 실행)
- 브라우저에서 열어볼 수 있는 HTML 뷰어 2종 생성

---

## 2. 날짜별 작업 기록

### 2026-04-03 (목)

#### [커밋] `crit`: VLM 백본 시각 장애 증거 확인 (Patch Index Repeat)

**발견한 문제**: V4 모델이 추론 시 동일한 patch token (`<patch_index_N>`)을 무한 반복 출력하는 현상 포착.

```
"Ring Ring Ring Lighted Ring Light Ring Ring Ring Ring..."
```

- **원인**: Kosmos-2의 `image_to_text_projection` 레이어가 action prediction fine-tuning 과정에서 덮어씌워짐. 비전 feature가 텍스트 생성 경로에 올바르게 전달되지 않는 **Backbone Blindness** 상태.
- **모델별 상태**:
  - Pure HF Kosmos-2: 정상 (그라운딩/캡셔닝 가능)
  - Google-robot pretrained: 불가 (오염됨)
  - V4 LoRA: 불가 (오염됨)
- **결론**: Fine-tuned 체크포인트에서 원본 grounding 능력 복구는 현실적으로 불가능. 검증 목적에는 별도 Pure HF Kosmos-2를 독립 로딩하는 방식으로 우회.
- **변경 파일**:
  - `docs/analysis/vla_perception_audit_v4.md`
  - `docs/analysis/vla_perception_live_dashboard.html` — 라이브 대시보드 간소화 (146줄 → 38줄)

#### [문서화] V4 추론 튜닝 실험 결과 (`docs/analysis/vla_tuning_report_20260403.md`)

V4 모델(`mobile_vla_v4_balanced_v2`, 87% 정확도)의 실제 로봇 주행 동작 튜닝 실험:

| 케이스 | Temperature | Penalty | Window | 결과 |
|---|---|---|---|---|
| Baseline | 1.0 | 0.0 | 8 | 실패 (장애물 충돌) |
| Case 1 (Soft) | 1.5 | -0.5 | 4 | 부분 성공 (FL 진동) |
| Case 2 (V2) | 1.1 | -0.1 | 4 | 실패 (직진만) |
| **Case 3 (Ban)** | **1.1** | **-100.0** | **1** | **성공 (FL/FR 곡선)** |

핵심 개선 사항:
1. **중복 Normalize 제거** — `inference_server.py`에서 이중 정규화 버그 수정 → 시각 민감도 복구
2. **Action Smoothing (EMA)** — `0.6 × current + 0.4 × previous`로 급격한 액션 전환 방지
3. **Dynamic History Window** — `head.history_len`을 런타임 `window_size`와 동기화
4. **Forward-Ban Masking** — logit에 -100.0 페널티로 직진 편향 강제 해제

---

### 2026-04-09 (수) — 주요 작업 집중

#### [커밋] `feat`: V5 데이터 수집 최적화 및 Visual Grounding 검증 기능 강화

**데이터 수집 현황** (이 시점 총 54 에피소드):

| 태스크 | 에피소드 수 |
|---|---|
| 중앙→왼쪽 경로 (`fixed_center`) | 15 |
| 중앙→오른쪽 경로 (`fixed_center`) | 15 |
| 중앙 직선 경로 (`fixed_center`) | 20 |
| 왼쪽→직선 경로 (`fixed_center`) | 4 |
| **합계** | **54** |

**Gradio 대시보드 강화** (`scripts/gradio_inference_dashboard.py`):
- `draw_bounding_box_from_text` 함수 추가: 모델 텍스트 출력 기반으로 이미지에 BBox 렌더링
- Trajectory Plot 생성 로직 개선: 로봇 이동 패턴 실시간 확인

**데이터 수집기 최적화** (`ROS_action/mobile_vla_data_collector.py`):
- End-state 분류 로직 개선 → 수집 일관성 향상
- 이미지 캡쳐 서비스와 동기화 강화 → Action-Observation 쌍 정렬 정확도 향상

**새 스크립트 추가**:
- `scripts/gradio_data_collector.py` (464줄) — Gradio 기반 데이터 수집 UI
- `scripts/gradio_offline_h5_analyzer.py` (199줄) — 오프라인 H5 파일 분석기
- `scripts/h5_to_images.py` (51줄) — H5 에피소드를 이미지 시퀀스로 추출

**환경 자동화**: `.vla_aliases`에 `vla-reset`, `vla-collect-gradio` 추가

#### [커밋] `feat(collector)`: Rotation 0.2 캘리브레이션 및 교수님 서버 업로드 스크립트

- `scripts/gradio_data_collector.py` — rotation 0.2 캘리브레이션 보정
- `scripts/upload_to_prof_server.py` 신규 (86줄) — 교수님 서버 자동 전송

#### [커밋] `data`: V5 데이터셋 150 에피소드 수집 완료 및 교수님 서버 전송

- `ROS_action/mobile_vla_dataset_v5/core_replay_db.json` 갱신 (810줄 추가)
- `scenario_progress.json`, `time_period_stats.json` 업데이트
- 교수님 서버 전송 완료 (260409 시점 스냅샷)

---

### 2026-04-10 (금) — 미커밋 코드 변경 (로컬)

현재 staged/unstaged 상태로 남아 있는 주요 변경사항들:

#### 1. `robovlm_nav/serve/inference_server.py` — 2DOF → 3DOF 전면 전환

**Action 차원 확장**: `[lx, az]` → `[lx, ly, az]`

핵심 변경:
- `prev_action = np.zeros(2)` → `np.zeros(3)` (EMA 버퍼)
- **9-class 레이블 맵 전면 재정의**: 기존 6-class에서 Label 2 & 8 복구

  ```python
  # 기존 (잘못됨): 2=LEFT(strafe), 3=RIGHT(strafe)
  # 수정 후: 2=ROTATE_LEFT(T키), 8=ROTATE_RIGHT(R키)
  self.class_index_action_map = {
      0: [0.0,    0.0,   0.0],  # STOP
      1: [speed,  0.0,   0.0],  # FORWARD (W)
      2: [0.0,    0.0, -0.5],   # ROTATE LEFT (T키)
      3: [0.0,  speed,   0.0],  # STRAFE LEFT (A)
      4: [0.0, -speed,   0.0],  # STRAFE RIGHT (D)
      5: [diag,  diag,   0.0],  # FWD+STRAFE LEFT
      6: [diag, -diag,   0.0],  # FWD+STRAFE RIGHT
      7: [-speed, 0.0,   0.0],  # BACKWARD (S)
      8: [0.0,    0.0,  +0.5],  # ROTATE RIGHT (R키)
  }
  ```

- **경로 정규화 강화**: 스테일 경로(`/home/soda/...`) 자동 치환 로직 개선. 존재하지 않는 절대경로 감지 → `project_root/.vlms/` 하위에서 재탐색
- **Tokenizer fallback**: config에 명시된 경로가 없으면 `project_root/.vlms/kosmos-2-patch14-224`로 자동 폴백
- **BFloat16 → Float16 강제**: LSTM act_head가 BFloat16 미지원 → Float16으로 통일
- **기본 체크포인트 변경**: V4 exp01 → `v5_exp01` (epoch05, val_loss=2.270)
- **기본 config 변경**: `mobile_vla_v4_exp01.json` → `mobile_vla_v5_exp01_discrete.json`
- **uvicorn CLI 인자화**: `--port`, `--host` 인자 추가. 서버 시작 전 모델 프리로드 보장
- **에러 로그 개선**: traceback 전체 출력으로 디버깅 편의성 향상

#### 2. `robovlm_nav/datasets/nav_h5_dataset_impl.py` — V5 H5 포맷 지원

V4와 V5의 H5 파일 구조가 다르다:
- V4: `f['images']` 직접 접근
- V5: `f['observations']['images']` 중첩 구조

모든 이미지 접근부를 자동 감지하도록 수정:
```python
images_src = f['observations']['images'] if 'observations' in f else f['images']
```

**Action 차원 처리**:
- `actions.append(episode_actions[t][:2])` → `[:3]` 로 확장
- V4 에피소드(action_dim < 3)는 자동으로 zero-padding:
  ```python
  if action_dim < 3:
      a = np.pad(a, (0, 3 - len(a)))  # [lx, az] → [lx, az, 0]
  ```

**신규 기능**:
- `stratified_split` — 경로 타입(straight/left/right)별로 독립 train/val 분할
- `exclude_path_types` — 특정 경로 타입 에피소드 필터링 (예: straight 제거)
- `grounding_prefix` — H5 instruction 앞에 `<grounding>` 자동 추가 옵션

**9-class 레이블링 수정**: az 축(제자리 회전)을 별도 조건으로 처리
```python
if is_az_pos and not (is_x_pos or is_x_neg or is_y_pos or is_y_neg):
    label = 4  # RIGHT (angular_z+ = 우회전)
elif is_az_neg and not ...:
    label = 3  # LEFT  (angular_z- = 좌회전)
```

**좌우 반전 augmentation**: discrete/continuous 구분 없이 `ly`, `az` 양쪽 부호 반전

#### 3. `robovlm_nav/train.py` — MPI hang 방지

Lightning이 mpi4py 환경에서 orted 데몬을 spawn해 블로킹되는 버그 수정:
```python
import lightning.fabric.plugins.environments.mpi as _mpi_env_mod
_mpi_env_mod._MPI4PY_AVAILABLE = False
```

#### 4. `scripts/inference_logger.py` — 경로 하드코딩 제거

`/home/soda/vla/docs/inference_reports` 하드코딩 → `VLA_ROOT` 환경변수 기반 동적 경로

#### 5. 신규 V5 테스트/분석 스크립트 (미커밋)

| 스크립트 | 역할 |
|---|---|
| `scripts/run_v5_grounding.py` | Pure HF Kosmos-2로 전 프레임 grounding 실행 → `v5_grounding.json` 저장 |
| `scripts/generate_v5_viewer.py` | H5 + grounding JSON → `v5_viewer.html` 생성 |
| `scripts/test_v5_simple_inf.py` | V5 체크포인트 단일 프레임 추론 테스트 |
| `scripts/test_v5_episode_rollout.py` | V5 에피소드 롤아웃 시뮬레이션 |
| `scripts/test_v5_ckpt_compare.py` | V5 체크포인트 간 비교 |
| `scripts/test_v5_pm_dm.py` | Policy/Data 모듈 단독 테스트 |
| `scripts/monitor_dashboard.py` | 학습 모니터링 대시보드 |
| `scripts/analysis/analyze_v5_distribution.py` | V5 데이터셋 클래스 분포 분석 |
| `scripts/run_v5_basket_analysis.py` | 바구니 탐지 분석 |
| `configs/mobile_vla_v5_exp01_discrete.json` | V5 Exp01 설정 (6-class discrete) |
| `configs/mobile_vla_v5_exp02_clip_norm.json` | V5 Exp02 설정 (CLIP Norm Loss) |
| `configs/mobile_vla_v5_exp02_no_straight.json` | V5 Exp02 설정 (직진 제외) |

#### 6. `.vlms/kosmos-2-patch14-224/` 토크나이저 파일 삭제 (미커밋)

config/tokenizer JSON 5종 삭제됨 (git에서 D 상태). 실제 모델 weights는 HuggingFace 캐시(`~/.cache/huggingface/hub/`)에서 로딩하도록 경로가 이미 전환되어 있어 영향 없음.

---

## 3. 세 모델의 차이: Pure HF vs Google-robot vs 우리 V4 LoRA

### 3.1 모델별 구조 비교

이번 주 Backbone Blindness 조사 과정에서 세 모델의 동작 차이를 체계적으로 정리했다.

| 항목 | Pure HF Kosmos-2 | Google-robot pretrained | 우리 V4 LoRA |
|---|---|---|---|
| **로딩 방식** | `AutoModelForVision2Seq.from_pretrained("microsoft/kosmos-2-patch14-224")` | `MobileVLAInference` via `kosmos_ph_google-robot-post-train.pt` | `MobileVLAInference` via V4 체크포인트 |
| **백본 구조** | 원본 HuggingFace 가중치 | `PeftModelForCausalLM` (LoRA wrapping) | `PeftModelForCausalLM` (LoRA wrapping) |
| **텍스트 생성** | 정상 — 자연어 출력 | 비정상 — garbage 출력 | 비정상 — garbage 출력 |
| **Grounding** | 가능 | 불가 | 불가 |
| **Action Prediction** | 불가 (학습 안 됨) | 가능 (Google 로봇 데이터 학습) | 가능 (우리 V3/V5 데이터 학습) |

### 3.2 왜 fine-tune하면 grounding이 망가지나

Kosmos-2 멀티모달 아키텍처는 세 레이어로 구성된다:

```
Image Encoder (Vision, frozen)
       ↓
image_to_text_projection   ← 핵심 브릿지 레이어 (full fine-tune됨)
       ↓
Text Decoder (Language Model)
       ↓
Grounding / Text Output
```

`image_to_text_projection`은 비전 feature를 언어 모델 토큰 공간으로 매핑하는 브릿지다. 이 레이어가 action prediction 학습 시 **전체 미세조정(full fine-tune)** 으로 덮어씌워진다:

```
학습 전: image features → text tokens (grounding/caption용)
학습 후: image features → action features (텍스트 생성 불가)
```

LoRA adapter를 우회해서 `backbone.base_model.model.generate()`를 직접 호출해도 동일하게 garbage가 나오는 이유:
1. LoRA adapter 자체도 action prediction 방향으로 수렴됨
2. `image_to_text_projection`은 LoRA가 아닌 **full fine-tune**이라 LoRA 우회로 복구 불가
3. 기반 모델 가중치 자체가 이미 오염

→ **결론**: fine-tuned 체크포인트에서 grounding 복구는 현실적으로 불가. Grounding 전용 별도 모델(Pure HF) 사용이 유일한 해결책.

### 3.3 세 모델의 실제 출력 예시

동일 이미지(복도 회색 바구니)에서 동일 프롬프트 `<grounding>The gray basket is at` 입력:

**Pure HF Kosmos-2** (정상):
```
"The gray box is located in the middle of the room."
Entity: "the gray box" | BBox: (0.33, 0.45) → (0.55, 0.83)
```

**Google-robot pretrained / V4 LoRA** (비정상):
```
"Ring Ring Ring Lighted Ring Light Ring Ring Ring Ring..."
Entity: <patch_index_493><patch_index_493><patch_index_493>...  (반복)
```

### 3.4 향후 grounding 능력을 유지하려면

이번 V5 이후 학습 설계 시 고려할 수 있는 옵션:

| 옵션 | 방식 | 단점 |
|---|---|---|
| A | `image_to_text_projection` frozen 유지, action head만 학습 | action prediction 성능 저하 가능 |
| B | Dual-head: grounding head(원본) + action head(별도 projection) | 구조 변경 필요, 메모리 증가 |
| **C (현재)** | Pure HF 별도 로딩 (오프라인 검증 전용) | 추론 시 모델 두 개 로딩 필요 |

현재는 옵션 C로 검증 목적에는 충분.

---

## 4. Visual Grounding 검증 파이프라인 상세

### 4.1 왜 진행했나

V4 모델이 Backbone Blindness 상태임을 확인한 후, "그러면 현재 수집된 V5 데이터에서 바구니가 실제로 제대로 찍히고 있나?"를 검증할 필요가 생겼다. Grounding으로 bbox를 추출해 데이터 품질을 독립적으로 확인하는 것이 목적.

### 4.2 프롬프트 탐색 과정

총 5가지 프롬프트 형식을 V5 실제 이미지(복도 회색 바구니)로 비교 테스트:

| 프롬프트 | 검출 결과 | 평가 |
|---|---|---|
| `<grounding>An image of a robot. Where is the gray basket?` | "The room" — 전체화면 | 실패 |
| `<grounding><phrase>gray basket</phrase>` | `<patch_index_493>...` — bbox는 정확하나 entity명 깨짐 | 사용 불가 |
| `<grounding>An image of <phrase>gray basket</phrase>` | 위와 동일 | 사용 불가 |
| `<grounding>A gray basket is located at` | "a white wall" — 배경 오검출 | 실패 |
| **`<grounding>The gray basket is at`** | **"the gray box" — 정확한 bbox** | **채택** |

`<phrase>` 태그 방식이 실패한 이유: entity 이름이 `<patch_index_N>` 포지셔널 토큰으로 치환되어 분류/필터링 단계에서 사용 불가.

`completion-style prompting` (`...is at`으로 끝나는 형식)이 효과적인 이유: 모델이 문장을 완성하려는 경향 때문에 specific location을 생성하게 됨.

### 4.3 후처리 파이프라인

```
Pure HF Kosmos-2 (HuggingFace 캐시)
    ↓
프롬프트: "<grounding>The gray basket is at"
    ↓
processor.post_process_generation() → (entity_name, bbox)
    ↓
필터 1: fullscreen bbox (area > 0.90) 제거
    ↓
필터 2: entity 이름 색상 분류
    - 초록:  "basket", "box", "container" 포함
    - 노랑:  "gray", "grey" 포함
    - 주황:  기타 (배경 오검출 가능성)
    ↓
저장: ROS_action/v5_data_bak/v5_grounding.json
```

820 프레임 (50 에피소드) 전수 실행 완료.

### 4.4 생성된 HTML 시각화 도구

#### `v5_viewer.html` (902줄)
에피소드 브라우저. 브라우저에서 로컬로 열 수 있는 단일 HTML 파일.
- 좌측 사이드바: 에피소드 목록 + 액션 분포 pill (STOP/FORWARD/LEFT/RIGHT/FWD+LEFT/FWD+RIGHT 컬러 코딩)
- 메인: 프레임 슬라이더 + 이미지 + 액션 레이블
- BBox 토글: grounding JSON 있을 때 bbox 오버레이 on/off
- 탭: 에피소드 뷰 / 통계 뷰 분리

#### `vla_analyzer_docs.html` (923줄)
분석 문서 포털. 다크 테마 SPA 형태.
- 헤더 네비게이션으로 섹션 이동
- 그라운딩 파이프라인 설계 결정, 프롬프트 비교 표, 향후 개선 방향 포함
- 코드 블록, 표, 배지 형태의 인터랙티브 문서

두 파일 모두 `ROS_action/v5_data_bak/` 에 위치. 외부 CDN 의존 없이 자체 완결형.

---

## 5. 전체 시스템 파이프라인

### 5.1 데이터 수집 파이프라인 (로봇 서버 — Jetson Orin `fevers`)

```
로봇 카메라 (RGB)
    ↓
ROS2 이미지 토픽 (/camera/image_raw)
    ↓
mobile_vla_data_collector.py
    ├── 키보드 입력 감지 (W/A/S/D/R/T)
    ├── 이미지 캡처 서비스 동기화
    │   └── Action-Observation 타임스탬프 정렬 (이번 주 개선)
    ├── End-state 분류 (이번 주 개선)
    │   └── 에피소드 종료 조건 자동 판단
    └── H5 저장
        └── observations/images[] + actions[] (V5 구조)
            ※ 파일명: episode_YYMMDD_HHMMSS_{scenario}.h5

scenarios/ 시나리오 관리 자동화
    └── scenario_progress.json, time_period_stats.json 자동 갱신

upload_to_prof_server.py (이번 주 추가)
    └── 교수님 서버로 rsync/scp 자동 전송
```

**키 → 레이블 매핑** (데이터 수집 기준):

| 키 | 레이블 번호 | 동작 |
|---|---|---|
| W | 1 | FORWARD |
| A | 3 | STRAFE LEFT |
| D | 4 | STRAFE RIGHT |
| S | 7 | BACKWARD |
| T | 2 | ROTATE LEFT (제자리) |
| R | 8 | ROTATE RIGHT (제자리) |
| 정지 | 0 | STOP |

### 5.2 모델 추론 파이프라인 (모델 서버 — Billy RTX A5000)

```
[FastAPI 서버] inference_server.py (:8000)
    ↓ POST /predict
    {image: base64, instruction: str}
    ↓
MobileVLAInference.predict()
    ├── 이미지 디코딩 → PIL → 전처리
    ├── image_history 윈도우 관리 (window_size=6)
    │
    ├── [Discrete 모드 — V5]
    │   ├── Kosmos-2 백본 forward → logits (num_classes=9)
    │   ├── Logit Penalization (Forward-Ban: P=-100.0)
    │   ├── Temperature Scaling
    │   ├── argmax → class_index
    │   └── class_index_action_map → [lx, ly, az]
    │
    └── Action Smoothing (EMA: 0.6×cur + 0.4×prev)
        ↓
    응답: {action: [lx, ly, az], latency_ms: float}
    ↓
Jetson: ROS2 cmd_vel 발행 → 로봇 주행
```

**서버 간 통신 (Tailscale VPN)**:
```
Jetson (100.85.118.58) ──Tailscale──► Billy (A5000)
    POST /predict                        FastAPI :8000
    X-API-Key: {key}                     모델 추론
                                         GPU: RTX A5000
```

### 5.3 그라운딩 검증 파이프라인 (오프라인 — Billy 서버)

이 파이프라인은 실시간 추론과 분리된 오프라인 검증 전용이다.

```
V5 H5 에피소드 파일들
(ROS_action/v5_data_bak/mobile_vla_dataset_v5/)
    ↓
scripts/h5_to_images.py
    └── H5 → 이미지 파일로 추출
        (v5_data_bak/mobile_vla_dataset_v5(Image)/)
    ↓
scripts/run_v5_grounding.py
    ├── Pure HF Kosmos-2 로딩 (HF 캐시)
    │   └── microsoft/kosmos-2-patch14-224
    ├── 프롬프트: "<grounding>The gray basket is at"
    ├── 프레임별 generate() → post_process_generation()
    ├── fullscreen bbox 필터링 (area > 0.90)
    └── v5_grounding.json 저장
        └── {episode_id: {frame_id: {bbox, entity, color}}}
    ↓
scripts/generate_v5_viewer.py
    ├── H5 메타 + 이미지 + grounding JSON 통합
    └── v5_viewer.html 생성 (자체완결형)
    ↓
브라우저로 v5_viewer.html 오픈
    └── 에피소드별 BBox 오버레이 시각 확인
```

### 5.4 학습 파이프라인 (Billy 서버)

```
configs/mobile_vla_v5_exp01_discrete.json
    └── parent: mobile_vla_v4_exp01.json (상속)
        ↓
robovlm_nav/train.py
    ├── MPI hang 방지 패치 (이번 주 추가)
    ├── NavTrainer 초기화
    └── MobileVLAH5Dataset
        ├── V4/V5 H5 포맷 자동 감지 (이번 주)
        ├── stratified_split (경로 타입별 분할, 이번 주)
        ├── exclude_path_types (특정 경로 필터링, 이번 주)
        ├── grounding_prefix 자동 추가 (이번 주)
        └── 9-class 레이블링 (az 축 처리 수정, 이번 주)
            ↓
        Kosmos-2 + LoRA fine-tuning (classification head)
            ↓
        runs/v5_nav/{exp_name}/{date}/checkpoints/
```

---

## 6. V4 → V5 전환 근거 확정

| 문제 | V4 증상 | V5 대응 |
|---|---|---|
| Regression Averaging | 갈림길에서 직진 예측 | Discrete Classification으로 전환 |
| Visual Override | 지시문 무시, 장면 암기 | CLIP Norm Loss로 텍스트 정렬 강제 |
| Patch Index Repeat | Grounding 불가 | Pure HF 별도 로딩 (오프라인 검증) |
| Label 2 & 8 소실 | 회전 명령 → STOP 매핑 | 9-class 맵에서 독립 레이블로 복구 |
| 2DOF 액션 | R/T 키 회전 표현 불가 | 3DOF `[lx, ly, az]`로 확장 |

### V4 vs V5 전체 설계 차이 (research.md 기반)

`research.md` (2026-04-08 작성)에서 정리한 두 단계의 큰 차이:

| 항목 | V4 | V5 (Phase 2) |
|---|---|---|
| **학습 데이터** | 다양한 환경 151+ 에피소드 | 9개 Canonical Path 반복 (목표 150+) |
| **목표** | 일반화 능력 | Gray Basket 정밀 제어 (95% 성공률) |
| **액션 스타일** | Forward/Curved/Diagonal 혼합 | Centering + Body Rotation + STOP 특화 |
| **서버 역할** | 순수 추론 엔진 | 추론 + 수집 + 검증 + 학습 지원 통합 플랫폼 |
| **피드백** | 이미지만으로 판단 | 거리/각도 기반 정밀 제어 (향후) |

### Stratified Split 도입 배경 (plan.md 기반)

기존 `nav_h5_dataset_impl.py`의 split 로직에 치명적 버그가 있었다:

```python
# 기존 (버그): config의 train_split 파라미터를 무시하고 0.8 하드코딩
split_idx = int(num_episodes * 0.8)
```

파일명 정렬 기준으로 앞 80%가 train이 되다 보니, **val 세트에 left_path 에피소드가 0개** 들어가는 문제 발생. 학습은 되는데 검증이 의미 없어지는 상황.

V5 Exp02에서 straight_path 제거(30 에피소드, left 15 + right 15만 사용) 시 실제 분포:

| 클래스 | 개수 | 비율 | 비고 |
|---|---|---|---|
| STOP | 0 | 0% | 데이터 없음 |
| FORWARD | 240 | 44.4% | 경로 진입/후반부 |
| LEFT | 15 | 2.8% | 희귀 |
| RIGHT | 15 | 2.8% | 희귀 |
| FWD+L | 135 | 25.0% | |
| FWD+R | 135 | 25.0% | |

이를 해결하기 위해 stratified split 구현: 경로 타입(straight/left/right)별로 독립적으로 train/val 분할 → val에 left/right 균등 포함 보장.

**class_weights 설계**:
```
STOP(0):    5.0  — 데이터 없지만 미래 확장성
FORWARD(1): 0.3  — 44% 존재, 강하게 억제
LEFT(2):    4.0  — 2.8% 희귀 클래스
RIGHT(3):   4.0  — 2.8% 희귀 클래스
FWD+L(4):  2.0  — 25% 존재, 중간
FWD+R(5):  2.0  — 25% 존재, 중간
```

### V5 실험 진행 현황

| 실험 | 상태 | val_loss | 주요 결과 |
|---|---|---|---|
| Exp01 (기본 분류) | 완료 | 2.270 | 직진 편향 발생 (클래스 불균형) |
| Exp02 (회전 특화/no_straight) | 완료 | 2.210 | 회전 비율 향상 |
| Exp03 (CLIP Norm) | 진행 중 | — | 텍스트-액션 정렬 강제 |

---

## 7. 오프라인 평가 결과 (PM/DM)

### 7.1 평가 방법

`scripts/test_v5_pm_dm.py` — val 데이터셋 전 시퀀스에 대해 모델 forward 후 argmax 예측과 GT 레이블을 비교.  
`scripts/test_v5_ckpt_compare.py` — 체크포인트별로 동일 에피소드를 프레임 단위(t=0~17)로 출력해 비교.

- **평가 데이터**: val split (train_split=0.85 기준) — left_path 3개 + right_path 3개 에피소드 (총 102 sequences)
- **메트릭**: PM (Perfect Match) = 예측 클래스 == GT 클래스 비율

### 7.2 체크포인트별 PM 비교 결과

#### `test_v5_pm_dm.py` 결과 (`/tmp/v5_eval_log.txt`)

**Exp01 best** (epoch=05, val_loss=2.270) — full data + straight 포함:

| 클래스 | GT 수 | 정답 수 | 정확도 |
|---|---|---|---|
| STOP | 0 | 0 | — |
| **FORWARD** | **75** | **75** | **100%** |
| LEFT | 0 | 0 | — |
| RIGHT | 0 | 0 | — |
| FWD+L | 28 | 0 | 0% |
| FWD+R | 7 | 0 | 0% |
| **전체 PM** | **110** | **75** | **68.18%** |

→ FORWARD만 맞히고 FWD+L, FWD+R을 전부 FORWARD로 예측. 전형적인 직진 편향.

#### `test_v5_ckpt_compare.py` 결과 (`/tmp/v5_ckpt_compare_result.json`)

3개 체크포인트 × 6개 에피소드 (left 3, right 3) × 17 frames:

| 체크포인트 | 전체 PM | 특이사항 |
|---|---|---|
| **exp01_best** (val=2.270) | **47.1%** | FORWARD만 예측 — FWD+L/R 모두 FORWARD로 collapse |
| **exp02_best** (val=2.210, no-straight) | **50.0%** | FWD+L 11.1% 회복 시작 (3/27 정답) |
| **exp02_last** (마지막 epoch) | **26.5%** | FWD+L 편향으로 swing — FORWARD를 전부 FWD+L로 잘못 예측 |

프레임 단위 분석 (exp01_best, right_path 에피소드):
```
t=00  GT=LEFT    PRED=SKIP(zero)  ⏭  ← 첫 프레임 zero action
t=01  GT=FWD+L   PRED=FORWARD     ❌
t=02  GT=FWD+L   PRED=FORWARD     ❌
t=03  GT=FWD+L   PRED=FORWARD     ❌
t=04  GT=FWD+L   PRED=FORWARD     ❌
t=05  GT=FWD+L   PRED=FORWARD     ❌
t=06  GT=FORWARD PRED=FORWARD     ✅
t=07  GT=FORWARD PRED=FORWARD     ✅
t=08  GT=FORWARD PRED=FORWARD     ✅
t=09  GT=FORWARD PRED=FORWARD     ✅
t=10  GT=FWD+R   PRED=FORWARD     ❌
t=11  GT=FWD+R   PRED=FORWARD     ❌
t=12  GT=FWD+R   PRED=FORWARD     ❌
t=13  GT=FWD+R   PRED=FORWARD     ❌
t=14  GT=FORWARD PRED=FORWARD     ✅
...
```

→ 경로 초반(t=01~05, 대각선 접근) + 경로 후반(t=10~13, 우측 보정) 구간에서 전부 FORWARD로 collapse. 직진 구간(t=06~09, t=14~17)만 정답.

#### `rollout` 결과 (`/tmp/v5_rollout_result.json`)

에피소드 단위 롤아웃 시뮬레이션 (right_path 4 + left_straight 4 에피소드):

| 에피소드 유형 | PM |
|---|---|
| right_path × 4 | 47.1% (모두 동일) |
| **left_straight × 4** | **100.0%** |
| **전체 평균** | **73.5%** |

→ left_straight (직선 경로)는 FORWARD만 나오므로 100% 달성. right_path는 여전히 FWD+R 구간 실패.

### 7.3 해석 및 시사점

**현재 상태**: exp02_best(val_loss=2.210)가 가장 좋은 체크포인트.
- FORWARD 정확도: 100% (직진 구간은 완벽)
- FWD+L 정확도: 11.1% (시작은 했음)
- FWD+R 정확도: 0% (아직 학습 안 됨)
- LEFT/RIGHT: 0% (val 세트에 해당 GT 없음 — 제자리 회전은 경로 초반 1프레임뿐)

**exp02_last가 오히려 나빠진 이유**: class_weight로 FWD+L을 너무 강하게 올리다 보니 FORWARD까지 FWD+L로 예측하는 역방향 편향 발생. best checkpoint가 학습 중간에 존재하는 전형적인 패턴.

**Exp03(CLIP Norm) 기대**: 텍스트(`left` vs `right`)와 액션 임베딩을 명시적으로 정렬하면 FWD+L vs FWD+R 구분이 개선될 것으로 기대.

---

## 8. 전체 변경 파일 목록

### 커밋된 변경사항

| 파일 | 변경 유형 | 내용 |
|---|---|---|
| `docs/analysis/vla_perception_audit_v4.md` | 수정 | Backbone Blindness 증거 요약 |
| `docs/analysis/vla_perception_live_dashboard.html` | 수정 | 대시보드 대폭 간소화 |
| `scripts/gradio_data_collector.py` | 신규 | Gradio 기반 데이터 수집 UI (464줄) |
| `scripts/gradio_offline_h5_analyzer.py` | 신규 | 오프라인 H5 분석기 (199줄) |
| `scripts/h5_to_images.py` | 신규 | H5→이미지 변환기 (51줄) |
| `scripts/upload_to_prof_server.py` | 신규 | 교수님 서버 업로드 스크립트 (86줄) |
| `scripts/gradio_inference_dashboard.py` | 수정 | BBox 시각화 및 Trajectory Plot 추가 |
| `ROS_action/mobile_vla_data_collector.py` | 수정 | End-state 분류 및 동기화 개선 |
| `ROS_action/.../core_replay_db.json` | 신규 | 150 에피소드 DB (810줄) |
| `docs/V5_DATA_COLLECTION_REPORT.md` | 신규 | 수집 현황 및 분석 리포트 |
| `docs/grounding_briefing.md` | 신규 | Grounding 기술 브리핑 |
| `docs/V4_to_V5_Experiment_Journey.md` | 신규 | V4→V5 실험 여정 전체 기록 |

### 미커밋 변경사항 (로컬)

| 파일 | 변경 유형 | 내용 |
|---|---|---|
| `robovlm_nav/serve/inference_server.py` | 수정 | 3DOF 확장, 9-class 맵, 경로 정규화, BF16→FP16, V5 기본값 |
| `robovlm_nav/datasets/nav_h5_dataset_impl.py` | 수정 | V4/V5 포맷 자동감지, stratified_split, exclude_path_types, az 레이블 수정 |
| `robovlm_nav/train.py` | 수정 | MPI hang 방지 패치 |
| `scripts/inference_logger.py` | 수정 | 경로 하드코딩 제거 |
| `scripts/run_v5_grounding.py` | 신규 | Pure HF grounding 전수 실행 |
| `scripts/generate_v5_viewer.py` | 신규 | HTML 뷰어 생성기 |
| `scripts/test_v5_*.py` (4종) | 신규 | V5 추론/롤아웃/비교 테스트 |
| `scripts/monitor_dashboard.py` | 신규 | 학습 모니터링 |
| `scripts/analysis/analyze_v5_distribution.py` | 신규 | 클래스 분포 분석 |
| `configs/mobile_vla_v5_exp01_discrete.json` | 신규 | V5 Exp01 설정 |
| `configs/mobile_vla_v5_exp02_clip_norm.json` | 신규 | V5 Exp02 CLIP Norm 설정 |
| `configs/mobile_vla_v5_exp02_no_straight.json` | 신규 | V5 Exp02 직진 제외 설정 |
| `ROS_action/v5_data_bak/v5_viewer.html` | 신규 | 에피소드 브라우저 (902줄) |
| `ROS_action/v5_data_bak/vla_analyzer_docs.html` | 신규 | 분석 문서 포털 (923줄) |
| `.vlms/kosmos-2-patch14-224/` (5파일) | 삭제 | HF 캐시로 이전 |

---

## 9. 다음 주 예정 작업

- [ ] 미커밋 변경사항 커밋 (특히 `inference_server.py`, `nav_h5_dataset_impl.py`)
- [ ] V5 Exp03 (CLIP Norm) 학습 결과 분석
- [ ] 150 에피소드 기반 Exp04 이상 설계
- [ ] `target_left_straight_path` 시나리오 추가 수집 (현재 4개로 부족)
- [ ] `v5_grounding.json` 기반 데이터 품질 검증
- [ ] 교수님 데모 준비

---

*작성: Claude Code / 2026-04-10*
