{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":96552,"databundleVersionId":11545599,"sourceType":"competition"},{"sourceId":305187,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":260401,"modelId":281553}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np # linear algebra\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nimport glob\nimport torchaudio\nfrom operator import itemgetter\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, classification_report, accuracy_score, precision_score, recall_score, f1_score\n        \ntrain_path = \"/kaggle/input/deepfake-detection-challenge-pav-2025/train/\"\ndev_path = \"/kaggle/input/deepfake-detection-challenge-pav-2025/dev\"\ntest_path = \"/kaggle/input/deepfake-detection-challenge-pav-2025/test/\"\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-28T06:00:05.284029Z","iopub.execute_input":"2025-03-28T06:00:05.284408Z","iopub.status.idle":"2025-03-28T06:00:05.290827Z","shell.execute_reply.started":"2025-03-28T06:00:05.284378Z","shell.execute_reply":"2025-03-28T06:00:05.289318Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset para o treinamento","metadata":{}},{"cell_type":"code","source":"class AudioDataset(Dataset):\n    def __init__(self, data_dir):\n        self.data_dir = data_dir\n        self.classes = [\"real\", \"fake\"]\n        self.audio_files = []\n        self.labels = []\n\n        for class_idx, class_name in enumerate(self.classes):\n            class_dir = os.path.join(data_dir, class_name)\n            print(os.path.join(class_dir, \"**\", \"*.flac\"))\n            for file in glob.glob(os.path.join(class_dir, \"**\", \"*.flac\"), recursive=True):\n                if file.endswith(\".flac\"):\n                    self.audio_files.append(os.path.join(class_dir, file))\n                    self.labels.append(class_idx)\n\n        self.mel_spectrogram = torchaudio.transforms.MelSpectrogram(\n            sample_rate=TARGET_SAMPLE_RATE, n_fft=1024, hop_length=512, n_mels=64\n        )\n\n    def __len__(self):\n        return len(self.audio_files)\n\n    def __getitem__(self, idx):\n        audio_file = self.audio_files[idx]\n        label = self.labels[idx]\n\n        # Load audio\n        audio, sr = torchaudio.load(audio_file)\n        # Convert to mono\n        if audio.shape[0] > 1:\n            audio = torch.mean(audio, dim=0).unsqueeze(0)\n\n        if sr != TARGET_SAMPLE_RATE:\n            audio = torchaudio.transforms.Resample(sr, TARGET_SAMPLE_RATE)(audio)\n\n        # Pad or truncate the audio to a fixed length\n        fixed_length = (\n            TARGET_SAMPLE_RATE * 3\n        )  # Adjust this value based on your requirements\n        \n        if audio.shape[1] < fixed_length:\n            audio = torch.nn.functional.pad(audio, (0, fixed_length - audio.shape[1]))\n        else:\n            audio = audio[:, :fixed_length]\n\n        audio = self.mel_spectrogram(audio)\n\n        return audio, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:05:52.384498Z","iopub.execute_input":"2025-03-28T05:05:52.384878Z","iopub.status.idle":"2025-03-28T05:05:52.397055Z","shell.execute_reply.started":"2025-03-28T05:05:52.384850Z","shell.execute_reply":"2025-03-28T05:05:52.395651Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset para inferência no conjunto de testes","metadata":{}},{"cell_type":"code","source":"class TestDataset(Dataset):\n    def __init__(self, data_dir):\n        self.data_dir = data_dir\n        self.audio_files = []\n\n        for file in os.listdir(data_dir):\n            if file.endswith(\".flac\"):\n                self.audio_files.append(os.path.join(data_dir, file)) \n\n        self.mel_spectrogram = torchaudio.transforms.MelSpectrogram(\n            sample_rate=TARGET_SAMPLE_RATE, n_fft=1024, hop_length=512, n_mels=64\n        )\n\n    def __len__(self):\n        return len(self.audio_files)\n\n    def __getitem__(self, idx):\n        audio_file = self.audio_files[idx]\n\n        audio, sr = torchaudio.load(audio_file)\n        if audio.shape[0] > 1:\n            audio = torch.mean(audio, dim=0).unsqueeze(0)\n\n        if sr != TARGET_SAMPLE_RATE:\n            audio = torchaudio.transforms.Resample(sr, TARGET_SAMPLE_RATE)(audio)\n\n        fixed_length = TARGET_SAMPLE_RATE * 3\n        if audio.shape[1] < fixed_length:\n            audio = torch.nn.functional.pad(audio, (0, fixed_length - audio.shape[1]))\n        else:\n            audio = audio[:, :fixed_length]\n\n        audio = self.mel_spectrogram(audio)\n\n        return audio, audio_file","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:05:54.966105Z","iopub.execute_input":"2025-03-28T05:05:54.966493Z","iopub.status.idle":"2025-03-28T05:05:54.974696Z","shell.execute_reply.started":"2025-03-28T05:05:54.966463Z","shell.execute_reply":"2025-03-28T05:05:54.973292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Modelo CNN Simples","metadata":{}},{"cell_type":"code","source":"class CNNNetwork(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv1 = nn.Sequential(\n            nn.Conv2d(\n                in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=2\n            ),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2),\n        )\n        self.conv2 = nn.Sequential(\n            nn.Conv2d(\n                in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=2\n            ),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2),\n        )\n        self.conv3 = nn.Sequential(\n            nn.Conv2d(\n                in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=2\n            ),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2),\n        )\n        self.conv4 = nn.Sequential(\n            nn.Conv2d(\n                in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=2\n            ),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2),\n        )\n        self.flatten = nn.Flatten()\n        self.linear = nn.Linear(128 * 5 * 7, 2)\n        self.log_softmax = nn.LogSoftmax(dim=1)\n\n    def forward(self, input_data):\n        x = input_data\n        x = self.conv1(x)\n        x = self.conv2(x)\n        x = self.conv3(x)\n        x = self.conv4(x)\n        x = self.flatten(x)\n        x = self.linear(x)\n        output = self.log_softmax(x)\n\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:05:58.990914Z","iopub.execute_input":"2025-03-28T05:05:58.991256Z","iopub.status.idle":"2025-03-28T05:05:58.999585Z","shell.execute_reply.started":"2025-03-28T05:05:58.991232Z","shell.execute_reply":"2025-03-28T05:05:58.998450Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Treinamento - Split train","metadata":{}},{"cell_type":"code","source":"TARGET_SAMPLE_RATE = 16000\n\nnum_epochs = 10\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:06:01.834123Z","iopub.execute_input":"2025-03-28T05:06:01.834515Z","iopub.status.idle":"2025-03-28T05:06:01.844180Z","shell.execute_reply.started":"2025-03-28T05:06:01.834485Z","shell.execute_reply":"2025-03-28T05:06:01.843030Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef train():\n    \n    # Usage\n    dataset = AudioDataset(train_path)\n    dataloader = DataLoader(dataset, batch_size=32, shuffle=True)\n\n    from torch.optim import Adam\n\n    # Create model, loss function, and optimizer\n    model = CNNNetwork()\n    criterion = nn.NLLLoss()\n    optimizer = Adam(model.parameters(), lr=0.001)\n\n    # Training loop\n    print(device)\n    model.to(device)\n\n    for epoch in range(num_epochs):\n        model.train()\n        running_loss = 0.0\n        running_acc = 0.0\n\n        for audio, labels in dataloader:\n            audio = audio.to(device)\n            labels = labels.to(device).long()\n\n            optimizer.zero_grad()\n            outputs = model(audio)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            running_loss += loss.item()\n            running_acc += (outputs.argmax(1) == labels).sum().item()\n\n        epoch_loss = running_loss / len(dataloader)\n        epoch_acc = running_acc / len(dataset)\n\n        print(\n            f\"Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.4f}, Accuracy: {epoch_acc:.4f}\"\n        )\n    torch.save(model.state_dict(), \"model.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:06:05.783977Z","iopub.execute_input":"2025-03-28T05:06:05.784322Z","iopub.status.idle":"2025-03-28T05:06:05.791833Z","shell.execute_reply.started":"2025-03-28T05:06:05.784297Z","shell.execute_reply":"2025-03-28T05:06:05.790602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T17:53:32.979083Z","iopub.execute_input":"2025-03-27T17:53:32.979404Z","iopub.status.idle":"2025-03-27T19:39:53.330930Z","shell.execute_reply.started":"2025-03-27T17:53:32.979381Z","shell.execute_reply":"2025-03-27T19:39:53.329939Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Carregar o modelo, caso já tenha treinado e queira continuar daqui","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = CNNNetwork()\nmodel.load_state_dict(torch.load(\"model.pth\", map_location=device))\nmodel.eval()\nmodel.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:10:34.870737Z","iopub.execute_input":"2025-03-28T05:10:34.871125Z","iopub.status.idle":"2025-03-28T05:10:34.890435Z","shell.execute_reply.started":"2025-03-28T05:10:34.871094Z","shell.execute_reply":"2025-03-28T05:10:34.889105Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Processo de avaliação dos resultados - Split dev","metadata":{}},{"cell_type":"code","source":"dev_dataset = AudioDataset(dev_path)\ndev_dataloader = DataLoader(dev_dataset, batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:17:07.626360Z","iopub.execute_input":"2025-03-28T05:17:07.626826Z","iopub.status.idle":"2025-03-28T05:17:09.764470Z","shell.execute_reply.started":"2025-03-28T05:17:07.626795Z","shell.execute_reply":"2025-03-28T05:17:09.763421Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inferências Dev","metadata":{}},{"cell_type":"code","source":"results_dev = []\n\nwith torch.no_grad():\n    for audio, labels in dev_dataloader:\n\n        audio = audio.to(device)\n        outputs = model(audio)\n\n        _, preds = torch.max(outputs, 1)\n\n        for i in range(len(outputs)):\n            results_dev.append((labels[i].item(), torch.exp(outputs[i][1]).item()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:33:33.506570Z","iopub.execute_input":"2025-03-28T05:33:33.507021Z","iopub.status.idle":"2025-03-28T05:35:06.120691Z","shell.execute_reply.started":"2025-03-28T05:33:33.506995Z","shell.execute_reply":"2025-03-28T05:35:06.119453Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cálculo do EER e seu Treshold","metadata":{}},{"cell_type":"code","source":"def ComputeErrorRates(scores, labels):\n\n      # Sort the scores from smallest to largest, and also get the corresponding\n      # indexes of the sorted scores.  We will treat the sorted scores as the\n      # thresholds at which the the error-rates are evaluated.\n      sorted_indexes, thresholds = zip(*sorted(\n          [(index, threshold) for index, threshold in enumerate(scores)],\n          key=itemgetter(1)))\n      sorted_labels = []\n      labels = [labels[i] for i in sorted_indexes]\n      fnrs = []\n      fprs = []\n\n      # At the end of this loop, fnrs[i] is the number of errors made by\n      # incorrectly rejecting scores less than thresholds[i]. And, fprs[i]\n      # is the total number of times that we have correctly accepted scores\n      # greater than thresholds[i].\n      for i in range(0, len(labels)):\n          if i == 0:\n              fnrs.append(labels[i])\n              fprs.append(1 - labels[i])\n          else:\n              fnrs.append(fnrs[i-1] + labels[i])\n              fprs.append(fprs[i-1] + 1 - labels[i])\n      fnrs_norm = sum(labels)\n      fprs_norm = len(labels) - fnrs_norm\n\n      # Now divide by the total number of false negative errors to\n      # obtain the false positive rates across all thresholds\n      fnrs = [x / float(fnrs_norm) for x in fnrs]\n\n      # Divide by the total number of corret positives to get the\n      # true positive rate.  Subtract these quantities from 1 to\n      # get the false positive rates.\n      fprs = [1 - x / float(fprs_norm) for x in fprs]\n      return fnrs, fprs, thresholds\n\ndef ComputeEER(fnrs, fprs, thresholds):\n    min_difference = float('inf')\n    eer = None\n    eer_threshold = None\n\n    # Iterate through all FNR and FPR values to find the minimum difference\n    for i in range(len(fnrs)):\n        difference = abs(fnrs[i] - fprs[i])\n        if difference < min_difference:\n            min_difference = difference\n            eer = (fnrs[i] + fprs[i]) / 2  # Approximate EER as the average at the point of minimum difference\n            eer_threshold = thresholds[i]\n\n    return eer, eer_threshold\n\nlabels = [infer[0] for infer in results_dev]\npreds = [infer[1] for infer in results_dev]\n\nfnrs, fprs, thresholds = ComputeErrorRates(preds, labels)\neer, eer_threshold = ComputeEER(fnrs, fprs, thresholds)\n\nprint(f\"EER: {eer}\\nEER Treshold: {eer_threshold}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:47:51.404903Z","iopub.execute_input":"2025-03-28T05:47:51.405278Z","iopub.status.idle":"2025-03-28T05:47:51.429847Z","shell.execute_reply.started":"2025-03-28T05:47:51.405249Z","shell.execute_reply":"2025-03-28T05:47:51.428735Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Verificar Matriz de Confusão e outras métricas","metadata":{}},{"cell_type":"code","source":"def plotar_matriz_confusao(y_true, y_pred, figsize=(10, 8)):\n\n    cm = confusion_matrix(y_true, y_pred)\n    \n    if len(cm) == 2:\n        tn, fp, fn, tp = cm.ravel()\n        \n        accuracy = accuracy_score(y_true, y_pred)\n        precision = precision_score(y_true, y_pred)\n        recall = recall_score(y_true, y_pred)\n        f1 = f1_score(y_true, y_pred)\n        \n        plt.figure(figsize=figsize)\n        \n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                    xticklabels=['Real', 'Fake'],\n                    yticklabels=['Real', 'Fake'])\n        \n        plt.title('Matriz de Confusão - Detecção de DeepFake', fontsize=16)\n        plt.ylabel('Rótulo Verdadeiro', fontsize=12)\n        plt.xlabel('Predição do Modelo', fontsize=12)\n        \n        metricas_texto = (\n            f'Acurácia: {accuracy:.4f}\\n'\n            f'Precisão: {precision:.4f}\\n'\n            f'Recall: {recall:.4f}\\n'\n            f'F1-Score: {f1:.4f}'\n        )\n        \n        plt.figtext(0.36, -0.1, metricas_texto, fontsize=12, \n                   bbox={\"facecolor\":\"lightblue\", \"alpha\":0.5, \"pad\":5})\n        \n        plt.tight_layout()\n        plt.show()\n        \n        print(\"\\nRelatório de Classificação:\")\n        print(classification_report(y_true, y_pred, target_names=['Real', 'Fake']))\n    else:\n        print(\"Erro: A matriz de confusão não é 2x2. Verifique os dados.\")\n\n\ndiscrete_preds = [1 if pred > eer_threshold else 0 for pred in preds]\nplotar_matriz_confusao(labels, discrete_preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T05:53:19.038078Z","iopub.execute_input":"2025-03-28T05:53:19.038533Z","iopub.status.idle":"2025-03-28T05:53:19.405169Z","shell.execute_reply.started":"2025-03-28T05:53:19.038502Z","shell.execute_reply":"2025-03-28T05:53:19.404002Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inferência - Spit Test","metadata":{}},{"cell_type":"code","source":"test_dataset = TestDataset(test_path)\ntest_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)\n\nresults = []\n\nwith torch.no_grad():\n    for audio, audio_file in test_dataloader:\n        audio = audio.to(device)\n        outputs = model(audio)\n\n        _, preds = torch.max(outputs, 1)\n\n        for i in range(len(outputs)):\n            results.append((audio_file[i], torch.exp(outputs[i][1]).item()))\n\nwith open(\"submission.csv\", \"w\") as f:\n    f.write(\"id,fake_prob\\n\")\n    for result in results:\n        f.write(\"/\".join(result[0].split(\"/\")[-2:]) + \",\" + str(result[1]) + \"\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-28T06:01:32.557577Z","iopub.execute_input":"2025-03-28T06:01:32.558067Z","iopub.status.idle":"2025-03-28T06:02:38.051077Z","shell.execute_reply.started":"2025-03-28T06:01:32.558036Z","shell.execute_reply":"2025-03-28T06:02:38.050064Z"}},"outputs":[],"execution_count":null}]}