| # AIFlow Math Ink 0.6 — 행동 문맥 모델 전환 보고서 |
|
|
| ## 결론 |
|
|
| 0.6의 남은 정확도는 고립 기호 분류기만 더 학습해서 해결되지 않는다. 동일한 교차획이 `x`, `X`, `\times`가 될 수 있고, 동일한 대문자도 분자·분모·첨자처럼 축소된 수식 슬롯에서는 식 전체 높이와 비교할 수 없기 때문이다. |
|
|
| 따라서 다음 책임 분리를 채택한다. |
|
|
| 1. trajectory/raster 신경망은 형태 후보 top-k와 확률을 만든다. |
| 2. Tray·관계 레이어는 분자·분모·첨자·피개항 등 로컬 좌표계를 만든다. |
| 3. 행동 문맥 레이어는 획 행동, 로컬 크기, 이웃 후보, 간격, Tray 역할을 사용해 같은 형태군 안의 의미만 재점수화한다. |
| 4. 수식 gate와 LaTeX 문법은 그 뒤에서 작동한다. |
| 5. 행동 증거가 부족하면 원래 top-k를 그대로 반환한다. |
|
|
| ```text |
| touch / raster |
| → 128×128 정규화·trajectory encoder |
| → 기호 top-k |
| → segmentation lattice·Tray·2D relation |
| → 행동 문맥 모델 |
| ├─ 로컬 슬롯별 대소문자 |
| ├─ x / X / \times 의미 역할 |
| └─ O / o / 0 등 hard-family 후보 보존 |
| → math/non-math gate |
| → Formula Expert·LaTeX |
| ``` |
|
|
| 행동 모델은 glyph 인식기를 덮어쓰는 새 OCR이 아니다. 신경망이 보존한 후보군 안에서 사용자가 그 기호를 어떤 역할로 배치했는지 추정하는 작은 후단 모델이다. |
|
|
| ## 지금까지 확인된 연구 결과 |
|
|
| ### Online/shared encoder |
|
|
| - 승인 HWRT·UJI Pen v1/v2·Pendigits 기반 128-step·19-channel 경로를 구축했다. |
| - seed 17·31·47 paired test 평균은 exact top-1 `83.00%`, top-5 `97.78%`, family top-1 `91.37%`다. |
| - logit ensemble은 exact `83.82%`, top-5 `98.04%`, casefold `90.93%`, shape-family `93.02%`다. |
| - exact 오류의 `43.95%`가 대소문자만 다른 오류이고 `56.86%`가 같은 shape family 안의 오류다. |
| - 이는 형태 후보는 상당 부분 살아 있으나 최종 의미 배정이 병목이라는 근거다. |
|
|
| ### Raster→virtual stroke |
|
|
| - top-4 virtual stroke와 shared encoder 재입력 경로를 구현했다. |
| - 후보 다양성은 개선됐지만 최종 raster 정확도는 release gate에 크게 미달했다. |
| - 3-seed 차이가 작아 seed variance보다 vectorizer와 online feature 분포 정렬이 주 병목이다. |
| - 따라서 행동 문맥은 raster vectorizer 병목을 감추는 대체물이 아니며, 두 문제를 별도 gate로 측정해야 한다. |
|
|
| ### 대소문자 문맥 |
|
|
| - `k/p/y`처럼 획 자체가 다른 쌍은 exact head가 담당한다. |
| - `c/o/s/u/v/w/x/z`처럼 주 차이가 상대 높이인 쌍만 context head가 담당한다. |
| - 기존 행 전체 기준 proxy는 validation `70.31→72.14%`, test `62.19→67.34%`로 개선됐지만, 연속 수식 P 데이터의 제품 근거는 아니다. |
| - 행 전체 기준은 축소된 분수·첨자 안의 대문자를 소문자로 내릴 수 있으므로 이번 구현에서 로컬 슬롯 기준으로 교체했다. |
|
|
| ### 구조·Tray |
|
|
| - 분수선은 완성된 분자·분모가 있을 때만 `FRACTION` Tray를 만든다. |
| - 근호는 radicand와 선택적 degree, 적분은 integrand와 선택적 upper/lower/differential을 분리한다. |
| - `=` 인식은 좌우 식의 완성 여부와 독립적으로 보존한다. |
| - Tray는 기호 이름을 결정하지 않고 기호가 놓인 수학적 슬롯과 로컬 기준선만 제공한다. |
|
|
| ## `x`, `X`, `\times` 판단 |
|
|
| `2 ? 3`이라는 이웃 정보만으로 가운데를 곱하기로 확정할 수 없다. 사용자가 변수 `x` 또는 문자 `X`를 숫자 사이에 쓸 수도 있기 때문이다. 이 모호성은 문법만으로 완전히 제거할 수 없다. |
|
|
| 현재 행동 레이어는 다음 원칙을 적용한다. |
|
|
| - 양옆 숫자는 보조 feature로만 사용한다. |
| - 교차획 시각 확률, infix 점유, 연산자형 간격, 명시적 operator context 중 하나 이상의 구조 증거가 없으면 no-op이다. |
| - 조건을 통과해도 `x/X/\times` family의 총 logit 질량을 보존한 채 `\times` prior만 더한다. |
| - 원 신경망의 `X` 증거가 충분히 강하면 행동 prior가 뒤집지 못한다. |
| - 본질적으로 모호한 입력은 top-k와 낮은 confidence로 반환하며 거짓 확정을 하지 않는다. |
|
|
| 향후 학습 feature는 다음과 같다. |
|
|
| - 원 신경망의 family logits와 entropy |
| - 앞뒤 token의 digit/identifier/operator 확률 질량 |
| - 좌우 간격, 중심선 정렬, 로컬 높이비 |
| - 교차획의 방향·획순서·pen-up·속도·시간 간격 |
| - segmentation lattice의 infix 점수 |
| - Tray ID·slot·관계 edge |
| - observed/canonical timestamp와 missing mask |
|
|
| 학습 target은 원 glyph 이름 하나가 아니라 `identifier_lower`, `identifier_upper`, `multiply_operator`, `other/abstain` 의미 역할로 둔다. |
|
|
| ## 분수·첨자 안의 대소문자 |
|
|
| 대문자 여부는 식 전체 canvas 높이와 비교하지 않는다. |
|
|
| - main row |
| - fraction numerator |
| - fraction denominator |
| - superscript/subscript |
| - root degree/radicand |
| - integral upper/lower/integrand |
|
|
| 각 슬롯을 별도의 `local_context_id`로 묶고, 같은 슬롯의 신뢰 가능한 숫자·형태 문자만 높이 anchor로 사용한다. 슬롯에 anchor가 없으면 case logits를 보존한다. 이 원칙은 작은 분자 안의 `X`를 main row보다 작다는 이유로 `x`로 강등하는 오류를 막는다. |
|
|
| ## 구현 |
|
|
| `behavior_context06.py`를 추가하고 `math_context_layer.py` 앞단에 연결했다. |
|
|
| 공개 입력 계약은 record별 다음 선택 feature를 사용한다. |
|
|
| ```json |
| { |
| "behavior_context": { |
| "local_context_id": "fraction:f1:numerator", |
| "cross_visual_probability": 0.91, |
| "infix_occupancy": 0.94, |
| "operator_spacing_score": 0.82, |
| "explicit_operator_context": 0.0 |
| } |
| } |
| ``` |
|
|
| Tray가 있으면 `tray_id + tray_slot`, 일반 행이면 `baseline_group`, 아무 정보도 없으면 `main`을 사용한다. `FRACTION.numerator/denominator`와 구조 관계의 `SUPERSCRIPT/SUBSCRIPT/ABOVE/BELOW/CONTAINS`는 별도 수작업 없이 로컬 context로 자동 변환한다. |
|
|
| 회귀 검증은 다음 네 항목을 고정했다. |
|
|
| 1. 축소된 분자 안의 `X` 보존 |
| 2. 숫자 이웃만으로 `\times` 강제 금지 |
| 3. 획·infix·간격 증거가 겹친 애매한 교차획의 소프트 `\times` 선택과 family 질량 보존 |
| 4. 강한 원본 `X` logit 보존 |
|
|
| 관련 context 회귀 테스트는 `15/15`, 전체 Python 회귀 테스트는 `264/264` 통과했다. |
|
|
| ## 행동 모델 파인튜닝 계획 |
|
|
| ### 단계 A — shadow feature 수집 |
|
|
| 기존 모델 weight는 고정하고 각 기호에 top-k, geometry, stroke behavior, Tray slot을 붙여 저장한다. 이미지나 원 stroke를 서버로 보내지 않는 온디바이스 원칙은 유지한다. 연구 데이터에는 원본 provenance와 writer/source/device split을 보존한다. |
|
|
| ### 단계 B — 작은 role head |
|
|
| 행동 head는 모바일 student에 붙일 수 있는 작은 TCN/MLP로 시작한다. pixel encoder를 다시 크게 학습하지 않고 다음 multi-task를 사용한다. |
|
|
| - cross semantic role CE |
| - local case role CE |
| - Tray slot consistency loss |
| - 원 family logit 보존 KL |
| - abstain/calibration loss |
|
|
| 초기 loss weight는 validation sweep 대상이며 아직 배포 상수로 고정하지 않는다. |
|
|
| ### 단계 C — 실제 수식 검증 |
|
|
| 합성 조합은 데이터 증강으로만 사용한다. 최종 선택은 writer/device/source-disjoint 연속 수식에서 수행한다. |
|
|
| - `x/X/\times` role top-1·top-k와 confusion matrix |
| - local slot별 case exact |
| - unchanged-safe rate: 행동 증거가 없는 표본의 원 top-1 보존율 |
| - family oracle 대비 회수율 |
| - ECE와 abstain coverage/accuracy |
| - main/numerator/denominator/script slice 최저 성능 |
|
|
| 비상용·용도 제한 데이터는 연구 평가에만 사용하고 배포 checkpoint 학습에는 포함하지 않는다. |
|
|
| ## 현재 판단 |
|
|
| - 행동 모델 방향 채택: **예** |
| - 기존 case layer를 로컬 슬롯 행동 layer로 교체: **예** |
| - `x/X/\times` hard rewrite: **아니오** |
| - CROHME R-track 조건부 역할 정확도 상승 확인: **예** |
| - 이를 제품 수치로 주장: **아니오** |
| - 0.6 상용 카나리: **아직 아니오** |
|
|
| ## 첫 GPU 행동학습 결과 |
|
|
| 기존 3-seed glyph teacher를 각각 동결하고 `128×19 stroke TCN + 49차 문맥 MLP` 행동 head를 GTX 1650 CUDA에서 학습했다. CROHME 2012 `trainData`는 writer 기준 fit 1,063식/validation 275식으로 나눴고 `testDataGT` 488식은 공식 held-out으로만 한 번 평가했다. |
|
|
| 역할 분모는 다음과 같다. |
|
|
| | split | lower identifier | upper identifier | multiply operator | |
| |---|---:|---:|---:| |
| | fit | 1,573 | 99 | 177 | |
| | validation | 401 | 22 | 44 | |
| | official test | 568 | 31 | 36 | |
|
|
| head 입력에는 다음을 사용했다. |
|
|
| - seed별 frozen 0.6 teacher의 lower/upper/times logits |
| - target 원본 stroke의 순서·방향·pen-up을 보존한 128×19 sequence |
| - 좌우 이웃의 정답 label이 아닌 teacher 예측 역할 확률 |
| - bbox, 간격, 중심선, 로컬 높이·폭 비율 |
| - 19개 cross visual feature와 stroke/point 수 |
|
|
| | 지표 | seed 17 | seed 31 | seed 47 | 평균 ± 표준편차 | |
| |---|---:|---:|---:|---:| |
| | teacher role accuracy | 74.33% | 60.79% | 60.94% | 65.35 ± 6.35%p | |
| | behavior role accuracy | 93.07% | 93.86% | 93.07% | **93.33 ± 0.37%p** | |
| | behavior macro-F1 | 76.64% | 77.17% | 75.42% | **76.41 ± 0.73%p** | |
| | lower recall | 95.07% | 96.30% | 95.60% | 95.66 ± 0.50%p | |
| | upper recall | 61.29% | 51.61% | 54.84% | **55.91 ± 4.02%p** | |
| | multiply recall | 88.89% | 91.67% | 86.11% | **88.89 ± 2.27%p** | |
| | ECE | 4.73% | 5.09% | 4.82% | 4.88 ± 0.15%p | |
|
|
| 평균 accuracy 이득은 `+27.98%p`이고 seed 간 편차도 작아 행동 문맥 방향은 유효하다. 특히 frozen teacher가 세 seed 모두 `\times`를 0% 회수하던 조건에서 행동 head는 86.11~91.67%를 회수했다. |
|
|
| 그러나 다음 한계 때문에 제품 승격은 금지한다. |
|
|
| 1. 정답 symbol grouping 이후의 조건부 역할 평가다. segmentation 오류는 분모에 없다. |
| 2. CROHME R-track weight라 상용 checkpoint에 병합할 수 없다. |
| 3. official test의 대문자 분모는 31개뿐이며 recall 평균도 55.91%다. |
| 4. 분수·첨자 로컬 case의 실제 device-disjoint 제품 분모가 아니다. |
| 5. 세 teacher별 head는 약 75.8KB지만 distillation/온디바이스 변환 단계로 넘기지 않는다. |
|
|
| 따라서 첫 학습 결론은 `x/X/\times` 의미 역할 head 채택, 대문자 데이터 확장, predicted grouping end-to-end 재평가다. 제품용 다음 학습은 승인된 trajectory를 수학 배치로 합성한 약한 P-compatible proxy와 실제 상용 허용 연속식 데이터를 구분해 사용해야 한다. |
|
|
| ## 후속 루프 — 합성 대문자와 predicted grouping |
|
|
| ### 승인 trajectory 분모 |
|
|
| HWRT·UJI Pen v1/v2의 writer-disjoint training split에는 상대크기군 각 대·소문자가 약 90~111개씩 있다. 이를 실제 stroke 그대로 사용하고 수식 행의 높이·좌우 숫자 anchor만 합성한 P-compatible proxy를 만들었다. |
|
|
| 첫 후보는 8개 case family 1,590개를 loss weight 0.35로 추가했다. 두 번째 후보는 실제 CROHME fit 정규화 통계를 고정하고 `c/x/z` family의 대문자 284개와 소문자 77개만 weight 0.15로 추가했다. |
|
|
| | seed 17 official test | 원 behavior | 전체 proxy | targeted proxy | |
| |---|---:|---:|---:| |
| | accuracy | 93.07% | 93.39% | 93.39% | |
| | macro-F1 | 76.64% | 75.62% | 76.45% | |
| | upper recall | **61.29%** | 51.61% | 54.84% | |
| | multiply recall | 88.89% | 86.11% | 88.89% | |
|
|
| 두 후보 모두 목표인 upper recall을 악화시켰다. 고립기호를 인위적으로 키운 데이터는 실제 연속식의 대문자 행동 분포를 대체하지 못하므로 조기 기각했고 seed 31·47로 확장하지 않았다. 작은 전체 accuracy 상승을 근거로 채택하지 않는다. |
|
|
| 로컬 MathWriting 2024 excerpt는 train/valid/test 각 100식과 symbols/synthetic 각 100개가 있지만 formula InkML에 symbol-to-stroke group 정답이 없다. LaTeX 문자열과 전체 stroke만으로 대문자 역할 target을 강제 정렬하면 label·필순을 발명하게 되므로 supervised behavior 학습에 넣지 않는다. |
|
|
| ### Predicted grouping 감사 |
|
|
| 공식 test 488식의 행동 대상 635개를 validation에서 고정한 lattice selector로 다시 평가했다. |
|
|
| | grouping 경로 | 전체 target | lower | upper | `\times` | `x` exact group | |
| |---|---:|---:|---:|---:|---:| |
| | base OCR fusion | 81.89% | 79.93% | 96.77% | 100% | 75.56% | |
| | Tray/infix joint | 81.42% | 79.58% | 93.55% | 100% | 75.11% | |
| | cross-visual + Tray/infix, gap 20% | 91.02% | 90.32% | 93.55% | 100% | 88.89% | |
| | full selector, gap 50% | 93.39% | 92.96% | 93.55% | 100% | **92.22%** | |
| | **protected family selector, gap 40%** | **92.91%** | **92.43%** | 93.55% | 100% | 91.56% | |
|
|
| base에서 실패 115건 중 `x`가 110건이고, 그중 96건이 두 획 oversplit이었다. 최초 cross-visual 결합은 전체 oversplit을 98→42건으로 줄이고 `x` exact grouping을 `+13.33%p` 올렸다. 반면 cross model 없는 Tray/infix 단독은 소폭 악화돼 행동 grouping 경로로 채택하지 않는다. |
|
|
| 현재 병목은 다음처럼 분리된다. |
|
|
| 1. `x`: 최종 결합 후 grouping recall은 91.56%로 90%대를 유지하며 잔여 38건은 oversplit 26, overmerge/혼합 12다. |
| 2. `X/C`: upper 역할 grouping은 93.55%이므로 주 병목은 behavior role head와 실제 연속식 대문자 데이터다. |
| 3. `\times`: grouping 100%, conditional role recall 88.89%이므로 의미 head가 병목이다. |
| 4. grouping과 role 오류가 독립이라고 가정한 근사 end-to-end recall은 lower 88.42%, upper 52.31%, multiply 88.89%다. 이는 실측 end-to-end 수치가 아니므로 제품 지표로 사용하지 않는다. |
| 5. 전체 488식 exact partition은 60.04%이며 fraction·superscript·단일기호 overmerge가 다음 segmentation 병목이다. |
|
|
| 따라서 R-track grouping 기준선은 `equality + cross-visual + protected multistroke family + Tray/infix`, cross gap ratio `0.40`으로 갱신한다. 대문자는 합성 isolated proxy가 아니라 실제 symbol-group annotation이 있는 연속식만 다음 supervised 학습 후보로 허용한다. |
|
|
| ### x prefilter 병목과 gap validation sweep |
|
|
| 실패를 candidate 생성→cross head→exact-cover selector로 분해했다. 두 획 `x`의 lattice candidate recall은 validation/test 모두 100%이고 test cross threshold recall은 98.37%였다. 그런데 실패 후보의 cross 확률 중앙값도 0.929인데 `infix_signal=0`이었다. 원인은 cross head 앞의 bbox gap 20% prefilter가 좌우 곡선 `)(`형 x를 모델에 전달하지 않은 것이다. |
|
|
| 전역 threshold와 merge weight를 고정하고 gap ratio `0.20~0.80`만 writer-validation에서 선택했다. Validation winner `0.50`을 equality head까지 포함한 full selector의 공식 test에 적용했다. |
|
|
| | 지표 | gap 20% | **gap 50%** | 변화 | |
| |---|---:|---:|---:| |
| | validation `x` grouping | 82.31% | **88.45%** | +6.14%p | |
| | validation exact partition | 63.27% | **65.82%** | +2.55%p | |
| | validation pair-F1 | 91.00% | **91.69%** | +0.69%p | |
| | official test `x` grouping | 88.89% | **92.22%** | +3.33%p | |
| | official test exact partition | 53.69% | **54.51%** | +0.82%p | |
| | official test pair-F1 | 88.38% | **88.72%** | +0.35%p | |
|
|
| gap 50%는 이 단계에서 전 지표가 함께 올라 채택했다. 이후 같은 기준에서 threshold `0.5`, weight `10`의 공격 후보는 test `x`를 94.44%로 올렸지만 exact partition 54.51→53.89%, pair-F1 88.72→88.41%, overmerge 21.11→23.57%로 악화돼 기각했다. 다획 family boost와 결합한 validation 재선택에서는 동일 test 성능을 내는 더 좁은 gap `0.40`이 선택됐다. 최종 cross 값은 gap `0.40`, threshold `0.692841...`, infix weight `8`이다. |
|
|
| ### 다획 family must-link와 geometry 보호 |
|
|
| `sum/pi/rightarrow/neq/pm`은 candidate recall이 validation/test 모두 100%였지만 합친 OCR alias가 기존 reliable 목록과 연결되지 않거나 3개의 singleton 점수에 밀렸다. `Sigma/pi-family/arrow-family/neq/pm` 후보에만 추가 symbol signal을 주고 boost `0~6`을 writer-validation에서 선택했다. |
|
|
| | 공식 test grouping | 기준 | boost 6 + 보호 | |
| |---|---:|---:| |
| | 다섯 family 전체 | 45.19% | **82.96%** | |
| | `\sum` | 25.81% | **70.97%** | |
| | `\pi` | 40.00% | **90.00%** | |
| | `\rightarrow` | 71.43% | **89.80%** | |
| | `\neq` | 23.08% | **76.92%** | |
| | `\pm` | 25.00% | **75.00%** | |
|
|
| 무보호 boost 6은 전체식 exact 57.38%까지 올렸지만 `hookrightarrow`, 좁은 `Sigma`, 세로로 긴 `pm` 오인이 단일기호를 먹었다. Training writer-validation의 실제 geometry 범위에서 arrow `pair_gap_max≤0.50`, `Sigma width_ref≥2.0`, `pm height_ref≤2.0` guard를 만들고 ontology 밖 `hookrightarrow` alias를 제거했다. 보호 후 공식 test exact는 **60.04%**, pair-F1은 **91.07%**, overmerge formula rate는 21.72%다. Gap-only 대비 각각 +5.53%p, +2.35%p이며 overmerge 증가는 +0.61%p로 제한됐다. |
|
|
| ### 최종 잔여 segmentation 병목 |
|
|
| 최종 full selector의 488식 잔여 실패는 exact 293, oversplit 89, overmerge 72, 혼합 34다. 구조별 exact는 sqrt 68.00%, plain 63.95%, subscript 57.01%, superscript 52.75%, fraction 52.10%다. 3획 기호 correct는 34→63개로 늘었고 oversplit은 57→32개로 줄었다. |
|
|
| | label | 오류/전체 | 주 실패 | |
| |---|---:|---| |
| | `x` | 38/450 | oversplit 26, overmerge·혼합 12 | |
| | `1` | 36/482 | 주변 획과 overmerge | |
| | `-` | 36/584 | overmerge | |
| | `\sin` physical part | 35/297 | overmerge 15, oversplit 18 | |
| | `\sum` | 9/31 | oversplit 8 | |
| | `i` | 9/41 | oversplit | |
| | `F` | 7/10 | oversplit | |
| | `\div` | 7/11 | oversplit | |
|
|
| 따라서 다음 우선순위는 전역 threshold가 아니라 분수·위첨자에서 `1/-/괄호`가 이웃 Tray로 넘어가지 않게 하는 local-baseline must-not-link, 그리고 남은 `F/div/i` 다획 family다. |
|
|
| ### Local-baseline 감사와 학습형 boundary 행동 head |
|
|
| 최종 selector의 overmerge를 정답 기호 단위로 다시 연결했다. 공식 test에는 299개 overmerge event와 149개 고유 선택 후보가 있었다. 오류가 집중된 정답은 `1` 36건, `-` 36건, 괄호 53건이며 구조별로 superscript 150건, fraction 112건, subscript 96건이 겹쳐 있었다. 기존 fraction penalty는 299건 중 4건에만 신호를 냈다. |
|
|
| 두 규칙형 가드는 채택하지 않았다. |
|
|
| 1. 합친 OCR이 구성획보다 약할 때 감점하는 component competition은 validation exact를 개선하지 못하고 family grouping을 악화시켜 weight 0을 선택했다. |
| 2. base partition에서 `SUPERSCRIPT/SUBSCRIPT/ABOVE/BELOW/CONTAINS` edge를 만든 local-baseline guard는 validation 3,293개·test 5,061개 후보에 신호를 만들었지만 실제 149개 오병합 후보에는 유효 신호가 0개였다. 이미 오병합된 partition 뒤에서 관계를 만들었기 때문에 내부 경계를 복원할 수 없었다. |
|
|
| 이에 grouping 이전의 모든 다획 후보를 대상으로, 둘 이상의 정답 기호를 침범하는지를 예측하는 geometry boundary 행동 head를 fit writer에 학습했다. 72,223개 후보 중 66,842개가 boundary 양성이며, validation에서 threshold 0.5·penalty weight 6을 선택했다. |
|
|
| | 지표 | 보호 selector | boundary 행동 head | 변화 | |
| |---|---:|---:|---:| |
| | validation exact partition | 68.00% | **69.82%** | +1.82%p | |
| | validation pair-F1 | 92.98% | **93.64%** | +0.66%p | |
| | official exact partition | 60.04% | **60.25%** | +0.20%p | |
| | official pair-F1 | 91.07% | **91.26%** | +0.19%p | |
| | official overmerge formula | 21.72% | **20.49%** | -1.23%p | |
| | official `x` grouping | 91.56% | 91.33% | -0.22%p | |
| | official 다획 family grouping | 82.96% | 81.48% | -1.48%p | |
|
|
| 사전 정의한 `x` -1%p, family -2%p, pair-F1 -0.25%p 보호 gate 안에서 exact와 pair-F1이 함께 올라 R-track head로 채택한다. 다만 CROHME 정답 경계로 학습한 비상업 연구 weight이므로 제품 checkpoint·teacher·distillation에는 사용할 수 없다. 상용 이전 경로는 동일 boundary target을 P-track 연속식으로 재학습하거나 shared trajectory encoder의 auxiliary boundary loss로 옮기는 것이다. 전체 Python 회귀는 271개가 통과했다. |
|
|
| ### P-track boundary auxiliary smoke |
|
|
| > 2026-07-24 정정: 아래 최초 smoke는 adapter checkpoint의 `shared_state_dict`를 적용하지 않은 base-only 결과이므로 현재 메인 모델 성능 근거에서 철회한다. 실험 이력으로만 보존한다. |
|
|
| 별도 sklearn guard를 최종 구조로 고정하지 않고 `MathInk06Model`의 shared 384차 trajectory embedding 위에 선택적 binary boundary head를 추가했다. 기본값은 비활성이라 기존 checkpoint key와 `forward() → exact,family` 계약은 변하지 않는다. 활성 경로만 online/raster candidate별 boundary logit을 반환하며 class imbalance용 binary auxiliary loss를 제공한다. |
|
|
| CROHME weight를 이전하지 않고 승인 paired training trajectory의 실제 단일기호를 음성으로, 서로 다른 두 기호를 같은 행·위첨자·아래첨자 위치에 배치한 후보를 양성으로 합성했다. Seed-17 base와 online adapter는 동결하고 head만 학습했다. |
|
|
| | P proxy validation | 결과 | |
| |---|---:| |
| | balanced validation samples | 2,400 | |
| | validation accuracy | 97.33% | |
| | validation F1 | 97.34% | |
| | validation ROC-AUC | 99.65% | |
| | validation single-symbol recall | 97.25% | |
| | validation cross-boundary recall | 97.42% | |
| | paired-test single/boundary recall | 96.92% / 98.00% | |
| | exact/family logit max abs 변화 | 0.0 / 0.0 | |
|
|
| Validation에서 single≥95%·boundary≥85%를 동시에 요구해 threshold 0.70을 선택했고, 선택에 사용하지 않은 paired writer/device-disjoint proxy test 2,400개에서 accuracy 97.46%, F1 97.47%, AUC 99.62%를 기록했다. 이는 P-track target 재구성이 가능하다는 smoke 증거지만 실제 연속식 writer/device-disjoint 검증은 아니다. 다음 단계는 실제 P 연속식 또는 사용자 local-only pseudo formula에서 symbol boundary annotation을 확보하고 main joint fine-tuning weight를 선택하는 것이다. 전체 Python 회귀는 275개가 통과했다. |
|
|
| ### 5-layout P boundary joint — 3 seed |
|
|
| > 2026-07-24 정정: 아래 최초 joint 결과도 동일한 shared-state 로딩 누락을 상속했으므로 delta 채택을 철회한다. 공개 배포 대상에서 제거한다. |
|
|
| Smoke의 배치를 같은 행·상첨자·하첨자에서 분자/분모 수직 슬롯과 넓은 중위식 양변까지 다섯 종류로 확장했다. Seed별 base·online adapter·boundary head를 독립 초기화하고 다음 module만 열었다. |
|
|
| - `trajectory_encoder.blocks.3` |
| - `trajectory_encoder.attention` |
| - `exact_head`, `family_head`, `boundary_head` |
|
|
| Authentic 4,800개에는 exact CE·family CE·single-symbol boundary loss를 주고, balanced boundary 후보 2,400개에는 boundary loss를 줬다. Validation 채택 조건은 authentic exact/family 각 -0.5%p 이내, single/boundary recall 각 95% 이상이다. |
|
|
| | paired proxy test | seed 17 | seed 31 | seed 47 | 평균 ± 표준편차 | |
| |---|---:|---:|---:|---:| |
| | exact top-1 | 77.66% | 79.38% | 77.84% | **78.29 ± 0.77%p** | |
| | exact 변화 | +3.07%p | +3.20%p | +2.70%p | **+2.99 ± 0.21%p** | |
| | family top-1 | 86.22% | 87.70% | 86.46% | **86.80 ± 0.65%p** | |
| | family 변화 | +2.78%p | +3.54%p | +2.62%p | **+2.98 ± 0.40%p** | |
| | single-symbol recall | 96.75% | 98.25% | 97.25% | **97.42 ± 0.62%p** | |
| | cross-boundary recall | 98.92% | 97.92% | 98.67% | **98.50 ± 0.42%p** | |
| | boundary F1 | 97.86% | 98.08% | 97.97% | **97.97 ± 0.09%p** | |
|
|
| 세 seed 모두 validation과 paired test gate를 개별 통과했고 exact/family도 모든 seed에서 개선돼 P formula-layout proxy delta로 채택한다. 이는 R-track head를 distill한 결과가 아니라 승인 P trajectory와 알려진 합성 boundary만 사용한 결과다. 다만 분리된 고립기호를 배치한 proxy이므로 실제 사용자의 연속식 stroke rhythm·co-articulation·device sampling 변동은 포함하지 않는다. 제품 검증은 false이며 다음 필수 gate는 실제 P 연속식 writer/device-disjoint boundary 평가다. |
|
|
| ### 3-seed device contract stress |
|
|
| 채택 delta를 별도 학습 없이 좌표 jitter, timestamp/speed 전체 결측, raw event 절반 희소화 후 6Hz 재보간, x 1.12/y 0.88 affine 조건에서 평가했다. 최초 sparse 실험은 canonical tensor를 직접 절반 삭제해 입력 계약을 위반했으므로 폐기했고, start/end·pen-up anchor를 보존한 재보간 경로로 다시 고정했다. |
|
|
| 모든 seed·stress가 clean 대비 exact/family -3%p 이내, single/boundary recall 90% 이상 gate를 통과했다. 최악 exact 하락은 seed-47 affine `-1.53%p`, 최악 family 하락은 seed-31 affine `-1.48%p`였다. Stress 전체의 single-symbol/cross-boundary recall 최저는 각각 96.67%다. Timestamp 결측과 2배 희소 sampling은 최악 exact 하락이 각각 -0.26%p와 -0.48%p 이내였다. |
|
|
| 이는 canonical 입력 계약의 변형 내성 증거이지 실제 Android digitizer·WDDM·battery/latency 검증이 아니다. 다음 실제 gate는 raw MotionEvent를 가진 P writer/device-disjoint 연속식이다. 전체 Python 회귀는 276개가 통과했다. |
|
|
| ### Shared-state 로딩 결함 정정 및 3-seed 재학습 |
|
|
| 정식 배포 구성을 감사한 결과 `online_adapter.pt`는 외부 modality adapter뿐 아니라 공동 학습된 `trajectory_encoder`, `exact_head`, `family_head`의 `shared_state_dict`도 저장하지만, P boundary 로더는 외부 adapter만 읽고 shared update를 누락했다. 올바른 합성 순서는 다음과 같다. |
|
|
| ```text |
| base checkpoint |
| → adapter shared_state_dict |
| → modality adapter |
| → 선택적 boundary head |
| → 검증을 통과한 경우에만 joint delta |
| ``` |
|
|
| 로더를 수정하고 이를 보장하는 회귀 테스트를 추가한 뒤 auxiliary와 joint를 seed 17·31·47에서 다시 학습했다. |
|
|
| | paired proxy test | seed 17 | seed 31 | seed 47 | 평균 | |
| |---|---:|---:|---:|---:| |
| | 정정된 main baseline exact | 82.95% | 83.24% | 82.92% | **83.03%** | |
| | joint exact | 82.71% | 82.95% | 82.79% | 82.81% | |
| | exact 변화 | -0.24%p | -0.29%p | -0.13%p | **-0.22%p** | |
| | 정정된 main baseline family | 91.22% | 91.51% | 91.38% | **91.37%** | |
| | joint family | 90.77% | 90.96% | 90.90% | 90.88% | |
| | family 변화 | -0.45%p | -0.56%p | -0.48%p | **-0.49%p** | |
| | single-symbol recall | 91.58% | 94.67% | 92.25% | 92.83% | |
| | cross-boundary recall | 98.50% | 98.75% | 98.17% | 98.47% | |
|
|
| 세 seed 모두 official proxy gate를 실패했다. Seed 17·47은 single-symbol 95% floor를, seed 31은 single-symbol floor와 family -0.5%p 비퇴행 조건을 실패했다. 따라서 corrected joint delta는 전부 기각하며 현재 유효 모델은 `base + shared_state_dict + modality adapter`다. 이전 78.29% 결과는 강한 main encoder를 빠뜨린 낮은 기준선과 비교한 것이므로 무효다. |
|
|
| Joint delta 없이 정정된 main과 새 auxiliary head만 대상으로 device stress를 다시 수행했다. Clean exact/family 평균은 각각 83.03%/91.37%다. 모든 software stress가 -3%p와 boundary recall 90% floor를 통과했고, 최악 exact/family 하락은 affine에서 -0.98%p/-1.08%p였다. 이는 auxiliary shadow의 입력 내성만 의미하며 head의 clean single recall이 91.42~94.50%라 release 채택 근거는 아니다. |
|
|
| ### Composite torch.export |
|
|
| 기존 export script도 base checkpoint만 읽어 실제 main adapter를 우회했다. Online은 `online adapter → shared trajectory encoder`, raster는 `virtual top-4 → raster adapter → shared trajectory encoder`가 그래프 안에 포함되도록 수정했다. Dual adapter는 입력 데이터에 따른 런타임 분기 대신 online/raster branch를 export 시점에 고정한다. |
|
|
| Seed 17·31·47 모두 online/raster 각각 대표 입력 76개에서 eager 대비 top-1 100% 일치, 최대 logit 절대오차 0.0으로 strict `torch.export` gate를 통과했다. 기존 출력 경로를 재사용할 때 오래된 `.pt2`가 남는 문제를 발견해 `.part → atomic replace`로 수정했고, fresh seed-17 두 graph 합계는 24,302,579 bytes로 25MiB 예산 안이다. 다만 `.pt2`는 Android LiteRT artifact가 아니다. 공식 LiteRT Torch 0.9.1은 Linux 환경을 요구하며 WSL 변환 환경 설치 중 WSL filesystem I/O 오류가 발생해 `.tflite` 변환과 runtime parity는 아직 미완료다. |
|
|
| Linux/Colab 변환을 위해 label이 없는 실제 HWRT-derived online/raster 대표 입력 76개를 5,721,772-byte cache로 고정했다. 최소 bundle은 composite base·adapter, 필요한 source, converter와 대표 cache만 포함하며 13개 entry·17,276,876 bytes다. Bundle 내부 SHA-256 전체 검증과 별도 디렉터리 추출 후 composite export smoke를 통과했다. Colab notebook은 공식 `litert-torch==0.9.1` 설치, 공개 Hugging Face bundle 다운로드, 해시 검증, online/raster `.tflite` 변환, 76개 전체 top-1 100%·logit 오차≤0.02·합계≤25MiB gate를 자동 실행한다. |
|
|
| Python 공개 런타임의 `MathInk06Engine`도 base-only였으므로 `adapter_checkpoint`를 공식 생성자 인자로 추가했다. 엔진은 base→adapter `shared_state_dict`→dual modality adapter 순서로 구성하고 online은 `adapter.online`, raster virtual trajectory는 `adapter.raster`를 거친다. 실제 seed-17 representative 입력에서 엔진과 export wrapper의 online/raster 최대 logit 오차는 모두 0.0이었다. Adapter를 생략한 호출은 호환용 base-only 경로로만 남긴다. |
|
|
| ### Composite CPU latency·memory proxy |
|
|
| 정정된 Python runtime을 실제 representative 76개로 Windows CPU에서 측정했다. 각 thread 조건은 같은 입력과 output checksum을 사용했다. |
|
|
| | Intra-op threads | Online p95 | Raster p95 | |
| |---:|---:|---:| |
| | 1 | 9.67ms | 26.63ms | |
| | 2 | 9.65ms | 24.99ms | |
| | 4 | 8.90ms | 22.10ms | |
|
|
| 세 조건 모두 online 50ms, raster 200ms software proxy를 통과했다. 이 값은 family-fusion 0.15를 포함한 runtime 재측정이다. Model과 composite adapter의 중복 제거 tensor state는 8,947,108 bytes다. 모델 로드 후 관측한 inference RSS 증가분은 17,440,768 bytes이고 두 값을 합친 구조 proxy는 26,387,876 bytes로 100MiB 안이다. 전체 Python process RSS는 CPython·PyTorch runtime을 포함하므로 Android peak memory와 직접 비교하지 않는다. 실제 Android LiteRT 3-tier latency·memory·battery·delegate 검증은 계속 필요하다. |
|
|
| ### 실제 P 연속식 데이터 계약 |
|
|
| 실제 데이터가 도착하기 전에 `AIFlow P Formula v1` JSON Schema와 fail-closed preflight를 추가했다. 각 formula는 다음 근거를 모두 가져야 한다. |
|
|
| - 고유 `formula_id`, 원본 추적용 `origin_id` |
| - 비어 있지 않은 `writer_id`, `device_id`, `source_id` |
| - `training|validation|test` split |
| - `rights_track=P`, `commercial_training_allowed=true` |
| - 양수 canvas 크기 |
| - token과 원본 raw stroke가 포함된 정답 symbol group |
|
|
| Origin·writer·device·source가 둘 이상의 split에 나타나면 제품 평가를 거부한다. Timestamp와 pressure가 없는 symbol은 삭제하거나 관측값으로 위장하지 않고 missing slice로 센다. 검증된 formula의 실제 symbol group은 boundary 음성, 인접한 두 symbol group 결합은 boundary 양성으로 만든다. 이 계약은 실제 성능값을 만들지는 않지만, 향후 P 입력이 CROHME 정답 group이나 합성 고립기호 proxy와 섞이는 것을 방지한다. |
|
|
| ### Online 3-seed 공통 오류와 역할 분리 |
|
|
| 정정된 composite 세 seed의 logit을 같은 writer-disjoint 분모에서 다시 감사했다. Validation 4,261개에서 seed별 exact top-1은 86.01~87.49%, log-probability ensemble은 87.84%, top-5는 99.53%였다. 선택에 쓰지 않은 paired-test 3,782개에서 seed별 exact는 82.92~83.24%, ensemble은 83.71%, top-5는 98.02%였다. 표본별로 정답 seed를 사후 선택하는 비배포 oracle도 88.05%라 seed 수만 늘려서는 92% exact gate에 도달하지 않는다. 세 seed가 모두 틀린 비율은 11.95%다. |
|
|
| 반면 paired-test 예측을 고립 필기에서 구별 불가능한 visual family로 접으면 92.99%다. Exact 오류의 56.98%, 전체의 9.28%p가 같은 visual family 안의 의미 라벨 혼동이었다. 가장 큰 혼동은 `O→0`, `x→X`, `W→w`, `z→Z`, `c↔C`, `s↔S`이고, validation에는 `|→I/l`, `\setminus→\backslash`도 나타났다. 따라서 현재 신경망의 주 병목은 궤적 형태 자체보다 고립기호에 존재하지 않는 상대 크기·수식 역할 정보다. |
|
|
| 결론적으로 0.6의 on-device 1단계는 visual family 후보를 안정적으로 반환하고, exact 대소문자·`O/0/o`·`x/X/\times` 의미는 같은 식 행의 크기와 행동 문맥 레이어가 결정해야 한다. 이를 고립기호 exact head에 더 강하게 강제하면 writer 독립 일반화를 악화시킬 가능성이 높다. 제품 92% gate는 `isolated visual-family ≥92%`와 `formula-context exact ≥92%`로 분리해 보고하되, 후자는 실제 P 연속식 없이 통과 처리하지 않는다. |
|
|
| Validation에서만 exact/family fusion weight를 0~0.30 범위로 고정한 결과 0.15가 선택됐다. Paired-test에는 한 번만 적용했으며 exact ensemble 83.71→83.82%(+0.11%p), macro-F1 +0.09%p였다. 개선 폭은 작지만 adapter checkpoint에 기록된 0.15가 Python 공개 runtime과 export graph에서 무시되던 결함을 수정했다. 새 seed-17 strict export는 대표 76개 top-1 100% 일치·logit 오차 0.0, online+raster 합계 24,300,116 bytes로 25MiB 안이다. |
|
|
| ### 연속 수식 visual-family 입력 계약과 R-track 적응 |
|
|
| 고립기호 paired-test에서 92.99%였던 visual-family가 실제 연속 수식에서도 유지되는지 CROHME 정답 symbol group 조건부로 다시 측정했다. 기존 행동 head의 93.33%는 정답 group과 `c/C`, `x/X/\times`, `z/Z` 역할이 주어진 조건부 값이어서, teacher 형태군 인식과 confidence abstain을 포함한 최종 exact를 직접 계산했다. |
|
|
| Seed별 threshold를 trainData writer-validation에서만 고정한 행동 exact gate는 official test에서 teacher exact를 평균 33.65→47.30%(+13.65%p)로 개선했다. Rewrite precision은 81.35%였다. 그러나 teacher visual-family 자체가 평균 53.02%여서 행동 head가 보지 못하는 앞단 domain 병목이 더 컸다. |
|
|
| Baseline 채널 주입을 `formula`, `missing`, `local_full`, `height_only` 네 방식으로 validation 비교했다. 전체 지원기호는 `missing` visual-family 76.55%가 가장 높았지만 행동 대상은 기존 `formula`가 65.52%로 가장 높아 전역 정규화 하나로 해결되지 않았다. 네 branch 중 하나라도 정답인 비배포 validation oracle도 전체 88.06%, 행동 대상 75.16%로 92%에 못 미쳤다. 따라서 경량 selector만 추가하는 안은 기각했다. |
|
|
| 구조적 회복 가능성을 확인하기 위해 제품 encoder와 online adapter를 동결하고, 그 뒤에 hidden 64 residual TCN formula adapter만 CROHME R-track truth group으로 학습했다. Recipe는 family CE + exact CE 0.10, baseline-context dropout 0.30이며 seed 17 probe validation에서 고정한 뒤 seed 17·31·47로 재현했다. |
|
|
| | 지표 | 3-seed 평균 | 최솟값 | |
| |---|---:|---:| |
| | writer-validation exact | 85.19% | 82.91% | |
| | writer-validation visual-family | **93.30%** | **93.03%** | |
| | official test exact | 82.38% | 81.13% | |
| | official test visual-family | 88.17% | 88.03% | |
|
|
| 세 seed 모두 writer-validation visual-family 92%를 넘겼으므로 연속식 적응 구조 자체는 유효하다. 하지만 official test는 세 seed 모두 92%를 실패했고 visual-family 일반화 gap은 평균 5.13%p다. 즉 잔여 병목은 모델 용량이나 정규화 selector보다 unseen writer/domain coverage다. 이 adapter는 CROHME R-track weight이므로 제품 checkpoint·distillation·LiteRT bundle에는 포함하지 않는다. 다음 제품 gate는 같은 recipe를 실제 P 연속식 writer/device-disjoint 데이터에 적용하는 것이다. |
|
|
| ### 승인 P 고립기호의 합성 수식 배치 재검증 |
|
|
| 기존 행동 학습기에 구현돼 있었지만 비활성 상태였던 `P_approved_synthetic_layout_proxy`를 seed 17에서 먼저 검증했다. HWRT/UJI의 실제 trajectory를 유지하고 같은 writer 또는 같은 source의 숫자를 좌우 anchor로 붙였으며, 대·소문자 높이 분포를 20% 겹치게 해 크기만으로 정답이 새지 않게 했다. P proxy 1,590개를 loss weight 0.35로 추가한 결과 official test accuracy는 93.07%로 같았지만 macro-F1은 76.64→73.62%, uppercase recall은 61.29→45.16%, multiplication recall은 88.89→86.11%로 하락했다. 따라서 31·47 seed 확장을 중단하고 checkpoint를 shadow 실패로 격리했다. |
|
|
| 정확도만 보고 가중치를 조정하지 않고 실제 CROHME writer-validation 행동 feature와 합성 P proxy의 분포를 역할별로 비교했다. 절대 standardized mean difference(SMD) 0.5를 호환 기준으로 두었으나 최대 layout SMD는 2.82였다. |
|
|
| | feature | 실제 lowercase | P proxy lowercase | SMD | 실제 uppercase | P proxy uppercase | SMD | |
| |---|---:|---:|---:|---:|---:|---:| |
| | formula-relative bbox height | 0.347 | 0.712 | +2.58 | 0.473 | 0.937 | +2.28 | |
| | local height ratio | 1.139 | 0.742 | -1.18 | 1.456 | 0.976 | -1.23 | |
| | formula-relative bbox width | 0.069 | 0.251 | +2.82 | 0.088 | 0.211 | +2.56 | |
|
|
| 고립기호를 숫자 사이에 배치하는 것만으로는 전체 formula bbox, Tray, 첨자와 이웃 부재 분포가 재현되지 않는다. 특히 proxy의 절대 bbox는 실제 연속식보다 두 배가량 크면서도 local height ratio는 반대 방향이라 행동 head에 모순된 신호를 줬다. 이 결과에 따라 승인 P 고립기호는 계속 shape encoder 학습에 사용하지만, 실제 formula bbox와 symbol group이 없는 한 행동 role 또는 formula adapter의 제품 근거로 사용하지 않는다. 다음 입력은 `AIFlow P Formula v1`을 통과한 실제 연속식이어야 한다. |
|
|
| 2026-07-24 외부 source를 공식 원문으로 재검증했다. Google MathWriting의 해당 dataset README는 데이터에 CC BY-NC-SA 4.0을 명시하고, CROHME 공식 배포 페이지도 academic/research purpose only와 commercial-use 금지를 명시한다. 두 source 모두 R-track 평가에는 유용하지만 P-track checkpoint에는 사용할 수 없다. 현재 확인된 공개 외부 P 연속식 source는 없으므로 제품 학습 데이터는 AIFlow 소유 수집 또는 별도 상용 라이선스 계약으로 확보해야 한다. |
|
|
| ### AIFlow Ink v1 → P Formula v1 사람 확정 경계 |
|
|
| 외부 P 연속식 부재가 코드 병목으로 남지 않도록 기존 drawer의 local-only `AIFlow Ink v1`을 제품 학습 계약으로 변환하는 경계를 추가했다. 자동 gridding 결과나 모델 예측 token은 정답으로 사용하지 않는다. 별도 annotation JSONL이 다음 값을 모두 제공해야 한다. |
|
|
| - raw record와 정확히 일치하는 `sample_id` |
| - device family가 아닌 실제 비식별 `device_id` |
| - `label_status=human_verified` |
| - 모든 `formula_cell_id → exact token` 전단사 |
|
|
| 변환기는 모든 raw stroke가 중복 없이 정확히 한 cell에 포함되는지 확인하고 원본 획 순서·timestamp·pressure 결측을 보존한다. 기준 0.6 checkpoint의 `exact_labels`가 중복 없는 378개인지 확인한 뒤 vocabulary 밖 token을 거부한다. 변환된 전체 corpus는 기존 P Formula preflight에서 origin·writer·device·source split 누수 0, training/validation/test 존재, P 권리, 최소 독립 source 조건을 모두 통과해야만 `.part → atomic replace` 방식으로 UTF-8 JSONL을 생성한다. 실패 시 report만 남기고 dataset은 쓰지 않는다. |
|
|
| ```powershell |
| python scripts/materialize_math_ink_06_p_formula.py ` |
| --input research/data/P_owned/curated ` |
| --annotations research/data/P_owned/annotations.jsonl ` |
| --checkpoint research/runs/<seed>/math_ink_06_candidate.pt ` |
| --output research/data/P_owned/p_formula_v1.jsonl ` |
| --report research/data/P_owned/p_formula_preflight.json |
| ``` |
|
|
| 이는 자동 수집 기능이 아니며 서버 전송도 하지 않는다. 사용자가 로컬에서 명시적으로 확정한 group/token만 변환한다. 실제 데이터가 없으므로 제품 성능값은 여전히 만들지 않으며, 이 단계는 다음 3-seed 학습 입력이 잘못된 pseudo label로 오염되는 것을 막는 준비다. |
|
|
| ### P-only formula adapter 학습·3-seed gate |
|
|
| Materialize된 P Formula v1을 별도 가공 없이 소비하는 GPU trainer와 3-seed summarizer를 추가했다. 각 symbol은 전체 formula ink bbox를 공통 좌표계로 사용해 128×19로 canonicalize되며, 원본 필순·formula-relative `bbox_top/bottom/height/center_y`·timestamp/pressure 결측 mask를 보존한다. |
|
|
| 학습은 product encoder와 online adapter를 동결하고 zero-init hidden-64 residual formula adapter만 연다. Loss는 family CE + exact CE 0.10, context dropout 0.30이며 source 빈도와 exact label 빈도의 역제곱근을 곱한 sampler를 사용한다. Checkpoint 선택은 validation family/visual-family만 사용하고 test는 선택된 state에 한 번 적용한다. |
|
|
| 각 seed는 다음 조건을 모두 통과해야 한다. |
|
|
| - exact top-1 ≥92% |
| - exact top-5 ≥99% |
| - macro-F1 ≥90% |
| - writer floor ≥75% |
| - timestamp·pressure 결측 slice 하락 ≤3%p |
|
|
| Seed 17·31·47 report가 정확히 하나씩 있고 모두 개별 gate를 통과한 경우에만 single mobile student distillation을 허용한다. 세 run의 원본 P Formula JSONL byte-level SHA-256도 반드시 같아야 하며, 빈 fingerprint나 서로 다른 corpus는 요약 단계에서 거부한다. Teacher ensemble 자체의 모바일 탑재는 항상 금지하며, 세 seed가 통과해도 LiteRT parity와 Android 3-tier gate 전까지 `product_validation=false`다. |
|
|
| 실제 seed-17 composite와 GTX 1650에서 2/2/2-symbol P fixture로 1-epoch end-to-end smoke를 실행했다. CUDA 학습, validation selection, test metric, 결측 slice, checkpoint/report 생성이 완료됐다. 극소 fixture의 seed gate는 예상대로 실패했고 checkpoint는 프로젝트 산출물·공개본에 포함하지 않았다. 이는 실행 계약 증거이며 성능 근거가 아니다. |
|
|
| ### 3-teacher 확률 증류·단일 모바일 student |
|
|
| 세 seed가 동일 P corpus의 개별 gate를 통과한 뒤 실행되는 fail-closed distiller를 추가했다. 각 teacher는 base → shared online adapter → P formula adapter 순서로 합성하며, exact vocabulary와 data SHA-256을 다시 대조한다. Temperature 2.0에서 seed별 softmax 확률을 평균하고 exact/family KL과 hard-label CE를 함께 사용해 hidden-64 formula adapter 하나만 학습한다. |
|
|
| Student는 validation visual-family/exact 순서로 한 번 선택하고 test에 한 번만 적용한다. 최종 checkpoint는 teacher weight를 포함하지 않으며 다음 두 조건을 모두 요구한다. |
|
|
| - student 자체 P seed gate: exact top-1 92%, top-5 99%, macro-F1 90%, writer floor 75%, 결측 slice 하락 3%p 이하 |
| - teacher 확률 ensemble 대비 exact top-1·top-5·visual-family 하락 각각 1%p 이하 |
|
|
| 동일 2/2/2-symbol fixture를 seed 17·31·47로 각각 학습하고 요약한 뒤 GTX 1650에서 student까지 CUDA smoke를 완료했다. Data fingerprint·teacher load order·probability ensemble·student checkpoint/report 생성은 통과했다. 극소 fixture의 정식 student gate는 예상대로 실패했으며 checkpoint는 `D:\AiflowTools` 격리 경로에만 남고 프로젝트·공개 저장소에는 포함하지 않는다. `litert_exported=false`, `product_validation=false`이며 실제 P 성능 근거가 아니다. |
|
|
| ### P Formula release 오케스트레이션·student export |
|
|
| 정식 P corpus가 도착한 뒤 사람이 여섯 명령을 수동으로 조립하면서 서로 다른 데이터나 checkpoint를 섞지 않도록 단일 release driver를 추가했다. 입력은 동일 P Formula JSONL과 seed 17·31·47의 제품 online adapter이며 실행 순서는 다음과 같다. |
|
|
| ```text |
| seed 17 train + gate |
| → seed 31 train + gate |
| → seed 47 train + gate |
| → same-data 3-seed AND summary |
| → probability-ensemble single student distillation + gate |
| → student-only strict torch.export |
| ``` |
|
|
| 각 단계의 report boolean이 `true`일 때만 다음 subprocess가 시작된다. Dry-run도 전체 preflight와 파일 존재 여부를 먼저 검사하고 UTF-8 `release_plan.json`에 정확한 argv와 data SHA-256을 남긴다. |
|
|
| 기존 export는 base+online/raster까지만 합성해 새 formula student를 포함하지 못했다. 이를 위해 `online adapter → formula adapter → shared trajectory classifier → family fusion 0.15` 전용 wrapper를 추가했다. Export는 checkpoint schema·P track·세 teacher lineage·동일 data SHA-256·distillation gate·teacher weight 미포함을 다시 확인한다. 실제 P test tensor만 representative로 쓰고 strict eager/export top-1 100%, 최대 logit 오차 0.02 이하, graph 25MB 이하를 요구한다. Gate를 통과하지 않은 fixture student는 CLI export가 차단된다. |
|
|
| 구조 진단에서는 실패 fixture checkpoint를 저장·배포하지 않고 메모리상 378-label composite만 합성했다. 두 대표 tensor에서 `torch.export(strict=True)` top-1 2/2, 최대 logit 오차 0.0을 확인했다. 이는 graph 호환성 근거일 뿐 fixture의 release 실패를 뒤집지 않으며 실제 LiteRT flatbuffer와 Android 검증은 계속 미완료다. |
|
|
| ### Android ODA runtime·AIFlow Ink v2 |
|
|
| 기존 저장소에는 Python 엔진만 있고 명세의 Android `recognizeOnline`·`recognizeRaster` 구현은 없었다. `android/aiflow-math-ink-runtime` Android library를 추가해 다음 공개 경계를 실제 Kotlin 코드로 고정했다. |
|
|
| - `recognizeOnline(strokes, canvas): SymbolResult` |
| - `recognizeRaster(bitmap): SymbolResult` |
| - `SymbolResult.toServerPayload()` |
|
|
| `AIFlowInkV2`는 `rawStrokes`, `canonicalTaps`, `timestampMode`, `sourceModality`, 로컬 debug용 `virtualHypotheses`를 갖는다. 원본 touch event는 메모리에 보존하지만 공개 server payload는 `candidates`, `confidence`, `modelVersion`, `latencyMs`만 만들며 raw stroke·tap·raster·virtual hypothesis를 포함하지 않는다. Session 호출은 lock으로 직렬화해 native interpreter의 thread-safety에 의존하지 않고 tensor buffer를 재사용한다. |
|
|
| Android `CanonicalTapEncoder`는 관측 timestamp를 6Hz로 재표본화하고 시작·끝·pen-up anchor를 보존한다. Timestamp가 없으면 호길이 기반 canonical time과 `missing_mask=1`을 사용하며 6Hz tap과 TCN 128-step을 분리한다. 첫 구현은 128-step으로 펼칠 때 timestamp를 index로 선형 배분해 Python 대비 speed channel이 달라졌다. 이를 좌표와 동일한 호길이 보간으로 수정했고, Python 학습 전처리의 대표 4행×19채널과 Android 출력이 채널별 절대오차 `1e-4` 이내임을 고정 테스트로 확인했다. Bitmap은 실제 ink bbox를 찾은 뒤 종횡비를 보존해 8px margin의 128×128 중앙에 배치한다. |
|
|
| 2026-07-17 갱신된 Google 공식 문서에서 Android의 현대 표준은 `CompiledModel`, 최신 LiteRT는 2.1.6으로 안내된다. 따라서 library는 `com.google.ai.edge.litert:litert:2.1.6`과 Kotlin 2.3.0을 사용하고 APK asset의 online/raster `.tflite` 두 개를 직접 로드한다. ML Kit custom model API나 서버를 거치지 않는다. |
|
|
| Android Gradle Plugin 8.8.2·Gradle 8.12·SDK 35·JBR 21 환경에서 Kotlin unit test 5개가 모두 통과했고 release AAR을 생성했다. AAR은 56,139 bytes, SHA-256 `2b75abe9040c537a04da6f352c139142850f3d84a2b26e2c7dc79fe23bf74bba`다. AAR에는 모델이 없으며 실제 `.tflite`가 아직 생성되지 않아 CompiledModel 실기기 inference, 3-tier latency/memory/battery는 계속 미완료다. |
|
|
| 공식 근거: |
|
|
| - `https://developers.google.com/edge/litert/android` |
| - `https://developers.google.com/edge/litert/next/android_kotlin` |
|
|
| 고정 recipe는 `research/configs/MATH-INK-06-P-FORMULA-v1.json`에 저장했다. |
|
|
| 전체 회귀는 328개가 통과했다. |
|
|
| ## 산출물 |
|
|
| - `src/math_grid_drawer/research/behavior_context06.py` |
| - `src/math_grid_drawer/research/behavior_role_head06.py` |
| - `src/math_grid_drawer/research/math_context_layer.py` |
| - `scripts/train_math_ink_06_behavior_role.py` |
| - `scripts/summarize_math_ink_06_behavior_seeds.py` |
| - `scripts/audit_math_ink_06_behavior_grouping.py` |
| - `scripts/audit_math_ink_06_x_edge_bottleneck.py` |
| - `scripts/sweep_math_ink_06_x_grouping_guard.py` |
| - `scripts/sweep_math_ink_06_cross_gap_guard.py` |
| - `scripts/audit_math_ink_06_multistroke_families.py` |
| - `scripts/sweep_math_ink_06_multistroke_family_guard.py` |
| - `scripts/audit_math_ink_06_multistroke_guard_regressions.py` |
| - `scripts/audit_math_ink_06_local_baseline_overmerge.py` |
| - `scripts/sweep_math_ink_06_component_competition_guard.py` |
| - `scripts/sweep_math_ink_06_local_baseline_guard.py` |
| - `scripts/train_math_ink_06_boundary_behavior_guard.py` |
| - `scripts/train_math_ink_06_p_boundary_auxiliary.py` |
| - `scripts/train_math_ink_06_p_boundary_joint.py` |
| - `scripts/summarize_math_ink_06_p_boundary_joint.py` |
| - `scripts/evaluate_math_ink_06_p_boundary_device_stress.py` |
| - `scripts/export_math_ink_06_litert.py` |
| - `scripts/build_math_ink_06_litert_colab_bundle.py` |
| - `scripts/benchmark_math_ink_06_composite.py` |
| - `scripts/audit_math_ink_06_online_error_consensus.py` |
| - `scripts/calibrate_math_ink_06_online_family_fusion.py` |
| - `scripts/audit_math_ink_06_behavior_exact_gate.py` |
| - `scripts/audit_math_ink_06_formula_context_contract.py` |
| - `scripts/train_math_ink_06_formula_adapter.py` |
| - `scripts/summarize_math_ink_06_formula_adapter_seeds.py` |
| - `scripts/audit_math_ink_06_p_proxy_shift.py` |
| - `scripts/materialize_math_ink_06_p_formula.py` |
| - `scripts/train_math_ink_06_p_formula_adapter.py` |
| - `scripts/summarize_math_ink_06_p_formula_seeds.py` |
| - `scripts/distill_math_ink_06_p_formula_student.py` |
| - `scripts/export_math_ink_06_p_formula_student.py` |
| - `scripts/run_math_ink_06_p_formula_release.py` |
| - `android/aiflow-math-ink-runtime` |
| - `scripts/preflight_math_ink_06_p_formula.py` |
| - `scripts/analyze_crohme_lattice_failures.py` |
| - `tests/test_behavior_context06.py` |
| - `tests/test_behavior_role_head06.py` |
| - `tests/test_p_formula_gate06.py` |
| - `tests/test_p_formula_intake06.py` |
| - `tests/test_distill_math_ink_06_p_formula_student.py` |
| - `tests/test_export_math_ink_06_p_formula_student.py` |
| - `tests/test_run_math_ink_06_p_formula_release.py` |
| - `src/math_grid_drawer/research/p_formula_intake06.py` |
| - `src/math_grid_drawer/research/p_formula_dataset06.py` |
| - `research/contracts/aiflow_p_formula_v1.schema.json` |
| - `research/contracts/aiflow_p_formula_annotation_v1.schema.json` |
| - `research/configs/MATH-INK-06-P-FORMULA-v1.json` |
| - `research/colab/AIFlow_Math_Ink_06_LiteRT.ipynb` |
| - `research/runs/math_ink_06_litert_colab_20260724/aiflow_math_ink_06_litert_bundle.zip` |
| - `research/runs/math_ink_06_composite_cpu_benchmark_familyfusion_20260724/report.json` |
| - `research/runs/math_ink_06_online_error_consensus_validation_3seed_20260724/report.json` |
| - `research/runs/math_ink_06_online_error_consensus_paired_test_3seed_20260724/report.json` |
| - `research/runs/math_ink_06_online_family_fusion_calibration_3seed_20260724/report.json` |
| - `research/runs/math_ink_06_composite_export_familyfusion_seed17_20260724/export_manifest.json` |
| - `research/runs/math_ink_06_behavior_exact_gate_3seed_20260724/report.json` |
| - `research/runs/math_ink_06_formula_context_contract_3seed_20260724/report.json` |
| - `research/runs/math_ink_06_formula_adapter_h64_3seed_20260724/run_summary.json` |
| - `research/runs/math_ink_06_behavior_role_p_proxy_w035_seed17_20260724/report.json` |
| - `research/runs/math_ink_06_p_proxy_shift_seed17_20260724/report.json` |
| - `research/AIFlow-MATH-INK-0.6-BEHAVIOR-CONTEXT-REPORT-20260724.md` |
| - `research/runs/math_ink_06_behavior_role_3seed_20260724/run_summary.json` |
| - `research/runs/math_ink_06_behavior_grouping_audit_20260724/report.json` |
| - `research/runs/math_ink_06_behavior_grouping_cross_joint_audit_20260724/report.json` |
| - `research/runs/math_ink_06_p_boundary_joint_sharedfix_3seed_20260724/run_summary.json` |
| - `research/runs/math_ink_06_p_boundary_device_stress_sharedfix_3seed_20260724` |
| - `research/runs/math_ink_06_composite_export_seed17_20260724/export_manifest.json` |
| - `research/runs/math_ink_06_x_grouping_guard_20260724/report.json` |
| - `research/runs/math_ink_06_x_edge_bottleneck_20260724/report.json` |
| - `research/runs/math_ink_06_cross_gap_guard_full_selector_20260724/report.json` |
| - `research/runs/math_ink_06_behavior_grouping_full_selector_gap50_20260724/report.json` |
| - `research/runs/math_ink_06_x_grouping_guard_gap50_full_selector_20260724/report.json` |
| - `research/runs/math_ink_06_full_selector_gap50_failure_audit_20260724/report.json` |
| - `research/runs/math_ink_06_cross_gap_guard_family6_full_selector_20260724/report.json` |
| - `research/runs/math_ink_06_multistroke_family_guard_geometry_protected_20260724/report.json` |
| - `research/runs/math_ink_06_multistroke_guard_regression_audit_20260724/report.json` |
| - `research/runs/math_ink_06_full_selector_gap40_family6_protected_failure_audit_20260724/report.json` |
| - `research/runs/math_ink_06_behavior_grouping_full_selector_gap40_family6_protected_20260724/report.json` |
| - `research/runs/math_ink_06_component_competition_guard_20260724/report.json` |
| - `research/runs/math_ink_06_local_baseline_guard_20260724/report.json` |
| - `research/runs/math_ink_06_local_baseline_overmerge_audit_20260724/report.json` |
| - `research/runs/math_ink_06_boundary_behavior_guard_20260724/report.json` |
| - `research/runs/math_ink_06_p_boundary_auxiliary_smoke_20260724/report.json` |
| - `research/runs/math_ink_06_p_boundary_auxiliary_fullsmoke_20260724/report.json` |
| - `research/runs/math_ink_06_p_boundary_joint_3seed_20260724/run_summary.json` |
| - `research/runs/math_ink_06_p_boundary_device_stress_3seed_20260724/report.json` |
|
|