{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 图像分类模型离线量化-快速开始"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该教程以图像分类模型MobileNetV1为例，说明如何快速使用PaddleSlim的[离线量化接口](https://github.com/PaddlePaddle/PaddleSlim/blob/develop/docs/docs/api/quantization_api.md)。 该示例包含以下步骤：\n",
    "\n",
    "1. 导入依赖\n",
    "2. 构建模型\n",
    "3. 训练模型\n",
    "4. 离线量化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 导入依赖\n",
    "PaddleSlim依赖Paddle1.7版本，请确认已正确安装Paddle，然后按以下方式导入Paddle和PaddleSlim:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "import paddleslim as slim\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 构建网络\n",
    "该章节构造一个用于对MNIST数据进行分类的分类模型，选用`MobileNetV1`，并将输入大小设置为`[1, 28, 28]`，输出类别数为10。               为了方便展示示例，我们在`paddleslim.models`下预定义了用于构建分类模型的方法，执行以下代码构建分类模型：\n",
    "\n",
    ">注意：paddleslim.models下的API并非PaddleSlim常规API，是为了简化示例而封装预定义的一系列方法，比如：模型结构的定义、Program的构建等。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "exe, train_program, val_program, inputs, outputs = \\\n",
    "    slim.models.image_classification(\"MobileNet\", [1, 28, 28], 10, use_gpu=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. 训练模型\n",
    "该章节介绍了如何定义输入数据和如何训练和测试分类模型。先训练分类模型的原因是离线量化需要一个训练好的模型。\n",
    "\n",
    "### 3.1 定义输入数据\n",
    "\n",
    "为了快速执行该示例，我们选取简单的MNIST数据，Paddle框架的`paddle.dataset.mnist`包定义了MNIST数据的下载和读取。\n",
    "代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle.dataset.mnist as reader\n",
    "train_reader = paddle.fluid.io.batch(\n",
    "        reader.train(), batch_size=128, drop_last=True)\n",
    "test_reader = paddle.fluid.io.batch(\n",
    "        reader.train(), batch_size=128, drop_last=True)\n",
    "train_feeder = fluid.DataFeeder(inputs, fluid.CPUPlace())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 训练和测试\n",
    "先定义训练和测试函数。在训练函数中执行了一个epoch的训练，因为MNIST数据集数据较少，一个epoch就可将top1精度训练到95%以上。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(prog):\n",
    "    iter = 0\n",
    "    for data in train_reader():\n",
    "        acc1, acc5, loss = exe.run(prog, feed=train_feeder.feed(data), fetch_list=outputs)\n",
    "        if iter % 100 == 0:\n",
    "            print('train', acc1.mean(), acc5.mean(), loss.mean())\n",
    "        iter += 1\n",
    "        \n",
    "def test(prog, outputs=outputs):\n",
    "    iter = 0\n",
    "    res = [[], []]\n",
    "    for data in train_reader():\n",
    "        acc1, acc5, loss = exe.run(prog, feed=train_feeder.feed(data), fetch_list=outputs)\n",
    "        if iter % 100 == 0:\n",
    "            print('test', acc1.mean(), acc5.mean(), loss.mean())\n",
    "        res[0].append(acc1.mean())\n",
    "        res[1].append(acc5.mean())\n",
    "        iter += 1\n",
    "    print('final test result', np.array(res[0]).mean(), np.array(res[1]).mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "调用``train``函数训练分类网络，``train_program``是在第2步：构建网络中定义的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('train', 0.0625, 0.5234375, 2.6373053)\n",
      "('train', 0.9375, 0.9921875, 0.20106347)\n",
      "('train', 0.953125, 1.0, 0.13234669)\n",
      "('train', 0.96875, 0.9921875, 0.18056682)\n",
      "('train', 0.9453125, 1.0, 0.15847622)\n"
     ]
    }
   ],
   "source": [
    "train(train_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "调用``test``函数测试分类网络，``val_program``是在第2步：构建网络中定义的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('test', 0.9609375, 0.9921875, 0.12996897)\n",
      "('test', 0.9609375, 1.0, 0.094265014)\n",
      "('test', 0.9453125, 1.0, 0.10511534)\n",
      "('test', 0.9765625, 1.0, 0.11341806)\n",
      "('test', 0.953125, 1.0, 0.17046008)\n",
      "('final test result', 0.9647603, 0.99943244)\n"
     ]
    }
   ],
   "source": [
    "test(val_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "保存inference model，将训练好的分类模型保存在``'./inference_model'``下，后续进行离线量化时将加载保存在此处的模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[u'save_infer_model/scale_0',\n",
       " u'save_infer_model/scale_1',\n",
       " u'save_infer_model/scale_2']"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "target_vars = [val_program.global_block().var(name) for name in outputs]\n",
    "fluid.io.save_inference_model(dirname='./inference_model',\n",
    "        feeded_var_names=[var.name for var in inputs],\n",
    "        target_vars=target_vars,\n",
    "        executor=exe,\n",
    "        main_program=val_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 离线量化\n",
    "\n",
    "调用离线量化接口，加载文件夹``'./inference_model'``训练好的分类模型，并使用10个batch的数据进行参数校正。此过程无需训练，只需跑前向过程来计算量化所需参数。离线量化后的模型保存在文件夹``'./quant_post_model'``下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2020-02-06 09:32:42,944-INFO: run batch: 0\n",
      "2020-02-06 09:32:42,944-INFO: run batch: 0\n",
      "2020-02-06 09:32:43,233-INFO: run batch: 5\n",
      "2020-02-06 09:32:43,233-INFO: run batch: 5\n",
      "2020-02-06 09:32:43,362-INFO: all run batch: 10\n",
      "2020-02-06 09:32:43,362-INFO: all run batch: 10\n",
      "2020-02-06 09:32:43,365-INFO: calculate scale factor ...\n",
      "2020-02-06 09:32:43,365-INFO: calculate scale factor ...\n",
      "2020-02-06 09:32:54,841-INFO: update the program ...\n",
      "2020-02-06 09:32:54,841-INFO: update the program ...\n"
     ]
    }
   ],
   "source": [
    "slim.quant.quant_post(\n",
    "        executor=exe,\n",
    "        model_dir='./inference_model',\n",
    "        quantize_model_path='./quant_post_model',\n",
    "        sample_generator=reader.test(),\n",
    "        batch_nums=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "加载保存在文件夹``'./quant_post_model'``下的量化后的模型进行测试，可看到精度和``3.2 训练和测试``中得到的测试精度相近，因此离线量化过程对于此分类模型几乎无损。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('test', 0.9765625, 0.9921875, 0.11411239)\n",
      "('test', 0.953125, 1.0, 0.111179784)\n",
      "('test', 0.953125, 1.0, 0.101078615)\n",
      "('test', 0.96875, 1.0, 0.0993958)\n",
      "('test', 0.9609375, 1.0, 0.16066414)\n",
      "('final test result', 0.9643096, 0.99931556)\n"
     ]
    }
   ],
   "source": [
    "quant_post_prog, feed_target_names, fetch_targets = fluid.io.load_inference_model(\n",
    "        dirname='./quant_post_model',\n",
    "        model_filename='__model__',\n",
    "        params_filename='__params__',\n",
    "        executor=exe)\n",
    "test(quant_post_prog, fetch_targets)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
