Потери в нейронной сети начинают увеличиваться, в то время как ускорение увеличивается для обоих наборов данных train / val.

Последние несколько дней я отлаживал свой NN, но не могу найти проблему.

Я создал полную необработанную реализацию многослойного персептрона для идентификации изображений набора данных MNIST.

Сеть, кажется, обучается, потому что точность данных теста после цикла поезда превышает точность 94%. У меня проблема с функцией потерь - она ​​начинает увеличиваться через некоторое время, когда точность test / val достигает ~ 76%.

Может ли кто-нибудь проверить мою математику прямого / обратного распространения и сказать мне, правильно ли реализована моя функция потерь, или предположить, что может быть не так?

Состав НС:

  • входной слой: 758 узлов (1 узел на пиксель)
  • скрытый слой 1: 300 узлов
  • скрытый слой 2:75 узлов
  • выходной слой: 10 узлов

Функции активации NN:

  • входной слой -> скрытый слой 1: ReLU
  • скрытый слой 1 -> скрытый слой 2: ReLU
  • скрытый слой 2 -> выходной слой 3: Softmax

NN Функция потерь:

  • Категориальная кросс-энтропия

Полный код CLEAN доступен здесь как Jupyter Notebook. / а>

Прямой / обратный проход нейронной сети:

def train(self, features, targets):
        n_records = features.shape[0]

        # placeholders for weights and biases change values
        delta_weights_i_h1 = np.zeros(self.weights_i_to_h1.shape)
        delta_weights_h1_h2 = np.zeros(self.weights_h1_to_h2.shape)
        delta_weights_h2_o = np.zeros(self.weights_h2_to_o.shape)
        delta_bias_i_h1 = np.zeros(self.bias_i_to_h1.shape)
        delta_bias_h1_h2 = np.zeros(self.bias_h1_to_h2.shape)
        delta_bias_h2_o = np.zeros(self.bias_h2_to_o.shape)

        for X, y in zip(features, targets):
            ### forward pass
            # input to hidden 1
            inputs_to_h1_layer = np.dot(X, self.weights_i_to_h1) + self.bias_i_to_h1
            inputs_to_h1_layer_activated = self.activation_ReLU(inputs_to_h1_layer)

            # hidden 1 to hidden 2
            h1_to_h2_layer = np.dot(inputs_to_h1_layer_activated, self.weights_h1_to_h2) + self.bias_h1_to_h2
            h1_to_h2_layer_activated = self.activation_ReLU(h1_to_h2_layer)

            # hidden 2 to output
            h2_to_output_layer = np.dot(h1_to_h2_layer_activated, self.weights_h2_to_o) + self.bias_h2_to_o
            h2_to_output_layer_activated = self.softmax(h2_to_output_layer)

            # output
            final_outputs = h2_to_output_layer_activated 

            ### backpropagation
            # output to hidden2
            error = y - final_outputs
            output_error_term = error.dot(self.dsoftmax(h2_to_output_layer_activated))

            h2_error = np.dot(output_error_term, self.weights_h2_to_o.T)
            h2_error_term = h2_error * self.activation_dReLU(h1_to_h2_layer_activated)

            # hidden2 to hidden1
            h1_error = np.dot(h2_error_term, self.weights_h1_to_h2.T) 
            h1_error_term = h1_error * self.activation_dReLU(inputs_to_h1_layer_activated)

            # weight & bias step (input to hidden)
            delta_weights_i_h1 += h1_error_term * X[:, None]
            delta_bias_i_h1 = np.sum(h1_error_term, axis=0)

            # weight & bias step (hidden1 to hidden2)
            delta_weights_h1_h2 += h2_error_term * inputs_to_h1_layer_activated[:, None]
            delta_bias_h1_h2 = np.sum(h2_error_term, axis=0)

            # weight & bias step (hidden2 to output)
            delta_weights_h2_o += output_error_term * h1_to_h2_layer_activated[:, None]
            delta_bias_h2_o = np.sum(output_error_term, axis=0)

        # update the weights and biases     
        self.weights_i_to_h1 += self.lr * delta_weights_i_h1 / n_records
        self.weights_h1_to_h2 += self.lr * delta_weights_h1_h2 / n_records
        self.weights_h2_to_o += self.lr * delta_weights_h2_o / n_records
        self.bias_i_to_h1 += self.lr * delta_bias_i_h1 / n_records
        self.bias_h1_to_h2 += self.lr * delta_bias_h1_h2 / n_records
        self.bias_h2_to_o += self.lr * delta_bias_h2_o / n_records

Реализация функции активации:

def activation_ReLU(self, x):
    return x * (x > 0)

def activation_dReLU(self, x):
    return 1. * (x > 0)

def softmax(self, x):
    z = x - np.max(x)
    return np.exp(z) / np.sum(np.exp(z))

def dsoftmax(self, x):
    # TODO: vectorise math
    vec_len = len(x)
    J = np.zeros((vec_len, vec_len))
    for i in range(vec_len):
        for j in range(vec_len):
            if i == j:
                J[i][j] = x[i] * (1 - x[j])
            else:
                J[i][j] = -x[i] * x[j]
    return J

Реализация функции потерь:

def categorical_cross_entropy(pred, target): 
    return (1/len(pred)) * -np.sum(target * np.log(pred))

person F1sher    schedule 27.06.2017    source источник
comment
Можно было бы реализовать это в Tensorflow и проверить соответствие ваших градиентов.   -  person Alex Alifimoff    schedule 27.06.2017


Ответы (1)


Мне удалось найти проблему.

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

Проблема с просчетом потерь была вызвана тем, что моя реализация Softmax работала только для ndarray dim == 1.

На этапе обучения я поместил в функцию активации только ndarray с dim 1, чтобы NN хорошо усвоила, но моя функция run() возвращала неверные прогнозы, поскольку я вставил в нее целые тестовые данные, а не только одну строку в цикле for. Из-за этого Softmax вычислялся «по матрице», а не «по строкам».

Это очень быстрое решение:

   def softmax(self, x):
        # TODO: vectorise math to speed up computation
        softmax_result = None
        if x.ndim == 1:
            z = x - np.max(x)
            softmax_result = np.exp(z) / np.sum(np.exp(z))
            return softmax_result
        else:
            softmax_result = []
            for row in x:
                z = row - np.max(row)
                row_softmax_result = np.exp(z) / np.sum(np.exp(z))
                softmax_result.append(row_softmax_result)
            return np.array(softmax_result)

Тем не менее, этот код следует векторизовать, чтобы по возможности избежать циклов for и if, потому что в настоящее время он уродлив и требует слишком много ресурсов ПК.

person F1sher    schedule 27.06.2017