{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":87793,"databundleVersionId":11553390,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport os\n\n# Configurer la gestion de la mémoire PyTorch\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\n\n# Définir le device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Utilisation du device : {device}\")\n\n# Étape 1 : Charger et préparer les données d'entraînement\ntrain_sequences = pd.read_csv('/kaggle/input/stanford-rna-3d-folding/train_sequences.csv')\ntrain_labels = pd.read_csv('/kaggle/input/stanford-rna-3d-folding/train_labels.csv')\n\n# Définir la fonction is_valid_sequence\nvalid_nucleotides = set('ACGU')\ndef is_valid_sequence(seq):\n    return all(nucleotide in valid_nucleotides for nucleotide in seq)\n\ntrain_sequences['is_valid'] = train_sequences['sequence'].apply(is_valid_sequence)\ntrain_clean = train_sequences[train_sequences['is_valid']].drop(columns=['is_valid'])\n\ntrain_labels['sequence_id'] = train_labels['ID'].str.rsplit('_', n=1).str[0]\ntrain_data = pd.merge(train_clean, train_labels, left_on='target_id', right_on='sequence_id')\n\ninvalid_target_ids = set(train_data[train_data[['x_1', 'y_1', 'z_1']].isna().any(axis=1)]['sequence_id'])\ninvalid_target_ids.update(train_data[train_data[['x_1', 'y_1', 'z_1']].apply(lambda x: x.map(np.isinf).any(), axis=1)]['sequence_id'])\ntrain_clean_filtered = train_clean[~train_clean['target_id'].isin(invalid_target_ids)]\n\n# Construire coords_dict\ncoords_dict = train_data.groupby('sequence_id')[['x_1', 'y_1', 'z_1']].apply(lambda x: x.values).to_dict()\nfor target_id in coords_dict:\n    coords_dict[target_id] = {i+1: coords for i, coords in enumerate(coords_dict[target_id])}\n\n# Créer un dataset personnalisé pour l'entraînement\nclass RNATrainDataset(Dataset):\n    def __init__(self, data, coords_dict, max_length=300):\n        self.data = data\n        self.coords_dict = coords_dict\n        self.tokens = {'A': 1, 'C': 2, 'G': 3, 'U': 4}\n        self.max_length = max_length\n\n    def __len__(self):\n        return len(self.data)\n    \n    def __getitem__(self, idx):\n        target_id = self.data.iloc[idx]['target_id']\n        sequence = self.data.iloc[idx]['sequence']\n        seq_tensor = torch.tensor([self.tokens.get(nuc, 0) for nuc in sequence], dtype=torch.long)\n        coords = np.array([self.coords_dict[target_id][i] for i in range(1, len(sequence) + 1)], dtype=np.float32)\n        coords_tensor = torch.tensor(coords, dtype=torch.float32)\n        length = min(len(sequence), self.max_length)\n        return seq_tensor[:length], coords_tensor[:length], length\n\n# Définir une fonction collate_fn personnalisée pour gérer le padding\ndef custom_collate_fn(batch):\n    seqs, coords, lengths = zip(*batch)\n    # Convertir lengths en tenseur\n    lengths = torch.tensor(lengths, dtype=torch.long)\n    # Padding des séquences et des coordonnées\n    seqs = torch.nn.utils.rnn.pad_sequence(seqs, batch_first=True, padding_value=0)\n    coords = torch.nn.utils.rnn.pad_sequence(coords, batch_first=True, padding_value=0)\n    return seqs, coords, lengths\n\n# Créer le dataset et le DataLoader\nmax_length = 300\ntrain_dataset = RNATrainDataset(train_clean_filtered, coords_dict, max_length=max_length)\nbatch_size = 16\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=custom_collate_fn)\n\n# Normaliser les coordonnées y (calculer y_mean et y_std sur un sous-ensemble pour économiser de la mémoire)\ny_samples = []\nfor i in range(min(100, len(train_dataset))):\n    _, coords, _ = train_dataset[i]\n    y_samples.append(coords)\ny_samples = torch.cat(y_samples, dim=0)\ny_mean = y_samples.mean(dim=0, keepdim=True)\ny_std = y_samples.std(dim=0, keepdim=True) + 1e-6\n\n# Définir le modèle\nclass RNAModel(nn.Module):\n    def __init__(self, vocab_size=5, embed_dim=64, hidden_dim=128):\n        super(RNAModel, self).__init__()\n        self.embedding = nn.Embedding(vocab_size, embed_dim)\n        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)\n        self.fc = nn.Linear(hidden_dim, 3)\n\n    def forward(self, x, lengths):\n        x = self.embedding(x)\n        x, _ = self.lstm(x)\n        x = self.fc(x)\n        return x\n\n# Initialiser et entraîner le modèle\nmodel = RNAModel().to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=0.0001)\n\n# Définir la fonction de perte\ndef masked_mse_loss(outputs, targets, lengths):\n    batch_size, max_length, _ = outputs.shape\n    mask = torch.zeros(batch_size, max_length, dtype=torch.bool, device=outputs.device)\n    for i in range(batch_size):\n        mask[i, :lengths[i]] = 1.0\n    mask = mask.unsqueeze(-1)\n    loss = (outputs - targets) ** 2\n    loss = loss * mask\n    mask_sum = mask.sum()\n    if mask_sum == 0:\n        return torch.tensor(0.0, requires_grad=True, device=outputs.device)\n    return loss.sum() / mask_sum\n\n# Entraîner le modèle avec accumulation de gradients\nnum_epochs = 5\naccumulation_steps = 4\nfor epoch in range(num_epochs):\n    model.train()\n    total_loss = 0\n    optimizer.zero_grad()\n    for batch_idx, (seqs, coords, lengths) in enumerate(train_loader):\n        # Déplacer les tenseurs sur le device\n        seqs = seqs.to(device)\n        coords = coords.to(device)\n        lengths = lengths.to(device)\n\n        # Normaliser les coordonnées\n        coords = (coords - y_mean.to(device)) / y_std.to(device)\n\n        # Prédire\n        outputs = model(seqs, lengths)\n        loss = masked_mse_loss(outputs, coords, lengths)\n        loss = loss / accumulation_steps\n        loss.backward()\n\n        total_loss += loss.item() * accumulation_steps\n\n        # Mettre à jour les poids après accumulation_steps itérations\n        if (batch_idx + 1) % accumulation_steps == 0:\n            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n            optimizer.step()\n            optimizer.zero_grad()\n            # Vider le cache de la mémoire GPU\n            torch.cuda.empty_cache()\n\n    print(f\"Époque {epoch+1}/{num_epochs}, Perte moyenne : {total_loss / len(train_loader):.4f}\")\n\n# Étape 5 : Prédire pour test_sequences\ntest = pd.read_csv('/kaggle/input/stanford-rna-3d-folding/test_sequences.csv')\ntest['is_valid'] = test['sequence'].apply(is_valid_sequence)\ntest_clean = test[test['is_valid']].drop(columns=['is_valid'])\n\n# Créer le dataset de test\nclass RNADataset(Dataset):\n    def __init__(self, data):\n        self.data = data\n        self.tokens = {nt: i for i, nt in enumerate('ACGU')}\n\n    def __len__(self):\n        return len(self.data)\n    \n    def __getitem__(self, idx):\n        sequence = [self.tokens[nt] for nt in self.data.loc[idx, 'sequence']]\n        sequence = np.array(sequence)\n        sequence = torch.tensor(sequence)\n        return {'sequence': sequence}\n\ntest_dataset = RNADataset(test_clean)\n\n# Définir lengths_test\nlengths_test_list = [len(seq) for seq in test_clean['sequence']]\nlengths_test = torch.tensor(lengths_test_list, dtype=torch.long).to(device)\n\n# Calculer le nombre total de nucléotides attendu\ntotal_nucleotides = test_clean['sequence'].str.len().sum()\nprint(f\"Nombre total de nucléotides attendu : {total_nucleotides}\")\n\n# Prédire les coordonnées\nmodel.eval()\npreds = []\nfor i in range(len(test_dataset)):\n    src = test_dataset[i]['sequence'].long()\n    src = src.unsqueeze(0).to(device)\n\n    # Générer 5 prédictions différentes\n    tmp = []\n    # 4 prédictions en mode train pour introduire de la variabilité\n    model.train()\n    for _ in range(4):\n        with torch.no_grad():\n            xyz = model(src, lengths_test[i:i+1]).squeeze()\n            # Dénormaliser les prédictions\n            xyz = xyz * y_std.to(device) + y_mean.to(device)\n        tmp.append(xyz.cpu().numpy())\n\n    # 1 prédiction en mode eval pour une prédiction stable\n    model.eval()\n    with torch.no_grad():\n        xyz = model(src, lengths_test[i:i+1]).squeeze()\n        # Dénormaliser les prédictions\n        xyz = xyz * y_std.to(device) + y_mean.to(device)\n    tmp.append(xyz.cpu().numpy())\n\n    tmp = np.stack(tmp, axis=0)  # Shape: (5, seq_len, 3)\n    preds.append(tmp)\n\n    # Vider le cache de la mémoire GPU\n    torch.cuda.empty_cache()\n\n# Construire le fichier submission.csv\ndata = []\nfor i in range(len(test_clean)):\n    sequence = test_clean.iloc[i]['sequence']\n    target_id = test_clean.iloc[i]['target_id']\n    seq_len = len(sequence)\n    \n    for j in range(seq_len):\n        row = [f\"{target_id}_{j+1}\", sequence[j], j+1]\n        for k in range(5):  # 5 prédictions\n            x, y, z = preds[i][k][j]\n            row.extend([x, y, z])\n        data.append(row)\n\n# Définir les colonnes\ncolumns = ['ID', 'resname', 'resid']\nfor i in range(1, 6):\n    columns.extend([f'x_{i}', f'y_{i}', f'z_{i}'])\n\n# Créer le DataFrame\nsubmission_df = pd.DataFrame(data, columns=columns)\n\n# Vérifier les NaN\nprint(\"Vérification des valeurs NaN dans submission_df :\")\nprint(submission_df.isna().sum())\nsubmission_df = submission_df.fillna(0)\n\n# Vérifier le nombre de lignes\nprint(f\"Nombre de lignes dans submission_df : {len(submission_df)}\")\nif len(submission_df) != total_nucleotides:\n    print(\"Erreur : Le nombre de lignes ne correspond pas au nombre de nucléotides attendu !\")\n\n# Vérifier les colonnes\nexpected_columns = ['ID', 'resname', 'resid'] + [f'{coord}_{i}' for i in range(1, 6) for coord in ['x', 'y', 'z']]\nprint(\"Colonnes attendues :\", expected_columns)\nprint(\"Colonnes dans submission_df :\", submission_df.columns.tolist())\n\n# Sauvegarder le fichier\nsubmission_df.to_csv('submission.csv', index=False)\nprint(\"Soumission créée : submission.csv\")\n\n# Afficher un aperçu\nprint(\"Aperçu de submission.csv :\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-31T12:10:06.733602Z","iopub.execute_input":"2025-03-31T12:10:06.733911Z","iopub.status.idle":"2025-03-31T12:10:18.180465Z","shell.execute_reply.started":"2025-03-31T12:10:06.733889Z","shell.execute_reply":"2025-03-31T12:10:18.179683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}