Newer
Older
TIASJudgeModel / docs / 04_SPEC / SPEC_02_学習・評価仕様.md

学習・評価仕様 (Training & Evaluation)

前処理 (Preprocessing)

項目方針
リサイズアスペクト比を保って 224×224 に収め,余白は黒で埋める(元画像は幅 449〜900・高さ 279〜1025 でばらつく)
正規化ImageNet の mean / std(事前学習の統計に合わせる)
マスク外領域黒 (0, 0, 0)(実データで確認済み.TEST_01).リサイズ時のパディングも黒で統一する

データ拡張 (Augmentation)

色を変える拡張は既定で無効にする. 舌の色そのものが正解ラベルであり,color jitter・彩度変更・ホワイトバランス変更はラベルを壊す.

使う使わない(既定)
左右反転・微小回転・微小スケール・ランダムクロップ色相・彩度・明度ジッタ・グレースケール化・強い正規化

※ 撮影条件差の吸収のために色系の拡張を試す場合は,独立した実験として効果を測る.

モデル (Model)

項目初期設定
アーキテクチャConvNeXt-Tiny(timm・ImageNet 事前学習)
出力5 クラス
学習方式全層ファインチューニング(データが少ないと判断した場合は段階的凍結を試す)
最適化AdamW + cosine annealing(warmup 付き)
早期終了検証セットの macro F1 を基準にする

まず 1 本を通すことを優先し,比較は timm での差し替えで後から行う.

学習の設定 (Training Settings)

設定 YAML の model・train 節で与える(SPEC_01「設定ファイル」).既定値は次のとおり.

項目既定値備考
model.nameconvnext_tinytimm のモデル名
model.pretrainedtrueImageNet 事前学習の重み(初回はサーバがダウンロードする).合成データの試験では false
train.image_size224アスペクト比を保って長辺を合わせ,余白を黒で埋める(レターボックス)
train.batch_size32—
train.max_epochs40—
train.patience8検証の macro F1 がこのエポック数改善しなければ打ち切る
train.lr1e-4AdamW
train.weight_decay0.05AdamW
train.warmup_epochs2線形 warmup の後 cosine で減衰
train.inner_val_ratio0.15交差検証の各 fold で,学習側から早期終了用の検証データを取る割合(下記)
train.num_workers4DataLoader
train.amptrueCUDA のときだけ混合精度
train.class_weightingnone損失のクラス重み(下記「クラス不均衡」→「対策の設定」)
train.samplerrandom学習データの抜き出し方(同上)
  • データ拡張(学習時のみ): 左右反転(確率 0.5)・回転 ±10°・拡大縮小 0.9〜1.1 倍・平行移動わずか.はみ出した部分は黒で埋める.色を変える拡張は行わない(上記「データ拡張」)
  • 正規化: ImageNet の mean / std
  • デバイス: CUDA があれば cuda(CUDA_VISIBLE_DEVICES で割り当てられた GPU),無ければ CPU
  • 高速化のため,レターボックス済みの画像(uint8)をメモリに載せてから学習してよい(1085 枚 × 224² × 3 ≒ 160 MB)
  • 平行移動は ±5%.学習時は端数のバッチを捨てる(BatchNorm を持つモデルで 1 件のバッチになると止まるため)
  • 学習率は step 単位の線形 warmup の後,max_epochs の終わりで 0 になる cosine
  • 早期終了は検証 macro F1 の厳密な改善(>)で判定し,同点なら先のエポックを残す
  • convnext_tiny の事前学習の重みは timm の既定タグ convnext_tiny.in12k_ft_in1k(ImageNet-12k で事前学習し ImageNet-1k で微調整したもの)が使われる

交差検証の実行 (Cross-Validation Run)

1 つのジョブで 5 つの fold を順に学習・評価し,1 つの実験ディレクトリ runs/<実験ID>/ にまとめて書き出す.「ややあり込み」と「ややあり除外」は別のジョブ(別の実験 ID)として回す.

  1. manifest 全体で患者単位・9 グループ層化の 5-fold 分割を作る(data.severities に関わらず同じ分割.上記「程度(ややあり)の扱い」)
  2. 各 fold について
    1. 学習側(held_out 以外)から train.inner_val_ratio の患者を検証用に取る(患者単位・9 グループ層化.data.severities を適用する前の全程度から取る.seed は split.seed + fold 番号.学習の seed も同じ)
    2. data.severities を学習用と検証用にだけ適用する(held_out には適用しない.下記)
    3. 検証の macro F1 が最良のエポックの重みを採用する(早期終了)
    4. held_out(全程度を含む)で 1 回だけ評価する
  3. 全 fold の held_out の予測をまとめて集計する

評価の出し方 (Evaluation Output)

held_out は data.severities に関わらず全程度の画像で評価し,次の部分集合ごとに指標を出す.これで 2 つの条件のジョブを同じ土俵で比べられる.

部分集合内容用途
allheld_out の全画像ややあり込みの全体性能
commonheld_out のうち strong と normal2 条件の比較(両条件とも学習に含まれる部分)
mildheld_out のうち mild境界例の扱い
  • 各部分集合で: macro F1・accuracy・クラス別 precision / recall / F1・混同行列(common と all)
  • mild では加えて予測の内訳を出す: 淡紅と予測された割合・元のクラスと予測された割合・その他のクラスと予測された割合(クラス別と全体)
  • fold ごとの値と,fold 間の平均 ± 標準偏差(不偏標準偏差.ddof=1)を出す.全 fold の予測をまとめた値(pooled)と,混同行列の全 fold の合計も出す
  • macro F1 は,その部分集合の正解に現れるクラスだけで平均する(mild には淡紅の正解が無いため,5 クラスで平均すると淡紅への予測が F1 = 0 として平均を下げてしまう).クラス別の指標も正解に現れるクラスだけを出す.淡紅への流れ込みは mild の予測の内訳で見る
  • 画像ごとの予測(個票)は predictions.csv に保存し,回収しない

出力 (Run Artifacts)

SPEC_01「実験の出力」の名前に従う.

ファイル内容回収
config.yaml解決済み設定・git ハッシュ・seedする
metrics.jsonfold ごと・fold 平均の指標(上記の部分集合ごと),各 fold の最良エポック,所要時間(下記)する
history.csvfold・エポックごとの学習損失・検証損失・検証 macro F1・学習率・そのエポックの所要秒数する
confusion_matrix.csv全 fold 合計の混同行列(common と all)する
log.txtログする
figures/*.png学習曲線・混同行列する
split.json・predictions.csv分割と個票の予測(患者 ID を含む)しない
checkpoints/fold<k>.pt各 fold の最良の重み(サーバには常に保存する)--with-checkpoints のときだけ

所要時間の記録(2026-09-28 ユーザー要望): metrics.json に次を含める.モデル間・条件間で計算コストも比べられるようにするため.

  • 全体の所要秒数(データの読み込みから書き出しまで)と,データの読み込み(レターボックス化)にかかった秒数
  • fold ごとの学習の所要秒数・held_out の評価の所要秒数・実際に回したエポック数・1 エポックあたりの平均秒数
  • 実行環境: デバイス(GPU 名または CPU)・モデル名・パラメータ数

データ分割 (Data Split)

すべての分割は患者単位で行う. 同一患者の画像が複数の分割に跨ってはならない.

現在のデータセットは 1 患者 1 枚であることを確認済みのため(2026-09-27.TEST_01),画像単位の分割が患者単位の分割と一致する.ただし実装は常に patient_id 単位で分割し,将来同一患者の複数枚が追加されても正しく動くようにする.

局面方式
日常の試行患者単位の単一分割(train / val / test).クラス分布を層化する
結論を出すとき患者単位の 5-fold 交差検証
  • 分割は patient_id と seed から決定的に生成し,config.yaml に記録する
  • test セットはモデル選択に使わない(val で選び,test は最後に一度だけ見る)
  • 層化は「クラス × 程度」(<label>_<severity> の 9 グループ)で行う.「ややあり除外」の条件で mild を抜いた後も,各分割のクラス比が保たれるようにするため(下記「程度(ややあり)の扱い」).最小のグループは 51 枚で,比率 0.15 なら各分割に 8 枚程度入る
    • 患者の層化グループは,その患者の label と severity で決める.同じ患者に複数の severity がある場合(将来の複数枚)は,最も強いもの(strong > mild > normal)を使う
  • 分割後にグループごとの件数を記録し,偏りを確認する
  • 実装方式: 1 患者は 1 つの label しか持たない(manifest の検証で保証)ため,患者の一覧を層化グループで分ければ「患者単位」と「層化」が同時に成り立つ
    • 単一分割: 層化グループごとに患者を seed で並べ替え,「患者数 × 比率」の四捨五入で test・val の人数を取り,残りを train とする.train に患者が残らないグループがあれば停止する
    • K-fold: 患者の一覧に scikit-learn の StratifiedKFold(shuffle あり・seed 指定.層化ラベルは層化グループ)を使う.評価側を held_out と呼ぶ(fold 内でさらに val を取るかは Step 4・5 で決める).患者数が fold 数に満たないグループがあれば停止する
    • どちらも最初に患者 ID を並べ替えるため,manifest の行順に依存しない
  • 分割結果(各分割の患者 ID)は個票に当たるため手元へ回収しない(サーバ上の runs/ にのみ保存する.SPEC_01「回収ホワイトリスト」)

程度(ややあり)の扱い (Severity Handling)

「ややあり」(mild)は,臨床上は淡紅(正常)と判断されることが多い境界例である(2026-09-27 ユーザー確認).学習に入れるか否かで結果が変わりうるため,入れる場合と入れない場合の両方で実験する.

実験条件 (Conditions)

設定 data.severities(SPEC_01「設定ファイル」)で使う程度を選ぶ.

条件data.severities使う画像枚数
ややあり込み[strong, mild, normal](既定)すべて1085
ややあり除外[strong, normal]「あり」と淡紅(正常)754
  • どちらの条件でも label は元のまま(5 クラス).「ややあり」を淡紅に付け替える扱いは行わない
  • 「ややあり除外」でも 5 クラスすべてが残る(淡紅は normal のみ,他の 4 クラスは strong が残るため)

分割との関係 (Split)

  • 分割は data.severities に関わらず,manifest 全体(1085 枚)で 1 回だけ行う.条件ごとの除外は,分割の後に各分割(train / val / test)から該当する画像を取り除いて適用する
  • これにより,「ややあり除外」の各分割は「ややあり込み」の各分割の部分集合になり,2 つの条件のモデルを同じ患者の test で比較できる(条件ごとに分割し直すと test の患者が入れ替わり,差が条件によるものか分割によるものか切り分けられない)
  • 同じ seed なら,どちらの条件でも同じ分割結果になる

評価 (Evaluation)

評価対象目的
条件間の比較test のうち strong と normal(両条件に共通する部分)同じデータで 2 つの条件のモデルを比べる
ややあり込みモデルの層別評価test 全体と,mild の部分境界例でどれだけ誤るか(「評価指標」の層別評価)
ややあり除外モデルに mild を推論させるtest のうち mildmild の画像が 淡紅と予測される割合と元のクラスと予測される割合を出す.臨床で淡紅と判断されやすいという知見がデータ上でも成り立つかを確かめる
  • mild の画像に対して淡紅と予測することを「誤り」と断定しない.上表の割合として報告し,解釈は人間が行う

クラス不均衡 (Class Imbalance)

実データのクラス分布は最大 / 最小 = 380 / 104 ≒ 3.7 倍(TEST_01).中程度の不均衡であり,初回は対策を入れずに素の結果を見る.混同行列でどのクラスが落ちているかを確認してから,クラス重み付き損失等を独立した実験として追加し,効果を切り分けて評価する.

程度ラベル(severity)を含めた 9 クラス分類は採らない(最小クラス 51 枚・不均衡 6.6 倍となり学習が不安定になる.目的は TIASjudge の 5 分類の置き換えであるため).

対策の設定 (Countermeasures)

素の結果(TEST_03)で紫・淡白の再現率が低かったため,次の 2 つを設定で選べるようにする.既定はどちらも「対策なし」で,既定のままなら従来と同じ学習になる.

キー値内容
train.class_weightingnone(既定)・inverse・sqrt_inverse損失のクラス重み.クラス c の学習画像数を n_c,全体を N,クラス数を K として,inverse は w_c ∝ N / (K・n_c),sqrt_inverse はその平方根.重みは学習データでの平均が 1 になるよう正規化する(Σ_c (n_c / N)・w_c = 1.損失の大きさを対策なしと揃え,学習率の意味を変えないため)
train.samplerrandom(既定)・balancedbalanced は各画像を 1 / n_c の確率で重複ありで抜き出し,どのクラスもほぼ同じ頻度で学習に出す.1 エポックに抜き出す枚数は学習画像数と同じ
  • クラスの画像数 n_c は,**各 fold の学習用データ(data.severities を適用した後)**から数える.検証用・held_out の枚数は使わない
  • 学習用データに 1 枚も無いクラスがある場合は停止する(重みが定義できないため).対策を指定しないとき(既定)は従来どおり停止しない
  • 重み付きの損失はバッチの件数で割って平均する(Σ_i w_{y_i}・CE_i / B).PyTorch の CrossEntropyLoss(weight=w) の既定は重みの和で割るため,上の正規化が効かなくなる.正規化で揃うのは学習データ全体での平均であり,1 つのミニバッチの損失はクラス比の偏りに応じて対策なしとずれうる
  • sampler: balanced でも,学習時に端数のバッチを捨てる規則(「学習の設定」)は従来どおりかかる
  • 重みは学習時の損失にだけかける.検証の損失は重みなしで計算する(対策の有無で早期終了の比較が変わらないようにするため.早期終了の基準は従来どおり検証 macro F1)
  • class_weighting と sampler: balanced は同時に指定できるが,偏りを二重に補正することになるため実験では組み合わせない
  • metrics.json に,使った対策(トップレベルの imbalance)と,fold ごとのクラス別枚数・クラス重み・1 回の抜き出しで各クラスが選ばれる確率(各 fold の class_balance.使わない側は null)を記録する
  • 重みを使うと history.csv の学習損失は重み付きの値になる(検証損失は重みなし).対策なしの run と学習損失を直接比べない

効果は,対策なしの結果と同じ seed・同じ fold どうしの対応比較で評価する(common の macro F1 と,クラス別の再現率・適合率・混同行列).少ないクラスの再現率が上がっても,他クラスの適合率が下がる入れ替わりが起きうるため,全体の数字だけで判断しない.

色特徴分類器 (Color Feature Classifier)

ラベルが舌の色そのものであり,データが少ないため,舌領域の色の統計量を特徴にした古典的な分類器を CNN と同じ分割・同じ評価で比べる(tias-train color-cv.結果は TEST_05).GPU は使わない.

特徴量 (Features)

元解像度の画像の舌領域の画素だけから取る(背景は RGB がすべて 0 の画素.レターボックス縮小は補間で背景の黒が縁に混ざるため使わない).長辺が color.max_side(既定 512.null で縮小しない)を超える画像は最近傍法で縮小する(新しい色を作らないため).画素を CIE L*a*b*(D65)に変換し,次の 107 次元を作る.

特徴次元
L*・a*・b*・C*(彩度 √(a*² + b*²))・h(色相角 atan2(b*, a*).度,(−180, 180])それぞれの平均・標準偏差(母標準偏差.ddof=0)・10/25/50/75/90 パーセンタイル5 × 7 = 35
a*-b* の 2 次元ヒストグラム(a* 0, 50・b* −10, 30 を 8 × 8 に等分.舌領域の画素数に対する割合)64
L* のヒストグラム(20, 100 を 8 等分.割合)8
  • ヒストグラムの範囲外の画素は端のビンに入れる(割合の合計を 1 に保つ).範囲は全データの平均色(TEST_02)を根拠に固定する
  • 色相角は 0° をまたがないよう (−180, 180] で表す(舌の色は赤〜紫で a* > 0 に集まるため)
  • 舌領域の画素が無い画像は停止する(manifest の何件目かで示し,ファイル名は出さない)

分類器と候補の選び方 (Classifier & Selection)

設定の color 節で指定する(CNN の設定には不要.color 節が無い設定で color-cv を実行すると停止する).

キー値内容
color.classifierlogistic・hist_gb標準化+多クラスのロジスティック回帰,または scikit-learn の HistGradientBoostingClassifier
color.logistic.c_values正の数のリスト(既定 0.01・0.1・1・10・100)正則化の強さ C の候補
color.hist_gb.learning_rates × max_depths正の数・正の整数または null のリスト学習率と木の深さの候補(組み合わせ)
color.max_side正の整数または null(既定 512)特徴抽出の前に縮小する長辺の上限
  • 分割は CNN と同じ(「交差検証の実行」の 1〜2.2.同じ seed なら split.json が完全に一致する)
  • 各 fold で,候補ごとに学習用データで学習し,内側の検証の macro F1 が最良の候補を採用する(厳密な改善で更新し,同点なら先の候補.CNN の最良エポックの選び方に対応).採用したモデル(学習用データだけで学習したもの)で held_out を 1 回だけ予測する
  • train.class_weighting のクラス重み(「クラス不均衡」)を sample_weight として使う.train.sampler: balanced は未対応で,指定すると停止する
  • 学習用データに無いクラスの予測確率は 0 とし,5 クラスの形を保つ
  • 出力は CNN と同じ名前・同じ形式(「出力」).ただし history.csv・学習曲線・重み(checkpoints/)は出さない.metrics.json の environment.model_name は color_logistic・color_hist_gb,最良エポックは null で,fold ごとの採用した候補(selected_params)と候補ごとの検証 macro F1(candidates),特徴名(color_classifier),特徴抽出の所要秒数(timing.feature_extraction_seconds)を加える

評価指標 (Metrics)

指標役割
macro F1主指標(クラス不均衡下で少数クラスを無視できないため)
accuracy参考(クラス分布に引っ張られる)
クラス別 precision / recallどのクラスが弱いかの特定
混同行列誤りの方向(隣接クラスとの混同か,飛んだ誤りか)の確認
  • 舌色が順序尺度として扱える(淡白 → 淡紅 → 紅 → 暗赤紅 → 紫 のような並び)なら,quadratic weighted kappa を追加する.順序の妥当性は TIASjudge の定義と医学的な解釈を確認してから決める(未確定)
  • 程度別の層別評価: severity ごと(strong / mild)にクラス別 recall を出す.「ややあり」(mild)は境界例であり誤りが集中すると予想されるため,全体の指標だけでは改善の効果が見えない
  • 医師ラベルの評価者間一致率が分かる場合は,モデル性能の上限の目安として併記する

ベースライン (Baseline)

既存 TIASjudge のルールベース診断を同一テストセットで実行し,同じ指標で並べる.実装の取り込み方(移植 / 別リポジトリ参照)は未定とし,ベースライン実装に着手する時点で決める.

再現性 (Reproducibility)

  • seed を設定ファイルで固定し,config.yaml に git コミットハッシュとともに記録する
  • 完全なビット再現性(torch.use_deterministic_algorithms)は速度とのトレードオフのため求めない.代わりに同一設定で複数 seed を回して振れ幅を確認する
  • 共用サーバのため,使用する GPU は明示的に指定する

未確定事項 (Open Items)

  • 舌色 5 分類を順序尺度として扱えるか(quadratic weighted kappa の採用可否)
  • 入力解像度 224 で足りるか(元画像は平均 675×614 で,約 1/3 に縮小することになる)
  • 黒背景が面積の大半を占めるため,正規化・統計を画像全体で取るか舌領域内のみで取るか
  • 「ややあり」を含めるか否かの結論(両条件の実験結果を見て決める)
  • ルールベース診断の取り込み方(色特徴分類器の「平均色だけ」の版を作れば,TIASjudge に近い条件の比較の土台になる.TEST_05)
  • 医師ラベルの評価者間一致率(分かれば性能の上限の目安になる)