diff --git a/CoordConv.ipynb b/CoordConv.ipynb new file mode 100644 index 0000000..1d6cc7e --- /dev/null +++ b/CoordConv.ipynb @@ -0,0 +1,625 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.modules.conv as conv\n", + "import torch.utils.data as utils\n", + "import torch.nn.functional as F\n", + "\n", + "from torch.autograd import Variable\n", + "from sklearn.model_selection import train_test_split\n", + "from torchsummary import summary" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class AddCoords(nn.Module):\n", + " def __init__(self, rank, with_r=False):\n", + " super(AddCoords, self).__init__()\n", + " self.rank = rank\n", + " self.with_r = with_r\n", + "\n", + " def forward(self, input_tensor):\n", + " r\"\"\"\n", + " input_tensor: (N, C_in,H,W)\n", + " :param input_tensor:\n", + " :return:\n", + " \"\"\"\n", + " if self.rank == 1:\n", + " raise NotImplementedError\n", + " batch_size_shape, channel_in_shape, dim_x = input_tensor.shape\n", + " xx_range = torch.arange(dim_x, dtype=torch.int32).unsqueeze(0)\n", + " xx_channel = xx_range.unsqueeze(0)\n", + "\n", + " xx_channel = xx_channel.float() / (dim_x - 1)\n", + " xx_channel = xx_channel * 2 - 1\n", + " xx_channel = xx_channel.repeat(batch_size_shape, 1, 1)\n", + "\n", + " if torch.cuda.is_available:\n", + " input_tensor = input_tensor.cuda()\n", + " xx_channel = xx_channel.cuda()\n", + " out = torch.cat([input_tensor, xx_channel], dim=1)\n", + "\n", + " if self.with_r:\n", + " rr = torch.sqrt(torch.pow(xx_channel - 0.5, 2))\n", + " out = torch.cat([out, rr], dim=1)\n", + " elif self.rank == 2:\n", + " batch_size_shape, channel_in_shape, dim_y, dim_x = input_tensor.shape\n", + " xx_ones = torch.ones([1, dim_x], dtype=torch.int32)\n", + " yy_ones = torch.ones([1, dim_y], dtype=torch.int32)\n", + "\n", + " xx_range = torch.arange(dim_y, dtype=torch.int32).unsqueeze(0)\n", + " yy_range = torch.arange(dim_x, dtype=torch.int32).unsqueeze(0)\n", + " xx_range = xx_range.unsqueeze(-1)\n", + " yy_range = yy_range.unsqueeze(-1)\n", + " \n", + " xx_channel = torch.matmul(xx_range, xx_ones)\n", + " yy_channel = torch.matmul(yy_range, yy_ones)\n", + " xx_channel = xx_channel.unsqueeze(0)\n", + " yy_channel = yy_channel.unsqueeze(0)\n", + " \n", + " # transpose y\n", + " yy_channel = yy_channel.permute(0, 1, 3, 2)\n", + " \n", + " xx_channel = xx_channel.float() / (dim_y - 1)\n", + " yy_channel = yy_channel.float() / (dim_x - 1)\n", + "\n", + " xx_channel = xx_channel * 2 - 1\n", + " yy_channel = yy_channel * 2 - 1\n", + "\n", + " xx_channel = xx_channel.repeat(batch_size_shape, 1, 1, 1)\n", + " yy_channel = yy_channel.repeat(batch_size_shape, 1, 1, 1)\n", + "\n", + " if torch.cuda.is_available:\n", + " input_tensor = input_tensor.cuda()\n", + " xx_channel = xx_channel.cuda()\n", + " yy_channel = yy_channel.cuda()\n", + " out = torch.cat([input_tensor, xx_channel, yy_channel], dim=1)\n", + "\n", + " if self.with_r:\n", + " rr = torch.sqrt(torch.pow(xx_channel - 0.5, 2) + torch.pow(yy_channel - 0.5, 2))\n", + " out = torch.cat([out, rr], dim=1)\n", + " elif self.rank == 3:\n", + " batch_size_shape, channel_in_shape, dim_z, dim_y, dim_x = input_tensor.shape\n", + " xx_ones = torch.ones([1, dim_x], dtype=torch.int32)\n", + " yy_ones = torch.ones([1, dim_y], dtype=torch.int32)\n", + " zz_ones = torch.ones([1, dim_z], dtype=torch.int32)\n", + "\n", + " xy_range = torch.arange(dim_y, dtype=torch.int32).unsqueeze(0)\n", + " xy_range = xy_range.unsqueeze(-1)\n", + "\n", + " yz_range = torch.arange(dim_z, dtype=torch.int32).unsqueeze(0)\n", + " yz_range = yz_range.unsqueeze(-1)\n", + "\n", + " zx_range = torch.arange(dim_x, dtype=torch.int32).unsqueeze(0)\n", + " zx_range = zx_range.unsqueeze(-1)\n", + "\n", + " xy_channel = torch.matmul(xy_range, xx_ones)\n", + " xy_channel = xy_channel.unsqueeze(0)\n", + " xx_channel = torch.stack([xy_channel + i for i in range(dim_z)], dim=2)\n", + "\n", + " yz_channel = torch.matmul(yz_range, yy_ones)\n", + " yz_channel = yz_channel.unsqueeze(0)\n", + " yy_channel = torch.stack([yz_channel + i for i in range(dim_x)], dim=4)\n", + "\n", + " zx_channel = torch.matmul(zx_range, zz_ones)\n", + " zx_channel = zx_channel.unsqueeze(0)\n", + " zx_channel = zx_channel.permute(0,1,3,2)\n", + " zz_channel = torch.stack([zx_channel + i for i in range(dim_y)], dim=3) \n", + "\n", + " if torch.cuda.is_available:\n", + " input_tensor = input_tensor.cuda()\n", + " xx_channel = xx_channel.cuda()\n", + " yy_channel = yy_channel.cuda()\n", + " zz_channel = zz_channel.cuda()\n", + " out = torch.cat([input_tensor, xx_channel, yy_channel, zz_channel], dim=1)\n", + "\n", + " if self.with_r:\n", + " rr = torch.sqrt(torch.pow(xx_channel - 0.5, 2) +\\\n", + " torch.pow(yy_channel - 0.5, 2) +\\\n", + " torch.pow(zz_channel - 0.5, 2))\n", + " out = torch.cat([out, rr], dim=1)\n", + " else:\n", + " raise NotImplementedError\n", + "\n", + " return out\n", + "\n", + "\n", + "class CoordConv2d(conv.Conv2d):\n", + " def __init__(self, in_channels, out_channels, kernel_size, stride=1,\n", + " padding=0, dilation=1, groups=1, bias=True, with_r=False):\n", + " super(CoordConv2d, self).__init__(in_channels, out_channels, kernel_size,\n", + " stride, padding, dilation, groups, bias)\n", + " self.rank = 2\n", + " self.addcoords = AddCoords(self.rank, with_r)\n", + " self.conv = nn.Conv2d(in_channels+self.rank+int(with_r), out_channels,\n", + " kernel_size, stride, padding, dilation, groups, bias)\n", + "\n", + " def forward(self, input_tensor):\n", + " r\"\"\"\n", + " 输入的尺度是(N, C_in,H,W),输出尺度(N,C_out,H_out,W_out)\n", + " :param input_tensor:\n", + " :return:\n", + " \"\"\"\n", + " out = self.addcoords(input_tensor)\n", + " out = self.conv(out)\n", + "\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate Data" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "datatype = 'uniform'\n", + "assert datatype in ['uniform', 'quadrant']\n", + "\n", + "if not os.path.exists('data-uniform/'):\n", + " os.makedirs('data-uniform/')\n", + "\n", + "if not os.path.exists('data-quadrant/'):\n", + " os.makedirs('data-quadrant/')\n", + "\n", + "np.random.seed(0)\n", + "torch.manual_seed(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "onehots = np.pad(np.eye(3136, dtype='float32').reshape((3136, 56, 56, 1)),\n", + " ((0, 0), (4, 4), (4, 4), (0, 0)), mode=\"constant\")\n", + "onehots = onehots.transpose(0, 3, 1, 2)\n", + "\n", + "onehots_tensor = torch.from_numpy(onehots)\n", + "\n", + "conv_layer = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(9,9), padding=4, stride=1)\n", + "w = torch.ones(1,1,9, 9)\n", + "conv_layer.weight.data = w\n", + "\n", + "images_tensor = conv_layer(onehots_tensor)\n", + "images = images_tensor.detach().numpy()\n", + "\n", + "if datatype == 'uniform':\n", + " # Create the uniform datasets\n", + " indices = np.arange(0, len(onehots), dtype='int32')\n", + " train, test = train_test_split(indices, test_size=0.2, random_state=0)\n", + "\n", + " train_onehot = onehots[train]\n", + " train_images = images[train]\n", + "\n", + " test_onehot = onehots[test]\n", + " test_images = images[test]\n", + "\n", + " np.save('data-uniform/train_onehot.npy', train_onehot)\n", + " np.save('data-uniform/train_images.npy', train_images)\n", + " np.save('data-uniform/test_onehot.npy', test_onehot)\n", + " np.save('data-uniform/test_images.npy', test_images)\n", + "else:\n", + " pos_quadrant = np.where(onehots == 1.0)\n", + "# print(onehots.shape)\n", + " X = pos_quadrant[2]\n", + " Y = pos_quadrant[3]\n", + " \n", + " train_set = []\n", + " test_set = []\n", + "\n", + " train_ids = []\n", + " test_ids = []\n", + "\n", + " for i, (x, y) in enumerate(zip(X, Y)):\n", + " if x > 32 and y > 32: # 4th quadrant\n", + " test_ids.append(i)\n", + " test_set.append([x, y])\n", + " else:\n", + " train_ids.append(i)\n", + " train_set.append([x, y])\n", + "\n", + " train_set = np.array(train_set)\n", + " test_set = np.array(test_set)\n", + " \n", + " train_set = train_set[:, None, None, :]\n", + " test_set = test_set[:, None, None, :]\n", + "\n", + " print(train_set.shape)\n", + " print(test_set.shape)\n", + "\n", + " train_onehot = onehots[train_ids]\n", + " test_onehot = onehots[test_ids]\n", + "\n", + " train_images = images[train_ids]\n", + " test_images = images[test_ids]\n", + "\n", + " print(train_onehot.shape, test_onehot.shape)\n", + " print(train_images.shape, test_images.shape)\n", + "\n", + " np.save('data-quadrant/train_set.npy', train_set)\n", + " np.save('data-quadrant/test_set.npy', test_set)\n", + " np.save('data-quadrant/train_onehot.npy', train_onehot)\n", + " np.save('data-quadrant/train_images.npy', train_images)\n", + " np.save('data-quadrant/test_onehot.npy', test_onehot)\n", + " np.save('data-quadrant/test_images.npy', test_images)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load data numpy" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train set : (2508, 2, 64, 64) 0.93650794 0.06349207\n", + "Test set : (628, 2, 64, 64) 0.93650794 0.06349207\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if datatype == 'uniform':\n", + " # Load the one hot datasets\n", + " train_onehot = np.load('data-uniform/train_onehot.npy').astype('float32')\n", + " test_onehot = np.load('data-uniform/test_onehot.npy').astype('float32')\n", + "\n", + " # (N, C, H, W) <=== 数据格式\n", + " # make the train and test datasets\n", + " # train\n", + " pos_train = np.where(train_onehot == 1.0)\n", + " X_train = pos_train[2]\n", + " Y_train = pos_train[3]\n", + " train_set = np.zeros((len(X_train), 2, 1, 1), dtype='float32')\n", + " for i, (x, y) in enumerate(zip(X_train, Y_train)):\n", + " train_set[i, 0, 0, 0] = x\n", + " train_set[i, 1, 0, 0] = y\n", + "\n", + " # test\n", + " pos_test = np.where(test_onehot == 1.0)\n", + " X_test = pos_test[2]\n", + " Y_test = pos_test[3]\n", + " test_set = np.zeros((len(X_test), 2, 1, 1), dtype='float32')\n", + " for i, (x, y) in enumerate(zip(X_test, Y_test)):\n", + " test_set[i, 0, 0, 0] = x\n", + " test_set[i, 1, 0, 0] = y\n", + "\n", + " train_set = np.tile(train_set, [1, 1, 64, 64])\n", + " test_set = np.tile(test_set, [1, 1, 64, 64])\n", + "\n", + " # Normalize the datasets\n", + " train_set /= (64. - 1.) # 64x64 grid, 0-based index\n", + " test_set /= (64. - 1.) # 64x64 grid, 0-based index\n", + "\n", + " print('Train set : ', train_set.shape, train_set.max(), train_set.min())\n", + " print('Test set : ', test_set.shape, test_set.max(), test_set.min())\n", + "\n", + " # Visualize the datasets\n", + "\n", + " plt.imshow(np.sum(train_onehot, axis=0)[0, :, :], cmap='gray')\n", + " plt.title('Train One-hot dataset')\n", + " plt.show()\n", + " plt.imshow(np.sum(test_onehot, axis=0)[0, :, :], cmap='gray')\n", + " plt.title('Test One-hot dataset')\n", + " plt.show()\n", + "\n", + "else:\n", + " # Load the one hot datasets and the train / test set\n", + " train_set = np.load('data-quadrant/train_set.npy').astype('float32')\n", + " test_set = np.load('data-quadrant/test_set.npy').astype('float32')\n", + "\n", + " train_onehot = np.load('data-quadrant/train_onehot.npy').astype('float32')\n", + " test_onehot = np.load('data-quadrant/test_onehot.npy').astype('float32')\n", + "\n", + " train_set = np.tile(train_set, [1, 1, 64, 64])\n", + " test_set = np.tile(test_set, [1, 1, 64, 64])\n", + "\n", + " # Normalize datasets\n", + " train_set /= train_set.max()\n", + " test_set /= test_set.max()\n", + "\n", + " print('Train set : ', train_set.shape, train_set.max(), train_set.min())\n", + " print('Test set : ', test_set.shape, test_set.max(), test_set.min())\n", + "\n", + " # Visualize the datasets\n", + "\n", + " plt.imshow(np.sum(train_onehot, axis=0)[0, :, :], cmap='gray')\n", + " plt.title('Train One-hot dataset')\n", + " plt.show()\n", + " plt.imshow(np.sum(test_onehot, axis=0)[0, :, :], cmap='gray')\n", + " plt.title('Test One-hot dataset')\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# flatten the datasets\n", + "train_onehot = train_onehot.reshape((-1, 64 * 64)).astype('int64')\n", + "test_onehot = test_onehot.reshape((-1, 64 * 64)).astype('int64')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "----------------------------------------------------------------\n", + " Layer (type) Output Shape Param #\n", + "================================================================\n", + " AddCoords-1 [-1, 5, 64, 64] 0\n", + " Conv2d-2 [-1, 32, 64, 64] 192\n", + " CoordConv2d-3 [-1, 32, 64, 64] 96\n", + " Conv2d-4 [-1, 64, 64, 64] 2,112\n", + " Conv2d-5 [-1, 64, 64, 64] 4,160\n", + " Conv2d-6 [-1, 1, 64, 64] 65\n", + " Conv2d-7 [-1, 1, 64, 64] 2\n", + "================================================================\n", + "Total params: 6,627\n", + "Trainable params: 6,627\n", + "Non-trainable params: 0\n", + "----------------------------------------------------------------\n" + ] + } + ], + "source": [ + "# model definition\n", + "\n", + "class Net(nn.Module):\n", + " def __init__(self):\n", + " super(Net, self).__init__()\n", + " self.coordconv = CoordConv2d(2, 32, 1, with_r=True)\n", + " self.conv1 = nn.Conv2d(32, 64, 1)\n", + " self.conv2 = nn.Conv2d(64, 64, 1)\n", + " self.conv3 = nn.Conv2d(64, 1, 1)\n", + " self.conv4 = nn.Conv2d( 1, 1, 1)\n", + "\n", + " def forward(self, x):\n", + " x = self.coordconv(x)\n", + " x = F.relu(self.conv1(x))\n", + " x = F.relu(self.conv2(x))\n", + " x = F.relu(self.conv3(x))\n", + " x = self.conv4(x)\n", + " x = x.view(-1, 64*64)\n", + " return x\n", + "\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "net = Net().to(device)\n", + "\n", + "summary(net, input_size=(2, 64, 64))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Make Datasets" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "train_tensor_x = torch.stack([torch.Tensor(i) for i in train_set])\n", + "train_tensor_y = torch.stack([torch.LongTensor(i) for i in train_onehot])\n", + "\n", + "train_dataset = utils.TensorDataset(train_tensor_x,train_tensor_y)\n", + "train_dataloader = utils.DataLoader(train_dataset, batch_size=32, shuffle=False)\n", + "\n", + "test_tensor_x = torch.stack([torch.Tensor(i) for i in test_set])\n", + "test_tensor_y = torch.stack([torch.LongTensor(i) for i in test_onehot])\n", + "\n", + "test_dataset = utils.TensorDataset(test_tensor_x,test_tensor_y)\n", + "test_dataloader = utils.DataLoader(test_dataset, batch_size=32, shuffle=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)\n", + "def cross_entropy_one_hot(input, target):\n", + " _, labels = target.max(dim=1)\n", + " return nn.CrossEntropyLoss()(input, labels)\n", + "criterion = cross_entropy_one_hot\n", + "epochs = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def train(epoch, net, train_dataloader, optimizer, criterion, device):\n", + " net.train()\n", + " iters = 0\n", + " for batch_idx, (data, target) in enumerate(train_dataloader):\n", + " data, target = Variable(data), Variable(target)\n", + " data, target = data.to(device), target.to(device)\n", + " optimizer.zero_grad()\n", + " output = net(data)\n", + " loss = criterion(output, target)\n", + " loss.backward()\n", + " optimizer.step()\n", + " iters += len(data)\n", + " print('Train Epoch: {} [{}/{} ({:.0f}%)] Loss: {:.6f}'.format(\n", + " epoch, iters, len(train_dataloader.dataset),\n", + " 100. * (batch_idx + 1) / len(train_dataloader), loss.data.item()), end='\\r', flush=True)\n", + " print(\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train Epoch: 1 [2508/2508 (100%)] Loss: 7.505956\n", + "Train Epoch: 2 [2508/2508 (100%)] Loss: 4.008380\n", + "Train Epoch: 3 [2508/2508 (100%)] Loss: 2.033982\n", + "Train Epoch: 4 [2508/2508 (100%)] Loss: 0.994723\n", + "Train Epoch: 5 [2508/2508 (100%)] Loss: 0.472303\n", + "Train Epoch: 6 [2508/2508 (100%)] Loss: 0.254768\n", + "Train Epoch: 7 [2508/2508 (100%)] Loss: 0.135046\n", + "Train Epoch: 8 [2508/2508 (100%)] Loss: 0.087108\n", + "Train Epoch: 9 [2508/2508 (100%)] Loss: 0.060861\n", + "Train Epoch: 10 [2508/2508 (100%)] Loss: 0.046419\n" + ] + } + ], + "source": [ + "for epoch in range(1, epochs + 1):\n", + " train(epoch, net, train_dataloader, optimizer, criterion, device)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def test(net, test_loader, optimizer, criterion, device):\n", + " net.eval()\n", + " test_loss = 0\n", + " correct = 0\n", + " for data, target in test_loader:\n", + " with torch.no_grad():\n", + " data, target = Variable(data), Variable(target)\n", + " data, target = data.to(device), target.to(device)\n", + " output = net(data)\n", + " test_loss += criterion(output, target).item()\n", + " _, pred = output.max(1, keepdim=True)\n", + " _, label = target.max(dim=1)\n", + " correct += pred.eq(label.view_as(pred)).sum().item()\n", + "\n", + " test_loss = test_loss\n", + " test_loss /= len(test_loader) # loss function already averages over batch size\n", + " print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n", + " test_loss, correct, len(test_loader.dataset),\n", + " 100. * correct / len(test_loader.dataset)))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Test set: Average loss: 0.0461, Accuracy: 628/628 (100%)\n", + "\n" + ] + } + ], + "source": [ + "test(net, test_dataloader, optimizer, criterion, device)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}