{ "cells": [ { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import cv2\n", "import os\n", "from tqdm import tqdm,trange\n", "import sklearn.metrics\n", "\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "\n", "import warnings\n", "warnings.filterwarnings(\"ignore\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initializing a project" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Project name: deepfake-shield-mayukh\n", "Artifacts path: /media/mayukh/Data/storage/repositories/repos/deep-shield-temp/notebooks/artifact_dir\n" ] } ], "source": [ "from os import path\n", "import mlrun\n", "\n", "# Set the base project name\n", "project_name_base = 'deepfake-shield'\n", "# Initialize the MLRun environment and save the project name and artifacts path\n", "project_name, artifact_path = mlrun.set_environment(project=project_name_base,\n", " user_project=True, api_path = './api_dir', artifact_path = './artifact_dir')\n", " \n", "# Display the current project name and artifacts path\n", "print(f'Project name: {project_name}')\n", "print(f'Artifacts path: {artifact_path}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialize training code with `mlrun`" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "# mlrun: start-code" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "import os\n", "import cv2\n", "import numpy as np\n", "import pandas as pd\n", "import sklearn.metrics\n", "from tqdm import tqdm,trange\n", "import matplotlib.pyplot as plt\n", "from mlrun.datastore import DataItem \n", "from mlrun.artifacts import PlotArtifact\n", "\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "\n", "import warnings\n", "warnings.filterwarnings(\"ignore\")\n", "\n", "import gc \n", "import random\n", "from torch.utils.data import Dataset, DataLoader\n", "from pytorchcv.model_provider import get_model as ptcv_get_model" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [], "source": [ "\n", "class ImageDataset(Dataset):\n", " def __init__(self, x, y, training=True, transform=None):\n", " self.x = x\n", " self.y = y\n", " self.transform = transform\n", " self.training = training\n", "\n", " def __len__(self):\n", " return len(self.x)\n", " \n", " def __getitem__(self, idx):\n", " if torch.is_tensor(idx):\n", " idx = idx.tolist()\n", " \n", " img_path = self.x[idx]\n", " \n", " img_numpy = read_img(img_path)\n", "# print(img_numpy.max(), img_numpy.min())\n", "\n", " if self.transform is not None:\n", " res = self.transform(image=img_numpy)\n", " img_numpy = res['image']\n", " \n", " img_tensor = torch.tensor(img_numpy).permute(-1,0,1)\n", " \n", "\n", " labels = self.y[idx]\n", " return {\n", " 'image': img_tensor,\n", " 'label': labels\n", " }" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [], "source": [ "class Head(torch.nn.Module):\n", " def __init__(self, in_f, out_f):\n", " super(Head, self).__init__()\n", "\n", " self.f = nn.Flatten()\n", " self.l = nn.Linear(in_f, 512)\n", " self.d = nn.Dropout(0.75)\n", " self.o = nn.Linear(512, out_f)\n", " self.b1 = nn.BatchNorm1d(in_f)\n", " self.b2 = nn.BatchNorm1d(512)\n", " self.r = nn.ReLU()\n", "\n", " def forward(self, x):\n", "# print(x.shape)\n", " x = x.reshape(x.size(0), -1)\n", " x = self.b1(x)\n", " x = self.d(x)\n", "\n", " x = self.l(x)\n", " x = self.r(x)\n", " x = self.b2(x)\n", " x = self.d(x)\n", "\n", " out = self.o(x)\n", " return out\n", " \n", "class FCN(torch.nn.Module):\n", " def __init__(self, base, in_f):\n", " super(FCN, self).__init__()\n", " self.base = base\n", " self.h1 = Head(in_f, 1)\n", " \n", " def forward(self, x):\n", " x = self.base(x)\n", " return self.h1(x)\n" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [], "source": [ "def get_model():\n", " model = ptcv_get_model(\"efficientnet_b4\")\n", " model = nn.Sequential(*list(model.children())[:-1]) # Remove original output layer\n", " model[0].final_block.pool = nn.Sequential(nn.AdaptiveAvgPool2d(1))\n", " model = FCN(model, 1792)\n", " model = model.cuda()\n", " \n", " return model" ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [], "source": [ "def read_img(path):\n", " return cv2.cvtColor(cv2.imread(path),cv2.COLOR_BGR2RGB)\n", "\n", "def shuffle(X,y):\n", " new_train=[]\n", " for m,n in zip(X,y):\n", " new_train.append([m,n])\n", " random.shuffle(new_train)\n", " X,y=[],[]\n", " for x in new_train:\n", " X.append(x[0])\n", " y.append(x[1])\n", " return X,y\n", "\n", "def get_random_sampling(paths, y, val_paths, val_y, num_train_samples = None, num_val_samples = None):\n", " real=[]\n", " fake=[]\n", " for m,n in zip(paths,y):\n", " if n==0:\n", " real.append(m)\n", " else:\n", " fake.append(m)\n", " # fake=random.sample(fake,len(real))\n", " paths,y=[],[]\n", " for x in real:\n", " paths.append(x)\n", " y.append(0)\n", " for x in fake:\n", " paths.append(x)\n", " y.append(1)\n", "\n", " real=[]\n", " fake=[]\n", " \n", " for m,n in zip(val_paths,val_y):\n", " if n==0:\n", " real.append(m)\n", " else:\n", " fake.append(m)\n", " \n", " \n", " val_paths,val_y=[],[]\n", " for x in real:\n", " val_paths.append(x)\n", " val_y.append(0)\n", " for x in fake:\n", " val_paths.append(x)\n", " val_y.append(1)\n", "\n", " X=[]\n", " for img in tqdm(paths):\n", " X.append(img)\n", " val_X=[]\n", " for img in tqdm(val_paths):\n", " val_X.append(img)\n", "\n", " # Balance with ffhq dataset\n", " ffhq = os.listdir('training_data/ffhq/thumbnails128x128')\n", " X_ = []\n", " for file in tqdm(ffhq):\n", " path = f'training_data/ffhq/thumbnails128x128/{file}'\n", " X_.append(path)\n", " random.shuffle(X_)\n", "\n", " for i in range(64773 - 12130):\n", " X.append(X_[i])\n", " y.append(0)\n", " \n", " \n", " del X_[0:64773 - 12130]\n", "\n", " for i in range(6108 - 1258):\n", " val_X.append(X_[i])\n", " val_y.append(0)\n", "\n", " X, y = shuffle(X,y)\n", " val_X, val_y = shuffle(val_X,val_y)\n", " \n", " if num_train_samples is not None:\n", " X, y = X[:num_train_samples], y[:num_train_samples]\n", " \n", " if num_val_samples is not None:\n", " val_X, val_y = X[:num_val_samples], y[:num_val_samples]\n", "\n", " return X, val_X, y, val_y\n", "\n", "def train_n_epochs(model, num_epochs , train_loader, val_loader, checkpoint_name = 'model.pth', lr = 0.001, loss = None, context = None):\n", " \n", " best_acc = 0.\n", " accuracies = []\n", " losses = []\n", " \n", " optimizer = torch.optim.AdamW(model.parameters(), lr=lr)\n", " scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5, mode='min', factor=0.7, verbose=True, min_lr=1e-5)\n", "\n", " for epoch in range(num_epochs):\n", " torch.cuda.empty_cache()\n", " gc.collect()\n", "\n", " model, loss = train_model(model, epoch, optimizer, scheduler, loss = loss, train_loader= train_loader)\n", " losses.append(loss)\n", " \n", " acc = evaluate_model(model, val_loader)\n", " accuracies.append(acc)\n", " \n", " if acc > best_acc:\n", " best_acc = acc\n", " print(f'Saving best model: {checkpoint_name}')\n", " torch.save(model.state_dict(), checkpoint_name)\n", " \n", " if context is not None:\n", " context.logger.info(f'epoch: {epoch+1} current acc: {round(acc, 4)} best acc: {round(best_acc, 4)}')\n", " \n", " \n", " fig, ax = plt.subplots(nrows = 1, ncols = 2, figsize = (12,4))\n", " \n", " ax[0].plot(losses)\n", " ax[0].set_ylabel('Loss', fontsize = 18)\n", " ax[0].set_xlabel('Epochs', fontsize = 18)\n", " \n", " ax[1].plot(accuracies, c = 'g')\n", " ax[1].set_ylabel('Accuracy', fontsize = 18)\n", " ax[1].set_xlabel('Epochs', fontsize = 18)\n", " \n", " context.log_artifact(PlotArtifact('training logs', body=fig))\n", "\n", " return model" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [], "source": [ "\n", "def evaluate_model(model, val_loader):\n", " model.eval()\n", " pred = []\n", " real = []\n", " with torch.no_grad():\n", " for batch in val_loader:\n", " \n", " img_batch, y_batch = batch['image'].cuda().float(), batch['label'].cuda().float()\n", "\n", " o1 = model(img_batch)\n", " \n", " for j in o1:\n", " pred.append(F.sigmoid(j))\n", " \n", " for i in y_batch:\n", " real.append(i.data.cpu())\n", " \n", " pred = [p.data.cpu().numpy() for p in pred]\n", " pred = [np.round(p) for p in pred]\n", " pred = np.array(pred)\n", " acc = sklearn.metrics.recall_score(real, pred, average='macro')\n", "\n", " return acc\n", "\n", "def criterion(pred1, targets):\n", " l1 = F.binary_cross_entropy(F.sigmoid(pred1).squeeze(-1), targets)\n", " return l1\n", "\n", "def train_model(model, epoch, optimizer, scheduler, loss , train_loader):\n", " model.train()\n", " total_loss = 0\n", "\n", " t = tqdm(train_loader, disable = True)\n", " for i, batch in enumerate(train_loader):\n", " \n", " img_batch = batch['image']\n", " img_batch = img_batch.cuda()\n", "\n", " y_batch = batch['label']\n", " y_batch = y_batch.cuda().float()\n", "\n", " optimizer.zero_grad()\n", "\n", " out = model(img_batch)\n", " loss = criterion(out, y_batch)\n", " \n", "\n", " total_loss += loss\n", " t.set_description(f'Epoch {epoch+1}, LR: %6f, Loss: %.4f'%(optimizer.state_dict()['param_groups'][0]['lr'],total_loss/(i+1)))\n", "\n", " loss.backward()\n", " optimizer.step()\n", " \n", " scheduler.step(1.)\n", "\n", " return model, total_loss" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [], "source": [ "from mlrun.mlutils.data import get_sample\n", "import pandas as pd \n", "import albumentations\n", "from albumentations import (\n", " ShiftScaleRotate, \n", " HorizontalFlip, \n", " Normalize, \n", " GaussNoise, \n", ")\n", "\n", "def get_training_data(train_dataset: DataItem, val_dataset: DataItem, \n", " label_column = 'labels', df_train_path = 'df_train.csv',\n", " df_val_path = 'df_val.csv', ideal_params = None): \n", " \n", " df_train = pd.read_csv(df_train_path)\n", " df_val = pd.read_csv(df_val_path)\n", " \n", " mean = [0.485, 0.456, 0.406]\n", " std = [0.229, 0.224, 0.225]\n", "\n", " train_transform = albumentations.Compose([\n", " ShiftScaleRotate(p=0.3, scale_limit=0.25, border_mode=1, rotate_limit=25),\n", " HorizontalFlip(p=0.2),\n", " GaussNoise(p=.2),\n", " Normalize(mean = mean, std = std),\n", " albumentations.augmentations.geometric.resize.Resize(128,128)\n", " ])\n", " \n", " val_transform = albumentations.Compose([\n", " Normalize(mean = mean, std = std),\n", " albumentations.augmentations.geometric.resize.Resize(128,128)\n", " ])\n", "\n", " \n", " raw, labels, header = get_sample(train_dataset, sample=-1, label=label_column)\n", " raw_val, labels_val, header = get_sample(val_dataset, sample=-1, label=label_column) \n", " \n", " X, y = raw['paths'].values, labels.values\n", " val_X, val_y = raw_val['paths'].values, labels_val.values\n", "\n", " X, val_X, Y, val_Y = get_random_sampling(\n", " df_train['paths'].values, \n", " df_train['labels'].values, \n", " df_val['paths'].values,\n", " df_val['labels'].values, \n", " num_train_samples= None, # Set this to None if you want to train on the whole dataset\n", " num_val_samples= None # Set this to None if you want to train on the whole dataset\n", " )\n", "\n", " train_dataset = ImageDataset(X, Y, transform=train_transform)\n", " val_dataset = ImageDataset(val_X, val_Y, transform=val_transform)\n", "\n", " train_loader = DataLoader(dataset=train_dataset, batch_size=ideal_params['batch_size'], shuffle=True, num_workers=4)\n", " val_loader = DataLoader(dataset=val_dataset, batch_size= 10, shuffle=False, num_workers=0)\n", "\n", " return {\n", " 'train_loader': train_loader,\n", " 'val_loader': val_loader\n", " }\n", "\n", "\n", "\n", "def train_model_auto(context, train_dataset: DataItem, val_dataset: DataItem, \n", " num_epochs, hyperparams, checkpoint_name, df_train_path, df_val_path):\n", " \n", " print(\"started training :)\")\n", " \n", " data = get_training_data(\n", " train_dataset= train_dataset,\n", " val_dataset= val_dataset,\n", " df_train_path = df_train_path,\n", " df_val_path = df_val_path,\n", " ideal_params= hyperparams\n", " )\n", " \n", " model = get_model()\n", " \n", " train_n_epochs(\n", " model = model,\n", " num_epochs = num_epochs,\n", " train_loader= data['train_loader'],\n", " val_loader= data['val_loader'],\n", " lr = hyperparams['learning_rate'],\n", " loss = criterion,\n", " checkpoint_name = checkpoint_name,\n", " context = context ## context passed into fn\n", " )\n", " " ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [], "source": [ "# mlrun: end-code" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "train_func = mlrun.code_to_function(\n", " name='train_model_auto',\n", " kind='job',\n", " image='mlrun/mlrun'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training the model with `mlrun`" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [], "source": [ "train_dataset = f'store://{project_name}/prep_data_deepfake_dataset_train'\n", "val_dataset = f'store://{project_name}/prep_data_deepfake_dataset_val'" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [], "source": [ "results = pd.read_csv('artifact_dir/iteration_results.csv')\n", "best_iter = results[results['output.loss'] == results['output.loss'].min()]\n", "\n", "ideal_params = {\n", " 'learning_rate': best_iter['param.learning_rate'].values[0],\n", " 'batch_size': best_iter['param.batch_size'].values[0]\n", "}" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'learning_rate': 0.002508, 'batch_size': 16}" ] }, "execution_count": 98, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ideal_params" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "> 2021-07-03 13:17:02,549 [info] starting run train_model_auto uid=a0174e29d33a47d2adf827fe393c5e02 DB=./api_dir\n", "started training :)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 104890/104890 [00:00<00:00, 4958585.11it/s]\n", "100%|██████████| 7366/7366 [00:00<00:00, 4893133.24it/s]\n", "100%|██████████| 70000/70000 [00:00<00:00, 3777388.26it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Saving best model: model.pt\n", "> 2021-07-03 13:17:07,229 [info] epoch: 1 current acc: 0.5 best acc: 0.5\n", "> 2021-07-03 13:17:08,558 [info] epoch: 2 current acc: 0.5 best acc: 0.5\n", "> 2021-07-03 13:17:09,903 [info] epoch: 3 current acc: 0.5 best acc: 0.5\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:11,580 [info] epoch: 4 current acc: 0.5244 best acc: 0.5244\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:13,270 [info] epoch: 5 current acc: 0.5366 best acc: 0.5366\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:15,008 [info] epoch: 6 current acc: 0.6463 best acc: 0.6463\n", "Epoch 7: reducing learning rate of group 0 to 1.7556e-03.\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:16,690 [info] epoch: 7 current acc: 0.7195 best acc: 0.7195\n", "> 2021-07-03 13:17:18,032 [info] epoch: 8 current acc: 0.6707 best acc: 0.7195\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:19,728 [info] epoch: 9 current acc: 0.7805 best acc: 0.7805\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:21,410 [info] epoch: 10 current acc: 0.8049 best acc: 0.8049\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:23,098 [info] epoch: 11 current acc: 0.878 best acc: 0.878\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:24,772 [info] epoch: 12 current acc: 0.9146 best acc: 0.9146\n", "Epoch 13: reducing learning rate of group 0 to 1.2289e-03.\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:26,451 [info] epoch: 13 current acc: 0.939 best acc: 0.939\n", "Saving best model: model.pt\n", "> 2021-07-03 13:17:28,121 [info] epoch: 14 current acc: 0.9512 best acc: 0.9512\n", "> 2021-07-03 13:17:29,573 [info] epoch: 15 current acc: 0.9268 best acc: 0.9512\n" ] }, { "data": { "text/html": [ "\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
deepfake-shield-mayukh
...3c5e02
0Jul 03 07:47:02completedtrain_model_auto
kind=
owner=mayukh
host=leopard
train_dataset
val_dataset
num_epochs=15
hyperparams={'learning_rate': 0.002508, 'batch_size': 16}
checkpoint_name=model.pt
df_train_path=df_train.csv
df_val_path=df_val.csv
training logs
\n", "
\n", "
\n", "
\n", " Title\n", " ×\n", "
\n", " \n", "
\n", "
\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "to track results use .show() or .logs() or in CLI: \n", "!mlrun get run a0174e29d33a47d2adf827fe393c5e02 --project deepfake-shield-mayukh , !mlrun logs a0174e29d33a47d2adf827fe393c5e02 --project deepfake-shield-mayukh\n", "> 2021-07-03 13:17:29,784 [info] run executed, status=completed\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "params = {\n", " 'num_epochs':15, \n", " 'hyperparams': ideal_params,\n", " 'checkpoint_name': 'model.pt',\n", " 'df_train_path': 'df_train.csv',\n", " 'df_val_path': 'df_val.csv'\n", "}\n", "\n", "inputs = {\n", " 'train_dataset': train_dataset,\n", " 'val_dataset': val_dataset\n", " \n", "}\n", "\n", "train_run = train_func.run(name='train_model_auto',\n", " handler=train_model_auto,\n", " inputs=inputs,\n", " params = params,\n", " local=True)" ] }, { "cell_type": "code", "execution_count": 116, "metadata": {}, "outputs": [], "source": [ "mean = [0.485, 0.456, 0.406]\n", "std = [0.229, 0.224, 0.225]\n", "\n", "val_transform = albumentations.Compose([\n", " Normalize(mean = mean, std = std),\n", " albumentations.augmentations.geometric.resize.Resize(128,128)\n", " ])\n", "\n", "df_val = pd.read_csv(params['df_val_path'])\n", "val_X, val_Y = df_val['paths'].values, df_val['labels'].values\n", "\n", "val_dataset = ImageDataset(val_X, val_Y, transform=val_transform)\n", "val_loader = DataLoader(dataset=val_dataset, batch_size= 10, shuffle=False, num_workers=0)" ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "final accuracy: 0.95\n" ] } ], "source": [ "best_model = get_model()\n", "best_model.load_state_dict(torch.load(params['checkpoint_name']))\n", "\n", "acc = evaluate_model(model = best_model.cuda(), val_loader = val_loader)\n", "print(f'final accuracy: {round(acc,2)}')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }