{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用卷积神经网络进行图像分类\n",
    "\n",
    "本示例教程将会演示如何使用飞桨的卷积神经网络来完成图像分类任务。这是一个较为简单的示例，将会使用一个由三个卷积层组成的网络完成[cifar10](https://www.cs.toronto.edu/~kriz/cifar.html)数据集的图像分类任务。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 设置环境\n",
    "\n",
    "我们将使用飞桨2.0beta版本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0.0-beta0\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.nn.functional as F\n",
    "from paddle.vision.transforms import Normalize\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "paddle.disable_static()\n",
    "print(paddle.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载并浏览数据集\n",
    "\n",
    "我们将会使用飞桨提供的API完成数据集的下载并为后续的训练任务准备好数据迭代器。cifar10数据集由60000张大小为32 * 32的彩色图片组成，其中有50000张图片组成了训练集，另外10000张图片组成了测试集。这些图片分为10个类别，我们的任务是训练一个模型能够把图片进行正确的分类。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "cifar10_train = paddle.vision.datasets.cifar.Cifar10(mode='train', transform=None)\n",
    "\n",
    "train_images = np.zeros((50000, 32, 32, 3), dtype='float32')\n",
    "train_labels = np.zeros((50000, 1), dtype='int32')\n",
    "for i, data in enumerate(cifar10_train):\n",
    "    train_image, train_label = data\n",
    "    train_image = train_image.reshape((3, 32, 32 )).astype('float32') / 255.\n",
    "    train_image = train_image.transpose(1, 2, 0)\n",
    "    train_images[i, :, :, :] = train_image\n",
    "    train_labels[i, 0] = train_label"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 浏览数据集\n",
    "\n",
    "接下来我们从数据集中随机挑选一些图片并显示，从而对数据集有一个直观的了解。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 437344 characters]\n",
      "text/plain": [
       "<Figure size 720x720 with 25 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "plt.figure(figsize=(10,10))\n",
    "sample_idxs = np.random.choice(50000, size=25, replace=False)\n",
    "\n",
    "for i in range(25):\n",
    "    plt.subplot(5, 5, i+1)\n",
    "    plt.xticks([])\n",
    "    plt.yticks([])\n",
    "    plt.imshow(train_images[sample_idxs[i]], cmap=plt.cm.binary)\n",
    "    plt.xlabel(class_names[train_labels[sample_idxs[i]][0]])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 组建网络\n",
    "\n",
    "接下来我们使用飞桨定义一个使用了三个二维卷积（`Conv2d`)且每次卷积之后使用`relu`激活函数，两个二维池化层（`MaxPool2d`），和两个线性变换层组成的分类网络，来把一个`(32, 32, 3)`形状的图片通过卷积神经网络映射为10个输出，这对应着10个分类的类别。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyNet(paddle.nn.Layer):\n",
    "    def __init__(self, num_classes=1):\n",
    "        super(MyNet, self).__init__()\n",
    "\n",
    "        self.conv1 = paddle.nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3))\n",
    "        self.pool1 = paddle.nn.MaxPool2d(kernel_size=2, stride=2)\n",
    "        \n",
    "        self.conv2 = paddle.nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3,3))\n",
    "        self.pool2 = paddle.nn.MaxPool2d(kernel_size=2, stride=2)    \n",
    "        \n",
    "        self.conv3 = paddle.nn.Conv2d(in_channels=64, out_channels=64, kernel_size=(3,3))\n",
    "\n",
    "        self.flatten = paddle.nn.Flatten()\n",
    "        \n",
    "        self.linear1 = paddle.nn.Linear(in_features=1024, out_features=64)\n",
    "        self.linear2 = paddle.nn.Linear(in_features=64, out_features=num_classes)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.pool1(x)\n",
    "        \n",
    "        x = self.conv2(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.pool2(x)\n",
    "        \n",
    "        x = self.conv3(x)\n",
    "        x = F.relu(x)\n",
    "\n",
    "        x = self.flatten(x)\n",
    "        x = self.linear1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.linear2(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型训练\n",
    "\n",
    "接下来，我们用一个循环来进行模型的训练，我们将会：\n",
    "- 使用`paddle.optimizer.Adam`优化器来进行优化。\n",
    "- 使用`F.softmax_with_cross_entropy`来计算损失值。\n",
    "- 使用`paddle.io.DataLoader`来加载数据并组建batch。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "epoch_num = 10\n",
    "batch_size = 32\n",
    "learning_rate = 0.001"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start training ... \n",
      "epoch: 0, batch_id: 0, loss is: [2.331658]\n",
      "epoch: 0, batch_id: 1000, loss is: [1.6067888]\n",
      "[validation] accuracy/loss: 0.5676916837692261/1.2106356620788574\n",
      "epoch: 1, batch_id: 0, loss is: [1.1509854]\n",
      "epoch: 1, batch_id: 1000, loss is: [1.3777964]\n",
      "[validation] accuracy/loss: 0.5818690061569214/1.1748384237289429\n",
      "epoch: 2, batch_id: 0, loss is: [1.051642]\n",
      "epoch: 2, batch_id: 1000, loss is: [1.0261706]\n",
      "[validation] accuracy/loss: 0.6607428193092346/0.9685573577880859\n",
      "epoch: 3, batch_id: 0, loss is: [0.8457774]\n",
      "epoch: 3, batch_id: 1000, loss is: [0.6820123]\n",
      "[validation] accuracy/loss: 0.6822084784507751/0.9241172075271606\n",
      "epoch: 4, batch_id: 0, loss is: [0.9059805]\n",
      "epoch: 4, batch_id: 1000, loss is: [0.587117]\n",
      "[validation] accuracy/loss: 0.7012779712677002/0.8670551180839539\n",
      "epoch: 5, batch_id: 0, loss is: [1.0894825]\n",
      "epoch: 5, batch_id: 1000, loss is: [0.9055369]\n",
      "[validation] accuracy/loss: 0.6954872012138367/0.8820587992668152\n",
      "epoch: 6, batch_id: 0, loss is: [0.4162583]\n",
      "epoch: 6, batch_id: 1000, loss is: [0.5274862]\n",
      "[validation] accuracy/loss: 0.7074680328369141/0.8538646697998047\n",
      "epoch: 7, batch_id: 0, loss is: [0.52636147]\n",
      "epoch: 7, batch_id: 1000, loss is: [0.70929015]\n",
      "[validation] accuracy/loss: 0.7107627987861633/0.8633227944374084\n",
      "epoch: 8, batch_id: 0, loss is: [0.57556355]\n",
      "epoch: 8, batch_id: 1000, loss is: [0.83717]\n",
      "[validation] accuracy/loss: 0.69319087266922/0.903077244758606\n",
      "epoch: 9, batch_id: 0, loss is: [0.88774866]\n",
      "epoch: 9, batch_id: 1000, loss is: [0.91165334]\n",
      "[validation] accuracy/loss: 0.7194488644599915/0.8668457865715027\n"
     ]
    }
   ],
   "source": [
    "val_acc_history = []\n",
    "val_loss_history = []\n",
    "\n",
    "def train(model):\n",
    "    print('start training ... ')\n",
    "    # turn into training mode\n",
    "    model.train()\n",
    "\n",
    "    opt = paddle.optimizer.Adam(learning_rate=learning_rate, \n",
    "                                parameters=model.parameters())\n",
    "\n",
    "    train_loader = paddle.io.DataLoader(cifar10_train,\n",
    "                                        places=paddle.CPUPlace(), \n",
    "                                        shuffle=True, \n",
    "                                        batch_size=batch_size)\n",
    "    \n",
    "    cifar10_test = paddle.vision.datasets.cifar.Cifar10(mode='test', transform=None)\n",
    "    valid_loader = paddle.io.DataLoader(cifar10_test, places=paddle.CPUPlace(), batch_size=batch_size)\n",
    "\n",
    "    for epoch in range(epoch_num):\n",
    "        for batch_id, data in enumerate(train_loader()):\n",
    "            x_data = paddle.cast(data[0], 'float32')\n",
    "            x_data = paddle.reshape(x_data, (-1, 3, 32, 32)) / 255.0\n",
    "            \n",
    "            y_data = paddle.cast(data[1], 'int64')\n",
    "            y_data = paddle.reshape(y_data, (-1, 1))\n",
    "                        \n",
    "            logits = model(x_data)\n",
    "            loss = F.softmax_with_cross_entropy(logits, y_data)\n",
    "            avg_loss = paddle.mean(loss)\n",
    "            \n",
    "            if batch_id % 1000 == 0:\n",
    "                print(\"epoch: {}, batch_id: {}, loss is: {}\".format(epoch, batch_id, avg_loss.numpy()))\n",
    "            avg_loss.backward()\n",
    "            opt.step()\n",
    "            opt.clear_grad()\n",
    "\n",
    "        # evaluate model after one epoch\n",
    "        model.eval()\n",
    "        accuracies = []\n",
    "        losses = []\n",
    "        for batch_id, data in enumerate(valid_loader()): \n",
    "            x_data = paddle.cast(data[0], 'float32')\n",
    "            x_data = paddle.reshape(x_data, (-1, 3, 32, 32)) / 255.0\n",
    "            \n",
    "            y_data = paddle.cast(data[1], 'int64')\n",
    "            y_data = paddle.reshape(y_data, (-1, 1))           \n",
    "            \n",
    "            logits = model(x_data)            \n",
    "            loss = F.softmax_with_cross_entropy(logits, y_data)\n",
    "            acc = paddle.metric.accuracy(logits, y_data)\n",
    "            accuracies.append(np.mean(acc.numpy()))\n",
    "            losses.append(np.mean(loss.numpy()))\n",
    "        \n",
    "        avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)\n",
    "        print(\"[validation] accuracy/loss: {}/{}\".format(avg_acc, avg_loss))\n",
    "        val_acc_history.append(avg_acc)\n",
    "        val_loss_history.append(avg_loss)\n",
    "        model.train()\n",
    "\n",
    "model = MyNet(num_classes=10)\n",
    "train(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x167d186d0>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 14876 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(val_acc_history, label = 'validation accuracy')\n",
    "\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.ylim([0.5, 0.8])\n",
    "plt.legend(loc='lower right')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The End\n",
    "\n",
    "从上面的示例可以看到，在cifar10数据集上，使用简单的卷积神经网络，用飞桨可以达到71%以上的准确率。你也可以通过调整网络结构和参数，达到更好的效果。"
   ]
  }
 ],
 "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
