Нейросеть. Обработка изображения. Заключительный вывод не меняется

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

Сама проблема описана в самом низу, тут описание кода
Я пытался написать нейросеть на Python по типу Big_jpg и ему подобным, которые увеличивают изображение без потери качества, использую библиотеки Numpy, Scipy и Pil. Так как я только разбираюсь и начинаю работать полноценно с изображениями, я пытаюсь реализовать его упрощённый вариант. Написал эту нейросеть на основе MNIST теста с 1 скрытым слоем, сначала доработал её и сделал возможность поставить любое количество скрытых слоёв с разным количеством узлов в каждом из них (причём эффективность не уменьшилась при тестах, что говорит о рабочем и правильно коде, то есть итоговая проблема вряд ли в этом), реализовал класс с преобразованием изображения в массив и подготовил тестовые данные.

На вход: изображение 100x100 png или jpg
В виде цели выступает второе изображение 200x200 png или jpg.
На выходе: изображение 200 на 200 png

При тренировке сети я сравниваю только три параметра - RGB цвета, прозрачность не беру в расчёты. А преобразую изображение в вид большого списка размерами width * height * 3, где каждые три подряд идущих значения - это три цвета одного пиксиля. И да, получается, что входной слой в 4 раза меньше выходящего, и я долго боялся, что это неправильный подход, так же как и совсем остальным, так как я не нашёл информации на эту тему на русском, пришлось как то самому всё додумывать самому.

Каждый пиксель в значение на вход преобразую по формуле: pixel_color / 255 * 0.99 - 0.01
Генерацию начальных весов реализовал так:

# self.Ws - количество массивов весов 
# self.lays - количество слоёв 
# self.inodes - количество узлов входного слоя 
# self.hnodes - список с количеством узлов в каждом из скрытых слоёв
# self.onodes - количество узлов выходящего слоя

for W in range(self.Ws):

            # Задаём весы между входящим и первым скрытом слоем
            if W == 0:
                test_W = (np.random.normal(0.0, pow(self.hnodes[0], -0.15), (self.hnodes[0], self.inodes)))
                
            # Задаём весы между скрытами слоями
            elif W == (self.lays - 1) - 1:
                test_W = (np.random.normal(0.0, pow(self.onodes, -0.15), (self.onodes, self.hnodes[self.h_lays - 1])))
                
            # Задаём весы между последним скрытом и выходящим слоем 
            else:
                test_W = (np.random.normal(0.0, pow(self.hnodes[W], -0.15), (self.hnodes[W], self.hnodes[W - 1])))

            # Добавляем создаваемые весы в список весов
            self.W_array.append(test_W)

А саму тренировку так:

# Тренировка нейронной сети
        # Преобразование тренеровочных наборов данных
        # Процесс повторяется e количество раз (e эпох)
        epochs = 2     
        
        p0 = 0
        for e in range(epochs):
            for img_num in range(len(imc.Train_img_arr[0])):
            
                all_values = []
                
                width = imc.Train_img_arr[0][img_num].shape[0] # imc - класс преобразования и хранения данных изображений
                height = imc.Train_img_arr[0][img_num].shape[1] # imc.Train_img_arr[0][img_num] - массив изображения с номером img_num, '0' - обозначает вход

                res = [] - временный список
                for i in imc.Train_img_arr[0][img_num].reshape(width * height).tolist():
                    res += [(j / 255.0 * 0.99999999) + 0.00000001 for j in i] # добавляем в временный список значения RGB пиксилей картинки по очерёдости для Входа

                # Получаем список значений с фильтром разделения
                
                all_values = res # приравниваем значения на вход с временным списком

                res = []
                for i in imc.Train_img_arr[1][img_num].reshape(width * height * 4).tolist():
                    res += [(j / 255.0 * 0.99999999) + 0.00000001 for j in i] # добавляем в временный список значения RGB пиксилей картинки по очерёдости для Цели

                # Подгоняем входные значения под промежуток входа нейронной сети
                inputs = (np.asfarray(all_values))

                # Создаём целевые выходные значения
                targets = (np.asfarray(res))
                
                # функция тренировки 
                n.train(inputs, targets)

                # Вывод прогресса тренировки в %
                p = round((len(imc.Train_img_arr[0]) * e + img_num) / epochs / len(imc.Train_img_arr[0]) * 100)
        
                if p0 != p:
                    print(p, "%")
                p0 = p



                
                if p >= 98 and p <= 99:
                    #for i in n.W_array:
                        #print(i.shape)
                    print(n.W_array[0][50])
                pass
            pass
        print("100 %")

Сначала я пробовал за пример брать только одно изображение, чтобы просто понять реально ли это технически. получил итог что он без каких либо проблем смог вывести не обходимую мне картинку, естественно она была точь в точь, как и пример.
Но ещё тогда возникла странность: когда я на ввод брал другое изображение, не то же, что из тренировки, он всё равно выводил одну и туже картинку из тестовой цели. Хоть я и думал, что он должен был преобразовать её просто неправильно в отличие от первой, скинул на своё же непонимание и отсутствие примеров.

Если вывод сильно отличается от другого, значит я менял параметры сети для тестов (картинка на вход была та же)

Примеры и вывод с 50 тренировочными примерами:

Вывод при 50 примерах Вывод при 50 примерах

50 примеров

И уже после того как я подготовил выборку из 50 примеров для тренировки и 10 для теста (Причём 5 из них были в том числе и в тесте). При проверке после обучения я получил 10 полностью одинаковых изображений вплоть до пиксиля. Визуально они выглядели как наложенные друг на друга изображения из тестовой выборки (изображения менялось при изменении параметров сети, такие как скрытые узлы и коэф. обучения)

Сама Проблема: Проанализировав массивы Входа, Выхода и Весов я пришёл к выводу, что в процессе обучения весь первый массив весов, который определяет значимость входа на итог, не просто маленькие а отрицательные со значениями примерно от -1 до -0.7.

Чтобы понять как можно это было исправить, я пытался изменять неправильно обученный массив весов вручную, чтобы понять может ли выйти что-то путное.

Я пробовал умножать весь массив на -1, или ставить модуль. В первом случае, картинка снова была одинаковая, но в негативе, во втором же они получились в некоторых случаях разные, а в других нет.

Тренировочные примеры:
Тренировка Тестовые примеры:
Тест

Домножил на -1.
Вывод:
Негатив

Поставил модуль.
Вывод:
введите сюда описание изображения

К сожалению этими действиями я так ни к чему и не пришёл, поэтому и пишу сюда.

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

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

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

Так же, важно отметить, что скрытых слоёв я пробовал от 1 до 10, но количество узлов в слое никогда не превышало 2000, из-за отсутствия технических возможностей + неоптимизированный код, который я написал сам.

Я попробую использовать большее количество примеров и менять скрытые слои, на случай если проблема всё таки только в примерах, но мне кажется, что это не очень поможет.

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

Важно отметить что одного кода для его понимания может не хватить из за работы с папками и файлами, поэтому кратко опишу всё дерево используемых папок:

<Папака> data  
            <Папка> cash
                       <Файлы кэша...>  
            <Папка> final_output
                       <Папка> Final_list
                                  <Тесты по 10 (или другое кол.) картинок)> 
                       <Одиночные тесты по одной картинке> 


    <Папка> Saved
                           <Файлы сохранения>
                <Папка> Test
                           <Папка> input
                                      <Картинки на вход>
                <Папка> Train
                           <Папка> input
                                      <Картинки на вход>
                           <Папка> target
                                      <Картинки на вход>
<Файл> neuro.py

Думаю папка Train и Test не требуют пояснений, так что поясню cash с Saved и final_output.
Первые две папка служат для кеширования массивов, именно массивов, почти всех которые я инициализировал, связано это с тем что при тестах на больших количествах узлах мне не хватало памяти, так что пытался решить эту проблему как мог, но столкнулся с проблемой что библиотека Pickle не кеширует больше 4 Гб так что написал обходную функцию сохраняющую массив в несколько файлов при слишком большом размере массива. (2 гб)

Третья отвечает за вывод результатов после теста, сначала я тестировал всегда по одной картинке, но когда я заметил что от картинки на вход результат не меняется, для более наглядного теста сделал тест сразу по 10 картинок, чтобы отслеживать разницу между ними, но сколько не пытался так и не пришёл к чему-то путному.

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

Весь код:
https://drive.google.com/open?id=1wGT1FEnoJLU_2UGJ0LcyPaqGYNg-x2-A
Прикладываю ссылку сразу на архив со всеми папками, тестовыми данными и кодом, так как сам код по размеры не вмешается.
Так как я прикладываю последнюю версию, которая успела изменится со времени создания вопроса, я поменял тестовые и тренировочные данные с картинок 100x100 / 200x200 на 20x20 / 40x40 для меньшего расхода памяти, и более эффективных тестов.
Так же там очень много лишних отступов и бесполезных комментариев, я ими пользуюсь чтобы если что можно была вернуть часть кода которую я пытался заменить (потому что когда писал не был уверен что будет работать).

Важно: Ещё раз повторюсь, я не прошу разбираться и делать всё за меня, мне достаточно краткого ответа по теории, из за чего оно может быть, чтобы я сам если что мог почитать, просто я сколько не искал, ничего не нашёл на русскоязычных форумах и статьях (английский не шарю). Поэтому я даже не представляю из за чего это может быть.

Единственное что нашёл это про свёрточные нейросети для более простой и удобной работы с изображениями. И я даже уже пробую переписать часть кода чтобы оно работало по тому алгоритму, но... Даже если я использую не самый оптимизированный и правильный способ, в теории же мой код должен работать. Сколько не думаю, не могу понять, а что же мешает, просто уже без понятия.


Ответы (0 шт):