{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 开发入门\n",
    "\n",
    "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/orange_pi/mindspore_dev_start.ipynb) [![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/orange_pi/mindspore_dev_start.py) [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/tutorials/source_zh_cn/orange_pi/dev_start.ipynb)\n",
    "\n",
    "因开发者可能会在OrangePi AIpro(下称:香橙派开发板)进行自定义模型和案例开发,本章节通过基于MindSpore的手写数字识别案例,说明香橙派开发板中的开发注意事项。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 环境准备\n",
    "\n",
    "开发者拿到香橙派开发板后,首先需要进行硬件资源确认、镜像烧录以及CANN和MindSpore版本的升级,才可运行该案例,具体如下:\n",
    "\n",
    "- 硬件:香橙派AIpro 16G 8-12T开发板\n",
    "- 镜像:香橙派官网Ubuntu镜像\n",
    "- CANN:8.0.RC3.alpha002\n",
    "- MindSpore:2.4.10\n",
    "\n",
    "### 镜像烧录\n",
    "\n",
    "运行该案例需要烧录香橙派官网Ubuntu镜像,参考[镜像烧录](https://www.mindspore.cn/tutorials/zh-CN/master/orange_pi/environment_setup.html#1-%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95%E4%BB%A5windows%E7%B3%BB%E7%BB%9F%E4%B8%BA%E4%BE%8B)章节。\n",
    "\n",
    "### CANN升级\n",
    "\n",
    "参考[CANN升级](https://www.mindspore.cn/tutorials/zh-CN/master/orange_pi/environment_setup.html#3-cann%E5%8D%87%E7%BA%A7)章节。\n",
    "\n",
    "### MindSpore升级\n",
    "\n",
    "参考[MindSpore升级](https://www.mindspore.cn/tutorials/zh-CN/master/orange_pi/environment_setup.html#4-mindspore%E5%8D%87%E7%BA%A7)章节。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n"
     ]
    }
   ],
   "source": [
    "from mindspore import nn\n",
    "from mindspore.dataset import vision, transforms\n",
    "from mindspore.dataset import MnistDataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 设置运行环境\n",
    "\n",
    "由于资源限制,需开启性能优化模式,具体设置如下参数:\n",
    "\n",
    " max_device_memory=\"2GB\" : 设置设备可用的最大内存为2GB。\n",
    "\n",
    " mode=mindspore.GRAPH_MODE : 表示在GRAPH_MODE模式中运行。\n",
    "\n",
    " device_target=\"Ascend\" : 表示待运行的目标设备为Ascend。\n",
    "\n",
    " jit_config={\"jit_level\":\"O2\"} : 编译优化级别开启极致性能优化,使用下沉的执行方式。\n",
    "\n",
    " ascend_config={\"precision_mode\":\"allow_mix_precision\"} : 自动混合精度,自动将部分算子的精度降低到float16或bfloat16。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import mindspore\n",
    "mindspore.set_context(max_device_memory=\"2GB\", mode=mindspore.GRAPH_MODE, device_target=\"Ascend\", jit_config={\"jit_level\":\"O2\"}, ascend_config={\"precision_mode\":\"allow_mix_precision\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据集准备与加载\n",
    "\n",
    "MindSpore提供基于Pipeline的[数据引擎](https://www.mindspore.cn/docs/zh-CN/master/design/data_engine.html),通过[数据集(Dataset)](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/dataset.html)实现高效的数据预处理。在本案例中,我们使用Mnist数据集,自动下载完成后,使用`mindspore.dataset`提供的数据变换进行预处理。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://repo.huaweicloud.com/repository/pypi/simple/\n",
      "Requirement already satisfied: download in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (0.3.5)\n",
      "Requirement already satisfied: tqdm in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (4.66.5)\n",
      "Requirement already satisfied: six in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (1.16.0)\n",
      "Requirement already satisfied: requests in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (2.32.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.4.0)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.10)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2.2.3)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2024.8.30)\n"
     ]
    }
   ],
   "source": [
    "#install download\n",
    "\n",
    "!pip install download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zip (10.3 MB)\n",
      "\n",
      "file_sizes: 100%|███████████████████████████| 10.8M/10.8M [00:00<00:00, 101MB/s]\n",
      "Extracting zip file...\n",
      "Successfully downloaded / unzipped to ./\n"
     ]
    }
   ],
   "source": [
    "# Download data from open datasets\n",
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/\" \\\n",
    "      \"notebook/datasets/MNIST_Data.zip\"\n",
    "path = download(url, \"./\", kind=\"zip\", replace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MNIST数据集目录结构如下:\n",
    "\n",
    "```text\n",
    "MNIST_Data\n",
    "└── train\n",
    "    ├── train-images-idx3-ubyte (60000个训练图片)\n",
    "    ├── train-labels-idx1-ubyte (60000个训练标签)\n",
    "└── test\n",
    "    ├── t10k-images-idx3-ubyte (10000个测试图片)\n",
    "    ├── t10k-labels-idx1-ubyte (10000个测试标签)\n",
    "\n",
    "```\n",
    "\n",
    "数据下载完成后,获得数据集对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_dataset = MnistDataset('MNIST_Data/train')\n",
    "test_dataset = MnistDataset('MNIST_Data/test')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "打印数据集中包含的数据列名,用于dataset的预处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['image', 'label']\n"
     ]
    }
   ],
   "source": [
    "print(train_dataset.get_col_names())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MindSpore的dataset使用数据处理流水线(Data Processing Pipeline),需指定map、batch、shuffle等操作。这里我们使用map对图像数据及标签进行变换处理,将输入的图像缩放为1/255,根据均值0.1307和标准差值0.3081进行归一化处理,然后将处理好的数据集打包为大小为64的batch。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def datapipe(dataset, batch_size):\n",
    "    image_transforms = [\n",
    "        vision.Rescale(1.0 / 255.0, 0),\n",
    "        vision.Normalize(mean=(0.1307,), std=(0.3081,)),\n",
    "        vision.HWC2CHW()\n",
    "    ]\n",
    "    label_transform = transforms.TypeCast(mindspore.int32)\n",
    "\n",
    "    dataset = dataset.map(image_transforms, 'image')\n",
    "    dataset = dataset.map(label_transform, 'label')\n",
    "    dataset = dataset.batch(batch_size)\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Map vision transforms and batch dataset\n",
    "train_dataset = datapipe(train_dataset, 64)\n",
    "test_dataset = datapipe(test_dataset, 64)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可使用[create_tuple_iterator](https://www.mindspore.cn/docs/zh-CN/master/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_tuple_iterator.html) 或[create_dict_iterator](https://www.mindspore.cn/docs/zh-CN/master/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_dict_iterator.html)对数据集进行迭代访问,查看数据和标签的shape和datatype。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32\n",
      "Shape of label: (64,) Int32\n"
     ]
    }
   ],
   "source": [
    "for image, label in test_dataset.create_tuple_iterator():\n",
    "    print(f\"Shape of image [N, C, H, W]: {image.shape} {image.dtype}\")\n",
    "    print(f\"Shape of label: {label.shape} {label.dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32\n",
      "Shape of label: (64,) Int32\n"
     ]
    }
   ],
   "source": [
    "for data in test_dataset.create_dict_iterator():\n",
    "    print(f\"Shape of image [N, C, H, W]: {data['image'].shape} {data['image'].dtype}\")\n",
    "    print(f\"Shape of label: {data['label'].shape} {data['label'].dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network<\n",
      "  (flatten): Flatten<>\n",
      "  (dense_relu_sequential): SequentialCell<\n",
      "    (0): Dense<input_channels=784, output_channels=512, has_bias=True>\n",
      "    (1): ReLU<>\n",
      "    (2): Dense<input_channels=512, output_channels=512, has_bias=True>\n",
      "    (3): ReLU<>\n",
      "    (4): Dense<input_channels=512, output_channels=10, has_bias=True>\n",
      "    >\n",
      "  >\n"
     ]
    }
   ],
   "source": [
    "# Define model\n",
    "class Network(nn.Cell):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.flatten = nn.Flatten()\n",
    "        self.dense_relu_sequential = nn.SequentialCell(\n",
    "            nn.Dense(28*28, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Dense(512, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Dense(512, 10)\n",
    "        )\n",
    "\n",
    "    def construct(self, x):\n",
    "        x = self.flatten(x)\n",
    "        logits = self.dense_relu_sequential(x)\n",
    "        return logits\n",
    "\n",
    "model = Network()\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在模型训练中,一个完整的训练过程(step)需要实现以下三步:\n",
    "\n",
    "1. **正向计算**:模型预测结果(logits),并与正确标签(label)求预测损失(loss)。\n",
    "2. **反向传播**:利用自动微分机制,自动求模型参数(parameters)对于loss的梯度(gradients)。\n",
    "3. **参数优化**:将梯度更新到参数上。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MindSpore使用函数式自动微分机制,因此针对上述步骤需要实现:\n",
    "\n",
    "1. 定义正向计算函数。\n",
    "2. 使用[value_and_grad](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.value_and_grad.html)通过函数变换获得梯度计算函数。\n",
    "3. 定义训练函数,使用[set_train](https://www.mindspore.cn/docs/zh-CN/master/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.set_train)设置为训练模式,执行正向计算、反向传播和参数优化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate loss function and optimizer\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = nn.SGD(model.trainable_params(), 1e-2)\n",
    "\n",
    "# 1. Define forward function\n",
    "def forward_fn(data, label):\n",
    "    logits = model(data)\n",
    "    loss = loss_fn(logits, label)\n",
    "    return loss, logits\n",
    "\n",
    "# 2. Get gradient function\n",
    "grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)\n",
    "\n",
    "# 3. Define function of one-step training\n",
    "def train_step(data, label):\n",
    "    (loss, _), grads = grad_fn(data, label)\n",
    "    optimizer(grads)\n",
    "    return loss\n",
    "\n",
    "def train(model, dataset):\n",
    "    size = dataset.get_dataset_size()\n",
    "    model.set_train()\n",
    "    for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):\n",
    "        loss = train_step(data, label)\n",
    "\n",
    "        if batch % 100 == 0:\n",
    "            loss, current = loss.asnumpy(), batch\n",
    "            print(f\"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除训练外,我们定义测试函数,用来评估模型的性能。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(model, dataset, loss_fn):\n",
    "    num_batches = dataset.get_dataset_size()\n",
    "    model.set_train(False)\n",
    "    total, test_loss, correct = 0, 0, 0\n",
    "    for data, label in dataset.create_tuple_iterator():\n",
    "        pred = model(data)\n",
    "        total += len(data)\n",
    "        test_loss += loss_fn(pred, label).asnumpy()\n",
    "        correct += (pred.argmax(1) == label).asnumpy().sum()\n",
    "    test_loss /= num_batches\n",
    "    correct /= total\n",
    "    print(f\"Test: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练过程需多次迭代数据集,一次完整的迭代称为一轮(epoch)。在每一轮,遍历训练集进行训练,结束后使用测试集进行预测。打印每一轮的loss值和预测准确率(Accuracy),可以看到loss在不断下降,Accuracy在不断提高。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1\n",
      "-------------------------------\n",
      "loss: 2.307922  [  0/938]\n",
      "loss: 1.746887  [100/938]\n",
      "loss: 0.848251  [200/938]\n",
      "loss: 0.607513  [300/938]\n",
      "loss: 0.369690  [400/938]\n",
      "loss: 0.382843  [500/938]\n",
      "loss: 0.293686  [600/938]\n",
      "loss: 0.391556  [700/938]\n",
      "loss: 0.227386  [800/938]\n",
      "loss: 0.189972  [900/938]\n",
      "Test: \n",
      " Accuracy: 91.1%, Avg loss: 0.314895 \n",
      "\n",
      "Epoch 2\n",
      "-------------------------------\n",
      "loss: 0.346725  [  0/938]\n",
      "loss: 0.268921  [100/938]\n",
      "loss: 0.247742  [200/938]\n",
      "loss: 0.196686  [300/938]\n",
      "loss: 0.264954  [400/938]\n",
      "loss: 0.320938  [500/938]\n",
      "loss: 0.368820  [600/938]\n",
      "loss: 0.274811  [700/938]\n",
      "loss: 0.373581  [800/938]\n",
      "loss: 0.441010  [900/938]\n",
      "Test: \n",
      " Accuracy: 92.8%, Avg loss: 0.247373 \n",
      "\n",
      "Epoch 3\n",
      "-------------------------------\n",
      "loss: 0.168976  [  0/938]\n",
      "loss: 0.313812  [100/938]\n",
      "loss: 0.195068  [200/938]\n",
      "loss: 0.329803  [300/938]\n",
      "loss: 0.464447  [400/938]\n",
      "loss: 0.170197  [500/938]\n",
      "loss: 0.280670  [600/938]\n",
      "loss: 0.324707  [700/938]\n",
      "loss: 0.134583  [800/938]\n",
      "loss: 0.191467  [900/938]\n",
      "Test: \n",
      " Accuracy: 93.9%, Avg loss: 0.207696 \n",
      "\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "epochs = 3\n",
    "for t in range(epochs):\n",
    "    print(f\"Epoch {t+1}\\n-------------------------------\")\n",
    "    train(model, train_dataset)\n",
    "    test(model, test_dataset, loss_fn)\n",
    "print(\"Done!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 保存模型\n",
    "\n",
    "模型训练完成后,需要将其参数进行保存。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saved Model to model.ckpt\n"
     ]
    }
   ],
   "source": [
    "# Save checkpoint\n",
    "mindspore.save_checkpoint(model, \"model.ckpt\")\n",
    "print(\"Saved Model to model.ckpt\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 权重加载"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "加载保存的权重分为两步:\n",
    "\n",
    "1. 重新实例化模型对象,构造模型。\n",
    "2. 加载模型参数,并将其加载至模型上。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[]\n"
     ]
    }
   ],
   "source": [
    "# Instantiate a random initialized model\n",
    "model = Network()\n",
    "# Load checkpoint and load parameter to model\n",
    "param_dict = mindspore.load_checkpoint(\"model.ckpt\")\n",
    "param_not_load, _ = mindspore.load_param_into_net(model, param_dict)\n",
    "print(param_not_load)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> `param_not_load`是未被加载的参数列表,为空时代表所有参数均加载成功。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型推理\n",
    "\n",
    "加载后的模型可以直接用于预测推理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicted: \"[2 1 0 4 1 7]\", Actual: \"[2 1 0 4 1 7]\"\n"
     ]
    },
    {
     "data": {
      "image/png": "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5lskm4xYQAgAAEpFMwAAgHI0AwAAKEczAACAcjQDAAAo594bFOVu8eLF3tx3NUHHjh2dLJWvJkBqGzlypDevX7++k504ccLJfLfnTsYrB5A63n33XSe78847nWzhwoXe/Rs3buxkDRs2dLJly5Y52VVXXeVkqXKFQUnMDAAAoBzNAAAAytEMAACgHM0AAADKcTviJPL11187WV5enpN17tw5AdWUD27pmjqaNGniZO+//753W98irHXr1jnZH/7wh9gLCwljNzrnnXeek/kW8fnGjohIgwYNAp1n5cqVTta1a1cna9eunXf/JUuWBK6ppE2bNjlZmzZtAu2bSNyOGAAAlIpmAAAA5WgGAABQjmYAAADluANhkvPdgRBIhJycHCcLurBKROTVV1+NZzlIYr7FgkuXLg20XaKsX7/em994441OFvQur+eff76TTZ061bvt3/72t0DHDAszAwAAKEczAACAcjQDAAAoRzMAAIBy3IEwifi+FVOmTHGye++9NxHllAvu4pacrrjiCidbtWqVk1Wq5P/9oaCgwMnq1KnjZD///HP0xSUJxm5kvre2nj17dkLOffToUSfz3YFw7dq13v1r1KjhZNOmTXOyMWPGBKrn0KFD3jwzMzPQ/uWBOxACAIBS0QwAAKAczQAAAMrRDAAAoBzNAAAAynE74hC0atXKm4exWhn6VKni/rP33So10pUDPv369XOyVL5yANHxXU0Q1N69e735J5984mSXXXaZk/muWrn99tudbPDgwd7zHDt2zMnGjRvnZC1atHCyXr16OVnVqlW952natKmT5eXlebcNAzMDAAAoRzMAAIByNAMAAChHMwAAgHJqFxCmp6c72R//+EfvthdccIGT+W4T6lsAuHPnTicbMGBAkBJFROTgwYOBtwWC8C3C6t69e6B916xZ483feuutmGpCaoi0CO/8888v8zGff/55b37PPfc42Zw5c5zMt4gv0qLEoI4fP+5kkW4zXJJvQaOIf5HtjBkzoiusHDEzAACAcjQDAAAoRzMAAIByNAMAACinYgGh745/Tz/9tJNFWkDoWxgYdAFh0O1E/O/LvW7dOu+2QBD169d3shdeeMHJfOPUt2Bq8uTJ3vP47uKGimf8+PHe3HdXS59nn33WyaZOnRr4/L47HZ577rlO9tVXXwU+JixmBgAAUI5mAAAA5WgGAABQjmYAAADlKtwCQt9CqIULFzpZ0LsKiojs2LHDyb7//nsn279/v5P5FiX67n4YKb/00kud7IMPPvDuD5R04YUXOlmTJk2czLeo1XdnuNzc3PgUhpTkW6wXjfvvv9/JYn2raxYLxgczAwAAKEczAACAcjQDAAAoRzMAAIByFW4BYceOHZ3M9/aavgVTjz76qPeYvrtu/fbbb06WmZnpZGvXrnWymjVres/jW8A4ffp0J/vss8+c7O233/YeEzpUrlzZm0+cODHQ/r7Fr763iwVQJJo7zCY7ZgYAAFCOZgAAAOVoBgAAUI5mAAAA5WgGAABQrsJdTeBb0e9b8elbfX/nnXcGPk+9evWcbNWqVU6WlZUV6NwiIqNGjXKyV155xcl8t1e+6KKLnGzPnj3e86Di6dChgzfv0aNHoP3nz5/vZF9++WVMNQGpqnHjxk7mu1ItVa8c8GFmAAAA5WgGAABQjmYAAADlaAYAAFCuwi0g9C3o8GWLFy8OfEzfokTfe737bnu8adMmJ7vhhhu85/Et+Lv44oud7Mcff3SyCRMmOJlvQSIqpp49ewbe1vfv4c0334xnOYCXb+F12M4++2wnmzVrlpOdddZZgY73008/efPly5dHV1iCMTMAAIByNAMAAChHMwAAgHI0AwAAKFfhFhDu2LHDyXx3ILzgggucrFu3bt5jzpgxw8latWrlZHl5eU42evRoJ4vmzoBHjx4NlPnujpWenh74mEgdffv2dbJx48YF3v+JJ55wsmXLlsVUE3T44YcfvHmk/2tKmjhxopMNGTLEu21+fn7wwkqoXr26k/31r3/1btu+fXsn69OnT6DzFBQUONmzzz7r3XbLli2BjhkWZgYAAFCOZgAAAOVoBgAAUI5mAAAA5dJMwPdg9C3CS0ZNmzZ1su3btwfaN9LX6HuKdu7c6WQ5OTlOlpubG+jc0Xj44YedzLc4plmzZt79fQsdEyWMt/xMlbEblG/sNWnSxLvt999/72S+u1qGOSZSBWNX5JZbbvHms2fPdrKgtS9YsMCb33///U62b98+J7v55pudzLcosF+/foHqiYbvbb5btGgR9/PEKsjYZWYAAADlaAYAAFCOZgAAAOVoBgAAUK7CLSD0mTZtmpNFuhuVz9y5c53s8ccfd7LNmzdHV1gZLV261Ml69erlZK1bt/bun6g6fViEFZ2GDRs62WeffeZkvrfZFhFZu3atk3Xo0CH2whRi7Ea2e/duJ2vUqFEIlUTPdxdB311aFy1a5GSPPfaYk/netj5sLCAEAAClohkAAEA5mgEAAJSjGQAAQDmaAQAAlKsSdgGJcM899zjZ6tWrnezAgQPe/T/66KO41xSL/fv3O9nbb7/tZGFeNYD4GDlypJP5rhyItIK5f//+8S4JcEyZMsXJ5syZE0IlkUVaUe+7vbvvZ0ZFx8wAAADK0QwAAKAczQAAAMrRDAAAoJyK2xEjeXBL18g6duzoZG+88YaTVatWzckGDRrkPeZLL70Ue2EQEcZutMaOHetkkyZNcrJ69eqVfzEi8uCDD3rz8ePHJ+T8YeJ2xAAAoFQ0AwAAKEczAACAcjQDAAAoxwJCJBSLsCLr1auXky1dutTJhg8f7mTPPvus95hhPN8VFWMXqYoFhAAAoFQ0AwAAKEczAACAcjQDAAAoxwJCJBSLsJCqGLtIVSwgBAAApaIZAABAOZoBAACUoxkAAEA5mgEAAJSjGQAAQDmaAQAAlKMZAABAOZoBAACUoxkAAEA5mgEAAJSjGQAAQDmaAQAAlKMZAABAOZoBAACUSzNhvEk3AABIGswMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDHg0bSpy001Ff1+1SiQtzT4mi5I1AiKMXaQuxm64krIZ+Ne/7CAo/DjtNJEWLURGjxbZty/s6oJbulTk738Pu4ribr7ZPqd9+oRdScXE2I2fb78VGTdOpHNnkdq1k+8HQ0XD2I2fK68s/lye/FG1ari1RVIl7AJOZcoUkXPOETl+XGTNGpE5c+w3euNGkZo1E1dHx44ix46JVKsW3X5Ll4rMnh3+wCz00Uf2H/xpp4VdScXH2I3dF1+IPPSQSPPmIm3aiLz/fni1aMLYjd2ECSI5OcWzo0dFbrlFpEePcGoqTVI3A1ddJXLJJfbPOTkiGRkijz0msmSJyHXXudsfPSqSnh7/OipVSv0foMaIjBkjcuONIrm5YVdT8TF2Y5edLXLokMjpp4ssWiQyaFDYFenA2I1d9+5u9sIL9vGGGxJbS1BJ+TJBJF262Mevv7av29SqJbJ9u8jVV9tpxMInuaBAZPp0kQsvtIOpYUORESNEjhwpfjxjRO67T6RJE9vxdu4ssmmTe95Ir119+KE9d/369h9D27YiM2bYz910k+1ORYpPERWKd40i9rnYvt3/uXnzbGd///3+z6N8MXajH7u1a9tGAOFi7Jb9/92TzZ9v6+3Xr/Rtw5DUMwMlFT7hGRn2MT9fpGdPkSuuEHnkkaIprBEj7HT4sGH2t+GvvxaZNUtkwwaRd98tes1m0iT7Db/6avuxfr2dwvn119JrefNN+7p7o0Yit90m8rvfiXz+ucj//mf/PmKEyJ49drt589z9y6PGrl3tY15e8fzHH0Xuvltk/HhbJxKPsVu2sYvwMXZjH7sHDtiaBg8un1mUuDBJ6LnnjBExZsUKYw4cMGbXLmMWLDAmI8OYGjWM+eYbY4YOtduMG1d833fesfmLLxbPX3+9eL5/vzHVqhnTu7cxBQVF240fb7cbOrQoW7nSZitX2r/n5xtzzjnGZGUZc+RI8fOcfKxRo+x+JZVHjcbYerKy3PPdcYet9/jxou1693a3Q+wYu9HXaEzksVto4cLiXwfij7EbfY3GlD52jTFm5ky779Klp94uTEn9MkG3biKZmSJnnSUyZIidnnr5ZZHGjYu2GTmy+D4LF4rUrWtfszl4sOgjO9vuv3Kl3W7FCtvl3Xpr8WmksWNLr2vDBttRjh0rUq9e8c+dfKxIyqvGvDy3O9261U6hPfywSPXqpdeG+GDsxj52EQ7GbvzH7vz59jn1rSVIFkn9MsHs2fbSlipV7Gs7LVvaRSWFqlSxr+mcbNs2ke+/FznjDP8x9++3jzt22MfmzYt/PjPTvhZ1KoXTZq1bB/s6SkpEjYVuu03ksstErrmmbLWibBi7Za8R4WLslr1Gn6++slfCjB5tn7tklcSlibRvX7Sq1ad69eKDVMQuEDnjDJEXX/Tvk5kZv/rKKlE1vvWWyOuviyxeXLxzzc+3l+zk5dkFWnXqxOd8KMLYRapi7MbX/Pn2MVmvIiiU1M1AWTRrZqd5Lr9cpEaNyNtlZdnHbdtEzj23KD9wwF1Z6juHiF2d361b5O0iTV0lokYRkZ077eOAAe7ndu+21xI//niwKTqUP8YuUhVjN7L58+25O3SIft9ESuo1A2Vx7bUiJ06ITJ3qfi4/X+S77+yfu3WzK0dnzrSXkRSaPr30c7RrZ3+QTp9edLxCJx+rcNVoyW3Kq8aSl7h06WJf6yv5kZlpO/+XXxbp27fULxcJwtgtvX4kJ8au/3MbNtirHa6/3v/5ZFLhZgY6dbKXj0ybJvLJJ/ZykKpVbZe3cKFdTDdwoP2BeMcddrs+fezlIxs2iCxbJtKgwanPUamSvStX374iF11kL1Np1EhkyxZ7Lery5Xa77Gz7OGaMvRSncmW7IKe8aix5icvZZ9uPksaOta8F9u8f7bOL8sTYdRdi3XeffSy8xnvePHtXPBGRiROje35Rfhi7/kWEhS9JJPtLBCKS3JcWrlsXeZuhQ41JT4/8+aeeMiY7214SU7u2MW3aGHPXXcbs2VO0zYkTxkyebEyjRna7K680ZuNGe5nIqS5xKbRmjTHdu9vjp6cb07atvYSkUH6+MbfeakxmpjFpae7lLvGs0Zhgl7gUbselheWDsRt9jcZEHrv2dzP/B+KLsRt9jcZEHrsnThjTuLEx7dpFfr6SSZoxJ0+EAAAAbSrcmgEAABAdmgEAAJSjGQAAQDmaAQAAlKMZAABAOZoBAACUC3zTobQgbwsFlCKMK1kZu4gHxi5SVZCxy8wAAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKBclbALiLd69eo52QMPPOBkEydOdLLDhw+XR0lxl5mZ6WT79+93subNm3v3//LLL+NeE3R77733nOyXX35xsh49ejjZb7/9Vi41AQiOmQEAAJSjGQAAQDmaAQAAlKMZAABAuQq3gPDiiy92shEjRjjZQw895GSpsoCwZcuWTlZQUBBCJdBm9OjR3vzSSy91ssqVKzvZ5Zdf7mSrVq2KuS4AsWFmAAAA5WgGAABQjmYAAADlaAYAAFAuZRcQ1qpVy5s/8cQTTta+fXsnO3LkSNxrKg+tW7d2sv/+978hVAKIDBo0yJv7FgsePXrUybZu3Rr3mgDEjpkBAACUoxkAAEA5mgEAAJSjGQAAQLmUXUDYq1cvb96qVSsna9y4sZN9/PHHca+pPDRr1szJ6tatG0Il0KZOnTpO1qBBg8D75+bmOtmePXtiqglA+WBmAAAA5WgGAABQjmYAAADlaAYAAFCOZgAAAOVS9mqCSy65xJv/+OOPTrZly5byLid0v/76q5Pl5+eHUAkqigEDBjjZBRdcEHj/jz76KJ7lIMEyMjKczPf/jIj//12kFmYGAABQjmYAAADlaAYAAFCOZgAAAOVSYgHhmWee6WTDhg3zbutbyKLhPdTfeecdJ8vLy0t8IagwJkyYENP+qXLLb4j8+c9/drIRI0Y42fHjx737Hz58ONB50tLSnMwYE2jfaKTKeebOnetkq1evdrJELNBkZgAAAOVoBgAAUI5mAAAA5WgGAABQLiUWEA4fPtzJIr2vekV7v/Trr78+7BKgwI033uhkzZo1C7z/Sy+95GSrVq2KpSQk0PPPP+9kybjgrqKd55prrnGyt99+28m6du0aXWFlwMwAAADK0QwAAKAczQAAAMrRDAAAoFxKLCCsVatW2CWEpnbt2mGXgArGtzDw8ccfj+mYOTk5Tnbs2LGYjonE6dKli5PdfvvtTta7d++YzlOpkvv7Z0FBQeD9fXfie+aZZ5zMV3s05/Et4uvUqZOTxfr1+Lz66qsx7V9WzAwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgXEpcTaBFvXr1nCwrKyvQvv/5z3/iXA0qgqpVqzrZzJkznax+/fqBjvfaa695859++im6wpBUfKvn165d62Snn356IsqJ6MSJE062d+9eJ4v16pjLLrvMyTp27OhkvisHorkd8bJly5zMd3VEIjAzAACAcjQDAAAoRzMAAIByNAMAACjHAsIkcvfddzvZ+eef72S+xVq5ubnlUhNS28iRI52sV69eZT5epFul+hZ2IbX5bie9e/fuECqJnq/OjIwM77bdu3d3sieeeKLM5z506JA3f+ONN5xs7NixThbWYlxmBgAAUI5mAAAA5WgGAABQjmYAAADlWECYgo4fP+5keXl5iS8ESc+3ADWo7777zslWrVpV9mKABDnrrLOc7J///Kd32549e5b5PIMHD3ayb775xrvtBx98UObzJAIzAwAAKEczAACAcjQDAAAoRzMAAIByKbGA0LeQKZIzzzzTyXxv2Tp79mwn27Vrl/eYderUcbKgb/k6fPhwJ2vevLl326ALWY4cORJoO+jRvn17b+5b4BTUU0895WRbt24t8/GARPEtqI7mrYV9lixZ4mSLFi2K6ZjJhJkBAACUoxkAAEA5mgEAAJSjGQAAQLk0E3BVRVpaWnnXElGNGjWcbPXq1d5t27VrF+iYvrdc3bRpk3fbBg0aOJlvoWKi3HfffU527733hlBJ9GJdxFMWYY7dRPn3v//tzYMuIPzqq6+crGXLlk6m+a2KGbuJ5fs/9rXXXnOytm3bOlmlSu7vuQUFBd7z7Nmzx8n69OnjZJ9++ql3/1QQZOwyMwAAgHI0AwAAKEczAACAcjQDAAAoRzMAAIByKXE74mPHjjnZsGHDvNv6VtX36tXLyWrWrOlkvlWpkezYsSPQdr5bB2/ZssW77ZAhQwKfH3r5bls9YMCAwPvv3r3bybp06eJkmq8cQOL4Vu6LiDz99NNOlpmZ6WS+lfK+/2Pnzp3rPc8zzzzjZAcOHPBuW5ExMwAAgHI0AwAAKEczAACAcjQDAAAolxILCH02btzozQcNGuRk2dnZTuZbiBKJb4HK8uXLA+9f0nXXXefNWUCIICZOnOhkVatWDbz/+++/72Q7d+6MqSYgiA4dOjiZbzyLBP8/Oi8vz8l8i8aDLvrWipkBAACUoxkAAEA5mgEAAJSjGQAAQLmUXUAYjY8//jjsEuLq+PHjYZeABDnvvPOcrHXr1oH3972H+4MPPhhTTUBJzZo1c7IHHnjAyQYOHOhkvgXakWzfvt3Jevfu7WQsFoweMwMAAChHMwAAgHI0AwAAKEczAACAcioWECabNWvWePOff/7ZyXxvtbxu3bq414TkNGrUKCerW7du4P2XLVvmZOvXr4+pJqAk32LBa665Ju7nmTBhgpN9+eWXcT+PRswMAACgHM0AAADK0QwAAKAczQAAAMqxgDAEu3bt8ub5+fmB9v/973/vZCtWrIipJoSvYcOGTpaTkxPTMb/44ouY9geCSEtLC5RVquT+/um7S6aIyIIFC5xs27ZtZagOQTAzAACAcjQDAAAoRzMAAIByNAMAAChHMwAAgHJcTZBE9u7d62R16tQJlCH1NW7c2MnS09MD7fvDDz9481mzZsVUE3Cypk2bevM2bdo4mTHGybZs2eJkc+fO9R7z0UcfdbJff/21lApRVswMAACgHM0AAADK0QwAAKAczQAAAMqxgDCJvPLKK0521113OdnmzZsTUA0SrUWLFk7mu6XrL7/84mSDBw/2HjMvLy/muoBCkcZT3759nSwjI8PJfIukd+zYEXNdiB0zAwAAKEczAACAcjQDAAAoRzMAAIByacZ3myjfhp6FTEC0Ag63uGLsIh4Yu0hVQcYuMwMAAChHMwAAgHI0AwAAKEczAACAcjQDAAAoRzMAAIByNAMAAChHMwAAgHI0AwAAKEczAACAcjQDAAAoRzMAAIByNAMAAChHMwAAgHI0AwAAKEczAACAcjQDAAAoRzMAAIByNAMAAChHMwAAgHJpxhgTdhEAACA8zAwAAKAczQAAAMrRDAAAoBzNAAAAytEMAACgHM0AAADK0QwAAKAczQAAAMrRDAAAoNz/A8c4HPDs8LxmAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "model.set_train(False)\n",
    "for data, label in test_dataset:\n",
    "    pred = model(data)\n",
    "    predicted = pred.argmax(1)\n",
    "    print(f'Predicted: \"{predicted[:6]}\", Actual: \"{label[:6]}\"')\n",
    "\n",
    "    # 显示数字及数字的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确,显示为蓝色;若预测错误,显示为红色\n",
    "        color = 'blue' if predicted[i] == label[i] else 'red'\n",
    "        plt.title('Predicted:{}'.format(predicted[i]), color=color)\n",
    "        plt.imshow(data.asnumpy()[i][0], interpolation=\"None\", cmap=\"gray\")\n",
    "        plt.axis('off')\n",
    "    plt.show()\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本案例已同步上线[GitHub仓](https://github.com/mindspore-courses/orange-pi-mindspore/tree/master/Online/01-quick%20start),更多案例可参考该仓库。\n",
    "\n",
    "本案例运行所需环境:\n",
    "\n",
    "- 硬件:香橙派AIpro 16G 8-12T开发板\n",
    "- 镜像:香橙派官网Ubuntu镜像\n",
    "- CANN:8.0.RC3.alpha002\n",
    "- MindSpore:2.4.10"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.9"
  },
  "vscode": {
   "interpreter": {
    "hash": "8c9da313289c39257cb28b126d2dadd33153d4da4d524f730c81a4aaccbd2ca7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}