{"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":45867,"databundleVersionId":6924515,"sourceType":"competition"}],"dockerImageVersionId":30588,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Notebook for fitting a CNN to UBC-OCEAN images using Adam\n\nRuns in kaggle environment with UBC-OCEAN competition\n\nAssumes images are held in the UBC-OCEAN/train_thumbnails/ directory and are indexed in train.csv","metadata":{}},{"cell_type":"code","source":"!pip install torchsummary","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:55:15.696168Z","iopub.execute_input":"2023-12-05T02:55:15.696882Z","iopub.status.idle":"2023-12-05T02:55:28.185431Z","shell.execute_reply.started":"2023-12-05T02:55:15.696848Z","shell.execute_reply":"2023-12-05T02:55:28.184364Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\n\nimport torch\nimport torchvision\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.io import read_image\nfrom torchvision.transforms import Resize, Normalize\nfrom torchsummary import summary\n\nimport matplotlib.pyplot as plt\nimport time\nfrom tqdm import tqdm","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:10.472418Z","iopub.execute_input":"2023-12-05T02:56:10.472807Z","iopub.status.idle":"2023-12-05T02:56:12.571726Z","shell.execute_reply.started":"2023-12-05T02:56:10.472775Z","shell.execute_reply":"2023-12-05T02:56:12.57084Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nprint(device)\n\nimg_shape = (224, 224) \nmeans = (0.485, 0.456, 0.406)\nstds = (0.229, 0.224, 0.225)\n\nbatch_size = 16\n\n# model size hyperparameters\nnum_layers = 4\nhidden_dim = 32\n\n# training hyperparameters\nlr = 1e-4\nepochs = 15","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:14.440102Z","iopub.execute_input":"2023-12-05T02:56:14.441022Z","iopub.status.idle":"2023-12-05T02:56:14.468165Z","shell.execute_reply.started":"2023-12-05T02:56:14.440988Z","shell.execute_reply":"2023-12-05T02:56:14.467339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/UBC-OCEAN/train.csv')","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:16.223868Z","iopub.execute_input":"2023-12-05T02:56:16.224601Z","iopub.status.idle":"2023-12-05T02:56:16.243242Z","shell.execute_reply.started":"2023-12-05T02:56:16.224569Z","shell.execute_reply":"2023-12-05T02:56:16.242536Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"num_classes = len(pd.unique(df['label']))","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:17.766845Z","iopub.execute_input":"2023-12-05T02:56:17.767529Z","iopub.status.idle":"2023-12-05T02:56:17.777784Z","shell.execute_reply.started":"2023-12-05T02:56:17.767495Z","shell.execute_reply":"2023-12-05T02:56:17.776853Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# read images from kaggle repository structure\nclass CancerDataset(Dataset):\n    \n    def __init__(self, df, img_dir='/kaggle/input/UBC-OCEAN/train_thumbnails/'):\n        self.df = df[df['is_tma'] == False]\n        self.img_dir = img_dir\n        \n        self.label_encoding = {\n            'HGSC': 0, \n            'LGSC': 1,\n            'EC': 2,\n            'CC': 3,\n            'MC': 4\n        }\n        \n        self.resize = Resize(size=img_shape)\n        self.normalize = Normalize(means, stds)\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        \n        row = self.df.iloc[idx]\n        \n        path = os.path.join(self.img_dir, str(row['image_id']) + '_thumbnail.png')\n        img = read_image(path)\n        img = self.resize(img)\n        img = torch.div(img, torch.tensor(255.))\n        img = self.normalize(img)\n        img = img.to(torch.float32)\n        img = img.to(device)\n        \n        label = row['label']\n        label_hot = np.zeros(num_classes)\n        label_hot[self.label_encoding[label]] = 1\n        label_hot = torch.tensor(label_hot).to(device)\n        \n        return img, label_hot","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:19.135056Z","iopub.execute_input":"2023-12-05T02:56:19.135777Z","iopub.status.idle":"2023-12-05T02:56:19.144781Z","shell.execute_reply.started":"2023-12-05T02:56:19.135747Z","shell.execute_reply":"2023-12-05T02:56:19.143936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds = CancerDataset(df)","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:21.443197Z","iopub.execute_input":"2023-12-05T02:56:21.443569Z","iopub.status.idle":"2023-12-05T02:56:21.45232Z","shell.execute_reply.started":"2023-12-05T02:56:21.443533Z","shell.execute_reply":"2023-12-05T02:56:21.45138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loader = DataLoader(ds, batch_size=batch_size)","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:23.065596Z","iopub.execute_input":"2023-12-05T02:56:23.065928Z","iopub.status.idle":"2023-12-05T02:56:23.070655Z","shell.execute_reply.started":"2023-12-05T02:56:23.065905Z","shell.execute_reply":"2023-12-05T02:56:23.069663Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"# create a CNN from previously declared hyperparameters\ndef create_model():\n\n    layers = []\n\n    input_channels = 3\n    output_channels = hidden_dim\n\n    kernel_size = 3\n    stride_length = 1\n\n    image_size = img_shape[0]\n\n    for i in range(num_layers):\n        layers.append(\n            nn.Conv2d(\n                in_channels=input_channels,\n                out_channels=output_channels,\n                kernel_size=kernel_size,\n                stride=stride_length\n            )\n        )\n        layers.append(nn.BatchNorm2d(output_channels))\n        layers.append(nn.ReLU())\n        image_size = int((image_size - kernel_size)/stride_length) + 1\n        input_channels = output_channels\n\n    layers.append(nn.Flatten())\n    layers.append(nn.Linear(image_size ** 2 * input_channels, num_classes))\n\n    model = nn.Sequential(*layers)\n        \n    model.to(device)\n    return model","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:24.474162Z","iopub.execute_input":"2023-12-05T02:56:24.474577Z","iopub.status.idle":"2023-12-05T02:56:24.482473Z","shell.execute_reply.started":"2023-12-05T02:56:24.474545Z","shell.execute_reply":"2023-12-05T02:56:24.481419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train","metadata":{}},{"cell_type":"code","source":"loss = torch.nn.CrossEntropyLoss()","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:41.117894Z","iopub.execute_input":"2023-12-05T02:56:41.118308Z","iopub.status.idle":"2023-12-05T02:56:41.122957Z","shell.execute_reply.started":"2023-12-05T02:56:41.118277Z","shell.execute_reply":"2023-12-05T02:56:41.122021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# train a model some number of times\nnum_trials = 1\n\nepoch_losses = []\ntime_per_epoch = []\n\nfor trial in range(num_trials):\n    \n    # Initialize model and optimizer (with regularization)\n    model = create_model()\n    optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5)\n\n    epoch_losses.append([])\n    time_per_epoch.append([])\n    \n    # train loop\n    for epoch in range(epochs):\n\n        total_loss = 0\n\n        start_time = time.time()\n\n        for (imgs, labels) in tqdm(loader):\n            optimizer.zero_grad()\n            outs = model(imgs)\n            l = loss(outs, labels)\n            l.backward()\n            optimizer.step()\n            total_loss += l.item()\n\n        end_time = time.time()\n        time_per_epoch[-1].append(end_time - start_time)\n\n        epoch_loss = total_loss / len(loader)\n        epoch_losses[-1].append(epoch_loss)\n        print(f'Epoch {epoch} loss: {epoch_loss}')","metadata":{"execution":{"iopub.status.busy":"2023-12-05T02:56:49.886934Z","iopub.execute_input":"2023-12-05T02:56:49.887656Z","iopub.status.idle":"2023-12-05T03:19:38.777143Z","shell.execute_reply.started":"2023-12-05T02:56:49.887624Z","shell.execute_reply":"2023-12-05T03:19:38.775847Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f'Average time per epoch: {sum([sum(i) for i in time_per_epoch]) / len(time_per_epoch) / len(time_per_epoch[0])} seconds')","metadata":{"execution":{"iopub.status.busy":"2023-12-05T03:20:23.800306Z","iopub.execute_input":"2023-12-05T03:20:23.801411Z","iopub.status.idle":"2023-12-05T03:20:23.806527Z","shell.execute_reply.started":"2023-12-05T03:20:23.801346Z","shell.execute_reply":"2023-12-05T03:20:23.805575Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# To show model architecture and params\nsummary(model, (3,224, 224))","metadata":{"execution":{"iopub.status.busy":"2023-12-05T03:20:25.488962Z","iopub.execute_input":"2023-12-05T03:20:25.489307Z","iopub.status.idle":"2023-12-05T03:20:25.510754Z","shell.execute_reply.started":"2023-12-05T03:20:25.489279Z","shell.execute_reply":"2023-12-05T03:20:25.509793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plot loss across epochs\ncolors = ['b', 'r', 'c', 'g', 'm']\n\nxs = [i+1 for i in range(epochs)]\nfor i in range(min(num_trials, 5)):\n    plt.plot(xs, epoch_losses[i], color=colors[i])\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss across training epochs')\nplt.savefig('loss.png')","metadata":{"execution":{"iopub.status.busy":"2023-12-05T03:20:52.51682Z","iopub.execute_input":"2023-12-05T03:20:52.517562Z","iopub.status.idle":"2023-12-05T03:20:52.847285Z","shell.execute_reply.started":"2023-12-05T03:20:52.517531Z","shell.execute_reply":"2023-12-05T03:20:52.846294Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# count number of parameters in a model with magnitudes >= 1\n    # i.e. number of parameters outside of constraints\nfor param in model.parameters():\n    mags = torch.abs(param)\n    flags = (mags >= 1)\n    print(torch.count_nonzero(flags))","metadata":{"execution":{"iopub.status.busy":"2023-12-05T03:21:00.803219Z","iopub.execute_input":"2023-12-05T03:21:00.804082Z","iopub.status.idle":"2023-12-05T03:21:00.821975Z","shell.execute_reply.started":"2023-12-05T03:21:00.804049Z","shell.execute_reply":"2023-12-05T03:21:00.821013Z"},"trusted":true},"execution_count":null,"outputs":[]}]}