{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":132097,"databundleVersionId":15841209}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Импорт библиотек","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nimport os\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport torchvision\nfrom torchvision.datasets import ImageFolder\n\nfrom torch.utils.data.dataloader import DataLoader\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import Subset\n\nimport matplotlib.pyplot as plt\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport random\nfrom PIL import Image\n\n%matplotlib inline\ntorch.manual_seed(0)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Проверка доступа к GPU","metadata":{}},{"cell_type":"code","source":"if torch.cuda.is_available():\n    device=torch.device(\"cuda:0\")\n    print(\"Training on GPU...\")\nelse:\n    device = torch.device(\"cpu\")\n    print(\"Training on CPU...\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Преобразования (Transforms)","metadata":{}},{"cell_type":"code","source":"# Creating a Transformation Object\ntrain_transform = torchvision.transforms.Compose([\n    # Converting images to the size that the model expects\n    torchvision.transforms.Resize(size=(224, 224)),\n    torchvision.transforms.RandomHorizontalFlip(), # A RandomHorizontalFlip to augment our data\n    torchvision.transforms.ToTensor(), # Converting to tensor\n    # Добавьте необходимую нормализацию,\n    # если будете применять предобученную модель, например, ResNet18\n    # torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                                     # std=[0.229, 0.224, 0.225]) # Normalizing the data to the data that the ResNet18 was trained on\n    \n])\n\n\nval_transform = torchvision.transforms.Compose([\n    # Converting images to the size that the model expects\n    torchvision.transforms.Resize(size=(224, 224)),\n    torchvision.transforms.ToTensor(), # Converting to tensor\n    # Добавьте необходимую нормализацию,\n    # если будете применять предобученную модель, например, ResNet18\n    # torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],\n    #                                  std=[0.229, 0.224, 0.225]) # Normalizing the data to the data that the ResNet18 was trained on\n    \n])\n\n\ntest_transform = torchvision.transforms.Compose([\n    # Converting images to the size that the model expects\n    torchvision.transforms.Resize(size=(224, 224)),\n    torchvision.transforms.ToTensor(), # Converting to tensor\n    # Добавьте необходимую нормализацию,\n    # если будете применять предобученную модель, например, ResNet18\n    # torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],\n    #                                  std=[0.229, 0.224, 0.225]) # Normalizing the data to the data that the ResNet18 was trained on\n    \n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Создание объектов Datasets с разбиением выборки на обучающую и валидационную","metadata":{}},{"cell_type":"code","source":"train_val_path=\"/kaggle/input/optical-coherence-tomography-classification/Dataset\"\n\ntrain_dataset = ImageFolder(train_val_path, transform=train_transform)\nval_dataset = ImageFolder(train_val_path, transform=val_transform)\n\nclass_names = train_dataset.classes\nprint(class_names) # list out all the classes","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Splitting the data into train and validation set\ndef split_train_val(tot_img, val_percentage=0.2, rnd=23):\n    # Here indices are randomly permuted \n    number_of_val = int(tot_img*val_percentage)\n    \n    np.random.seed(rnd)\n    indexs = np.random.permutation(tot_img)\n    return indexs[0:number_of_val], indexs[number_of_val:]\n\nrandomness = 1\nval_per = 0.2\n\nall_len = len(train_dataset)\n\nval_indices, train_indices = split_train_val(all_len, val_per, randomness)\n\nprint(val_indices, \"validation data:\", val_indices.shape)\nprint(train_indices, \"train data:\", train_indices.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = Subset(train_dataset, train_indices)\nval_dataset = Subset(val_dataset, val_indices)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img0, label0 = train_dataset[9627]\nprint(img0.shape, label0)\n\nimg1,label1 = val_dataset[20]\nprint(img1.shape, label1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show(img, label):\n    print(\"label-->\",class_names[label])\n    img = img.numpy().transpose((1, 2, 0)) # Channel first then height and width\n    # Если вы применили нормализацию, например, для ResNet18,\n    # то для визуализации и корректного отображения нужно сделать обратные преобразования\n    # mean = np.array([0.485, 0.456, 0.406])\n    # std = np.array([0.229, 0.224, 0.225])\n    # img = img * std + mean\n    # img = np.clip(img, 0., 1.)\n    plt.imshow(img)\n\nshow(*train_dataset[6])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Dataloaders","metadata":{}},{"cell_type":"code","source":"batch_size = 256\n\ntrain_dataloader = DataLoader(train_dataset, batch_size, shuffle=True)\nval_dataloader = DataLoader(val_dataset, batch_size, shuffle=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Создание модели","metadata":{}},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n    def __init__(self):\n        super(SimpleCNN, self).__init__()\n        # Convolutional layers\n        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)\n        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1)\n        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n\n        # Fully connected layers\n        self.fc1 = nn.Linear(64 * 28 * 28, 128)\n        self.fc2 = nn.Linear(128, 4)  # Output size: 4 classes\n\n    def forward(self, x):\n        # current x.shape [batch_size, 3, 224, 224]\n        # First convolutional layer with ReLU and max pooling\n        x = F.relu(self.conv1(x))\n        # current x.shape [batch_size, 16, 224, 224]\n        x = F.max_pool2d(x, kernel_size=2, stride=2)  # Output size: (16, 112, 112)\n        # current x.shape [batch_size, 16, 112, 112]\n\n        # Second convolutional layer with ReLU and max pooling\n        x = F.relu(self.conv2(x))\n        # current x.shape [batch_size, 32, 112, 112]\n        x = F.max_pool2d(x, kernel_size=2, stride=2)  # Output size: (32, 56, 56)\n        # current x.shape [batch_size, 32, 56, 56]\n\n        # Third convolutional layer with ReLU and max pooling\n        x = F.relu(self.conv3(x))\n        # current x.shape [batch_size, 64, 56, 56]\n        x = F.max_pool2d(x, kernel_size=2, stride=2)  # Output size: (64, 28, 28)\n        # current x.shape [batch_size, 64, 28, 28]\n\n        # Flatten the tensor for the fully connected layer\n        x = x.view(x.size(0), -1)  # Flatten the output to (batch_size, 64*28*28)\n        # current x.shape [batch_size, 50176]\n\n        # First fully connected layer\n        x = F.relu(self.fc1(x))\n        # current x.shape [batch_size, 128]\n        \n        # Output layer\n        x = self.fc2(x)  # Output shape: (batch_size, 4)\n        # current x.shape [batch_size, 4]\n        return x\n        \n\nmodel = SimpleCNN()\nprint(model)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_names","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = model.to(device)\n\ncriterion = torch.nn.CrossEntropyLoss()\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train(epochs):\n    print('Starting training..')\n    for e in range(0, epochs):\n        print('='*20)\n        print(f'Starting epoch {e + 1}/{epochs}')\n        print('='*20)\n\n        train_loss = 0.\n        val_loss = 0.  # Not computing val_loss since we'll be evaluating the model multiple times within one epoch\n        \n        model.train() # set model to training phase\n        \n        for train_step, (images, labels) in enumerate(train_dataloader):\n            \n            images = images.to(device)\n            labels = labels.to(device)\n            \n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            # Once we get the loss we need to take a gradient step\n            loss.backward() # Back propagation\n            optimizer.step() # Completes the gradient step by updating all the parameter values (we are using all parameters)\n            train_loss += loss.item() # Loss is a tensor which can't be added to train_loss so .item() converts it to float\n            \n            # Evaluating the model every 20th step\n            if train_step % 20 == 0:\n                print('Evaluating at step', train_step)\n\n                accuracy = 0\n\n                model.eval() # set model to eval phase\n\n                for val_step, (images, labels) in enumerate(val_dataloader):\n                    \n                    images = images.to(device)\n                    labels = labels.to(device)\n                    \n                    with torch.no_grad():\n                        outputs = model(images)\n                        \n                    loss = criterion(outputs, labels)\n                    val_loss += loss.item()\n\n                    _, preds = torch.max(outputs, 1)\n                    accuracy += sum((preds.cpu() == labels.cpu()).numpy()) # adding correct preds to acc\n\n                val_loss /= (val_step + 1)\n                accuracy = accuracy/len(val_dataset)\n                print(f'Validation Loss: {val_loss:.4f}, Accuracy: {accuracy:.4f}')\n\n                model.train()\n\n                if accuracy >= 0.6:\n                    print('Performance condition satisfied, stopping..')\n                    return\n\n        train_loss /= (train_step + 1)\n\n        print(f'Training Loss: {train_loss:.4f}')\n    print('Training complete..')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Запуск обучения","metadata":{}},{"cell_type":"code","source":"%%time\n\ntrain(epochs=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Создание класса TestDataset\nНаследуемся от базового класса Dataset, модифицируем метод get_item, который теперь возвращает только изображение, т.к. метка класса неизвестна и ее нужно предсказать в рамках соревнования","metadata":{}},{"cell_type":"code","source":"class TestDataset(Dataset):\n    def __init__(self, root_dir, transform=None): \n        self.root_dir = root_dir\n        self.transform = transform\n        self.filenames = sorted(os.listdir(root_dir))\n\n    def __len__(self):\n        return len(self.filenames)\n\n    def __getitem__(self, idx):\n        img_name = os.path.join(self.root_dir,\n                                self.filenames[idx])\n        \n        image = Image.open(img_name)\n        image = image.convert('RGB')\n        \n        if self.transform:\n            sample = self.transform(image)\n\n        return sample","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"root_dir = '/kaggle/input/optical-coherence-tomography-classification/Test'\ntest_dataset = TestDataset(root_dir, test_transform)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img0 = test_dataset[112]\nprint(img0.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataloader = DataLoader(test_dataset, batch_size, shuffle=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Predict на тестовой выборке","metadata":{}},{"cell_type":"code","source":"# Generate predictions\npredictions = []\n\nmodel.eval()  # Set model to evaluation mode\n\nfor images in test_dataloader:\n    images = images.to(device)\n    \n    with torch.no_grad():\n        outputs = model(images)\n        \n    _, preds = torch.max(outputs, 1)\n    \n    preds = preds.cpu()\n    predictions.extend(preds.tolist())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Формирование файла submission","metadata":{}},{"cell_type":"code","source":"predictions_df = pd.DataFrame({'ImageId': range(1, len(predictions) + 1), 'Label': predictions})\n\npredictions_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predictions_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}