{"cells":[{"metadata":{},"cell_type":"markdown","source":"The form of `Dig-MNIST.csv` seems to be more complicated than `train.csv`.  \nWe try to learn **[STN(Spatial Transformer Networks\n)](https://papers.nips.cc/paper/5854-spatial-transformer-networks.pdf)** from `Dig-MNIST.csv` and test it from `train.csv`."},{"metadata":{},"cell_type":"markdown","source":"# 1. Preparation"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"%matplotlib inline\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport random\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport torch\nfrom tqdm import tqdm","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cfg = {\n    # Batch Size for Training and Varidation\n        \"batch_size\": 1024,\n    # CUDA:0 or CPU\n        \"device\": torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\"),\n    # Epoch Size for Training and Validation\n        \"epoch_size\": 50,\n    # Path to Dig-MNIST.csv\n        \"path_Dig-MNIST_csv\": Path(\"../input/Kannada-MNIST/Dig-MNIST.csv\"),\n    # Path to train.csv\n        \"path_train_csv\": Path(\"../input/Kannada-MNIST/train.csv\"),\n    # Random Seed\n        \"seed\": 17122019,\n    # Ratio of Training Dataset against Overall One\n        \"train_dataset_ratio\": 0.9,\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"random.seed(cfg[\"seed\"])\nnp.random.seed(cfg[\"seed\"])\ntorch.manual_seed(cfg[\"seed\"])\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(cfg[\"seed\"])\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"sns.set(style=\"darkgrid\", context=\"notebook\", palette=\"muted\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 2. Dataset"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"class KannadaMNISTDataset(torch.utils.data.Dataset):\n    def __init__(self,\n                 path_csv: Path,\n                 cfg: dict):\n        df_csv = pd.read_csv(path_csv)\n\n        self.imgs = df_csv.drop([\"label\"], axis=1).values.astype(np.int32)\n        # Reshape Image from (data_size, 784) to (data_size, 1, 28, 28)\n        self.imgs = self.imgs.reshape(-1, 1, 28, 28)\n        # Scale Image from [0, 255] to [0.0, 1.0]\n        self.imgs = torch.tensor(self.imgs/255.0,\n                                 dtype=torch.float32,\n                                 device=cfg[\"device\"])\n\n        self.labels = torch.tensor(df_csv[\"label\"],\n                                   dtype=torch.int64,\n                                   device=cfg[\"device\"])\n\n    def __len__(self):\n        return self.labels.shape[0]\n\n    def __getitem__(self, idx):\n        img = self.imgs[idx]\n        label = self.labels[idx]\n        return img, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":false},"cell_type":"code","source":"def create_training_datasets(cfg: dict):\n    # Create Overall Dataset Setting KannadaMNISTTransform\n    overall_dataset = KannadaMNISTDataset(cfg[\"path_Dig-MNIST_csv\"], cfg)\n    # Split Overall Dataset into Training and Validation Ones\n    train_size = int(len(overall_dataset) * cfg[\"train_dataset_ratio\"])\n    valid_size = len(overall_dataset) - train_size\n    train_dataset, valid_dataset = torch.utils.data.random_split(overall_dataset,\n                                                                 [train_size, valid_size])\n    return train_dataset, valid_dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"%%time\n# Training Datasets\ntrain_dataset, valid_dataset = create_training_datasets(cfg)\n# Test Dataset\ntest_dataset = KannadaMNISTDataset(cfg[\"path_train_csv\"], cfg)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"args = (len(train_dataset), len(valid_dataset), len(test_dataset))\nprint(\"Train:%d,Valid:%d,Test:%d\" % args)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 3. Train"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ThisNetwork(torch.nn.Module):\n    def __init__(self):\n        super(ThisNetwork, self).__init__()\n\n        # STN Localization(CNN)\n        self.loc_cnn = torch.nn.Sequential(\n            # (batch,1,28,28) -> (batch,8,24,24)\n            torch.nn.Conv2d(in_channels=1,\n                            out_channels=8,\n                            kernel_size=5),\n            torch.nn.BatchNorm2d(num_features=8),\n            torch.nn.ReLU(inplace=True),\n            # (batch,8,24,24) -> (batch,8,12,12)\n            torch.nn.MaxPool2d(kernel_size=2,\n                               stride=2),\n            # (batch,8,12,12) -> (batch,16,8,8)\n            torch.nn.Conv2d(in_channels=8,\n                            out_channels=16,\n                            kernel_size=5),\n            torch.nn.BatchNorm2d(num_features=16),\n            torch.nn.ReLU(inplace=True),\n            # (batch,16,8,8) -> (batch,16,4,4)\n            torch.nn.MaxPool2d(kernel_size=2,\n                               stride=2),\n        )\n\n        # STN Localization(FC)\n        self.loc_fc = torch.nn.Sequential(\n            # (batch,256) -> (batch,64)\n            torch.nn.Linear(in_features=256,\n                            out_features=64),\n            torch.nn.ReLU(inplace=True),\n            torch.nn.Dropout(p=0.5),\n            # (batch,64) -> (batch,6)\n            torch.nn.Linear(in_features=64,\n                            out_features=6),\n        )\n\n        self.cnn = torch.nn.Sequential(\n            # (batch,1,28,28) -> (batch,64,28,28)\n            torch.nn.Conv2d(in_channels=1,\n                            out_channels=64,\n                            kernel_size=3,\n                            padding=1),\n            torch.nn.BatchNorm2d(num_features=64),\n            torch.nn.ReLU(inplace=True),\n            # (batch,64,28,28) -> (batch,64,14,14)\n            torch.nn.MaxPool2d(kernel_size=2,\n                               stride=2),\n            # (batch,64,14,14) -> (batch,128,14,14)\n            torch.nn.Conv2d(in_channels=64,\n                            out_channels=128,\n                            kernel_size=3,\n                            padding=1),\n            torch.nn.BatchNorm2d(num_features=128),\n            torch.nn.ReLU(inplace=True),\n            # (batch,128,14,14) -> (batch,128,7,7)\n            torch.nn.MaxPool2d(kernel_size=2,\n                               stride=2),\n        )\n\n        self.avgpool = torch.nn.AdaptiveAvgPool2d(output_size=3)\n\n        self.fc = torch.nn.Sequential(\n            # (batch,1152) -> (batch,256)\n            torch.nn.Linear(in_features=1152,\n                            out_features=256),\n            torch.nn.ReLU(inplace=True),\n            torch.nn.Dropout(p=0.5),\n            # (batch,256) -> (batch,10)\n            torch.nn.Linear(in_features=256,\n                            out_features=10),\n        )\n        self.log_softmax = torch.nn.LogSoftmax(dim=-1)\n\n    def forward(self, x):\n        # STN Localization Network\n        # (batch,1,28,28) -> (batch,256)\n        theta = self.loc_cnn(x)\n        theta = theta.view(theta.size(0), -1)\n        # (batch,256) -> (batch,2,3)\n        theta = self.loc_fc(theta).view(-1, 2, 3)\n\n        # STN Grid Generator\n        # (batch,1,28,28), (batch,2,3) -> (batch,28,28,2)\n        grid = torch.nn.functional.affine_grid(theta, x.size(),\n                                               align_corners=True)\n\n        # STN Sampler\n        # (batch,1,28,28), (batch,28,28,2) -> (batch,1,28,28)\n        x = torch.nn.functional.grid_sample(x, grid,\n                                            align_corners=True)\n\n        # Non-STN\n        # (batch,1,28,28) -> (batch,128,7,7)\n        x = self.cnn(x)\n        # (batch,128,7,7) -> (batch,1152)\n        x = self.avgpool(x)\n        x = x.view(x.size(0), -1)\n        # (batch,1152) -> (batch,10)\n        x = self.fc(x)\n        return self.log_softmax(x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"network = ThisNetwork().to(cfg[\"device\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def learn(network: torch.nn.Module,\n          train_dataset: KannadaMNISTDataset,\n          valid_dataset: KannadaMNISTDataset,\n          cfg: dict):\n    result = {\"Epoch\" : [],\n              \"Type\" : [],\n              \"Average Loss\" : [],\n              \"Accuracy\" : []}\n    criterion = torch.nn.NLLLoss()\n    optimizer = torch.optim.Adam(network.parameters())\n    train_loader = torch.utils.data.DataLoader(train_dataset,\n                                               batch_size=cfg[\"batch_size\"],\n                                               shuffle=True)\n    valid_loader = torch.utils.data.DataLoader(valid_dataset,\n                                               batch_size=cfg[\"batch_size\"],\n                                               shuffle=True)\n\n    # Start\n    for epoch in range(1, cfg[\"epoch_size\"]+1):\n        # Training\n        sum_loss = 0.0\n        sum_correct = 0\n        for imgs, true_labels in tqdm(train_loader):\n            network.zero_grad()\n            pred_probs = network(imgs)\n            pred_labels = torch.argmax(pred_probs, dim=1)\n            loss = criterion(pred_probs, true_labels)\n            loss.backward()\n            optimizer.step()\n            sum_loss += loss.item() * imgs.shape[0]\n            sum_correct += int(torch.sum(pred_labels == true_labels))\n        ave_loss = sum_loss / len(train_dataset)\n        accuracy = 100.0 * sum_correct / len(train_dataset)\n        result[\"Epoch\"].append(epoch)\n        result[\"Type\"].append(\"Training\")\n        result[\"Average Loss\"].append(ave_loss)\n        result[\"Accuracy\"].append(accuracy)\n        args = (epoch, cfg[\"epoch_size\"], ave_loss, accuracy)\n        print_str = \"[Training]Epoch:%d/%d,Average Loss:%.3f,Accuracy:%.2f%%\"\n        print(print_str % args)\n\n        # Validation\n        sum_loss = 0.0\n        sum_correct = 0\n        for imgs, true_labels in tqdm(valid_loader):\n            pred_probs = network(imgs)\n            pred_labels = torch.argmax(pred_probs, dim=1)\n            loss = criterion(pred_probs, true_labels)\n            sum_loss += loss.item() * imgs.shape[0]\n            sum_correct += int(torch.sum(pred_labels == true_labels))\n        ave_loss = sum_loss / len(valid_dataset)\n        accuracy = 100.0 * sum_correct / len(valid_dataset)\n        result[\"Epoch\"].append(epoch)\n        result[\"Type\"].append(\"Validation\")\n        result[\"Average Loss\"].append(ave_loss)\n        result[\"Accuracy\"].append(accuracy)\n        args = (epoch, cfg[\"epoch_size\"], ave_loss, accuracy)\n        print_str = \"[Validation]Epoch:%d/%d,Average Loss:%.3f,Accuracy:%.2f%%\"\n        print(print_str % args)\n\n    return result","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"%%time\nresult = learn(network,\n               train_dataset,\n               valid_dataset,\n               cfg)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"sns.relplot(x=\"Epoch\",\n            y=\"Average Loss\",\n            hue=\"Type\",\n            kind=\"line\",\n            data=pd.DataFrame(result))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"sns.relplot(x=\"Epoch\",\n            y=\"Accuracy\",\n            hue=\"Type\",\n            kind=\"line\",\n            data=pd.DataFrame(result))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 4. Test"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def test(test_dataset: KannadaMNISTDataset,\n         network: torch.nn.Module,\n         cfg: dict):\n    test_true_labels = np.array([])\n    test_pred_labels = np.array([])\n    test_loader = torch.utils.data.DataLoader(test_dataset,\n                                              batch_size=cfg[\"batch_size\"])\n\n    # Prediction\n    for imgs, true_labels in tqdm(test_loader):\n        pred_probs = network(imgs)\n        pred_labels = torch.argmax(pred_probs, dim=1)\n        test_true_labels = np.concatenate([test_true_labels,\n                                           true_labels.cpu().numpy()])\n        test_pred_labels = np.concatenate([test_pred_labels,\n                                           pred_labels.cpu().numpy()])\n    return test_true_labels, test_pred_labels","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"%%time\ntest_true_labels, test_pred_labels = test(test_dataset, network, cfg)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"target_str = [\"Image No.%d\" % num for num in range(10)]\nreport_str = classification_report(test_true_labels,\n                                   test_pred_labels,\n                                   target_names=target_str,\n                                   digits=3)\nprint(report_str)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cm = pd.DataFrame(confusion_matrix(test_true_labels, test_pred_labels),\n                  columns=np.unique(test_true_labels),\n                  index=np.unique(test_pred_labels))\ncm.index.name = \"True Image No.\"\ncm.columns.name = \"Predicted Image No.\"\nsns.heatmap(cm, annot=True, cmap=\"Blues\", fmt=\"d\")","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"}},"nbformat":4,"nbformat_minor":1}