# [16-3] 다기관 뇌 MRI·비식별 음성 특징 융합 치매 위험도 층화 — 설계 명세서

| 항목 | 내용 |
| --- | --- |
| 참조 하네스 | `cog16/t163.py` (표준 라이브러리 전용, Python 3.15.2에서 실행) |
| 고정 입력 | `cog16/fixtures/t163_cohorts.json` (SHA-256 `062582e1970f945f54177e0e3f95205a0c9bb9e472c5b9ce005098161b4b376e`) |
| 단위 시험 | `tests/test_cog16_t163.py` — 28건 통과, 약 4초 |
| 실행 | `python3 -B -c "from cog16 import t163; from cog16._contract import load_fixture; print(t163.run_block(load_fixture('t163_cohorts.json'))['gates'])"` — 7.3–17.6초(다른 작업이 함께 도는 부하에서 7회 측정한 범위), 두 번 실행한 결과 바이트 동일 |
| 독립 감사 | `python3 -B -m cog16.audit --run evidence/cog16-run.json --output evidence/cog16-audit.json` — 증거 문서에 실린 사례별 점수에서 게이트·효과 배율·시드 반복에 쓰인 AUROC와 DeLong 구간 21개를 전수 비교 정의로 다시 계산하고, 1차 평가의 ECE·Brier와 게이트 문자열을 다시 유도한다. 풀이 모듈을 불러오지 않는다 |
| 자료 범위 | 합성 코호트 `SYN_A`·`SYN_B`·`SYN_S`·`SYN_P`만 사용. ADNI·OASIS-3·DementiaBank 원자료는 이 저장소에 없다 |

**이 문서의 수치가 뜻하는 것.** 10장의 수치는 fixture가 선언한 합성 가정 위에서 파이프라인(보정·융합·평가·통제)이 설계대로 동작하는지를 확인한 결과다. 실제 코호트의 성능 측정값이 아니다. 실제 외부 코호트 AUROC는 DUA 자료를 반입한 뒤 11장의 절차로 같은 하네스에서 산출한다.

---

## 1. 범위와 안전 경계

1. **용도**: 임상의 판독을 보조하는 연구용 위험도 층화 점수. 단독 확정 진단을 내지 않는다. 모든 출력 상태는 임상의 검토를 전제로 한다(8장).
2. **입력**: DUA·IRB 통제 아래 비식별 공개 코호트에서 이미 추출한 정량 특징만 받는다. 영상 원본, 음성 원본, 전사 원문은 모델 입력 스키마에 없다.
3. **성문 비식별화**: 음성은 추출 구역 안에서 발화 단위 스칼라 요약량으로만 변환한다. 원음 경로, 전사, 프레임 단위 궤적(F0 윤곽·MFCC 열), 화자 임베딩(x-vector 계열)은 반출 스키마가 거부한다(`validate_speech_payload`, 8건 사례). 음높이처럼 성별·연령에 묶이는 요약량은 CN 규준 대비 공변량 보정 잔차(3.1절)로만 모델에 들어간다. 대상 식별자는 기관 보유 키로 만든 키 해시이며 하네스는 합성 ID만 다룬다.
4. **모델 구성**: 외부 파운데이션 LLM 증류를 쓰지 않는다. 영상은 FreeSurfer 계열 ROI 용적·피질 두께, 음성은 신호처리 기반 운율·휴지와 구문 주석 기반 복잡도 요약량이며, 그 위에 모달리티별 소형 인코더와 융합망을 둔다.
5. **실행 범위**: 하네스는 특징 추출 이후 단계를 실행한다. FreeSurfer 실행, 음향 추출기, torch 구현, 적대 학습(3.4절)은 이 저장소에서 실행하지 않았고 규격으로만 둔다.

---

## 2. 데이터 스키마

### 2.1 대상·방문 행

| 필드 | 형 | 제약 | 비고 |
| --- | --- | --- | --- |
| `sid` | string | 코호트 안 유일, 키 해시 | 원 ID·이름·생년월일 금지 |
| `cohort` | enum | `ADNI` \| `OASIS3` \| `DEMENTIABANK` (하네스: `SYN_A` \| `SYN_B` \| `SYN_S` \| `SYN_P`) | |
| `site` | string | 기관 코드 | 외부 평가 기관은 학습에 없어야 함 |
| `unit` | string \| null | 스캐너(장비·프로토콜) 배치 ID | 영상 보정 단위 |
| `spUnit` | string \| null | 녹음 조건(장비·환경·수집 지점) ID | 음성 보정 단위 |
| `y` | 0 \| 1 | 0 = CN, 1 = MCI/AD | 2.3절 대응표 |
| `stage` | enum | `CN` \| `MCI` \| `AD` | 층화 보고용 |
| `age`, `sex`, `edu` | number, 0/1, number | 연령 50–95 지원 범위 | 공변량 |
| `icv` | number | 두개내용적 z | 영상 공변량 |
| `mri` | number[10] \| null | ROI 용적 6 + 피질 두께 4 | 유한 실수 |
| `sp` | number[6] \| null | 운율 2, 휴지 2, 구문 1, 어휘 1 | 유한 실수 |
| `lag` | integer | 음성 방문일 − 영상 방문일(일) | 동시 보유 판정 |
| `split*` | enum | `TRAIN` \| `VAL` \| `TEST` \| `CAL` | 대상 단위 배정 |

하네스 특징 이름은 추상 인덱스다(`ROI_V0..5`, `ROI_T0..3`, `SP_PROS_F0VAR`, `SP_PROS_RATE`, `SP_PAUSE_RATIO`, `SP_PAUSE_MEANLEN`, `SP_SYNT_DEPTH`, `SP_LEX_DIVERSITY`). 실제 해부 구조·음향 정의와의 대응은 반입 시 사전 등록한다(11장 3단계).

### 2.2 특징 추출 규격(미실행, 입력 계약)

| 모달리티 | 추출 | 반출 형식 | 품질 조건 |
| --- | --- | --- | --- |
| T1 MRI | FreeSurfer 계열 분할의 ROI 용적(mm³), 피질 두께(mm), ICV | ROI당 스칼라 | 분할 실패·움직임 QC 탈락 행은 `REJECT_INPUT` |
| 음향 운율 | F0 변동, 발화 속도 | 발화 단위 스칼라 | 유성 구간 길이 하한 |
| 휴지 | 무음 휴지 비율, 평균 휴지 길이(VAD 기반) | 발화 단위 스칼라 | 녹음 길이 하한 |
| 구문·어휘 | 구문 깊이, 어휘 다양도(형태·구문 주석 계층 기반) | 발화 단위 스칼라 | 전사 원문은 반출하지 않음 |

### 2.3 라벨 대응(사전 등록 대상)

| 코호트 | CN (y = 0) | MCI/AD (y = 1) | 기록할 차이 |
| --- | --- | --- | --- |
| ADNI | 진단 CN | 진단 MCI, 치매 | 기준 사례 구성 |
| OASIS-3 | CDR 0 | CDR ≥ 0.5 | CDR 0.5를 양성군에 두는 것은 근사다. 임상 진단 필드와의 일치율을 함께 보고 |
| DementiaBank | 대조군 | 치매군 | 양성군 구성이 영상 코호트와 다르다(스펙트럼 차이) |

사례 구성(MCI:AD 비)이 코호트마다 다르므로 7.3절의 층화 AUROC와 기준 구성 표준화 AUROC를 필수로 보고한다.

---

## 3. 다기관 스캐너·음향 도메인 편향 보정

### 3.1 참조 모형과 표준화 잔차

특징 $j$, 취득 단위 $g$(스캐너 배치 또는 녹음 조건), 대상 $i$에 대해

$$y_{ijg} = \alpha_j + \mathbf{x}_i^\top \boldsymbol\beta_j + \sigma_j\left(\gamma_{gj} + \delta_{gj}\,\varepsilon_{ijg}\right)$$

$\mathbf{x}_i$ = (연령, 성별, 교육연수, ICV)의 중심화 값이며 음성은 ICV를 뺀다. ICV는 공변량 잔차 방식으로 보정한다(두께 특징의 ICV 계수는 자료가 0 근처로 정한다).

**참조 적합은 학습 분할의 CN 행에서만 한다.** 단위 지시변수와 공변량을 함께 넣은 최소제곱으로 $\hat\alpha_{gj}, \hat{\boldsymbol\beta}_j$를 얻고

$$\hat\alpha_j = \sum_g \frac{n_g}{N}\hat\alpha_{gj}, \qquad \hat\sigma_j^2 = \frac{1}{N}\sum_{i}\left(y_{ijg} - \hat\alpha_{gj} - \mathbf{x}_i^\top\hat{\boldsymbol\beta}_j\right)^2$$

$$z_{ijg} = \frac{y_{ijg} - \hat\alpha_j - \mathbf{x}_i^\top\hat{\boldsymbol\beta}_j}{\hat\sigma_j}$$

$z$는 CN 규준 대비 공변량 보정 표준화 편차다. 진단 라벨은 설계 행렬에 넣지 않는다. 따라서 적용 시점에 평가 대상의 라벨이 필요 없고, 기관별 유병률 차이가 단위 효과 추정에 섞이지 않는다.

### 3.2 ComBat 경험적 베이즈 수축 (Johnson 2007, Fortin 2018)

단위 $g$의 CN 참조 행 $n_g$개에서 $\hat\gamma_{gj} = \bar z_{\cdot jg}$, $\hat\delta^2_{gj} = \mathrm{Var}_i(z_{ijg})$. 사전분포 $\gamma_{gj}\sim\mathcal N(\bar\gamma_g, \bar\tau_g^2)$, $\delta^2_{gj}\sim\mathrm{IG}(\bar\lambda_g, \bar\theta_g)$의 초모수는 특징 방향 적률로 정한다.

$$\bar\gamma_g = \operatorname{mean}_j\hat\gamma_{gj},\quad \bar\tau_g^2 = \operatorname{var}_j\hat\gamma_{gj},\quad \bar V = \operatorname{mean}_j\hat\delta^2_{gj},\quad \bar S^2 = \operatorname{var}_j\hat\delta^2_{gj}$$

$$\bar\lambda_g = \frac{\bar V^2 + 2\bar S^2}{\bar S^2},\qquad \bar\theta_g = \frac{\bar V^3 + \bar V\bar S^2}{\bar S^2}$$

사후 평균은 다음 두 식을 수렴할 때까지 번갈아 갱신한다(상대 변화 $<10^{-8}$).

$$\gamma^*_{gj} = \frac{n_g\bar\tau_g^2\,\hat\gamma_{gj} + \delta^{*2}_{gj}\,\bar\gamma_g}{n_g\bar\tau_g^2 + \delta^{*2}_{gj}},\qquad \delta^{*2}_{gj} = \frac{\bar\theta_g + \tfrac12\sum_i\left(z_{ijg} - \gamma^*_{gj}\right)^2}{\tfrac{n_g}{2} + \bar\lambda_g - 1}$$

보정값과 모델 입력:

$$z^*_{ijg} = \frac{z_{ijg} - \gamma^*_{gj}}{\delta^*_{gj}},\qquad y^*_{ijg} = \hat\alpha_j + \mathbf{x}_i^\top\hat{\boldsymbol\beta}_j + \hat\sigma_j z^*_{ijg}$$

모델은 $z^*$를 받는다. 원 공변량은 모델 입력에 넣지 않는다(7.6절). 표준 ComBat과 다른 점은 세 가지다: 참조가 CN 한정이고, 참조가 학습 분할에 동결되며, 척도 항 $\delta$가 질병 편차를 포함한 잔차 전체에 걸린다(스캐너 이득 해석, 합성 생성기와 같은 가정).

### 3.3 학습에 없던 취득 단위

외부 코호트의 단위 $g'$는 학습에 없다. 단위 모수는 시험 분할과 겹치지 않는 보정용 분할 $\mathcal C_{g'}$에서만 추정하고, $\hat\alpha, \hat{\boldsymbol\beta}, \hat\sigma$는 동결한 값을 쓴다.

| 모드 | 사용하는 행 | 라벨 사용 | 추정량의 기댓값 |
| --- | --- | --- | --- |
| `NONE` | 없음 | 없음 | $\gamma^*=0,\ \delta^*=1$ (단위 효과가 그대로 남음) |
| `LABEL_FREE` | $\mathcal C_{g'}$ 전체 | 없음 | $\gamma_{g'j} + \delta_{g'j}\,\pi_{g'}\,d_j\,\mathbb E[s\mid y{=}1]$ — 단위 유병률 $\pi_{g'}$에 비례해 질병 신호를 흡수 |
| `CN_REF` | $\mathcal C_{g'}$ 중 CN | 보정용 행의 CN 여부만 | $\gamma_{g'j}$ (불편), 분산 $\approx 1/n_{\mathrm{CN}}$ |

`CN_REF`가 기본이다. 보정용 행의 라벨은 CN 참조 선택에만 쓰며 모델 가중치, 임계, 온도에는 쓰지 않는다. 이것도 외부 코호트 정보를 쓰는 적응이므로 `NONE` 결과를 무적응 민감도 분석으로 반드시 함께 보고한다(게이트 표의 `zeroAdaptation`).

### 3.4 도메인 적대적 정규화 (Ganin 2016) — 수식 규격

인코더 $E_\theta$, 라벨 헤드 $C_\phi$, 단위 판별기 $D_\psi$(학습 단위 $G$개 분류)에 대해

$$\min_{\theta,\phi}\max_{\psi}\ \mathcal L_{\mathrm{cls}}\big(C_\phi(E_\theta(\mathbf z^*)), y\big) - \lambda_{\mathrm{adv}}\,\mathcal L_{\mathrm{dom}}\big(D_\psi(E_\theta(\mathbf z^*)), g\big)$$

기울기 반전층 $R_\lambda(\mathbf h)=\mathbf h$, $\partial R_\lambda/\partial\mathbf h = -\lambda I$로 한 번의 역전파에 구현한다.

$$\theta \leftarrow \theta - \eta\left(\frac{\partial\mathcal L_{\mathrm{cls}}}{\partial\theta} - \lambda_{\mathrm{adv}}\frac{\partial\mathcal L_{\mathrm{dom}}}{\partial\theta}\right),\quad \psi\leftarrow\psi - \eta\frac{\partial\mathcal L_{\mathrm{dom}}}{\partial\psi},\quad \lambda_{\mathrm{adv}}(p) = \frac{2}{1+e^{-10p}} - 1$$

- **CN 조건부 적대 손실**: 단위별 유병률이 다르면 판별기가 질병 신호로 단위를 맞히고, 반전된 기울기가 그 신호를 지운다. $\mathcal L_{\mathrm{dom}}$는 $y=0$ 행에서만 계산한다(3.1절의 CN 참조와 같은 원리).
- **적용 순서**: ComBat으로 1·2차 적률을 맞춘 뒤, 잔여 단위 정보가 남을 때만 적대 항을 켠다.
- **켜는 기준(하네스 실행)**: 단위 탐침(`unit_probe`) — 외부 CN 시험 행의 절반으로 가우스 나이브 베이즈 단위 분류기를 맞추고 나머지 절반에서 균형 정확도를 잰다. 단위 라벨을 섞은 40회 영분포의 최댓값을 넘으면 잔여 단위 정보가 있다고 판정한다. 특징 수준과 인코더 임베딩 수준에서 각각 잰다.
- 하네스는 적대 학습을 실행하지 않는다. 탐침과 판정 기준까지가 실행 범위다.

---

## 4. 비동시·부분 결측 모달리티 정렬 스키마

### 4.1 세 정렬군과 마스크

| 정렬군 | 마스크 $(m_M, m_S)$ | 공개 자료에서의 출처 |
| --- | --- | --- |
| `MRI_ONLY` | (1, 0) | ADNI, OASIS-3 |
| `SPEECH_ONLY` | (0, 1) | DementiaBank |
| `PAIRED` | (1, 1) | 세 공개 자료에는 같은 대상의 영상·음성 쌍이 없다(`docs/SOURCE-REGISTER.md` 16-3 행). 하네스는 합성 `SYN_P`에서만 평가 |

- **시점 정합 창**: $|t_S - t_M| \le W$, $W = 180$일. 창을 넘으면 라벨 시점에 가까운 모달리티만 남기고 다른 쪽 마스크를 0으로 내린다(`PAIRED_DEMOTED_STALE`). 하네스에서 900명 중 30명이 강등되었다.
- **분할 단위는 대상**이다. 한 대상의 모든 방문·모달리티는 같은 분할에 둔다. 평가는 대상당 한 행을 쓴다.
- **마스크와 특징의 일치**: 마스크가 1인데 특징이 없으면 위반이다. 특징이 있어도 창 밖이면 마스크를 0으로 둘 수 있다.

### 4.2 분할 역할

| 분할 | 쓰이는 곳 | 금지 |
| --- | --- | --- |
| `TRAIN` | 보정 참조(CN 행), 모델 가중치 | |
| `VAL` | 조기 종료, 온도 $T$ | |
| `TEST` | 내부 참고 성능 | 적합에 사용 금지 |
| `CAL` | 학습에 없던 단위의 $\gamma^*, \delta^*$ | 가중치·온도·임계에 사용 금지 |
| `EXT_TEST` | 외부 지표 | 어떤 적합에도 사용 금지 |
| `REF` | 학습 단위의 참조 행(합성 쌍 R2 체제) | |

### 4.3 정렬 감사(`audit_manifest`)

| 위반 코드 | 조건 |
| --- | --- |
| `DUPLICATE_SUBJECT_ID` | 같은 분할에 같은 `sid`가 두 번 |
| `SUBJECT_IN_MULTIPLE_SPLITS` | 한 `sid`가 둘 이상의 분할에 존재(`CAL`과 `EXT_TEST` 겹침 포함) |
| `SUBJECT_IN_MULTIPLE_SITES` | 한 `sid`가 둘 이상의 기관에 존재 |
| `EXTERNAL_UNIT_IN_TRAIN` | 외부 평가 단위(스캐너·녹음 조건)가 학습 측에 존재 |
| `EXTERNAL_SITE_IN_TRAIN` | 외부 평가 기관이 학습 측에 존재 |
| `PAIR_LINK_MISMATCH` | 두 마스크가 1인데 영상 키와 음성 키가 다른 대상 |
| `PAIR_WINDOW_EXCEEDED` | 두 마스크가 1인데 $|\text{lag}| > W$ |
| `MASK_FEATURE_MISMATCH` | 마스크 1인데 특징 없음 |

파이프라인은 실행 전에 실제 사용 명부를 감사하고 위반이 하나라도 있으면 중단한다. 서로 다른 공개 코호트에 같은 사람이 중복 등록되었는지는 ID로 판별할 수 없으므로 코호트 문서의 모집 기관·기간 중복 여부를 반입 기록에 남긴다.

---

## 5. 결측 모달리티 강건형 융합 아키텍처

### 5.1 인코더와 교차 모달 게이트 어텐션

모달리티 $k\in\{M,S\}$, 상대 모달리티 $k'$.

$$\mathbf H_k = \mathrm{Enc}_k(\mathbf z^*_k)\in\mathbb R^{T_k\times d},\qquad \bar{\mathbf h}_k = \operatorname{mean}_t \mathbf H_{k,t}$$

$$\mathbf c_k = \sum_{t}\operatorname{softmax}_t\!\left(\frac{(W_Q\bar{\mathbf h}_k)^\top (W_K\mathbf H_{k',t})}{\sqrt d}\right) W_V\mathbf H_{k',t}$$

$$s_k = \mathbf w_k^\top\bar{\mathbf h}_k + m_{k'}\,\mathbf o_k^\top\mathbf c_k + c^0_k,\qquad g_k=\sigma(s_k),\qquad \mathbf f=\sum_k m_k\,g_k\,\bar{\mathbf h}_k,\qquad \ell = \mathbf w_o^\top\mathbf f + b_o$$

각 모달리티의 게이트 $g_k$는 자기 표현과 상대 모달리티의 내용(교차 항)을 함께 본다. 융합은 정규화하지 않은 게이트 합이어서 두 모달리티가 모두 있으면 근거가 더해진다.

**하네스가 실행한 축약형**: 모달리티당 토큰 1개($T_k=1$), $\mathrm{Enc}_k(\mathbf z)=\tanh(W_k\mathbf z+\mathbf b_k)$, $d=6$. 토큰이 하나면 softmax 가중이 1이므로 $\mathbf c_k = \mathbf h_{k'}$이고 $W_V$는 $\mathbf o_k$에 흡수된다. 역전파는 손으로 유도했다.

$$\frac{\partial\mathcal L}{\partial\ell}=\sigma(\ell)-y,\quad \frac{\partial\mathcal L}{\partial\mathbf f}=\frac{\partial\mathcal L}{\partial\ell}\mathbf w_o,\quad \frac{\partial\mathcal L}{\partial s_k}=\Big(\frac{\partial\mathcal L}{\partial\mathbf f}\cdot\mathbf h_k\Big)g_k(1-g_k)$$

$$\frac{\partial\mathcal L}{\partial\mathbf h_k}= g_k\frac{\partial\mathcal L}{\partial\mathbf f}+\frac{\partial\mathcal L}{\partial s_k}\mathbf w_k+m_{k'}\frac{\partial\mathcal L}{\partial s_{k'}}\mathbf o_{k'},\qquad \frac{\partial\mathcal L}{\partial W_k}=\Big(\frac{\partial\mathcal L}{\partial\mathbf h_k}\odot(1-\mathbf h_k^2)\Big)\mathbf z_k^{*\top}$$

**결측 불변 성질**: $m_k=0$이면 인코더 $k$와 그 교차 벡터의 어떤 모수도 출력에 영향을 주지 않는다. 단위 시험이 음성 인코더 가중치를 바꿔도 영상 단독 로짓이 비트 단위로 같음을 확인한다. 두 모달리티가 모두 없으면 `REJECT_INPUT`이다.

### 5.2 모달리티 드롭아웃 학습

동시 보유 행의 마스크를 매 반복 다시 뽑는다: 확률 $p_d$로 영상 제거, 확률 $p_d$로 음성 제거, 나머지는 유지($p_d=0.3$, 둘 다 제거하지 않음).

$$\mathcal L(\Theta) = \mathbb E_{(\mathbf z,y)}\,\mathbb E_{\tilde{\mathbf m}\sim q(\cdot\mid\mathbf m)}\Big[\mathrm{BCE}\big(\ell_\Theta(\mathbf z,\tilde{\mathbf m}),y\big)\Big] + \lambda_2\lVert W\rVert_2^2$$

공개 자료에는 실제 쌍이 없으므로 학습 체제를 둘로 나눠 따로 보고한다.

| 체제 | 두 토큰 입력의 출처 | 가정 |
| --- | --- | --- |
| **R1** (공개 자료로 가능한 체제) | 라벨 조건부 의사쌍: 영상 단독 행과 음성 단독 행을 같은 라벨·같은 성별·연령 차 5세 이내로 짝지음 | $\mathbf z_M \perp \mathbf z_S \mid (y, \text{성별}, \text{연령대})$. 실제 쌍에서 보정 상태를 다시 확인해야 한다 |
| **R2** (동시 보유 코호트가 생긴 뒤) | 실제 쌍 | 없음 |

### 5.3 불확실성 보정

**Temperature Scaling (Guo 2017)** — 마스크 패턴별 온도 $T_{\mathbf m}\in\{T_M, T_S, T_{MS}\}$를 내부 검증 분할에서만 맞춘다.

$$\hat p = \sigma(\ell/T_{\mathbf m}),\qquad T_{\mathbf m} = \arg\min_{T>0}\ -\sum_{i\in\mathrm{VAL}_{\mathbf m}}\big[y_i\log\sigma(\ell_i/T)+(1-y_i)\log(1-\sigma(\ell_i/T))\big]$$

음의 로그우도는 $1/T$에 대해 볼록이므로 1차원 황금분할 탐색으로 푼다. 외부 코호트의 유병률 이동은 온도로 고쳐지지 않는다(10.2절의 `eceWidthTs`).

**Dirichlet evidential 출력 (Sensoy 2018)** — $\mathbf v = W_e\mathbf f+\mathbf b_e$, $\alpha_c = 1+\operatorname{softplus}(v_c)$, $S=\sum_c\alpha_c$.

$$\hat p_c = \alpha_c/S,\qquad u = K/S\ \ (\text{vacuity},\ K=2)$$

$$\mathcal L_{\mathrm{ev}} = \log S - \log\alpha_y + \lambda_t\,\mathrm{KL}\big(\mathrm{Dir}(\tilde{\boldsymbol\alpha})\,\Vert\,\mathrm{Dir}(\mathbf 1)\big),\qquad \tilde{\boldsymbol\alpha} = \mathbf y + (1-\mathbf y)\odot\boldsymbol\alpha,\qquad \lambda_t=\lambda_0\min(1, t/T_a)$$

두 범주에서는 오답 범주 $w=1-y$에 대해 KL 항이 닫힌 형태가 된다.

$$\mathrm{KL} = \log\alpha_w - 1 + \frac{1}{\alpha_w},\qquad \frac{\partial\mathcal L_{\mathrm{ev}}}{\partial\alpha_y} = \frac1S-\frac1{\alpha_y},\qquad \frac{\partial\mathcal L_{\mathrm{ev}}}{\partial\alpha_w} = \frac1S+\lambda_t\frac{\alpha_w-1}{\alpha_w^2}$$

하네스는 동결한 융합 표현 $\mathbf f$ 위에 evidential 헤드를 사후 학습한다($\lambda_0=0.3$, $T_a=10$). 닫힌 형태는 일반 Dirichlet KL 식과, 기울기는 중심 차분과 대조했다.

---

## 6. PyTorch 모듈 인터페이스

이 저장소에는 torch가 없다. 아래는 **실행하지 않은 인터페이스 규격**이다. 수치 동작의 기준은 `cog16/t163.py`의 표준 라이브러리 구현이다. torch 구현은 `tokens=1`에서 하네스 가중치를 대응 사상으로 옮겨 넣었을 때 로짓이 일치하는지($10^{-5}$ 이내) 시험한 뒤에 쓴다.

```python
import torch
from torch import Tensor, nn
from torch.autograd import Function
from torch.nn import functional as F


class ModalityEncoder(nn.Module):
    def __init__(self, in_dim: int, hidden: int, tokens: int = 1, dropout: float = 0.0):
        super().__init__()
        self.tokens, self.hidden = tokens, hidden
        self.proj = nn.Linear(in_dim, hidden * tokens)
        self.drop = nn.Dropout(dropout)

    def forward(self, x: Tensor) -> Tensor:                    # (B, in_dim) -> (B, tokens, hidden)
        return self.drop(torch.tanh(self.proj(x))).view(x.shape[0], self.tokens, self.hidden)


class CrossModalGatedAttention(nn.Module):
    """g_k = sigmoid(w.h_k + o.c_k + c0); c_k attends over the other modality's tokens."""

    def __init__(self, hidden: int, heads: int = 1):
        super().__init__()
        self.attn = nn.MultiheadAttention(hidden, heads, batch_first=True)
        self.self_score = nn.Linear(hidden, 1)
        self.cross_score = nn.Linear(hidden, 1, bias=False)

    def forward(self, own: Tensor, other: Tensor, other_present: Tensor) -> tuple[Tensor, Tensor]:
        pooled = own.mean(dim=1)                                           # (B, hidden)
        context, _ = self.attn(pooled.unsqueeze(1), other, other)          # (B, 1, hidden)
        context = context.squeeze(1) * other_present.unsqueeze(1).to(own.dtype)
        gate = torch.sigmoid(self.self_score(pooled) + self.cross_score(context))
        return gate * pooled, gate                                         # (B, hidden), (B, 1)


class MissingModalityFusion(nn.Module):
    def __init__(self, mri_dim: int = 10, speech_dim: int = 6, hidden: int = 32, tokens: int = 1):
        super().__init__()
        self.enc_mri = ModalityEncoder(mri_dim, hidden, tokens)
        self.enc_speech = ModalityEncoder(speech_dim, hidden, tokens)
        self.gate_mri = CrossModalGatedAttention(hidden)
        self.gate_speech = CrossModalGatedAttention(hidden)
        self.head = nn.Linear(hidden, 1)

    def forward(self, x_mri: Tensor, x_speech: Tensor, mask: Tensor) -> dict[str, Tensor]:
        """mask: (B, 2) bool = (image present, speech present). Absent inputs are zero-filled
        by the caller and every row must hold at least one True (checked upstream)."""
        present = mask.to(x_mri.dtype)
        h_mri, h_speech = self.enc_mri(x_mri), self.enc_speech(x_speech)
        f_mri, g_mri = self.gate_mri(h_mri, h_speech, mask[:, 1])
        f_speech, g_speech = self.gate_speech(h_speech, h_mri, mask[:, 0])
        fused = f_mri * present[:, :1] + f_speech * present[:, 1:]
        return {"logit": self.head(fused).squeeze(1), "fused": fused,
                "gates": torch.cat([g_mri, g_speech], dim=1) * present}


def modality_dropout(mask: Tensor, p: float, generator: torch.Generator | None = None) -> Tensor:
    """Rows holding both modalities lose the image with probability p, else the speech with
    probability p; never both. Single-modality rows are returned unchanged."""
    both = mask.all(dim=1)
    u = torch.rand(mask.shape[0], generator=generator, device=mask.device)
    out = mask.clone()
    out[both & (u < p), 0] = False
    out[both & (u >= p) & (u < 2 * p), 1] = False
    return out


class MaskConditionalTemperature(nn.Module):
    """One positive temperature per mask pattern, fitted on the internal validation split only."""

    def __init__(self):
        super().__init__()
        self.log_t = nn.Parameter(torch.zeros(3))      # speech only, image only, both

    def forward(self, logit: Tensor, mask: Tensor) -> Tensor:
        index = mask[:, 0].long() * 2 + mask[:, 1].long() - 1
        return logit / torch.exp(self.log_t[index])


class EvidentialHead(nn.Module):
    def __init__(self, hidden: int, classes: int = 2):
        super().__init__()
        self.linear = nn.Linear(hidden, classes)

    def forward(self, fused: Tensor) -> dict[str, Tensor]:
        alpha = 1.0 + F.softplus(self.linear(fused))
        strength = alpha.sum(dim=1, keepdim=True)
        return {"alpha": alpha, "prob": alpha / strength, "vacuity": alpha.shape[1] / strength.squeeze(1)}


def evidential_loss(alpha: Tensor, target: Tensor, kl_weight: float) -> Tensor:
    """log S - log alpha_y + kl_weight * KL(Dir(alpha_tilde) || Dir(1)); target is (B,) int64."""
    one_hot = F.one_hot(target, alpha.shape[1]).to(alpha.dtype)
    nll = torch.log(alpha.sum(dim=1)) - torch.log((alpha * one_hot).sum(dim=1))
    tilde = one_hot + (1.0 - one_hot) * alpha
    total = tilde.sum(dim=1, keepdim=True)
    kl = (torch.lgamma(total.squeeze(1)) - torch.lgamma(tilde).sum(dim=1)
          - torch.lgamma(torch.tensor(float(alpha.shape[1]), dtype=alpha.dtype, device=alpha.device))
          + ((tilde - 1.0) * (torch.digamma(tilde) - torch.digamma(total))).sum(dim=1))
    return (nll + kl_weight * kl).mean()


class _Reverse(Function):
    @staticmethod
    def forward(ctx, x: Tensor, weight: float) -> Tensor:
        ctx.weight = weight
        return x.view_as(x)

    @staticmethod
    def backward(ctx, grad: Tensor):
        return -ctx.weight * grad, None


class UnitAdversary(nn.Module):
    """Acquisition-unit classifier behind a gradient-reversal layer (section 3.4).
    The caller computes its cross-entropy on CN rows only."""

    def __init__(self, hidden: int, units: int):
        super().__init__()
        self.classifier = nn.Sequential(nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, units))

    def forward(self, embedding: Tensor, weight: float) -> Tensor:
        return self.classifier(_Reverse.apply(embedding, weight))
```

프레임워크와 무관한 전처리·평가 인터페이스는 하네스 함수가 곧 규격이다.

| 함수 | 입력 → 출력 |
| --- | --- |
| `combat_fit(samples, use_units, shrink)` | (단위, 공변량, 특징) 참조 행 → `{alpha, beta, sigma, units}` |
| `standardize(model, features, cov)` | → $z$ |
| `unseen_unit_params(zrows, labels, mode)` | 보정용 행 → $(\gamma^*, \delta^*)$, `mode ∈ {NONE, LABEL_FREE, CN_REF}` |
| `remove_unit(z, params)` | → $z^*$ |
| `unit_probe(z_by_unit, rng)` | → 균형 정확도, 영분포 평균·최댓값, `insideNull` |
| `audit_manifest(entries, pair_window)` | → 위반 코드 목록 |
| `delong(pos, neg, level)` · `delong_paired_cases(scores_a, scores_b, labels)` | → AUC, 분산, CI · 차이, z, p (두 점수를 한 라벨 벡터로 함께 나눠 사례 정렬을 강제) |
| `ece_equal_width` · `ece_equal_mass` · `calibration_slope` · `fit_temperature` | 보정 지표 |
| `match_pairs(rows, age_caliper, edu_caliper)` | → (사례, 대조) 쌍 |
| `route(case, cfg)` | → 출력 상태 코드 |

---

## 7. 외부 코호트 검증과 교란변수 통제 프로토콜

### 7.1 교차 평가 매트릭스

| 학습 | 평가 | 입력 | 성격 |
| --- | --- | --- | --- |
| ADNI 영상 (+음성 학습분) | OASIS-3 | 영상 단독 | **1차 외부 평가 A→B** |
| OASIS-3 영상 (+음성 학습분) | ADNI | 영상 단독 | **1차 외부 평가 B→A** |
| DementiaBank 학습 녹음 조건 | 같은 조건의 다른 화자 | 음성 단독 | 내부(화자 분리) |
| DementiaBank 학습 녹음 조건 | 학습에 없던 녹음 조건 | 음성 단독 | 2차(조건 제외) |
| 영상 + 음성 (R1 또는 R2) | 동시 보유 코호트 | 영상 단독 / 음성 단독 / 융합 | 2차. 공개 자료에는 대상이 없어 합성에서만 실행 |

각 칸마다 대상 수(양성/음성), AUROC, DeLong 95% CI, ECE(등폭·등질량, 보정 전·후), Brier, 보정 기울기를 보고한다. 영상 외부 평가는 `NONE`·`LABEL_FREE`·`CN_REF` 세 모드와 단위별 AUROC를 함께 낸다.

### 7.2 AUROC와 DeLong 구간 (DeLong 1988, Sun & Xu 2014)

양성 $X_1..X_m$, 음성 $Y_1..Y_n$, $\psi(x,y)=\mathbb 1[x>y]+\tfrac12\mathbb 1[x=y]$.

$$\hat\theta=\frac1{mn}\sum_{i}\sum_{j}\psi(X_i,Y_j),\qquad V_{10}(X_i)=\frac1n\sum_j\psi(X_i,Y_j),\qquad V_{01}(Y_j)=\frac1m\sum_i\psi(X_i,Y_j)$$

$$\widehat{\mathrm{Var}}(\hat\theta)=\frac{S_{10}}{m}+\frac{S_{01}}{n},\qquad S_{10}=\frac{1}{m-1}\sum_i\big(V_{10}(X_i)-\hat\theta\big)^2,\quad S_{01}=\frac{1}{n-1}\sum_j\big(V_{01}(Y_j)-\hat\theta\big)^2$$

$$\mathrm{CI}_{95\%}=\Big[\hat\theta\pm z_{0.975}\sqrt{\widehat{\mathrm{Var}}(\hat\theta)}\Big]\cap[0,1]$$

중간순위 $T_Z$(전체), $T_X$(양성 안), $T_Y$(음성 안)로 $O((m+n)\log(m+n))$에 계산한다.

$$\hat\theta=\frac{\tfrac1m\sum_iT_Z(X_i)-\tfrac{m+1}{2}}{n},\qquad V_{10}(X_i)=\frac{T_Z(X_i)-T_X(X_i)}{n},\qquad V_{01}(Y_j)=1-\frac{T_Z(Y_j)-T_Y(Y_j)}{m}$$

같은 대상에 매긴 두 점수 A·B의 비교:

$$z=\frac{\hat\theta_A-\hat\theta_B}{\sqrt{\dfrac{S_{10}^{AA}+S_{10}^{BB}-2S_{10}^{AB}}{m}+\dfrac{S_{01}^{AA}+S_{01}^{BB}-2S_{01}^{AB}}{n}}}$$

하네스의 대응 비교는 모두 `delong_paired_cases(scores_a, scores_b, labels)`를 거친다. 두 점수를 같은 라벨 벡터로 나누므로 사례 정렬이 관례가 아니라 구조로 보장되고, 길이가 다르면 실행을 거부한다.

대상당 한 행을 쓰므로 독립 가정이 성립한다. 종단 방문을 여러 행으로 평가할 때는 대상 단위 군집 부트스트랩으로 바꾼다.

### 7.3 사례 구성 층화

AUROC는 양성군 혼합에 대해 선형이다: $\mathrm{AUROC}=\pi_{\mathrm{MCI}}\,\mathrm{AUROC}_{\mathrm{CN\,vs\,MCI}}+\pi_{\mathrm{AD}}\,\mathrm{AUROC}_{\mathrm{CN\,vs\,AD}}$. 코호트마다 MCI 비율이 다르면 같은 모델의 통합 AUROC가 달라지므로 다음 셋을 함께 보고한다: 통합 AUROC, 단계별 AUROC, 기준 구성(MCI 0.6 : AD 0.4, 사전 등록값) 표준화 AUROC.

### 7.4 보정 지표

$$\mathrm{ECE}=\sum_{b=1}^{B}\frac{n_b}{N}\,\big|\bar y_b-\bar p_b\big|,\qquad B=10$$

등폭 구간과 등질량 구간 두 가지로 계산한다. Brier 점수와 보정 기울기($y\sim a+b\cdot\mathrm{logit}$의 $b$; $b<1$이면 과신)를 같이 낸다. 온도는 내부 검증 분할에서만 맞추고 외부 지표는 보정 전·후를 모두 적는다.

### 7.5 게이트 판정 규칙

| 게이트 | 조건 | 지위 |
| --- | --- | --- |
| `primaryPoint` | A→B, B→A의 `CN_REF` 통합 AUROC가 모두 $\ge 0.90$ | 과제 목표 지표 |
| `primaryLowerBound` | 두 방향의 DeLong 95% 하한이 모두 $\ge 0.90$ | 병기(엄격 기준) |
| `zeroAdaptation` | 두 방향의 `NONE` AUROC | 민감도 분석, 항상 보고 |
| `secondary` | 음성 미관측 조건, 동시 보유 융합(R1, R2) | 별도 보고 |
| `pipelineChecks` | 기지 정답 검사, 음성·양성 대조가 모두 통과 | 게이트 결과를 읽기 위한 전제 |

`pipelineChecks`가 FAIL이면 모든 게이트 값은 `NOT_EVALUATED`로 나온다(단위 시험으로 확인).

### 7.6 교란변수 통제

1. **입력 단계**: 모델 입력은 CN 규준 대비 공변량 보정 잔차 $z^*$다. 연령·성별·교육연수·ICV의 정상 범위 효과는 참조 모형이 제거하며 원 공변량은 모델에 넣지 않는다.
2. **공변량 단독 기준 모델**: 연령·성별·교육연수만으로 학습 코호트에서 맞춘 로지스틱 모델의 외부 AUROC. 본 모델의 변별력이 인구학적 차이에서 온 것이 아님을 보이는 대조다.
3. **매칭 검증**: 외부 시험 분할에서 사례마다 같은 성별, 연령 차 3세·교육연수 차 2년 이내의 미사용 대조를 척도화 거리 최소로 1:1 탐욕 매칭한다. 매칭 전후 표준화 평균차(SMD, 목표 $|{\rm SMD}|<0.1$), 매칭 부분집합의 모델 AUROC와 DeLong CI, 같은 부분집합의 공변량 단독 AUROC(0.5 근처여야 함)를 보고한다.
4. **단위 교란**: 단위별 유병률이 다른 상태에서 보정하지 않으면 모델이 단위 효과를 질병 신호로 배울 수 있다. 무보정 로지스틱 절제(`rawAuroc`)와 단위별 AUROC로 확인한다.

### 7.7 음성·양성 대조와 강건성

| 통제 | 방법 | 통과 기준 |
| --- | --- | --- |
| 시험 라벨 순열 | 외부 시험 라벨을 200회 섞어 AUROC 재계산 | 평균이 $0.5\pm3\,\mathrm{sd}/\sqrt{K}$ 안, 관측값이 전부보다 큼 |
| 학습 라벨 순열 | 학습 라벨을 40회 섞어 다시 적합(ComBat + 로지스틱 경로) | 같음. 단일 순열의 DeLong 구간은 0.5를 덮지 않을 수 있으므로 분포로 판정 |
| 누출 양성 대조 | 명부에 결함 9종을 주입 | 9종 모두 해당 위반 코드로 검출 |
| 효과크기 가정 | 합성 질병 효과 배율 0.5–1.25 | 판정이 가정에 따라 바뀌는지 표로 공개 |
| 시드 반복 | 코호트·단위 효과를 새로 뽑아 5회 | 통과 횟수와 최솟값 공개 |
| 보정용 표본 수 | 단위당 10–80행 | AUROC·ECE·단위 탐침의 변화 공개 |
| 라벨 접근 통제 | 외부 시험 라벨을 전부 뒤집어 전체 경로를 다시 실행. 따로, 보정용 분할 라벨을 뒤집어 다시 실행 | 시험 라벨: 내부 지표 동일, 외부 AUROC가 정확히 $1-\text{AUROC}$. 보정용 라벨: `NONE`·`LABEL_FREE` 불변, `CN_REF`만 변함 |

라벨 접근 통제는 단위 시험 `LabelAccessTests`가 실행한다(증거 문서 밖, A→B, 시드 163001). 시험 라벨 540개를 뒤집었을 때 외부 AUROC는 `NONE` 0.936255 → 0.063745, `LABEL_FREE` 0.910563 → 0.089437, `CN_REF` 0.933861 → 0.066139로 세 모드 모두 합이 1이었고 내부 지표는 같았다. 적합 단계가 시험 라벨을 읽지 않는다는 뜻이다. 보정용 라벨을 뒤집으면 `CN_REF`만 0.923199로 바뀌었다. 명부 감사는 분할의 겹침을 보고, 이 통제는 라벨이 실제로 흘러간 경로를 본다.

---

## 8. 실패 안전 상태 기계 (IEC 62304 정렬)

```
RECEIVED → VALIDATE ─ 실패 ─────────────→ REJECT_INPUT
              │
              ▼
          UNIT_CHECK ─ 보정 모수 없음 ───→ HOLD_UNIT_UNCALIBRATED
              │
              ▼
        SUPPORT_CHECK ─ 지원 범위 밖 ───→ ABSTAIN_OOD
              │
              ▼
           SCORE ─ 경계 확률·쌍 시점 초과 → REVIEW_LOW_CONFIDENCE
              │
              ▼
        ASSIST_REPORT
```

| 상태 | 진입 조건(위에서부터 먼저 걸리는 것이 우선) | 출력 |
| --- | --- | --- |
| `REJECT_INPUT` | 스키마 위반, 비유한 값, 두 모달리티 모두 없음, 금지 필드 | 점수 없음, 사유 코드 |
| `HOLD_UNIT_UNCALIBRATED` | 학습에 없던 단위인데 `CN_REF` 보정 모수가 없음 | 점수 없음, 보정용 표본 요청 |
| `ABSTAIN_OOD` | 연령이 [50, 95] 밖, 또는 $\mathrm{RMS}(z^*)>4$ | 점수 없음, 분포 밖 표시 |
| `REVIEW_LOW_CONFIDENCE` | $\hat p\in[0.35, 0.65]$, 또는 동시 보유 입력의 시점 차가 창을 넘음 | 점수와 구간, 우선 검토 표시 |
| `ASSIST_REPORT` | 위 조건에 해당 없음 | 점수, 사용 모달리티, 보정 모드, 임상의 검토 필수 표시 |

- 어느 단계든 예외나 시간 초과는 `REJECT_INPUT`으로 닫는다. 진단을 뜻하는 상태는 없다.
- 위험 통제와 하네스 근거:

| 위해 요인 | 통제 | 하네스 근거 |
| --- | --- | --- |
| 미보정 장비 입력의 계통 오분류 | `HOLD_UNIT_UNCALIBRATED` | A→B 외부에서 AUROC는 같아도(0.933 대 0.935) 고정 임계 0.5의 정확도는 `CN_REF` 0.859, `NONE` 0.776이다(evidential 출력 기준). 평균 vacuity는 0.342 대 0.333으로 이 이동을 드러내지 못했다. 그래서 불확실성 점수에 맡기지 않고 상태로 차단한다 |
| 원음·성문 반출 | 반출 스키마 검사 | 사례 8/8 |
| 분할 누출로 인한 성능 과대 | 실행 전 명부 감사, 위반 시 중단 | 결함 9/9 검출 |
| 시점이 어긋난 쌍 | 창 초과 시 마스크 강등 | 900명 중 30명 강등 |
| 경계 확률의 과신 | 검토 구간 라우팅 | A→B 오류율: 검토 구간 0.473(55명), 그 밖 0.097(485명) |

- IEC 62304 대응: 요구사항 분석(5.2) ↔ 9장 추적표, 단위 구현·검증(5.5) ↔ `tests/test_cog16_t163.py`, 시스템 시험(5.7) ↔ `run_block` 증거 문서, 위험 관리(7) ↔ 위 표, 형상 관리(8) ↔ fixture SHA-256 잠금과 결정적 출력. 소프트웨어 안전 등급은 제품 위험 분석에서 확정한다. 이 문서는 그 분석을 대신하지 않는다.

---

## 9. 요구사항 ↔ 하네스 검사 추적표

| 요청 산출물 | 설계 위치 | 하네스 구현 | 검사 |
| --- | --- | --- | --- |
| ComBat 수식 | 3.1–3.2 | `combat_fit`, `eb_unit`, `standardize`, `remove_unit` | `combatRecovery`(주입 효과 복원), 단위 시험 3건 |
| 미관측 기관 처리 | 3.3 | `unseen_unit_params`, `Harmonised.calibrate` | 유병률 편향 단위 시험, 세 모드 절제, 보정용 표본 수 표 |
| 도메인 적대적 정규화 | 3.4, 6 | 수식·인터페이스, `unit_probe`(특징·임베딩) | 탐침 영분포 단위 시험. 적대 학습은 미실행 |
| 결측 모달리티 정렬 스키마 | 4 | `manifest_entry`, `audit_manifest`, 창 강등 | 결함 9종, 깨끗한 명부 0건 |
| 모달리티별 인코더·게이트 어텐션 | 5.1 | `FusionNet.step` | 유한차분 기울기(최대 상대 오차 $4.4\times10^{-9}$), 결측 불변 시험 |
| 모달리티 드롭아웃 | 5.2 | `train_net`, `pseudo_pairs` | 단독·융합 세 마스크 모두 학습됨을 확인하는 단위 시험, R1·R2 비교 |
| Temperature Scaling | 5.3 | `fit_temperature` | 알려진 왜곡($T=2$) 복원 |
| Dirichlet evidential | 5.3 | `evidential_loss`, `train_evidential_head`, `evidential_predict` | 기울기 중심 차분, KL 닫힌 형태 대조, 무근거 입력에서 $\hat p=0.5$, $u=1$ |
| 교차 평가 매트릭스 | 7.1 | `run_direction` | 10장 |
| DeLong 95% CI | 7.2 | `delong`, `delong_paired`, `delong_paired_cases` | 손계산 예제(AUC 13/18, 분산 5/81), 전수 계산 일치, 동점·극값 |
| ECE | 7.4 | `ece_equal_width`, `ece_equal_mass` | 완전 보정 표본 0, 알려진 격차 0.4 |
| 연령·성별·교육연수 매칭 | 7.6 | `match_pairs`, `smd` | 캘리퍼·중복 사용 금지 단위 시험 |
| 판독 보조·실패 안전 | 8 | `route`, `validate_speech_payload` | 라우터 11/11, 반출 스키마 8/8 |
| 게이트 판정 | 7.5 | `run_block`의 `gates`, `effect_sweep`, `seed_replication` | 검사 실패 시 `NOT_EVALUATED`, 약한 효과 가정에서 FAIL을 내는지 단위 시험 |

---

## 10. 하네스 실측 결과 (합성 fixture, 시드 163001)

### 10.1 합성 가정

- 코호트: `SYN_A` 880명(스캐너 4대, 기관 3곳, 단위 유병률 0.30–0.70), `SYN_B` 780명(스캐너 3대, 기관 2곳, 0.25–0.55), `SYN_S` 780명(녹음 조건 3개), `SYN_P` 900명(합성 동시 보유).
- 단위 효과: 가법 $\gamma_{gj}$ 평균 −0.6–0.7(표준편차 단위), 승법 $\delta_{gj}$ 평균 0.8–1.3.
- 질병 효과(배율 1.0): 중증도 평균 MCI 1.6, AD 3.2에 특징별 계수(최대 −1.0)를 곱한다. 가장 큰 특징의 평균 편차가 MCI −1.6, AD −3.2 표준편차다. **이 값은 문헌 추정이 아니라 fixture 선언값이며, 게이트 판정은 이 가정에 따라 바뀐다(10.6절).**

### 10.2 영상 외부 교차 평가

| 방향 | 조건 | n (양성/음성) | AUROC | DeLong 95% CI | ECE 등폭 (보정 전 / TS 후) | Brier | 보정 기울기 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| A→B | 내부 시험 | 220 (108/112) | 0.890 | 0.849–0.932 | 0.056 / 0.058 | — | 0.98 |
| A→B | 외부 `NONE` | 540 (207/333) | 0.935 | 0.914–0.957 | 0.134 / 0.131 | 0.132 | 1.09 |
| A→B | 외부 `LABEL_FREE` | 540 | 0.909 | 0.883–0.936 | 0.144 / 0.142 | 0.150 | 1.57 |
| A→B | **외부 `CN_REF`** | 540 | **0.932** | 0.909–0.955 | 0.054 / 0.057 | 0.096 | 1.13 |
| B→A | 내부 시험 | 195 (75/120) | 0.948 | 0.920–0.975 | 0.069 / 0.083 | — | 1.35 |
| B→A | 외부 `NONE` | 560 (275/285) | 0.878 | 0.851–0.906 | 0.116 / 0.100 | 0.157 | 0.86 |
| B→A | 외부 `LABEL_FREE` | 560 | 0.915 | 0.893–0.937 | 0.320 / 0.304 | 0.267 | 2.46 |
| B→A | **외부 `CN_REF`** | 560 | **0.926** | 0.905–0.946 | 0.098 / 0.089 | 0.122 | 1.10 |

- `CN_REF` 대 `NONE`(같은 대상, DeLong 대응 비교): A→B 차이 −0.003 (p = 0.52), B→A 차이 +0.047 (z = 5.22, p < 0.001).
- 단위별 AUROC는 보정 모드와 거의 무관하다(A→B: 0.911 / 0.937 / 0.949 대 0.911 / 0.937 / 0.948). 통합 AUROC의 차이는 단위 사이 오프셋에서 나온다. 그래서 보정의 AUROC 이득은 단위 구성에 따라 있기도 없기도 하고, 보정(calibration) 이득은 두 방향에서 모두 나타났다(Brier 0.132 → 0.096, 0.157 → 0.122).
- `LABEL_FREE`는 3.3절의 예측대로 유병률을 흡수한다. B→A에서 ECE 0.320, 기울기 2.46.
- 단계별: A→B에서 CN 대 MCI 0.879, CN 대 AD 0.993, 기준 구성 표준화 0.925(시험 분할 구성 MCI 0.536). B→A에서 0.886, 0.984, 0.925.
- 로지스틱 절제(단위 항 없이 학습 대 ComBat): A→B 0.943 대 0.935 (p = 0.15), B→A 0.885 대 0.926 (p < 0.001).

### 10.3 단위 탐침과 보정용 표본 수

| 방향 | `NONE` | `LABEL_FREE` | `CN_REF` | 임베딩(`CN_REF`) | 우연 수준 | 영분포 최댓값(`CN_REF` / 임베딩) |
| --- | --- | --- | --- | --- | --- | --- |
| A→B | 0.698 | 0.496 | 0.434 | 0.397 | 0.333 | 0.425 / 0.406 |
| B→A | 0.739 | 0.336 | 0.411 | 0.316 | 0.250 | 0.328 / 0.323 |

보정용 80행에서 특징 수준 탐침은 영분포를 조금 넘고(잔여 단위 정보 있음), 인코더 임베딩은 영분포 안이다. 참조를 외부 CN 전체로 늘린 진단 항목(`DIAGNOSTIC_ALL_CN_REFERENCE`, 시험 행을 포함하므로 지표 계산에는 쓰지 않음)에서는 특징 탐침이 0.289(우연 0.333), 0.211(우연 0.250)로 영분포 안에 들어온다. 잔여분은 보정 방식의 결함이 아니라 보정용 표본의 추정 잡음이다.

| 단위당 보정용 행 | A→B AUROC / ECE / 탐침 | B→A AUROC / ECE / 탐침 |
| --- | --- | --- |
| 10 | 0.923 / 0.053 / 0.494 | 0.932 / 0.043 / 0.656 |
| 20 | 0.928 / 0.055 / 0.550 | 0.929 / 0.073 / 0.570 |
| 40 | 0.929 / 0.068 / 0.479 | 0.931 / 0.064 / 0.542 |
| 80 | 0.935 / 0.065 / 0.434 | 0.926 / 0.077 / 0.411 |

(ComBat + 로지스틱 경로.) AUROC는 보정용 표본 수에 둔감하고 단위 탐침은 표본이 늘수록 내려간다.

### 10.4 음성, 합성 동시 보유, evidential

| 평가 | n (양성/음성) | AUROC | DeLong 95% CI | ECE 등폭 (TS 후) |
| --- | --- | --- | --- | --- |
| 음성 내부(화자 분리) | 130 (68/62) | 0.833 | 0.764–0.902 | 0.106 |
| 음성 미관측 조건 `NONE` | 180 (76/104) | 0.885 | 0.834–0.936 | 0.074 |
| 음성 미관측 조건 `CN_REF` | 180 | 0.876 | 0.823–0.928 | 0.054 |
| 합성 쌍 R1 영상 단독 | 407 (209/198) | 0.924 | 0.898–0.950 | 0.058 |
| 합성 쌍 R1 음성 단독 | 407 | 0.887 | 0.856–0.918 | 0.055 |
| **합성 쌍 R1 융합** | 407 | **0.955** | 0.936–0.974 | 0.050 |
| 합성 쌍 R2 영상 단독 | 407 | 0.915 | 0.887–0.943 | 0.062 |
| 합성 쌍 R2 음성 단독 | 407 | 0.892 | 0.861–0.923 | 0.057 |
| **합성 쌍 R2 융합** | 407 | **0.951** | 0.931–0.971 | 0.035 |

- 융합 대 단독(대응 DeLong): R1에서 영상 대비 +0.031 (z = 3.36, p < 0.001), 음성 대비 +0.068 (z = 5.44). R2에서 +0.036, +0.060 (모두 p < 0.001).
- R1(의사쌍)과 R2(실제 합성 쌍)의 융합 AUROC·ECE·보정 기울기(0.99 대 0.97)는 이 표본에서 구분되지 않았다. 합성 생성기는 중증도를 공유하고 모달리티 잡음은 독립으로 두었다. 실제 쌍에서 모달리티 잡음이 상관되어 있으면 R1의 보정 상태가 달라질 수 있어 실제 쌍 확보 시 재확인 항목으로 둔다.
- R2 모델의 영상 외부 AUROC(A→B, `CN_REF`)는 0.923으로 R1(0.932)과 같은 범위다.
- Evidential 헤드(A→B 외부 `CN_REF`): AUROC 0.933, ECE 0.094, 평균 vacuity 0.342. vacuity로 오분류를 가려내는 AUROC 0.816. 정확도 0.859 → vacuity 하위 80%만 남기면 0.917. 합성 쌍 융합에서는 0.875 → 0.957.

### 10.5 교란 통제와 대조

| 항목 | A→B | B→A |
| --- | --- | --- |
| 공변량 단독 AUROC | 0.701 | 0.598 |
| SMD 매칭 전 (연령 / 성별 / 교육) | 0.627 / 0.388 / −0.251 | 0.258 / 0.145 / −0.244 |
| 매칭 쌍 수 | 175 | 223 |
| SMD 매칭 후 (연령 / 교육; 성별은 정확 일치) | 0.040 / −0.013 | 0.019 / −0.048 |
| 매칭 부분집합 모델 AUROC (95% CI) | 0.935 (0.910–0.961) | 0.926 (0.903–0.949) |
| 매칭 부분집합 공변량 단독 AUROC | 0.515 | 0.508 |
| 라우팅: `ASSIST_REPORT` n (오류율) | 485 (0.097) | 496 (0.139) |
| 라우팅: `REVIEW_LOW_CONFIDENCE` n (오류율) | 55 (0.473) | 64 (0.469) |

- 시험 라벨 순열 200회: 평균 0.500, 표준편차 0.024, 범위 0.439–0.556. 관측 0.932가 전부보다 크다.
- 학습 라벨 순열 40회: 평균 0.487, 표준편차 0.139, 범위 0.218–0.739. 관측 0.935가 전부보다 크다.
- 명부 결함 9종 전부 검출. 실제 사용 명부(R1 2,940행, R2 3,340행) 위반 0건.
- 기지 정답 검사 전부 통과: DeLong 손계산·전수 일치, ECE 0과 0.4, 기울기 검사, evidential 기울기·KL, ComBat 복원(γ 최대 오차 0.122, δ 0.065, 보정 후 잔여 0.025), 반출 스키마 8/8, 라우터 11/11.

### 10.6 게이트 판정과 가정 민감도

| 게이트 | AUROC | DeLong 하한 | 점추정 ≥ 0.90 | 하한 ≥ 0.90 |
| --- | --- | --- | --- | --- |
| 1차 A→B (`CN_REF`) | 0.932 | 0.909 | PASS | PASS |
| 1차 B→A (`CN_REF`) | 0.926 | 0.905 | PASS | PASS |
| 무적응 A→B (`NONE`) | 0.935 | 0.914 | PASS | PASS |
| 무적응 B→A (`NONE`) | 0.878 | 0.851 | FAIL | FAIL |
| 2차 음성 미관측 조건 | 0.876 | 0.823 | FAIL | FAIL |
| 2차 합성 쌍 융합 R1 | 0.955 | 0.936 | PASS | PASS |
| 2차 합성 쌍 융합 R2 | 0.951 | 0.931 | PASS | PASS |

`pipelineChecks` = PASS.

| 합성 질병 효과 배율 | A→B AUROC (95% CI), ComBat + 로지스틱 | 판정 |
| --- | --- | --- |
| 0.50 | 0.804 (0.766–0.842) | FAIL |
| 0.75 | 0.889 (0.860–0.919) | FAIL |
| 1.00 | 0.935 (0.913–0.957) | PASS |
| 1.25 | 0.959 (0.941–0.977) | PASS |

| 시드 오프셋 | A→B `NONE` | A→B `CN_REF` (하한) | B→A `NONE` | B→A `CN_REF` (하한) |
| --- | --- | --- | --- | --- |
| 1000 | 0.917 | 0.911 (0.885) | 0.919 | 0.925 (0.904) |
| 2000 | 0.925 | 0.925 (0.900) | 0.927 | 0.935 (0.915) |
| 3000 | 0.912 | 0.931 (0.909) | 0.939 | 0.935 (0.915) |
| 4000 | 0.899 | 0.881 (0.848) | 0.889 | 0.915 (0.890) |
| 5000 | 0.944 | 0.942 (0.924) | 0.916 | 0.935 (0.916) |

시드 반복 10회 평가 중 점추정 통과 9회, 하한 통과 7회, `CN_REF` 최솟값 0.881, 평균 0.923. `CN_REF`가 `NONE`보다 낮게 나온 경우도 있다(시드 4000의 A→B).

**읽는 법**: 합성 가정 배율 1.0에서 1차 게이트는 통과하고, 배율 0.75 이하에서는 탈락한다. 따라서 이 결과가 보이는 것은 게이트 판정 로직과 통제가 통과·탈락을 가정에 맞게 낸다는 사실이다. 실제 코호트에서 0.90에 도달하는지는 11장의 실행으로만 정해진다.

---

## 11. 실제 코호트 반입 시 실행 절차

1. **접근 조건 확인**: ADNI, OASIS-3, DementiaBank 각각의 DUA·승인 상태와 반출 가능 범위를 `docs/SOURCE-REGISTER.md`에 기록한다.
2. **특징 추출(추출 구역 안)**: 2.2절 규격으로 스칼라 특징을 만들고 `validate_speech_payload`와 행 스키마 검사를 통과한 것만 반출한다.
3. **사전 등록과 잠금**: 특징 인덱스 대응, 라벨 대응(2.3절), 분할 시드, 보정 모드(`CN_REF`, 단위당 보정용 행 수), 창 $W$, 캘리퍼, 기준 사례 구성, 게이트 임계 0.90을 fixture로 고정하고 SHA-256을 잠근다. 잠금 뒤에는 평가를 한 번만 실행한다.
4. **명부 감사**: `manifest_entry` 형식의 명부를 만들고 `audit_manifest`가 0건인지 확인한다. 코호트 사이 대상 중복 가능성은 반입 기록에 적는다.
5. **적재기 교체**: `build_cohorts`(합성 생성)를 2.1절 행 스키마를 내는 적재기로 바꾼다. `Harmonised`, `train_net`, `run_direction`, 지표·통제 함수는 자료와 무관하게 그대로 쓴다.
6. **실행**: 두 방향 `run_direction`과 통제(순열, 결함 주입, 매칭, 단위 탐침)를 돌려 10장과 같은 표를 낸다. `pipelineChecks`가 PASS일 때만 게이트를 읽는다.
7. **보고**: 영상 외부 평가와 음성 평가는 따로 계산해 보고한다(`docs/SOURCE-REGISTER.md` 16-3 행). 동시 보유 융합 게이트는 같은 대상의 영상·음성 쌍을 가진 코호트가 등록될 때까지 `미평가`로 둔다.
8. **torch 구현 대조**: 6장 모듈을 쓰는 경우 같은 fixture에서 하네스와 로짓 일치 시험을 먼저 통과시킨다. 적대 항(3.4절)은 단위 탐침이 영분포를 넘을 때만 켜고, 켠 전후의 탐침과 외부 AUROC를 함께 보고한다.

---

## 12. 참고 문헌

- Johnson, Li, Rabinovic (2007) — ComBat 경험적 베이즈 배치 보정.
- Fortin 외 (2018) — 다기관 피질 두께 측정의 ComBat 보정.
- Ganin 외 (2016) — 도메인 적대적 신경망과 기울기 반전층.
- DeLong, DeLong, Clarke-Pearson (1988) — 상관된 ROC 곡선 아래 면적의 비교.
- Sun, Xu (2014) — DeLong 분산의 중간순위 고속 계산.
- Guo 외 (2017) — 신경망 보정과 Temperature Scaling.
- Sensoy, Kaplan, Kandemir (2018) — Evidential deep learning.
- LaMontagne 외 (2019) — OASIS-3.
- Becker 외 (1994) — DementiaBank Pitt 말뭉치의 출처 연구.

저자·연도만 적었다. 서지 세부(권·쪽·DOI)는 원문 대조 뒤 `docs/SOURCE-REGISTER.md`에 등록한다.
