Улучшение реальных результатов нейронной сети, обученной с помощью набора данных mnist

Я построил нейронную сеть с keras, используя набор данных mnist, и теперь я пытаюсь использовать его на фотографиях реальных рукописных цифр. Конечно, я не ожидаю, что результаты будут идеальными, но те результаты, которые я получаю в настоящее время, имеют много возможностей для улучшения.

Для начала я тестирую его на нескольких фотографиях отдельных цифр, написанных моим самым четким почерком. Они квадратные, имеют те же размеры и цвет, что и изображения в наборе данных mnist. Они сохраняются в папке с именем индивидуальный_тест, например, например: 7 (2) _digit.jpg.

Сеть часто ужасно уверена в неверном результате, и я приведу вам пример:

явно 7

Результаты, которые я получил для этого изображения, следующие:

result:  3 . probabilities:  [1.9963557196245318e-10, 7.241294497362105e-07, 0.02658148668706417, 0.9726449251174927, 2.5416460047722467e-08, 2.6078915027483163e-08, 0.00019745019380934536, 4.8302300825753264e-08, 0.0005754049634560943, 2.8358477788259506e-09]

Таким образом, сеть на 97% уверена, что это 3, и эта картина далеко не единственный случай. Из 38 изображений только 16 были распознаны правильно. Что меня шокирует, так это то, что сеть настолько уверена в своем результате, хотя и не может быть дальше от правильного результата.

РЕДАКТИРОВАТЬ
После добавления порога к prepare_image (img = cv2.threshold(img, 0.1, 1, cv2.THRESH_BINARY_INV)[1]) производительность немного улучшилась. Теперь он получает 19 из 38 изображений правильно, но для некоторых изображений, включая показанное выше, он по-прежнему уверен в неверном результате. Вот что я получаю сейчас:

result:  3 . probabilities:  [1.0909866760000497e-11, 1.1584616004256532e-06, 0.27739930152893066, 0.7221096158027649, 1.900260038212309e-08, 6.555900711191498e-08, 4.479645940591581e-05, 6.455550760620099e-07, 0.0004443934594746679, 1.0013242457418414e-09]

Так что теперь он уверен в своем результате только на 72%, что лучше, но все же ...



Что я могу сделать, чтобы повысить производительность? Могу ли я лучше подготовить свои изображения? Или я должен добавить свои изображения к обучающим данным? И если да, то как я могу это сделать?

ИЗМЕНИТЬ

Вот как выглядит изображение, показанное выше, после применения к нему prepare_image:
 моя фотография после лечения
После использования порога это то же самое картинка выглядит так:
 after threshold
Для сравнения: это одно из изображений, предоставленных набором данных mnist:
 одна из множества цифр
Они очень похожи на меня. Как я могу это улучшить?
Вот мой код (включая порог):

# import keras and the MNIST dataset
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from keras.utils import np_utils
# numpy is necessary since keras uses numpy arrays
import numpy as np

# imports for pictures
import matplotlib.pyplot as plt
import PIL
import cv2

# imports for tests
import random
import os

class mnist_network():
    def __init__(self):
        """ load data, create and train model """
        # load data
        (X_train, y_train), (X_test, y_test) = mnist.load_data()
        # flatten 28*28 images to a 784 vector for each image
        num_pixels = X_train.shape[1] * X_train.shape[2]
        X_train = X_train.reshape((X_train.shape[0], num_pixels)).astype('float32')
        X_test = X_test.reshape((X_test.shape[0], num_pixels)).astype('float32')
        # normalize inputs from 0-255 to 0-1
        X_train = X_train / 255
        X_test = X_test / 255
        # one hot encode outputs
        y_train = np_utils.to_categorical(y_train)
        y_test = np_utils.to_categorical(y_test)
        num_classes = y_test.shape[1]


        # create model
        self.model = Sequential()
        self.model.add(Dense(num_pixels, input_dim=num_pixels, kernel_initializer='normal', activation='relu'))
        self.model.add(Dense(num_classes, kernel_initializer='normal', activation='softmax'))
        # Compile model
        self.model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

        # train the model
        self.model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=200, verbose=2)

        self.train_img = X_train
        self.train_res = y_train
        self.test_img = X_test
        self.test_res = y_test


    def predict_result(self, img, show = False):
        """ predicts the number in a picture (vector) """
        assert type(img) == np.ndarray and img.shape == (784,)

        if show:
            img = img.reshape((28, 28))
            # show the picture
            plt.imshow(img, cmap='Greys')
            plt.show()
            img = img.reshape(img.shape[0] * img.shape[1])

        num_pixels = img.shape[0]
        # the actual number
        res_number = np.argmax(self.model.predict(img.reshape(-1,num_pixels)), axis = 1)
        # the probabilities
        res_probabilities = self.model.predict(img.reshape(-1,num_pixels))

        return (res_number[0], res_probabilities.tolist()[0])    # we only need the first element since they only have one


    def prepare_image(self, img, show = False):
        """ prepares the partial images used in partial_img_rec by transforming them
            into numpy arrays that the network will be able to process """
        # convert to greyscale
        img = img.convert("L")
        # rescale image to 28 *28 dimension
        img = img.resize((28,28), PIL.Image.ANTIALIAS)
        # inverse colors since the training images have a black background
        #img =  PIL.ImageOps.invert(img)
        # transform to vector
        img = np.asarray(img, "float32")
        img = img / 255.
        img[img < 0.5] = 0.

        img = cv2.threshold(img, 0.1, 1, cv2.THRESH_BINARY_INV)[1]

        if show:
            plt.imshow(img, cmap = "Greys")

        # flatten image to 28*28 = 784 vector
        num_pixels = img.shape[0] * img.shape[1]
        img = img.reshape(num_pixels)

        return img


    def partial_img_rec(self, image, upper_left, lower_right, results=[], show = False):
        """ partial is a part of an image """
        left_x, left_y = upper_left
        right_x, right_y = lower_right

        print("current test part: ", upper_left, lower_right)
        print("results: ", results)
        # condition to stop recursion: we've reached the full width of the picture
        width, height = image.size
        if right_x > width:
            return results

        partial = image.crop((left_x, left_y, right_x, right_y))
        if show:
            partial.show()
        partial = self.prepare_image(partial)

        step = height // 10

        # is there a number in this part of the image? 
        res, prop = self.predict_result(partial)
        print("result: ", res, ". probabilities: ", prop)
        # only count this result if the network is at least 50% sure
        if prop[res] >= 0.5:        
            results.append(res)
            # step is 80% of the partial image's size (which is equivalent to the original image's height) 
            step = int(height * 0.8)
            print("found valid result")
        else:
            # if there is no number found we take smaller steps
            step = height // 20 
        print("step: ", step)
        # recursive call with modified positions ( move on step variables )
        return self.partial_img_rec(image, (left_x + step, left_y), (right_x + step, right_y), results = results)

    def individual_digits(self, img):
        """ uses partial_img_rec to predict individual digits in square images """
        assert type(img) == PIL.JpegImagePlugin.JpegImageFile or type(img) == PIL.PngImagePlugin.PngImageFile or type(img) == PIL.Image.Image

        return self.partial_img_rec(img, (0,0), (img.size[0], img.size[1]), results=[])

    def test_individual_digits(self):
        """ test partial_img_rec with some individual digits (shape: square) 
            saved in the folder 'individual_test' following the pattern 'number_digit.jpg' """
        cnt_right, cnt_wrong = 0,0
        folder_content = os.listdir(".\individual_test")

        for imageName in folder_content:
            # image file must be a jpg or png
            assert imageName[-4:] == ".jpg" or imageName[-4:] == ".png"
            correct_res = int(imageName[0])
            image = PIL.Image.open(".\\individual_test\\" + imageName).convert("L")
            # only square images in this test
            if image.size[0]  != image.size[1]:
                print(imageName, " has the wrong proportions: ", image.size,". It has to be a square.")
                continue 
            predicted_res = self.individual_digits(image)

            if predicted_res == []:
                print("No prediction possible for ", imageName)
            else:
                predicted_res = predicted_res[0]

            if predicted_res != correct_res:
                print("error in partial_img-rec! Predicted ", predicted_res, ". The correct result would have been ", correct_res)
                cnt_wrong += 1
            else:
                cnt_right += 1
                print("correctly predicted ",imageName)
        print(cnt_right, " out of ", cnt_right + cnt_wrong," digits were correctly recognised. The success rate is therefore ", (cnt_right / (cnt_right + cnt_wrong)) * 100," %.")

    def multiple_digits(self, img):
        """ takes as input an image without unnecessary whitespace surrounding the digits """

        #assert type(img) == myImage
        width, height = img.size
        # start with the first square part of the image
        res_list = self.partial_img_rec(img, (0,0),(height ,height), results = [])
        res_str = ""
        for elem in res_list:
            res_str += str(elem)
        return res_str

    def test_multiple_digits(self):
        """ tests the function 'multiple_digits' using some images saved in the folder 'multi_test'.
            These images contain multiple handwritten digits without much whitespac surrounding them.
            The correct solutions are saved in the files' names followed by the characte '_'. """

        cnt_right, cnt_wrong = 0,0
        folder_content = os.listdir(".\multi_test")
        for imageName in folder_content:
            # image file must be a jpg or png
            assert imageName[-4:] == ".jpg" or imageName[-4:] == ".png"            
            image = PIL.Image.open(".\\multi_test\\" + imageName).convert("L")

            correct_res = imageName.split("_")[0]
            predicted_res = self.multiple_digits(image)
            if correct_res == predicted_res:
                cnt_right += 1
            else:
                cnt_wrong += 1
                print("Error in multiple_digits! The network predicted ", predicted_res, " but the correct result would have been ", correct_res)

        print("The network predicted correctly ", cnt_right, " out of ", cnt_right + cnt_wrong, " pictures. That's a success rate of ", cnt_right / (cnt_right + cnt_wrong) * 100, "%.")

network = mnist_network()
# this is the image shown above
result = network.individual_digits(PIL.Image.open(".\individual_test\\7(2)_digit.jpg"))

person Johanna    schedule 30.12.2019    source источник
comment
Вам следует попробовать больше эпох для обучения, поскольку 10 недостаточно для правильного обучения, вы можете изменить такие параметры, как размер пакета, скорость обучения, оптимизатор и т. Д., Ваша предварительная обработка правильная, но, как подсказывает один из ответов, вы также должны выполнить пороговую обработку. После этого следует попробовать разное количество слоев и нейронов. Кроме того, вы можете посмотреть на сверточные сети, которые лучше подходят для изображений.   -  person SajanGohil    schedule 30.12.2019
comment
@SajanGohil в данном случае с этими параметрами 10 эпох более чем достаточно, так как она начинает переобучаться в 5 эпоху.   -  person Geeocode    schedule 31.12.2019
comment
Джоанна, пожалуйста, посмотрите мой полный ответ ниже, как я и обещал.   -  person Geeocode    schedule 01.01.2020
comment
Если проблема обычно в 7, это может быть связано с тем, что вы использовали европейскую 7 (с косой чертой) в тесте по сравнению с американской 7 (без косой черты) в поезде.   -  person jeremy_rutman    schedule 01.01.2020
comment
@jeremy_rutman Так же обстоит дело и с множеством других цифр, но я выбрал именно эту 7, потому что она очень хорошо показывает проблему. Я попытался скормить ему американские цифры, но это не сработало.   -  person Johanna    schedule 01.01.2020
comment
Два возможных объяснения того, что вы видите: 1. Вы применили этап предварительной обработки во время обучения и не сделали того же на своем тестовом наборе. 2. Вы видите различия в распределении (сдвиг домена), т.е. ваши обучающие примеры отличаются от вашего тестового примера. В этом случае вам нужно посмотреть на тонкую настройку (потренироваться на наборе данных MNIST и убедиться, что вы получили хорошую точность при валидации, а затем точно настроить модель на нескольких примерах из вашего набора данных)   -  person Ahmad Baracat    schedule 02.01.2020
comment
@jeremy_rutman Хотя я не упомянул в своем ответе, из-за его масштабов я также протестировал этот сценарий и обнаружил, что версия с косой чертой недостаточно представлена ​​в MNIST, но существует. Таким образом, если я выполнил предварительную обработку (см. Мой ответ ниже), разрезанная версия также будет правильно классифицирована.   -  person Geeocode    schedule 02.01.2020


Ответы (3)


Обновление:

У вас есть три варианта повышения производительности в этой конкретной задаче:

  1. Используйте сверточную сеть, так как она лучше справляется с задачами с пространственными данными, такими как изображения, и является более генеративным классификатором, как этот.
  2. Используйте или Создайте и / или сгенерируйте больше изображений ваших типов и обучите свою сеть с их помощью, ваша сеть тоже сможет их изучить.
  3. Предварительно обработайте ваши изображения, чтобы они лучше соответствовали исходным изображениям MNIST, с которыми вы ранее тренировали свою сеть.

Я только что провел эксперимент. Я проверил изображения MNIST относительно одного представленного числа каждое. Я взял ваши изображения и сделал некоторую предварительную обработку, которую предлагал вам ранее, например:

1. сделал некоторый порог, но просто убрал фоновый шум вниз, потому что исходные данные MNIST имеют некоторый минимальный порог только для пустого фона:

image[image < 0.1] = 0.

2. Как ни странно, размер числа внутри изображения оказался решающим, поэтому я масштабировал число внутри изображения 28 x 28, например у нас есть больше отступов вокруг числа.

3. Я инвертировал изображения, так как данные MNIST от keras также были инвертированы.

image = ImageOps.invert(image)

4. Наконец, мы масштабировали данные, как мы это делали во время обучения:

image = image / 255.

После предварительной обработки я обучил модель с помощью набора данных MNIST с параметрами epochs=12, batch_size=200 и результатами:

введите здесь описание изображения  введите описание изображения здесь

Результат: 1 с вероятностями: 0,6844741106033325

 result:  **1** . probabilities:  [2.0584749904628552e-07, 0.9875971674919128, 5.821426839247579e-06, 4.979299319529673e-07, 0.012240586802363396, 1.1566483948399764e-07, 2.382085284580171e-08, 0.00013023221981711686, 9.620113416985987e-08, 2.5273093342548236e-05]

введите здесь описание изображения  введите описание изображения здесь

Результат: 6 с вероятностями: 0,9221984148025513.

result:  6 . probabilities:  [9.130864782491699e-05, 1.8290626258021803e-07, 0.00020504613348748535, 2.1564576968557958e-07, 0.0002401985548203811, 0.04510130733251572, 0.9221984148025513, 1.9014490248991933e-07, 0.03216308355331421, 3.323434683011328e-08]

введите здесь описание изображения  введите описание изображения здесь

Результат: 7 с вероятностями: 0,7105212807655334 Примечание.

result:  7 . probabilities:  [1.0372193770535887e-08, 7.988557626958936e-06, 0.00031014863634482026, 0.0056108818389475346, 2.434678014751057e-09, 3.2280522077599016e-07, 1.4190952857262573e-09, 0.9940618872642517, 1.612859932720312e-06, 7.102244126144797e-06]

С вашим числом 9 было немного сложно:

введите здесь описание изображения  введите описание изображения здесь

Как я понял, модель с набором данных MNIST уловила две основные «особенности», относящиеся к 9. Верхняя и нижняя части. Верхние части с красивой круглой формой, как на вашем изображении, - это не 9, а в основном 3 для вашей модели, обученной по набору данных MNIST. Нижняя часть 9 в основном представляет собой выпрямленную кривую в соответствии с набором данных MNIST. Таким образом, ваша идеальная форма 9 всегда является 3 для вашей модели из-за образцов MNIST, если вы снова не обучите модель с достаточным количеством образцов вашей формы 9. Чтобы проверить свои мысли, я провел подэксперимент с 9 s:

Мой 9 с перекошенными верхними частями (в основном подходит для 9 согласно MNIST), но со слегка изогнутым низом (не подходит для 9 согласно MNIST. ):

введите здесь описание изображения

Результат: 9 с вероятностями: 0,5365301370620728.

Мой 9 с перекошенным верхом (в основном подходит для 9 согласно MNIST) и с прямым низом (подходит для 9 согласно MNIST):

введите здесь описание изображения

Результат: 9 с вероятностями: 0,923724353313446.

Ваш 9 с неверно истолкованными свойствами формы:

введите здесь описание изображения

Результат: 3 с вероятностями: 0.8158268928527832

result:  3 . probabilities:  [9.367801249027252e-05, 3.9978775021154433e-05, 0.0001467708352720365, 0.8158268928527832, 0.0005801069783046842, 0.04391581565141678, 6.44062723154093e-08, 7.099170943547506e-06, 0.09051419794559479, 0.048875387758016586]


И, наконец, еще одно доказательство важности масштабирования (заполнения) изображения, о чем я уже говорил выше:

введите здесь описание изображения

Результат: 3 с вероятностями: 0,9845736622810364

введите здесь описание изображения

Результат: 9 с вероятностями: 0,923724353313446.

Итак, мы видим, что наша модель уловила некоторые особенности, которые она интерпретирует, всегда классифицируя как 3 в случае слишком большой формы внутри изображения с небольшим размером отступа.

Я думаю, что с помощью CNN мы можем повысить производительность, но способ выборки и предварительной обработки всегда имеет решающее значение для достижения максимальной производительности в задаче машинного обучения.

Я надеюсь, что это помогает.

Обновление 2:

Я обнаружил еще одну проблему, которую я также проверил и подтвердил, что размещение числа внутри изображения также имеет решающее значение, что имеет смысл для этого типа NN. Хороший пример: номера 7 и 9, которые были помещены по центру в наборе данных MNIST, ближе к низу изображения, привели к усложнению классификации, если мы поместим новый номер. для классификации в центре изображения. Я проверил теорию, сдвинув 7 и 9 вниз, оставив больше места в верхней части изображения, и результат был почти 100% точность. Поскольку это проблема пространственного типа, я полагаю, что с помощью CNN мы могли бы устранить ее с большей эффективностью. Однако было бы лучше, если бы MNIST был привязан к центру, или мы могли бы сделать это программно, чтобы избежать проблемы.

person Geeocode    schedule 30.12.2019
comment
@Johanna В этом случае я сначала тренировался бы с MINST, а затем переучиваю обученную модель с вашими новыми изображениями и их расширенными образцами. - person Geeocode; 31.12.2019
comment
@Johanna завтра я посмотрю на это снова, так как это для меня довольно подозрительно, хотя мне было бы интересно, каковы будут результаты после тренировки сети с вашими изображениями. К вашему сведению, я пробовал использовать CNN, но результат почти такой же. - person Geeocode; 31.12.2019
comment
Я также проводил подобные тесты и могу подтвердить, что небольшое отступление вокруг цифры может улучшить результаты. У меня была 1 цифра, дополненная одним пикселем влево или вправо, что дало мне совершенно разные результаты. @Geeocode, думаете ли вы, что расширение данных внутри MNIST было бы решением? - person lucians; 28.09.2020
comment
@lucians Определенно. Если честно, когда я впервые получил результаты, я не хотел верить своим глазам. Затем я повторил тесты и изучил и другие аспекты, так что я думаю, что увеличение в любом случае улучшило бы простые результаты. - person Geeocode; 29.09.2020

Какой был ваш результат теста в наборе данных MNIST? И одна вещь, которая приходит мне в голову, что в ваших изображениях отсутствует пороговое значение,

Пороговое значение - это метод, при котором значение пикселя ниже определенного пикселя обнуляется. См. Примеры порогового определения OpenCV где угодно. Возможно, вам нужно использовать обратное пороговое значение и снова проверить свои результаты.

Сделайте, сообщите, есть ли прогресс.

person MbeforeL    schedule 30.12.2019
comment
Результат теста был где-то между 95 и 98%. Что касается пороговых значений - чем они отличаются от `img [img‹ 0.5] = 0`? - person Johanna; 30.12.2019
comment
@Johanna пиксели выше 0,5 станут 255, чего не произойдет с img[img<0.5]=0 - person SajanGohil; 31.12.2019
comment
Джоанна, вы сами видите разницу: ваши изображения имеют оттенки серого, а не пороговые значения, светло-серая часть должна быть полностью белой. Постарайтесь добиться этого. - person MbeforeL; 31.12.2019
comment
Я использовал порог для изображений, который немного улучшил производительность, но не решил проблему. Пожалуйста, посмотрите отредактированный пост. - person Johanna; 31.12.2019
comment
@SourabhSinha Набор данных MNIST ограничен только нижним порогом, например img [img ‹0.1] = 0, только для белого фона. Просто проверьте это, я сделал. - person Geeocode; 31.12.2019

Основная проблема, с которой вы сталкиваетесь, заключается в том, что изображения, которые вы тестируете, отличаются от изображений MNIST, вероятно, из-за подготовленных вами изображений, можете ли вы показать изображение из тех, с которыми вы тестируете, после того, как примените к нему prepare_image.

person hola    schedule 30.12.2019
comment
Спасибо за ваш ответ. Пожалуйста, посмотрите мой отредактированный пост, где я показываю картинку после применения к ней prepare_image. - person Johanna; 30.12.2019
comment
Я думаю, что у вас может быть одна из двух проблем: ваши изображения инвертированы по цвету (черный - белый и наоборот), или распределение пикселей в ваших тестовых изображениях действительно отличается по сравнению с обучающими изображениями, я думаю, что обе проблемы могут быть решены если вы используете сверточные слои вместо плотных слоев - person hola; 30.12.2019