斯坦福机器学习课程 第十一周 (1)照片OCR

问题描述和流水线(Pipeline)

视频地址

在这一段和下几段视频中,我想向你介绍一种 机器学习的应用实例:照片OCR技术。我想介绍这部分内容的原因主要有以下三个:

  • 第一,我想向你展示一个复杂的机器学习系统是如何被组合起来的。

  • 第二,我想介绍一下机器学习流水线(machine learning pipeline)的有关概念以及在决定下一步做什么时如何分配资源。

  • 最后,我也想通过介绍照片OCR问题的机会来告诉你机器学习的诸多有意思的想法和理念。其中之一是如何将机器学习应用到计算机视觉问题中,第二是有关人工数据合成(artificial data synthesis)的概念。

照片OCR是指照片光学字符识别(photo optical character recognition)

随着数码摄影的日益流行,以及近年来手机中拍照功能的逐渐成熟,我们现在很容易就会有一大堆从各地拍摄的数码照片。吸引众多开发人员的其中一个应用是如何让计算机更好地理解这些照片的内容。这种照片OCR技术主要解决的问题是让计算机读出照片中拍到的文字信息。

照片OCR技术的一个应用场景是方便照片的搜索。例如你找出下面这张照片时,你只需要输入图片中的文字“LULA B’s ANTIQUE MALL”即可把照片找出来:

虽然现在OCR对扫描的文档来说已经是一个比较简单的问题了,但对于数码照片来说现在还是一个比较困难的机器学习问题。研究这个的目的不仅仅是因为这可以让计算机更好地理解我们的户外图像,更重要的是它衍生了很多应用。比如在帮助盲人方面,假如你能为盲人提供一种照相机,这种相机可以“看见”他们前面有什么东西,可以告诉他们面前的路牌上写的是什么字。现在也有研究人员将照片OCR技术应用到汽车导航系统中,想象一下你的车能读出街道的标识,并且将你导航至目的地。

OCR的大概步骤

照片OCR的大体步骤如下:

  • 1.文字识别技术(Text detection)

首先使用文字识别技术(Text detection)将给定的图片扫描一遍,找出这张图片中哪里有文字信息:

  • 2.字符切分

接下来就是重点关注这些文字区域,对这些文字区域的矩形轮廓进行字符切分。

  • 3.字符分类

当文字被分割成独立的字符之后,我们可以尝试运行一个分类器,输入这些可识别的字符,然后试着识别出上面的字符。

因此通过完成所有这些工作,按理说你就能识别出 这个字段写的是“LULAB’s ANTIQUE MALL”,然后图片中其他有文字的地方也是类似的方法进行处理。


其实有很多照片OCR系统会进行更为复杂的处理,比如在最后会进行拼写校正。

假如你的字符分割和分类系统告诉你它识别到的字是“C1eaning”,那么很多拼写修正系统会告诉你,这可能是单词“Cleaning”的拼写,你的字符分类算法刚才把字母“l”识别成了数字“1”。

但在本课中,我们会不考虑最后这一步拼写检查,只关注前面三个步骤。

机器学习流水线(machine learning pipeline)

像上面这样的一个系统,我们把它称之为机器学习流水线(machine learning pipeline)

下面是OCR的流水线:

具体来说,这幅图表示的就是照片OCR的流水线。

我们有一幅图像,然后传给文字检测系统,识别出文字以后,我们将字段分割为独立的字符,最后我们对单个的字母进行识别。

在很多复杂的机器学习系统中,这种流水线形式都非常普遍。在流水线中会有多个不同的模块,比如在本例中我们有文字检测、字符分割和字母识别,其中每个模块都可能是一个机器学习组件。

如果你要设计一个机器学习系统,其中你需要作出的最重要的决定,就是你要怎样组织好这个流水线。换句话说,在这个照片OCR问题中,你应该如何将这个问题分成一系列不同的模块。你需要设计这个流程,以及你的流水线中的每一个模块,这通常会影响到你最终的算法的表现。

如果你有一个工程师的团队在完成同样类似的任务,那么通常你可以让不同的人来完成不同的模块。我可以假设文字检测这个模块需要大概1到5个人、字符分割部分需要另外1到5个人、字母识别部分还需要另外1到5个人:

因此使用流水线的方式通常提供了一个很好的办法来将整个工作分给不同的组员去完成。(当然所有这些工作都可以由一个人来完成,如果你希望这样做的话。)

在复杂的机器学习系统中,流水线的概念已经渗透到各种应用中。你刚才看到的只是一种照片OCR流水线的运作过程。在接下来的几段视频中,我还将继续向你介绍更多的一些关于流水线的内容。我们还将使用这个例子来展示机器学习中其他一些非常重要的概念。

滑动窗体

视频地址

在上一节我们谈到了照片OCR流水线以及其工作原理。我们讲到可以照一张照片然后将其通过一系列机器学习组件来尝试读出图片中的文字信息。

在本节,我想再多介绍一些照片OCR流水线中的组件是如何工作的。

本节我们介绍一下滑动窗体(sliding windows)的分类器。在照片OCR中,我们可以使用滑动窗体(sliding windows)分类器来识别图片中的文字。

滑动窗体步骤

  • 1.文字检测

滑动窗的第一个步骤是文字检测(text detection)

对于OCR问题,我们想要达到下面这种从图片中识别文字的功能:

文字识别是计算机视觉中的一个非同寻常的问题,因为取决于你想要找到的文字的长度。这些长方形区域会呈现不同的宽高比。

例子:行人检测问题

为了更好地介绍图像检测,我们从一个简单一点的行人探测的例子开始。

假设我们想要实现一个从图中识别出行人的应用:

这个问题似乎比文字检测的问题更简单,因为大部分的行人都比较相似。因此可以使用一个固定宽高比的矩形(就是上图中的红色矩形)来分离出你希望找到的行人。但在文字检测中,文字区域的宽高比就无法固定了。

虽然在行人检测的问题中,行人可能会与相机处于不同的距离位置,因此这些矩形的高度也取决于他们离相机的距离远近,但这个矩形的宽高比应该是一样的。

为了建立一个行人检测系统,以下是具体步骤:

  • 1.指定行人矩形比例

    假如说我们把宽高比标准化到$82:36$这样一个比例。

  • 2.搜集样本

    接下来我们要做的就是到街上去收集一大堆正负训练样本。

正样本$(y=1)$ 负样本$(y=0)$

在典型的行人识别应用中,我们可以有1000个到10000个训练样本不等,甚至更多。

  • 3.训练算法

    如果你能得到大规模训练样本的话,然后你要做的事是训练一个神经网络或者别的什么学习算法,输入这些$82×36$维的图像块,然后对$y$进行分类,把图像块分成”有行人”和”没有行人”两类。

    因此这一步实际上是一个监督学习。你通过一个图像块然后决定这个图像块里有没有行人。


现在假如我们获得一张新的测试样本图像:

我们如果想要从这张图中找到行人,首先要做的是对这个图像取一小块长方形($82×36$):

我们将这个图像块通过我们训练得到的分类器来确定这个图像块中是不是有行人。

然后我们把这个绿色的长方形图片滑动一点点:

得到一个新的图像块,并同样把它传入我们的分类器 看看这里面有没有行人。

完成这一步之后,我们再向右滑动一点窗口,同样地把图像块传入分类器。

你每次滑动窗口的大小是一个参数,通常被称为步长(step size),有时也称为步幅参数(stride parameter)。步长为1代表每次移动一个像素,这样通常表现得最好但可能计算量比较大,因此通常使用4个像素、或者8个像素、或者更多像素作为步长值。

通过固定步长,你的窗体去逐步扫描完整个图,并在每一步扫描过程中,将窗体扫描到的图片代入之前训练的行人识别的分类器中,直到窗体滑过图片中所有不同的位置:

但这个矩形是非常小的,只能探测到某种尺寸的行人。接下来我们要做的是看看更大的图像块。因此我们用更大矩形来滑过图片,传入分类器运行:

顺便说一下,“用更大一些的图像块”的意思是当你用这样的图像块时,我们需要将扫描得到的图片重新压缩到分类器可以识别的尺寸($82×36$像素)。

以此类推,接下来你可以用一个更大的矩形,以同样的方式滑动窗口。直到完成最后的扫描过程之后,你的算法应该就能检测出图像中是否出现行人了。

因此整个步骤就是:训练一个分类器,然后用一个滑动窗分类器来找出图像中出现的行人。


OCR文字检测

接下来我们转向文字识别的例子,对于照片OCR流水线中,要检测出文字需要的步骤如下:

训练分类器

跟行人检测类似你也可以先收集一些带标签的训练集:

正样本:出现文字的区域$(y=1)$ 负样本:没有出现文字的区域$(y=0)$

通过使用这些训练集来训练识别文字的分类器。

滑动窗体,识别文字区域

训练完了以后,我们就可以把它应用到测试集图片中了。我们以这幅图片为例:

这里我们用一个固定的比例的矩形作为窗体来运行滑动窗体。如果我这样做的话,最终得到的结果是这样的:

白色区域代表找到了文字的区域,黑色区域代表没有找到文字。不同的灰度表示分类器给出的输出结果的概率值,所以比如有些灰色的阴影表示分类器在这片区域似乎发现了文字,但并不十分确信;而比较白亮的区域则表示分类器预测这个区域有文字 的概率比较大。

代入“展开器”(expansion operator)

现在我们还没完成文字检测呢,因为我们实际上想做的是在图像中有文字的各区域都画上矩形窗,所以我们还需要完成一步。我们取出分类器的输出,然后输入到一个被称为“展开器”(expansion operator)的东西。

展开器的作用就是取过这张图片,对每一个白色的小点都扩展为一块白色的区域。

从数学上来讲,对于每一个像素,我们都考察一下它是不是在左边这幅图中的某个白色像素的范围之内。比如说,某一个像素点在最左边那幅图中白色像素点的五或十个像素范围中,那么我们将把右边那幅图的相同像素设为白色。

这样做的效果就是,我们把左边图中的所有的白色小点都扩展了一下,让它们都变大了一些。现在我们可以根据右边的这张图锁定那些连接部分(也就是这些连续的白色区域)然后围绕着它们画个框就行了。

具体来讲,如果我们分析这些白色区域,我们可以简单地凭直觉来判断哪些区域是比较奇怪的,因为我们知道有文字的区域应该不是很高的,而是比较宽的。所以我们忽略那些又高又瘦的白块,这两个:

然后对剩下的那些,从比例上来看比较像正常的文字区域的白块画上矩形窗:

这个例子中漏掉了一些字,因为这些字的宽高比看起来不正常,以及写在玻璃上的文字比较难读出来,但整体来讲,检测效果还不错。

这就是使用滑动窗来进行文字检测。

找到这些有文字的长方形以后,我们现在就能够剪下这些图像区域,然后应用流水线的后面步骤对文字进行识别。

字符分割

如果你还记得的话,你应该知道流水线的第二步是字符分割。所以给出下面这样的图像我们应该怎样分割出图像中的单个字符呢?

同样地,我们还是使用一种监督学习算法,用一些是否存在字符之间的分割区域的正样本和一些负样本来训练一个分类器。

正样本:存在字符间分各区域$(y=1)$ 负样本:不存在字符间分割区域$(y=0)$

因此我们要做的就是使用神经网络或者其他的学习算法来训练一个分类器,试着对这些正负样本进行分类。训练好这个分类器以后,我们就要把这个分类器应用到我们文字中。

使用同样的窗体滑动方式(只不过使用的分类器不同),扫描文字检测系统输出的文字区域图像:

分类器告诉我们$y=1$时,就意味着我们需要在中间画一条线,分开两个字符,否则就跳过。如果正常的话,分类器会告诉我们应该在什么地方来将图像分割为独立的字符。

获取大量数据和人工数据

视频地址

想要获得一个高效的机器学习系统,我们通常需要在低偏差的算法中代入大量的训练数据。但是我们如何获取大量的训练数据呢?

其实在机器学习中有一个很棒的想法,叫做“人工数据合成”(artificial data synthesis)

人工数据合成的概念通常包含两种不同的变体:

  • 第一种,是我们白手起家来创造新的数据。
  • 第二种,是我们通过扩大一个已经存在的带标签的小的训练集,来获得数据。

这节课中我们将对这两种方法进行介绍。

创造新样本

为了介绍人工数据合成的概念,让我们还是用之前用过的照片OCR流水线中的字母识别问题来举例。

假如我们可以在别处收集到一大堆标签数据:

我们的目标就是对任意一个图像块,我们能够识别出图像中心的那个字符。

同时,为了方便,我把这些图像都视为灰度图像而不是彩色图像(实际上用彩色的图像对这个问题的解决也起不了多大作用)。

我们有了这些原始数据,那么我们怎样才能获得一个更大的训练集呢?

众所周知,现代计算机中通常都有一个很大的字体库,我们也可以下载到很多免费的字体样式:

所以如果你想要获得更多的训练样本,其中一种方法是你可以采集同一个字符的不同种字体,然后将这些字符加上不同的随机背景来创造训练样本。

通过这样的操作之后,你可以得到这样一个合成之后的训练集:

在生成模拟数据的时候,需要考虑对模拟的样本进行模糊、变形、旋转等操作,因为这样创造出来的样本比较真实。如果你草率的生成一些样本,那么最终训练出来的算法可能效果不是很好。

因此通过使用合成的数据,实际上已经获得了无限的训练样本。这就是人工数据合成。

通过已有的样本创造新样本

人工数据合成的第二种方法是使用你已经有的样本。

我们选取一个真实的样本,然后通过添加别的数据来扩大你的训练集。

比如下图中字母A,来自于一个真实的图像(不是一个合成的图像):

为了方便描述,我在图像上加了一些灰色的网格。实际上是没有这些格子的。你要做的就是取出这个图像,进行人工扭曲,或者人工变形:

这样从一个图像A就能生成16种新的样本。

所以用这种方法,你可以把一个很小的带标签训练集突然一下扩大,得到更多的训练样本。

同样地,要把这个概念投入应用,还是需要仔细考虑的。比如要考虑什么样的变形是合理的。

引入形变方法的注意事项

如果你想要通过对原始数据形变产生新数据,那么在选择要引入的干扰或变形要能代表你可能会在 测试集中看到的噪音源或干扰项。

比如对于字符识别这个例子,这种扭曲的方法事实上还是很合理的:

因为这种程度的扭曲,在测试集中是会遇到的。

相对而言,为你的数据添加一些纯随机的噪声,通常来讲是没什么用的。我不确定你从这里能否看出下图中对样本的变形:

这里我们队这四幅图像中每一个图像的每一个像素都加了一些随机高斯噪声。这其实是完全没有意义的。除非你觉得在你的测试集中会遇到这些像素的噪声,否则的话这些随机的噪声是无意义的,起不到多大作用。


人工数据合成的过程并没有什么技巧可言,有时候你只能一遍遍地尝试,然后观察效果。

但你在确定需要添加什么样的变形时,你一定要考虑好你添加的那些额外的变形量是有意义的,能让你产生的训练样本至少在某种程度上是具有一定的代表性,能代表你可能会在测试集中看到的某种图像。

总结

最后,我将介绍一些在获取更多训练数据时的注意事项。

尝试获取更多数据之前,先优化好你的分类器

首先,在考虑如何产生大量人工训练样本之前,通常最好应该先保证你已经有了一个低偏差的分类器。这样得到大量的数据才真的会起作用。

标准的方法是画出学习曲线,然后确保你已经有了一个低偏差或者高方差的分类器。如果你没有得到一个低偏差的分类器,你还可以尝试增大分类器的特征数,或者在神经网络中增大隐藏层单元数,直到你得到一个偏差比较小的分类器。

你一定要避免的是,花了几个星期的时间或者几个月的工夫,考虑好了怎么样能获得比较好的人工合成数据,然后才意识到即使获得了大量的训练数据,自己的学习算法的表现依然没有提高多少。

尝试和你的团队沟通,头脑风暴

第二,当我在解决机器学习问题时,通常我会问我的团队或者我的学生:“我们要付出多少工作量来获得10倍于我们现有的数据量?”

但让我经常感到有一点吃惊的是,他们的回答都是:“这并不是什么难事,最多花上几天时间,我们就能给一个机器学习问题获得十倍于我们现有数据量的数据。”而且通常来说,如果你能得到10倍的数据量,那么你一般都能让你的学习算法表现更好。

因此,如果你加入某个产品设计小组,要设计某个机器学习的应用产品,可以问问你的团队这个问题。说不定几分钟的头脑风暴以后,你的团队就会想出一种方法,真的一下子能获得10倍的数据量。

“众包” (crowd sourcing)–人工标记样本

另一种很好的办法,我们称之为“众包” (crowd sourcing)的办法。

现在已经有一些网站,或者一些服务机构能让你通过网络雇一些人替你完成标记大量训练数据的工作。通常都很廉价。

很明显这种方法,就像学术文献一样,它也是很复杂的,同时取决于标记人的可靠性。

可能“亚马逊土耳其机器人”(Amazon Mechanical Turk)就是当前最流行的一个众包选择。

上限分析:流水线上的下一步工作是什么

视频地址

在前面的课程中,我不止一次地说过在你开发机器学习系统时,你最宝贵的资源就是你的时间

作为一个开发者,你需要正确选择下一步的工作。或者也许你有一个开发团队共同开发一个机器学习系统,同样最宝贵的还是开发系统所花费的时间。

你需要尽量避免的你和你的团队花费了大量时间 在某一个模块上,在几周甚至几个月的努力以后才意识到所有这些付出的劳动,都对你最终系统的表现并没有太大的帮助。

在这本节,我将介绍一下关于上限分析(ceiling analysis)的内容。这种方式通常能提供一种很有价值的信号,告诉你流水线中的哪个部分最值得你花时间。

上限分析主要思想

依然以照片OCR流水线为例:

当我们面对这样一个流水线时,你应该怎样分配资源呢?哪一个方框最值得你投入精力,投入时间去改善效果呢?

为了回答这个问题,我们可以对学习系统使用一个数值评价量度

假如我们用字符准确度作为这个量度,给定一个 测试样本图像,这个数值就表示我们对测试图像中的文字识别正确的比例。

我们假设整个系统的估计准确率为72%(对测试集上的图像分别运行流水线上的每一个模块操作之后,整个测试集的准确率是72%):

模块 准确率
整个系统 72%

下面是上限分析的主要思想:

首先,我们要模拟在文字检测准确率100%的情况下,得出当前系统的准确率。(我们可以通过人工的方式找出这种样本)

模块 准确率
整个系统 72%
文字检测 89%

然后以同样的方式,得出在文字检测,以及字符切分准确率100%的情况下,当前系统的准确率。

模块 准确率
整个系统 72%
文字检测 89%
字符切分 90%

最后,我们也要写出在文字检测字符切分以及字符识别准确率100%的情况下,当前系统的准确率(当然是100%)。

模块 准确率
整个系统 72%
文字检测 89%
字符切分 90%
字符识别 100%

有了这些数据,我们就知道了每一个模块进行改善它们各自的上升空间是多大。

我们可以看到,如果我们拥有完美的文字检测模块,那么整个系统的表现将会从准确率72%上升到89%,因此效果的增益是17%。这就意味着,如果你在现有系统的基础上花费时间和精力改善文字检测模块的效果,那么系统的表现可能会提高17%。

而相对来讲,如果我们取得完美的字符分割模块,那么最终系统表现只提升了1%。这给我们提供了一个很重要的信息,那就是不管我们投入多大精力在字符分割上,系统效果的潜在上升空间也都是很小很小。所以你就不会让一个比较大的工程师团队花时间忙于字符分割模块,因为通过上限分析我们知道了即使你把字符分割模块做得再好,再怎么完美,你的系统表现最多也只能提升1%。

最后,如果我们取得完美的字符识别模块,那么整个系统的表现将提高10%。所以,同样你也可以分析10%的效果提升值得投入多少工作量。

上限分析的原理

下面我换一个复杂一点的例子再来演绎一下上限分析的原理。

假如说你想对这张图像进行人脸识别:

这是一个偏人工智能的例子,当然这并不是现实中的人脸识别技术,但我想通过这个例子来向你展示一个流水线,并且给你另一个关于上限分析的实例。

假设我们有如下流水线:

其中具体步骤对应的操作如下:

预处理(移除背景图):

检测人脸:

眼睛分割:

鼻子分割:

嘴分割:

那么对这个流水线怎么进行上限分析呢?

我们同样需要每次关注一个步骤,来计算整体的准确率:

模块 准确率
整个系统 85%
预处理(移除背景图) 85.1%
检测人脸 91%
眼睛分割 95%
鼻子分割 96%
嘴分割 97%
逻辑回归 100%

从数据中,我们可以很明显的看出来,预处理阶,准确率提高了0.1%。这是个很明显的信号,它告诉我们即便把背景分割做得很好,但整个系统的表现也并不会提高多少。所以似乎并不值得花太多精力在预处理或者背景移除上。

在每次通过这个系统的时候,随着使用有正确标签的测试集的模块越来越多,整个系统的表现逐步上升,这样你就能很清楚地看到通过不同的步骤,系统的表现增加了多少(比如有了完美的脸部识别,整个系统的表现似乎提高了5.9%,这告诉你也许在脸部检测上多做点努力是有意义的)。

因此,通过上限分析,很清楚地指出了哪一个模块是最值得花精力去完善的。

一个真实的故事:

原来有一个大概两个人的研究小组,花了整整18个月都在完善背景移除的效果(我不详细地讲具体的细节和原因是什么),就为了得到一个更好的背景移除效果。事实上他们确实研究出了非常复杂的算法,貌似最后还发表了一篇文章,但最终他们发现所有付出的这些劳动,都不能给他们研发系统的整体表现带来比较大的提升。而如果要是之前,他们组某个人做一下上限分析,他们就会提前意识到这个问题。

后来,他们中有一个人跟我说,如果他们之前也做了某种这样的分析,他们就会可以把精力花在 其他更重要的模块上,而不是把18个月花在背景移除上。

一些建议

经过这么多年在机器学习中的摸爬滚打,我已经学会了不要凭自己的直觉来判断应该改进哪个模块,相反地如果要解决某个机器学习问题,最好能把问题分成多个模块,然后做一下上限分析。

这通常它可以告诉你一个更可靠的,关于该把劲儿往哪儿使的方法。

坚持原创技术分享,您的支持将鼓励我继续创作!