{"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":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30674,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport torch\nimport torch.optim as optim\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\nimport matplotlib.pyplot as plt\nfrom torchvision import transforms\nfrom torch.utils.tensorboard import SummaryWriter\nfrom torchvision.transforms import ToPILImage\nimport random\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\ndevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n\nseed = 2002\ntorch.manual_seed(seed)\nnp.random.seed(seed)\nrandom.seed(seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(seed)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n        \nprint(device)\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-02T03:24:51.480295Z","iopub.execute_input":"2024-04-02T03:24:51.480657Z","iopub.status.idle":"2024-04-02T03:24:51.501799Z","shell.execute_reply.started":"2024-04-02T03:24:51.480629Z","shell.execute_reply":"2024-04-02T03:24:51.500754Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Digit Recognizer \n## Introduction\n- In this notebook we will be going over all of the steps you need to make a CNN for Digit Recognition utilizing PyTorch at every step of the way. We will be utilizing the Dataset in order to efficiently load data and apply data augmentations. We will also utilize the dataloader to efficiently load our data in batches. Finally we will model our Neural Network and architect the training, evaluating and predicting loops.\n\n## Table Of Contents","metadata":{}},{"cell_type":"markdown","source":"# Exploratory Data Analysis\n- If you're not familiar with the dataset it's very clean and ready-to-go. However, there are some analysis that is necessary before we build our model. The first thing we need to discover is the dimensions of our dataset and perform necessary preprocessing before our data pipeline.","metadata":{}},{"cell_type":"code","source":"# Load the train dataset\ndf = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:51.503963Z","iopub.execute_input":"2024-04-02T03:24:51.504290Z","iopub.status.idle":"2024-04-02T03:24:53.897733Z","shell.execute_reply.started":"2024-04-02T03:24:51.504261Z","shell.execute_reply":"2024-04-02T03:24:53.896584Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:53.899086Z","iopub.execute_input":"2024-04-02T03:24:53.899403Z","iopub.status.idle":"2024-04-02T03:24:53.918771Z","shell.execute_reply.started":"2024-04-02T03:24:53.899375Z","shell.execute_reply":"2024-04-02T03:24:53.917672Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.info()","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:53.920345Z","iopub.execute_input":"2024-04-02T03:24:53.920814Z","iopub.status.idle":"2024-04-02T03:24:53.956786Z","shell.execute_reply.started":"2024-04-02T03:24:53.920782Z","shell.execute_reply":"2024-04-02T03:24:53.955716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the previous cells, we can see that these digits are 784 pixels which makes up a 28x28 image. Depending on the architecture you use how you represent this data is important but we will concern ourselves with that when we build our data pipeline. ","metadata":{}},{"cell_type":"code","source":"# See Pixel Ranges for random column\ndf['pixel774'].unique()","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:53.959127Z","iopub.execute_input":"2024-04-02T03:24:53.959439Z","iopub.status.idle":"2024-04-02T03:24:53.966291Z","shell.execute_reply.started":"2024-04-02T03:24:53.959411Z","shell.execute_reply":"2024-04-02T03:24:53.965330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Preprocessing\n- From the previous cell we can see our pixel ranges are from (0-254) which is as expected for this data. It is common practice to normalize these values by dividing by 255. This helps the models in terms of efficiency and effectiveness.","metadata":{}},{"cell_type":"code","source":"df.loc[:, df.columns != 'label'] = df.loc[:, df.columns != 'label'] / 255.0","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:53.967728Z","iopub.execute_input":"2024-04-02T03:24:53.968035Z","iopub.status.idle":"2024-04-02T03:24:55.343180Z","shell.execute_reply.started":"2024-04-02T03:24:53.968001Z","shell.execute_reply":"2024-04-02T03:24:55.342130Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Just to make sure we will check for missing values.","metadata":{}},{"cell_type":"code","source":"# Find number of null values among ALL columns\ndf.isna().sum().sum()","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.344523Z","iopub.execute_input":"2024-04-02T03:24:55.345218Z","iopub.status.idle":"2024-04-02T03:24:55.426838Z","shell.execute_reply.started":"2024-04-02T03:24:55.345184Z","shell.execute_reply":"2024-04-02T03:24:55.425769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Pipeline\n- In this section we will define the architecture for our PyTorch dataset. If you're not familiar with these they are a very clean way to encapsulate data and make data pipelines much easier. We will then load this dataset into a dataloader and take a sample to verify it's working as inteneded.","metadata":{}},{"cell_type":"markdown","source":"## Dataset\n- This dataset has a couple moving parts first we've made target and augment optional for the case of our submission data. For the train data we will perform a series of augmentations which is random rotations and affine transformations. You can add to this part and is a good thing to tweak, however you must keep in mind some basic observations about the data. Some transformations such as large rotations or color are not beneficial or hurtful. It's important to perform the normalization step to all data regardless if it undergoes additional transformations. For the get method we are just returning our data as 28x28 tensors due to our models architecture.","metadata":{}},{"cell_type":"code","source":"class DigitDataset(Dataset):\n    def __init__(self, features, target=None, augment=False):\n        \"\"\"\n        Initialize the dataset with features, optional target labels, and an augmentation flag.\n        \n        Args:\n            features (DataFrame): The feature data.\n            target (DataFrame, optional): The target labels. Defaults to None.\n            augment (bool, optional): Whether to apply data augmentation. Defaults to False.\n        \"\"\"\n        self.features = features\n        self.target = target\n        self.augment = augment\n\n        # Define transformations based on whether augmentation is requested\n        if self.augment:\n            # When augmentation is true, include random rotations and affine transformations\n            self.transforms = transforms.Compose([\n                transforms.RandomRotation(degrees=15),\n                transforms.RandomAffine(degrees=0, translate=(0.05, 0.05), scale=(0.95, 1.05)),\n                transforms.ToTensor(),\n                transforms.Normalize((0.5,), (0.5,))  # Normalize the images\n            ])\n        else:\n            # Without augmentation, only convert to tensor and normalize\n            self.transforms = transforms.Compose([\n                transforms.ToTensor(),\n                transforms.Normalize((0.5,), (0.5,))  # Normalize the images\n            ])\n\n    def __len__(self):\n        \"\"\"\n        Return the length of the dataset.\n        \"\"\"\n        # Use the length of target if it exists, else use the length of features\n        return len(self.target) if self.target is not None else len(self.features)\n\n    def __getitem__(self, idx):\n        \"\"\"\n        Get the item at the given index.\n        \n        Args:\n            idx (int): The index of the item.\n        \n        Returns:\n            Tuple[Tensor, Tensor]: A tuple of feature tensor and label tensor if target is provided, otherwise just the feature tensor.\n        \"\"\"\n        # Convert features to tensor and reshape for image format\n        features_tensor = torch.tensor(self.features.iloc[idx].values, dtype=torch.float).view(1, 28, 28)\n        # Convert the tensor to PIL Image to apply torchvision transforms\n        features_pil = ToPILImage()(features_tensor)\n        # Apply the defined transformations\n        features_tensor = self.transforms(features_pil)\n\n        if self.target is not None:\n            # Convert target to tensor if it exists\n            label_tensor = torch.tensor(self.target.iloc[idx], dtype=torch.long)\n            return features_tensor, label_tensor\n        else:\n            return features_tensor\n","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:26:28.464016Z","iopub.execute_input":"2024-04-02T03:26:28.464411Z","iopub.status.idle":"2024-04-02T03:26:28.475932Z","shell.execute_reply.started":"2024-04-02T03:26:28.464382Z","shell.execute_reply":"2024-04-02T03:26:28.475011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load dataframe into dataset\ndataset = DigitDataset(df.drop(['label'], axis=1), df['label'], True)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:26:29.955670Z","iopub.execute_input":"2024-04-02T03:26:29.956469Z","iopub.status.idle":"2024-04-02T03:26:30.233826Z","shell.execute_reply.started":"2024-04-02T03:26:29.956433Z","shell.execute_reply":"2024-04-02T03:26:30.232590Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize our dataloader\ndataloader = DataLoader(dataset, batch_size=64, shuffle=True)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:26:31.395365Z","iopub.execute_input":"2024-04-02T03:26:31.395762Z","iopub.status.idle":"2024-04-02T03:26:31.401730Z","shell.execute_reply.started":"2024-04-02T03:26:31.395734Z","shell.execute_reply":"2024-04-02T03:26:31.400234Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get a sample from the dataloader\nfor features, label in dataloader:\n    break","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:26:32.527216Z","iopub.execute_input":"2024-04-02T03:26:32.527603Z","iopub.status.idle":"2024-04-02T03:26:32.700735Z","shell.execute_reply.started":"2024-04-02T03:26:32.527573Z","shell.execute_reply":"2024-04-02T03:26:32.699785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a given sample to verify transformations\nplt.imshow(features[0].cpu().squeeze(), cmap='gist_gray')","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:26:34.675339Z","iopub.execute_input":"2024-04-02T03:26:34.676359Z","iopub.status.idle":"2024-04-02T03:26:34.954789Z","shell.execute_reply.started":"2024-04-02T03:26:34.676325Z","shell.execute_reply":"2024-04-02T03:26:34.953665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model Building\n- In this section of the notebook we will define our models architecture, define our training loop with KFold Cross Validation, and save the best model. We will use this model to predict our test data in order to get a ballpark for our models performance. We will then finally make predictions on our submission data.","metadata":{}},{"cell_type":"markdown","source":"## Model Architecture","metadata":{}},{"cell_type":"code","source":"class DigitClassifier(nn.Module):\n    def __init__(self, num_classes, p=0.3):\n        \"\"\"\n        Initializes the DigitClassifier model with two convolutional layers,\n        two linear layers, batch normalization, max pooling, ReLU activation, and dropout.\n        \n        Args:\n            num_classes (int): The number of classes for classification.\n            p (float, optional): Dropout probability. Defaults to 0.3.\n        \"\"\"\n        super(DigitClassifier, self).__init__()\n        \n        # First convolutional layer with batch normalization\n        self.c1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=(5, 5), stride=1, padding=0)\n        self.bn1 = nn.BatchNorm2d(16)  # Batch normalization for the first conv layer\n        \n        # Second convolutional layer with batch normalization\n        self.c2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=(3, 3), stride=1, padding=0)\n        self.bn2 = nn.BatchNorm2d(32)  # Batch normalization for the second conv layer\n        \n        # Fully connected layers for classification\n        self.fc1 = nn.Linear(32 * 5 * 5, 128)  # First fully connected layer\n        self.fc2 = nn.Linear(128, num_classes)  # Second fully connected layer to output class scores\n        \n        # Max pooling, ReLU activation, and dropout applied after convolutional layers\n        self.mp = nn.MaxPool2d(kernel_size=(2, 2))  # Max pooling\n        self.relu = nn.ReLU()  # ReLU activation\n        self.dropout = nn.Dropout(p)  # Dropout\n        \n    def relu_mp_dropout(self, x):\n        \"\"\"\n        Applies ReLU activation, max pooling, and dropout in sequence.\n        \n        Args:\n            x (Tensor): Input tensor.\n        \n        Returns:\n            Tensor: Transformed tensor.\n        \"\"\"\n        x = self.relu(x)\n        x = self.mp(x)\n        x = self.dropout(x)\n        return x\n\n    def forward(self, x):\n        \"\"\"\n        Defines the forward pass of the model.\n        \n        Args:\n            x (Tensor): Input tensor representing batch of images.\n        \n        Returns:\n            Tensor: Output tensor with class scores.\n        \"\"\"\n        # Apply first conv layer, batch normalization, followed by ReLU, max pooling, and dropout\n        x = self.c1(x)\n        x = self.bn1(x)\n        x = self.relu_mp_dropout(x)\n        \n        # Apply second conv layer, batch normalization, followed by ReLU, max pooling, and dropout\n        x = self.c2(x)\n        x = self.bn2(x)\n        x = self.relu_mp_dropout(x)\n        \n        # Flatten the output for the fully connected layer\n        x = x.view(x.size(0), -1)\n        x = self.fc1(x)\n        x = self.relu(x)  # Apply ReLU activation after first fully connected layer\n        x = self.dropout(x)  # Apply dropout\n        output = self.fc2(x)  # Final output layer\n        \n        return output\n","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:27:00.674859Z","iopub.execute_input":"2024-04-02T03:27:00.675605Z","iopub.status.idle":"2024-04-02T03:27:00.687799Z","shell.execute_reply.started":"2024-04-02T03:27:00.675564Z","shell.execute_reply":"2024-04-02T03:27:00.687004Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Verify our classes\nclasses = sorted(df['label'].unique())\nclasses","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:27:03.399149Z","iopub.execute_input":"2024-04-02T03:27:03.399761Z","iopub.status.idle":"2024-04-02T03:27:03.410497Z","shell.execute_reply.started":"2024-04-02T03:27:03.399694Z","shell.execute_reply":"2024-04-02T03:27:03.409339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize model\nmodel = DigitClassifier(len(classes))","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:27:13.422252Z","iopub.execute_input":"2024-04-02T03:27:13.423017Z","iopub.status.idle":"2024-04-02T03:27:13.431984Z","shell.execute_reply.started":"2024-04-02T03:27:13.422976Z","shell.execute_reply":"2024-04-02T03:27:13.430595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Verify tensors are correct size\nmodel(features).shape","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:27:15.202359Z","iopub.execute_input":"2024-04-02T03:27:15.203041Z","iopub.status.idle":"2024-04-02T03:27:15.309107Z","shell.execute_reply.started":"2024-04-02T03:27:15.203000Z","shell.execute_reply":"2024-04-02T03:27:15.308122Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get test data for later\ndf, df_test = train_test_split(df, test_size=0.1, stratify=df['label'])","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.828930Z","iopub.status.idle":"2024-04-02T03:24:55.829307Z","shell.execute_reply.started":"2024-04-02T03:24:55.829121Z","shell.execute_reply":"2024-04-02T03:24:55.829137Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize test data loader for later\ntest_dataset = DigitDataset(df_test.drop(['label'], axis=1), df_test['label'])\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=True)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.830605Z","iopub.status.idle":"2024-04-02T03:24:55.831595Z","shell.execute_reply.started":"2024-04-02T03:24:55.831397Z","shell.execute_reply":"2024-04-02T03:24:55.831416Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training Loop","metadata":{}},{"cell_type":"code","source":"EPOCHS = 100  # Total number of training epochs\npatience = 20  # Patience for early stopping\ntrain_losses, val_losses = [], []  # Lists to track training and validation losses\nkfold = StratifiedKFold(n_splits=5, shuffle=True)  # K-Fold Cross-validation setup\n\nbest_overall_accuracy = 0.0  # Track the best accuracy across all folds\nbest_overall_loss = float('inf')  # Track the best loss across all folds\nbest_model_state = None  # Placeholder for the best model state\n\n# Loop over each fold\nfor fold, (train_idx, val_idx) in enumerate(kfold.split(df.index, df['label'])):\n    print(f'Fold {fold + 1}/{kfold.n_splits}\\n')\n\n    # Splitting the dataset into training and validation based on current fold\n    df_train, df_val = df.iloc[train_idx], df.iloc[val_idx]\n\n    # Creating dataset objects for training and validation sets\n    train_dataset = DigitDataset(df_train.drop(['label'], axis=1), df_train['label'])\n    val_dataset = DigitDataset(df_val.drop(['label'], axis=1), df_val['label'])\n\n    # DataLoader objects for loading the data in batches\n    train_data_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n    val_data_loader = DataLoader(val_dataset, batch_size=64, shuffle=True)\n\n    # Initialize the model and move it to the configured device (GPU/CPU)\n    model = DigitClassifier(len(classes))\n    model.to(device)\n\n    # Setup optimizer and learning rate scheduler\n    optimizer = optim.Adam(model.parameters(), lr=0.01)\n    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.5)\n    loss_fn = nn.CrossEntropyLoss()\n\n    # Initialize variables to track the best validation loss and accuracy for the current fold\n    best_val_loss = float('inf')\n    best_accuracy = 0  \n    best_epoch = 0\n\n    # Training loop\n    for epoch in range(EPOCHS):\n        model.train()  # Set model to training mode\n        curr_loss = 0\n        for features, labels in train_data_loader:  \n            features, labels = features.to(device), labels.to(device)\n            optimizer.zero_grad()  # Clear gradients\n            outputs = model(features)  # Forward pass\n\n            loss = loss_fn(outputs, labels)  # Calculate loss\n            loss.backward()  # Backward pass\n            optimizer.step()  # Update weights\n\n            curr_loss += loss.item() * features.size(0)  # Accumulate the loss\n\n        # Calculate average training loss for the epoch\n        train_loss = curr_loss / len(train_data_loader.dataset)\n        train_losses.append(train_loss)\n\n        model.eval()  # Set model to evaluation mode\n        curr_loss = 0\n        correct_predictions = 0\n\n        # Validation loop (no gradient updates)\n        with torch.no_grad():\n            for features, labels in val_data_loader: \n                features, labels = features.to(device), labels.to(device)\n                outputs = model(features)\n                loss = loss_fn(outputs, labels)\n                curr_loss += loss.item() * features.size(0)\n                _, predicted = torch.max(outputs.data, 1)\n                correct_predictions += (predicted == labels).sum().item()\n\n        # Calculate average validation loss and accuracy for the epoch\n        val_loss = curr_loss / len(val_data_loader.dataset)\n        val_losses.append(val_loss)\n        accuracy = correct_predictions / len(val_data_loader.dataset)\n\n        # Print epoch summary\n        print(f'Epoch {epoch+1}/{EPOCHS} - Train Loss: {train_loss:.4f}, Validation Loss: {val_loss:.4f}, Accuracy: {accuracy:.4f}')\n\n        # Update best model if validation loss has improved\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            best_accuracy = accuracy\n            best_model_state = model.state_dict()\n            print(f'Model improved and saved at epoch {epoch+1} with validation loss: {val_loss:.4f}, accuracy: {accuracy:.4f}')\n\n        # Check for overall improvement across all folds and update if necessary\n        if accuracy > best_overall_accuracy or val_loss < best_overall_loss:\n            best_overall_accuracy = accuracy\n            best_overall_loss = val_loss\n            best_model_state = model.state_dict()\n\n        # Early stopping condition\n        if epoch - best_epoch >= patience:\n            print('Patience limit reached. Stopping training.')\n            break\n        \n        scheduler.step()  # Update the learning rate\n\n# Save the best model state found across all folds\nif best_model_state:\n    torch.save(best_model_state, 'best_model.bin')\n    print(\"Best model across all folds saved.\")","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.832669Z","iopub.status.idle":"2024-04-02T03:24:55.833066Z","shell.execute_reply.started":"2024-04-02T03:24:55.832882Z","shell.execute_reply":"2024-04-02T03:24:55.832906Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_model_path = 'best_model.bin'\nmodel.load_state_dict(torch.load(best_model_path))\nmodel.to(device) ","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.834169Z","iopub.status.idle":"2024-04-02T03:24:55.834526Z","shell.execute_reply.started":"2024-04-02T03:24:55.834349Z","shell.execute_reply":"2024-04-02T03:24:55.834365Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def evaluate(data_loader):\n    model.eval()\n    \n    losses = []\n    correct_preds = 0\n    num_examples = 0  \n\n    with torch.no_grad():\n        for features, labels in data_loader:\n            features, labels = features.to(device), labels.to(device)\n            outputs = model(features)\n            _, preds = torch.max(outputs, dim=1)  \n            loss = loss_fn(outputs, labels)\n            \n            correct_preds += torch.sum(preds == labels).item()\n            losses.append(loss.item())\n            num_examples += labels.size(0) \n\n    average_loss = np.mean(losses)\n    accuracy = correct_preds / num_examples \n    \n    return accuracy, average_loss\n\n\nevaluate(test_loader)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.835690Z","iopub.status.idle":"2024-04-02T03:24:55.836080Z","shell.execute_reply.started":"2024-04-02T03:24:55.835908Z","shell.execute_reply":"2024-04-02T03:24:55.835924Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predict(data_loader):\n    model.eval()\n    predictions = []\n\n    with torch.no_grad():\n        for features in data_loader:  \n            features = torch.tensor(features).to(device)\n            outputs = model(features)\n            _, preds = torch.max(outputs, dim=1)\n            predictions.append(preds.cpu())  \n    predictions = torch.cat(predictions, dim=0)\n    \n    return predictions","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.837175Z","iopub.status.idle":"2024-04-02T03:24:55.837530Z","shell.execute_reply.started":"2024-04-02T03:24:55.837353Z","shell.execute_reply":"2024-04-02T03:24:55.837369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_submit = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')\ndf_submit.loc[:, df_submit.columns != 'label'] = df_submit.loc[:, df_submit.columns != 'label'] / 255.0\nsubmit_dataset = DigitDataset(df_submit)\nsubmit_dataloader = DataLoader(submit_dataset, batch_size=64)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.840379Z","iopub.status.idle":"2024-04-02T03:24:55.841021Z","shell.execute_reply.started":"2024-04-02T03:24:55.840774Z","shell.execute_reply":"2024-04-02T03:24:55.840793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions = predict(submit_dataloader)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.844074Z","iopub.status.idle":"2024-04-02T03:24:55.844477Z","shell.execute_reply.started":"2024-04-02T03:24:55.844280Z","shell.execute_reply":"2024-04-02T03:24:55.844297Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions_list = predictions.tolist()\ndf_submit['Label'] = predictions_list\ndf_submit['ImageId'] = range(1, len(df_submit) + 1)\ndf_submit = df_submit[['Label', 'ImageId']]\ndf_submit.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-02T03:24:55.845680Z","iopub.status.idle":"2024-04-02T03:24:55.846220Z","shell.execute_reply.started":"2024-04-02T03:24:55.845891Z","shell.execute_reply":"2024-04-02T03:24:55.845907Z"},"trusted":true},"execution_count":null,"outputs":[]}]}