{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e6a6649b",
   "metadata": {},
   "source": [
    "# Train an NNAlign model to predict both binding core and affinity of MHC class II ligands\n",
    "The notebook consists of the following sections:\n",
    "\n",
    "0. Module imports, define functions, set constants\n",
    "1. Load Data\n",
    "2. Build Model\n",
    "3. Select Hyper-paramerters\n",
    "4. Compile Model\n",
    "5. Train Model\n",
    "6. Evaluation\n",
    "\n",
    "## Exercise\n",
    "\n",
    "The exercise is to modify the conventional Feed-Forward Neural Network (FFNN) so that it uses an NNAlign-like forward algorithm to identify a common binding core within each MHC class II ligand. In our case, the binding core length is fixed to 9 amino acids.\n",
    "\n",
    "Unlike the `DRB1_0101_cores` dataset, where the 9-mer binding cores were already identified and provided, the `DRB1_0101` dataset contains the original full-length MHC class II peptide ligands. These ligands have variable lengths, typically between 12 and 21 amino acids, and the position of the binding core is not given to the model.\n",
    "\n",
    "To handle this, for each peptide we generate all possible continuous 9-mer candidate cores. Each candidate core is encoded and passed through the neural network. The peptide-level prediction is then obtained by selecting the candidate core with the highest prediction score, using a max operation over all cores belonging to the same peptide.\n",
    "\n",
    "In this way, the model learns not only the binding pattern itself, but also which 9-mer segment of each peptide is most likely to act as the binding core.\n",
    "\n",
    "#### Performance evaluation\n",
    "\n",
    "Run the notebook and compare the performance of this NNAlign-like model with the conventional padded FFNN on the `DRB1_0101` dataset. Also compare the results with the model trained on `DRB1_0101_cores` with the conventional padded FFNN, where the binding cores were provided beforehand."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "539a3d32",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import math\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import roc_auc_score, roc_curve"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "000ffcb6",
   "metadata": {},
   "source": [
    "# Utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "60665494",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_blosum(filename):\n",
    "    \"\"\"\n",
    "    Load the BLOSUM matrix used for peptide encoding.\n",
    "    \"\"\"\n",
    "\n",
    "    aa = ['A', 'R', 'N' ,'D', 'C', 'Q', 'E', 'G', 'H', 'I', 'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V', 'X']\n",
    "    df = pd.read_csv(filename, sep='\\s+', comment='#', index_col=0)\n",
    "\n",
    "    blosum_matrix = torch.tensor(df.loc[aa, aa].to_numpy(), dtype=torch.float32)\n",
    "\n",
    "    aa_to_idx = {aa:i for i, aa in enumerate(aa)}\n",
    "\n",
    "    return blosum_matrix, aa_to_idx\n",
    "\n",
    "\n",
    "def load_peptide_target(filename):\n",
    "    \"\"\"\n",
    "    Read amino acid sequence of peptides and corresponding target values from text file.\n",
    "    \"\"\"\n",
    "    df = pd.read_csv(filename, sep='\\s+', usecols=[0,1], names=['peptide','target'])\n",
    "    return df.sort_values(by='target', ascending=False).reset_index(drop=True)\n",
    "\n",
    "\n",
    "def encode_peptides(X_in, blosum_file, core_len=9):\n",
    "    \"\"\"\n",
    "    Encode all possible fixed-length candidate cores from each peptide using BLOSUM.\n",
    "\n",
    "    Returns the encoded cores, one target per peptide, and an index mapping each core\n",
    "    to its original peptide.\n",
    "    \"\"\"\n",
    "    \n",
    "\n",
    "    blosum50, aa_to_idx = load_blosum(blosum_file)\n",
    "\n",
    "    X_idx = []\n",
    "    pep_idx = []\n",
    "    for i, peptide in enumerate(X_in[\"peptide\"]):\n",
    "        for j in range(len(peptide)-core_len+1):\n",
    "            core = peptide[j:j+core_len]\n",
    "            X_idx.append([aa_to_idx.get(aa, -1) for aa in core])\n",
    "            pep_idx.append(i)\n",
    "\n",
    "    X_idx = torch.tensor(X_idx, dtype=torch.long)\n",
    "\n",
    "    X_out = blosum50[X_idx]\n",
    "\n",
    "    return X_out, torch.tensor(X_in[\"target\"].to_numpy(), dtype=torch.float32).reshape(-1, 1), torch.tensor(pep_idx, dtype=torch.long).reshape(-1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "dd4d9543",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_losses(train_losses, valid_losses, n_epochs):\n",
    "    # Plotting the losses \n",
    "    fig,ax = plt.subplots(1,1, figsize=(9,5))\n",
    "    ax.plot(range(n_epochs), train_losses, label='Train loss', c='b')\n",
    "    ax.plot(range(n_epochs), valid_losses, label='Valid loss', c='m')\n",
    "    ax.legend()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "aa3347ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "def xavier_initialization_normal(layer_weights):\n",
    "    return nn.init.xavier_normal_(layer_weights)\n",
    "\n",
    "def random_initialization_normal(layer_weights):\n",
    "    return nn.init.normal_(layer_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "47d6ec44",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_network(net, x_train, y_train, pep_idx_train, optimizer):\n",
    "    \"\"\"\n",
    "    Trains the network for a single epoch, running the forward and backward pass, and compute and return the loss.\n",
    "    \"\"\"\n",
    "\n",
    "    net.train()\n",
    "\n",
    "    loss_fn = nn.MSELoss()\n",
    "    \n",
    "    # Forward pass\n",
    "    a2  = net(x_train, pep_idx_train)\n",
    "\n",
    "    # backward pass\n",
    "    loss = loss_fn(a2, y_train)\n",
    "    optimizer.zero_grad()\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "\n",
    "    return loss.item()\n",
    "\n",
    "\n",
    "def eval_network(net, x_valid, y_valid, pep_idx_valid):\n",
    "    \"\"\"\n",
    "    Evaluates the network ; Note that we do not update weights (no backward pass)\n",
    "    \"\"\"\n",
    "\n",
    "    net.eval()\n",
    "\n",
    "    loss_fn = nn.MSELoss()\n",
    "\n",
    "    with torch.no_grad():\n",
    "        a2 = net(x_valid, pep_idx_valid)\n",
    "        loss = loss_fn(a2, y_valid)\n",
    "\n",
    "    return loss.item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f69a7b53",
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_ffnn_model(filepath, model):\n",
    "    \"\"\"\n",
    "    Save PyTorch FFNN model weights and architecture metadata.\n",
    "    \"\"\"\n",
    "\n",
    "    if not filepath.endswith(\".pt\"):\n",
    "        filepath = filepath + \".pt\"\n",
    "\n",
    "    dict_to_save = {\n",
    "        \"input_size\": model.in_layer.in_features,\n",
    "        \"hidden_size\": model.in_layer.out_features,\n",
    "        \"output_size\": model.out_layer.out_features,\n",
    "        \"state_dict\": model.state_dict()\n",
    "    }\n",
    "\n",
    "    torch.save(dict_to_save, filepath)\n",
    "\n",
    "    print(f\"Saved FFNN model at {filepath}\")\n",
    "\n",
    "\n",
    "def load_ffnn_model(filepath, model=None):\n",
    "    \"\"\"\n",
    "    Load PyTorch FFNN model weights and architecture metadata.\n",
    "    \"\"\"\n",
    "\n",
    "    loaded_dict = torch.load(filepath, map_location=\"cpu\")\n",
    "\n",
    "    if model is None:\n",
    "        model = SimpleFFNN(\n",
    "            input_size=loaded_dict[\"input_size\"],\n",
    "            hidden_size=loaded_dict[\"hidden_size\"],\n",
    "            output_size=loaded_dict[\"output_size\"]\n",
    "        )\n",
    "\n",
    "    assert model.in_layer.in_features == loaded_dict[\"input_size\"], \\\n",
    "        f\"Input size mismatch. Model: {model.in_layer.in_features}, loaded: {loaded_dict['input_size']}\"\n",
    "\n",
    "    assert model.in_layer.out_features == loaded_dict[\"hidden_size\"], \\\n",
    "        f\"Hidden size mismatch. Model: {model.in_layer.out_features}, loaded: {loaded_dict['hidden_size']}\"\n",
    "\n",
    "    assert model.out_layer.out_features == loaded_dict[\"output_size\"], \\\n",
    "        f\"Output size mismatch. Model: {model.out_layer.out_features}, loaded: {loaded_dict['output_size']}\"\n",
    "\n",
    "    model.load_state_dict(loaded_dict[\"state_dict\"])\n",
    "\n",
    "    print(\n",
    "        f\"Model loaded successfully from {filepath}\\n\"\n",
    "        f\"with architecture: \"\n",
    "        f\"input_size={loaded_dict['input_size']}, \"\n",
    "        f\"hidden_size={loaded_dict['hidden_size']}, \"\n",
    "        f\"output_size={loaded_dict['output_size']}\"\n",
    "    )\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97275e6b",
   "metadata": {},
   "source": [
    "# Model creation and training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d2555122",
   "metadata": {},
   "outputs": [],
   "source": [
    "class SimpleFFNN(nn.Module):\n",
    "\n",
    "\n",
    "    def __init__(self, input_size, hidden_size, output_size, initialization_function=xavier_initialization_normal):\n",
    "\n",
    "        super().__init__()\n",
    "\n",
    "        self.in_layer = nn.Linear(input_size, hidden_size)\n",
    "        self.out_layer = nn.Linear(hidden_size, output_size)\n",
    "        self.hidden_act = nn.ReLU()\n",
    "        self.out_act = nn.Sigmoid()\n",
    "\n",
    "        initialization_function(self.in_layer.weight)\n",
    "        initialization_function(self.out_layer.weight)\n",
    "\n",
    "        print(f'Input -> Hidden Layer Weight Matrix Shape: {self.in_layer.weight.shape}',\n",
    "              f'First Layer Bias Weights Vector Shape: {self.in_layer.bias.shape}',\n",
    "              f'Hidden -> Output layer Weight Matrix Shape: {self.out_layer.weight.shape}',\n",
    "              f'Second Layer Bias Weights Vector Shape: {self.out_layer.bias.shape}', sep=\"\\n\")\n",
    "\n",
    "        \n",
    "    def forward(self, x, pep_idx):\n",
    "\n",
    "        z1 = self.in_layer(x)\n",
    "        a1 = self.hidden_act(z1)\n",
    "        z2 = self.out_layer(a1)\n",
    "        a2 = self.out_act(z2)\n",
    "\n",
    "        # Number of peptides in the batch\n",
    "        n_peptides = int(pep_idx.max().item()) + 1\n",
    "\n",
    "        a2_best = torch.full(\n",
    "        size=(n_peptides, a2.shape[1]),\n",
    "        fill_value=-float(\"inf\"),\n",
    "        dtype=a2.dtype\n",
    "        )\n",
    "\n",
    "        # For each core prediction in a2, assign it to its peptide group in pep_idx\n",
    "        # If several cores belong to the same peptide, keep the maximum score\n",
    "        a2_best.scatter_reduce_(\n",
    "            dim=0,\n",
    "            index=pep_idx,\n",
    "            src=a2,\n",
    "            reduce=\"amax\",\n",
    "            include_self=True\n",
    "        )\n",
    "        \n",
    "        return a2_best"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2446b13",
   "metadata": {},
   "source": [
    "# Data loading and encoding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9e4d5c6b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Preview of the dataframe ; Peptides have to be *encoded* to BLOSUM matrices\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>peptide</th>\n",
       "      <th>target</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>MVKVLDAVRGSPA</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>KRFLYKKLPSVEGLHA</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>AERLFKAMKGLGTRDN</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>AEIEYAMAYSKAAFER</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>APPVRHLIATQLLS</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            peptide  target\n",
       "0     MVKVLDAVRGSPA     1.0\n",
       "1  KRFLYKKLPSVEGLHA     1.0\n",
       "2  AERLFKAMKGLGTRDN     1.0\n",
       "3  AEIEYAMAYSKAAFER     1.0\n",
       "4    APPVRHLIATQLLS     1.0"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "N_datapoints:\n",
      "Train data:\t 4000\n",
      "Valid data:\t 1500\n",
      "Test data:\t 1500\n",
      "Maximum peptide length of each data set:\n",
      "Train:\t 21\n",
      "Valid:\t 21\n",
      "Test:\t 21\n"
     ]
    }
   ],
   "source": [
    "torch.manual_seed(42)\n",
    "\n",
    "# Replace your data paths with the actual paths and desired alleles\n",
    "ALLELE = 'DRB1_0101' #'DRB1_0101_cores', 'DRB1_0101'  \n",
    "DATAPATH = \"/Users/pablovf_22/Documents/TA/algorithms_in_bioinformatics_2026/data/\"\n",
    "blosum_file = f'{DATAPATH}NNDeep/BLOSUM50'\n",
    "train_data = f'{DATAPATH}NNDeep/{ALLELE}/train_EL'\n",
    "valid_data = f'{DATAPATH}NNDeep/{ALLELE}/valid_EL'\n",
    "test_data = f'{DATAPATH}NNDeep/{ALLELE}/test_EL'\n",
    "\n",
    "# Loading the peptides.\n",
    "train_raw = load_peptide_target(train_data)\n",
    "valid_raw = load_peptide_target(valid_data)\n",
    "test_raw = load_peptide_target(test_data)\n",
    "\n",
    "print('Preview of the dataframe ; Peptides have to be *encoded* to BLOSUM matrices')\n",
    "display(train_raw.head())\n",
    "\n",
    "print('N_datapoints:')\n",
    "print('Train data:\\t', train_raw.shape[0])\n",
    "print('Valid data:\\t', valid_raw.shape[0])\n",
    "print('Test data:\\t', test_raw.shape[0])\n",
    "\n",
    "print('Maximum peptide length of each data set:')\n",
    "print('Train:\\t',  train_raw['peptide'].apply(len).max())\n",
    "print('Valid:\\t', valid_raw['peptide'].apply(len).max())\n",
    "print('Test:\\t', test_raw['peptide'].apply(len).max())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a317b7d3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Peptide length counts in the train data\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>len</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>348</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>358</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>363</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>430</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>406</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>424</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>418</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>431</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>404</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>418</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     count\n",
       "len       \n",
       "12     348\n",
       "13     358\n",
       "14     363\n",
       "15     430\n",
       "16     406\n",
       "17     424\n",
       "18     418\n",
       "19     431\n",
       "20     404\n",
       "21     418"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "train_raw['len']=train_raw['peptide'].apply(len)\n",
    "print('Peptide length counts in the train data')\n",
    "display(train_raw.groupby('len').agg(count=('peptide','count')))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "88ad07c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([34637, 9, 21])\n"
     ]
    }
   ],
   "source": [
    "core_len = 9\n",
    "x_train_, y_train_, pep_idx_train_ = encode_peptides(train_raw, blosum_file, core_len)\n",
    "x_valid_, y_valid_, pep_idx_valid_ = encode_peptides(valid_raw, blosum_file, core_len)\n",
    "x_test_, y_test_, pep_idx_test_ = encode_peptides(test_raw, blosum_file, core_len)\n",
    "# We now have matrices of shape (N_cores, core_length, n_features)\n",
    "print(x_train_.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ec655a7",
   "metadata": {},
   "source": [
    "## Now create a model and run it.\n",
    "\n",
    "Play around with the hyperparameters (number of epochs, learning rate, hidden size) and see what the changes do!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9c48d7b5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input -> Hidden Layer Weight Matrix Shape: torch.Size([50, 189])\n",
      "First Layer Bias Weights Vector Shape: torch.Size([50])\n",
      "Hidden -> Output layer Weight Matrix Shape: torch.Size([1, 50])\n",
      "Second Layer Bias Weights Vector Shape: torch.Size([1])\n"
     ]
    }
   ],
   "source": [
    "# Reshaping the matrices so they're flat because feed-forward networks are \"one-dimensional\"\n",
    "x_train_ = x_train_.reshape(x_train_.shape[0], -1)\n",
    "x_valid_ = x_valid_.reshape(x_valid_.shape[0], -1)\n",
    "x_test_ = x_test_.reshape(x_test_.shape[0], -1)\n",
    "# Define sizes\n",
    "input_size = x_train_.shape[1] # also known as \"n_features\"\n",
    "# Model and training hyperparameters\n",
    "learning_rate = 0.05\n",
    "hidden_units = 50\n",
    "n_epochs = 2000\n",
    "output_size = 1\n",
    "\n",
    "# Creating a model instance \n",
    "# You can use either `xavier_initialization_normal` or `random_initialization_normal`\n",
    "# for the initialization_function argument of the class\n",
    "network = SimpleFFNN(input_size, hidden_units, output_size)\n",
    "optimizer = torch.optim.SGD(network.parameters(), lr=learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "ac8db606",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0: Train Loss: 0.7595\tValid Loss: 0.5838\n",
      "Epoch 100: Train Loss: 0.0833\tValid Loss: 0.0913\n",
      "Epoch 200: Train Loss: 0.0776\tValid Loss: 0.0885\n",
      "Epoch 300: Train Loss: 0.0724\tValid Loss: 0.0859\n",
      "Epoch 400: Train Loss: 0.0680\tValid Loss: 0.0842\n",
      "Epoch 500: Train Loss: 0.0642\tValid Loss: 0.0830\n",
      "Epoch 600: Train Loss: 0.0606\tValid Loss: 0.0824\n",
      "Epoch 700: Train Loss: 0.0573\tValid Loss: 0.0817\n",
      "Epoch 800: Train Loss: 0.0544\tValid Loss: 0.0811\n",
      "Epoch 900: Train Loss: 0.0518\tValid Loss: 0.0803\n",
      "Epoch 1000: Train Loss: 0.0493\tValid Loss: 0.0796\n",
      "Epoch 1100: Train Loss: 0.0470\tValid Loss: 0.0789\n",
      "Epoch 1200: Train Loss: 0.0447\tValid Loss: 0.0786\n",
      "Epoch 1300: Train Loss: 0.0426\tValid Loss: 0.0786\n",
      "Epoch 1400: Train Loss: 0.0406\tValid Loss: 0.0787\n",
      "Epoch 1500: Train Loss: 0.0388\tValid Loss: 0.0788\n",
      "Epoch 1600: Train Loss: 0.0371\tValid Loss: 0.0790\n",
      "Epoch 1700: Train Loss: 0.0354\tValid Loss: 0.0793\n",
      "Epoch 1800: Train Loss: 0.0339\tValid Loss: 0.0797\n",
      "Epoch 1900: Train Loss: 0.0324\tValid Loss: 0.0800\n",
      "Saved FFNN model at /Users/pablovf_22/Documents/TA/algorithms_in_bioinformatics_2026/data//NNDeep/some_ffnn_model.pt\n"
     ]
    },
    {
     "data": {
      "image/png": "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7R4I7QAAALCN0O6B0A4AAADbCO0eCO0AAACwjdDugdAOAAAA2wjtHgjtAAAAsI3Q7oHQDgAA0LhOOeUUueGGG0LzXbp0kalTp3p+y+mLL77o+zGTDaHdA6EdAADAn7PPPltOP/30qLe9++67Jlh/8skncW/ODz/8UK688sqU/jEQ2j0Q2gEAAPy5/PLL5Y033pD169fXuu3JJ5+UQYMGSb9+/eLenIceeqg0b948pX8MhHYPhHYAAAB//vM//9ME7Keeeipi+bZt2+T55583of7777+XkSNHSqdOnUwQP/roo+Vvf/tbnY/bpUZ5zL///W856aSTJCcnR/r27WsOFOL1448/SnFxsbRp08a8jjPOOMM8rmvNmjXmkwO9vUWLFnLkkUfKnDlzQve95JJLzP+1WbNmcsQRR5iDkoaU0aCP3gQQ2gEAQCJwHEeCO4JWnjuteZopbfGSkZFhgrCG9ttvvz10Hw3s1dXVJqxrgB84cKDceuutkpubK6+++qpceuml0r17dxkyZIjncwSDQTn//PMlPz9fPvjgAykvLz+gWvXLLrvMhPSXX37ZvA59PWeeeaZ8/vnnkpmZKWPHjpWqqip55513TGjX5S1btjT3veOOO8z8a6+9Ju3atZNVq1bJzp07pSER2j0Q2gEAQCLQwP5uy3etPPeJ206U9Bbpvtb95S9/KZMnT5a3337bnPypdBT6ggsukLy8PDPddNNNofWvvfZaef311+W5557zFdrffPNN+fLLL819OnbsaJbdd999ZqTcLzesv/fee3L88cebZX/961+lsLDQnMx64YUXytq1a81r1k8CVLdu3UL319uOOeYYU+7jfhLQ0CiP8UBoBwAA8K93794mCM+cOdPM6yi0noSqpTFKR9zvueceE4bbtm1rRq81gGsQ9uOLL74w4doN7GrYsGFx/Yj0MfRTgaFDh4aWHXLIIdKrVy9zm7ruuuvkd7/7nZxwwgkyceLEiBNor776ann22WdlwIABcsstt8iiRYukoTHS7oHQDgAAEoGWqOiIt63njocGdB1BnzZtmhll19KXk08+2dymo/C///3vTY26BnctPdHyFi1FSSRXXHGFFBUVmfKdf/zjHzJp0iR56KGHzP9LR/W15l1r3LWe/tRTTzXlNA8++GCDvR5G2j0Q2gEAQCLQ+nAtUbEx+alnD3fRRRdJWlqa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bt6lHxkcdthhjLgDAACgyXCrSfxUq8cV2tu1ayfp6elSWloasVznCwoKot5Hl8ezvsrOzjZTzf8QI+4AAABoarZu3WqqS+ottGdlZcnAgQNl3rx5pgOMeyKqzo8bNy7qfYYNG2Zuv+GGG0LL3njjDbPcr44dO8q6deukVatWEggEpDHpAYMeLOjzcxIs2499Lznwe8v2Y99LPvzesv1Scd9zHMcEds269V4eo2Uro0aNkkGDBsmQIUNk6tSppjvM6NGjze3FxcXSqVMnU5eurr/+ejn55JPloYcekrPOOkueffZZ+eijj+Sxxx7z/Zx6smvnzp3FJv0hEtrZfux7yYXfW7Yf+17y4feW7Zdq+16exwj7AYd2beG4adMmmTBhgjmZVFs3zp07N3Sy6dq1a03Idh1//PHyzDPPyG9/+1v5zW9+I0cccYS8+OKLctRRR8X71AAAAEBKOqATUbUUJlY5zIIFC2otu/DCC80EAAAAoBG+ETXV6Amx+u2v4SfGgu3HvpfY+L1l+7HvJR9+b9l+7Hv1/I2oAAAAABoXI+0AAABAgiO0AwAAAAmO0A4AAAAkOEI7AAAAkOAI7QAAAECCI7R7mDZtmnTp0kVycnJk6NChsmTJEkl1+m23gwcPllatWkn79u3l3HPPlZUrV0asc8opp0ggEIiYrrrqqoh19Iu49Ftymzdvbh7n5ptvlj179khTduedd9baLr179w7dvmvXLhk7dqwccsgh0rJlS7nggguktLRUUn27Kf09rLntdNLtpdjnIr3zzjty9tlnm6/G1u2kX2oXThuH6ZfkdejQQZo1aybDhw+Xf//73xHr/PDDD3LJJZeYbwhs3bq1XH755bJt27aIdT755BM58cQTzXukfg34Aw88IE152+3evVtuvfVWOfroo6VFixZmHf0m8O+++85zf73//vtTetupyy67rNZ2Of300yPWSdX9zs/2i/YeqNPkyZNTet+b5COX1NffV/0+omOPPda0KO3Ro4c89dRT0mi05SOie/bZZ52srCxn5syZzooVK5wxY8Y4rVu3dkpLS1N6kxUVFTlPPvmk89lnnznLly93zjzzTOewww5ztm3bFlrn5JNPNttrw4YNoam8vDx0+549e5yjjjrKGT58uPPPf/7TmTNnjtOuXTtn/PjxTlM2ceJE58gjj4zYLps2bQrdftVVVzmF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",
      "text/plain": [
       "<Figure size 900x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Training loops\n",
    "train_losses = []\n",
    "valid_losses = []\n",
    "\n",
    "# Run n_epochs of training\n",
    "for epoch in range(n_epochs):\n",
    "    train_loss = train_network(network, x_train_, y_train_,pep_idx_train_, optimizer)\n",
    "    valid_loss = eval_network(network, x_valid_, y_valid_, pep_idx_valid_)\n",
    "    train_losses.append(train_loss)\n",
    "    valid_losses.append(valid_loss)\n",
    "    # For the first, every 5% of the epochs and last epoch, we print the loss \n",
    "    # to check that the model is properly training. (loss going down)\n",
    "    if (n_epochs >= 10 and epoch % math.ceil(0.05 * n_epochs) == 0) or epoch == 0 or epoch == n_epochs:\n",
    "        print(f\"Epoch {epoch}: Train Loss: {train_loss:.4f}\\tValid Loss: {valid_loss:.4f}\")\n",
    "\n",
    "# save model + plot losses (put your own savename to be used for the model and predictions)\n",
    "model_savepath = f'{DATAPATH}/NNDeep/some_ffnn_model.pt' # /path/to/your/stuff/filename.pt\n",
    "save_ffnn_model(model_savepath, model=network)\n",
    "\n",
    "# plotting the losses \n",
    "plot_losses(train_losses, valid_losses, n_epochs)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d1cad36",
   "metadata": {},
   "source": [
    "# Evaluation on test data\n",
    "\n",
    "In this part, you will reload a trained model and use it for evaluation.\n",
    "\n",
    "Have a look at how the predictions scores are obtained. The predictions scores are the output of the network after a sigmoid function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "63d213fa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model loaded successfully from /Users/pablovf_22/Documents/TA/algorithms_in_bioinformatics_2026/data//NNDeep/some_ffnn_model.pt\n",
      "with architecture: input_size=189, hidden_size=50, output_size=1\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x1061c7280>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAu0AAALgCAYAAAAgIEm5AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAe9lJREFUeJzt3Qd4VGX2x/FfEhJq6L33IlVQEStrQ7Hg7orYELuCuisIC4iKuyp2xYIF667YsIErCGJHBfFPkU4o0luQkgKkkPt/zp0dSEISUia5U76f5xlvMi3vvDfEM2fOe94ox3EcAQAAAAha0V4PAAAAAEDBCNoBAACAIEfQDgAAAAQ5gnYAAAAgyBG0AwAAAEGOoB0AAAAIcgTtAAAAQJArpxCQlZWlrVu3Kj4+XlFRUV4PBwAAACgx2y4pOTlZDRs2VHR0dOgH7RawN2nSxOthAAAAAAG3adMmNW7cOPSDdsuw+19Q1apVvR4OAAAAUGJJSUluYtof64Z80O4vibGAnaAdAAAA4aQw5d8sRAUAAACCHEE7AAAAEOQI2gEAAIAgFxI17YVtC5menu71MACEgNjYWMXExHg9DAAAIitot2D9999/dwN3ACiM6tWrq379+uz9AAAICeXCoSn9tm3b3KyZtcw5VmN6AJHN/mbs379fO3fudL9v0KCB10MCACD8g/bMzEz3f8C2k1SlSpW8Hg6AEFCxYkX3aIF73bp1KZUBAAS9kE9LHzp0yD3GxcV5PRQAIcT/Jj8jI8ProQAAEP5Be1Ga0gMAfzMAAKEobIJ2AAAAIFwRtEco+2RiypQpCnfNmzfX+PHjA/q6I2XuAABA8CBo98h1113nBn92sZ7RLVq00D/+8Q8dPHjQqyFFBOs0dMEFFxTqvg888IC6detWoucoDe+99567cPL2228/6ra33nrLbWVY2DcbH3/8sXr37q1q1aqpSpUq6tKli/71r39p9+7dxe7Mcv/997sdWWyx5znnnKPVq1cfc579/xb8l/bt2+e4j/27sNdbq1Ytd5x//etftWPHjhz32bhxoy688EK3Vt0Wl44YMcJdqA4AQDggaPfQ+eef7waA69at0zPPPKNXXnlFY8eO9XJIQSmQm2ZZX+7y5ct7/hwl8frrr7tv8Cx4L8mbvDFjxmjAgAE68cQT9cUXX2jp0qV66qmn9Ntvv+ntt98u1nM+/vjjeu655/Tyyy/rl19+UeXKldWnT59jjrNjx47uvwX/5ccff8xx+9ChQ/Xf//5XH374ob7//ntt3bpVf/nLX3IsSLeA3X5Xfv75Z/373/9238DYGwgAAMKCEwL27dvn2FDtmNuBAwec5cuXu8dQMmjQIKdfv345rvvLX/7iHH/88Ye/37Vrl3PFFVc4DRs2dCpWrOh06tTJeffdd3M85swzz3TuvPNOZ8SIEU6NGjWcevXqOWPHjs1xn4SEBOf00093ypcv73To0MH58ssv3fn89NNPD99n8eLFzp/+9CenQoUKTs2aNZ2bb77ZSU5OPmq8Dz/8sFO3bl2nWrVqzj//+U8nIyPDGT58uPuzGzVq5LzxxhsFvm4b7+233+5eqlat6tSqVcu59957naysrMP3adasmfOvf/3LGThwoBMfH+/+bDN79mzntNNOc8fYuHFj93WnpKQcftyOHTuciy66yL29efPmzqRJk9zneuaZZw7fJ/fr3rRpkzvHNv5KlSo5PXr0cObOneu8+eab7n2zX+y6vJ6jsHP3xBNPOPXr13fvM2TIECc9Pd0pqnXr1rm/C3v37nV69uzpvPPOOzlutzHauclL9nH/8ssv7vfjx4/P87579uwp8tjsHNrrs9fpZ+O037v33nsv38fZ72vXrl3zvd2eIzY21vnwww8PX7dixQp3/HPmzHG/nz59uhMdHe1s37798H1eeukl93csLS0tz+cN1b8dAIDwUVCMmxuZ9iBhWU7LEGZvXWnZyR49emjatGnu7bfccosGDhyoefPm5XisZRUto2mZTct0WnnDrFmz3Ntsl1jLSNrz2u2WAR05cmSOx6emprrZ0Bo1aujXX391s5lfffWV7rjjjhz3++abb9wM5w8//KCnn37a/VTgoosuch9nz33bbbfp1ltv1ebNmwt8rTbecuXKua/j2WefdZ/rtddey3GfJ598Ul27dtXChQt13333ae3ate4nE1YWsXjxYn3wwQduNjb7GK3kaNOmTfr222/10Ucf6cUXXzy8gU5eUlJSdOaZZ2rLli367LPP3AyzZbBtziwDfffdd+fIANt1uRV27mxM9hrs6M8C2yV7iYjV3x/Lm2++6WaUrZzlmmuucbPuxfHOO++4ZSZDhgzJ83Z/ic3s2bPd+xV0secytivx9u3b3ZIYPxtnz549NWfOnALHYyU0ttdCy5YtdfXVV7ulLn7z58932zJmf14rn2natOnh57Vj586dVa9evcP3sfOSlJSkZcuWFWuOAAAIKk4ICNdMe0xMjFO5cmU3E2mvzzKFH330UYGPu/DCC5277747R+bass/ZnXjiic7IkSPdr2fOnOmUK1fO2bJly+Hbv/jiixxZ14kTJ7qZ5uxZ62nTpuXIXNp4LWt96NChw/dp166dm8H3y8zMdF9PQVlVG69l+7Nn1m2sdp2f/ZxLL700x+NuvPFG55ZbbslxnWXebYx27letWuW+pnnz5h2Vjc0v0/7KK6+4mfw//vijSBng4s6dzY9f//79nQEDBhz+/vnnn3fOOusspyA2902aNHGmTJnifp+YmOjExcW52feiZtovuOACp0uXLs6x7N+/31m9enWBl6SkJPe+P/30k/sztm7dmuM57LVefvnl+f4My5JPnjzZ+e2335wZM2Y4vXr1cpo2bXr4ee3TBHududnv+T/+8Q/3a/t047zzzstxe2pqqjsee/68hOrfDgBA+CDT/r/FepbZtMxzWlqaHnroIfdNih0tW2wZSsuMzpw5U5988omWL1/u1uJaZi77fW1B3oQJE9zsrmVjLeu9YMECt/48MTHx8H3teYrqT3/6kxYtWuRmqQcNGqTrr7/ezSRnr9N98MEH3QxizZo13aym/ZzsWUhjiwezs0WA/gzzihUr1KRJEzeL6derV68c97f7WFbbsvV+p556qptxXrVq1eHrLOscHX3kwxnLatrY/GxxpC0ULCi7bU4++eQcffVtPJZp9W+UZU444YQcj7EsuGWms2d4LZNqY7QMr70Gy97bJxPZs7H5Lco0NvfHH3+8O7fFVZS5s/nJ6xwZy8x//fXXBf4s+/TEMvt9+/Z1v69du7bOPfdcvfHGG0Uety+GPzZbTNq6desCL/Hx8SoJW9Tbv39/9/fYzun06dO1d+9eTZ48uUTPCwBAOCmnMHXllVe6Ryt3MPfee2+O44033njUY4477rg87+vv0pE9OO7evXuO+1iwUVQW6FnQYyzwsuDP3kz4x/bEE0+45SPWstCCY7v/XXfdddTCTOs+k50FxBY0BlpeP6e0fnb2INhfymKlN3/729+Ouq+VSSQkJBR7K/uyEIh5st8NexOZfdz2HPaG8p///Kf7hqpq1apuYG/XZ3+DZUGwv1zFtG3b1i0vsrKT3GPLzspjjtUpx97AWkmLLdA11tXF3pT42fd5deHJj73RsvGtWbPG/d6e137n7TVkfxNmz+v/mXbMXTbm7y7jvw8AAKGMmvYgYQHWPffc474JOHDggHvdTz/9pH79+rm1yxbQW71vUYPTDh06uHXeVpPtN3fu3KPuY5lsC/b87GfbmNq1a6dAs08WsrPxtGnTJkcmOjd7k2SfhuSV6bV6fcuqW3s/q3/2s0y3P1jNi70Js2x7fu0N7XmzZ//zUlZz98cff2jq1Kl6//333TH7L1bzv2fPHn355Zfu/exn2jzYbdnZp0PGgmFz1VVXuW+ErO4/L/55s088sv+8vC6XXHKJe19rW2oBcvZPDOyTKzvfuT/dKYiNyz4l8wf+9umJvbHI/rx2bu0TJ//z2nHJkiU5Pr2wTybsTYz/zTgAAKGMoD2IWImABa5WjmMskLXAwxaoWhmGZZpz96Y+Flu8Z4Gald9YcGmZU2v1l51lSStUqODexxa82mLJO++80130mn1hX6BYsDVs2DA38LIypueff15///vfC3yMLZ61ebAyEgsUrZzGglj/gk8LVm2hqs2RBYkWvN90000FZtPt0xgLMi+99FI30LbWm9a33L+40RaGWumN/bxdu3a5ZVa5BWruXnjhBZ199tn53m4tGK306PLLL1enTp0OX+zNnJXL+BekWhnOeeedpxtuuMENcm38M2bMcBec2kLaRo0aufezxaH2KZQttrWjveYNGza4j7HfQ1ssW9TyGPv0wD4JspIxKyWzIPraa691S7Nsjv3sddrr9Rs+fLjbxnH9+vXuOf7zn//s/jvwf1pmnw7Yp0/2O2Pza+fWSsksULdSK2Ov2YJzm3f7PbcyMnsDbJ+SedmeEwCAQCFoDyJWk21BqNXhW+bWgg7LMFvpjW2A4w8wi8Iyvp9++qmbvT/ppJPcQPbhhx/OcR/bjMaCHMs4W8/uyy677KjAKpAskPOPx4IqC9itM05BLCtugZ190nD66ae7tejWgzt7rb51VrHvrSOMdcyx57RNdvJjmXTLUNt9LPC1EqRHH330cMbf1hfYGwFbe1CnTh33DUZugZo7e1Ng2eX8WPmUBbPZ1wL42TgtSLbnMNZZx+bA3sBYEG8lRfaJTe4OPY899pjeffdd902O/Y7ZfS0wtrm2NyHFYW8A7E2Lzb3Nh2XN7U2DvbHxs9fpH6uxbkMWoNsbL3tTYm9O7NMXm3M/28fAOhXZaz3jjDPcfwu2FsXPztnnn3/uHi2Yt0+n7PfMOikBABAOomzlqoKcfcRu2bZ9+/a5H3dnZ20RLZtoH81nDwwQnOzNh9U3W50+4CX+dgAAgjnGzY1MOwAAABDkCNoBAACAIBe2LR8RnL777juvhwAAABD+mXbbwv7iiy92F/zZorgpU6YUKlCzBZXWxcG6TWTfvh0AAABAgIN262pibeb8bQmPxRaJXnjhhYd3/7SWcNbBpDg7iAIAAACRqMjlMbY74rF2SMzu5Zdfdju7PPXUU4c3o7GdGK2FW3F2EQUAAAAiTanXtNumLbbBT3YWrFvGPT+2iU32jWysHQ4AAACk1aul5GTfTDRpItm2Fra59/r1OWencmXbfND39f82xs7BNoy2btm//y7t2ZPzNtuU2i4Wgq1Zk/M227OuY0ff14sXS5mZOW+3zberVLF9OKRsG1W7ateWmjaV9u+XVq7MeVt0tNStm+/r5cutNW/O21u2lKpXl7Zvl7ZuzXmbXW+3p6dLS5ce/Vrtee35bWP5lJQj11vn82bNotxxKdKD9u3btx+1M6R9b4G4bbCT146VjzzyiP75z3+W9tAAAABCLmC3oNjP9vK7/XZp+nRp4MCc97VNo/+3ybd69Mj7uVq3lu67T3rnnZy3jR0rPfCA7/Hnn5/ztlatjgTytpl3tv3yXD//LPXqJT39tG2Ol/O2IUMkq7C2gD33mGyDbX+etn9/X+Ce3dSp0iWX2GaK0j335LztssukDz/0vUnI67XaGwB7s2F7OX7/ffZbovT443s0YkQNBbug7B4zevRod2dGPwvwm9hbSQAAgAjmz7BPmmQlx75Mu+nbV5o//+hMu1/u20zjxr7jgw9K2cIul2XZjQXfuR9rwa/f11/nnWk39pzXXJPzNn9Gu337o5/XMuF+FoDnlWk3119vVRs6KtNubCP0vF5rbKzvOHHikUy77SBuO3E3axb8AXuZBO223fiOHTtyXGff265PeWXZjXWZsQu8Fyk7mD7wwANuJyRbLA0AQFmw8GjLlpzXVavmy2RnZEhLlhz9GAvEzztPOukkqU2bI9fXrOm75Kd79/xva9HCd8mLbdJZ0GO7dMn/NntT4H9jkFulSgU/r5Xu5Kd+fd8lL3FxBT+vvaGwEuw1a9Zo9Og+ql49SqGi1DdX6tWrl762t2HZzJo1y70+kl133XVuy8xHH300x/UWONr1ocLad9p4z8/12dnevXvd64vSl93m5NJLL1U4aN++vfvG08rDcmvevHmeb4LsjYO9QcrOHn/nnXeqZcuW7vPZJ07WcjX3v6miWLx4sU4//XRVqFDBfb7HH3/8mI/59ddfdfbZZ6t69eqqUaOGuy7lt99+O3z7wYMH3fPXuXNnlStXLs/z6P+dz33p6C+MBIAIYyUpVsqR/TJ0qO+2vXuPvs0ulgG3BnzZA3YUXkZGhhu079q1y/1/WigpctCekpLiZiP9GUlr6Whfb9y48XBpy7XXXnv4/rfddpvWrVunf/zjH1q5cqVefPFFTZ48WUP9v5URzIKmxx57THtyr/4oo1/aQLEg7auvvtK3336rUGMLUDJzf65XQtYdydZrXHbZZfr3v/9d7OdZv369evTooW+++UZPPPGElixZohkzZrjtU2+3AsZisFKz8847T82aNdP8+fPd57U3CxPt88IC/s3bm7KmTZvql19+cV9ffHy8G7j7f48OHTrkfnL2t7/97aiF537PPvustm3bdviyadMm1axZU/2tcBEAIoj9b9/KPy680FfKkf3irwG3eDL3bXbJXvKCovviiy+0ZcsWnXnmmQo5ThF9++23jj0s92XQoEHu7XY888wzj3pMt27dnLi4OKdly5bOm2++WaSfuW/fPvdn2DG3AwcOOMuXL3ePocTm6aKLLnLat2/vjBgx4vD1n376qftas5s9e7Zz2mmnORUqVHAaN27s3HnnnU5KSsrh2+3+9rjsqlWrdnief//9d/c+77//vnPGGWc45cuXd2/btWuXc8UVVzgNGzZ0Klas6HTq1Ml59913czyPncu///3v+b4Oex77WTfffLNz0kknHb5+z5497s+0c++3ceNGp3///u79a9So4VxyySXu2MzYsWOP+p2yx/71r391br/99sPPYWOx21asWOF+n5aW5lSqVMmZNWuW+/3Bgwfd+alTp477Ok899VRn3rx5R/3+Tp8+3enevbsTGxvrXmc/v2vXrofvt2bNGqdFixbuz87KynKK4rrrrnNGjRrlfPHFF07btm2Pur1Zs2bOM888c9T1ucdwwQUXOI0aNcpxrrPPb3G8+OKL7tzbvPmNHDnSadeuXb6P+fXXX905s/Pnt3jxYve61atX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DwcoNWTaAQBAyHaJsUWnS5YsUZMmTbweDlCqCNoBAEBImjp1qptlv+iii7weClDqKI8BIngBqtWz206mV18t1ajh9YgAoPBeeOEFDR48WDExMUwbIgJBOxChAXvbtke+njTJ6xEBQOFr2BcuXOguOiVgRyQhaAcikGXYjQXrNFoAECoyMzOVkZGhlJQUxdObFhGGoB2IYB06SBUqeD0KACicX3/91c209+7dmylDxGEhKhBhtm71egQAUHTPPvusunbtqtNOO43pQ0Qi0w5EEKtf/8tfpG++sVZpXo8GAI7NMuvTpk3T7bffrnLlCFsQuci0AxFWy750qW/3UwAIdv769ZYtWxKwI+LxlhUI85aOtt9InTrS7t3SihVejwoACm/9+vXauHGju9spEOnItANh3NKxRw9p8mTfddOnS9dc4/uapgsAgt3LL7+sGjVqELAD/0OmHQjzlo7nnef7um9faf58X8Depo2nwwOAfDmOo9dff1233nqroqKimCngfwjagTArh+naNWdLRyuNMTVr+i4AEKzS09O1detWXXTRRQTsQC6UxwBhVg6TmuqrYz/lFMpgAISOrKws7du3T+vWrVP9+vW9Hg4QdAjagTArh6lc2Zdd/+QTymAAhI4PPvjA7RRz1llneT0UIChRHgOEESuHiYnxfV2vntejAYDCefrppzV06FBKYoACELQDQV6jblq0kGrUkHbskLZsyXm/atU8GR4AlNjBgwf122+/6bbbbiNgB46BoB0I4hp1P2vb2L+/9M470t1357zvxRdLH38s7dwpVa9e5kMFgGIH7FbHbipVqsQsAsdA0A4EeY26lbxYpt1cfbXUu/fRmfbY2CNdYgAgFPz000+qVauWevbs6fVQgJBA0A4EWYY9JUWqWFE6/nipe3df0O5nderUqgMIdY8//rhbwx5rGQcAhULQDgRZSUxcnJSWJi1Y4PWIACDwfdinTZum4cOHKzqaBnZAURC0A0FWEvPWW16PBAACb//+/crIyFDHjh0J2IFiIGgHgky7dl6PAAACy3EcrV27VqmpqTr55JOZXqAY+GwKAACUqmeffVbNmzcnYAdKgEw7ECRswenSpVLLll6PBAACw1o6Tpw4UXfddRdTCpQQQTsQJKxjTMeOXo8CAAIjJSVFiYmJuuyyy5hSIAAI2oFSsH691Ly57+slS6SMjJy3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/PPPu3HfjBkzPD8NER+0n3TSSc7tt99+eEIOHTrkNGzY0HnkkUfynLDLL7/cufDCC3Nc17NnT+fWW28t9ZMFb899bpmZmU58fLzz73//m1MTAefezvcpp5zivPbaa86gQYMI2iPk3L/00ktOy5YtnfT09DIcJYLh3Nt9zzrrrBzXWSB36qmncoJClAoRtP/jH/9wOnbsmOO6AQMGOH369HG8FtHlMenp6Zo/f75b5uAXHR3tfj9nzpw8H2PXZ7+/6dOnT773R/ic+9z279+vjIwM1axZsxRHimA59//6179Ut25d3XjjjZyUCDr3n332mXr16uWWx9SrV0+dOnXSuHHjdOjQoTIcObw496eccor7GH8Jzbp169yyqL59+3JCwticII7zyimC7dq1y/3Da3+Is7PvV65cmedjtm/fnuf97XqE97nPbeTIkW6NXO5/3Ai/c//jjz/q9ddf16JFi8polAiWc2+B2jfffKOrr77aDdjWrFmjIUOGuG/YbdtzhO+5v+qqq9zHnXbaaVaVoMzMTN1222265557ymjU8ML2fOK8pKQkHThwwF3f4JWIzrQDxfXoo4+6CxI//fRTd0ETwldycrIGDhzoLkSuXbu218NBGcvKynI/YZk4caJ69OihAQMGaMyYMXr55Zc5F2HOFiPapyovvviiFixYoE8++UTTpk3Tgw8+6PXQEKEiOtNu/wOOiYnRjh07clxv39evXz/Px9j1Rbk/wufc+z355JNu0P7VV1+pS5cupTxSeH3u165dq/Xr17vdB7IHcqZcuXJatWqVWrVqxYkK03/31jEmNjbWfZxfhw4d3GyclVzExcWV+rjhzbm/77773DfsN910k/u9dYtLTU3VLbfc4r5xs/IahJ/6+cR5VatW9TTLbiL6N87+2Frm5Ouvv87xP2P73moY82LXZ7+/mTVrVr73R/ice/P444+7WZYZM2bohBNOKKPRwstzb+1dlyxZ4pbG+C+XXHKJ/vSnP7lfWxs4hO+/+1NPPdUtifG/UTMJCQluME/AHt7n3tYt5Q7M/W/efGsaEY56BXOc50Q4awFlLZ3eeustt7XPLbfc4raA2r59u3v7wIEDnVGjRuVo+ViuXDnnySefdNv+jR07lpaPEXLuH330Ubdd2EcffeRs27bt8CU5OdnDV4GyOPe50T0mcs79xo0b3S5Rd9xxh7Nq1Srn888/d+rWres89NBDHr4KlMW5t/+/27l/77333DaAX375pdOqVSu3ixxCR3Jystuq2S4W9j799NPu1xs2bHBvt3Nu5z53y8cRI0a4cZ61eqblYxCxHpxNmzZ1AzJrCTV37tzDt5155pnu/6Czmzx5stO2bVv3/tYWaNq0aR6MGmV97ps1a+b+g899sT/sCP9/99kRtEfWuf/555/d1r4W8Fn7x4cffthtAYrwPvcZGRnOAw884AbqFSpUcJo0aeIMGTLE2bNnj0ejR3F8++23ef6/23+u7WjnPvdjunXr5v6e2L/5N9980wkGUfYfr7P9AAAAAPIX0TXtAAAAQCggaAcAAACCHEE7AAAAEOQI2gEAAIAgR9AOAAAABDmCdgAAACDIEbQDAAAAQY6gHQAAAAhyBO0AAABAkCNoBwAAAIIcQTsAAACg4Pb/SaDgyUTckq8AAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 900x900 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Reload the model and evaluate it\n",
    "#model_savepath = \n",
    "reloaded_network = load_ffnn_model(model_savepath, model=network)\n",
    "\n",
    "reloaded_network.eval()\n",
    "\n",
    "with torch.no_grad():\n",
    "    test_prediction_scores = reloaded_network(x_test_, pep_idx_test_)\n",
    "\n",
    "# Convert to numpy for sklearn\n",
    "y_test_np = y_test_.squeeze().numpy()\n",
    "test_scores_np = test_prediction_scores.squeeze().numpy()\n",
    "\n",
    "test_auc = roc_auc_score(y_test_np, test_scores_np)\n",
    "test_fpr, test_tpr, _ = roc_curve(y_test_np, test_scores_np)\n",
    "\n",
    "f, a = plt.subplots(1, 1, figsize=(9, 9))\n",
    "\n",
    "a.plot([0, 1], [0, 1], ls=':', lw=0.5, label='Random prediction: AUC=0.500', c='k')\n",
    "a.plot(test_fpr, test_tpr, ls='--', lw=1, label=f'Neural Network: AUC={test_auc:.3f}', c='b')\n",
    "a.legend()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "algorithms_2026",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
