{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":118765,"databundleVersionId":15231210}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:48.997669Z","iopub.execute_input":"2026-03-15T09:56:48.998194Z","iopub.status.idle":"2026-03-15T09:56:49.003826Z","shell.execute_reply.started":"2026-03-15T09:56:48.998165Z","shell.execute_reply":"2026-03-15T09:56:49.0029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport numpy as np\nimport os\nfrom collections import Counter\nimport torch\nimport torch.nn as nn\nimport numpy as np\nimport pandas as pd\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:49.005629Z","iopub.execute_input":"2026-03-15T09:56:49.005946Z","iopub.status.idle":"2026-03-15T09:56:56.752232Z","shell.execute_reply.started":"2026-03-15T09:56:49.005919Z","shell.execute_reply":"2026-03-15T09:56:56.751208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 📁 Define the base directory for Kaggle environment\nBASE_DIR = '/kaggle/input/stanford-rna-3d-folding-2'\n\nprint(\"Loading train_sequences.csv\")\ntrain_seq = pd.read_csv(f'{BASE_DIR}/train_sequences.csv')\n\nprint(f\"📌 Sequences shape: {train_seq.shape}\")\ntrain_seq.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:56.753367Z","iopub.execute_input":"2026-03-15T09:56:56.753783Z","iopub.status.idle":"2026-03-15T09:56:57.627488Z","shell.execute_reply.started":"2026-03-15T09:56:56.753754Z","shell.execute_reply":"2026-03-15T09:56:57.626693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Loading vali_sequences.csv\")\nvali_seq = pd.read_csv(f'{BASE_DIR}/validation_sequences.csv')\n\nprint(f\"📌 Sequences shape: {vali_seq.shape}\")\nvali_seq.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.628462Z","iopub.execute_input":"2026-03-15T09:56:57.628786Z","iopub.status.idle":"2026-03-15T09:56:57.64932Z","shell.execute_reply.started":"2026-03-15T09:56:57.628755Z","shell.execute_reply":"2026-03-15T09:56:57.648511Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Loading test_sequences.csv\")\ntest_seq = pd.read_csv(f'{BASE_DIR}/test_sequences.csv')\n\nprint(f\"📌 Sequences shape: {test_seq.shape}\")\ntest_seq.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.651223Z","iopub.execute_input":"2026-03-15T09:56:57.651484Z","iopub.status.idle":"2026-03-15T09:56:57.67014Z","shell.execute_reply.started":"2026-03-15T09:56:57.651458Z","shell.execute_reply":"2026-03-15T09:56:57.669172Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"1. 配列の「重複」と「類似性」の分析\nデータ例を見ると、sequence（配列）が全く同じなのに、target_id や temporal_cutoff（登録日）が異なるデータが複数あります。\n\n分析内容:\n\nユニークな配列の数はいくつか？\n\n同じ配列に対して、どれくらい異なる構造（正解データ）が存在するか？\n\n目的: 学習データと検証データ（Validation）を分ける際、**同じ配列が両方に混ざらないようにする（GroupKFold）**ためです。これを怠ると、スコアが不当に高く出てしまう「リーク」が発生します。","metadata":{}},{"cell_type":"code","source":"train_seqs = set(train_seq['sequence'])\nvali_seqs = set(vali_seq['sequence'])\noverlap = train_seqs.intersection(vali_seqs)\nprint(f\"共通する配列数: {len(overlap)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.671481Z","iopub.execute_input":"2026-03-15T09:56:57.671801Z","iopub.status.idle":"2026-03-15T09:56:57.68129Z","shell.execute_reply.started":"2026-03-15T09:56:57.671772Z","shell.execute_reply":"2026-03-15T09:56:57.680492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"2. 配列長の分布（Sequence Length）\nRNAの折り畳み予測の難易度は、配列長に比例して爆発的に上がります。\n\n分析内容: sequence の文字数（長さ）のヒストグラムを作成。\n\n目的: * モデルのメモリ制限（Max Length）をどこに設定するか決める。\n\n短い配列（予測しやすい）と長い配列（難しい）で、バリデーションスコアにどれくらい差が出るかを確認する。","metadata":{}},{"cell_type":"code","source":"# まずはシンプルに、**TrainとValidationの「配列長の統計量」**を出してみてください。\n\nprint(\"Train sequence length stats:\")\nprint(train_seq['sequence'].str.len().describe())\n\nprint(\"\\nValidation sequence length stats:\")\nprint(vali_seq['sequence'].str.len().describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.682272Z","iopub.execute_input":"2026-03-15T09:56:57.682523Z","iopub.status.idle":"2026-03-15T09:56:57.713173Z","shell.execute_reply.started":"2026-03-15T09:56:57.682497Z","shell.execute_reply":"2026-03-15T09:56:57.712138Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"① 超長大なデータの確認\nTrainにある length > 10,000 のデータが何者か見てみてください。","metadata":{}},{"cell_type":"code","source":"# 長い順に表示\nprint(train_seq[train_seq['sequence'].str.len() > 10000][['target_id', 'description']])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.714581Z","iopub.execute_input":"2026-03-15T09:56:57.71552Z","iopub.status.idle":"2026-03-15T09:56:57.72962Z","shell.execute_reply.started":"2026-03-15T09:56:57.715489Z","shell.execute_reply":"2026-03-15T09:56:57.728923Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"この「12万塩基」のデータをどう料理するか決めるために、以下のコードを実行して**「ターゲットごとのチェーン（鎖）の数」**を推測してみませんか？","metadata":{}},{"cell_type":"code","source":"# 巨大データの stoichiometry（化学量論）や all_sequences の中身を少し覗く\nprint(train_seq[train_seq['sequence'].str.len() > 10000][['target_id', 'stoichiometry', 'all_sequences']].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.730764Z","iopub.execute_input":"2026-03-15T09:56:57.731233Z","iopub.status.idle":"2026-03-15T09:56:57.752172Z","shell.execute_reply.started":"2026-03-15T09:56:57.731196Z","shell.execute_reply":"2026-03-15T09:56:57.751226Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"12万塩基の正体が、何本のチェーンの集合体なのか（num_chains）」 を出してみてください。","metadata":{}},{"cell_type":"code","source":"# 特定の巨大データの情報を深掘りする\ntarget_row = train_seq[train_seq['target_id'] == '5TGM'].iloc[0]\n\nprint(f\"--- Target: {target_row['target_id']} ---\")\nprint(f\"Sequence Length: {len(target_row['sequence'])}\")\nprint(\"\\n[Stoichiometry]\")\nprint(target_row['stoichiometry'])\n\nprint(\"\\n[All Sequences (First 1000 characters)]\")\n# 文字列の最初の方だけをしっかり出す\nprint(target_row['all_sequences'][:1000])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.753279Z","iopub.execute_input":"2026-03-15T09:56:57.753602Z","iopub.status.idle":"2026-03-15T09:56:57.771974Z","shell.execute_reply.started":"2026-03-15T09:56:57.753565Z","shell.execute_reply":"2026-03-15T09:56:57.771065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 鎖の数がどれくらい複雑か（セミコロンの数でカウント）\ntrain_seq['num_chains'] = train_seq['stoichiometry'].str.count(';') + 1\nprint(\"\\n鎖の数の統計:\")\nprint(train_seq['num_chains'].describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.77341Z","iopub.execute_input":"2026-03-15T09:56:57.773774Z","iopub.status.idle":"2026-03-15T09:56:57.797995Z","shell.execute_reply.started":"2026-03-15T09:56:57.773737Z","shell.execute_reply":"2026-03-15T09:56:57.796776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 配列の長さを計算\ntest_seq['seq_len'] = test_seq['sequence'].str.len()\n\nprint(\"--- Test Sequence Length Statistics ---\")\nprint(test_seq['seq_len'].describe())\n\n# 可視化：分布を確認\nplt.figure(figsize=(10, 5))\nsns.histplot(test_seq['seq_len'], bins=30, kde=True, color='skyblue')\nplt.title('Distribution of Test Sequence Lengths')\nplt.xlabel('Sequence Length')\nplt.ylabel('Count')\nplt.grid(axis='y', alpha=0.3)\nplt.show()\n\n# 巨大な配列（例：512塩基以上）がどれくらいあるか確認\nthreshold = 512\nlarge_seqs = test_seq[test_seq['seq_len'] > threshold]\nprint(f\"\\n{threshold}塩基を超えるデータの数: {len(large_seqs)} ({len(large_seqs)/len(test_seq)*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:57.800279Z","iopub.execute_input":"2026-03-15T09:56:57.80063Z","iopub.status.idle":"2026-03-15T09:56:58.15535Z","shell.execute_reply.started":"2026-03-15T09:56:57.800593Z","shell.execute_reply":"2026-03-15T09:56:58.154451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# sample_submissionがあれば、そのID形式を確認\ntry:\n    sample_sub = pd.read_csv(f'{BASE_DIR}/sample_submission.csv')\n    print(\"\\n--- Sample Submission ID Format ---\")\n    print(sample_sub['ID'].head(10))\n    \n    # IDを'_'で分割して、[ターゲットID, 残基番号] になっているか確認\n    id_parts = sample_sub['ID'].str.split('_', expand=True)\n    if id_parts.shape[1] >= 2:\n        print(\"\\nParsed ID structure (Target | Residue Index):\")\n        print(id_parts.head())\nexcept FileNotFoundError:\n    print(\"\\nsample_submission.csvが見つかりません。\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:58.159124Z","iopub.execute_input":"2026-03-15T09:56:58.159447Z","iopub.status.idle":"2026-03-15T09:56:58.202102Z","shell.execute_reply.started":"2026-03-15T09:56:58.159416Z","shell.execute_reply":"2026-03-15T09:56:58.201148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\ndef process_rna_labels(df, is_train=True):\n    # カラム名を小文字に統一 (X_1 -> x_1)\n    df.columns = [c.lower() for c in df.columns]\n    \n    # IDを分割してターゲットIDを取得\n    df['target_id'] = df['id'].apply(lambda x: x.split('_')[0])\n    df['resid_idx'] = df['id'].apply(lambda x: int(x.split('_')[1]))\n    \n    # 座標のクリーニング (-1e+18 を NaN に)\n    # x_1, y_1, z_1 だけをとりあえず対象にする\n    coord_cols = ['x_1', 'y_1', 'z_1']\n    for col in coord_cols:\n        df.loc[df[col] < -1e+15, col] = np.nan\n        \n    # ターゲットごとにグループ化して、リストに格納\n    processed_list = []\n    for target_id, group in df.groupby('target_id'):\n        group = group.sort_values('resid_idx')\n        \n        # 座標を (L, 3) の numpy配列に\n        coords = group[coord_cols].values\n        \n        # 配列長と、座標が有効な残基数を記録\n        valid_mask = ~np.isnan(coords).any(axis=1)\n        \n        processed_list.append({\n            'target_id': target_id,\n            'coords': coords,\n            'valid_mask': valid_mask, # 学習時にLossを計算する場所を特定するため\n            'length': len(group)\n        })\n        \n    return processed_list\n\n# 実行例\n# tr_list = process_rna_labels(train_df, is_train=True)\n# val_list = process_rna_labels(val_df, is_train=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:58.203253Z","iopub.execute_input":"2026-03-15T09:56:58.20362Z","iopub.status.idle":"2026-03-15T09:56:58.212456Z","shell.execute_reply.started":"2026-03-15T09:56:58.20358Z","shell.execute_reply":"2026-03-15T09:56:58.211554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_l = pd.read_csv(f'{BASE_DIR}/train_labels.csv')\nvali_l = pd.read_csv(f'{BASE_DIR}/validation_labels.csv')\n\ntr_list = process_rna_labels(train_l, is_train=True)\nval_list = process_rna_labels(vali_l, is_train=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:56:58.213427Z","iopub.execute_input":"2026-03-15T09:56:58.213671Z","iopub.status.idle":"2026-03-15T09:57:23.904883Z","shell.execute_reply.started":"2026-03-15T09:56:58.213641Z","shell.execute_reply":"2026-03-15T09:57:23.903971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def check_processed_data(data_list, name=\"Dataset\"):\n    print(f\"--- {name} Summary ---\")\n    print(f\"Total Targets: {len(data_list)}\")\n    \n    # 最初1件のデータ構造を確認\n    sample = data_list[0]\n    print(f\"Sample Target ID: {sample['target_id']}\")\n    print(f\"Sequence Length: {sample['length']}\")\n    print(f\"Coordinate Shape: {sample['coords'].shape}\") # (L, 3) であるべき\n    \n    # 座標にNaN以外の有効な値が含まれているか\n    valid_count = sample['valid_mask'].sum()\n    print(f\"Valid Coordinates: {valid_count} / {sample['length']} ({(valid_count/sample['length'])*100:.1f}%)\")\n    print(\"\\n\")\n\ncheck_processed_data(tr_list, \"Train\")\ncheck_processed_data(val_list, \"Validation\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:23.906313Z","iopub.execute_input":"2026-03-15T09:57:23.906688Z","iopub.status.idle":"2026-03-15T09:57:23.913882Z","shell.execute_reply.started":"2026-03-15T09:57:23.906648Z","shell.execute_reply":"2026-03-15T09:57:23.912847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 配列データの読み込み\ntrain_seqs = pd.read_csv(f'{BASE_DIR}/train_sequences.csv')\nval_seqs = pd.read_csv(f'{BASE_DIR}/validation_sequences.csv')\n\n# ターゲットIDをキーにして辞書化（高速化のため）\ntrain_seq_dict = dict(zip(train_seqs['target_id'], train_seqs['sequence']))\nval_seq_dict = dict(zip(val_seqs['target_id'], val_seqs['sequence']))\n\ndef merge_sequences(data_list, seq_dict):\n    new_list = []\n    for item in data_list:\n        target_id = item['target_id']\n        if target_id in seq_dict:\n            item['sequence'] = seq_dict[target_id]\n            # 念のため配列長と座標長が一致するか最終チェック\n            if len(item['sequence']) == item['length']:\n                new_list.append(item)\n            else:\n                print(f\"Warning: Length mismatch in {target_id}\")\n    return new_list\n\n# 合体！\ntr_final = merge_sequences(tr_list, train_seq_dict)\nval_final = merge_sequences(val_list, val_seq_dict)\n\nprint(f\"Final Train samples: {len(tr_final)}\")\nprint(f\"Final Val samples: {len(val_final)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:23.915146Z","iopub.execute_input":"2026-03-15T09:57:23.915495Z","iopub.status.idle":"2026-03-15T09:57:24.319963Z","shell.execute_reply.started":"2026-03-15T09:57:23.915459Z","shell.execute_reply":"2026-03-15T09:57:24.318713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def criterion(pred, target, mask):\n    # pred, target: (Batch, SeqLen, 3)\n    # mask: (Batch, SeqLen)\n    \n    # 2乗誤差\n    loss = torch.sum((pred - target) ** 2, dim=-1) # (Batch, SeqLen)\n    \n    # マスクされた場所(有効な座標がある場所)のみを抽出\n    masked_loss = loss[mask].mean()\n    return masked_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.321128Z","iopub.execute_input":"2026-03-15T09:57:24.321514Z","iopub.status.idle":"2026-03-15T09:57:24.327155Z","shell.execute_reply.started":"2026-03-15T09:57:24.321473Z","shell.execute_reply":"2026-03-15T09:57:24.326332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nclass RNA5HeadModel(nn.Module):\n    def __init__(self, d_model=128, nhead=8, num_layers=6, dim_feedforward=512):\n        super(RNA5HeadModel, self).__init__()\n        \n        # 1. 塩基の埋め込み (A, C, G, U, N + Padding)\n        self.embedding = nn.Embedding(6, d_model)\n        \n        # 2. 位置エンコーディング\n        self.pos_embedding = nn.Parameter(torch.zeros(1, 512, d_model))\n        \n        # 3. Transformer エンコーダー\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model=d_model, \n            nhead=nhead, \n            dim_feedforward=dim_feedforward,\n            batch_first=True\n        )\n        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)\n        \n        # 4. 座標出力層 (5つの独立したヘッド)\n        # 各ヘッドが (x, y, z) の3次元を出力\n        self.heads = nn.ModuleList([\n            nn.Linear(d_model, 3) for _ in range(5)\n        ])\n        \n    def forward(self, input_ids):\n        # input_ids: (Batch, SeqLen)\n        seq_len = input_ids.size(1)\n        \n        x = self.embedding(input_ids)\n        x = x + self.pos_embedding[:, :seq_len, :]\n        \n        # 共通の特徴量を抽出\n        features = self.transformer_encoder(x) # (Batch, SeqLen, d_model)\n        \n        # 5つのヘッドからそれぞれの座標を計算\n        # output shape: (Batch, 5, SeqLen, 3)\n        outputs = torch.stack([head(features) for head in self.heads], dim=1)\n        \n        return outputs\n\n# モデルのインスタンス化\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = RNA5HeadModel().to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.328164Z","iopub.execute_input":"2026-03-15T09:57:24.328449Z","iopub.status.idle":"2026-03-15T09:57:24.396158Z","shell.execute_reply.started":"2026-03-15T09:57:24.328415Z","shell.execute_reply":"2026-03-15T09:57:24.395287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nclass RNA5HeadModel(nn.Module):\n    def __init__(self, d_model=128, num_layers=3):\n        super(RNA5HeadModel, self).__init__()\n        \n        # 1. 塩基の埋め込み (A, C, G, U, N + Padding)\n        self.embedding = nn.Embedding(6, d_model)\n        \n        # 2. 双方向GRU (Transformerの代わり)\n        # メモリ効率が良く、標準ライブラリのみで動作\n        self.gru = nn.GRU(\n            input_size=d_model,\n            hidden_size=d_model,\n            num_layers=num_layers,\n            batch_first=True,\n            bidirectional=True,\n            dropout=0.1 if num_layers > 1 else 0\n        )\n        \n        # 双方向の出力を d_model のサイズに統合する層\n        self.fc_reduce = nn.Linear(d_model * 2, d_model)\n        \n        # 3. 座標出力層 (5つの独立したヘッド)\n        # ここも以前の構成を完全に維持\n        self.heads = nn.ModuleList([\n            nn.Linear(d_model, 3) for _ in range(5)\n        ])\n        \n    def forward(self, input_ids):\n        # 埋め込み\n        x = self.embedding(input_ids) # (Batch, SeqLen, d_model)\n        \n        # GRUによる特徴抽出\n        # Transformerと違い、どんなに長いシーケンスも $O(L)$ で処理可能\n        x_gru, _ = self.gru(x) # (Batch, SeqLen, d_model * 2)\n        \n        # 特徴量の次元をヘッドの入力に合わせる\n        features = self.fc_reduce(x_gru) # (Batch, SeqLen, d_model)\n        \n        # 5つのヘッドからそれぞれの座標を計算\n        # output shape: (Batch, 5, SeqLen, 3)\n        outputs = torch.stack([head(features) for head in self.heads], dim=1)\n        \n        return outputs\n\n# インスタンス化 (名前が同じなので、これ以降のコードは変更不要です)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = RNA5HeadModel(d_model=128, num_layers=3).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.397307Z","iopub.execute_input":"2026-03-15T09:57:24.39757Z","iopub.status.idle":"2026-03-15T09:57:24.416907Z","shell.execute_reply.started":"2026-03-15T09:57:24.397544Z","shell.execute_reply":"2026-03-15T09:57:24.415972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def best_of_5_loss(preds, target, mask):\n#     \"\"\"\n#     preds: (Batch, 5, SeqLen, 3) - モデルの5つの予測\n#     target: (Batch, SeqLen, 3) - 1つだけの正解\n#     mask: (Batch, SeqLen) - 有効な座標フラグ\n#     \"\"\"\n#     # 各ヘッドごとのMSEを計算\n#     # targetを (Batch, 1, SeqLen, 3) に拡張して比較\n#     diff = (preds - target.unsqueeze(1)) ** 2\n#     mse_per_head = diff.sum(dim=-1) # (Batch, 5, SeqLen)\n    \n#     # マスク適用後の1サンプルあたりのロス\n#     # 有効な残基のみの平均を取る\n#     masked_mse = (mse_per_head * mask.unsqueeze(1)).sum(dim=-1) / mask.sum(dim=-1).unsqueeze(1) # (Batch, 5)\n    \n#     # 5つの予測のうち、最も誤差が小さいヘッドを選択\n#     best_loss, _ = torch.min(masked_mse, dim=1) # (Batch,)\n    \n#     return best_loss.mean()\n\ndef best_of_5_loss(preds, target, mask):\n    # 1. 差の絶対値を計算 (L1 Lossベース)\n    # これにより、異常に遠い座標があっても勾配が爆発しません\n    abs_diff = torch.abs(preds - target.unsqueeze(1))\n    \n    # Huber Loss的な挙動: 1以下の誤差は2乗、1以上は線形にする\n    # シンプルに L1 (絶対値誤差) にするだけでも NaN 対策には非常に有効です\n    error_per_residue = torch.where(abs_diff < 1.0, 0.5 * abs_diff**2, abs_diff - 0.5)\n    \n    # 残基ごとの合計誤差\n    error_sum = error_per_residue.sum(dim=-1) # (Batch, 5, SeqLen)\n    \n    # マスク適用と平均\n    weights = mask.unsqueeze(1).float()\n    num_valid = mask.sum(dim=-1).unsqueeze(1).float() + 1e-8\n    \n    masked_error = (error_sum * weights).sum(dim=-1) / num_valid # (Batch, 5)\n    \n    # 5つの中で最小のものを選択\n    best_loss, _ = torch.min(masked_error, dim=1)\n    \n    return best_loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.418271Z","iopub.execute_input":"2026-03-15T09:57:24.419056Z","iopub.status.idle":"2026-03-15T09:57:24.427384Z","shell.execute_reply.started":"2026-03-15T09:57:24.419023Z","shell.execute_reply":"2026-03-15T09:57:24.42667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from torch.nn.utils.rnn import pad_sequence\n\n# def rna_collate_fn(batch):\n#     \"\"\"\n#     長さの違うデータをパディングしてバッチ化する関数\n#     \"\"\"\n#     input_ids = [item['input_ids'] for item in batch]\n#     labels = [item['labels'] for item in batch]\n    \n#     # 配列をパディング (一番長いものに合わせる)\n#     input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=5) # 5: Padding ID\n    \n#     # 座標をパディング (NaNで埋める)\n#     labels_padded = pad_sequence(labels, batch_first=True, padding_value=float('nan'))\n    \n#     # どこが有効なデータかを示すマスクを作成\n#     mask = ~torch.isnan(labels_padded).any(dim=-1)\n    \n#     # 後の計算のためにNaNを0に変換 (maskがあるから0でOK)\n#     labels_padded = torch.nan_to_num(labels_padded, nan=0.0)\n    \n#     return {\n#         'input_ids': input_ids_padded,\n#         'labels': labels_padded,\n#         'mask': mask\n#     }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.428787Z","iopub.execute_input":"2026-03-15T09:57:24.429235Z","iopub.status.idle":"2026-03-15T09:57:24.447795Z","shell.execute_reply.started":"2026-03-15T09:57:24.429205Z","shell.execute_reply":"2026-03-15T09:57:24.446639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class RNADataset(Dataset):\n#     def __init__(self, data_list, max_len=512, mode='train'):\n#         self.data = data_list\n#         self.max_len = max_len\n#         self.mode = mode\n#         self.nt_map = {'A': 0, 'C': 1, 'G': 2, 'U': 3, 'N': 4}\n\n#     def __len__(self):\n#         return len(self.data)\n\n#     def __getitem__(self, idx):\n#         item = self.data[idx]\n        # seq = item['sequence']\n        # coords = item['coords'].copy() # 元データを壊さないようコピー\n        \n        # # --- [追加] 座標の正規化 ---\n        # # 1. 重心を 0 に (Centering)\n        # # NaNを除外して有効な座標の平均を計算\n        # mask = ~np.isnan(coords).any(axis=1)\n        # if mask.any():\n        #     center = coords[mask].mean(axis=0)\n        #     coords[mask] = coords[mask] - center\n            \n        #     # 2. スケーリング (Scaling)\n        #     # 10.0 で割ることで数値を小さくし、学習を安定させる\n        #     coords[mask] = coords[mask] / 10.0\n        # # ------------------------\n\n        # # 配列を数値化\n        # input_ids = np.array([self.nt_map.get(s, 4) for s in seq])\n        \n        # # ランダムクロップ (学習時のみ)\n        # if self.mode == 'train' and len(input_ids) > self.max_len:\n        #     start = np.random.randint(0, len(input_ids) - self.max_len)\n        #     input_ids = input_ids[start:start+self.max_len]\n        #     coords = coords[start:start+self.max_len]\n            \n        # return {\n        #     'input_ids': torch.tensor(input_ids, dtype=torch.long),\n        #     'labels': torch.tensor(coords, dtype=torch.float32),\n        #     'target_id': item['target_id']\n        # }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.449039Z","iopub.execute_input":"2026-03-15T09:57:24.44999Z","iopub.status.idle":"2026-03-15T09:57:24.465998Z","shell.execute_reply.started":"2026-03-15T09:57:24.449947Z","shell.execute_reply":"2026-03-15T09:57:24.465025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.nn.utils.rnn import pad_sequence\n\nclass RNADataset(Dataset):\n    def __init__(self, data_list, max_len=512, mode='train'):\n        self.data = data_list\n        self.max_len = max_len\n        self.mode = mode\n        self.nt_map = {'A': 0, 'C': 1, 'G': 2, 'U': 3, 'N': 4}\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        item = self.data[idx]\n        seq = item['sequence']\n        coords = item['coords'].copy()\n        \n        # 1. 座標の正規化（10倍スケールダウン）\n        mask_valid = ~np.isnan(coords).any(axis=1)\n        if mask_valid.any():\n            center = coords[mask_valid].mean(axis=0)\n            coords[mask_valid] = (coords[mask_valid] - center) / 10.0\n        \n        input_ids = np.array([self.nt_map.get(s, 4) for s in seq])\n        \n        # 2. クロップ処理（ここが重要！）\n        # 長すぎる場合に、input_ids と coords の両方を同じ範囲で切る\n        if len(input_ids) > self.max_len:\n            if self.mode == 'train':\n                start = np.random.randint(0, len(input_ids) - self.max_len)\n            else:\n                start = 0 # Val/Testは先頭から\n            input_ids = input_ids[start:start+self.max_len]\n            coords = coords[start:start+self.max_len]\n\n        return {\n            'input_ids': torch.tensor(input_ids, dtype=torch.long),\n            'labels': torch.tensor(coords, dtype=torch.float32)\n        }\n\ndef rna_collate_fn(batch):\n    # バッチ内の最大長に合わせてパディング\n    input_ids = [item['input_ids'] for item in batch]\n    labels = [item['labels'] for item in batch]\n    \n    # input_ids をパディング (0埋め)\n    input_ids_padded = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=0)\n    \n    # labels をパディング (NaN埋め)\n    labels_padded = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=float('nan'))\n    \n    # マスクの作成 (データが存在する場所を True に)\n    # labels_padded の shape は (Batch, SeqLen, 3) なので、x座標がNaNでない場所を見る\n    mask = ~torch.isnan(labels_padded[:, :, 0])\n    \n    # NaNを0に置換（計算でエラーにならないように）\n    labels_padded = torch.where(torch.isnan(labels_padded), torch.tensor(0.0), labels_padded)\n    \n    return {\n        'input_ids': input_ids_padded,\n        'labels': labels_padded,\n        'mask': mask\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.467142Z","iopub.execute_input":"2026-03-15T09:57:24.467503Z","iopub.status.idle":"2026-03-15T09:57:24.484939Z","shell.execute_reply.started":"2026-03-15T09:57:24.467465Z","shell.execute_reply":"2026-03-15T09:57:24.483766Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\n# Dataset の作成 (まだなら実行)\ntrain_dataset = RNADataset(tr_final, max_len=512, mode='train')\nval_dataset = RNADataset(val_final, max_len=512, mode='val')\n\n# DataLoader の作成 (ここで rna_collate_fn を使う)\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True, collate_fn=rna_collate_fn)\nval_loader = DataLoader(val_dataset, batch_size=8, shuffle=False, collate_fn=rna_collate_fn)\n\nprint(\"DataLoaderの作成に成功しました！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.486285Z","iopub.execute_input":"2026-03-15T09:57:24.486596Z","iopub.status.idle":"2026-03-15T09:57:24.502609Z","shell.execute_reply.started":"2026-03-15T09:57:24.486567Z","shell.execute_reply":"2026-03-15T09:57:24.501469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 異常値をチェックして、正常なデータだけを残す\ntr_final_cleaned = []\nextreme_count = 0\n\nfor item in tr_final:\n    # 座標の絶対値が 1000 を超えるものが1つでもあれば異常とみなす\n    # (1000ÅはRNA構造としては巨大すぎます)\n    if np.any(np.abs(item['coords']) > 1000):\n        extreme_count += 1\n        continue\n    tr_final_cleaned.append(item)\n\nprint(f\"除外した異常データ数: {extreme_count}\")\nprint(f\"学習に使用する正常データ数: {len(tr_final_cleaned)}\")\n\n# Dataset を作り直す\ntrain_dataset = RNADataset(tr_final_cleaned, max_len=512, mode='train')\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True, collate_fn=rna_collate_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.504322Z","iopub.execute_input":"2026-03-15T09:57:24.50467Z","iopub.status.idle":"2026-03-15T09:57:24.596476Z","shell.execute_reply.started":"2026-03-15T09:57:24.504632Z","shell.execute_reply":"2026-03-15T09:57:24.595299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Validationデータのクリーニング\nval_final_cleaned = []\nval_extreme_count = 0\n\nfor item in val_final:\n    if np.any(np.abs(item['coords']) > 1000):\n        val_extreme_count += 1\n        continue\n    val_final_cleaned.append(item)\n\nprint(f\"Val除外数: {val_extreme_count}, Val正常データ数: {len(val_final_cleaned)}\")\n\n# Dataset / DataLoader の作成\nval_dataset = RNADataset(val_final_cleaned, max_len=512, mode='val')\nval_loader = DataLoader(val_dataset, batch_size=8, shuffle=False, collate_fn=rna_collate_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.597792Z","iopub.execute_input":"2026-03-15T09:57:24.598076Z","iopub.status.idle":"2026-03-15T09:57:24.606204Z","shell.execute_reply.started":"2026-03-15T09:57:24.598049Z","shell.execute_reply":"2026-03-15T09:57:24.605129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. 異常なIDを特定\nbad_ids = ['4ERL', '4WSB', '6W6L']\n\n# 2. 異常IDを含まない新しいリストを作成\ntr_final_cleaned = [item for item in tr_final if item['target_id'] not in bad_ids]\n\n# 3. 念のための最終検品（何も表示されなければ合格）\nfor item in tr_final_cleaned:\n    if np.any(np.abs(item['coords']) > 1000):\n        print(f\"まだ異常があります: {item['target_id']}\")\n\nprint(f\"元の数: {len(tr_final)}, クリーン後の数: {len(tr_final_cleaned)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.607627Z","iopub.execute_input":"2026-03-15T09:57:24.608146Z","iopub.status.idle":"2026-03-15T09:57:24.704856Z","shell.execute_reply.started":"2026-03-15T09:57:24.60811Z","shell.execute_reply":"2026-03-15T09:57:24.704064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. 新しいリストを使ってDatasetを再生成\ntrain_dataset = RNADataset(tr_final_cleaned, max_len=512, mode='train')\n\n# 5. DataLoaderも再生成（これで古いデータは完全に消えます）\ntrain_loader = DataLoader(\n    train_dataset, \n    batch_size=16, \n    shuffle=True, \n    collate_fn=rna_collate_fn\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.705725Z","iopub.execute_input":"2026-03-15T09:57:24.706555Z","iopub.status.idle":"2026-03-15T09:57:24.711041Z","shell.execute_reply.started":"2026-03-15T09:57:24.706516Z","shell.execute_reply":"2026-03-15T09:57:24.71021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\nfrom tqdm import tqdm\n\n# 1. DataLoaderの設定\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True, collate_fn=rna_collate_fn)\nval_loader = DataLoader(val_dataset, batch_size=8, shuffle=False, collate_fn=rna_collate_fn)\n\n# 2. Optimizerの設定\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)\n\n# 学習ループの直前にこれを入れて、異常なデータがないか確認してください\nfor i, item in enumerate(tr_final_cleaned):\n    if np.any(np.abs(item['coords']) > 1000): # 1000Åを超える異常な座標\n        print(f\"Index {i} (ID: {item['target_id']}) has extreme coordinates!\")\n\n# 3. 学習ループ\nnum_epochs = 10\n\nfor epoch in range(num_epochs):\n    model.train()\n    train_loss = 0\n    \n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}\")\n    for batch in pbar:\n        # データをGPUへ\n        input_ids = batch['input_ids'].to(device)\n        labels = batch['labels'].to(device)\n        mask = batch['mask'].to(device)\n        \n        optimizer.zero_grad()\n        \n        # 予測 (Batch, 5, SeqLen, 3)\n        preds = model(input_ids)\n        \n        # 先ほど定義した Best-of-5 Loss を計算\n        loss = best_of_5_loss(preds, labels, mask)\n        \n        \n        # optimizer.step() の直前に追加してください\n        loss.backward()\n        \n        # 勾配が大きくなりすぎないように制限（爆発防止）\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        \n        optimizer.step()\n        \n        train_loss += loss.item()\n        pbar.set_postfix(loss=loss.item())\n    \n    # バリデーション\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for batch in val_loader:\n            input_ids = batch['input_ids'].to(device)\n            labels = batch['labels'].to(device)\n            mask = batch['mask'].to(device)\n            \n            preds = model(input_ids)\n            loss = best_of_5_loss(preds, labels, mask)\n            val_loss += loss.item()\n\n    \n    # 各エポックの終了後にモデルの重みを保存\n    torch.save(model.state_dict(), f'rna_model_v1_epoch_{epoch+1}.pth')        \n    print(f\"Epoch {epoch+1} Summary: Train Loss: {train_loss/len(train_loader):.4f}, Val Loss: {val_loss/len(val_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T09:57:24.71215Z","iopub.execute_input":"2026-03-15T09:57:24.712521Z","iopub.status.idle":"2026-03-15T10:23:33.464042Z","shell.execute_reply.started":"2026-03-15T09:57:24.712486Z","shell.execute_reply":"2026-03-15T10:23:33.462845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission(model, data_list, device, filename=\"submission.csv\"):\n    model.eval()\n    results = []\n    max_len = 512  # モデルの pos_embedding のサイズ制限\n    \n    print(\"Generating submission...\")\n    for item in tqdm(data_list):\n        target_id = item['target_id']\n        seq = item['sequence']\n        \n        # 1. 512文字を超えていたらカットする（RuntimeError回避）\n        if len(seq) > max_len:\n            seq = seq[:max_len]\n         # 2. 入力データの準備   \n        input_ids = torch.tensor([{'A':0,'C':1,'G':2,'U':3,'N':4}.get(s,4) for s in seq], dtype=torch.long).unsqueeze(0).to(device)\n       \n        with torch.no_grad():\n            # 予測結果の取得\n            preds = model(input_ids)\n            # 10倍して元の座標スケールに戻す\n            preds = (preds * 10.0).cpu().numpy()[0]\n            \n        for i, nt in enumerate(seq):\n            # 例に合わせ、ID, resname, resid を作成\n            row = [\n                f\"{target_id}_{i+1}\", # ID\n                nt,                    # resname (G, A, etc.)\n                i + 1                  # resid (1, 2, 3...)\n            ]\n            for head in range(5):\n                row.extend(preds[head, i].tolist())\n            results.append(row)\n            \n    # カラム名を例に合わせる (x_1, y_1, z_1 ...)\n    columns = ['ID', 'resname', 'resid']\n    for i in range(1, 6):\n        columns.extend([f'x_{i}', f'y_{i}', f'z_{i}'])\n        \n     # 5. PandasでCSVとして保存   \n    import pandas as pd\n    sub_df = pd.DataFrame(results, columns=columns)\n    sub_df.to_csv(filename, index=False)\n    print(f\"Saved correctly formatted submission to {filename}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T10:23:55.958234Z","iopub.execute_input":"2026-03-15T10:23:55.958612Z","iopub.status.idle":"2026-03-15T10:23:55.96922Z","shell.execute_reply.started":"2026-03-15T10:23:55.958581Z","shell.execute_reply":"2026-03-15T10:23:55.968123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def create_submission(model, data_list, device, filename=\"submission.csv\"):\n#     model.eval()\n#     results = []\n#     max_len = 512  # モデルの pos_embedding のサイズ制限\n    \n#     print(\"Generating submission...\")\n#     # 進捗を表示しながら推論\n#     for item in tqdm(data_list):\n#         target_id = item['target_id']\n#         seq = item['sequence']\n        \n#         # 1. 512文字を超えていたらカットする（RuntimeError回避）\n#         if len(seq) > max_len:\n#             seq = seq[:max_len]\n            \n#         # 2. 入力データの準備\n#         nt_map = {'A': 0, 'C': 1, 'G': 2, 'U': 3, 'N': 4}\n#         input_ids = torch.tensor([nt_map.get(s, 4) for s in seq], dtype=torch.long).unsqueeze(0).to(device)\n       \n#         with torch.no_grad():\n#             # 予測結果の取得 (1, 5, SeqLen, 3)\n#             preds = model(input_ids)\n#             # 10倍して元の座標スケールに戻す\n#             preds = (preds * 10.0).cpu().numpy()[0] # (5, SeqLen, 3)\n            \n#         # 3. 各塩基（レジデント）ごとに1行作成\n#         for i, nt in enumerate(seq):\n#             # ID形式: [PDB_ID]_[塩基番号]\n#             row = [f\"{target_id}_{i+1}\"]\n#             # 5つのヘッドそれぞれの (x, y, z) を平坦化して追加\n#             for head in range(5):\n#                 row.extend(preds[head, i].tolist()) # [x, y, z]\n#             results.append(row)\n            \n#     # 4. カラム名（ヘッダー）の作成\n#     columns = ['id']\n#     for i in range(1, 6):\n#         columns.extend([f'x{i}', f'y{i}', f'z{i}'])\n        \n#     # 5. PandasでCSVとして保存\n#     import pandas as pd\n#     sub_df = pd.DataFrame(results, columns=columns)\n#     sub_df.to_csv(filename, index=False)\n#     print(f\"Successfully saved {len(sub_df)} rows to {filename}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T10:23:33.466903Z","iopub.status.idle":"2026-03-15T10:23:33.467909Z","shell.execute_reply.started":"2026-03-15T10:23:33.467639Z","shell.execute_reply":"2026-03-15T10:23:33.467672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# テストデータの読み込み\ntest_df = pd.read_csv(f'{BASE_DIR}/test_sequences.csv')\n\n# 推論用のリスト形式に変換\ntest_list = []\nfor _, row in test_df.iterrows():\n    test_list.append({\n        'target_id': row['target_id'],\n        'sequence': row['sequence']\n    })\n\nprint(f\"Total test samples: {len(test_list)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T10:24:00.25575Z","iopub.execute_input":"2026-03-15T10:24:00.25677Z","iopub.status.idle":"2026-03-15T10:24:00.268913Z","shell.execute_reply.started":"2026-03-15T10:24:00.256737Z","shell.execute_reply":"2026-03-15T10:24:00.268037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 実行指示 ---\n# 学習が終わった直後に呼び出す（val_final_cleaned を対象にする）\n\n# --- 1. 学習済み重みのロード ---\n# 学習ループで最後に保存されたエポックの番号を指定してください（例: epoch 10 なら 10）\nlast_epoch = num_epochs\nmodel_path = f'rna_model_v1_epoch_{last_epoch}.pth'\n\nprint(f\"Loading weights from {model_path}...\")\nmodel.load_state_dict(torch.load(model_path, map_location=device))\nmodel.to(device)\n\ncreate_submission(model, test_list, device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T10:24:14.267625Z","iopub.execute_input":"2026-03-15T10:24:14.267972Z","iopub.status.idle":"2026-03-15T10:24:15.929532Z","shell.execute_reply.started":"2026-03-15T10:24:14.267943Z","shell.execute_reply":"2026-03-15T10:24:15.92856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# 作成されたファイルを読み込み\ndf_check = pd.read_csv('submission.csv')\n\n# データの形状（行数、列数）を表示\nprint(f\"Submission Shape: {df_check.shape}\")\nprint(\"-\" * 30)\n\n# 冒頭5行を表示\ndisplay(df_check.head())\n\n# 各カラムに欠損値（NaN）がないかも一応チェック\nprint(\"-\" * 30)\nprint(\"Null values check:\")\nprint(df_check.isnull().sum().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T10:25:30.138864Z","iopub.execute_input":"2026-03-15T10:25:30.139211Z","iopub.status.idle":"2026-03-15T10:25:30.17986Z","shell.execute_reply.started":"2026-03-15T10:25:30.139179Z","shell.execute_reply":"2026-03-15T10:25:30.179127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# 1. あなたが作った予測ファイルを読み込む\ndf_pred = pd.read_csv('submission.csv')\n\n# 2. コンペ公式のサンプルファイルを読み込む\ndf_sample = pd.read_csv(f'{BASE_DIR}/sample_submission.csv')\n\n# 3. IDをキーにして、サンプルの順番通りにあなたの予測値をマッピングする\n# サンプルの ID, resname, resid はそのまま使い、座標列だけあなたの予測で更新します\ncoords_cols = [f'{c}_{i}' for i in range(1, 6) for c in ['x', 'y', 'z']]\n\n# 一旦、IDをインデックスにしてマッピングしやすくします\ndf_pred.set_index('ID', inplace=True)\ndf_sample.set_index('ID', inplace=True)\n\n# 座標列を上書き\ndf_sample.update(df_pred[coords_cols])\n\n# 4. インデックスを戻して保存\n# 💡 lineterminator='\\r\\n' を指定してサンプルと同じ CRLF に合わせます\ndf_sample.reset_index().to_csv('submission.csv', index=False, lineterminator='\\r\\n')\n\nprint(\"✅ サンプルの形式に100%同期させました。これでエラーは出ないはずです！\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}