.. _megengine-quick-start: ================== MegEngine 快速上手 ================== .. note:: * 本教程假设读者具有最基础的 Python 代码编程经验,以及了解 “深度学习” 基本概念; * 在进行下面的步骤之前,请确认你已经按照 :ref:`install` 页面的指示安装好 MegEngine. >>> import megengine >>> print(megengine.__version__) .. seealso:: * 本教程展示模型开发基本流程,对应源码: :docs:`examples/quick-start.py` * 只关注模型部署流程的用户可以阅读 :ref:`megengine-deploy` 🚀🚀🚀 概览 ---- 这份快速上手教程将引导你:1. 使用 MegEngine 框架开发出经典的 LeNet 神经网络模型; 2. 使用它在 MNIST 手写数字数据集上完成训练和测试;3. 将模型用于实际的手写数字分类任务。 在最开始,我们会对 MNIST 数据集有一个最基本的了解,并尝试使用 MegEngine 中的 :mod:`~.data` 模块完成 MNIST 数据集的获取和预处理,将其划分成训练数据集和测试数据集; 同时你还会准备好相应的 :class:`~.DataLoader`, 负责在后续训练和测试模型时完成数据供给。 紧接着你会用 :mod:`~.functional` 和 :mod:`~.module` 模块设计好 LeNet 模型结构。 接下来的步骤也很简单,对模型进行训练!训练的过程中我们需要用到 :mod:`~.autodiff` 模块和 :mod:`~.optimizer` 模块,前者在训练的过程中会记录梯度信息, 后者负责根据梯度信息对模型中的参数进行更新,以达到优化的目的。 最终,我们会对训练好的模型进行测试,你也可以用自己的手写数字样本来试试效果~ 注意:本教程的目的是为 MegEngine 初见用户展示框架最基本的使用流程, 因此不会对每个步骤以及背后的原理进行非常详细的解释,也不会展现出 MegEngine 的全部特性。 如果你对整个深度学习的流程不是很清楚,不用担心,可以尝试跑通这个教程,最后会有进一步的指引。 数据加载和预处理 ---------------- .. admonition:: 数据集介绍 `MNIST `_ :footcite:p:`lecun2010mnist` 手写数字数据集中包含 60,000 张训练图像和 10,000 张测试图像,每张图片是 28x28 像素的灰度图。 如今 MNIST 已然成为机器学习领域的 “Hello, world!”, 用来验证框架和库的可用性。 .. figure:: ../_static/images/MnistExamples.png By `Josef Steppan - Own work `_ , CC BY-SA 4.0 获取 MNIST 数据集 ~~~~~~~~~~~~~~~~~ 在 MegEngine 中可以 :ref:`megengine-dataset` 来获取 MNIST 数据集: .. code-block:: python from megengine.data.dataset import MNIST from os.path import expanduser MNIST_DATA_PATH = expanduser("~/data/datasets/MNIST") train_dataset = MNIST(MNIST_DATA_PATH, train=True) test_dataset = MNIST(MNIST_DATA_PATH, train=False) .. dropdown:: :fa:`question,mr-1` 使用 MegEngine 下载 MNIST 数据集速度慢或总是失败 调用 MegEngine 中的 :class:`~.MNIST` 接口将从 MNIST 官网下载数据集,MegEngine 不提供镜像或加速服务。 本质上可以看作是运行了一份单独的 MNIST 数据集获取与处理脚本(你也可以自己编写脚本来搞定这件事)。 在 `MegStudio `_ 平台中提供了 MNIST 数据集镜像,需注意: * 在创建项目时选择 MNIST 数据集,将 ``MNIST_DATA_PATH`` 设置为 ``/home/megstudio/dataset/MNIST/``; * 在调用 :class:`~.MNIST` 接口时将 ``download`` 参数设置为 ``False``, 避免再次下载。 准备 DataLoader ~~~~~~~~~~~~~~~ 我们将上一步得到的训练集和测试集作为参数输入给 :class:`~.DataLoader`: .. code-block:: python import megengine.data as data import megengine.data.transform as T train_sampler = data.RandomSampler(train_dataset, batch_size=64) test_sampler = data.SequentialSampler(test_dataset, batch_size=4) transform = T.Compose([ T.Normalize(0.1307*255, 0.3081*255), T.Pad(2), T.ToMode("CHW"), ]) train_dataloader = data.DataLoader(train_dataset, train_sampler, transform) test_dataloader = data.DataLoader(test_dataset, test_sampler, transform) 在上面的代码中,我们对数据集的抽样规则和预处理策略也进行了定义, 例如指定了训练集的 ``batch_size`` 为 64, 抽样方式为随机抽样... 并分别将对应的 ``sampler`` 和 ``transform`` 作为构造 ``DataLoader`` 的初始化参数提供。 .. seealso:: 想要了解更多细节,可以参考 :ref:`data-guide` 。 定义模型结构 ------------ `LeNet `_ :footcite:p:`lecun1998gradient` 网络模型的结构如下图所示(图片截取自论文): .. figure:: ../_static/images/lenet5.png Architecture of LeNet a Convolutional Neural Network here for digits recognition. Each plane is a feature map ie a set of units whose weights are constrained to be identical. 在 MegEngine 中定义网络最常见的方式是创建一个继承自 :class:`~.module.Module` 的类: .. code-block:: python import megengine.functional as F import megengine.module as M class LeNet(M.Module): def __init__(self): super().__init__() self.conv1 = M.Conv2d(1, 6, 5) self.conv2 = M.Conv2d(6, 16, 5) self.fc1 = M.Linear(16 * 5 * 5, 120) self.fc2 = M.Linear(120, 84) self.classifier = M.Linear(84, 10) self.relu = M.ReLU() self.pool = M.MaxPool2d(2, 2) def forward(self, x): x = self.pool(self.relu(self.conv1(x))) x = self.pool(self.relu(self.conv2(x))) x = F.flatten(x, 1) x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.classifier(x) return x model = LeNet() * 需要在 ``__init__`` 方法中调用 ``super().__init__``; * 需要在 ``__init__`` 方法中定义需要用到的结构,并在 ``forward`` 中定义前向计算过程。 .. seealso:: 想要了解更多细节,可以参考 :ref:`module-guide` 。 训练:优化模型参数 ------------------ 得到前向计算输出后,为了优化模型参数,我们还需要: * 使用 :class:`~.GradManager` 对参数梯度进行管理; * 使用 :class:`~.Optimizer` 进行反向传播和参数更新(以 :class:`~.SGD` 为例)。 .. code-block:: python import megengine.optimizer as optim import megengine.autodiff as autodiff gm = autodiff.GradManager().attach(model.parameters()) optimizer = optim.SGD( model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4 ) 接下来训练我们的模型:将训练数据集分批地喂入模型,前向计算得到预测值, 根据设计好的损失函数(本教程中使用交叉熵 :func:`~.cross_entropy` )计算。 接着调用 :meth:`.GradManager.backward` 方法来自动进行反向计算并记录梯度信息, 然后根据这些梯度信息来更新模型中的参数,即调用 :meth:`.Optimizer.step` 方法。 .. code-block:: python import megengine epochs = 10 model.train() for epoch in range(epochs): total_loss = 0 for batch_data, batch_label in train_dataloader: batch_data = megengine.Tensor(batch_data) batch_label = megengine.Tensor(batch_label) with gm: logits = model(batch_data) loss = F.nn.cross_entropy(logits, batch_label) gm.backward(loss) optimizer.step().clear_grad() total_loss += loss.item() print(f"Epoch: {epoch}, loss: {total_loss/len(train_dataset)}") .. warning:: 记得将数据转为 MegEngine :class:`~.Tensor` 格式,参考 :ref:`tensor-guide` 。 .. seealso:: 想要了解更多细节,可以参考 :ref:`autodiff-guide` / :ref:`optimizer-guide` 。 测试:评估模型性能 ------------------ 在测试集上验证一下我们刚才训练好的 LeNet 模型的性能: .. code-block:: python model.eval() correct, total = 0, 0 for batch_data, batch_label in test_dataloader: batch_data = megengine.Tensor(batch_data) batch_label = megengine.Tensor(batch_label) logits = model(batch_data) pred = F.argmax(logits, axis=1) correct += (pred == batch_label).sum().item() total += len(pred) print(f"Correct: {correct}, total: {total}, accuracy: {float(correct)/total}") 通常会得到一个在测试集上接近甚至超过 99% 预测正确率的模型。 注:通常的训练流程中应当使用验证集,每训练一段时间就及时验证,这里简化了这一步。 推理:用单张图片验证 -------------------- 我们也可以选择使用自己的手写数字图片来验证模型效果(你可以选择使用自己的图片): .. figure:: ../_static/images/handwrittern-digit.png :height: 250 .. code-block:: python import cv2 import numpy as np def process(image): image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) image = cv2.resize(image, (32, 32)) image = np.array(255 - image) return image image = cv2.imread("/data/handwrittern-digit.png") processed_image = process(image) .. dropdown:: :fa:`question,mr-1` 这里为什么需要进行预处理 我们训练好的模型要求输入图片是形状为 32x32 的灰度图(单通道),且黑白底色要对应。 比如将白底黑字变换成黑底白字,就会对 255 这个值求差(因为表示范围为 [0, 255] )。 上面是针对输入图片样本所做的一些必要预处理步骤,接下来将其输入模型进行推理: >>> logit = model(megengine.Tensor(processed_image).reshape(1, 1, 32, 32)) >>> pred = F.argmax(logit, axis=1).item() >>> pred 6 可以发现,我们训练出的 LeNet 模型成功地将手写该数字图片的标签类别预测为 6 ! .. seealso:: 这里展示的是最简单的模型推理情景,MegEngine 是一个训练推理一体化的框架, 能将训练好的模型导出,在 C++ 环境下高效地进行推理部署,可参考 :ref:`deployment` 中的介绍。 接下来做些什么? ---------------- 我们已经成功地使用 MegEngine 框架完成了手写数字分类任务,很简单吧~ .. admonition:: 文档中还提供了更多内容 :class: note 如果你对整个机器学习(深度学习)的流程依旧不是很清楚,导致阅读本教程有些吃力,不用担心。 我们准备了更加基础的 《 :ref:`deep-learning` 》—— 它可以看作是当前教程内容的手把手教学版本,补充了更多细节和概念解释。 将从机器学习的基本概念开始讲起,循序渐进地帮助你理解整个开发流程, 在接触到更多经典模型结构的同时,也会更加了解如何使用 MegEngine 框架。 一些像是 :ref:`serialization-guide` 和 :ref:`hub-guide` 的用法,也会在该系列教程中进行简单介绍。 同时,由于这仅仅是一份快速上手教程,许多模型开发的进阶特性没有进行介绍,例如 :ref:`distributed-guide` / :ref:`quantization-guide` ... 等专题,可以在 :ref:`user-guide` 中找到。 值得一提的是,MegEngine 不仅仅是一个深度学习训练框架,同时也支持便捷高效的模型推理部署。 关于模型推理部署的内容,可以参考 :ref:`deployment` 页面的介绍与 《 :ref:`megengine-deploy` 》。 .. admonition:: 任何人都可以成为 MegEngine 教程的贡献者 :class: note 由于开发者视角所带来的一些局限性,我们无法做到完全以用户视角来撰写文档中的各块内容,尽善尽美是长期追求。 如果你在阅读 MegEngine 教程的过程中产生了疑惑,或是有任何的建议,欢迎你加入 MegEngine 文档建设中来。 参考 :ref:`docs` 页面了解更多细节。 参考文献 -------- .. footbibliography::