{"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":[{"sourceId":116062,"databundleVersionId":14875579,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13665986,"sourceType":"datasetVersion","datasetId":8688768},{"sourceId":13666014,"sourceType":"datasetVersion","datasetId":8688902},{"sourceId":14468760,"sourceType":"datasetVersion","datasetId":9222784},{"sourceId":14479263,"sourceType":"datasetVersion","datasetId":9248138}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# CAFA評価ツールの取得\n!git clone https://github.com/claradepaolis/CAFA-evaluator-PK.git\n%cd CAFA-evaluator-PK\n!pip install -e .\n%cd ..","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T08:42:38.195655Z","iopub.execute_input":"2026-01-12T08:42:38.196769Z","iopub.status.idle":"2026-01-12T08:42:53.349900Z","shell.execute_reply.started":"2026-01-12T08:42:38.196719Z","shell.execute_reply":"2026-01-12T08:42:53.348322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nimport torch\nimport time\nimport logging\nimport gc\nimport random\nimport copy\nfrom scipy import sparse\nfrom tqdm import tqdm\n\nfrom pathlib import Path\nimport pandas as pd\nimport numpy as np\nimport gc\nimport datetime\n\nfrom torch.utils.data import TensorDataset, DataLoader\nfrom torch.optim.optimizer import Optimizer\n\n# Configure logging\nlogging.basicConfig(\n    level=logging.INFO, \n    format='%(asctime)s - %(levelname)s - %(message)s',\n    force=True  # これを追加\n)\nlogger = logging.getLogger(__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T08:42:53.352336Z","iopub.execute_input":"2026-01-12T08:42:53.352711Z","iopub.status.idle":"2026-01-12T08:43:01.741228Z","shell.execute_reply.started":"2026-01-12T08:42:53.352671Z","shell.execute_reply":"2026-01-12T08:43:01.740038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Sophia optimizer implementation from https://github.com/kyegomez/Sophia.git\nclass SophiaG(Optimizer):\n    \"\"\"\n    SophiaG optimizer class.\n    \"\"\"\n\n    def __init__(self, params, lr=1e-4, betas=(0.965, 0.99), rho=0.04,\n                 weight_decay=1e-1, *, maximize: bool = False,\n                 capturable: bool = False, dynamic: bool = False):\n        \"\"\"\n        Initialize the optimizer.\n        \"\"\"\n        if not 0.0 <= lr:\n            raise ValueError(f\"Invalid learning rate: {lr}\")\n        if not 0.0 <= betas[0] < 1.0:\n            raise ValueError(f\"Invalid beta parameter at index 0: {betas[0]}\")\n        if not 0.0 <= betas[1] < 1.0:\n            raise ValueError(f\"Invalid beta parameter at index 1: {betas[1]}\")\n        if not 0.0 <= rho:\n            raise ValueError(f\"Invalid rho parameter at index 1: {rho}\")\n        if not 0.0 <= weight_decay:\n            raise ValueError(f\"Invalid weight_decay value: {weight_decay}\")\n        defaults = dict(lr=lr, betas=betas, rho=rho,\n                        weight_decay=weight_decay,\n                        maximize=maximize, capturable=capturable, dynamic=dynamic)\n        super(SophiaG, self).__init__(params, defaults)\n\n    def __setstate__(self, state):\n        \"\"\"\n        Set the state of the optimizer.\n        \"\"\"\n        super().__setstate__(state)\n        for group in self.param_groups:\n            group.setdefault('maximize', False)\n            group.setdefault('capturable', False)\n            group.setdefault('dynamic', False)\n        state_values = list(self.state.values())\n        step_is_tensor = (len(state_values) != 0) and torch.is_tensor(state_values[0]['step'])\n        if not step_is_tensor:\n            for s in state_values:\n                s['step'] = torch.tensor(float(s['step']))\n\n    @torch.no_grad()\n    def update_hessian(self):\n        \"\"\"\n        Update the hessian.\n        \"\"\"\n        for group in self.param_groups:\n            beta1, beta2 = group['betas']\n            for p in group['params']:\n                if p.grad is None:\n                    continue\n                state = self.state[p]\n\n                if len(state) == 0:\n                    state['step'] = torch.zeros((1,), dtype=torch.float, device=p.device) \\\n                        if self.defaults['capturable'] else torch.tensor(0.)\n                    state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n                    state['hessian'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n\n                if 'hessian' not in state.keys():\n                    state['hessian'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n\n                state['hessian'].mul_(beta2).addcmul_(p.grad, p.grad, value=1 - beta2)\n\n    @torch.no_grad()\n    def update_exp_avg(self):\n        \"\"\"\n        Update the exponential average.\n        \"\"\"\n        for group in self.param_groups:\n            beta1, beta2 = group['betas']\n            for p in group['params']:\n                if p.grad is None:\n                    continue\n                state = self.state[p]\n                state['exp_avg'].mul_(beta1).add_(p.grad, alpha=1 - beta1)\n\n    @torch.no_grad()\n    def step(self, closure=None, bs=5120):\n        \"\"\"\n        Perform a step of the optimizer.\n        \"\"\"\n        loss = None\n        if closure is not None:\n            with torch.enable_grad():\n                loss = closure()\n\n        self.update_hessian()\n        self.update_exp_avg()\n\n        for group in self.param_groups:\n            params_with_grad = []\n            grads = []\n            exp_avgs = []\n            state_steps = []\n            hessian = []\n            beta1, beta2 = group['betas']\n\n            for p in group['params']:\n                if p.grad is None:\n                    continue\n                params_with_grad.append(p)\n\n                if p.grad.is_sparse:\n                    raise RuntimeError('Hero does not support sparse gradients')\n                grads.append(p.grad)\n                state = self.state[p]\n                # State initialization\n                if len(state) == 0:\n                    state['step'] = torch.zeros((1,), dtype=torch.float, device=p.device) \\\n                        if self.defaults['capturable'] else torch.tensor(0.)\n                    state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n                    state['hessian'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n\n                if 'hessian' not in state.keys():\n                    state['hessian'] = torch.zeros_like(p, memory_format=torch.preserve_format)\n\n                exp_avgs.append(state['exp_avg'])\n                state_steps.append(state['step'])\n                hessian.append(state['hessian'])\n\n                if self.defaults['capturable']:\n                    bs = torch.ones((1,), dtype=torch.float, device=p.device) * bs\n\n            self._sophiag(params_with_grad,\n                          grads,\n                          exp_avgs,\n                          hessian,\n                          state_steps,\n                          bs=bs,\n                          beta1=beta1,\n                          beta2=beta2,\n                          rho=group['rho'],\n                          lr=group['lr'],\n                          weight_decay=group['weight_decay'],\n                          maximize=group['maximize'],\n                          capturable=group['capturable'])\n\n        return loss\n\n    def _sophiag(self, params,\n                 grads,\n                 exp_avgs,\n                 hessian,\n                 state_steps,\n                 capturable,\n                 *,\n                 bs: int,\n                 beta1: float,\n                 beta2: float,\n                 rho: float,\n                 lr: float,\n                 weight_decay: float,\n                 maximize: bool):\n        \"\"\"\n        SophiaG function.\n        \"\"\"\n        if not all(isinstance(t, torch.Tensor) for t in state_steps):\n            raise RuntimeError(\"API has changed, `state_steps` argument must contain a list of singleton tensors\")\n\n        self._single_tensor_sophiag(params,\n                                    grads,\n                                    exp_avgs,\n                                    hessian,\n                                    state_steps,\n                                    bs=bs,\n                                    beta1=beta1,\n                                    beta2=beta2,\n                                    rho=rho,\n                                    lr=lr,\n                                    weight_decay=weight_decay,\n                                    maximize=maximize,\n                                    capturable=capturable)\n\n    def _single_tensor_sophiag(self, params,\n                               grads,\n                               exp_avgs,\n                               hessian,\n                               state_steps,\n                               *,\n                               bs: int,\n                               beta1: float,\n                               beta2: float,\n                               rho: float,\n                               lr: float,\n                               weight_decay: float,\n                               maximize: bool,\n                               capturable: bool):\n        \"\"\"\n        SophiaG function for single tensor.\n        \"\"\"\n        for i, param in enumerate(params):\n            grad = grads[i] if not maximize else -grads[i]\n            exp_avg = exp_avgs[i]\n            hess = hessian[i]\n            step_t = state_steps[i]\n\n            if capturable:\n                assert param.is_cuda and step_t.is_cuda and bs.is_cuda\n\n            if torch.is_complex(param):\n                grad = torch.view_as_real(grad)\n                exp_avg = torch.view_as_real(exp_avg)\n                hess = torch.view_as_real(hess)\n                param = torch.view_as_real(param)\n\n            # update step\n            step_t += 1\n\n            # Perform stepweight decay\n            param.mul_(1 - lr * weight_decay)\n\n            # Decay the first and second moment running average coefficient\n            exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)\n\n            if capturable:\n                step = step_t\n                step_size = lr\n                step_size_neg = step_size.neg()\n\n                ratio = (exp_avg.abs() / (rho * bs * hess + 1e-15)).clamp(None, 1)\n                param.addcmul_(exp_avg.sign(), ratio, value=step_size_neg)\n            else:\n                step = step_t.item()\n                step_size_neg = - lr\n\n                ratio = (exp_avg.abs() / (rho * bs * hess + 1e-15)).clamp(None, 1)\n                param.addcmul_(exp_avg.sign(), ratio, value=step_size_neg)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T08:43:01.742741Z","iopub.execute_input":"2026-01-12T08:43:01.743198Z","iopub.status.idle":"2026-01-12T08:43:01.776860Z","shell.execute_reply.started":"2026-01-12T08:43:01.743170Z","shell.execute_reply":"2026-01-12T08:43:01.775003Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 特徴量取得\ndef get_features(list_features_id):\n\n    logger.info(f'Start get_feature()')\n    X_submit, submit_protein_ids = None, None\n    # t5とesmの特徴量を取得\n    for i0, features_id in enumerate(list_features_id):\n\n        fn_X, fn_protein_ids, fn_X_submit, fn_submit_protein_ids = get_paths_to_features(features_id)\n        fn = fn_X\n\n        if i0 == 0:\n            X = np.load(fn).astype(np.float32)[:config[\"n_samples_to_consider\"], :]\n        else:\n            X = np.concatenate([X, np.load(fn).astype(np.float32)[:config[\"n_samples_to_consider\"], :]], axis=1)\n\n        protein_ids = np.load(fn_protein_ids)[:config[\"n_samples_to_consider\"]]\n        vec_train_protein_ids = protein_ids\n\n        # load feature for submit\n        if config[\"mode_submit\"]:\n\n          fn = fn_X_submit\n          if i0 == 0:\n              X_submit = np.load(fn).astype(np.float32)\n          else:\n              X_submit = np.concatenate([X_submit, np.load(fn).astype(np.float32)], axis=1)\n\n          fn = fn_submit_protein_ids\n          submit_protein_ids = np.load(fn)\n\n    # CVのlossは下がるけど、leakのスコアが下がるため一旦stay    \n    # # taxon dictの読み込み\n    # train_tax_dict = get_tax(TRAIN_FASTA_PATH)\n    # test_tax_dict = get_tax(TEST_FASTA_PATH)\n\n    # # taxonomyのone hot encodingを追加\n    # temp_vec_train_protein_ids = [i.split(\"|\")[1] for i in vec_train_protein_ids]\n    # tax_vect = np.array([train_tax_dict[s] for s in temp_vec_train_protein_ids])\n    # X = np.concatenate([X, tax_vect], axis=1)\n    # if config[\"mode_submit\"]:\n    #   tax_vect = np.array([test_tax_dict[s] for s in submit_protein_ids])\n    #   X_submit = np.concatenate([X_submit, tax_vect], axis=1)\n      \n    logger.info('End get_feature()')\n\n    return X, vec_train_protein_ids, X_submit, submit_protein_ids\n\n# 特徴量データ取得\ndef get_paths_to_features(features_id):\n    if features_id == 'esm2S1280':\n        fn_X = Path(ESM2_DIR / 'train_embeds.npy')\n        fn_protein_ids = Path(ESM2_DIR / 'train_ids.npy')\n        fn_X_submit = Path(ESM2_DIR / 'test_embeds.npy')\n        fn_submit_protein_ids = Path(ESM2_DIR / 'test_ids.npy')\n    elif features_id == 't5':\n        fn_X = Path(T5_DIR / 'train_embeds.npy')\n        fn_protein_ids = Path(T5_DIR / 'train_ids.npy')\n        fn_X_submit = Path(T5_DIR / 'test_embeds.npy')\n        fn_submit_protein_ids = Path(T5_DIR / 'test_ids.npy')\n\n    return fn_X, fn_protein_ids, fn_X_submit, fn_submit_protein_ids\n\n# 学習の事前準備\ndef preprocess_data(vec_train_protein_ids):\n\n    logger.info('Start preprocess_data()')\n\n    # Yの取得\n    fn = Path(FEAT_DIR / 'Y_31466_sparse_float32.npz')\n    Y = sparse.load_npz(fn)\n    logger.info(f'train Y shape {Y.shape} loaded')\n    Y = Y[:config[\"n_samples_to_consider\"], :config[\"n_labels_to_consider\"]].toarray()\n    logger.info(f'train Y shape {Y.shape} truncated')\n\n    fn = Path(FEAT_DIR / 'Y_31466_labels.npy')\n    Y_labels = np.load(fn, allow_pickle=True)[:config[\"n_labels_to_consider\"]]\n    labels_to_consider = Y_labels\n\n    gc.collect()\n\n    # Xの取得\n    dict_set_allowed_train_indexes = {}\n\n    ## 前処理済みデータの読み込み\n    fn = Path(FEAT_DIR / 'train_ids_cut43k.npy')\n    allowed_train_ids = np.load(fn)\n\n    ## 学習対象のEntryIDから真ん中の EntryID だけを抽出したリストを作成\n    vec_train_protein_ids = [s.split('|')[1] if '|' in s else s for s in vec_train_protein_ids]\n\n    ## 前処理データと学習対象の共通部分をvec_train_protein_idsの番号で取得\n    vec_allowed_train_indexes_43k = [ix for ix in range(len(vec_train_protein_ids)) if\n                                      vec_train_protein_ids[ix] in (allowed_train_ids)]\n    set_allowed_train_indexes_43k = set(vec_allowed_train_indexes_43k)\n    dict_set_allowed_train_indexes['43k'] = set_allowed_train_indexes_43k\n\n    logger.info(f'End get_feature()')\n\n    return Y, labels_to_consider, dict_set_allowed_train_indexes\n\n# モデル構成\nclass Model(nn.Module):\n    def __init__(self, input_features, output_features):\n        super().__init__()\n\n        self.activation = nn.PReLU()\n\n        self.bn1 = nn.BatchNorm1d(input_features)\n        self.fc1 = nn.Linear(input_features, 800)\n        self.ln1 = nn.LayerNorm(800, elementwise_affine=True)\n\n        self.bn2 = nn.BatchNorm1d(800)\n        self.fc2 = nn.Linear(800, 600)\n        self.ln2 = nn.LayerNorm(600, elementwise_affine=True)\n\n        self.bn3 = nn.BatchNorm1d(600)\n        self.fc3 = nn.Linear(600, 400)\n        self.ln3 = nn.LayerNorm(400, elementwise_affine=True)\n\n        self.bn4 = nn.BatchNorm1d(1200)\n        self.fc4 = nn.Linear(1200, output_features)\n        self.ln4 = nn.LayerNorm(output_features, elementwise_affine=True)\n\n        self.sigm = nn.Sigmoid()\n\n    def forward(self, inputs):\n\n        fc1_out = self.bn1(inputs)\n        fc1_out = self.ln1(self.fc1(inputs))\n        fc1_out = self.activation(fc1_out)\n\n        x = self.bn2(fc1_out)\n\n        x = self.ln2(self.fc2(x))\n        x = self.activation(x)\n\n        x = self.bn3(x)\n\n        x = self.ln3(self.fc3(x))\n        x = self.activation(x)\n\n        x = torch.cat([x, fc1_out], axis=-1)\n\n        x = self.bn4(x)\n\n        x = self.ln4(self.fc4(x))\n        out = self.sigm(x)\n        return out\n\n# モデルload\ndef get_model(model_config):\n    str_model_id = str(model_config['id'])\n    model = Model(X.shape[1], Y.shape[1])\n    model.to(device)\n    namepostfix = model_config.get('namepostfix', \"\")\n    str_model_id += namepostfix\n    logger.info(f\"Model {str_model_id} created for fold {model_config['namepostfix']}\") # Added log for verification\n    return model, str_model_id\n\n# 推論\ndef model_predict_pytorch(model, XX, model_config, str_model_id=''):\n    t0_submit = time.time()\n    XX = torch.tensor(XX, dtype=torch.float32).to(device)\n\n    model.eval()\n    with torch.no_grad():\n        Y_pred = model(XX).cpu().numpy()\n\n    return Y_pred\n\n# downsample\ndef get_downsampled_IX_train(IX_train, mode_downsample_train):\n    if mode_downsample_train in dict_set_allowed_train_indexes.keys():\n        set_allowed_train_indexes = dict_set_allowed_train_indexes[mode_downsample_train]\n        IX_train = [t for t in IX_train if t in set_allowed_train_indexes]\n    elif 'random_subsample_percent' in str(mode_downsample_train):\n        random_subsample_percent = float(str(mode_downsample_train).split('_')[-1])  # 'random_subsample_percent_90'\n        IX_train = np.random.permutation(IX_train)[:int(len(IX_train) * random_subsample_percent / 100)]\n    return IX_train\n\n# fit\ndef model_fit_pytorch_Sophia_Andrey1(model, X_train, Y_train, X_val, Y_val, model_config, seed, str_model_id=''):\n    # --- DataLoader用のジェネレータ固定 ---\n    g = torch.Generator()\n    g.manual_seed(seed)\n\n    # ---- 重みの取得 ---\n    weight_dict = get_weight()\n    # ia_dict は { 'GO:0005515': 0.15, ... } という辞書を想定\n    ia_weights = [weight_dict.get(go_id, 0.0) for go_id in labels_to_consider]\n\n    # 2. Tensorに変換してGPUへ\n    weight_tensor = torch.tensor(ia_weights, dtype=torch.float32).to(device)\n\n    criterion = nn.BCELoss(weight=weight_tensor) \n    # criterion = nn.BCELoss() \n    max_epoch = model_config.get('epochs', 20)\n    BATCH_SIZE = model_config.get('batch_size', 128)\n    LEARNING_RATE = model_config.get('LR', 0.001)\n\n    optimizer = SophiaG(model.parameters(), lr=LEARNING_RATE, betas=(0.965, 0.99), rho=0.01, weight_decay=1e-1)\n\n    # Tensor化 & Device移動\n    X_train = torch.tensor(X_train, dtype=torch.float32).to(device)\n    Y_train = torch.tensor(Y_train, dtype=torch.float32).to(device)\n    X_val = torch.tensor(X_val, dtype=torch.float32).to(device)\n    Y_val = torch.tensor(Y_val, dtype=torch.float32).to(device)\n\n    train_dataloader = DataLoader(TensorDataset(X_train, Y_train), batch_size=BATCH_SIZE, shuffle=True, generator=g)\n    val_dataloader = DataLoader(TensorDataset(X_val, Y_val), batch_size=BATCH_SIZE, shuffle=False)\n\n    # early stop\n    best_score = 9999\n    stop_counter = 0\n\n    for i, epoch in enumerate(range(max_epoch)):\n        # --- TRAIN LOOP ---\n        model.train()\n        train_loss = 0\n        for x_batch, y_batch in train_dataloader:\n            optimizer.zero_grad()\n            preds = model(x_batch)\n            loss = criterion(preds, y_batch)\n            loss.backward()\n            optimizer.step()\n            train_loss += loss.item()\n        \n        avg_train_loss = train_loss / len(train_dataloader)\n\n        # --- VALIDATION LOOP ---\n        model.eval()\n        val_loss = 0\n        with torch.no_grad():\n            for x_v, y_v in val_dataloader:\n                v_preds = model(x_v)\n                v_loss = criterion(v_preds, y_v)\n                val_loss += v_loss.item()\n        \n        avg_val_loss = val_loss / len(val_dataloader)\n        # エポックごとの結果を表示\n        if config[\"logging_flag\"]:\n            logger.info(f\"Epoch [{epoch+1}/{max_epoch}] | Train Loss: {avg_train_loss:.5f} | Val Loss: {avg_val_loss:.5f}\")\n\n        # --- early stop ---\n        if avg_val_loss < best_score:\n            best_score = copy.deepcopy(avg_val_loss)\n            best_model_state = copy.deepcopy(model.state_dict())\n            stop_counter = 0\n        else:\n            stop_counter += 1\n            if stop_counter >= config[\"model_param\"][\"patience\"]:\n                logger.info(f'early_stopping at epoch {i + 1}, best_epoch : {i + 1 - config[\"model_param\"][\"patience\"]}, best_val_loss : {best_score}')\n                break\n\n    # 一番良かった時の重みをモデルに戻す\n    if best_model_state is not None:\n        model.load_state_dict(best_model_state)\n\n    return model\n        \n\n# \ndef get_model(model_config):\n    str_model_id = str(model_config['id'])\n    model = Model(X.shape[1], Y.shape[1])\n    model.to(device)\n    namepostfix = model_config.get('namepostfix', \"\")\n    str_model_id += namepostfix\n    return model, str_model_id\n\n# seed固定\ndef seed_all(RANDOM_SEED):\n    if RANDOM_SEED is not None:\n        try:\n            SEED = RANDOM_SEED\n            random.seed(SEED)\n            np.random.seed(SEED)\n            torch.manual_seed(SEED)\n            torch.cuda.manual_seed_all(SEED)\n            torch.backends.cudnn.deterministic = True\n            torch.backends.cudnn.benchmark = False\n\n        except Exception as e:\n            print(f\"Exception: {e}\")\n\n# 推論関数\ndef model_predict_pytorch(model, XX, model_config, str_model_id=''):\n    t0_submit = time.time()\n    XX = torch.tensor(XX, dtype=torch.float32).to(device)\n\n    model.eval()\n    with torch.no_grad():\n        Y_pred = model(XX).cpu().numpy()\n\n    return Y_pred\n\n# CAFA評価用の関数\ndef cafa_eval(X_submit, submit_protein_ids, model, model_config, labels_to_consider):\n    # leakデータの読み込み\n    df = pd.read_csv(DATA_PATH, sep=\"\\t\")\n    # リークデータの特徴量に絞り込み\n    # submit_protein_ids を一旦 Index オブジェクトにする\n    mask = pd.Index(submit_protein_ids).isin(df[\"EntryID\"])\n    X_submit_leak = X_submit[mask]\n    submit_protein_ids_leak = submit_protein_ids[mask]\n    # 推論実施\n    Y_pred_leak = model_predict_pytorch(model, X_submit_leak, model_config)\n    # 変換処理\n    df = pd.DataFrame(Y_pred_leak, index=submit_protein_ids_leak, columns=labels_to_consider)\n    df_long = df.reset_index().melt(id_vars='index', var_name='columns', value_name='value')\n    PRED_PATH = Path(OUT_DIR, \"temp_cafa_pred\")\n    PRED_PATH.mkdir(parents=True, exist_ok=True)\n    PRED_FILE = Path(PRED_PATH / f'temp_cafa_pred_{len(labels_to_consider)}.tsv')\n    df_long.to_csv(PRED_FILE, header=False, index=False, sep='\\t')\n    # 正解データの変換\n    df = pd.read_csv(DATA_PATH, sep=\"\\t\").loc[:, [\"EntryID\", \"term\"]]\t\n    TRUTH_PATH = Path(OUT_DIR, \"temp_cafa_gt.tsv\")\n    df.to_csv(TRUTH_PATH, header=False, index=False, sep='\\t')\n    # 評価\n    results = []\n    !python3 -m cafaeval {OBO_PATH} {PRED_PATH} {TRUTH_PATH} -ia {IA_PATH} -out_dir {OUT_DIR}\n    df = pd.read_csv(f\"{OUT_DIR}/evaluation_best_f.tsv\", sep=\"\\t\")\n    for sub_ont in [\"biological_process\", \"cellular_component\", \"molecular_function\"]:\n        print(f'{sub_ont} F1-score : {df[df[\"ns\"] == sub_ont][\"f\"].iloc[0]}')\n        results.append(df[df[\"ns\"] == sub_ont][\"f\"].iloc[0])\n    print(f'average F1-score : {np.mean(results)}')\n\n# taxonomyのone hot encodeの追加\ndef get_tax(fasta_path):\n    # trainとtestのTop30のtaxonomy ID\n    tax_list = [\n        3055, 3702, 4577, 6239, 7227, 7955, 8355, 8364, 9031, 9541,\n        9601, 9606, 9615, 9823, 9913, 9986, 10090, 10116, 36329, 39946,\n        39947, 44689, 83332, 83333, 99287, 208964, 224308, 227321, 237561, 243232,\n        284593, 284811, 284812, 330879, 367110, 559292\n    ]\n\n    # one hot encoding\n    fasta = pd.read_feather(fasta_path)\n    tax = fasta['taxonomyID'].map(\n        {x: n for (n, x) in enumerate(tax_list, 1)}\n    ).fillna(0).astype(np.int32).values[:, np.newaxis]\n    tax = tax == np.arange(len(tax_list) + 1)[np.newaxis, :]\n    tax = tax.astype(np.float32)\n\n    tax_dict = dict(zip(fasta[\"EntryID\"].values, tax))\n\n    return tax_dict\n\n# weightのdict作成\ndef get_weight():\n    # IAの重みを取得\n    df = pd.read_csv(IA_PATH, sep=\"\\t\", header = None)\n    df[1] = df[1] + 0.1\n    weight_dict = dict(zip(df[0], df[1]))\n    return weight_dict\n\n# submission用の成形関数\ndef make_submission(submit_protein_ids, labels_to_consider):\n    data = np.load(\"/kaggle/input/cafa6-nn-original/Y_submit.npy\")\n    df = pd.DataFrame(data, index=submit_protein_ids, columns=labels_to_consider)\n\n    # 設定\n    chunk_size = 10000\n    threshold = 0.1\n    long_df_list = []\n    \n    # df は推論結果の DataFrame (index=EntryID, columns=GO terms)\n    # 10000行ずつループ\n    for i in range(0, len(df), chunk_size):\n        # チャンクの切り出し\n        df_chunk = df.iloc[i : i + chunk_size]\n        \n        # stack() を使って一気に縦持ちに変換\n        s = df_chunk.stack()\n        \n        # 0.1以上の値だけにフィルタリング\n        s = s[s >= threshold]\n        \n        # DataFrameに整形 (index, GO, value)\n        chunk_long = s.reset_index()\n        chunk_long.columns = ['index', 'GO', 'value']\n        \n        long_df_list.append(chunk_long)\n    \n    # 最後にすべてを結合\n    df_final_long = pd.concat(long_df_list, ignore_index=True)\n    \n    return df_final_long","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T08:43:01.778093Z","iopub.execute_input":"2026-01-12T08:43:01.778436Z","iopub.status.idle":"2026-01-12T08:43:01.845317Z","shell.execute_reply.started":"2026-01-12T08:43:01.778401Z","shell.execute_reply":"2026-01-12T08:43:01.844101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 設定\nconfig = {\n    # path\n    \"base_path\" : Path(\"/kaggle/input/cafa6-protein-function-prediction\"),\n    \"embeds_path\" : Path(\"embeds\"),\n    \"helpers_path\" : Path(\"helpers\"),\n    \"models_path\" : Path(\"models\"),\n    \n    # 学習データのサンプリング数\n    \"n_samples_to_consider\" : 100000,\n    # 学習GO数\n    \"n_labels_to_consider\" : 2000,\n    \n    # fold数\n    \"n_folds_to_process\" : 5,\n    \n    # seed\n    \"BASE_SEED\" : 42,\n    \n    # submitを入れるか\n    \"mode_submit\" : True,\n    # OOFを入れるか\n    \"flag_compute_oof_predictions\" : True,\n    # OOFごとに評価値を計算するか\n    \"flag_compute_stat_for_each_model\" : False,\n    # downsampleのキー\n    \"mode_downsample_train_default\" : '43k',\n    # log出力フラグ\n    \"logging_flag\" : False,\n    \n    \n    # 学習用パラメータ\n    \"model_param\" : {\n          'id': 'pMLP', \n          'n_selfblend': 12,\n          'epochs': 5000,\n          'custom_model_fit_function_name': 'model_fit_pytorch_Sophia_Andrey1',\n          'patience' : 10\n  }\n}\n\n# path\nDATA_DIR = config['base_path']\nT5_DIR = Path(\"/kaggle/input/cafa-6-t5-embeddings\")\nESM2_DIR = Path(\"/kaggle/input/cafa-6-esm2-embeddings\")\nFEAT_DIR = Path(DATA_DIR / config['helpers_path'] / 'feats')\nFOLDS_DIR = Path(DATA_DIR / config['helpers_path'])\nOUT_DIR = Path(\"/kaggle/working/\")\nMODEL_DIR = Path(OUT_DIR / config['models_path'] / 'nn_serg')\nDATA_PATH = Path(DATA_DIR / 'temporal/labels/prop_test_leak_no_dup.tsv')\nOBO_PATH = Path(DATA_DIR / \"Train/go-basic.obo\")\nIA_PATH = Path(\"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\")\nTRAIN_FASTA_PATH = Path(DATA_DIR / \"helpers/fasta/train_seq.feather\")\nTEST_FASTA_PATH = Path(DATA_DIR / \"helpers/fasta/test_seq.feather\")\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# 実行\nif __name__ == '__main__':\n    \n    # 特徴量設定\n    ## PLM\n    list_features_id = ['t5', 'esm2S1280']\n    ## taxonomy\n    X, vec_train_protein_ids, X_submit, submit_protein_ids = get_features(list_features_id)\n    \n    # 前処理\n    Y, labels_to_consider, dict_set_allowed_train_indexes = preprocess_data(vec_train_protein_ids)\n\n    submit = make_submission(submit_protein_ids, labels_to_consider)\n    submit.to_csv(\"submission.tsv\", header=False, index=False, sep=\"\\t\")\n    \n    # # 各タンパク質が該当するfold (group k-fold)の取得\n    # fn = Path(FOLDS_DIR / 'folds_gkf.npy')\n    # folds = np.load(fn)[:config[\"n_samples_to_consider\"]]\n    # for k in set(folds):\n    #   m = folds == k\n    \n    # ######################### Output ##################################################\n    # df_stat = pd.DataFrame()\n    \n    # if (config[\"mode_submit\"] is not None) and (config[\"mode_submit\"] != False):\n    #   Y_submit = np.zeros((X_submit.shape[0], Y.shape[1]), dtype=np.float16)\n    \n    # if config[\"flag_compute_oof_predictions\"]:\n    #   Y_pred_oof_blend = np.zeros((Y.shape), dtype=np.float16)\n    \n    # ########################## Preparations ###########################################\n    # if config[\"flag_compute_stat_for_each_model\"]:\n    #   Y_pred_oof = np.zeros((Y.shape), dtype=np.float16)\n    \n    # list_folds_ix = np.sort(list(set(folds)))\n    # logger.info(f'Start training models {datetime.datetime.now()}');\n    # ########################## Main modelling  ###########################################\n    # list_main_config_model_feature_etc = []  #\n    \n    # # Pytorch MLP model\n    # model_config = config[\"model_param\"]\n    # mode_downsample_train = model_config.get('mode_downsample_train', config[\"mode_downsample_train_default\"])\n    \n    # n_selfblend = model_config.get('n_selfblend', 1)\n    \n    # # blend cycle\n    # for i, i_selfblend in enumerate(range(n_selfblend)): \n    #     # seed固定\n    #     seed = config[\"BASE_SEED\"] + i_selfblend\n    #     seed_all(seed)\n        \n    #     logger.info(f'Start training models cycle {i + 1} at seed : {seed}');\n        \n    #     for ix_fold in list_folds_ix[:config[\"n_folds_to_process\"]]:\n    #         model, str_model_id = get_model(model_config)\n            \n    #         # 1. データの分割 (現在のfoldを検証用、それ以外を学習用にする)\n    #         mask_fold = (folds == ix_fold)\n    #         IX_train = np.where(mask_fold == 0)[0]\n    #         IX_val = np.where(mask_fold == 1)[0] \n            \n    #         # # 2. ダウンサンプリングの適用\n    #         # IX_train = get_downsampled_IX_train(IX_train, mode_downsample_train)\n            \n    #         # 3. 実際のデータ抽出\n    #         X_train, Y_train = X[IX_train, :], Y[IX_train, :]\n    #         X_val, Y_val = X[IX_val, :], Y[IX_val, :]\n    #         logger.info(f'train X shape : {X_train.shape}')\n    #         logger.info(f'val X shape : {X_val.shape}')\n            \n            \n    #         str_model_id = f\"{i} {str_model_id} Fold:{ix_fold}\"\n            \n    #         # 4. 修正した学習関数の呼び出し (X_val, Y_valを渡す)\n    #         model = model_fit_pytorch_Sophia_Andrey1(model, X_train, Y_train, X_val, Y_val, model_config, seed, str_model_id)\n            \n    #         # 5. CAFA eval\n    #         cafa_eval(X_submit, submit_protein_ids, model, model_config, labels_to_consider)\n            \n    #         del X_train, Y_train\n    #         torch.cuda.empty_cache()\n    #         gc.collect()\n            \n    #         # 6. modelの保存\n    #         MODEL_DIR.mkdir(parents=True, exist_ok=True)\n    #         print(f\"Created directory: {MODEL_DIR}\")\n    #         torch.save(model.state_dict(), Path(MODEL_DIR / f'model_{i_selfblend}_{ix_fold}.pt'))\n            \n    #         # 7. oofの実行\n    #         if config[\"flag_compute_oof_predictions\"]:\n    #           # 予測の実行\n    #           Y_pred_oof_fold = model_predict_pytorch(model, X_val, model_config, str_model_id)\n                \n    #           # Y_pred_oof_blend に予測値を蓄積 (後で n_selfblend で割る必要がある)\n    #           Y_pred_oof_blend[IX_val, :] += (Y_pred_oof_fold / n_selfblend).astype(np.float16)\n    #           del X_val, Y_pred_oof_fold\n    #           gc.collect()\n\n    #         # 8. testデータの実行\n    #         if config[\"mode_submit\"]:\n    #             chunk_size = 10000  # メモリ状況に応じて調整 (1万〜2万程度が目安)\n    #             n_samples_submit = X_submit.shape[0]\n                \n    #             # チャンクごとにループ\n    #             for i in tqdm(range(0, n_samples_submit, chunk_size)):\n    #                 start = i\n    #                 end = min(i + chunk_size, n_samples_submit)\n                    \n    #                 # 部分的に取り出して推論\n    #                 X_chunk = X_submit[start:end]\n    #                 Y_pred_chunk = model_predict_pytorch(model, X_chunk, model_config, str_model_id)\n                    \n    #                 # Y_submit の該当する位置に加算\n    #                 Y_submit[start:end, :] += (Y_pred_chunk / (n_selfblend * config[\"n_folds_to_process\"])).astype(np.float16)\n                    \n    #                 # メモリ解放\n    #                 del X_chunk, Y_pred_chunk\n    #                 torch.cuda.empty_cache()\n                \n    #             gc.collect()\n\n    # # oof保存\n    # if config[\"flag_compute_oof_predictions\"]:\n    #     fn = Path(OUT_DIR / 'Y_pred_oof_blend.npy')\n    #     np.save(fn, Y_pred_oof_blend)\n    #     logger.info(f'File {fn} saved. Y_oof.shape: {Y_pred_oof_blend.shape}.')\n\n    # # test predict保存\n    # if config[\"mode_submit\"]:\n    #     fn = Path(OUT_DIR / 'Y_submit.npy')\n    #     np.save(fn, Y_submit)\n    #     logger.info(f'File {fn} saved. Y_submit.shape: {Y_submit.shape}.')\n\n    # # make submit\n    # submit = make_submission(submit_protein_ids, labels_to_consider)\n    # submit.to_csv(\"submit.tsv\", header=False, index=False, sep=\"\\t\")\n    # logger.info(f'save submission_file')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T08:52:58.788697Z","iopub.execute_input":"2026-01-12T08:52:58.789572Z","iopub.status.idle":"2026-01-12T08:58:22.213180Z","shell.execute_reply.started":"2026-01-12T08:52:58.789496Z","shell.execute_reply":"2026-01-12T08:58:22.211858Z"}},"outputs":[],"execution_count":null}]}