SOLAR FILAMENT MODEL ARCHITECTURE モデル構成の変遷と EdgeAttNet(EG-MHSA)アーキテクチャの数理仕様

0.39 Kaggle Public LB(444チーム中 13〜16位相当)
+0.0028 全707観測 OOF 有意改善(95%CI [0.0009, 0.0048])
35.2M MediSegEdgeAttUNet パラメータ総数
3× 高速 単一モデル推論による計算コスト 1/3 圧縮

01歴代アーキテクチャの比較と変遷マトリクス

太陽フィラメント検出は、全面像(2048 × 2048 px)に対する対象領域(前景)の比率が約 0.23% と小さく、微細な線状境界の正確な抽出が求められる課題です。本プロジェクトでは、セマンティック抽出からインスタンス分離、U-Net による輪郭精密化、エッジ誘導型自己注意機構(EG-MHSA)へとモデル設計を順次検証・改良してきました。

世代 / アプローチ モデル構成 パラメータ 全Fold OOF Public LB 特長と課題・棄却理由
第1世代
Semantic U-Net
3D/2D U-Net (面塗り) ➔ 連結成分抽出 31.0M 0.1920 0.19 近接するフィラメントが1つの塊に癒着し、検出個体数が過小評価されてFN/FPが二重発生。【構造的限界により棄却】
第2世代
YOLO11-seg 単段
YOLO11m-seg (1280px, 3-Fold) 約 22M 0.3937 0.32 個体ごとの分離・発見には成功するが、プロトタイプマスクの解像度が粗く、細線境界で IoU 0.5 の合格閾値を下回る。
第3世代
2段パイプライン (V35)
YOLO11m-seg + MediSeg U-Net (shrink@0.45) 53.0M (アンサンブル) 0.4426 0.37 YOLO候補を U-Net で内側から削り込み輪郭を締める。3-Fold アンサンブルに依存し計算時間が 3倍。
第4世代(現行)
EdgeAttNet (V52)
YOLO11m (Fold 2単体) + MediSegEdgeAttUNet (EG-MHSA) 35.2M 0.4454 0.39 EG-MHSA によるエッジ誘導型自己注意を導入。単一モデル構成により推論処理時間を抑制しつつ精度を確保。

※ コンペティション主催者は公式アナウンス(2026年8月20日)において、「PQ スコアが 0.35 を超えるモデルは学術的・実用的に大きな価値がある(Any PQ score of greater than 0.35 is of great value to us)」と述べています。第4世代 EdgeAttNet(V52)は、単一モデル構成でありながら Public LB 0.39(全Fold OOF 0.4454)を達成し、この有用性基準を満たしています。

02Instance-First 2段パイプライン全体構成

推論パイプラインは、「全体像からの個体候補検出(YOLO)」と「切り出しパッチに対する境界精密化(EdgeAttNet)」の 2 段階で構成されています。

H-Alpha 全面像 2048 × 2048 px 前景率 0.23% Stage 1: 個体候補検出 YOLO11m-seg (Fold 2) 解像度: 1280 × 1280 conf 閾値: 0.26 単一モデル推論 (計算コスト 1/3・3倍高速) Stage 2: 境界精密化 (Refiner) MediSegEdgeAttUNet (35.2M) パッチ Crop (320px, context 1.65) EG-MHSA (エッジ誘導自己注意) 微細な主軸 (Spine)・側枝 (Barb) 保持 shrink@0.45 内側削り込み mask = refined ∧ proposal 排他化 & 出力 COCO RLE 重複画素の排除 drop_contained Public LB 0.39 13〜16位相当

図: 推論パイプライン構成。全体像からの候補検出(YOLO11m)と局所パッチの境界精密化(MediSegEdgeAttUNet)を組み合わせた処理フロー。

03MediSegEdgeAttUNet の設計思想と EG-MHSA の数理構成

1. 医療画像セグメンテーション(MediSeg)手法の適用背景

医療画像解析(網膜血管や神経線維などの微小構造抽出)では、背景に対して対象領域が極めて小さく、細線構造の連続性維持が重要視されます。これは太陽フィラメントや太陽電池電極の欠陥検査における課題と数理的に共通しています。
本モデルでは、医療画像セグメンテーションで用いられるマルチスケール U-Net 構造を基盤とし、ボトルネック層にエッジ誘導型自己注意機構(EG-MHSA, arXiv:2509.02964)を組み込んだ MediSegEdgeAttUNet(約 35.2M パラメータ) を構築しました。

① Drop-in 互換性

入出力インターフェースを (B, 3, 320, 320) ➔ (B, 1, 320, 320) に統一。既存パイプラインへの容易な組み込みを実現。

② 低計算コスト Attention

20×20(400 トークン)のボトルネックでのみ自己注意を実行。計算量 \(O(N^2)\) の増加を抑え、推論遅延を最小化。

③ 微細側枝 (barb) の保持

入力画像のエッジ勾配を自己注意に注入。ダウンサンプリング時に失われがちな主軸(spine)や側枝(barb)の連続性を保護。

2. EG-MHSA(エッジ誘導自己注意)の数理メカニズム

フィラメントの境界は太陽表面の模様と類似しており、通常の Self-Attention では細い側枝(barb)や尖端が背景特徴に埋没してアテンションが拡散する傾向があります。 EG-MHSA は、入力画像から抽出した空間エッジ事前分布を Query および Key に直接加算注入することで、境界構造へのアテンション重みを適切に誘導します。

Bottleneck 特徴量 x [B, 1024, 20, 20] 入力パッチ画像 Input [B, 3, 320, 320] エッジ事前分布 E E = σ(Conv3×3(Input)) [B, 1, 320, 320] ボトルネック射影 E' Conv1×1(Interp(E, 20×20)) [B, 1024, 20, 20] Q, K, V テンソル生成 Q = x_flat + E'_flat K = x_flat + E'_flat V = x_flat (加算なし) Multi-Head Self-Attention num_heads = 8 Attn = softmax(QKᵀ / √d) V LayerNorm(Attn + x) Decoder へ [B, 1024, 20, 20]

図: EG-MHSA の数理フロー。入力パッチ画像から抽出した空間エッジ事前分布 E' を Query と Key に注入し、幾何構造に対する自己注意の重みを強化。

3. 境界精密化のための複合損失関数(Composite Refiner Loss)

MediSegEdgeAttUNet の学習では、極細線の境界を適切に捉えるため、領域損失(BCE + SoftDice) に加えて、モルフォロジー勾配を用いた 境界差分損失(Boundary L1 Loss) を組み合わせた複合損失を採用しています。

Loss = 0.35 · BCE(logits, targets) + 0.65 · SoftDice(logits, targets) + 0.15 · L1(Boundary(pred), Boundary(targets))

ここで Boundary(M) = MaxPool(M, k=5) - (-MaxPool(-M, k=5)) は、モルフォロジー演算(膨張 − 収縮)により輪郭境界(幅 2〜3px)を抽出する処理です。この境界損失により、輪郭付近の誤差を低減し、境界追従性を向上させます。

4. 推論時における内側削り込み(shrink 演算)との結合

推論時は、YOLO11m が提案した個体候補 P と、MediSegEdgeAttUNet が出力した確率マップ \(M_{\text{prob}} \ge 0.45\) の積集合(AND 演算)を計算します:

\[ R_{\text{refined}} = (M_{\text{prob}} \ge 0.45) \land P_{\text{proposal}} \]

YOLO が候補領域の大枠を特定し、MediSegEdgeAttUNet がその内側の微小境界を精査することで、過大検出を抑制しながら境界精度の向上を図っています。

04レイヤー別テンソル遷移マップ (Layer Inspector)

入力パッチ画像(320 × 320 px)が MediSegEdgeAttUNet 内を通過する際のテンソル形状遷移です。確認したいレイヤーカードをクリックすると、直下に詳細なパラメータ・演算構成・役割が表示されます。

Input Patch
[B, 3, 320, 320]
YOLO候補bbox中心 + context 1.65
DoubleConv (inc)
[B, 64, 320, 320]
Conv3×3 ➔ BN ➔ ReLU (×2)
Down 1
[B, 128, 160, 160]
MaxPool2d(2) ➔ DoubleConv
Down 2
[B, 256, 80, 80]
MaxPool2d(2) ➔ DoubleConv
Down 3
[B, 512, 40, 40]
MaxPool2d(2) ➔ DoubleConv
Down 4 + EG-MHSA
[B, 1024, 20, 20]
8-Head Self-Attention + Edge Prior
Up 1 (+Skip Enc4)
[B, 512, 40, 40]
ConvTranspose2d ➔ Concat ➔ Conv
Up 2 (+Skip Enc3)
[B, 256, 80, 80]
ConvTranspose2d ➔ Concat ➔ Conv
Up 3 (+Skip Enc2)
[B, 128, 160, 160]
ConvTranspose2d ➔ Concat ➔ Conv
Up 4 (+Skip Enc1)
[B, 64, 320, 320]
ConvTranspose2d ➔ Concat ➔ Conv
Output Head
[B, 1, 320, 320]
Conv1×1 ➔ Sigmoid 確率マップ
Down 4 + EG-MHSA Bottleneck (最深部)
パラメータ数: 約 15.21 M
テンソル形状 (B, C, H, W)
[B, 1024, 20, 20]
受容野 / 空間解像度
20 × 20 px (1/16 縮小)
特徴マップ役割
大域トポロジー & エッジ誘導注意

ボトルネック層における自己注意機構。入力画像から抽出した空間エッジ事前分布 E' を Query および Key に加算注入し、20×20 の特徴マップ上で大域的な関係性を捉え、微細な線状構造(spine / barb)の連続性を維持します。

MaxPool2d(2×2, stride=2) ➔ DoubleConv(512 ➔ 1024) ➔ EdgeGuidedMHSA(1024ch, 8-Heads, E'=Conv1x1(Interp(E)))

05Fold 2 単体推論採用の工学的根拠

本プロジェクトでは、3-Fold のアンサンブルを行わず、Fold 2 単体モデル を本番推論に採用しています。これには客観的な検証データに基づく 2 つの理由があります。

検証フェーズ Fold 0 Fold 1 Fold 2 3-Fold 全結合 (n=707)
生 YOLO のみ (refiner 前) 0.3978 (最高) 0.3882 0.3950 0.3937
確定後処理込み (V35 基準) 0.4393 0.4415 0.4471 (最高値) 0.4426
EdgeAttNet 改善幅 (Δ) +0.0062 +0.0009 +0.0012 +0.0028 (95%CI 有意)
  • 理由 1: 後処理込みの OOF PQ スコアが 3-Fold 中で最も高い(0.4471)
    生 YOLO 単体では Fold 0 が高かったものの、U-Net による境界精密化(shrink@0.45)を通した確定スコアでは Fold 2 が +0.0521 伸び、3-Fold 中最高のスコアを記録しました。
  • 理由 2: アンサンブル統合による推論遅延の排除
    3 つのモデルを多数決で統合するアンサンブルは計算時間を 3 倍消費します。Fold 2 単体に絞ることで、推論処理時間を約 1/3 に低減しつつ、検証スコア(Public LB 0.39)を維持しました。
OOF スコア(0.4454)と Public Leaderboard(0.39)が単純比較できない理由:
・評価母集団の違い: OOF(0.4454)は学習データ全 707 観測を網羅した統計的確定値です。一方、Public LB(0.39)は未知テストデータの約 20〜30% を対象とした小規模サブセットでの暫定採点です。
・アノテータ分布のブレ: MAGFiLO では専門家ごとに注釈傾向が異なり、Public テストの担当アノテータの癖によって採点の厳しさが変動します。
・推論構成の違い: OOF 0.4454 は全 Fold の統合検証であるのに対し、Public 0.39 は計算コストを約 1/3 に圧縮した Fold 2 単体モデルで算出した値です。
専門家アノテーション同士の一致度(参照アノテーション一致ベースライン PQ 0.3398)との関係:
MAGFiLO で複数アノテータが付いた 296 観測において、専門家アノテータ同士の一致度は PQ 0.3398 です。個体レベルの照合では約 46.1% の確率で境界や個体数の解釈が分かれます。この値は性能の上限ではなく比較のための基準値であり、OOF PQ(0.4454)はこれを上回っています。これはモデルが専門家より正確という意味ではなく、複数アノテータのラベルから“コンセンサス的”な境界を学習している可能性を示すもので、検証は今後の課題です。
(2026-08-30訂正:算出コードに参照アノテータ自身との自己比較が混入する不具合があり、旧数値 0.7587/0.6051 は水増しされていました。)
学術論文・原著データセット出典(正当な学術引用に基づく表示) Nature Scientific Data (CC BY 4.0)

本タスクの正解アノテーション基準および複数専門家による注釈構造は、以下のオープンアクセス学術論文に基づいています。
論文名: A dataset of manually annotated filaments from H-alpha observations
著者: Azim Ahmadzadeh, Rohan Adhyapak, Kartik Chaurasiya, Laxmi Alekhya Nagubandi, V. Aparna, Petrus C. Martens, Alexei Pevtsov, Luca Bertello, et al.
掲載誌: Nature Scientific Data, Vol. 11, Article 1031 (2024). DOI: 10.1038/s41597-024-03876-y

※ 本サイトに掲載している検出結果輪郭(黄色ポリゴン)は、自社開発の AI モデルが推論により独自に算出した成果物であり、データセットに含まれる手作業アノテーションの再配布ではありません。

06PyTorch コアコード実装(MediSegEdgeAttUNet)

solar_filament/models/mediseg_models.py に実装されている MediSegEdgeAttUNet、ボトルネック自己注意 EdgeGuidedMHSA、および複合損失関数 refiner_loss の実稼働コードです。

import torch
import torch.nn as nn
import torch.nn.functional as F

# ============================================================
# 1. Edge-Guided Multi-Head Self-Attention (EG-MHSA)
# ============================================================
class EdgeGuidedMHSA(nn.Module):
    """U-Net の Bottleneck でエッジ事前分布を Query/Key に注入する自己注意層"""
    def __init__(self, in_channels: int = 3, bottleneck_channels: int = 1024, num_heads: int = 8):
        super().__init__()
        # E = σ(Conv3×3(Input)): 入力パッチから空間エッジマップ (B, 1, H_in, W_in) を抽出
        self.edge_conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1)
        # E' = Conv1×1(Interp(E)): ボトルネック解像度・チャンネル数 (B, 1024, 20, 20) へ射影
        self.edge_proj = nn.Conv2d(1, bottleneck_channels, kernel_size=1)
        self.mhsa = nn.MultiheadAttention(bottleneck_channels, num_heads, batch_first=True)
        self.norm = nn.LayerNorm(bottleneck_channels)

    def forward(self, bottleneck: torch.Tensor, original_image: torch.Tensor) -> torch.Tensor:
        b, c, h, w = bottleneck.shape
        
        # 1. エッジ事前分布の抽出とボトルネック空間への射影
        edge_map = torch.sigmoid(self.edge_conv(original_image))
        edge_map = F.interpolate(edge_map, size=(h, w), mode="bilinear", align_corners=True)
        edge_prior = self.edge_proj(edge_map)  # (B, 1024, 20, 20)

        # 2. テンソル平坦化 (B, C, H, W) ➔ (B, N, C), N = 400 トークン
        x_flat = bottleneck.flatten(2).transpose(1, 2)
        edge_flat = edge_prior.flatten(2).transpose(1, 2)
        
        # 3. Query & Key へのエッジ誘導事前分布の加算注入 (Value は元特徴量を保持)
        qk = x_flat + edge_flat
        attn_out, _ = self.mhsa(qk, qk, x_flat)

        # 4. 残差接続 + LayerNorm ➔ 元の 2D テンソル形状 (B, 1024, 20, 20) へ復元
        attn_out = self.norm(attn_out + x_flat)
        return attn_out.transpose(1, 2).reshape(b, c, h, w)


# ============================================================
# 2. MediSegEdgeAttUNet 完全定義 (約 35.2M パラメータ)
# ============================================================
class DoubleConv(nn.Module):
    def __init__(self, in_ch: int, out_ch: int):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )
    def forward(self, x): return self.net(x)

class Down(nn.Module):
    def __init__(self, in_ch: int, out_ch: int):
        super().__init__()
        self.net = nn.Sequential(nn.MaxPool2d(2), DoubleConv(in_ch, out_ch))
    def forward(self, x): return self.net(x)

class Up(nn.Module):
    def __init__(self, in_ch: int, out_ch: int):
        super().__init__()
        self.up = nn.ConvTranspose2d(in_ch, in_ch // 2, kernel_size=2, stride=2)
        self.conv = DoubleConv(in_ch, out_ch)
    def forward(self, x1, x2):
        x1 = self.up(x1)
        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)

class MediSegEdgeAttUNet(nn.Module):
    """MediSeg-AI のマルチスケール U-Net 骨格に EG-MHSA を融合した精密化モデル"""
    def __init__(self, in_channels: int = 3, out_channels: int = 1, num_heads: int = 8):
        super().__init__()
        self.inc = DoubleConv(in_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        self.down4 = Down(512, 1024)
        
        # ボトルネック注意層
        self.edge_attn = EdgeGuidedMHSA(in_channels, bottleneck_channels=1024, num_heads=num_heads)
        
        self.up1 = Up(1024, 512)
        self.up2 = Up(512, 256)
        self.up3 = Up(256, 128)
        self.up4 = Up(128, 64)
        self.outc = nn.Conv2d(64, out_channels, kernel_size=1)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Encoder
        x1 = self.inc(x)         # [B, 64, 320, 320]
        x2 = self.down1(x1)      # [B, 128, 160, 160]
        x3 = self.down2(x2)      # [B, 256, 80, 80]
        x4 = self.down3(x3)      # [B, 512, 40, 40]
        x5 = self.down4(x4)      # [B, 1024, 20, 20]
        
        # EG-MHSA Attention (元画像 x を参照してエッジ注入)
        x5 = self.edge_attn(x5, x)

        # Decoder with Skip Connections
        out = self.up1(x5, x4)   # [B, 512, 40, 40]
        out = self.up2(out, x3)  # [B, 256, 80, 80]
        out = self.up3(out, x2)  # [B, 128, 160, 160]
        out = self.up4(out, x1)  # [B, 64, 320, 320]
        return self.outc(out)    # [B, 1, 320, 320] Logits


# ============================================================
# 3. 複合損失関数 (Composite Refiner Loss)
# ============================================================
def boundary_map(tensor: torch.Tensor) -> torch.Tensor:
    """微分可能なモルフォロジー演算(膨張 - 収縮)により輪郭境界 (2〜3px) を抽出"""
    dilation = F.max_pool2d(tensor, kernel_size=5, stride=1, padding=2)
    erosion = -F.max_pool2d(-tensor, kernel_size=5, stride=1, padding=2)
    return torch.clamp(dilation - erosion, 0.0, 1.0)

def soft_dice_loss(logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
    probs = torch.sigmoid(logits)
    intersection = (probs * targets).sum((1, 2, 3))
    denominator = probs.sum((1, 2, 3)) + targets.sum((1, 2, 3))
    return (1.0 - (2.0 * intersection + 1.0) / (denominator + 1.0)).mean()

def refiner_loss(logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
    bce = F.binary_cross_entropy_with_logits(logits, targets)
    dice = soft_dice_loss(logits, targets)
    edge_l1 = F.l1_loss(boundary_map(torch.sigmoid(logits)), boundary_map(targets))
    return 0.35 * bce + 0.65 * dice + 0.15 * edge_l1

07科学・実運用への展開 — 宇宙天気と太陽電池(PV)検査

微細な線状構造を抽出する本アーキテクチャ(EG-MHSA)は、宇宙物理観測および産業検査の双方において実用的な応用が期待されます。

宇宙天気予報 (Space Weather)

ダークフィラメントの主軸(spine)連続性と側枝(barb)の配向角を抽出することで、磁場カイラリティ(ねじれ方向)の判定やフレア警戒の基礎データとして活用できます。

太陽電池(PV)EL発光欠陥検査

太陽電池セルの数十μm幅フィンガー電極断線や微細マイクロクラック(ひび割れ)を、高解像度かつ高速に 1本単位で分離・検出。局所発熱(ホットスポット)による性能低下の早期発見に貢献します。