第5章:ハイブリッドアプローチ

第4章では、組成ベース特徴量(Magpie)とGNN構造ベース特徴量(CGCNN)を定量的に比較し、各手法の長所と短所を明確にしました。本章では、これら2つのアプローチを統合するハイブリッドモデルを構築し、「両方の良いとこ取り」による性能向上を目指します。

🎯 学習目標

5.1 ハイブリッド特徴量の設計原理

ハイブリッドアプローチの核心は、異なる情報源から得られる特徴量を効果的に統合することです。組成ベースとGNN構造ベースの特徴量は、相補的な情報を持っています。

5.1.1 特徴量の相補性

観点 組成ベース特徴量 GNN構造ベース特徴量 ハイブリッドの利点
情報粒度 元素レベル(平均・分散) 原子レベル(位置・結合) マルチスケール表現
データ要求量 少ない(<10,000) 多い(>50,000) 中規模データで効率化
計算コスト 低い(秒オーダー) 高い(分オーダー) 効率と精度のバランス
解釈可能性 高い(元素特性) 中(構造パターン) 多角的な解釈
構造感度 なし(同素体区別不可) 高い(結晶構造依存) 構造情報を考慮

5.1.2 統合戦略の分類

特徴量の統合には、主に3つの戦略があります:

graph TD A[ハイブリッド統合戦略] --> B[Early Fusion
特徴量レベル統合] A --> C[Late Fusion
予測レベル統合] A --> D[Intermediate Fusion
中間層統合] B --> B1[単純連結
Magpie + GNN embeddings] B --> B2[重み付き結合
Attention機構] C --> C1[単純平均
RF予測 + CGCNN予測] C --> C2[スタッキング
メタモデル学習] D --> D1[ALIGNN
Line graph + Atom graph] D --> D2[MEGNet
Multi-scale aggregation] style A fill:#667eea,color:#fff style B fill:#4caf50,color:#fff style C fill:#ff9800,color:#fff style D fill:#764ba2,color:#fff

Early Fusion(特徴量レベル統合):組成ベース特徴量とGNN埋め込みを連結し、単一のモデルで学習

$$\mathbf{h}_{\text{hybrid}} = [\mathbf{h}_{\text{composition}}; \mathbf{h}_{\text{GNN}}]$$

Late Fusion(予測レベル統合):各モデルの予測を統合してアンサンブル予測を生成

$$\hat{y}_{\text{hybrid}} = \alpha \hat{y}_{\text{RF}} + (1-\alpha) \hat{y}_{\text{CGCNN}}$$

Intermediate Fusion(中間層統合):ニューラルネットワークの中間層で異なる表現を統合

5.2 Early Fusion:特徴量連結アプローチ

最もシンプルなハイブリッド手法は、組成ベース特徴量(Magpie 145次元)とGNN埋め込み(例:128次元)を単純に連結することです。

5.2.1 特徴量連結の実装

💻 コード例1:Early Fusion(特徴量連結)の実装
# Early Fusion: 組成ベース + GNN埋め込みの連結
import torch
import torch.nn as nn
from torch_geometric.data import Data
from torch_geometric.nn import CGConv, global_mean_pool
import numpy as np
from matminer.featurizers.composition import ElementProperty

class HybridEarlyFusion(nn.Module):
    def __init__(self, composition_dim=145, atom_fea_len=92, nbr_fea_len=41,
                 gnn_hidden=128, n_conv=3):
        super(HybridEarlyFusion, self).__init__()

        # GNN部分(CGCNN)
        self.atom_embedding = nn.Linear(atom_fea_len, gnn_hidden)
        self.conv_layers = nn.ModuleList([
            CGConv(gnn_hidden, nbr_fea_len) for _ in range(n_conv)
        ])
        self.bn_layers = nn.ModuleList([
            nn.BatchNorm1d(gnn_hidden) for _ in range(n_conv)
        ])

        # ハイブリッド統合層
        # 組成特徴量(145次元)+ GNN埋め込み(128次元)= 273次元
        hybrid_dim = composition_dim + gnn_hidden
        self.fc1 = nn.Linear(hybrid_dim, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, 1)
        self.activation = nn.Softplus()
        self.dropout = nn.Dropout(0.2)

    def forward(self, data, composition_features):
        """
        Parameters:
        -----------
        data : torch_geometric.data.Data
            グラフデータ(原子ノード、エッジ、エッジ特徴量)
        composition_features : torch.Tensor, shape (batch_size, 145)
            組成ベース特徴量(Magpie)

        Returns:
        --------
        out : torch.Tensor, shape (batch_size,)
            予測値
        """
        x, edge_index, edge_attr, batch = data.x, data.edge_index, data.edge_attr, data.batch

        # GNN埋め込みの計算
        x = self.atom_embedding(x)
        for conv, bn in zip(self.conv_layers, self.bn_layers):
            x = conv(x, edge_index, edge_attr)
            x = bn(x)
            x = self.activation(x)

        # グローバルプーリング(グラフレベル表現)
        gnn_embedding = global_mean_pool(x, batch)  # shape: (batch_size, 128)

        # Early Fusion: 組成特徴量とGNN埋め込みを連結
        hybrid_features = torch.cat([composition_features, gnn_embedding], dim=1)  # (batch_size, 273)

        # 予測層
        h = self.fc1(hybrid_features)
        h = self.activation(h)
        h = self.dropout(h)
        h = self.fc2(h)
        h = self.activation(h)
        h = self.dropout(h)
        out = self.fc3(h)

        return out.squeeze()

# データ準備関数
def prepare_hybrid_data(structures, targets, featurizer):
    """
    PyTorch Geometricデータと組成特徴量を準備

    Parameters:
    -----------
    structures : list of Structure
        結晶構造のリスト
    targets : np.ndarray
        目標値
    featurizer : ElementProperty
        Magpie特徴量抽出器

    Returns:
    --------
    graph_data : list of Data
        グラフデータのリスト
    composition_features : torch.Tensor
        組成特徴量
    """
    graph_data = []
    composition_features = []

    for struct, target in zip(structures, targets):
        # グラフデータ作成(Chapter 4のstructure_to_pyg_data関数を使用)
        graph = structure_to_pyg_data(struct, target)
        graph_data.append(graph)

        # 組成特徴量抽出
        comp = struct.composition
        comp_feat = featurizer.featurize(comp)
        composition_features.append(comp_feat)

    composition_features = torch.tensor(composition_features, dtype=torch.float32)

    return graph_data, composition_features

# Matbenchでの訓練例
from matbench.bench import MatbenchBenchmark

mb = MatbenchBenchmark(autoload=False)
task = mb.matbench_mp_e_form
task.load()

# Magpie特徴量抽出器
featurizer = ElementProperty.from_preset("magpie")

# 訓練データとテストデータ(Fold 0のみ)
train_inputs, train_outputs = task.get_train_and_val_data(task.folds[0])
test_inputs, test_outputs = task.get_test_data(task.folds[0], include_target=True)

print("=== ハイブリッドデータを準備中... ===")
train_graphs, train_comp_feats = prepare_hybrid_data(train_inputs, train_outputs.values, featurizer)
test_graphs, test_comp_feats = prepare_hybrid_data(test_inputs, test_outputs.values, featurizer)

# カスタムDataLoaderの定義
from torch.utils.data import Dataset, DataLoader as TorchDataLoader
from torch_geometric.data import Batch

class HybridDataset(Dataset):
    def __init__(self, graph_data, composition_features):
        self.graph_data = graph_data
        self.composition_features = composition_features

    def __len__(self):
        return len(self.graph_data)

    def __getitem__(self, idx):
        return self.graph_data[idx], self.composition_features[idx]

def hybrid_collate_fn(batch):
    graphs, comp_feats = zip(*batch)
    batched_graph = Batch.from_data_list(graphs)
    batched_comp_feats = torch.stack(comp_feats)
    return batched_graph, batched_comp_feats

train_dataset = HybridDataset(train_graphs, train_comp_feats)
test_dataset = HybridDataset(test_graphs, test_comp_feats)

train_loader = TorchDataLoader(train_dataset, batch_size=32, shuffle=True, collate_fn=hybrid_collate_fn)
test_loader = TorchDataLoader(test_dataset, batch_size=32, shuffle=False, collate_fn=hybrid_collate_fn)

# モデル訓練
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = HybridEarlyFusion().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.L1Loss()

print("\n=== ハイブリッドモデルを訓練中... ===")
model.train()
for epoch in range(50):
    total_loss = 0
    for batch_graph, batch_comp_feats in train_loader:
        batch_graph = batch_graph.to(device)
        batch_comp_feats = batch_comp_feats.to(device)

        optimizer.zero_grad()
        out = model(batch_graph, batch_comp_feats)
        loss = criterion(out, batch_graph.y)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}/50, Loss: {total_loss/len(train_loader):.4f}")

# テスト評価
model.eval()
y_true, y_pred = [], []

with torch.no_grad():
    for batch_graph, batch_comp_feats in test_loader:
        batch_graph = batch_graph.to(device)
        batch_comp_feats = batch_comp_feats.to(device)
        out = model(batch_graph, batch_comp_feats)
        y_true.extend(batch_graph.y.cpu().numpy())
        y_pred.extend(out.cpu().numpy())

from sklearn.metrics import mean_absolute_error, r2_score
mae = mean_absolute_error(y_true, y_pred)
r2 = r2_score(y_true, y_pred)

print(f"\n=== Hybrid Early Fusion結果 ===")
print(f"MAE:  {mae:.4f} eV/atom")
print(f"R²:   {r2:.4f}")

# 出力例:
# === Hybrid Early Fusion結果 ===
# MAE:  0.0265 eV/atom  # CGCNN単独(0.0286)より7.3%改善
# R²:   0.9614          # CGCNN単独(0.9524)より向上

5.2.2 Early Fusionの性能分析

性能比較(Matbench mp_e_form):

手法 MAE (eV/atom) 相対改善率
Random Forest(Magpie) 0.0325 0.9321 ベースライン
CGCNN 0.0286 0.9524 +12.0%
Hybrid Early Fusion 0.0265 0.9614 +18.5%

Early Fusionの利点:

Early Fusionの課題:

5.3 Late Fusion:アンサンブル予測

Late Fusionは、Random ForestとCGCNNを独立に訓練し、予測段階で統合するアプローチです。各モデルの予測を重み付き平均することで、アンサンブル効果を得ます。

5.3.1 Late Fusionの実装

💻 コード例2:Late Fusion(アンサンブル予測)の実装
# Late Fusion: Random Forest + CGCNN アンサンブル
from sklearn.ensemble import RandomForestRegressor
import numpy as np

# Random Forestモデルの訓練(Chapter 4のコードを再利用)
print("=== Random Forestを訓練中... ===")
X_train_magpie = extract_magpie_features(train_inputs)
X_test_magpie = extract_magpie_features(test_inputs)
y_train = train_outputs.values
y_test = test_outputs.values

rf_model = RandomForestRegressor(n_estimators=100, max_depth=30, random_state=42, n_jobs=-1)
rf_model.fit(X_train_magpie, y_train)

# Random Forestの予測
rf_pred_train = rf_model.predict(X_train_magpie)
rf_pred_test = rf_model.predict(X_test_magpie)

# CGCNNモデルの訓練(Chapter 4のコードを再利用)
print("\n=== CGCNNを訓練中... ===")
train_data_cgcnn = [structure_to_pyg_data(s, t) for s, t in zip(train_inputs, y_train)]
test_data_cgcnn = [structure_to_pyg_data(s, t) for s, t in zip(test_inputs, y_test)]

train_loader_cgcnn = DataLoader(train_data_cgcnn, batch_size=32, shuffle=True)
test_loader_cgcnn = DataLoader(test_data_cgcnn, batch_size=32, shuffle=False)

cgcnn_model = CGCNNMatbench().to(device)
optimizer_cgcnn = torch.optim.Adam(cgcnn_model.parameters(), lr=0.001)
criterion = nn.L1Loss()

# CGCNN訓練(簡略版:30エポック)
cgcnn_model.train()
for epoch in range(30):
    for batch in train_loader_cgcnn:
        batch = batch.to(device)
        optimizer_cgcnn.zero_grad()
        out = cgcnn_model(batch)
        loss = criterion(out, batch.y)
        loss.backward()
        optimizer_cgcnn.step()

# CGCNNの予測
cgcnn_model.eval()
cgcnn_pred_train, cgcnn_pred_test = [], []

with torch.no_grad():
    for batch in train_loader_cgcnn:
        batch = batch.to(device)
        out = cgcnn_model(batch)
        cgcnn_pred_train.extend(out.cpu().numpy())

    for batch in test_loader_cgcnn:
        batch = batch.to(device)
        out = cgcnn_model(batch)
        cgcnn_pred_test.extend(out.cpu().numpy())

cgcnn_pred_train = np.array(cgcnn_pred_train)
cgcnn_pred_test = np.array(cgcnn_pred_test)

# 最適な重みαを訓練データで探索
print("\n=== 最適アンサンブル重みを探索中... ===")
alphas = np.linspace(0, 1, 21)  # 0.0, 0.05, 0.10, ..., 1.0
best_alpha = 0
best_mae = float('inf')

for alpha in alphas:
    ensemble_pred_train = alpha * rf_pred_train + (1 - alpha) * cgcnn_pred_train
    mae_train = mean_absolute_error(y_train, ensemble_pred_train)

    if mae_train < best_mae:
        best_mae = mae_train
        best_alpha = alpha

print(f"最適重み α = {best_alpha:.2f}")
print(f"訓練MAE = {best_mae:.4f} eV/atom")

# テストデータでアンサンブル予測
ensemble_pred_test = best_alpha * rf_pred_test + (1 - best_alpha) * cgcnn_pred_test

mae_test = mean_absolute_error(y_test, ensemble_pred_test)
r2_test = r2_score(y_test, ensemble_pred_test)

print(f"\n=== Late Fusion(アンサンブル)結果 ===")
print(f"RF重み: {best_alpha:.2f}, CGCNN重み: {1-best_alpha:.2f}")
print(f"MAE:  {mae_test:.4f} eV/atom")
print(f"R²:   {r2_test:.4f}")

# 個別モデルとの比較
rf_mae = mean_absolute_error(y_test, rf_pred_test)
cgcnn_mae = mean_absolute_error(y_test, cgcnn_pred_test)

print(f"\n=== 個別モデル性能 ===")
print(f"RF単独:       MAE = {rf_mae:.4f} eV/atom")
print(f"CGCNN単独:    MAE = {cgcnn_mae:.4f} eV/atom")
print(f"Late Fusion:  MAE = {mae_test:.4f} eV/atom")
print(f"改善率(RF比):    {(rf_mae - mae_test) / rf_mae * 100:.2f}%")
print(f"改善率(CGCNN比): {(cgcnn_mae - mae_test) / cgcnn_mae * 100:.2f}%")

# 出力例:
# 最適重み α = 0.25
# === Late Fusion(アンサンブル)結果 ===
# RF重み: 0.25, CGCNN重み: 0.75
# MAE:  0.0272 eV/atom
# R²:   0.9582
#
# === 個別モデル性能 ===
# RF単独:       MAE = 0.0325 eV/atom
# CGCNN単独:    MAE = 0.0286 eV/atom
# Late Fusion:  MAE = 0.0272 eV/atom
# 改善率(RF比):    16.31%
# 改善率(CGCNN比): 4.90%

5.3.2 Late Fusionの分析

最適重みの解釈:

Late Fusionの利点:

Late Fusionの課題:

5.4 ALIGNN:最先端ハイブリッドモデル

ALIGNN(Atomistic Line Graph Neural Network)は、原子グラフ(atom graph)線グラフ(line graph)の両方を用いる最先端のハイブリッドGNNです。線グラフでは、原子間の結合(bond)をノードとして扱い、結合角度情報を明示的にモデル化します。

5.4.1 ALIGNNのアーキテクチャ

graph LR A[結晶構造] --> B[原子グラフ
Atom Graph] A --> C[線グラフ
Line Graph] B --> D[Atom Graph
Convolution] C --> E[Line Graph
Convolution] D --> F[相互作用層
Atom-Line Interaction] E --> F F --> G[グローバルプーリング] G --> H[予測層] H --> I[物性予測値] style A fill:#667eea,color:#fff style F fill:#764ba2,color:#fff style I fill:#4caf50,color:#fff

原子グラフ(Atom Graph):

$$G_{\text{atom}} = (V_{\text{atom}}, E_{\text{atom}})$$

ノード:原子、エッジ:原子間結合

線グラフ(Line Graph):

$$G_{\text{line}} = (V_{\text{line}}, E_{\text{line}})$$

ノード:結合、エッジ:結合角度(同じ原子を共有する2つの結合)

5.4.2 ALIGNNの簡易実装

💻 コード例3:ALIGNN簡易実装
# ALIGNN簡易実装(教育目的)
import torch
import torch.nn as nn
from torch_geometric.nn import MessagePassing, global_mean_pool
from torch_geometric.data import Data

class ALIGNNConv(MessagePassing):
    """
    ALIGNN畳み込み層(簡略版)
    """
    def __init__(self, node_dim, edge_dim):
        super(ALIGNNConv, self).__init__(aggr='add')
        self.node_dim = node_dim
        self.edge_dim = edge_dim

        # メッセージ計算用MLP
        self.message_mlp = nn.Sequential(
            nn.Linear(2 * node_dim + edge_dim, node_dim),
            nn.Softplus(),
            nn.Linear(node_dim, node_dim)
        )

        # ノード更新用MLP
        self.update_mlp = nn.Sequential(
            nn.Linear(2 * node_dim, node_dim),
            nn.Softplus(),
            nn.Linear(node_dim, node_dim)
        )

    def forward(self, x, edge_index, edge_attr):
        """
        Parameters:
        -----------
        x : torch.Tensor, shape (num_nodes, node_dim)
            ノード特徴量
        edge_index : torch.Tensor, shape (2, num_edges)
            エッジインデックス
        edge_attr : torch.Tensor, shape (num_edges, edge_dim)
            エッジ特徴量

        Returns:
        --------
        out : torch.Tensor, shape (num_nodes, node_dim)
            更新されたノード特徴量
        """
        return self.propagate(edge_index, x=x, edge_attr=edge_attr)

    def message(self, x_i, x_j, edge_attr):
        # メッセージ: [送信元ノード、受信先ノード、エッジ特徴量]
        msg_input = torch.cat([x_i, x_j, edge_attr], dim=-1)
        return self.message_mlp(msg_input)

    def update(self, aggr_out, x):
        # ノード更新: [元のノード特徴量、集約されたメッセージ]
        update_input = torch.cat([x, aggr_out], dim=-1)
        return self.update_mlp(update_input)

class ALIGNNSimple(nn.Module):
    """
    ALIGNN簡易実装(原子グラフのみ、線グラフは省略)
    """
    def __init__(self, atom_fea_len=92, nbr_fea_len=41, hidden_dim=128, n_conv=3):
        super(ALIGNNSimple, self).__init__()

        # 原子埋め込み
        self.atom_embedding = nn.Linear(atom_fea_len, hidden_dim)

        # ALIGNN畳み込み層
        self.conv_layers = nn.ModuleList([
            ALIGNNConv(hidden_dim, nbr_fea_len) for _ in range(n_conv)
        ])
        self.bn_layers = nn.ModuleList([
            nn.BatchNorm1d(hidden_dim) for _ in range(n_conv)
        ])

        # 予測層
        self.fc1 = nn.Linear(hidden_dim, 64)
        self.fc2 = nn.Linear(64, 1)
        self.activation = nn.Softplus()

    def forward(self, data):
        x, edge_index, edge_attr, batch = data.x, data.edge_index, data.edge_attr, data.batch

        # 原子埋め込み
        x = self.atom_embedding(x)

        # ALIGNN畳み込み
        for conv, bn in zip(self.conv_layers, self.bn_layers):
            x_new = conv(x, edge_index, edge_attr)
            x = bn(x_new) + x  # 残差接続
            x = self.activation(x)

        # グローバルプーリング
        x = global_mean_pool(x, batch)

        # 予測
        x = self.fc1(x)
        x = self.activation(x)
        x = self.fc2(x)

        return x.squeeze()

# ALIGNN訓練(Matbench mp_e_form)
print("=== ALIGNN簡易版を訓練中... ===")
alignn_model = ALIGNNSimple().to(device)
optimizer_alignn = torch.optim.Adam(alignn_model.parameters(), lr=0.001)
criterion = nn.L1Loss()

# 訓練ループ
alignn_model.train()
for epoch in range(50):
    total_loss = 0
    for batch in train_loader_cgcnn:
        batch = batch.to(device)
        optimizer_alignn.zero_grad()
        out = alignn_model(batch)
        loss = criterion(out, batch.y)
        loss.backward()
        optimizer_alignn.step()
        total_loss += loss.item()

    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}/50, Loss: {total_loss/len(train_loader_cgcnn):.4f}")

# テスト評価
alignn_model.eval()
y_true_alignn, y_pred_alignn = [], []

with torch.no_grad():
    for batch in test_loader_cgcnn:
        batch = batch.to(device)
        out = alignn_model(batch)
        y_true_alignn.extend(batch.y.cpu().numpy())
        y_pred_alignn.extend(out.cpu().numpy())

mae_alignn = mean_absolute_error(y_true_alignn, y_pred_alignn)
r2_alignn = r2_score(y_true_alignn, y_pred_alignn)

print(f"\n=== ALIGNN簡易版結果 ===")
print(f"MAE:  {mae_alignn:.4f} eV/atom")
print(f"R²:   {r2_alignn:.4f}")

# 出力例:
# === ALIGNN簡易版結果 ===
# MAE:  0.0278 eV/atom
# R²:   0.9548

# 注:完全なALIGNNは線グラフも使用し、さらに高性能(MAE ~0.025 eV/atom)
⚠️ 注意:簡易実装の限界

本コード例は教育目的の簡易実装です。完全なALIGNN実装は線グラフ(Line Graph)も使用し、結合角度情報を明示的にモデル化します。公式実装(NIST ALIGNN GitHub)ではMAE ~0.025 eV/atomの性能を達成しています。

5.4.3 ALIGNNの性能評価

手法 MAE (eV/atom) 特徴
CGCNN 0.0286 原子グラフのみ
ALIGNN簡易版 0.0278 残差接続 + 改良メッセージパッシング
ALIGNN完全版 0.0250 原子グラフ + 線グラフ + 結合角度

ALIGNNの優位性:

ALIGNNの課題:

5.5 MEGNet:マルチタスク学習

MEGNet(Materials Graph Network)は、複数の材料物性を同時に予測するマルチタスク学習フレームワークです。異なる物性間の相関を利用し、データ効率とモデル汎化性能を向上させます。

5.5.1 マルチタスク学習の原理

マルチタスク学習では、複数のタスク$T_1, T_2, \ldots, T_K$を同時に学習します:

$$\mathcal{L}_{\text{multi}} = \sum_{k=1}^{K} \lambda_k \mathcal{L}_k$$

ここで、$\lambda_k$はタスク$k$の重み、$\mathcal{L}_k$はタスク$k$の損失関数です。

マルチタスク学習の利点:

5.5.2 MEGNetの実装

💻 コード例4:MEGNet風マルチタスク学習の実装
# MEGNet風マルチタスクGNNの実装
import torch
import torch.nn as nn
from torch_geometric.nn import GATConv, global_mean_pool

class MEGNetMultiTask(nn.Module):
    """
    MEGNet風マルチタスクGNN
    生成エネルギーとバンドギャップを同時予測
    """
    def __init__(self, atom_fea_len=92, nbr_fea_len=41, hidden_dim=128, n_conv=3, n_tasks=2):
        super(MEGNetMultiTask, self).__init__()

        # 共有GNN層(全タスクで共通)
        self.atom_embedding = nn.Linear(atom_fea_len, hidden_dim)

        self.conv_layers = nn.ModuleList([
            GATConv(hidden_dim, hidden_dim, heads=4, concat=False, edge_dim=nbr_fea_len)
            for _ in range(n_conv)
        ])
        self.bn_layers = nn.ModuleList([
            nn.BatchNorm1d(hidden_dim) for _ in range(n_conv)
        ])

        # タスク固有の予測ヘッド
        self.task_heads = nn.ModuleList([
            nn.Sequential(
                nn.Linear(hidden_dim, 64),
                nn.Softplus(),
                nn.Linear(64, 1)
            ) for _ in range(n_tasks)
        ])

        self.activation = nn.Softplus()

    def forward(self, data, task_idx=None):
        """
        Parameters:
        -----------
        data : torch_geometric.data.Data
            グラフデータ
        task_idx : int or None
            予測するタスクのインデックス(Noneの場合は全タスク予測)

        Returns:
        --------
        out : torch.Tensor or list of torch.Tensor
            タスク予測値
        """
        x, edge_index, edge_attr, batch = data.x, data.edge_index, data.edge_attr, data.batch

        # 共有GNN埋め込み
        x = self.atom_embedding(x)
        for conv, bn in zip(self.conv_layers, self.bn_layers):
            x = conv(x, edge_index, edge_attr)
            x = bn(x)
            x = self.activation(x)

        # グローバルプーリング
        graph_embedding = global_mean_pool(x, batch)

        # タスク固有の予測
        if task_idx is not None:
            # 単一タスク予測
            return self.task_heads[task_idx](graph_embedding).squeeze()
        else:
            # 全タスク予測
            return [head(graph_embedding).squeeze() for head in self.task_heads]

# マルチタスクデータの準備(生成エネルギー + バンドギャップ)
from matbench.bench import MatbenchBenchmark

mb = MatbenchBenchmark(autoload=False)

# タスク1: 生成エネルギー(mp_e_form)
task1 = mb.matbench_mp_e_form
task1.load()

# タスク2: バンドギャップ(mp_gap)
task2 = mb.matbench_mp_gap
task2.load()

# 共通の構造を持つデータを抽出(実装簡略化のため、ここでは同じ構造IDを仮定)
# 実際にはMaterials Project IDで結合する

print("=== マルチタスクデータを準備中... ===")

# Fold 0のみ使用
train_inputs_1, train_outputs_1 = task1.get_train_and_val_data(task1.folds[0])
test_inputs_1, test_outputs_1 = task1.get_test_data(task1.folds[0], include_target=True)

train_inputs_2, train_outputs_2 = task2.get_train_and_val_data(task2.folds[0])
test_inputs_2, test_outputs_2 = task2.get_test_data(task2.folds[0], include_target=True)

# 簡略化のため、最初の10,000サンプルのみ使用
n_samples = 10000
train_inputs_1 = train_inputs_1[:n_samples]
train_outputs_1 = train_outputs_1.values[:n_samples]
train_inputs_2 = train_inputs_2[:n_samples]
train_outputs_2 = train_outputs_2.values[:n_samples]

# グラフデータ構築
def create_multitask_data(structures, targets_task1, targets_task2):
    """
    マルチタスクグラフデータを作成
    """
    data_list = []
    for struct, t1, t2 in zip(structures, targets_task1, targets_task2):
        graph = structure_to_pyg_data(struct, t1)
        graph.y_task1 = torch.tensor([t1], dtype=torch.float)
        graph.y_task2 = torch.tensor([t2], dtype=torch.float)
        data_list.append(graph)
    return data_list

train_data_multi = create_multitask_data(train_inputs_1, train_outputs_1, train_outputs_2)
test_data_multi = create_multitask_data(test_inputs_1[:1000],
                                         test_outputs_1.values[:1000],
                                         test_outputs_2.values[:1000])

train_loader_multi = DataLoader(train_data_multi, batch_size=32, shuffle=True)
test_loader_multi = DataLoader(test_data_multi, batch_size=32, shuffle=False)

# MEGNetマルチタスクモデルの訓練
print("\n=== MEGNetマルチタスクモデルを訓練中... ===")
megnet_model = MEGNetMultiTask(n_tasks=2).to(device)
optimizer_megnet = torch.optim.Adam(megnet_model.parameters(), lr=0.001)

# タスク重み(損失のバランス調整)
lambda_task1 = 1.0  # 生成エネルギー
lambda_task2 = 0.5  # バンドギャップ(スケール調整)

megnet_model.train()
for epoch in range(30):
    total_loss = 0
    for batch in train_loader_multi:
        batch = batch.to(device)
        optimizer_megnet.zero_grad()

        # 2タスクの予測
        pred_task1, pred_task2 = megnet_model(batch)

        # マルチタスク損失
        loss_task1 = nn.L1Loss()(pred_task1, batch.y_task1.squeeze())
        loss_task2 = nn.L1Loss()(pred_task2, batch.y_task2.squeeze())

        loss = lambda_task1 * loss_task1 + lambda_task2 * loss_task2
        loss.backward()
        optimizer_megnet.step()
        total_loss += loss.item()

    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}/30, Total Loss: {total_loss/len(train_loader_multi):.4f}")

# テスト評価(各タスク)
megnet_model.eval()
y_true_task1, y_pred_task1 = [], []
y_true_task2, y_pred_task2 = [], []

with torch.no_grad():
    for batch in test_loader_multi:
        batch = batch.to(device)
        pred_task1, pred_task2 = megnet_model(batch)

        y_true_task1.extend(batch.y_task1.squeeze().cpu().numpy())
        y_pred_task1.extend(pred_task1.cpu().numpy())

        y_true_task2.extend(batch.y_task2.squeeze().cpu().numpy())
        y_pred_task2.extend(pred_task2.cpu().numpy())

mae_task1 = mean_absolute_error(y_true_task1, y_pred_task1)
mae_task2 = mean_absolute_error(y_true_task2, y_pred_task2)

print(f"\n=== MEGNetマルチタスク結果 ===")
print(f"Task 1(生成エネルギー): MAE = {mae_task1:.4f} eV/atom")
print(f"Task 2(バンドギャップ):  MAE = {mae_task2:.4f} eV")

# 単一タスクモデルとの比較(参考)
print(f"\n単一タスクCGCNN比較:")
print(f"Task 1: マルチタスク {mae_task1:.4f} vs 単一タスク ~0.0286 eV/atom")
print(f"Task 2: マルチタスク {mae_task2:.4f} vs 単一タスク ~0.180 eV")

# 出力例:
# === MEGNetマルチタスク結果 ===
# Task 1(生成エネルギー): MAE = 0.0292 eV/atom
# Task 2(バンドギャップ):  MAE = 0.185 eV
#
# 単一タスクCGCNN比較:
# Task 1: マルチタスク 0.0292 vs 単一タスク ~0.0286 eV/atom(わずかに劣化)
# Task 2: マルチタスク 0.185 vs 単一タスク ~0.180 eV(同程度)

5.5.3 マルチタスク学習の効果

マルチタスク学習のメリット:

マルチタスク学習の課題:

5.6 ハイブリッドモデルの性能比較

本章で実装した全ハイブリッド手法の性能を統合比較します。

💻 コード例5:ハイブリッド手法の統合比較
# ハイブリッド手法の統合比較
import matplotlib.pyplot as plt
import pandas as pd

# 性能データ(Matbench mp_e_form)
results = {
    'Model': [
        'Random Forest (Magpie)',
        'CGCNN',
        'Hybrid Early Fusion',
        'Hybrid Late Fusion',
        'ALIGNN (Simple)',
        'ALIGNN (Full)',
        'MEGNet Multi-Task'
    ],
    'MAE (eV/atom)': [0.0325, 0.0286, 0.0265, 0.0272, 0.0278, 0.0250, 0.0292],
    'R²': [0.9321, 0.9524, 0.9614, 0.9582, 0.9548, 0.9680, 0.9510],
    'Training Time (min)': [0.75, 30.5, 32.0, 31.25, 35.0, 45.0, 50.0],
    'Category': ['Composition', 'GNN', 'Hybrid', 'Hybrid', 'Hybrid', 'Hybrid', 'Multi-Task']
}

df = pd.DataFrame(results)

# 可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# MAE比較
colors = {'Composition': '#4caf50', 'GNN': '#667eea', 'Hybrid': '#764ba2', 'Multi-Task': '#ff9800'}
ax1 = axes[0]
bars = ax1.barh(df['Model'], df['MAE (eV/atom)'],
                color=[colors[cat] for cat in df['Category']])
ax1.set_xlabel('MAE (eV/atom)', fontsize=12)
ax1.set_title('予測精度比較(Lower is Better)', fontsize=14, fontweight='bold')
ax1.invert_yaxis()

# ベースラインとの比較線
ax1.axvline(0.0325, color='red', linestyle='--', linewidth=1, alpha=0.7, label='RF Baseline')
ax1.legend()

# 訓練時間 vs MAE
ax2 = axes[1]
for idx, row in df.iterrows():
    ax2.scatter(row['Training Time (min)'], row['MAE (eV/atom)'],
                s=200, color=colors[row['Category']], alpha=0.7, edgecolors='black', linewidth=1.5)
    ax2.text(row['Training Time (min)'], row['MAE (eV/atom)'],
             row['Model'], fontsize=8, ha='right', va='bottom')

ax2.set_xlabel('訓練時間 (分)', fontsize=12)
ax2.set_ylabel('MAE (eV/atom)', fontsize=12)
ax2.set_title('訓練時間 vs 精度のトレードオフ', fontsize=14, fontweight='bold')
ax2.invert_yaxis()
ax2.grid(alpha=0.3)

plt.tight_layout()
plt.savefig('hybrid_comparison.png', dpi=300, bbox_inches='tight')
plt.show()

# 統計サマリー
print("=== ハイブリッド手法統合比較 ===")
print(df.to_string(index=False))

# 最良モデルの識別
best_mae_idx = df['MAE (eV/atom)'].idxmin()
best_efficiency_idx = (df['MAE (eV/atom)'] / df['Training Time (min)']).idxmin()

print(f"\n最高精度モデル: {df.loc[best_mae_idx, 'Model']} (MAE = {df.loc[best_mae_idx, 'MAE (eV/atom)']:.4f})")
print(f"最高効率モデル: {df.loc[best_efficiency_idx, 'Model']} (MAE/Time = {df.loc[best_efficiency_idx, 'MAE (eV/atom)'] / df.loc[best_efficiency_idx, 'Training Time (min)']:.6f})")

# 出力例:
# === ハイブリッド手法統合比較 ===
#                       Model  MAE (eV/atom)     R²  Training Time (min)    Category
#   Random Forest (Magpie)         0.0325  0.9321                 0.75 Composition
#                    CGCNN         0.0286  0.9524                30.50         GNN
#      Hybrid Early Fusion         0.0265  0.9614                32.00      Hybrid
#       Hybrid Late Fusion         0.0272  0.9582                31.25      Hybrid
#           ALIGNN (Simple)         0.0278  0.9548                35.00      Hybrid
#             ALIGNN (Full)         0.0250  0.9680                45.00      Hybrid
#        MEGNet Multi-Task         0.0292  0.9510                50.00  Multi-Task
#
# 最高精度モデル: ALIGNN (Full) (MAE = 0.0250)
# 最高効率モデル: Random Forest (Magpie) (MAE/Time = 0.043333)

5.6.1 性能分析のまとめ

手法 精度 効率 実装難易度 推奨シナリオ
Hybrid Early Fusion ⭐⭐⭐⭐ ⭐⭐⭐ 中規模データ、実装容易性重視
Hybrid Late Fusion ⭐⭐⭐ ⭐⭐ 既存モデルの統合、安定性重視
ALIGNN (Full) ⭐⭐⭐⭐⭐ ⭐⭐ 最高精度が必須、計算リソース十分
MEGNet Multi-Task ⭐⭐⭐ ⭐⭐⭐ 複数物性予測、データ効率重視

5.7 本章のまとめ

本章では、組成ベースとGNN構造ベース特徴量を統合するハイブリッドアプローチを体系的に学びました。

主要な知見

🎯 実務上の推奨

演習問題

演習1:Early Fusionの特徴量次元 Easy

問題:Magpie特徴量(145次元)とGNN埋め込み(256次元)をEarly Fusionで統合する場合、統合後の特徴量次元はいくつになるか?また、過学習リスクを低減するための手法を2つ挙げよ。

解答:

統合後の特徴量次元: 145 + 256 = 401次元

過学習リスク低減手法:

  1. Dropout: 統合層と予測層の間にDropout(例:p=0.3)を挿入し、ランダムにニューロンを無効化
  2. L2正則化: 損失関数に重み減衰項を追加(例:weight_decay=1e-4)
演習2:Late Fusionの最適重み Easy

問題:Random Forest(MAE 0.035 eV/atom)とCGCNN(MAE 0.028 eV/atom)のLate Fusionで、最適重みα=0.20が得られた。この重みの意味を解釈し、なぜCGCNNの重みが高いのか説明せよ。

解答:

重みの意味:

$$\hat{y}_{\text{ensemble}} = 0.20 \times \hat{y}_{\text{RF}} + 0.80 \times \hat{y}_{\text{CGCNN}}$$

CGCNNの予測を80%、RFの予測を20%で統合。

CGCNN重みが高い理由:

演習3:ALIGNNの線グラフ Medium

問題:原子グラフと線グラフの違いを説明し、線グラフが結合角度情報をどのように表現するか具体例を示せ。

解答:

原子グラフ(Atom Graph):

線グラフ(Line Graph):

結合角度情報の表現:

線グラフのエッジ特徴量として、2つの結合が作る角度θを以下のようにエンコード:

angle_feature = torch.cos(theta)  # cosθを特徴量に使用
# 例: H-O-H角度104.5° → cos(104.5°) ≈ -0.25

これにより、ALIGNNは「直線的な結合(θ=180°)」と「屈曲した結合(θ<120°)」を明示的に区別できます。

演習4:マルチタスク学習の損失重み Medium

問題:生成エネルギー(スケール:-5~5 eV/atom)とバンドギャップ(スケール:0~10 eV)を同時予測するマルチタスクGNNにおいて、適切なタスク重みλ₁、λ₂を設計せよ。単純に$\lambda_1 = \lambda_2 = 1.0$とした場合の問題点も説明すること。

解答:

問題点($\lambda_1 = \lambda_2 = 1.0$):

生成エネルギーとバンドギャップのスケールが異なるため、損失の大きさが不均衡になります:

# 生成エネルギーの典型的なMAE: 0.03 eV/atom
loss_task1 = 0.03

# バンドギャップの典型的なMAE: 0.18 eV
loss_task2 = 0.18

# 総損失(λ₁ = λ₂ = 1.0)
total_loss = 1.0 * 0.03 + 1.0 * 0.18 = 0.21
# → バンドギャップの損失が6倍大きい → 生成エネルギーの学習が不十分

適切なタスク重みの設計:

各タスクの損失を同程度にするため、スケールの逆数で重み付け:

# タスク重みの設定
lambda_1 = 1.0  # 生成エネルギー(基準)
lambda_2 = 0.03 / 0.18 ≈ 0.17  # バンドギャップ(スケール調整)

# または、標準偏差の逆数を使用
std_task1 = 1.5  # 生成エネルギーの標準偏差
std_task2 = 2.0  # バンドギャップの標準偏差

lambda_1 = 1 / std_task1 ≈ 0.67
lambda_2 = 1 / std_task2 = 0.50

# 正規化して合計を1にする
lambda_1 = 0.67 / (0.67 + 0.50) ≈ 0.57
lambda_2 = 0.50 / (0.67 + 0.50) ≈ 0.43
演習5:ハイブリッド手法の選択 Medium

問題:以下の3つのシナリオに対して、最適なハイブリッド手法を選択し、その理由を述べよ。

シナリオA: データ30,000サンプル、GPU利用可能、精度優先、実装期限2週間

シナリオB: データ100,000サンプル、GPU複数台、最高精度が必須、計算時間制約なし

シナリオC: 既存のRFモデルとCGCNNモデルがあり、統合したい、リスク回避重視

解答:

シナリオA → Hybrid Early Fusion

シナリオB → ALIGNN (Full)

シナリオC → Hybrid Late Fusion

演習6:Early Fusionの実装 Hard

問題:Hybrid Early FusionモデルにAttention機構を導入し、組成特徴量とGNN埋め込みの重要度を動的に調整するコードを記述せよ。

解答:

# Attention機構付きEarly Fusion
import torch
import torch.nn as nn
import torch.nn.functional as F

class AttentionEarlyFusion(nn.Module):
    def __init__(self, composition_dim=145, gnn_dim=128):
        super(AttentionEarlyFusion, self).__init__()

        # 特徴量変換層(同じ次元に統一)
        self.comp_transform = nn.Linear(composition_dim, gnn_dim)
        # GNN部分(省略、CGCNNと同じ)

        # Attention機構
        self.attention_comp = nn.Linear(gnn_dim, 1)
        self.attention_gnn = nn.Linear(gnn_dim, 1)

        # 予測層
        self.fc = nn.Sequential(
            nn.Linear(gnn_dim, 64),
            nn.Softplus(),
            nn.Linear(64, 1)
        )

    def forward(self, data, composition_features):
        # 組成特徴量を変換(145 → 128次元)
        comp_transformed = self.comp_transform(composition_features)

        # GNN埋め込み計算(省略、CGCNNと同じ処理)
        # gnn_embedding = ... (shape: batch_size, 128)

        # Attention重みの計算
        alpha_comp = self.attention_comp(comp_transformed)  # (batch_size, 1)
        alpha_gnn = self.attention_gnn(gnn_embedding)       # (batch_size, 1)

        # Softmax正規化
        attention_weights = F.softmax(torch.cat([alpha_comp, alpha_gnn], dim=1), dim=1)
        w_comp = attention_weights[:, 0:1]  # 組成特徴量の重み
        w_gnn = attention_weights[:, 1:2]   # GNN埋め込みの重み

        # 重み付き統合
        hybrid_features = w_comp * comp_transformed + w_gnn * gnn_embedding

        # 予測
        out = self.fc(hybrid_features)
        return out.squeeze(), w_comp.squeeze(), w_gnn.squeeze()

# 使用例
model = AttentionEarlyFusion().to(device)
# ... 訓練 ...

# 推論時にAttention重みを確認
model.eval()
with torch.no_grad():
    pred, w_comp, w_gnn = model(test_data, test_comp_feats)
    print(f"組成特徴量重み: {w_comp.mean():.3f}")
    print(f"GNN埋め込み重み: {w_gnn.mean():.3f}")

# 出力例:
# 組成特徴量重み: 0.285
# GNN埋め込み重み: 0.715
# → データに応じて動的に重み調整
演習7:負の転移の検出 Hard

問題:マルチタスク学習において、「負の転移(Negative Transfer)」が発生しているかを検出する手法を提案し、その対策を3つ挙げよ。

解答:

負の転移の検出手法:

# 負の転移の検出
# 単一タスクモデルとマルチタスクモデルの性能を比較

# 単一タスクモデルの訓練
single_task1_model = train_single_task(task1_data)
single_task2_model = train_single_task(task2_data)

# マルチタスクモデルの訓練
multi_task_model = train_multi_task(task1_data, task2_data)

# 性能評価
mae_single_task1 = evaluate(single_task1_model, task1_test_data)
mae_single_task2 = evaluate(single_task2_model, task2_test_data)

mae_multi_task1 = evaluate_multitask(multi_task_model, task1_test_data, task_idx=0)
mae_multi_task2 = evaluate_multitask(multi_task_model, task2_test_data, task_idx=1)

# 負の転移の判定
if mae_multi_task1 > mae_single_task1:
    print("Task 1で負の転移発生")
if mae_multi_task2 > mae_single_task2:
    print("Task 2で負の転移発生")

# 出力例:
# Task 1で負の転移発生(マルチ 0.0295 > 単一 0.0286)
# → タスク間の相関が低い、またはタスク重みが不適切

負の転移の対策:

  1. タスククラスタリング:相関の高いタスクのみをグループ化してマルチタスク学習
    # タスク間の相関を計算
    from scipy.stats import pearsonr
    
    # Task 1とTask 2の予測値の相関
    corr, _ = pearsonr(y_pred_task1, y_pred_task2)
    
    if corr > 0.5:
        print("高相関 → マルチタスク学習推奨")
    else:
        print("低相関 → 単一タスク学習推奨")
    
  2. タスク固有の層を増やす:共有層を浅くし、タスク固有層を深くすることで負の転移を抑制
    self.shared_layers = nn.Sequential(  # 共有: 2層のみ
        nn.Linear(input_dim, 128),
        nn.Softplus()
    )
    
    self.task1_layers = nn.Sequential(  # タスク固有: 3層
        nn.Linear(128, 128),
        nn.Softplus(),
        nn.Linear(128, 64),
        nn.Softplus(),
        nn.Linear(64, 1)
    )
    
  3. 動的タスク重み調整:訓練中にタスク重みを適応的に変更
    # Uncertainty Weighting(不確実性に基づく重み調整)
    class MultiTaskUncertaintyWeighting(nn.Module):
        def __init__(self, n_tasks=2):
            super().__init__()
            self.log_vars = nn.Parameter(torch.zeros(n_tasks))
    
        def forward(self, losses):
            # タスクkの重み: 1 / (2 * σ_k²)
            weighted_losses = []
            for i, loss in enumerate(losses):
                precision = torch.exp(-self.log_vars[i])
                weighted_loss = precision * loss + self.log_vars[i]
                weighted_losses.append(weighted_loss)
            return sum(weighted_losses)
    
    # 使用例
    uncertainty_weighting = MultiTaskUncertaintyWeighting(n_tasks=2)
    total_loss = uncertainty_weighting([loss_task1, loss_task2])
    
演習8:ハイブリッドモデルの解釈可能性 Hard

問題:Hybrid Early Fusionモデルにおいて、「組成特徴量とGNN埋め込みのどちらが予測に寄与しているか」を定量的に分析する手法を提案し、実装せよ。

解答:

# ハイブリッドモデルの解釈可能性分析
import numpy as np
from sklearn.inspection import permutation_importance

def hybrid_feature_importance_analysis(model, test_data, test_comp_feats, test_targets):
    """
    組成特徴量とGNN埋め込みの寄与度を分析

    Returns:
    --------
    comp_importance : float
        組成特徴量の重要度
    gnn_importance : float
        GNN埋め込みの重要度
    """
    model.eval()

    # ベースライン予測(通常の予測)
    with torch.no_grad():
        baseline_pred = model(test_data, test_comp_feats).cpu().numpy()
    baseline_mae = mean_absolute_error(test_targets, baseline_pred)

    # 組成特徴量をゼロにした場合の予測
    zero_comp_feats = torch.zeros_like(test_comp_feats)
    with torch.no_grad():
        pred_no_comp = model(test_data, zero_comp_feats).cpu().numpy()
    mae_no_comp = mean_absolute_error(test_targets, pred_no_comp)

    # GNN埋め込みをゼロにした場合の予測(モデル内部を変更)
    # 簡略版:GNN部分をマスクする代わりに、別途GNN埋め込みなしモデルを訓練
    # ここではPermutation Importanceを使用

    # 組成特徴量の重要度(MAE増加量)
    comp_importance = mae_no_comp - baseline_mae

    # GNN埋め込みの重要度(類推:ランダムシャッフル)
    n_permutations = 10
    gnn_mae_increases = []

    for _ in range(n_permutations):
        # テストデータをシャッフル(GNN埋め込みをランダム化)
        shuffled_indices = np.random.permutation(len(test_data))
        shuffled_data = [test_data[i] for i in shuffled_indices]

        with torch.no_grad():
            pred_shuffled = model(Batch.from_data_list(shuffled_data).to(device),
                                   test_comp_feats).cpu().numpy()
        mae_shuffled = mean_absolute_error(test_targets, pred_shuffled)
        gnn_mae_increases.append(mae_shuffled - baseline_mae)

    gnn_importance = np.mean(gnn_mae_increases)

    return comp_importance, gnn_importance

# 実行
comp_imp, gnn_imp = hybrid_feature_importance_analysis(
    hybrid_model, test_data_list, test_comp_feats, test_targets
)

# 相対的重要度を計算
total_imp = comp_imp + gnn_imp
comp_ratio = comp_imp / total_imp * 100
gnn_ratio = gnn_imp / total_imp * 100

print(f"=== ハイブリッドモデルの特徴量重要度 ===")
print(f"組成特徴量の寄与: {comp_ratio:.1f}%")
print(f"GNN埋め込みの寄与: {gnn_ratio:.1f}%")

# 可視化
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6))
ax.bar(['組成特徴量', 'GNN埋め込み'], [comp_ratio, gnn_ratio],
       color=['#667eea', '#764ba2'])
ax.set_ylabel('相対的重要度 (%)', fontsize=12)
ax.set_title('ハイブリッドモデルの特徴量寄与度', fontsize=14, fontweight='bold')
ax.set_ylim(0, 100)

for i, v in enumerate([comp_ratio, gnn_ratio]):
    ax.text(i, v + 2, f'{v:.1f}%', ha='center', va='bottom', fontsize=12, fontweight='bold')

plt.tight_layout()
plt.savefig('hybrid_feature_importance.png', dpi=300, bbox_inches='tight')
plt.show()

# 出力例:
# === ハイブリッドモデルの特徴量重要度 ===
# 組成特徴量の寄与: 32.5%
# GNN埋め込みの寄与: 67.5%
# → GNN埋め込みがより重要だが、組成情報も有意に寄与

参考文献

  1. Choudhary, K., DeCost, B. (2021). Atomistic Line Graph Neural Network for improved materials property predictions. npj Computational Materials, 7(1), 185, pp. 1-8.
  2. Chen, C., Ye, W., Zuo, Y., Zheng, C., Ong, S. P. (2019). Graph Networks as a Universal Machine Learning Framework for Molecules and Crystals. Chemistry of Materials, 31(9), 3564-3572, pp. 3564-3572.
  3. Ruder, S. (2017). An Overview of Multi-Task Learning in Deep Neural Networks. arXiv preprint arXiv:1706.05098, pp. 1-13.
  4. Crawshaw, M. (2020). Multi-Task Learning with Deep Neural Networks: A Survey. arXiv preprint arXiv:2009.09796, pp. 1-23.
  5. Fung, V., Zhang, J., Juarez, E., Sumpter, B. G. (2021). Benchmarking graph neural networks for materials chemistry. npj Computational Materials, 7(1), 84, pp. 1-8.
  6. Goodall, R. E. A., Lee, A. A. (2020). Predicting materials properties without crystal structure: Deep representation learning from stoichiometry. Nature Communications, 11, 6280, pp. 1-9.
  7. Veličković, P., Cucurull, G., Casanova, A., Romero, A., Liò, P., Bengio, Y. (2018). Graph Attention Networks. International Conference on Learning Representations, pp. 1-12.