如何提高在 MNIST 上训练的模型的数字识别?

Posted

技术标签:

【中文标题】如何提高在 MNIST 上训练的模型的数字识别?【英文标题】:How to improve digit recognition of a model trained on MNIST? 【发布时间】:2020-02-12 09:20:21 【问题描述】:

我正在使用 Java 进行手印多位识别,使用 OpenCV 库进行预处理和分割,并使用在 MNIST 上训练的 Keras 模型(精度为 0.98)进行识别。

除了一件事之外,识别似乎工作得很好。网络经常无法识别那些(数字“一”)。我不知道它是否是由于预处理/不正确的分割实现而发生的,或者如果在标准 MNIST 上训练的网络只是没有看到看起来像我的测试用例的第一。

这是有问题的数字在预处理和分割后的样子:

变为 并归类为 4

变为 并归类为 7

变为 并被归类为 4。 等等……

这是否可以通过改进分割过程来解决?还是通过增强训练集?

编辑:增强训练集(数据增强)肯定会有所帮助,我已经在测试,但正确预处理的问题仍然存在。

我的预处理包括调整大小、转换为灰度、二值化、反转和膨胀。代码如下:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

然后将预处理后的图像分割成单独的数字,如下所示:

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) 
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);


for (int i = 0; i < rects.size(); i++) 
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);

【问题讨论】:

你使用蒙版或(蒙版?)原始灰度像素作为分类的输入? @Micka 我正在使用预处理(二值化、倒置、扩张)版本。与 MNIST 训练集匹配的那些。我的帖子中有预处理后数字“1”的例子。 【参考方案1】:

我相信你的问题是扩张过程。我知道您希望标准化图像尺寸,但您不应该打破比例,您应该调整到一个轴所需的最大值(允许最大重新缩放而不让另一个轴尺寸超过最大尺寸的轴)并填充与背景颜色的图像的其余部分。 并不是说“标准 MNIST 只是没有看到看起来像你的测试用例的第一”,而是让你的图像看起来像不同的训练数字(被识别的数字)

如果您保持图像(源图像和后处理图像)的正确纵横比,您会发现您不仅调整了图像大小,而且“扭曲”了它。这可能是非均匀膨胀或不正确调整大小的结果

【讨论】:

我相信@SiR有一定的分量,尽量不要改变数字文字的纵横比。 对不起,我不太明白。您认为我的扩张过程或调整大小过程是问题所在吗?我只用这行Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC); 调整开头的图像大小。这里长宽比保持不变,我在哪里打破比例? @SiR 回答您上面的编辑:是的,我不只是调整图像的大小,我应用了不同的操作,其中之一是膨胀,这是一种形态学操作,会导致轻微的“失真” “因为它会导致图像中的明亮区域“增长”。或者你的意思是最后调整大小,我将图像设为 28x28? @youngpanda,您可能会发现***.com/questions/28525436/… 的讨论很有趣。它可能会给你一个线索,为什么你的方法没有带来好的结果 @SiR 谢谢你的链接,我对 LeNet 很熟悉,但很高兴再次阅读【参考方案2】:

已经发布了一些答案,但它们都没有回答您关于图像预处理的实际问题。

只要是研究项目,我认为您的实施没有任何重大问题,做得很好。

但有一点需要注意,您可能会错过。 数学形态学中有基本的运算:腐蚀和膨胀(由你使用)。还有复杂的操作:基本操作的各种组合(例如打开和关闭)。 Wikipedia link 不是最好的简历参考,但您可以从它开始了解。

通常最好使用 打开而不是腐蚀关闭而不是膨胀,因为在这种情况下原始二值图像变化少得多(但达到了清除锐边或填充间隙的预期效果)。 因此,在您的情况下,您应该检查关闭(图像膨胀,然后使用相同的内核腐蚀)。 如果即使使用 1*1 内核(1 像素超过图像的 16%),在扩展时也会大大修改 8*8 的超小图像,这在较大的图像上会更少)。

要形象化这个想法,请参阅以下图片(来自 OpenCV 教程:1、2):

膨胀:

关闭:

希望对你有帮助。

【讨论】:

感谢您的意见!实际上它不是一个研究项目,那会有什么问题呢?..我应用膨胀时图像很大,8x8不是图像的大小,它是高度的调整因子和宽度。但尝试不同的数学运算仍然是一种改进选择。我不知道打开和关闭,我会尝试一下!谢谢。 我的错,误读了 resize 调用,因为它是 8*8 作为新尺寸。如果您想在现实世界中使用 OCR,您应该考虑将原始网络迁移到您使用领域的典型数据上。至少检查它是否提高了准确性,一般应该这样做。 要检查的另一件事是预处理顺序:灰度->二进制->逆->调整大小。调整大小是一项昂贵的操作,我认为不需要将其应用于彩色图像。如果你有一些特定的输入格式,符号分割可以在没有轮廓检测的情况下完成(成本更低),但它可能很难实现。 如果我有除 MNIST 之外的另一个数据集,我可以尝试迁移学习 :) 我会尝试更改预处理顺序并回复您。谢谢!对于我的问题,我还没有找到比轮廓检测更简单的选择... 好的。您可以自己从将使用 OCR 的图像中收集数据集,这是一种常见的做法。【参考方案3】:

因此,您需要一种复杂的方法,因为您的计算级联的每一步都基于之前的结果。在您的算法中,您具有以下特征:

    图像预处理

如前所述,如果应用调整大小,则会丢失有关图像纵横比的信息。您必须对数字图像进行相同的重新处理才能获得与训练过程中暗示的相同的结果。

如果您只是通过固定大小的图片裁剪图像,则更好。在该变体中,您不需要在训练过程之前进行轮廓查找和调整数字图像的大小。然后,您可以对裁剪算法进行一些更改以更好地识别:只需找到轮廓并将您的手指放在相关图像框架的中心而不调整大小以进行识别。

您还应该更加注意二值化算法。我有研究二值化阈值对学习错误的影响的经验:我可以说这是一个非常重要的因素。您可以尝试另一种二值化算法来检查这个想法。例如,您可以使用this library 来测试替代二值化算法。

    学习算法

为了提高识别质量,您在训练过程中使用cross-validation。这可以帮助您避免训练数据出现overfitting 的问题。例如,您可以阅读this article,其中解释了如何将它与 Keras 一起使用。

有时,更高的准确率衡量标准并不能说明真正的识别质量,因为经过训练的 ANN 没有在训练数据中找到模式。它可能与上面解释的训练过程或输入数据集有关,也可能由 ANN 架构选择引起。

    ANN 架构

这是个大问题。如何定义更好的 ANN 架构来解决任务?没有通用的方法来做这件事。但是有几种方法可以更接近理想。例如,您可以阅读this book。它可以帮助您更好地解决问题。您还可以找到here 一些启发式公式来适应您的 ANN 隐藏层/元素的数量。还有here,你会发现一些关于这个的概述。

我希望这会有所帮助。

【讨论】:

1.如果我理解正确,我无法裁剪为固定尺寸,这是一张多位数的图片,所有箱子的尺寸/位置等都不同。或者你的意思是不同的?是的,我尝试了不同的二值化方法并调整了参数,如果这就是你的意思。 2.其实MNIST上的识别度很好,没有过拟合,我说的准确率就是测试准确率。网络或其训练都不是问题。 3. 感谢所有链接,不过我对自己的架构非常满意,当然还有改进的余地。 是的,你明白了。但是您总是有可能使您的数据集更加统一。在您的情况下,最好像您已经做的那样按轮廓裁剪数字图像。但在那之后,最好根据 x 和 y 比例的数字图像的最大尺寸将数字图像扩展为统一尺寸。你可能更喜欢数字轮廓区域的中心来做那件事。它将为您的训练算法提供更干净的输入数据。 你的意思是我必须跳过膨胀吗?最后,当我应用边框(每边 50 像素)时,我已经将图像居中。之后,我将每个数字的大小调整为 28x28,因为这是 MNIST 所需的大小。你的意思是我可以以不同的方式调整到 28x28 的大小吗? 是的,膨胀是不可取的。您的轮廓可能具有不同的高度和宽度比率,这就是您需要在此处改进算法的原因。至少您应该使图像尺寸具有相同的比例。由于您有 28x28 的输入图片尺寸,因此您必须准备 x 和 y 比例具有相同 1:1 比例的图像。您应该得到的不是每个图片边的 50 px 边框,而是满足条件的 X、Y px 边框:contourSizeX+borderSizeX == contourSizeY+borderSizeY。就是这样。 我已经尝试过不使用膨胀(忘记在帖子中提及)。它没有改变任何结果......我的边界号码是实验性的。理想情况下,我需要我的数字适合 20x20 的盒子(在数据集中进行尺寸标准化),然后使用质心移动它......【参考方案4】:

经过一些研究和实验,我得出的结论是图像预处理本身不是问题(我确实更改了一些建议的参数,例如膨胀大小和形状,但它们对结果并不重要)。然而,真正有帮助的是以下两件事:

    正如@f4f 所注意到的,我需要用真实世界的数据收集自己的数据集。这已经非常有帮助了。

    我对分段预处理进行了重要更改。在获得单独的轮廓后,我首先对图像进行尺寸标准化以适合20x20 像素框(就像它们在MNIST 中一样)。之后,我使用质心将框置于28x28 图像中间(对于二值图像,它是两个维度的平均值)。

当然,仍然存在难以分割的情况,例如重叠或连接的数字,但以上更改回答了我最初的问题并提高了我的分类性能。

【讨论】:

以上是关于如何提高在 MNIST 上训练的模型的数字识别?的主要内容,如果未能解决你的问题,请参考以下文章

PaddlePaddle框架学习MNIST手写数字识别

专栏 | 在PaddlePaddle上实现MNIST手写体数字识别

TensorFlow学习笔记MNIST手写数字识别

用训练好的模型进行图像识别

在 Keras 的 MNIST 数字识别中获得不同的测试数据精度

TensorFlow MNIST 手写数字识别之过拟合