--- language: - ko - en library_name: pytorch license: other pipeline_tag: feature-extraction tags: - online-handwriting - mathematical-expression-recognition - trajectory - temporal-convolution - on-device - pytorch --- # AIFlow Math Ink 0.6 AIFlow Math Ink 0.6은 수학 필기를 **이미지보다 point/stroke sequence로 먼저 처리하는** 온디바이스 연구 모델이다. 원본 touch event를 보존하면서 모델 입력만 6Hz canonical tap으로 재표본화하고, 각 고립 기호의 378-class top-k와 visual-family 확률을 반환한다. 현재 공개본은 seed 17·31·47의 연구 checkpoint와 grouping/behavior head를 포함한다. 전체 수식 LaTeX decoder나 완성된 Android LiteRT 배포본은 아니다. ![AIFlow Math Ink 0.6 architecture](assets/architecture.svg) ## 핵심 입력 계약 ![Stroke encoding](assets/stroke_encoding.svg) 실제 펜 입력은 다음 순서로 처리한다. 1. 기기의 모든 touch event를 원본 timestamp와 함께 메모리에 보존한다. 2. stroke별 시작점·끝점·pen-up을 필수 anchor로 남긴다. 3. 모델용 temporal view만 6Hz로 재표본화한다. 4. 종횡비를 보존해 128×128 ink space에 중앙 정규화한다. 5. 최대 128 event, 19개 feature를 `128×19` tensor로 만든다. 19개 channel: ```text shape_x, shape_y, canvas_x, canvas_y, direction_x, direction_y, curvature, pen_up, stroke_progress, aspect_ratio, bbox_top, bbox_bottom, bbox_height, center_y, baseline_available, time_delta, speed, missing_mask, source_modality ``` - timestamp가 실제로 있으면 `timestamp_mode=observed` - 정적 이미지처럼 시간이 없으면 `timestamp_mode=canonical`, `missing_mask=1` - 추정 시간을 실제 관측 시간처럼 저장하지 않는다. ## 모델 구조 ```text Online stroke → 6 Hz canonical taps (128×19) → online dual-TCN adapter ────────────────────────────────────────────┐ │ Raster 128×128 │ → spatial encoder │ → causal virtual-trajectory decoder │ → top-4 stroke hypotheses │ ────────────────────────────────────────────┤ ▼ shared residual TCN (hidden=128) ├─ exact head: 378 labels ├─ family head: 324 families └─ top-k probabilities Local formula context + stroke tensor → 49-d context MLP + stroke TCN → identifier_lower / identifier_upper / multiply_operator Segmentation lattice geometry → boundary behavior head → candidate crosses a symbol boundary? ``` Raster 경로도 최종 label shortcut을 사용하지 않는다. 이미지에서 top-4 virtual stroke를 만든 후 동일 trajectory encoder로 다시 인식한다. ## 공개 checkpoint 각 seed는 세 파일로 구성된다. ```text models/ seed17/ base_378.pt online_adapter.pt behavior_role_head.pt seed31/ ... seed47/ ... auxiliary/ boundary_auxiliary_head.pt seed17/boundary_joint_delta.pt seed31/boundary_joint_delta.pt seed47/boundary_joint_delta.pt artifacts/ boundary_behavior_guard.joblib ``` | 파일 | 역할 | 주요 계약 | |---|---|---| | `base_378.pt` | 공통 trajectory/raster base | hidden 128, top-4 hypothesis, 378 exact, 324 family | | `online_adapter.pt` | 실제 온라인 stroke 보정 | `dual_tcn_v3`, `top4-skeleton-128x19` | | `behavior_role_head.pt` | `x/X/×` 역할 문맥 | 128×19 stroke + 49 context feature | | `boundary_behavior_guard.joblib` | 잘못된 다기호 병합 억제 | geometry 17 feature, threshold 0.5, weight 6 | | `boundary_auxiliary_head.pt` | shared embedding용 P-track boundary smoke | 384→1, main exact/family head 비퇴행 | | `boundary_joint_delta.pt` | seed별 제한 joint fine-tuning delta | last TCN + attention + exact/family/boundary heads | 세 seed teacher를 그대로 모바일에 넣는 것이 최종 목표는 아니다. release 경로는 seed ensemble을 하나의 student로 distillation한 뒤 LiteRT INT8/FP16을 비교하는 것이다. ## Checkpoint metadata 확인 아래 코드는 network를 실행하지 않고 checkpoint 계약을 확인한다. ```python from pathlib import Path import torch root = Path("models/seed17") base = torch.load(root / "base_378.pt", map_location="cpu", weights_only=False) adapter = torch.load(root / "online_adapter.pt", map_location="cpu", weights_only=False) role = torch.load(root / "behavior_role_head.pt", map_location="cpu", weights_only=False) print(base["model_version"]) print(base["max_events"], base["sample_hz"]) print(len(base["feature_names"]), len(base["exact_labels"]), len(base["family_labels"])) print(adapter["adapter_architecture"], adapter["feature_contract"]) print(role["role_labels"], role["context_features"]) ``` `state_dict`를 실제 module에 적재하려면 이 저장소의 snapshot 코드뿐 아니라 전체 AIFlow 연구 코드가 필요하다. checkpoint 내부 경로는 lineage 기록이며 로컬 절대경로에 의존해 추론하지 않는다. ## 현재 성능 ### 378-label trajectory classifier 최신 online case-context seed 17 기준: | split | top-1 | top-5 | family top-1 | |---|---:|---:|---:| | writer validation, 4,261 samples | 86.13% | 99.48% | 92.94% | | paired writer-disjoint test, 3,782 samples | 82.87% | 97.73% | 91.22% | Paired source는 HWRT 내부 writer hash split과 UJI Pen v1/v2 writer-disjoint 계약을 사용했다. HWRT official test는 거대 writer 중복 때문에 제외했다. ### 수식 행동 head 정답 symbol grouping 이후의 CROHME 조건부 역할 평가: | 지표 | 3-seed 평균 | |---|---:| | role accuracy | 93.33% | | macro-F1 | 76.41% | | lowercase identifier recall | 95.66% | | uppercase identifier recall | 55.91% | | multiplication recall | 88.89% | | ECE | 4.88% | ### Grouping boundary head | 지표 | 이전 | boundary 적용 | |---|---:|---:| | CROHME exact partition | 60.04% | 60.25% | | pair-F1 | 91.07% | 91.26% | | overmerge formula rate | 21.72% | 20.49% | ### P boundary shared-state 정정 초기 공개 P delta는 `online_adapter.pt`의 `shared_state_dict`를 적용하지 않은 loader 결함을 상속했다. 이 때문에 강한 main encoder를 빠뜨린 낮은 기준선과 비교했으며, 해당 auxiliary/joint checkpoint와 성능 주장을 철회했다. 올바른 합성 순서인 `base → adapter shared state → modality adapter → optional head`로 세 seed를 다시 학습한 결과는 다음과 같다. | paired proxy test | 정정된 main baseline | joint 결과 | 변화 | |---|---:|---:|---:| | exact top-1 | **83.03%** | 82.81% | -0.22%p | | family top-1 | **91.37%** | 90.88% | -0.49%p | | single-symbol recall | — | 92.83% | 95% floor 실패 | | cross-boundary recall | — | 98.47% | 통과 | 세 seed 모두 release gate를 실패했으므로 joint delta는 배포하지 않는다. 현재 유효한 main 구성은 seed별 `base_378.pt + online_adapter.pt`이며 adapter 안의 `shared_state_dict`를 반드시 먼저 적용해야 한다. Joint delta 없는 main+shadow auxiliary device stress에서 최악 exact/family 하락은 affine 변형의 -0.98%p/-1.08%p였다. Software stress는 통과했지만 clean single recall이 91.42~94.50%이므로 auxiliary head도 제품 채택 대상이 아니다. ### Composite torch.export Export 그래프는 이제 base-only가 아니라 online/raster modality adapter와 shared state를 포함한다. 세 seed 모두 각 경로 대표 입력 76개에서 eager 대비 top-1 100% 일치, 최대 logit 절대오차 0.0을 기록했다. Seed-17의 online/raster `.pt2` 합계는 18,657,779 bytes다. `.pt2`는 Android용 `.tflite`가 아니다. LiteRT Torch 0.9.1 변환과 Android runtime parity는 아직 완료되지 않았으므로 `litert_exported=false`를 유지한다. ## 출력 범위 의도한 모바일 API: ```text recognizeOnline(strokes, canvas) → SymbolResult recognizeRaster(bitmap) → SymbolResult SymbolResult: candidates[{token, probability}] confidence modelVersion latencyMs ``` 이미지, raw stroke, virtual stroke는 서버 payload로 전송하지 않는다. virtual hypotheses는 로컬 debug에서만 노출한다. ## 알려진 한계 - 378-label paired writer/device-disjoint top-1 목표 92%에 아직 미달한다. - 전체 수식 LaTeX, Tray decoder, gridding은 0.7 범위다. - uppercase 역할 recall과 `O/0`, styled-letter hard family가 남은 병목이다. - raster virtual-stroke 경로는 digit/Greek slice에서는 개선됐지만 378-label release gate를 통과하지 못했다. - boundary/behavior head는 CROHME R-track 학습물이므로 제품 weight로 distill할 수 없다. - P boundary auxiliary/joint는 shared-state 정정 후 세 seed release gate를 실패해 checkpoint를 철회했다. - Device stress는 채택되지 않은 shadow auxiliary의 software perturbation 결과이며 실제 stylus/device-disjoint 성능 근거가 아니다. - Android LiteRT 변환, PyTorch/LiteRT logit parity, 저가·중급·고급 기기 benchmark가 남아 있다. ## 데이터와 권리 이 저장소에는 원본 필기 데이터, 이미지, OCR cache를 포함하지 않는다. - HWRT database: ODbL-1.0 취급, attribution 및 파생 database 검토 필요 - UJI/Pendigits 계열: 각 원 출처 조건을 별도로 따라야 함 - CROHME/MathWriting: 비상업 R-track 평가 또는 연구 head에만 사용 - checkpoint와 report는 현재 **research-only / non-commercial** 공개물이다. - 상용 checkpoint는 권리 검토를 통과한 P-track 데이터로 처음부터 재학습해야 한다. 이 공개는 제품 정확도·상용 배포 가능성·LiteRT 호환성을 보증하지 않는다. ## 보안 PyTorch checkpoint와 joblib/pickle은 신뢰할 수 없는 출처에서 로드하면 임의 코드를 실행할 수 있다. `MANIFEST.json`의 SHA-256을 확인하고 신뢰된 환경에서만 사용한다. ## 연구 자료 - 전체 기술 보고서: [`reports/RESEARCH_REPORT.md`](reports/RESEARCH_REPORT.md) - boundary 학습 결과: [`reports/boundary_behavior_guard_report.json`](reports/boundary_behavior_guard_report.json) - 3-seed 행동 head: [`reports/behavior_role_3seed_summary.json`](reports/behavior_role_3seed_summary.json) - P boundary 정정 3-seed: [`reports/p_boundary_joint_sharedfix_3seed_summary.json`](reports/p_boundary_joint_sharedfix_3seed_summary.json) - 정정 main device stress: [`reports/p_boundary_device_stress_sharedfix_3seed.json`](reports/p_boundary_device_stress_sharedfix_3seed.json) - Seed-17 composite export: [`exports/seed17/export_manifest.json`](exports/seed17/export_manifest.json) - 실제 P formula schema: [`contracts/aiflow_p_formula_v1.schema.json`](contracts/aiflow_p_formula_v1.schema.json) - 파일 checksum: [`MANIFEST.json`](MANIFEST.json)