一个纯Python实现的DNN手写数字分类器

自动微分

用深度学习框架来构建神经网络已经不是一件稀奇事,但一个深度学习框架是如何工作的如今很多人是并不清楚也并不关心的。按如今的定义,这属于一个新兴领域的范畴,机器学习系统(Mlsys),是一个与机器/深度学习与系统都紧密相关的交叉学科,如今正在如火如荼地发展。与“系统”相关的话题总是无法避开各种工程上的复杂技巧和优化手段,但深度学习框架的本质在我看来依然是一个非常简单的概念:自动微分(automatic differentiation,或autodiff)

什么是自动微分?以下定义摘自多伦多大学CS321(这门课的涵盖范围广泛得让人震惊)的讲义

Automatic differentiation (autodiff) refers to a general way of taking a program which computes a value, and automatically constructing a procedure for computing derivatives of that value.

换言之,”自动微分“其实并非某一项具体技术的名字,它更像是一类问题,这类问题要求我们提供一种自动求导的方法。而我们所熟知的反向传播(Backprop)就是解决这类问题的一个具体方案,在机器学习领域,反向传播和自动微分基本上是等同的概念。我们假设读者都大致了解反向传播的原理,这里不做详细介绍。

现代的深度学习框架大多基于计算图,其中包括静态图和动态图,静态图的代表作品是Tensorflow1.x,而动态图则以Pytorch为首。它们的操作粒度是单个op。有的早期的深度学习框架则采用了不同的设计,如Caffe。

Caffe的设计哲学是系统级的抽象。设计者注意到神经网络的基本组成单元是层(layers),如卷积层、池化层、dropout层等等。这些层的计算方式各不相同,但是抽象出来,其本质都是一个”接受输入-计算-输出结果“的模块。因此熟悉面向对象变成的读者不难想象Caffe的实现方式,简单来说,Caffe实现了一个layer基类,并通过继承实现了许多不同的layer;将layers和数据进一步组合封装,就成了Net类。我认为这样的设计在工程上是非常合理,且优美的。这个设计思路对我们实现本篇的DNN结构是最简单方便的,因此我们决定,使用与Caffe类似的以层为粒度的设计方法以及与Pytorch类似的前端接口来做我们这次的玩具级别的小项目。

为什么要写这个项目?

这个项目(以它的代码量我觉得称作项目稍显夸大)是我另一个玩具级别项目的热身项目,是我正式开始前的小练习。

问题和模型

这基本上是深度学习的hello world问题了,简单来说,这个问题判断一张黑白像素图上写的数字是0-9中的哪一个。关于MNIST这个数据集的介绍可以参考这里,而为了省事,我们选用了这个处理为CSV格式的版本。

这是个简单的问题,使用传统机器学习也能达到比较高的准确率。这里我们并不打算采用多么复杂的结构,事实上,我采用的是一个很简单的DNN模型:6层全连接层,激活函数均为ReLU,最后接入softmax,以交叉熵为loss,进行分类。优化器是简单的SGD,没有使用dropout等技巧。

项目结构

由于模型简单,项目结构自然也是一目了然,几乎可以望文生义:

  • Activations.py:激活函数。
  • Initializers.py:初始化方式。
  • Layers.py:不同的层。
  • nn.py:神经网络相关,在我们的项目里仅有DNNClassifier的实现。
  • Loss.py:损失函数。

This blog is under a CC BY-NC-SA 3.0 Unported License
Link to this article: https://huangweiran.club/2020/06/03/一个纯Python实现的DNN手写数字分类器/