原标题:如何快速使用Python神经网络识别手写字符?(文末福利)
点击标题下[异步社区]可快速关注
在本文中,我们将进一步探讨一些使用Python神经网络识别手写字符非常有趣的想法。如果只是想了解神经网络的基本知识,那不必阅读本文,可以先阅读《Python神经网络编程》前面2章节的内容。
这是一个有趣的额外部分,所以节奏会稍微加快一些,但是我们仍然尝试使用简单的语言来解释这些想法。
1.1 自己的手写数字
在本文中,我们一直使用来自MNIST数据集的数字图片。为什么不使用自己的笔迹呢?
在这个实验中,我们将使用自己的笔迹创建测试数据集。我们也将尝试使用不同的书写风格,使用嘈杂或抖动的图片,来观察神经网络的应对能力如何。
你可以使用任何喜欢的图像编辑或绘画软件来创建图片。不必使用昂贵的Photoshop,GIMP是免费开源的替代软件,适用于Windows、Mac和Linux等系统。甚至可以用一支笔将数字写在纸上,并用智能手机、相机或任何合适的扫描仪,将手写数字变成图片格式。唯一的要求是图片为正方形(宽度等于长度),并且将其保存为PNG格式。在喜欢的图像编辑器中,保存格式选项的菜单通常为“File→Save As”或“File→Export”。
下面是我制作的一些图片。
数字5就是我的笔迹。数字4是用粉笔而不是马克笔写的。数字3是我的笔迹并有意切成一段一段的。数字2是传统的报纸或书籍字体,但是进行了模糊处理。数字6有意做成抖动的样子,好像是在水中的倒影。最后一张图片与前面的数字相同,但是添加了噪声,来看看我们是否可以增加神经网络的工作难度。
虽然这很有趣,但是这里蕴含了很严肃的一点。人类大脑在遭受损害后,其能力依然能够得到良好发挥,科学家对此深感震惊。这暗示着,神经网络将它们所学到的知识分布在几条链接权重上,也就是说,如果若干链接权重遭受了一定损害,神经网络也可以表现得相当好。这同时意味着,如果输入图像被损坏或不完整,神经网络也可以表现得相当好。这是一种很强大的功能,这就是我们希望用上图中断断续续的3进行测试的能力。
我们需要创建较小的PNG图片,将它们调整到28个像素乘以28个像素,这样就可以匹配曾经用过的来自MNIST数据集的图片。你可以使用图像编辑器做到这一点。
Python库再次帮助了我们,它从常见的图像文件格式中(包括PNG格式)读取和解码数据。看看下面这段简单的代码:
scipy.misc.imread()函数帮助我们从图像文件,如PNG或JPG文件中,读取数据。必须导入scipy.misc库来使用这个函数。参数“flatten=True”将图像变成简单的浮点数数组,如果图像是彩色的,那么颜色值将被转换为所需要的灰度。
下一行代码重塑数组,将其从28×28的方块数组变成很长的一串数值,这是我们需要馈送给神经网络的数据。此前,我们已经多次进行这样的操作了。但是,这里比较新鲜的一点是将数组的值减去了255.0。这样做的原因是,常规而言,0指的是黑色,255指的是白色,但是,MNIST数据集使用相反的方式表示,因此不得不将值逆转过来以匹配MNIST数据。
最后一行代码是我们很熟悉的,它将数据值进行缩放,使得它们的范围变成0.01到1.0。演示读取PNG文件的示例代码可以在GitHub上找到:
· https://github.com/makeyourownneuralnetwork/makeyourownneuralnetwork/ blob/master/part3_load_own_images.ipynb
我们需要创建基本的神经网络,这个神经网络使用MNIST训练数据集进行训练,然后,不使用MNIST测试集对网络进行测试,而是使用自己创建的图像数据对网络进行测试。
在GitHub上,可通过如下链接获得新程序:
·https://github.com/makeyourownneuralnetwork/makeyourownneuralnetwork/ blob/master/part3_neural_network_mnist_and_own_data.ipynb
这样做成功了吗?当然成功了。下图总结使用我们自己制作的图像查询的结果。
可以看到,神经网络能够识别我们创建的所有图像,包括有意损坏的数字“3”。只有在识别添加了噪声的数字“6”时失败了。
使用你自己的图像,尤其是手写的图像试试看,证明你的神经网络确实能够工作。
并且,仔细观察,要将图像损坏或变形到什么程度,神经网络才会失败。神经网络的弹性将会给你留下深刻的印象。
1.2 神经网络大脑内部
在求解各种各样我们不知道如何使用简约明快的规则解决的问题时,神经网络发挥了重要作用。想象一下,写下一组规则,将这些规则应用于手写数字图像,来确定数字是什么,这件事并不是那么容易,并且我们的尝试也可能不会那么成功。
1.2.1 神秘的黑盒子
一旦神经网络得到了训练,并且在测试数据上表现良好,那么基本上你就拥有了一个神秘的黑盒子。你不知道这个黑盒子如何计算出答案,但是它确实成功地计算出了答案。
如果你只对答案感兴趣,而不真正关心它们如何得出这个答案的,那么对你来说,这就不是一个问题了。但是,我要指出这是这些机器学习方法类型的缺点,即虽然黑盒子(神经网络)已经学会如何求解问题,但是其所学习到的知识常常不能转化为对问题的理解和智慧。
让我们来看看是否可以到神经网络内部一探究竟,是否能够理解神经网络所学习到的知识,将神经网络通过训练搜集到的知识可视化。
我们可以观察权重,这毕竟是神经网络学习的内容。但是,权重不太可能告诉我们太多信息。特别是,神经网络的工作方式是将学习分布到不同的链接权重中。这种方式使得神经网络对损坏具有了弹性,这就像是生物大脑的运行方式。删除一个节点甚至相当多的节点,都不太可能彻底破坏神经网络良好的工作能力。
这里有一个疯狂的想法。
1.2.2 向后查询
在通常情况下,我们馈送给已受训练的神经网络一个问题,神经网络弹出一个答案。在我们的例子中,这个问题是人类的手写数字图像。答案是表示数字0到9中的某个标签。
如果将这种方式反转,向后操作,会发生什么呢?如果馈送一个标签到输出节点,通过已受训练的网络反向输入信号,直到输入节点弹出一个图像,那会怎么样?下图显示了正常的正向查询和疯狂的反向向后查询的想法。
我们已经知道如何通过网络传播信号,使用链接权重调节信号,在应用激活函数之前在节点处重新组合信号。除了使用的是逆激活函数以外,所有这一切操作也都适用于反向传播信号。如果y = f(x) 是正向激活函数,那么这个函数的逆就是x = g(y)。使用简单的代数,求出逻辑函数的逆,也并非难事:
y = 1 / (1 + e-x)
1 + e-x = 1/y
e-x = (1/y) -1 = (1 - y) / y
-x = ln [ (1-y) / y ]
x = ln [ y / (1-y) ]
这就是所谓的对数函数,就像Python为逻辑S函数提供scipy.special.expit()一样,Python中的scipy.special库也提供了这个函数,即scipy.special.logit()。
在应用逆激活函数logit()之前,我们需要确保信号是有效的。这是什么意思呢?还记得吧,逻辑S函数接受了任何数值,输出0和1之间的某个值,但是不包括0和1本身。逆函数必须接受相同的范围0和1之间的某个值,不包括0和1,弹出任何正值或负值。为了实现这一目标,我们简单地接受输出层中的所有值,应用logit(),并将它们重新调整到有效范围。我选择的范围为0.01至0.99。
这段代码在网上始终可用,请访问GitHub以获取:
·https://github.com/makeyourownneuralnetwork/makeyourownneuralnetwork/ blob/master/part3_neural_network_mnist_backquery.ipynb
1.2.3 标签“0”
来看看如果我们使用标签“0”进行反向查询,会发生什么情况。也就是说,我们向输出节点展示了一些值,除了使用值0.99展示给第一个节点表示标签“0”,其余节点都展示了0.01。换句话说,也就是数组[0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,0.01]。
下图显示了输入节点弹出的图像。
这真是太有趣了!
这个图像让我们对神经网络的“大脑”有了一种深刻的见解。这个图像是什么意思?该如何解释这个图像呢?
我们注意到最主要的特征是,图像中的圆形。我们是在询问神经网络——对于答案“0”,最理想的问题是什么,因此,这个图像是有道理的。
我们也注意到深色、浅色和一些介中的灰色区域。
·深色区域是问题图像中应该使用笔来标记的部分,这部分图像组成了支持证据,证明答案为“0”,可以这样理解,这些部分看起来组成了0的形状轮廓。
· 浅色区域是问题图像中应该没有任何笔痕的部分,这支持了答案为“0”。同样,可以这样理解,这些部分形成了0形状的中间部分。
· 大体上,神经网络对灰色区域不是很敏感。
因此,粗略来讲,我们实际上已经理解了,针对如何将图像归类为标签“0”,神经网络已经学习到的知识。
这是一种难得的见解,对于较多层、较复杂的神经网络或较复杂的问题而言,可能没有如此容易解释的结果。我们鼓励你进行实验,亲自动手试一试。
1.2.4 更多的大脑扫描
下图显示了其他数字向后查询的结果。
哇!同样是一些非常有趣的图像,就像使用超声波扫描神经网络的大脑一样。
关于这些图像,我们做了一些注解:
·“7”真的很清楚。可以看到在查询图像中标记的深色位置,强烈暗示了这是标签“7”。也可以看到额外的“白色”区域,这些区域没有任何标记。这两个特点结合起来,指示出了这是“7”。
· 这同样适用于数字“3”,有标记的深色区域指示出了“3”,白色的区域也非常清晰。
·“2”和“5”具有类似的清晰度。
· 数字“4”有点有趣,这个形状出现在4个象限中,是4个互相分隔的区域。
· “8”主要是由“雪人”构成的,这个“雪人”由白色区域形成,表明8的特征在于保持了“头部和身体”区的标记。
· 数字“1”令人相当费解。这看起来好像神经网络较多关注无需标记的区域,而较少关注需要标记的区域。没关系,这就是网络从样本中学到的知识。
· 数字“9”一点都不清楚。它有一个明确的深色区域,还有一些形状相对精细的白色区域。这就是神经网络所学习到的知识,总体来说,当与网络学会的其他数字结合时,这允许神经网络的表现达到了97.5%的准确度。我们观察一下这个图片,并得出结论,有更多的培训样本将有助于神经网络学到更清晰的“9”的模板。
现在,你对神经网络大脑的工作方式应该有了一个深刻的了解了吧。
1.3 创建新的训练数据:旋转图像
如果思考一下MNIST训练数据,你就会意识到,这是关于人们所书写数字的一个相当丰富的样本集。这里有各种各样、各种风格的书写,有的写得很好,有的写得很糟。
神经网络必须尽可能多地学习这些变化类型。在这里,有多种形式的数字“4”,有些被压扁了,有些比较宽,有些进行了旋转,有些顶部是开放的,有些顶部是闭合的,这对神经网络的学习都是有帮助的。
如果我们能够创造更多的变化类型作为样本,会不会有用处呢?如何做到这一点呢?再多收集几千个人类手写数字样本,对我们来说有点不太容易。我们可以这样做,但是工作量有点大。
一个很酷的想法就是利用已有的样本,通过顺时针或逆时针旋转它们,比如说旋转10度,创建新的样本。对于每一个训练样本而言,我们能够生成两个额外的样本。我们可以使用不同的旋转角度创建更多的样本,但是,目前,让我们只尝试+10和-10两个角度,看看这种想法能不能成功。
同样,Python的许多扩展包和程序库都很有用。ndimage.interpolation.rotate()可以将数组转过一个给定的角度,这正是我们所需要的。请记住,由于我们将神经网络设计成为接收一长串输入信号,因此输入的是784长的一串一维数字。我们需要将这一长串数字重新变成28×28的数组,这样就可以旋转这个数组,然后在将这个数组馈送到神经网络之前,将数组解开,重新变成一长串的784个信号。
假设得到了先前的scaled_input数组,下列代码演示了如何使用ndimage.interpolation.rotate()函数:
可以看到,原先的scaled_input数组被重新转变为28乘以 28的数组,然后进行了调整。reshape=False,这个参数防止程序库过分“热心”,将图像压扁,使得数组旋转后可以完全适合,而没有剪掉任何部分。在原始图像中,一些数组元素不存在,但是现在这些数组元素进入了视野,cval就是用来填充数组元素的值。由于我们要移动输入值范围,避免0作为神经网络的输入值,因此不使用0.0作为默认值,而是使用0.01作为默认值。
小型MNIST训练集的记录6(第7条记录)是一个手写数字“1”。在下图中可以看到,原先的数字图片和使用代码生成的两个额外的变化形式。
可以清楚地看到这种方式的好处。原始图像的版本旋转+10度,提供了一个样本,就像某些人的书写风格是将1向后倾斜。将原来图片的版本顺时针旋转10度更有趣。和原始的版本相比,这个版本在某种意义上是更具代表性的学习图片。
让我们创建新的Python Notebook,使用原来的神经网络代码,不过,现在我们将原始图片朝顺时针和逆时针两个方向旋转10度,作为额外的训练样本,来训练神经网络。这段代码在GitHub上可以得到,请访问以下链接:
·https://github.com/makeyourownneuralnetwork/makeyourownneuralnetwork/ blob/master/part2_neural_network_mnist_data_with_rotations.ipynb
设定学习率为0.1,并且只使用一个训练世代,初始运行神经网络,所得的性能是0.9669。这对于没有使用额外旋转图像进行训练的神经网络的性能0.954而言,是一个长足的进步。这样的表现,和列在Yann LeCeun网站中的记录相比也已经是名列前茅了。
让我们进行一系列的实验,改变世代的数目看看是否能够让已经不错的表现更上一层楼。现在,我们创建了更多的训练数据,可以采用更小、更谨慎的学习步长,因此将学习率减少到0.01,这样就总体上延长了学习时间。
请记住,由于特定的神经网络架构或训练数据的完整性,事情很可能存在内在的限制,因此我们不会期待得到98%或以上的准确度,或者甚至是100%的准确度。我们说“特定的神经网络架构”,意思是在每一层节点数目的选择、隐藏层的选择和激活函数的选择等。
我们旋转训练图像的角度,将其作为额外的训练样本,下图显示了在这种情况下的神经网络的性能。同时,下图也显示了没有使用额外旋转的训练样本时神经网络的性能,以便进行简单的比较。
可以看到,在5个世代的情况下,最好的结果是0.9745或97.5%的准确度。这再一次打破了我们先前的纪录。
值得注意的是,如果旋转的角度过大,神经网络的性能会出现下降。由于旋转较大的角度意味着创建了实际上不能代表数字的图像,因此神经网络的性能出现了下降,这是可以理解的。想象一下,将“3”向一个方向旋转90度,这就不再是3了。因此,将过度旋转的图像添加到训练样本中,增加了错误样本,降低了训练的质量。对于最大化附加数据的价值,10度看起来是最佳角度。
在10个世代的情况下,神经网络的性能出现了峰值,打破了记录,达到了0.9787,几乎到达98%!对于这种简单的神经网络而言,这是一个惊人的结果,也是最佳的一种状态。请记住,有些人会对神经网络或数据进行一些巧妙的处理,我们还未这样做,我们只是保持简单的神经网络,但是却依然取得了令人骄傲的结果。
做得好!
1.4 结语
在本文中,我希望你已经明白,人类能够轻而易举解决的一些问题,对传统计算机而言却难以解决。图像识别就是这些所谓的“人工智能”的挑战之一。
神经网络使图像识别以及广泛的其他各类难题,都获得了空前的进步。求解这类难题的早期动力的一个关键性部分是生物大脑,如鸽子或昆虫的大脑,虽然这些生物大脑比起今天的超级计算机似乎简单一些,反应也较慢,但是它们依然能够执行复杂的任务,如飞行、喂食、建设家园。这些生物大脑对损害或对不完美的信号,也非常有弹性。数字计算机和传统计算却不能拥有这种能力。
今天,在人工智能中,神经网络是一些神奇的应用程序成功的关键部分。人们对神经网络和机器学习,特别是深度学习——也就是使用了有层次结构的机器学习方法,依然充满了巨大兴趣。在2016年年初,在古老的围棋对弈领域,谷歌的DeepMind击败了世界级大师。和国际象棋相比,围棋需要更深入的战略,更加微妙,研究人员原本以为计算机需要好几年的时间才能下得好围棋。因此,此次事件成为了人工智能史上一个巨大的里程碑。神经网络在计算机的成功中发挥了关键作用。
我希望你已经明白了,神经网络背后的核心思想其实是非常简单的。我希望你也可以从神经网络的实验中找到乐趣。也许,你已经有了探索其他类型的机器学习和人工智能的兴趣。
如果你做到了这些事情,那么我就算大功告成了。
本文摘自《Python神经网络编程》
《Python神经网络编程》
[英]塔里克·拉希德(Tariq Rashid) 著
点击封面购买纸书
当前,深度学习和人工智能的发展和应用给人们留下了深刻的印象。神经网络是深度学习和人工智能的关键元素,然而,真正了解神经网络工作机制的人少之又少。本书用轻松的笔触,一步一步揭示了神经网络的数学思想,并介绍如何使用Python 3.5编程语言开发神经网络。
本书将带领您进行一场妙趣横生却又有条不紊的旅行——从一个非常简单的想法开始,逐步理解神经网络的工作机制。您无需任何超出中学范围的数学知识,并且本书还给出易于理解的微积分简介。本书的目标是让尽可能多的普通读者理解神经网络。读者将学习使用Python开发自己的神经网络,训练它识别手写数字,甚至可以与专业的神经网络相媲美。
本书适合想要了解深度学习、人工智能和神经网络的读者阅读,尤其适合想要通过Python编程进行神经网络开发的读者参考。
小福利
关注【异步社区】服务号,转发本文至朋友圈或 50 人以上微信群,截图发送至异步社区服务号后台,并在文章底下留言你的开发经验,或者试读本书感受,我们将选出3名读者赠送《Python神经网络编程》1本,赶快积极参与吧!活动截止时间:2018年4月25日
在“异步社区”后台回复“关注”,即可免费获得2000门在线视频课程;推荐朋友关注根据提示获取赠书链接,免费得异步图书一本。赶紧来参加哦!
扫一扫上方二维码,回复“关注”参与活动!
阅读原文,购买《Python神经网络编程》
责任编辑: