Проблема с сверточным слоем в Python: получение всех нулей на выходе и завершение на определенной итерации

Вопрос или проблема

В настоящее время я работаю над реализацией свёрточного слоя на Python для модели обработки естественного языка. Однако я столкнулся с проблемой в свёрточном слое, которую не могу решить.

Проблема состоит из двух частей:

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

  2. Завершение на определённой итерации: Кроме того, прямой проход завершается внезапно на определённой итерации, а именно на 30-й итерации. Я не могу понять, почему цикл выходит преждевременно.

Вот упрощённая версия моего класса Conv1DLayer:

import numpy as np

class Conv1DLayer:

    def __init__(self, num_filters, filter_size):
        self.num_filters = num_filters
        self.filter_size = filter_size
        self.conv_filter = np.random.randn(filter_size, 1)

    def loss(self, pred, target):
        # вычислить функцию потерь
        return np.mean((pred - target) ** 2)

    def forward(self, inputs):
        self.inputs = inputs
        num_inputs = inputs.shape[1]
        output_length = num_inputs - self.filter_size + 1
        self.output = np.zeros((self.num_filters, output_length))
        # Свёртка
        # размер входа в основном соответствует размеру словаря
        print("размер входа: ", inputs.shape)
        print("количество входов: ", num_inputs)
        print("размер фильтра: ", self.conv_filter.shape)
        print("размер фильтра: ", self.filter_size)
        print("размер выходных данных: ", self.output.shape)
        print("длина выходных данных: ", output_length)
        for i in range(output_length):
            if i+self.filter_size > num_inputs:
                break
            receptive_field = inputs[i:i+self.filter_size, 1].toarray()
            print("размер рецептивного поля: ", receptive_field.shape)
            self.output[:, i] = np.dot(receptive_field.T, self.conv_filter)
            print("выход на " + str(i) + str(self.output[:, i]))
            self.output[:, i] = np.maximum(0, self.output[:, i])

        return self.output

    def backward(self, grad_outputs, learning_rate):
        grad_input = np.zeros(grad_outputs.shape)
        grad_filter = np.zeros(self.conv_filter.shape)

        for i in range(grad_outputs.shape[0]):
            for j in range(self.num_filters):
               receptive_field = self.inputs[i:i+self.filter_size]
               grad_input[i:i+self.filter_size] += self.conv_filter[:, j] * grad_outputs[i, j]
               grad_filter[:, j] += receptive_field * grad_outputs[i, j]

            # Обновление весов
            self.conv_filter -= learning_rate * grad_filter

        return grad_input

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

Вот мой вывод

$ python model.py
размер входа:  (32, 2010)
количество входов:  2010
размер фильтра:  (3, 1)
размер фильтра:  3
размер выходных данных:  (10, 2008)
длина выходных данных:  2008
размер рецептивного поля:  (3, 1)
выход на 0[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 1[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 2[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 3[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 4[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 5[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 6[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 7[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 8[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 9[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 10[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 11[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 12[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 13[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 14[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 15[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 16[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 17[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 18[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 19[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 20[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 21[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 22[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 23[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 24[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 25[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 26[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 27[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 28[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (3, 1)
выход на 29[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
размер рецептивного поля:  (2, 1)
Трассировка (последний вызов был последним):
  Файл "C:\Users\maste_0c98yk4\OneDrive\Desktop\Projects\Natural Language Processing Model - Sentiment Analysis\model.py", строка 89, в <module>
    model.train(X, labels, num_epochs=10, batch_size=32)
  Файл "C:\Users\maste_0c98yk4\OneDrive\Desktop\Projects\Natural Language Processing Model - Sentiment Analysis\model.py", строка 44, в train   
    conv_output = self.conv_layer.forward(inputs)
  Файл "C:\Users\maste_0c98yk4\OneDrive\Desktop\Projects\Natural Language Processing Model - Sentiment Analysis\convolution.py", строка 32, в forward
    self.output[:, i] = np.dot(receptive_field.T, self.conv_filter)
ValueError: формы (1,2) и (3,1) не согласованы: 2 (разм. 1) != 3 (разм. 0)
(tf) 

Ожидаемое поведение:

  1. Прямой проход должен правильно вычислять свёртку, производя ненулевые значения на выходе.
  2. Цикл должен итерироваться по всем допустимым позициям, не завершаясь преждевременно.

Моя оценка
Я считаю, что проблема, скорее всего, заключается в том, как я использую индексы, но ни chatgpt, ни bard не смогли её исправить, поэтому это может быть что-то глубже.

Любая помощь будет очень ценна. Спасибо

Для второй проблемы:
Размер ваших входных данных (32,2010).
введите описание изображения здесь

Здесь вы выполняете цикл for (выходная длина) количество раз, то есть 2008. В рецептивном поле вы создаете ndarray размерности (3, 1) из первого индекса (по сути, нарезая 1-й столбец) входов (которые имеют длину 32), поэтому на 31-й итерации размер рецептивного поля составляет (2,1).

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

Поэтому, во время операции произведения точки ваши две матрицы – это receptive_field.T(1,2) и self.conv_filter(3,1). Эти матрицы не могут быть перемножены, именно поэтому вы получаете ошибку значения, что формы не согласованы.

Редактировать:
Смотрите этот код для прямого прохода. Я учёл только 1 фильтр в этом случае. Вы можете изменить его для большего количества фильтров (объединив выходы свёртки для разных фильтров по оси z). Также я предположил, что вы хотите выполнить свёртку вдоль оси x.

В свёртке мы скользим фильтром по входным данным, и значение свёртки основано на окне вокруг xt согласно:
введите описание изображения здесь
На практике a изменяется от 0 до размера фильтра.

    import numpy as np

class Conv1DLayer:

    def __init__(self, num_filters, filter_size):
        self.num_filters = num_filters
        self.filter_size = filter_size
        self.conv_filter = np.random.randn(filter_size, 1)
        print(self.conv_filter)

    def loss(self, pred, target):
        # вычислить функцию потерь
        return np.mean((pred - target) ** 2)

    def forward(self, inputs):
        self.inputs = inputs
        rows = inputs.shape[0]
        columns = inputs.shape[1]
        output_length = columns - self.filter_size + 1
        self.output = np.zeros((rows, output_length))
        # Свёртка
        # размер входа в основном соответствует размеру словаря
        print("размер входа: ", inputs.shape)
        print("количество входов: ", rows)
        print("размер фильтра: ", self.conv_filter.shape)
        print("размер фильтра: ", self.filter_size)
        print("размер выходных данных: ", self.output.shape)
        print("длина выходных данных: ", output_length)
        j=0
        while (j < rows):
            i = 0
            while (i+self.filter_size <= columns):
                receptive_field = inputs[j:j+1, i:i+self.filter_size]
                print("размер рецептивного поля: ", receptive_field.shape)
                self.output[j, i] = np.dot(receptive_field, self.conv_filter)
                print("выход на " + str(j)+" " +str(i) + " "+ str(self.output[j, i]))
                self.output[j, i] = np.maximum(0, self.output[j, i])
                i=i+1
            j= j+1

        return self.output

Вы можете ознакомиться с этой статьёй для более ясного представления о 1d свёртке.
Надеюсь, это поможет!

Ответ или решение

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

1. Проблема с выводом (все значения ноль)

Возможные причины:

  • Некорректное приготовление входных данных: Убедитесь, что входные данные (inputs) корректно подготавливаются и содержат ожидаемые значения. Если вы используете разреженные векторы (например, scipy.sparse), которые необходимо конвертировать в плотные массивы, необходимо это сделать перед выполнением свертки.

  • Неверная индексация: Ваша реализация свёрточного слоя может содержать ошибки в индексации, что может приводить к тому, что вы получаете нулевые значения. Когда вы берете receptive_field, убедитесь, что вы ссылаетесь на правильные индексы.

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

Что делать:

  • Проверьте входные данные, используя вывод значений перед выполнением свёртки.
  • Подумайте об инициализации свёрточного фильтра с помощью более масштабных значений или использования другого метода, например, инициализация по методу Глорта, чтобы избежать попадания в состояние "глубокого нуля".

2. Преждевременное завершение на определенной итерации

Возможные причины:

  • Прерывание цикла: Ваша реализация использует условие i + self.filter_size > num_inputs в цикле. Это условие неправильно для вашей логики, так как то, что вы на самом деле хотите проверить, это то, что индекс i находится в допустимых пределах.

  • Неверная работа с размерностью элементов: Когда ваш receptive_field запрашивает элементы слишком близкие к последним позициям входных данных, это может привести к ошибке, связанной с несовпадением форматов.

Что делать:

  • Измените условие выхода из цикла. Вместо использования разбиения индекса попробуйте другой вариант, чтобы обеспечить корректность индексации и не допустить выхода за границы массива.
  • Обратите внимание на то, как создается receptive_field, и убедитесь, что размерности корректно соответствуют фильтрам и входным данным.

Исправленная версия вашего кодa

Вот пример того, как можно переписать метод forward, чтобы избежать описанных проблем:

import numpy as np

class Conv1DLayer:

    def __init__(self, num_filters, filter_size):
        self.num_filters = num_filters
        self.filter_size = filter_size
        self.conv_filter = np.random.randn(filter_size, 1)

    def forward(self, inputs):
        self.inputs = inputs
        num_inputs = inputs.shape[1]
        output_length = num_inputs - self.filter_size + 1
        self.output = np.zeros((self.num_filters, output_length))

        for i in range(output_length):
            receptive_field = inputs[:, i:i + self.filter_size]
            self.output[:, i] = np.dot(receptive_field, self.conv_filter)
            self.output[:, i] = np.maximum(0, self.output[:, i])  # Применение функции активации

        return self.output

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...