{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 开发入门\n",
    "\n",
    "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.9.0/tutorials/zh_cn/orange_pi/mindspore_dev_start.ipynb)&emsp;[![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.9.0/tutorials/zh_cn/orange_pi/mindspore_dev_start.py)&emsp;[![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_source.svg)](https://atomgit.com/mindspore/docs/blob/r2.9.0/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 | 镜像 | CANN Toolkit/Ops | MindSpore |\n",
    "| :----:| :----: | :----:| :----: |\n",
    "| 8T 16G | Ubuntu | 8.5.0 | 2.8.0 |\n",
    "\n",
    "### 镜像烧录\n",
    "\n",
    "运行该案例需要烧录香橙派官网Ubuntu镜像，参考[镜像烧录](https://www.mindspore.cn/tutorials/zh-CN/r2.9.0/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/r2.9.0/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/r2.9.0/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": [
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:499: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/usr/local/miniconda3/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",
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:499: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/usr/local/miniconda3/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": [
    "import mindspore\n",
    "from mindspore import mint\n",
    "from mindspore.nn import Cell, SGD\n",
    "from mindspore.mint import nn\n",
    "from mindspore.dataset import vision, transforms\n",
    "from mindspore.dataset import MnistDataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据集准备与加载\n",
    "\n",
    "MindSpore提供基于Pipeline的[数据引擎](https://www.mindspore.cn/docs/zh-CN/r2.9.0/features/data_engine.html)，通过[数据集（Dataset）](https://www.mindspore.cn/tutorials/zh-CN/r2.9.0/beginner/dataset.html)实现高效的数据预处理。在本案例中，我们使用MNIST数据集，自动下载完成后，使用`mindspore.dataset`提供的数据变换进行预处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Defaulting to user installation because normal site-packages is not writeable\n",
      "Requirement already satisfied: download in /home/HwHiAiUser/.local/lib/python3.9/site-packages (0.3.5)\n",
      "Requirement already satisfied: tqdm in /home/HwHiAiUser/.local/lib/python3.9/site-packages (from download) (4.66.5)\n",
      "Requirement already satisfied: six in /usr/local/miniconda3/lib/python3.9/site-packages (from download) (1.16.0)\n",
      "Requirement already satisfied: requests in /home/HwHiAiUser/.local/lib/python3.9/site-packages (from download) (2.32.2)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/HwHiAiUser/.local/lib/python3.9/site-packages (from requests->download) (2.2.3)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (3.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (2023.11.17)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (2.0.4)\n"
     ]
    }
   ],
   "source": [
    "# install download\n",
    "\n",
    "!pip install download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "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:02<00:00, 4.50MB/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": 4,
   "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": 5,
   "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": 6,
   "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",
    "        transforms.TypeCast(mindspore.float16)\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": 7,
   "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/r2.9.0/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_tuple_iterator.html) 或[create_dict_iterator](https://www.mindspore.cn/docs/zh-CN/r2.9.0/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_dict_iterator.html)对数据集进行迭代访问，查看数据和标签的shape和datatype。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float16\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": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float16\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": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network<\n",
      "  (dense1): Linear<input_features=784, output_features=512, has_bias=True>\n",
      "  (dense2): Linear<input_features=512, output_features=512, has_bias=True>\n",
      "  (dense3): Linear<input_features=512, output_features=10, has_bias=True>\n",
      "  (relu): ReLU<>\n",
      "  >\n"
     ]
    }
   ],
   "source": [
    "# Define model\n",
    "class Network(Cell):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.flatten = mint.flatten\n",
    "        self.dense1 = nn.Linear(28*28, 512, dtype=mindspore.float16)\n",
    "        self.dense2 = nn.Linear(512, 512, dtype=mindspore.float16)\n",
    "        self.dense3 = nn.Linear(512, 10, dtype=mindspore.float16)\n",
    "        self.relu = nn.ReLU()\n",
    "\n",
    "    def construct(self, x):\n",
    "        x = self.flatten(x, start_dim=1)\n",
    "        x = self.dense1(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.dense2(x)\n",
    "        x = self.relu(x)\n",
    "        logits = self.dense3(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/r2.9.0/api_python/mindspore/mindspore.value_and_grad.html)通过函数变换获得梯度计算函数。\n",
    "3. 定义训练函数，使用[set_train](https://www.mindspore.cn/docs/zh-CN/r2.9.0/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.set_train)设置为训练模式，执行正向计算、反向传播和参数优化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate loss function and optimizer\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = 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": 12,
   "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": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1\n",
      "-------------------------------\n",
      "loss: 2.298828  [  0/938]\n",
      "loss: 1.756836  [100/938]\n",
      "loss: 0.783691  [200/938]\n",
      "loss: 0.732910  [300/938]\n",
      "loss: 0.426514  [400/938]\n",
      "loss: 0.547363  [500/938]\n",
      "loss: 0.283203  [600/938]\n",
      "loss: 0.833496  [700/938]\n",
      "loss: 0.241455  [800/938]\n",
      "loss: 0.342773  [900/938]\n",
      ".Test: \n",
      " Accuracy: 90.7%, Avg loss: 0.321171 \n",
      "\n",
      "Epoch 2\n",
      "-------------------------------\n",
      "loss: 0.275879  [  0/938]\n",
      "loss: 0.311035  [100/938]\n",
      "loss: 0.294189  [200/938]\n",
      "loss: 0.458740  [300/938]\n",
      "loss: 0.292725  [400/938]\n",
      "loss: 0.177612  [500/938]\n",
      "loss: 0.367920  [600/938]\n",
      "loss: 0.219482  [700/938]\n",
      "loss: 0.226685  [800/938]\n",
      "loss: 0.230103  [900/938]\n",
      "Test: \n",
      " Accuracy: 92.8%, Avg loss: 0.253441 \n",
      "\n",
      "Epoch 3\n",
      "-------------------------------\n",
      "loss: 0.310791  [  0/938]\n",
      "loss: 0.213379  [100/938]\n",
      "loss: 0.247925  [200/938]\n",
      "loss: 0.227783  [300/938]\n",
      "loss: 0.518066  [400/938]\n",
      "loss: 0.197266  [500/938]\n",
      "loss: 0.199219  [600/938]\n",
      "loss: 0.143188  [700/938]\n",
      "loss: 0.383545  [800/938]\n",
      "loss: 0.290283  [900/938]\n",
      "Test: \n",
      " Accuracy: 93.8%, Avg loss: 0.215057 \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": 14,
   "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": 15,
   "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": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicted: \"[6 9 4 8 9 3]\", Actual: \"[6 9 4 8 9 3]\"\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "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(f'Predicted:{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/inference/01-quick%20start)，更多案例可参考该仓库。\n",
    "\n",
    "本案例运行所需环境：\n",
    "\n",
    "| 香橙派AIpro | 镜像 | CANN Toolkit/Ops | MindSpore |\n",
    "| :----:| :----: | :----:| :----: |\n",
    "| 8T 16G | Ubuntu | 8.5.0 | 2.8.0 |"
   ]
  }
 ],
 "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.9.2"
  },
  "vscode": {
   "interpreter": {
    "hash": "8c9da313289c39257cb28b126d2dadd33153d4da4d524f730c81a4aaccbd2ca7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
