{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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"},"papermill":{"default_parameters":{},"duration":1087.035813,"end_time":"2023-01-22T10:17:16.597092","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2023-01-22T09:59:09.561279","version":"2.3.4"},"colab":{"provenance":[]},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":39272,"databundleVersionId":4629629,"sourceType":"competition"},{"sourceId":4619805,"sourceType":"datasetVersion","datasetId":2688675}],"dockerImageVersionId":30616,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **CV Task 3 Solution**\n\n## **DJS Synapse Learning Period**\n![image.png](data:image/png;base64,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)","metadata":{"papermill":{"duration":0.010044,"end_time":"2023-01-22T09:59:18.486192","exception":false,"start_time":"2023-01-22T09:59:18.476148","status":"completed"},"tags":[],"id":"07810ece"}},{"cell_type":"markdown","source":"## importing and installing stuff","metadata":{"id":"Y3iW0S4HfaSb"}},{"cell_type":"code","source":"!pip install pydicom -q","metadata":{"id":"5nMAz_AQt3N0","outputId":"5a367117-0401-41e1-9838-07b1a238ccec","execution":{"iopub.status.busy":"2023-12-11T17:10:21.338397Z","iopub.execute_input":"2023-12-11T17:10:21.339293Z","iopub.status.idle":"2023-12-11T17:10:32.873166Z","shell.execute_reply.started":"2023-12-11T17:10:21.339258Z","shell.execute_reply":"2023-12-11T17:10:32.87181Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pydicom\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.optim import Adam\nfrom torch.utils.data import DataLoader, Dataset, WeightedRandomSampler\nfrom ignite.engine import Events, create_supervised_trainer, create_supervised_evaluator\nfrom ignite.metrics import Accuracy, Loss, RunningAverage\nfrom ignite.contrib.handlers import ProgressBar\nfrom sklearn.model_selection import train_test_split\nfrom torchvision import models, transforms","metadata":{"papermill":{"duration":3.715291,"end_time":"2023-01-22T09:59:22.251424","exception":false,"start_time":"2023-01-22T09:59:18.536133","status":"completed"},"tags":[],"id":"d51b515d","execution":{"iopub.status.busy":"2023-12-11T17:10:32.875713Z","iopub.execute_input":"2023-12-11T17:10:32.876567Z","iopub.status.idle":"2023-12-11T17:10:32.883762Z","shell.execute_reply.started":"2023-12-11T17:10:32.876526Z","shell.execute_reply":"2023-12-11T17:10:32.88272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## theoretical background\n\nUntil now, you have played around with Convolutional Neural Networks. For years, they were the dominant force in Computer Vision. Until a groundbreaking paper introduced transformers. Initially introduced for Natural Language Processing tasks, they were then also employed in Computer Vision as well. This is that original paper:\n\n[Attention is All you Need](https://proceedings.neurips.cc/paper_files/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf)\n\nIf you didn't understand to much from that, worry not. Go through below resources as well:\n\n[Basic overview (from an NLP viewpoint)](https://medium.com/inside-machine-learning/what-is-a-transformer-d07dd1fbec04)\n\n[A bit more in depth (again, from an NLP viewpoint)](https://towardsdatascience.com/transformers-141e32e69591)\n\n\nTo get more background on how exactly vision transformers work:\n\n[For visual learners](https://youtu.be/qU7wO02urYU?si=bj8Xj-DG2qDbwdnH)\n\n[Read through this as well](https://www.v7labs.com/blog/vision-transformer-guide)\n\nTransformers found their initial applications in natural language processing (NLP) tasks. To use this NLP model for computer vision tasks, we have to divide our input image into patches. After flattening the patches, we can treat each flattened patches as single word. We add positional embeddings to the linear projection of flattened patches. An extra token is added at the beginning for classification tasks. In BERT model, this token is called [CLS] token.\n\nSo if our input image size is (512, 512), after dividing the image into patches of size (16, 16), we get 1024 (32 times 32) patches. After flattening the patches and projecting the flattened patches, we have 1024 tokens. After adding positional embeddings and concatenating classification token at the beginning, we have 1025 tokens.\n\nWe then feed our tokens into the transformer encoder. Transformer encoder is made up of self attention and feedforward network. This [video](https://www.youtube.com/watch?v=_UVfwBqcnbM) by AssemblyAI explains the transformer architecture beautifully.\n\nThe number of tokens in the output of the transformer encoder is equal to number of input tokens. We take the first token from the output (corresponds to the classification token) and feed the token in a multilayer perceptron head for classification.","metadata":{"papermill":{"duration":0.005588,"end_time":"2023-01-22T09:59:18.497792","exception":false,"start_time":"2023-01-22T09:59:18.492204","status":"completed"},"tags":[],"id":"7cdc0644"}},{"cell_type":"markdown","source":"##dataset info\nThe dataset was contributed by mammography screening programs in Australia and the U.S. It includes detailed labels, with radiologists’ evaluations and follow-up pathology results for suspected malignancies.\n\nThe dataset is stored in dicom formats. Converting dicom data to png/jpg just by rescaling it will harm the quality of the data. [This notebook](https://www.kaggle.com/code/raddar/convert-dicom-to-np-array-the-correct-way/notebook) is an awesome resource for anyone working with dicom files for X-Ray.\n\nTo know more about how dicom images work, go through [this](https://towardsdatascience.com/understanding-dicom-bce665e62b72). They are specially used for X - Ray images.","metadata":{"papermill":{"duration":0.005403,"end_time":"2023-01-22T09:59:18.50875","exception":false,"start_time":"2023-01-22T09:59:18.503347","status":"completed"},"tags":[],"id":"0aed2f4a"}},{"cell_type":"markdown","source":"## utility functions\nWe write some utility functions beforehand. To know how to work with PyDicom funtions, read [this](https://towardsdatascience.com/introducing-pydicom-its-classes-methods-and-attributes-518c1d71162).","metadata":{"papermill":{"duration":0.005416,"end_time":"2023-01-22T09:59:18.530519","exception":false,"start_time":"2023-01-22T09:59:18.525103","status":"completed"},"tags":[],"id":"d95bbc52"}},{"cell_type":"code","source":"def read_xray(file_path, img_size=None):\n    \"\"\"\n    Read the dicom data and get the image\n    Args:\n        file_path: The path of the dicom file\n        img_size: Size of the output image\n    \"\"\"\n\n    dicom = pydicom.read_file(file_path)\n    img = dicom.pixel_array\n\n    if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        img = np.max(img) - img\n\n    if img_size:\n        img = cv2.resize(img, img_size)\n\n    # Add channel dim at First\n    img = img[np.newaxis]\n\n    # Converting img to float32\n    img = img / np.max(img)\n    img = img.astype(\"float32\")\n\n    return img","metadata":{"papermill":{"duration":0.015247,"end_time":"2023-01-22T09:59:22.272633","exception":false,"start_time":"2023-01-22T09:59:22.257386","status":"completed"},"tags":[],"id":"221fb680","execution":{"iopub.status.busy":"2023-12-11T17:10:32.885071Z","iopub.execute_input":"2023-12-11T17:10:32.885401Z","iopub.status.idle":"2023-12-11T17:10:32.899376Z","shell.execute_reply.started":"2023-12-11T17:10:32.88537Z","shell.execute_reply":"2023-12-11T17:10:32.898622Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def patchify(batch, patch_size):\n    \"\"\"\n    Patchify the batch of images\n\n    Shape:\n        batch: (b, h, w, c)\n        output: (b, nh, nw, ph, pw, c)\n    \"\"\"\n    b, c, h, w = batch.shape\n    ph, pw = patch_size\n    nh, nw = h // ph, w // pw\n\n    batch_patches = torch.reshape(batch, (b, c, nh, ph, nw, pw))\n    batch_patches = torch.permute(batch_patches, (0, 1, 2, 4, 3, 5))\n\n    return batch_patches","metadata":{"papermill":{"duration":0.015657,"end_time":"2023-01-22T09:59:22.293761","exception":false,"start_time":"2023-01-22T09:59:22.278104","status":"completed"},"tags":[],"id":"d80572d3","execution":{"iopub.status.busy":"2023-12-11T17:10:32.901363Z","iopub.execute_input":"2023-12-11T17:10:32.901627Z","iopub.status.idle":"2023-12-11T17:10:32.909899Z","shell.execute_reply.started":"2023-12-11T17:10:32.901604Z","shell.execute_reply":"2023-12-11T17:10:32.909114Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We test our `patchify` function on a single image.","metadata":{"papermill":{"duration":0.00537,"end_time":"2023-01-22T09:59:22.30469","exception":false,"start_time":"2023-01-22T09:59:22.29932","status":"completed"},"tags":[],"id":"645afc03"}},{"cell_type":"code","source":"FILE_PATH = ('/kaggle/input/rsna-breast-cancer-detection/'\n             'train_images/10006/1459541791.dcm')\n\nimg = read_xray(FILE_PATH, img_size=(512, 512))\n\nbatch = torch.tensor(img[None])\npatch_size = (16, 16)\nbatch_patches = patchify(batch, patch_size)\n\npatches = batch_patches[0]\nc, nh, nw, ph, pw = patches.shape\n\nplt.figure(figsize=(5, 5))\nplt.imshow(img[0], cmap=\"gray\")\nplt.axis(\"off\")\n\nplt.figure(figsize=(5, 5))\nfor i in range(nh):\n    for j in range(nw):\n        plt.subplot(nh, nw, i * nw + j + 1)\n        plt.imshow(patches[0, i, j], cmap=\"gray\")\n        plt.axis(\"off\")","metadata":{"papermill":{"duration":48.930892,"end_time":"2023-01-22T10:00:11.241361","exception":false,"start_time":"2023-01-22T09:59:22.310469","status":"completed"},"tags":[],"id":"522d21b9","outputId":"92211244-22b1-4e50-fe3d-c0f167d522e8","execution":{"iopub.status.busy":"2023-12-11T17:10:32.910871Z","iopub.execute_input":"2023-12-11T17:10:32.91111Z","iopub.status.idle":"2023-12-11T17:11:12.980804Z","shell.execute_reply.started":"2023-12-11T17:10:32.91109Z","shell.execute_reply":"2023-12-11T17:11:12.979841Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_mlp(in_features, hidden_units, out_features):\n    \"\"\"\n    Returns a MLP head\n    \"\"\"\n    dims = [in_features] + hidden_units + [out_features]\n    layers = []\n    for dim1, dim2 in zip(dims[:-2], dims[1:-1]):\n        layers.append(nn.Linear(dim1, dim2))\n        layers.append(nn.ReLU())\n    layers.append(nn.Linear(dims[-2], dims[-1]))\n    return nn.Sequential(*layers)","metadata":{"papermill":{"duration":0.017061,"end_time":"2023-01-22T10:00:11.2657","exception":false,"start_time":"2023-01-22T10:00:11.248639","status":"completed"},"tags":[],"id":"5b559866","execution":{"iopub.status.busy":"2023-12-11T17:11:12.982225Z","iopub.execute_input":"2023-12-11T17:11:12.982962Z","iopub.status.idle":"2023-12-11T17:11:12.989725Z","shell.execute_reply.started":"2023-12-11T17:11:12.982925Z","shell.execute_reply":"2023-12-11T17:11:12.988648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## image to sequence block\nThis Block takes a batch of image as input and returns a batch of sequences. Later on we feed this sequences into the transformer encoder.","metadata":{"papermill":{"duration":0.006314,"end_time":"2023-01-22T10:00:11.278755","exception":false,"start_time":"2023-01-22T10:00:11.272441","status":"completed"},"tags":[],"id":"6cb491e3"}},{"cell_type":"code","source":"class Img2Seq(nn.Module):\n    \"\"\"\n    This layers takes a batch of images as input and\n    returns a batch of sequences\n\n    Shape:\n        input: (b, h, w, c)\n        output: (b, s, d)\n    \"\"\"\n    def __init__(self, img_size, patch_size, n_channels, d_model):\n        super().__init__()\n        self.patch_size = patch_size\n        self.img_size = img_size\n\n        nh, nw = img_size[0] // patch_size[0], img_size[1] // patch_size[1]\n        n_tokens = nh * nw\n\n        token_dim = patch_size[0] * patch_size[1] * n_channels\n        self.linear = nn.Linear(token_dim, d_model)\n        self.cls_token = nn.Parameter(torch.randn(1, 1, d_model))\n        self.pos_emb = nn.Parameter(torch.randn(n_tokens, d_model))\n\n    def __call__(self, batch):\n        batch = patchify(batch, self.patch_size)\n\n        b, c, nh, nw, ph, pw = batch.shape\n\n        # Flattening the patches\n        batch = torch.permute(batch, [0, 2, 3, 4, 5, 1])\n        batch = torch.reshape(batch, [b, nh * nw, ph * pw * c])\n\n        batch = self.linear(batch)\n        cls = self.cls_token.expand([b, -1, -1])\n        emb = batch + self.pos_emb\n\n        return torch.cat([cls, emb], axis=1)","metadata":{"papermill":{"duration":0.018674,"end_time":"2023-01-22T10:00:11.304195","exception":false,"start_time":"2023-01-22T10:00:11.285521","status":"completed"},"tags":[],"id":"58ae05ba","execution":{"iopub.status.busy":"2023-12-11T17:11:12.990743Z","iopub.execute_input":"2023-12-11T17:11:12.991021Z","iopub.status.idle":"2023-12-11T17:11:13.00468Z","shell.execute_reply.started":"2023-12-11T17:11:12.990988Z","shell.execute_reply":"2023-12-11T17:11:13.00383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## visual transformer module\nThis modules wraps up everything. We can divide this module into 3 parts:\n* An image to sequence encoder\n* Transformer encoder\n* Multilayer perceptron head classification\n\nWe use `torch.nn.TransformerEncoder` and `torch.nn.TransformerEncoderLayer` to implement our transformer encoder. We highly recommend to read the official documentation to [learn more about the layers](https://pytorch.org/docs/stable/generated/torch.nn.TransformerEncoder.html).\nFor the loss function, we will be using [gelu](https://arxiv.org/abs/1606.08415). You can also use this video to [learn about it](https://www.youtube.com/watch?v=FWhMkpo9yuM).","metadata":{"papermill":{"duration":0.006736,"end_time":"2023-01-22T10:00:11.317817","exception":false,"start_time":"2023-01-22T10:00:11.311081","status":"completed"},"tags":[],"id":"26d7647d"}},{"cell_type":"code","source":"class ViT(nn.Module):\n    def __init__(\n        self,\n        img_size,\n        patch_size,\n        n_channels,\n        d_model,\n        nhead,\n        dim_feedforward,\n        blocks,\n        mlp_head_units,\n        n_classes,\n    ):\n        super().__init__()\n        \"\"\"\n        Args:\n            img_size: Size of the image\n            patch_size: Size of the patch\n            n_channels: Number of image channels\n            d_model: The number of features in the transformer encoder\n            nhead: The number of heads in the multiheadattention models\n            dim_feedforward: The dimension of the feedforward network model in the encoder\n            blocks: The number of sub-encoder-layers in the encoder\n            mlp_head_units: The hidden units of mlp_head\n            n_classes: The number of output classes\n        \"\"\"\n        self.img2seq = Img2Seq(img_size, patch_size, n_channels, d_model)\n\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model, nhead, dim_feedforward, activation=\"gelu\", batch_first=True\n        )\n        self.transformer_encoder = nn.TransformerEncoder(\n            encoder_layer, blocks\n        )\n        self.mlp = get_mlp(d_model, mlp_head_units, n_classes)\n\n        self.output = nn.Sigmoid() if n_classes == 1 else nn.Softmax()\n\n    def __call__(self, batch):\n\n        batch = self.img2seq(batch)\n        batch = self.transformer_encoder(batch)\n        batch = batch[:, 0, :]\n        batch = self.mlp(batch)\n        output = self.output(batch)\n        return output","metadata":{"papermill":{"duration":0.018775,"end_time":"2023-01-22T10:00:11.34348","exception":false,"start_time":"2023-01-22T10:00:11.324705","status":"completed"},"tags":[],"id":"00b544d2","execution":{"iopub.status.busy":"2023-12-11T17:11:13.005794Z","iopub.execute_input":"2023-12-11T17:11:13.006141Z","iopub.status.idle":"2023-12-11T17:11:13.019684Z","shell.execute_reply.started":"2023-12-11T17:11:13.00611Z","shell.execute_reply":"2023-12-11T17:11:13.019006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## training\n\nHere, we design a simple training to loop to train or `ViT` model on a subset of dataset. We use an already cropped dataset.","metadata":{"papermill":{"duration":0.006607,"end_time":"2023-01-22T10:00:11.356973","exception":false,"start_time":"2023-01-22T10:00:11.350366","status":"completed"},"tags":[],"id":"e396a0ac"}},{"cell_type":"markdown","source":"## hyperparameters setup","metadata":{"papermill":{"duration":0.007061,"end_time":"2023-01-22T10:00:11.370556","exception":false,"start_time":"2023-01-22T10:00:11.363495","status":"completed"},"tags":[],"id":"829d3a13"}},{"cell_type":"code","source":"img_size = (512, 512)\npatch_size = (16, 16)\nn_channels = 1\nd_model = 1024\nnhead = 4\ndim_feedforward = 2048\nblocks = 8\nmlp_head_units = [1024, 512]\nn_classes = 1\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')","metadata":{"papermill":{"duration":0.137093,"end_time":"2023-01-22T10:00:11.514575","exception":false,"start_time":"2023-01-22T10:00:11.377482","status":"completed"},"tags":[],"id":"6a5ac8c3","execution":{"iopub.status.busy":"2023-12-11T17:11:13.020725Z","iopub.execute_input":"2023-12-11T17:11:13.020996Z","iopub.status.idle":"2023-12-11T17:11:13.03484Z","shell.execute_reply.started":"2023-12-11T17:11:13.020974Z","shell.execute_reply":"2023-12-11T17:11:13.034011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## dataset loading\nWe will be using the [CLAHE](https://www.geeksforgeeks.org/clahe-histogram-eqalization-opencv/) algorithm to improve contrast between the tiled images. Apart from this, we are using the pytorch data handling modules like DataLoaded which you can read about [here](https://pytorch.org/docs/stable/data.html).","metadata":{"papermill":{"duration":0.007039,"end_time":"2023-01-22T10:00:11.528747","exception":false,"start_time":"2023-01-22T10:00:11.521708","status":"completed"},"tags":[],"id":"4587c40c"}},{"cell_type":"code","source":"class RSNADataset(Dataset):\n\n    def __init__(self, df, img_path):\n        self.df = df\n        self.img_path = img_path\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        patient_id, image_id, cancer = self.df.iloc[idx][['patient_id', 'image_id', 'cancer']]\n        file = os.path.join(self.img_path, f'{patient_id}_{image_id}.png')\n        file = cv2.imread(file, cv2.COLOR_BGR2GRAY)\n        clahe = cv2.createCLAHE(clipLimit = 15, tileGridSize=[8, 8])\n        file = clahe.apply(file)\n        file = file / file.max()\n        X = torch.tensor(file[np.newaxis].astype('float32')).to(device)\n        y = torch.tensor([cancer]).float().to(device)\n        return X, y","metadata":{"papermill":{"duration":0.018483,"end_time":"2023-01-22T10:00:11.554179","exception":false,"start_time":"2023-01-22T10:00:11.535696","status":"completed"},"tags":[],"id":"8349a5ae","execution":{"iopub.status.busy":"2023-12-11T17:11:13.037465Z","iopub.execute_input":"2023-12-11T17:11:13.037739Z","iopub.status.idle":"2023-12-11T17:11:13.046765Z","shell.execute_reply.started":"2023-12-11T17:11:13.037716Z","shell.execute_reply":"2023-12-11T17:11:13.045697Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/rsna-breast-cancer-detection/train.csv')\ncounts = df['cancer'].value_counts()\ndf['weights'] = df['cancer'].apply(lambda x: 1/counts[x])\n\ntrain_df, val_df = train_test_split(df, test_size=0.3, stratify=df['cancer'])","metadata":{"papermill":{"duration":0.432908,"end_time":"2023-01-22T10:00:11.993993","exception":false,"start_time":"2023-01-22T10:00:11.561085","status":"completed"},"tags":[],"id":"c8cdd3ff","execution":{"iopub.status.busy":"2023-12-11T17:11:13.047719Z","iopub.execute_input":"2023-12-11T17:11:13.04802Z","iopub.status.idle":"2023-12-11T17:11:13.461807Z","shell.execute_reply.started":"2023-12-11T17:11:13.047997Z","shell.execute_reply":"2023-12-11T17:11:13.460937Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_path = '/kaggle/input/rsna-breast-cancer-512-pngs'\ntrain_samples = 1000\nval_samples = 500\n\ntrain_ds = RSNADataset(train_df, img_path)\nval_ds = RSNADataset(val_df, img_path)\n\ntrain_sampler = WeightedRandomSampler(train_df['weights'].values, train_samples)\ntrain_loader = DataLoader(train_ds, batch_size=8, sampler=train_sampler)\n\nval_sampler = WeightedRandomSampler(val_df['weights'].values, val_samples)\nval_loader = DataLoader(val_ds, batch_size=32, sampler=val_sampler)","metadata":{"papermill":{"duration":0.018775,"end_time":"2023-01-22T10:00:12.019912","exception":false,"start_time":"2023-01-22T10:00:12.001137","status":"completed"},"tags":[],"id":"b2c20004","execution":{"iopub.status.busy":"2023-12-11T17:11:13.462863Z","iopub.execute_input":"2023-12-11T17:11:13.463139Z","iopub.status.idle":"2023-12-11T17:11:13.470132Z","shell.execute_reply.started":"2023-12-11T17:11:13.463116Z","shell.execute_reply":"2023-12-11T17:11:13.469199Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## model creation and training start","metadata":{"papermill":{"duration":0.007287,"end_time":"2023-01-22T10:00:12.034465","exception":false,"start_time":"2023-01-22T10:00:12.027178","status":"completed"},"tags":[],"id":"87621c6e"}},{"cell_type":"code","source":"model = ViT(\n    img_size = (512, 512),\n    patch_size = (16, 16),\n    n_channels = 1,\n    d_model = 1024,\n    nhead = 4,\n    dim_feedforward = 1024,\n    blocks = 8,\n    mlp_head_units = [512, 512],\n    n_classes = 1,\n).to(device)\n\noptimizer = Adam(model.parameters())\ncriterion = nn.BCELoss()\n\ntrainer = create_supervised_trainer(model, optimizer, criterion, device=device)\nval_metrics = {\n    \"bce\": Loss(criterion)\n}\nevaluator = create_supervised_evaluator(model, metrics=val_metrics, device=device)","metadata":{"papermill":{"duration":3.667453,"end_time":"2023-01-22T10:00:15.709219","exception":false,"start_time":"2023-01-22T10:00:12.041766","status":"completed"},"tags":[],"id":"44c7f2df","execution":{"iopub.status.busy":"2023-12-11T17:11:13.471232Z","iopub.execute_input":"2023-12-11T17:11:13.471491Z","iopub.status.idle":"2023-12-11T17:11:13.660997Z","shell.execute_reply.started":"2023-12-11T17:11:13.471469Z","shell.execute_reply":"2023-12-11T17:11:13.660025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"log_interval = 10\nmax_epochs = 5\nbest_loss = float('inf')\n\nRunningAverage(output_transform=lambda x: x).attach(trainer, 'loss')\n\npbar = ProgressBar()\npbar.attach(trainer, ['loss'])\n\n@trainer.on(Events.EPOCH_COMPLETED)\ndef log_validation_results(trainer):\n    global best_loss\n    evaluator.run(val_loader)\n    loss = evaluator.state.metrics['bce']\n    if loss < best_loss:\n        best_loss = loss\n        torch.save(model.state_dict(), 'best_model_vit.pt')\n    print(f\"Validation Results - Epoch: {trainer.state.epoch} Avg loss: {loss:.2f}\")\n\noutput_state = trainer.run(train_loader, max_epochs=max_epochs)","metadata":{"papermill":{"duration":848.375644,"end_time":"2023-01-22T10:14:24.091936","exception":false,"start_time":"2023-01-22T10:00:15.716292","status":"completed"},"tags":[],"id":"081e32ba","execution":{"iopub.status.busy":"2023-12-11T17:11:13.662192Z","iopub.execute_input":"2023-12-11T17:11:13.662473Z","iopub.status.idle":"2023-12-11T17:24:32.032098Z","shell.execute_reply.started":"2023-12-11T17:11:13.662449Z","shell.execute_reply":"2023-12-11T17:24:32.031159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **End of Task**\n\n> ©Synapse 2023 - 2024\n\n","metadata":{}}]}