尽管神经网络在原始数据集上表现良好,但仍然对输入图像进行错误分类

alt*_*udo 3 python neural-network tensorflow

链接到相关数据集

在开始之前,先介绍一些可能相关的事情:

  • 输入文件格式为 JPEG。我使用's将它们转换为numpy数组matplotlibimread
  • tensorflow然后分别使用的image.resize方法和方法将 RGB 图像重新整形并转换为灰度图像image.rgb_to_grayscale

这是我的模型:

model = Sequential(
    [
        tf.keras.Input(shape=(784,),),
        Dense(200, activation= "relu"),
        Dense(150, activation= "relu"),
        Dense(100, activation= "relu"),
        Dense(50, activation= "relu"),
        Dense(26, activation= "linear")
    ]
)
Run Code Online (Sandbox Code Playgroud)

神经网络在数据集上的准确率达到 98.9%。但是,当我尝试使用自己的图像时,它总是将输入分类为“A”。

我什至达到了反转图像颜色的程度(黑到白,反之亦然;原始灰度图像的字母为黑色,其余部分为白色)。

img = plt.imread("20220922_194823.jpg")
img = tf.image.rgb_to_grayscale(img)
plt.imshow(img, cmap="gray")
Run Code Online (Sandbox Code Playgroud)

显示此图像。

img.shape回报TensorShape([675, 637, 1])

img = 1 - img
img = tf.image.resize(img, [28,28]).numpy()
plt.imshow(img, cmap="gray")
Run Code Online (Sandbox Code Playgroud)

是结果img = 1-img

我怀疑神经网络不断将输入图像分类为“A”,因为某些像素不是完全黑/白的。

但它为什么要这么做呢?将来我该如何避免这个问题?

这是笔记本。

Ban*_*ngo 7

我已经下载并测试了你的模型。当针对 Kaggle 数据集运行时,准确性如您所述。您也走上了正确的道路,反转了您自己的图像的输入值,即不起作用的图像。但是您应该查看一下训练输入:这些值在 0-255 的范围内,而您用 1-x 反转这些值,假设浮点数在 0-1 之间。我在 Paint 中画了一个简单的“X”和“P”,将其保存为 PNG(应该与 JPEG 的工作方式相同),神经网络可以很好地识别它们。为此,我使用 OpenCV 重新缩放它,对其进行灰度化,然后反转它(白色像素的值为 255,而训练输入使用 0 作为空白像素)。

\n

这是我所做的粗略代码:

\n
import numpy as np\nimport keras\nimport cv2\n\ndef load_image(path):\n    image = cv2.imread(path)\n    image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)\n    image = 255 - cv2.resize(image, (28,28))\n    image = image.reshape((1,784))\n    return image\n\ndef load_dataset(path):\n    dataset = np.loadtxt(path, delimiter=\',\')\n    X = dataset[:,0:784]\n    Y = dataset[:,0]\n    return X, Y\n\ndef benchmark(model, X, Y):\n    test_count = 100\n    tests = np.random.randint(0, X.shape[0], test_count)\n    correct = 0\n    p = model.predict(X[tests])\n    for i, ti in enumerate(tests):\n        if Y[ti] == np.argmax(p[i]):\n            correct += 1\n    print(f\'Accuracy: {correct / test_count * 100}\')\n\ndef recognize(model, image):\n    alph = "abcdefghijklmnopqrstuvwxyz"\n    p = model.predict(image)[0]\n    letter = alph[np.argmax(p)]\n    print(f\'Image prediction: {letter}\')\n\n    top3 = dict(sorted(\n        zip(alph, 100 * np.exp(p) / sum(np.exp(p))), \n        key=lambda x: x[1],\n        reverse=True)[:3])\n    print(f\'Top 3: {top3}\')\n\nimg_x = load_image(\'x.png\')\nimg_p = load_image(\'p.png\')\nX, Y = load_dataset(\'chardata.csv\')\nmodel = keras.models.load_model(\'CharRecognition.h5\')\nbenchmark(model, X, Y)\nrecognize(model, img_x)\nrecognize(model, img_p)\n
Run Code Online (Sandbox Code Playgroud)\n

预测分别为“x”和“p”。我还没有尝试过其他信件,但上面指出的问题似乎是高度确定性问题的一部分。

\n

这是我使用的图像(正如我所说,两者都是手绘的,没有生成任何内容):

\n

神经网络 X 的样本输入\n神经网络 P 的样本输入

\n

我还使用 JPEG 图像来运行它。您所需要做的就是更改imread. OpenCV 检测格式。如果您不想或不能使用 OpenCV 但仍然遇到问题,我可以扩展答案。不过,它可能超出了您的实际问题的范围。相关文档:OpenCV文档。Pillow 和 scikit-image 的工作原理非常相似。

\n

我注意到输出产生的值变化很大 - 许多值都用长科学记数法打印。这使得评估神经网络的输出变得困难。因此,当你不使用 softmax 层时,你也可以单独计算概率,就像我在recognize(参见维基百科:Softmax的公式和更多解释)中所做的那样。我在这里提到它是因为它可以帮助将来解决此类问题,并使其他试图帮助您解决问题的人更容易。

\n

对于上面的图像,它会生成类似这样的内容,这表明该类别具有很高的确定性:

\n
Image prediction: x\nTop 3: {\'x\': 100.0, \'a\': 0.0, \'b\': 0.0}\nImage prediction: p\nTop 3: {\'p\': 100.0, \'d\': 2.6237523e-09, \'q\': 7.537843e-12}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么在你的例子中预测总是“a”?假设你没有犯任何其他错误,我必须猜测,但我认为这可能是因为字母占据了图像中的大量区域,所以大部分区域被填充的倒置图像in 与它最为相似。或者,神经网络看到的“a”的倒置图像与训练期间看到的“a”的图像最接近。这是一个猜测。但如果你给神经网络一些它在训练过程中从未真正见过的东西,我认为任何人能做的最好的事情就是猜测结果。我本来预计它可能会更随机地分布在类别中,因此您的代码中可能存在其他一些问题,可能与评估预测有关。

\n

出于好奇,我又使用了两张图像,它们看起来根本不像字母:

\n

废话\n在此输入图像描述

\n

神经网络坚持的第一张图像是“e”:

\n
Top 3: {\'e\': 99.99985, \'s\': 0.00014580016, \'c\': 1.3610912e-06}\n
Run Code Online (Sandbox Code Playgroud)\n

它确信第二个图像是“a”:

\n
Top 3: {\'a\': 100.0, \'h\': 1.28807605e-08, \'r\': 1.0681121e-10}\n
Run Code Online (Sandbox Code Playgroud)\n

对于你的神经网络来说,这类随机图像可能只是“看起来像一个 a”。此外,众所周知,神经网络有时很容易被愚弄,并会专注于看起来非常违反直觉的特征:Jiawei Su、Danilo Vasconcellos Vargas 和 Sakurai Kouichi,\xe2\x80\x9cOne Pixel Attack for Fooling Deep Neural网络,\xe2\x80\x9d IEEE Transactions on Evolutionary Computation 23,no。5(2019 年 10 月):828\xe2\x80\x9341,https://doi.org/10.1109/TEVC.2019.2890858

\n

我认为关于训练神经网络还有一个值得学习的教训:我预计,在您正在解决的分类问题的情况下,这似乎已经成为许多机器学习中的规范入门问题在课程中,即使在训练有素的网络中,不明确属于任何训练类的输入也会表现为分布在多个类中的预测,这表明输入的模糊性。但是,正如我们在这里所看到的,显然,“未知”输入根本不需要产生这样的结果。即使这样的情况也可以产生似乎显示出输入属于某个类别的高度确定性的结果,例如神经网络表明无意义的潦草文字是“e”的明显“确定性”程度。

\n

因此,也许可以得出另一个结论:如果想要适当地处理不属于任何训练类别的输入,则必须明确地为此目的训练神经网络。我的意思是,必须添加一类额外的非字母图像,并使用无意义的图像、杂项图像(例如上面的花)或什至非常接近字母的图像(例如数字和数字)来训练它。非拉丁文字符号。也许正是“杂项类别”的紧密程度可以帮助神经网络更清楚地了解字母的构成。然而,正如我们在这里所看到的,在一组目标类别上训练神经网络,然后简单地期望它也能够在这些类别之外的输入的情况下给出有用的预测似乎是不够的。有些人可能会觉得我在这一点上想得太多,并使这个话题变得复杂化,但我认为对神经网络的观察已经足够重要了,至少对我自己来说,这是非常值得牢记的。

\n

预处理图像

\n

从评论里的交流来看,这个问题还有另外一个方面。我画的图像恰好效果很好。然而,当我增加对比度时,它们就不再被识别。我将首先介绍我是如何做到这一点的。由于它是机器学习中的常见函数,因此我有一个有点非常规的想法,即应用缩放的 sigmoid 函数,以便将值保持在 0-255 范围内,保留一些相对阴影,但调高对比度。更多信息请参见:维基百科:Sigmoid。我说“非常规”是因为我不认为它是你通常用于图像的东西,但由于这个函数在机器学习中非常普遍,特别是激活函数,我认为重新利用它可能会很有趣尽管与图像处理中更常见的算法相比,其性能可能很糟糕。

\n

(旁白:我曾经对音频处理做过几乎完全相同的事情,最终,当应用于音量时,其功能就像压缩器一样。这就是我们正在做的事情:我们“压缩”这里的灰度范围,而不完全消除过渡。我相信,这最终真正查明了这个神经网络的问题,因为它是一个看起来更具体的修改,但几乎正确地继续摆脱了神经网络如果您愿意的话,稍微调整一下这个“广义 sigmoid”函数中的参数,使其更平滑(这意味着:不太陡峭,以保留更多的过渡。玩转 Desmos 图并查看 PyPlot 预览, ),并更好地了解神经网络在什么时候放弃并说“我不再认识这个了。”更倾向于图形化的人可能还会想起经常用于smoothstep调整粗糙度的函数着色器 GLSL 中的边缘:smoothstep)。

\n

乙状锁链\n锁链图

\n

公式(s = 25,b = 50 似乎给出了良好的结果):

\n

公式

\n

然后,我使用如下代码对图像进行预处理:

\n
import matplotlib.pyplot as plt\n\ndef preprocess(before):\n    s, b = 25, 50\n    f = lambda x: np.exp(s*(x/255 - s/b)) / (1 + np.exp(s*(x/255 - s/b)))\n    after = f(before)\n\n    fig, ax = plt.subplots(1,2)\n    ax[0].imshow(before, cmap=\'gray\')\n    ax[1].imshow(after, cmap=\'gray\')\n    plt.show()\n\n    return after\n
Run Code Online (Sandbox Code Playgroud)\n

load_image在重塑之前调用上面的内容。在将图像输入神经网络之前,它会并排显示结果。一般来说,不仅在机器学习中,而且在统计中,在进一步使用数据之前了解数据、预览数据并对其进行健全性检查似乎是一种很好的做法。这也可能尽早提示您输入图像有什么问题。

\n

下面是一个使用上面图像的示例,展示了预处理之前和之后的外观:

\n

高对比度 1\n高对比度2

\n

考虑到这是一个临时的想法并且有些非常规,它似乎运作得很好。然而,以下是这些图像处理后的新预测:

\n
Image prediction: l\nTop 3: {\'l\': 11.176592, \'y\': 9.341431, \'x\': 7.692416}\n\nImage prediction: q\nTop 3: {\'q\': 11.703363, \'p\': 9.119178, \'l\': 7.6522427}\n
Run Code Online (Sandbox Code Playgroud)\n

它根本不再识别这些图像,这证实了您可能遇到的一些问题。您的神经网络已经“学习”了字母周围的灰色模糊过渡,使其成为它所考虑的特征的一部分。我使用这个网站来绘制图像:JSPaint。也许部分是运气或直觉,我使用画笔而不是钢笔工具,因为我可能会遇到与您遇到的相同问题,因为它不会产生从黑色到白色的过渡。这对我来说似乎很自然,因为它似乎最适合你训练输入的“感觉”,即使它一开始看起来像是一个微不足道、可以忽略不计的细节。运气、经验——我不知道。但是,您因此想要做的是使用留下“模糊边界”的工具,或者编写另一个预处理步骤,该步骤与我刚刚演示的相反,以显示负面情况,并为边界添加模糊。

\n

数据增强

\n

我以为我早就解决了这个问题,但它确实表明了处理神经网络可以很快得到解决,看起来。这个问题的核心似乎最终触及了机器学习的一些基础知识。我将清楚地说明我认为这个例子最终展示的内容,非常具有说明性,也许比大多数其他读者更适合我自己:

\n
\n

你的神经网络只学习你教它的东西。

\n
\n

解释可能很简单,而且可能有重要的例外,即你没有教你的神经网络识别具有清晰边框的字母,因此它没有学习如何识别它们。我不是一位出色的机器学习专家,因此对于更有经验的人来说,这可能都不是新闻。但这让我想起了机器学习中的一项技术,我认为它可以很好地应用于这种场景,那就是“数据增强”:

\n
\n

数据分析中的数据增强是通过添加现有数据的稍微修改的副本或从现有数据新创建的合成数据来增加数据量的技术。它充当正则化器,有助于在训练机器学习模型时减少过度拟合。

\n
\n

维基百科:数据增强

\n

佩雷斯、路易斯和杰森·王。\xe2\x80\x9c使用深度学习进行图像分类中数据增强的有效性。\xe2\x80\x9d arXiv,2017 年 12 月 13 日。https://doi.org/10.48550/arXiv.1712.04621。\n

\n

好消息可能是,我已经为您提供了进一步训练神经网络所需的一切,除了您已经从该 CSV 文件加载的数百兆字节的训练数据之外,不需要任何其他数据。在学习过程中,使用上面的对比度增强预处理函数创建每个训练图像的变体,以便它学会处理此类变化。

\n
    \n
  • 另一种模型架构最终会不会对这些细节不那么挑剔?

    \n
  • \n
  • 也许不同的激活函数可以更灵活地处理这些情况?

    \n
  • \n
\n

我不知道,但总的来说,这些对于机器学习来说似乎是非常有趣的问题。

\n

调试神经网络

\n

这个答案已经达到了我确实没有想到的程度,所以我开始感到有必要为再次添加它而道歉,但这立即导致人们思考一个更广泛的问题,这个问题可能一直困扰着机器学习社区(或者至少是像我一样拥有卑微经验的人):

\n

如何调试神经网络?

\n

到目前为止,这是一系列的尝试和错误,一些运气,一点点直觉,但有时当神经网络不工作时,感觉就像在黑暗中拍摄。这可能远非完美,但似乎已经在网上传播的一种方法是可视化哪些神经元针对给定输入激活,以便了解图像中的哪些区域或更一般的输入影响最终的预测最重要的是神经网络。

\n

为此,Keras 已经提供了一些功能,让您可以访问每个模型层的输出。提醒一下,相关模型的架构如下所示:

\n
_________________________________________________________________\n Layer (type)                Output Shape              Param #   \n=================================================================\n dense (Dense)               (None, 200)               157000                                                                 \n dense_1 (Dense)             (None, 150)               30150                                                                  \n dense_2 (Dense)             (None, 100)               15100                                                                  \n dense_3 (Dense)             (None, 50)                5050                                                                  \n dense_4 (Dense)             (None, 26)                1326      \n                                                             \n=================================================================\nTotal params: 208,626\nTrainable params: 208,626\nNon-trainable params: 0\n_________________________________________________________________\n
Run Code Online (Sandbox Code Playgroud)\n

您可以通过创建新模型并组合每层的输出来访问每层的激活。我们可以绘制。现在,使用 CNN 会容易得多,而且这些可能更适合图像,但这很好。问题的作者对这些还不满意,所以让我们继续我们所拥有的。对于 CNN 层,我们自然会绘制一个二维形状,但神经元的密集层是一维的。在这种情况下,我喜欢做的就是将它们填充到下一个更大的正方形,尽管它并不完美。

\n
def trace(model, image):\n    outputs = [layer.output for layer in model.layers] \n    trace_model = keras.models.Model(inputs=model.input, outputs=outputs)\n    p = trace_model.predict(image) \n\n    fig, ax = plt.subplots(1, len(p))\n    for i, layer in enumerate(p):\n        neurons = layer[0].shape[0]\n        square = int(np.ceil(np.sqrt(neurons)))\n        padding = square**2 - neurons\n        activations = np.append(layer[0], [np.min(layer[0])]*padding).reshape((square,square))\n        ax[i].imshow(activations)\n\n    plt.show()\n
Run Code Online (Sandbox Code Playgroud)\n

正如我所说,使用 CNN 层会更好,这就是为什么互联网上与该主题相关的大多数资源都会使用这些层,所以我认为建议密集层的一些东西可能会有用。

\n

以下是上面字母“x”和“p”的相同图像的结果:

\n

x 的激活\np 的激活

\n

我们可以看到每个图都绘制了一幅图像,神经网络的每一层都有一个图像。据我所知,这个颜色图是“viridis”,是 pyplot 当前的默认颜色图,其中蓝色是最低值,黄色是最高值。您可以看到每层图像末尾的填充,除非它恰好已经是一个完美的正方形(例如 100 的情况)。可能有更好的方法来清楚地描述这些。在第二张图像“p”的情况下,可以从最后一层的输出中看出最终分类,因为最亮、最黄的点位于第三行第四列(​​“p”是第 16 个点)字母表中的字母,16 = 2x6+4,因为 26 个字母的下一个更高的正方形是 36,所以它最终形成一个 6x6 的正方形)。

\n

要获得关于问题所在或发生的情况的明确答案仍然有些困难,但这可能是一个有用的开始。使用 CNN 的其他实例可以更清楚地显示哪种形状会触发神经网络,但这种技术的变体也许也可以应用于密集层。为了仔细尝试解释这些图像,它似乎确实可能证实神经网络对于它所了解的图像特征非常具体,正如这两个图像的第一层中的单一明亮的黄色点可能表明的那样。理想情况下,人们更可能期望的是,神经网络会考虑图像中具有相似权重的更多特征,从而更加关注字母的整体形状。然而,我对此不太确定,正确解释这些“激活图”可能并不简单。

\n