{"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"},{"sourceId":7086476,"sourceType":"datasetVersion","datasetId":4082975}],"dockerImageVersionId":30588,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport PIL\nimport pandas as pd\nimport timm\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import accuracy_score\nimport matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nfrom PIL import Image\nimport pickle\nfrom torch.utils.data import DataLoader, TensorDataset, WeightedRandomSampler, Subset, Dataset\nfrom sklearn.model_selection import KFold\nimport torch.optim as optim\nfrom sklearn.model_selection import train_test_split\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom sklearn.metrics import balanced_accuracy_score\nfrom sklearn.metrics import confusion_matrix, balanced_accuracy_score, ConfusionMatrixDisplay\nfrom torchvision import transforms\nimport torch.nn.functional as F\n\nPIL.Image.MAX_IMAGE_PIXELS = 933120000000\n\n\nLR = 5e-4\nEPOCHS = 100\nBATCH_SIZE = 4\nN_SPLITS = 5\nN_ACCUMULATE = 16\nMODEL_NAME = 'res2next50.in1k'\nImage_size = 512\nWEIGHT_DECAY = 1e-4\nnum_classes = 5\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(torch.cuda.is_available())\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-11-30T05:42:44.126629Z","iopub.execute_input":"2023-11-30T05:42:44.12699Z","iopub.status.idle":"2023-11-30T05:42:49.640005Z","shell.execute_reply.started":"2023-11-30T05:42:44.126952Z","shell.execute_reply":"2023-11-30T05:42:49.639089Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Set a higher limit to allow the image to be processed (use with caution)\nImage.MAX_IMAGE_PIXELS = None  # This disables the limit completely, be careful\n# or set it to a specific limit you consider safe\n# Image.MAX_IMAGE_PIXELS = your_preferred_limit\n\n# Disable DecompressionBombError\nImage.MAX_IMAGE_PIXELS = None\n\n# Load labels\nlabels_df = pd.read_csv('/kaggle/input/UBC-OCEAN/train.csv')\nlabels_mapping = {'HGSC': 0, 'EC': 1, 'MC': 2, 'CC': 3, 'LGSC': 4}\n\n# Prepare empty lists to fill with image data and labels\nX_train = []\nY_train = []\n\n# Path to the images\nimage_folder = '/kaggle/input/UBC-OCEAN/train_thumbnails/'\nimage_folder_2 = '/kaggle/input/UBC-OCEAN/train_images/'\n\n# Process images\nfor index, row in tqdm(labels_df.iterrows(), total=labels_df.shape[0], desc=\"Processing images\"):\n    image_path = os.path.join(image_folder, f\"{row['image_id']}_thumbnail.png\")\n    if os.path.exists(image_path):\n        with Image.open(image_path) as img:\n            # Resize image and convert to RGB (in case any images are grayscale)\n            img_resized = img.resize((Image_size, Image_size))\n            # Convert image data to numpy array and append it to the list\n            X_train.append(np.array(img_resized))\n            # Map the label to its corresponding integer and append it to the list\n            Y_train.append(labels_mapping[row['label']])\n    else:\n        image_path = os.path.join(image_folder_2, f\"{row['image_id']}.png\")\n        with Image.open(image_path) as img:\n            # Resize image and convert to RGB (in case any images are grayscale)\n            img_resized = img.resize((Image_size, Image_size))\n            # Convert image data to numpy array and append it to the list\n            X_train.append(np.array(img_resized))\n            # Map the label to its corresponding integer and append it to the list\n            Y_train.append(labels_mapping[row['label']])\n\n# Convert lists to numpy arrays\nX_train = np.array(X_train)\nY_train = np.array(Y_train)\n\n# Verify the shape of the arrays\nprint(f\"X_train shape: {X_train.shape}\")\nprint(f\"Y_train shape: {Y_train.shape}\")\n\n# You can now proceed with your training using X_train and Y_train\n\n\n\n","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:42:49.641924Z","iopub.execute_input":"2023-11-30T05:42:49.642334Z","iopub.status.idle":"2023-11-30T05:45:38.138266Z","shell.execute_reply.started":"2023-11-30T05:42:49.642305Z","shell.execute_reply":"2023-11-30T05:45:38.137333Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"X_train shape: {X_train.shape}\")\nX_train_tensor = torch.tensor(X_train).float()\nY_train_tensor = torch.from_numpy(Y_train).long()  # Assuming Y_train contains integer class labels\nplt.imshow(X_train_tensor[0]/255)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:45:38.13953Z","iopub.execute_input":"2023-11-30T05:45:38.139817Z","iopub.status.idle":"2023-11-30T05:45:39.253986Z","shell.execute_reply.started":"2023-11-30T05:45:38.139792Z","shell.execute_reply":"2023-11-30T05:45:39.253078Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nX_train_tensor = X_train_tensor.view(-1, Image_size, Image_size, 3)\ndataset = TensorDataset(X_train_tensor, Y_train_tensor)\n# 分割数据集为训练集和验证集\ntrain_idx, val_idx = train_test_split(\n    np.arange(len(X_train)),\n    test_size=0.2,  # 20%作为验证集\n    random_state=43,  # 为了可重复性\n    stratify=Y_train  # 保持类别分布一致\n)\n\n\n\ntrain_dataset = Subset(dataset, train_idx)\nval_dataset = Subset(dataset, val_idx)\n\nval_labels = [dataset.tensors[1][i].item() for i in val_idx]\n\ntrain_labels = [dataset.tensors[1][i].item() for i in train_idx]\n\nval_images = [dataset[i][0] for i in val_idx]\ntrain_images = [dataset[i][0] for i in train_idx]\n\nplt.imshow(train_images[0]/255)\nplt.show()\nprint(train_images[0].shape)\n# Assuming `dataset` is a PyTorch Dataset and `val_idx`, `train_idx` are lists of indices\n\n\n\nY_train = np.array(Y_train)  # Convert to numpy array if not already\nclass_sample_count = np.array([len(np.where(Y_train[train_idx] == t)[0]) for t in np.unique(Y_train[train_idx])])\n\n\nweights = 1. / class_sample_count\nsamples_weights = np.array([weights[t] for t in Y_train[train_idx]])\n\n# Convert to a PyTorch tensor\nsamples_weights = torch.from_numpy(samples_weights)\nsamples_weights = samples_weights.double()\n\n# Create a sampler for weighted sampling\nsampler = WeightedRandomSampler(samples_weights, len(samples_weights))\n\n\n\n\n# Create data loaders for training and validation\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, sampler=sampler)\n#train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\nprint(len(np.unique(Y_train)))\n\n\nfirst_batch_images, first_batch_labels = next(iter(train_loader))\n\nplt.imshow(first_batch_images[0]/255)\nplt.show()\n\n\n","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:45:39.255289Z","iopub.execute_input":"2023-11-30T05:45:39.255602Z","iopub.status.idle":"2023-11-30T05:45:39.838886Z","shell.execute_reply.started":"2023-11-30T05:45:39.255573Z","shell.execute_reply":"2023-11-30T05:45:39.837917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_transforms = transforms.Compose([\n    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n    transforms.RandomVerticalFlip(),    # 随机垂直翻转\n    transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.1),\n    #transforms.CenterCrop((224, 224)),  # 添加中心裁剪, 替换size_height和size_width为所需的尺寸\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 标准化\n])\n\n# 应用转换\nfor images, labels in train_loader:\n    print(images.shape)\n    images = images/255\n    images = images.permute(0, 3, 1, 2)\n    enhanced_image = image_transforms(images)\n    print(images.shape)\n    enhanced_image = enhanced_image.permute(0, 2, 3, 1)\n    plt.imshow(enhanced_image[0])\n    plt.show()\n    break  # 只获取第一个batch，所以跳出循环","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:45:39.841605Z","iopub.execute_input":"2023-11-30T05:45:39.842001Z","iopub.status.idle":"2023-11-30T05:45:40.325897Z","shell.execute_reply.started":"2023-11-30T05:45:39.841959Z","shell.execute_reply":"2023-11-30T05:45:40.324993Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_model(model, train_loader, val_loader, criterion, optimizer, scheduler, epochs, device, patience=18):\n    model.to(device)\n\n    history = {'train_loss': [], 'val_loss': [], 'train_balanced_acc': [], 'val_balanced_acc': []}\n\n    best_val_acc = 0.0  # Keep track of the best validation accuracy\n    \n    \n    best_val_loss = float('inf')  # 可以根据需要监控验证准确率\n    epochs_no_improve = 0  # 初始化没有改进的epoch数\n\n    for epoch in range(epochs):\n        model.train()\n        train_loss = 0.0\n        train_preds = []\n        train_targets = []\n\n        # 使用 tqdm 实时更新\n        train_loop = tqdm(train_loader, position=0, leave=True)\n        for batch_index, (inputs, labels) in enumerate(train_loop):\n            inputs, labels = inputs.to(device), labels.to(device)\n            inputs = inputs/255\n            inputs = inputs.permute(0, 3, 1, 2)\n            inputs = image_transforms(inputs)\n\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            loss.backward()\n\n            # 每 N_ACCUMULATE 次批次后，更新梯度\n            if (batch_index + 1) % N_ACCUMULATE == 0:\n                optimizer.step()\n                optimizer.zero_grad()\n\n            # 累积损失和正确率\n            train_loss += loss.item()\n            _, predicted = torch.max(outputs, 1)\n\n            # 实时更新\n            train_loop.set_description(f\"Epoch [{epoch + 1}/{epochs}]\")\n            train_preds.extend(predicted.view(-1).cpu().numpy())\n            train_targets.extend(labels.view(-1).cpu().numpy())\n        # 确保最后一次累积的梯度也被更新\n        if len(train_loader) % N_ACCUMULATE != 0:\n            optimizer.step()\n            optimizer.zero_grad()\n        \n        # Validation phase\n        model.eval()\n        val_preds = []\n        val_targets = []\n        val_loss = 0.0\n        val_correct = 0\n        with torch.no_grad():\n            for inputs, labels in val_loader:\n                inputs, labels = inputs.to(device), labels.to(device)\n                inputs = inputs/255\n                inputs = inputs.permute(0, 3, 1, 2)\n                inputs = image_transforms(inputs)\n\n                outputs = model(inputs)\n                loss = criterion(outputs, labels)\n\n                val_loss += loss.item()\n                _, predicted = torch.max(outputs, 1)\n                val_preds.extend(predicted.view(-1).cpu().numpy())\n                val_targets.extend(labels.view(-1).cpu().numpy())\n                val_correct += (predicted == labels).sum().item()\n\n        train_loss /= len(train_loader)\n        val_loss /= len(val_loader)\n        \n        train_balanced_acc = balanced_accuracy_score(train_targets, train_preds)\n        val_balanced_acc = balanced_accuracy_score(val_targets, val_preds)\n        scheduler.step(val_balanced_acc)\n\n        history['train_loss'].append(train_loss)\n        history['val_loss'].append(val_loss)\n        history['train_balanced_acc'].append(train_balanced_acc)\n        history['val_balanced_acc'].append(val_balanced_acc)\n        \n        if val_balanced_acc > best_val_acc:\n            best_val_acc = val_balanced_acc\n            epochs_no_improve = 0  # 重置没有改进的epoch数\n            torch.save(model, f'model_checkpoint.pth')\n            print(f\"Checkpoint saved at epoch {epoch + 1}\")\n        else:\n            epochs_no_improve += 1\n            print(f\"No improvement in validation loss for {epochs_no_improve} consecutive epochs.\")\n            \n            # 如果达到早停的容忍度，则停止训练\n            if epochs_no_improve == patience:\n                print(f\"Early stopping triggered after {epoch + 1} epochs.\")\n                break\n\n        # Print metrics\n        print(\n            f\" Epoch {epoch + 1}/{epochs}, Train Loss: {train_loss:.4f}, Train Balanced Acc: {train_balanced_acc:.4f}, Val Loss: {val_loss:.4f}, Val Balanced Acc: {val_balanced_acc:.4f}\")\n\n\n    return history\n\n\ndef plot_metrics(history):\n    actual_epochs = len(history['train_loss'])  # 获取实际完成的训练轮次数量\n    epochs_range = range(1, actual_epochs + 1)\n    plt.figure(figsize=(14, 5))\n\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs_range, history['train_loss'], label='Train Loss')\n    plt.plot(epochs_range, history['val_loss'], label='Validation Loss')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.title('Loss Over Time')\n    plt.legend()\n\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs_range, history['train_balanced_acc'], label='Train Accuracy')\n    plt.plot(epochs_range, history['val_balanced_acc'], label='Validation Accuracy')\n    plt.xlabel('Epochs')\n    plt.ylabel('Accuracy')\n    plt.title('Accuracy Over Time')\n    plt.legend()\n\n    plt.show()\n    \n    \n# Initialize the model\n\nclass CustomEfficientNet(nn.Module):\n    def __init__(self, num_classes, dropout_rate=0.2):\n        super().__init__()\n        # 加载预训练的模型但不带分类器\n        self.base_model = timm.create_model(MODEL_NAME, pretrained=True, num_classes=0)\n        \n        # 冻结预训练模型的所有参数\n        for param in self.base_model.parameters():\n            param.requires_grad = False\n        \n        # 获取去掉分类器后的特征数量\n        features_num = self.base_model.num_features\n        \n        # 增加自定义层\n        self.global_avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.batch_norm1 = nn.BatchNorm1d(features_num)\n        \n        # 添加多个全连接层\n        self.fc1 = nn.Linear(features_num, features_num // 2)\n        self.batch_norm2 = nn.BatchNorm1d(features_num // 2)\n        self.fc2 = nn.Linear(features_num // 2, features_num // 4)\n        self.batch_norm3 = nn.BatchNorm1d(features_num // 4)\n        \n        self.dropout = nn.Dropout(dropout_rate)\n        self.classifier = nn.Linear(features_num // 4, num_classes)\n\n    def forward(self, x):\n        # 获取基础模型的特征\n        x = self.base_model.forward_features(x)\n        # 全局平均池化\n        x = self.global_avg_pool(x).view(x.size(0), -1)\n        \n        # 应用批归一化\n        x = self.batch_norm1(x)\n        \n        # 第一个全连接层\n        x = self.fc1(x)\n        x = F.relu(x)\n        x = self.dropout(x)\n\n        # 第二个全连接层\n        x = self.fc2(x)\n        x = F.relu(x)\n        x = self.dropout(x)\n\n        # 分类层\n        x = self.classifier(x)\n        return x\n\n\n# 现在模型中的预训练参数都被冻结了，只有自定义的分类器层的参数是可训练的。\n\ndef get_all_layers(module):\n    for layer in module.children():\n        if list(layer.children()):  # If it's a nested module, fetch its children\n            yield from get_all_layers(layer)\n        else:\n            yield layer","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:45:40.327297Z","iopub.execute_input":"2023-11-30T05:45:40.327584Z","iopub.status.idle":"2023-11-30T05:45:40.359003Z","shell.execute_reply.started":"2023-11-30T05:45:40.32756Z","shell.execute_reply":"2023-11-30T05:45:40.35806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create an instance of the custom model\n#model = CustomEfficientNet(num_classes=5, dropout_rate=0.2)\n\nmodel = timm.create_model(MODEL_NAME, pretrained=True, num_classes=5)\n\n# Get all layers as a list\n# all_layers = list(get_all_layers(model.base_model))\n\n# # Compute the number of layers to unfreeze (e.g., the latter half of the model's layers)\n# layers_to_unfreeze = len(all_layers) // 2\n\n# # Unfreeze the latter half of the layers\n# for layer in all_layers[-layers_to_unfreeze:]:\n#     for param in layer.parameters():\n#         param.requires_grad = False\n\n        \nweights = torch.tensor(weights, dtype=torch.float32)\nprint(weights)\n# 创建加权的CrossEntropyLoss\n#criterion = nn.CrossEntropyLoss()\ncriterion = nn.CrossEntropyLoss(weight=weights.to(device))\n# Define loss function and optimizer\noptimizer = optim.Adam(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n\nlr_scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.2, patience=4, verbose=True)\n\n# Device configuration\ndevice = torch.device(\"cuda:0\")\n\n# Train the model\nhistory = train_model(model, train_loader, val_loader, criterion, optimizer, lr_scheduler, EPOCHS, device)\n\n# Plot the metrics\n\nplot_metrics(history)","metadata":{"execution":{"iopub.status.busy":"2023-11-30T05:45:40.360313Z","iopub.execute_input":"2023-11-30T05:45:40.360615Z","iopub.status.idle":"2023-11-30T06:38:27.79033Z","shell.execute_reply.started":"2023-11-30T05:45:40.360589Z","shell.execute_reply":"2023-11-30T06:38:27.789419Z"},"trusted":true},"execution_count":null,"outputs":[]}]}