如何提高在 MNIST 上训练的模型的数字识别?
How to improve digit recognition of a model trained on MNIST?
我正在使用 Java
进行手印多数字识别,使用 OpenCV
库进行预处理和分割,以及 Keras
在 MNIST 上训练的模型(精度为 0.98 ) 进行识别。
除一件事外,识别效果似乎很好。网络经常无法识别这些(数字 "one")。我无法弄清楚它是否是由于分割的预处理/不正确的实现而发生的,或者如果在标准 MNIST 上训练的网络只是没有看到看起来像我的测试用例的第一名。
这是经过预处理和分割后有问题的数字的样子:
becomes 并分类为 4
.
becomes 分类为 7
.
becomes 分类为 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);
}
我认为你的问题是膨胀过程。我知道你希望标准化图像尺寸,但你不应该打破比例,你应该调整到一个轴所需的最大值(允许最大重新缩放而不让另一个轴尺寸超过最大尺寸的轴)并填充与图像的其余部分的背景颜色。
不是 "standard MNIST just hasn't seen the number one which looks like your test cases",你让你的图像看起来像不同的训练数字(被识别的数字)
如果您保持图像(来源和 post- 处理)的正确纵横比,您会发现您不仅调整了图像的大小,而且 "distorted" 它。这可能是非均匀扩张或不正确调整大小的结果
因此,您需要一种复杂的方法,使计算级联的每一步都基于先前的结果。在您的算法中,您具有以下特征:
- 图像预处理
如前所述,如果应用调整大小,则会丢失有关图像纵横比的信息。您必须对数字图像进行相同的再处理才能获得训练过程中暗示的相同结果。
如果您只是通过固定大小的图片裁剪图像,效果会更好。在该变体中,您不需要在训练过程之前进行轮廓查找和调整数字图像的大小。然后你可以对你的裁剪算法做一点改变以更好地识别:简单地找到轮廓并将你的数字放在相关图像框的中心而不调整大小以进行识别。
还有二值化算法你要多注意。我有研究二值化阈值对学习错误的影响的经验:我可以说这是一个非常重要的因素。您可以尝试另一种二值化算法来检查这个想法。例如,您可以使用 this library 来测试备用二值化算法。
- 学习算法
要提高识别质量,您可以使用 cross-validation at the training process. This helps you to avoid the problem of overfitting for your training data. For example you may read this article,其中解释了如何将其与 Keras 一起使用。
有时,更高的准确度测量值并不能说明真正的识别质量,因为经过训练的 ANN 无法在训练数据中找到模式。它可能与上面解释的训练过程或输入数据集有关,也可能是由ANN架构选择引起的。
- 人工神经网络架构
这是个大问题。如何定义更好的 ANN 架构来解决任务?没有通用的方法来做那件事。但是有几种方法可以更接近理想。例如,您可以阅读 this book. It helps you to make a better vision for your problem. Also you may find here some heuristics formulas to fit the number of hidden layers/elements for your ANN. Also here,您会找到对此的一些概述。
希望对您有所帮助。
已经发布了一些答案,但它们都没有回答您关于 图像预处理 的实际问题。
轮到我了,只要它是一个研究项目,我认为你的实施没有任何重大问题,做得很好。
但是您可能会错过一件事。
数学形态学中有基本的运算:腐蚀和膨胀(你用的)。还有复杂的操作:基本操作的各种组合(例如打开和关闭)。
Wikipedia link 不是最好的 CV 参考,但您可以从它开始了解想法。
通常最好使用 opening 而不是 erosion 和 closing 而不是 dilation 因为在这种情况下原始二值图像的变化要小得多(但达到了清洁锐边或填充间隙的预期效果)。
所以在你的情况下你应该检查关闭(图像膨胀然后用相同的内核腐蚀)。
如果即使使用 1*1 内核(1 像素超过图像的 16%)进行扩展时,超小图像 8*8 也会发生很大修改,这在较大的图像上较少)。
为了形象化这个想法,请看下面的图片(来自 OpenCV 教程:1, 2):
扩张:
结束语:
希望对您有所帮助。
经过一些研究和实验,我得出的结论是图像预处理本身不是问题(我确实更改了一些建议的参数,例如膨胀大小和形状,但它们对结果并不重要)。然而,有帮助的是以下两件事:
正如@f4f 所注意到的,我需要用真实世界的数据收集我自己的数据集。这已经帮了大忙了。
我对分割预处理进行了重要更改。获得单独的轮廓后,我首先对图像进行大小归一化以适合 20x20
像素框(因为它们在 MNIST
中)。之后,我使用质心(对于二值图像,质心是两个维度的平均值)将框置于 28x28
图像的中间。
当然,仍然存在一些难以分割的情况,例如重叠或连接的数字,但上述更改回答了我最初的问题并提高了我的分类性能。
我正在使用 Java
进行手印多数字识别,使用 OpenCV
库进行预处理和分割,以及 Keras
在 MNIST 上训练的模型(精度为 0.98 ) 进行识别。
除一件事外,识别效果似乎很好。网络经常无法识别这些(数字 "one")。我无法弄清楚它是否是由于分割的预处理/不正确的实现而发生的,或者如果在标准 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);
}
我认为你的问题是膨胀过程。我知道你希望标准化图像尺寸,但你不应该打破比例,你应该调整到一个轴所需的最大值(允许最大重新缩放而不让另一个轴尺寸超过最大尺寸的轴)并填充与图像的其余部分的背景颜色。 不是 "standard MNIST just hasn't seen the number one which looks like your test cases",你让你的图像看起来像不同的训练数字(被识别的数字)
如果您保持图像(来源和 post- 处理)的正确纵横比,您会发现您不仅调整了图像的大小,而且 "distorted" 它。这可能是非均匀扩张或不正确调整大小的结果
因此,您需要一种复杂的方法,使计算级联的每一步都基于先前的结果。在您的算法中,您具有以下特征:
- 图像预处理
如前所述,如果应用调整大小,则会丢失有关图像纵横比的信息。您必须对数字图像进行相同的再处理才能获得训练过程中暗示的相同结果。
如果您只是通过固定大小的图片裁剪图像,效果会更好。在该变体中,您不需要在训练过程之前进行轮廓查找和调整数字图像的大小。然后你可以对你的裁剪算法做一点改变以更好地识别:简单地找到轮廓并将你的数字放在相关图像框的中心而不调整大小以进行识别。
还有二值化算法你要多注意。我有研究二值化阈值对学习错误的影响的经验:我可以说这是一个非常重要的因素。您可以尝试另一种二值化算法来检查这个想法。例如,您可以使用 this library 来测试备用二值化算法。
- 学习算法
要提高识别质量,您可以使用 cross-validation at the training process. This helps you to avoid the problem of overfitting for your training data. For example you may read this article,其中解释了如何将其与 Keras 一起使用。
有时,更高的准确度测量值并不能说明真正的识别质量,因为经过训练的 ANN 无法在训练数据中找到模式。它可能与上面解释的训练过程或输入数据集有关,也可能是由ANN架构选择引起的。
- 人工神经网络架构
这是个大问题。如何定义更好的 ANN 架构来解决任务?没有通用的方法来做那件事。但是有几种方法可以更接近理想。例如,您可以阅读 this book. It helps you to make a better vision for your problem. Also you may find here some heuristics formulas to fit the number of hidden layers/elements for your ANN. Also here,您会找到对此的一些概述。
希望对您有所帮助。
已经发布了一些答案,但它们都没有回答您关于 图像预处理 的实际问题。
轮到我了,只要它是一个研究项目,我认为你的实施没有任何重大问题,做得很好。
但是您可能会错过一件事。 数学形态学中有基本的运算:腐蚀和膨胀(你用的)。还有复杂的操作:基本操作的各种组合(例如打开和关闭)。 Wikipedia link 不是最好的 CV 参考,但您可以从它开始了解想法。
通常最好使用 opening 而不是 erosion 和 closing 而不是 dilation 因为在这种情况下原始二值图像的变化要小得多(但达到了清洁锐边或填充间隙的预期效果)。 所以在你的情况下你应该检查关闭(图像膨胀然后用相同的内核腐蚀)。 如果即使使用 1*1 内核(1 像素超过图像的 16%)进行扩展时,超小图像 8*8 也会发生很大修改,这在较大的图像上较少)。
为了形象化这个想法,请看下面的图片(来自 OpenCV 教程:1, 2):
扩张:
结束语:
希望对您有所帮助。
经过一些研究和实验,我得出的结论是图像预处理本身不是问题(我确实更改了一些建议的参数,例如膨胀大小和形状,但它们对结果并不重要)。然而,有帮助的是以下两件事:
正如@f4f 所注意到的,我需要用真实世界的数据收集我自己的数据集。这已经帮了大忙了。
我对分割预处理进行了重要更改。获得单独的轮廓后,我首先对图像进行大小归一化以适合
20x20
像素框(因为它们在MNIST
中)。之后,我使用质心(对于二值图像,质心是两个维度的平均值)将框置于28x28
图像的中间。
当然,仍然存在一些难以分割的情况,例如重叠或连接的数字,但上述更改回答了我最初的问题并提高了我的分类性能。