{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":73479,"databundleVersionId":8152410,"sourceType":"competition"}],"dockerImageVersionId":30684,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Load Data","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\nBASE_PATH = '/kaggle/input/byu-locating-bacterial-flagellar-motors-v2'\ndf = pd.read_csv(f'{BASE_PATH}/train.csv')\nsubmission_df = pd.read_csv(f'{BASE_PATH}/sample_submission.csv')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-11T02:22:06.066163Z","iopub.execute_input":"2024-04-11T02:22:06.066515Z","iopub.status.idle":"2024-04-11T02:22:07.178208Z","shell.execute_reply.started":"2024-04-11T02:22:06.066486Z","shell.execute_reply":"2024-04-11T02:22:07.177120Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Visualize dataframe\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:22:10.656533Z","iopub.execute_input":"2024-04-11T02:22:10.656915Z","iopub.status.idle":"2024-04-11T02:22:10.674386Z","shell.execute_reply.started":"2024-04-11T02:22:10.656885Z","shell.execute_reply":"2024-04-11T02:22:10.673136Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ncount_labels = df['motor_visible'].value_counts()\n\nplt.bar(count_labels.index, count_labels.values)\nplt.xlabel('Categories')\nplt.ylabel('Counts')\nplt.title('Motor Visibility')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-04-09T21:57:52.230911Z","iopub.execute_input":"2024-04-09T21:57:52.231335Z","iopub.status.idle":"2024-04-09T21:57:52.535470Z","shell.execute_reply.started":"2024-04-09T21:57:52.231306Z","shell.execute_reply":"2024-04-09T21:57:52.534268Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h3> The training dataset is properly balanced 😄 </h3>","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\nbin_size = 30\n\n# Create bins every bin_size for x and y\nbins_x = np.arange(0, df['x'].max() + 50, bin_size)\nbins_y = np.arange(0, df['y'].max() + 50, bin_size)\n\n\n# Calculate 2D histogram counts\ncounts, x_edges, y_edges = np.histogram2d(df['x'], df['y'], bins=[bins_x, bins_y])\n\n# Plotting\nplt.figure(figsize=(10, 6))\nplt.hist2d(df['x'], df['y'], bins=[bins_x, bins_y], cmap='viridis')\nplt.colorbar(label='Counts')\nplt.xlabel('X')\nplt.ylabel('Y')\nplt.title('Distribution of X and Y values')\nplt.grid(True)\n\n# Add dashed red line at y=300\nplt.axhline(y=300, color='red', linestyle='--')\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-04-09T23:06:19.056466Z","iopub.execute_input":"2024-04-09T23:06:19.056946Z","iopub.status.idle":"2024-04-09T23:06:19.406006Z","shell.execute_reply.started":"2024-04-09T23:06:19.056905Z","shell.execute_reply":"2024-04-09T23:06:19.405015Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4> Nothing especial, only the locations are well distributed over the image too. With a higher frequency for y values above 300. 🤔</h4>","metadata":{}},{"cell_type":"markdown","source":"# Preprocessing Operations","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import StandardScaler\nfrom PIL import Image\n\nimport torchvision.transforms.functional as TF\nimport matplotlib.pyplot as plt\nimport random\nimport math\n\nclass Transforms():\n    \n    def __init__(self):\n        # Normalize our positions in range between [-1, 1] like our linear nn output\n        self.scaler = StandardScaler()\n        self.scaler.fit([[-1, -1], [512, 512]])\n        \n    def normalize(self, image, position, size):\n        return TF.normalize(image, [0.5], [0.5]), self.scaler.transform(np.array([position]))[0]\n\n    def __call__(self, image, position, size=224):\n        image = Image.fromarray(image)\n        image = TF.resize(image, size)\n        image = TF.to_tensor(image)\n        image, position = self.normalize(image, position, size)\n        return image, position","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:22:20.347479Z","iopub.execute_input":"2024-04-11T02:22:20.348130Z","iopub.status.idle":"2024-04-11T02:22:29.384411Z","shell.execute_reply.started":"2024-04-11T02:22:20.348095Z","shell.execute_reply":"2024-04-11T02:22:29.383481Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4> The images are rescale to 224 width and height and the position of the bacteria was normalized between [-1, 1] for the neural network prediction </h4>","metadata":{}},{"cell_type":"markdown","source":"# Preparing Data","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset\nimport torch\nimport cv2\nimport os\n\nclass BYUDataset(Dataset):\n    def __init__(self, data_dir, df, transform=None):\n        self.positions = df[['x','y']].values\n        self.data_dir = data_dir\n        self.transform = transform\n        self.image_paths = [os.path.join(data_dir, img_name) for img_name in df['image_name'].values]\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        position = self.positions[idx]\n        original_img = cv2.imread(img_path)\n\n        if self.transform:\n            image, position = self.transform(original_img, position)\n\n        return image, torch.FloatTensor(position), img_path","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:22:29.618790Z","iopub.execute_input":"2024-04-11T02:22:29.619100Z","iopub.status.idle":"2024-04-11T02:22:29.627984Z","shell.execute_reply.started":"2024-04-11T02:22:29.619073Z","shell.execute_reply":"2024-04-11T02:22:29.626564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Spliting Data","metadata":{}},{"cell_type":"markdown","source":"<h4> This validation split was used to visualize the models performance in a unseen data before the submission by giving up some training samples.</h4>","metadata":{}},{"cell_type":"code","source":"transform = Transforms()\n\ntrain_size = int(len(df) * 0.8)\ntrain_df, valid_df = df[:train_size], df[train_size:]\ntrain_dataset = BYUDataset(\n    '/kaggle/input/byu-locating-bacterial-flagellar-motors-v2/train', \n    train_df, \n    transform=transform\n)\n\nvalid_dataset = BYUDataset(\n    '/kaggle/input/byu-locating-bacterial-flagellar-motors-v2/train', \n    valid_df, \n    transform=transform\n)\n\nprint(f'Train Size: {len(train_df)}, Validation Size:{len(valid_df)}')","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:22:29.740717Z","iopub.execute_input":"2024-04-11T02:22:29.741143Z","iopub.status.idle":"2024-04-11T02:22:29.766950Z","shell.execute_reply.started":"2024-04-11T02:22:29.741112Z","shell.execute_reply":"2024-04-11T02:22:29.765785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# My dataset is returning the tensor image and the location of the flagellar motor\n# With the location you can deduce the label \ntrain_dataset[1]","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:22:33.607710Z","iopub.execute_input":"2024-04-11T02:22:33.608105Z","iopub.status.idle":"2024-04-11T02:22:33.773861Z","shell.execute_reply.started":"2024-04-11T02:22:33.608076Z","shell.execute_reply":"2024-04-11T02:22:33.772773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Architecture","metadata":{}},{"cell_type":"code","source":"from torch import nn\n\nclass AlexNet(nn.Module):\n    def __init__(self, num_classes=2):\n        super(AlexNet, self).__init__()\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n\n            nn.Conv2d(64, 192, kernel_size=5, padding=2),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n\n            nn.Conv2d(192, 384, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(384, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(256, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n        )\n        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))\n        self.classifier = nn.Sequential(\n            nn.Dropout(),\n            nn.Linear(256 * 6 * 6, 4096),\n            nn.ReLU(inplace=True),\n            nn.Dropout(),\n            nn.Linear(4096, 4096),\n            nn.ReLU(inplace=True),\n            nn.Linear(4096, num_classes),\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.classifier(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:26:35.280476Z","iopub.execute_input":"2024-04-11T02:26:35.281069Z","iopub.status.idle":"2024-04-11T02:26:35.293792Z","shell.execute_reply.started":"2024-04-11T02:26:35.281040Z","shell.execute_reply":"2024-04-11T02:26:35.292646Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Setting Hiperparameters","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nnum_classes = 2 # x and y position\nnum_epochs = 25 # iterations over the data\nbatch_size = 16 \nlearning_rate = 0.01\n\n#model = ResNet34(ResidualBlock, [3, 4, 6, 3]).to(device)\nmodel = AlexNet().to(device)\n\n# Loss and optimizer\ncriterion = nn.MSELoss()\noptimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.001, momentum = 0.9)\n\ntrain_dataloader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size)\nvalid_dataloader = torch.utils.data.DataLoader(valid_dataset,batch_size=batch_size, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:26:55.584797Z","iopub.execute_input":"2024-04-11T02:26:55.585522Z","iopub.status.idle":"2024-04-11T02:26:56.261839Z","shell.execute_reply.started":"2024-04-11T02:26:55.585488Z","shell.execute_reply":"2024-04-11T02:26:56.260927Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"import time\n\nbest_epoch = 0\nbest_loss = np.inf\n\nprint('Epoch | Training Loss | Validation Loss | Duration')\nprint('------+---------------+-----------------+---------')\n\nfor epoch in range(num_epochs):\n    model.train()  # Set the model to training mode\n    train_loss = 0.0\n    start_time = time.time()\n\n    for inputs, targets, _ in train_dataloader:\n        inputs, targets = inputs.to(device), targets.to(device)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n\n    # Validation loop\n    model.eval()  # Set the model to evaluation mode\n    val_loss = 0.0\n\n    with torch.no_grad():\n        for inputs, targets, _ in valid_dataloader:\n            inputs, targets = inputs.to(device), targets.to(device)\n            outputs = model(inputs)\n            val_loss += criterion(outputs, targets).item()\n\n\n    train_loss /= len(train_dataloader)\n    val_loss /= len(valid_dataloader)\n    duration = (time.time() - start_time)/60\n    \n    if best_loss > val_loss:\n        best_loss = val_loss\n        best_epoch = epoch\n\n        # Save the trained model in the best epoch\n        torch.save(model.state_dict(), 'AlexNet_BYU.pth')\n\n    print(f'{str(epoch + 1).zfill(2)}    | {train_loss:.4f}        | {val_loss:.4f}          |  {duration:.2f}m')","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:26:58.016397Z","iopub.execute_input":"2024-04-11T02:26:58.017203Z","iopub.status.idle":"2024-04-11T02:38:30.921448Z","shell.execute_reply.started":"2024-04-11T02:26:58.017168Z","shell.execute_reply":"2024-04-11T02:38:30.920318Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4> I Recommend train with GPU. Also i had better results with Alex Net instead Resnet34</h4>","metadata":{}},{"cell_type":"markdown","source":"# Visualizing","metadata":{}},{"cell_type":"code","source":"# Loading Best Saved Model if needed in my case my best result was in epoch 22\nmodel = AlexNet().to(device)\nmodel.load_state_dict(torch.load('/kaggle/working/AlexNet_BYU.pth'))\nmodel.eval()","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:45:42.815876Z","iopub.execute_input":"2024-04-11T02:45:42.816243Z","iopub.status.idle":"2024-04-11T02:45:43.632372Z","shell.execute_reply.started":"2024-04-11T02:45:42.816214Z","shell.execute_reply":"2024-04-11T02:45:43.631407Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with torch.no_grad():\n    \n    # Get first batch\n    inputs, targets, imgs_path = next(iter(valid_dataloader))\n    inputs, targets = inputs.to(device), targets.to(device)\n    \n    # Predict Values\n    outputs = model(inputs)\n\n    # Convert to NumPy arrays\n    inputs_np = inputs.cpu().numpy()\n    targets_np = targets.cpu().numpy()\n    outputs_np = outputs.cpu().numpy()\n    \n    # Create Plot to visualize our results\n    fig, axs = plt.subplots(4, 4, figsize=(12, 12))\n    \n    # Draw subplots\n    for i, ax in enumerate(axs.flat):\n        if i < len(inputs_np):\n            img = cv2.imread(imgs_path[i])\n            ax.imshow(img)\n\n            # Rescale from [-1, 1] to Orignal size\n            targets_np[i] = transform.scaler.inverse_transform([targets_np[i]])[0]\n            outputs_np[i] = transform.scaler.inverse_transform([outputs_np[i]])[0]\n            \n            # Draw Points\n            ax.scatter(targets_np[i, 0], targets_np[i, 1], label='Target', color='blue', marker='o')\n            ax.scatter(outputs_np[i, 0], outputs_np[i, 1], label='Output', color='red', marker='x')\n            ax.set_title(f'Sample {os.path.basename(imgs_path[i])}')\n            ax.axis('off')\n\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:45:48.247462Z","iopub.execute_input":"2024-04-11T02:45:48.247837Z","iopub.status.idle":"2024-04-11T02:45:50.420085Z","shell.execute_reply.started":"2024-04-11T02:45:48.247807Z","shell.execute_reply":"2024-04-11T02:45:50.419163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"test_dataset = BYUDataset(\n    '/kaggle/input/byu-locating-bacterial-flagellar-motors-v2/val', \n    submission_df, \n    transform=transform\n)\ntest_dataloader = torch.utils.data.DataLoader(test_dataset,batch_size=batch_size, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:46:10.393437Z","iopub.execute_input":"2024-04-11T02:46:10.394185Z","iopub.status.idle":"2024-04-11T02:46:10.403560Z","shell.execute_reply.started":"2024-04-11T02:46:10.394152Z","shell.execute_reply":"2024-04-11T02:46:10.402226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tqdm.notebook import tqdm\n\nwith torch.no_grad():\n    predictions = []\n    for inputs, targets, _ in tqdm(test_dataloader):\n        inputs, targets = inputs.to(device), targets.to(device)\n        outputs = model(inputs).cpu().numpy()\n        for output in outputs:\n            predictions.append(output)\n    predictions = transform.scaler.inverse_transform(predictions)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:46:13.885324Z","iopub.execute_input":"2024-04-11T02:46:13.886042Z","iopub.status.idle":"2024-04-11T02:46:23.741732Z","shell.execute_reply.started":"2024-04-11T02:46:13.886010Z","shell.execute_reply":"2024-04-11T02:46:23.740729Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"result = submission_df.copy()\nresult[['x','y']] = predictions\ndef classify(row):\n    if row['x'] < 15 or row['y'] < 15:\n        return 0\n    else:\n        return 1\n\nresult['motor_visible'] = result.apply(classify, axis=1)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:47:03.452393Z","iopub.execute_input":"2024-04-11T02:47:03.453291Z","iopub.status.idle":"2024-04-11T02:47:03.480495Z","shell.execute_reply.started":"2024-04-11T02:47:03.453254Z","shell.execute_reply":"2024-04-11T02:47:03.479372Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"result.to_csv('/kaggle/working/result.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:47:51.521098Z","iopub.execute_input":"2024-04-11T02:47:51.521818Z","iopub.status.idle":"2024-04-11T02:47:51.533057Z","shell.execute_reply.started":"2024-04-11T02:47:51.521777Z","shell.execute_reply":"2024-04-11T02:47:51.532156Z"},"trusted":true},"execution_count":null,"outputs":[]}]}