2016-03-29 3 views
3

Я пытаюсь реализовать функцию softmax для нейронной сети, написанной на Numpy. Пусть h - значение softmax заданного сигнала i.Производное Softmax в подходах NumPy 0 (реализация)

softmax function

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

the softmax partial derivative

В настоящее время я застрял на вопрос, где все частные производные к 0, как обучение прогрессирует. Я сопоставил свою математику с this excellent answer, но моя математика, похоже, не работает.

import numpy as np 
def softmax_function(signal, derivative=False): 
    # Calculate activation signal 
    e_x = np.exp(signal) 
    signal = e_x/np.sum(e_x, axis = 1, keepdims = True) 

    if derivative: 
     # Return the partial derivation of the activation function 
     return np.multiply(signal, 1 - signal) + sum(
      # handle the off-diagonal values 
      - signal * np.roll(signal, i, axis = 1) 
      for i in xrange(1, signal.shape[1]) 
     ) 
    else: 
     # Return the activation signal 
     return signal 
#end activation function 

Параметр signal содержит входной сигнал, посылаемый в функцию активации и имеет форму (n_samples, n_features).

# sample signal (3 samples, 3 features) 
signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]] 

Следующий код отрезала является полностью рабочей функцией активации и включаются только в качестве справки и доказательства (в основном для себя), что концептуальная идея на самом деле работает.

from scipy.special import expit 
import numpy as np 
def sigmoid_function(signal, derivative=False): 
    # Prevent overflow. 
    signal = np.clip(signal, -500, 500) 

    # Calculate activation signal 
    signal = expit(signal) 

    if derivative: 
     # Return the partial derivation of the activation function 
     return np.multiply(signal, 1 - signal) 
    else: 
     # Return the activation signal 
     return signal 
#end activation function 

Редактировать

  • Проблемы интуитивно сохраняется с простыми сетями однослойными. Softmax (и его производная) применяется на конечном слое.

ответ

8

Это ответ на вопрос о том, как вычислить производную функции softmax более векторным способом numpy. Тем не менее, тот факт, что частичные производные подходят к нулю, может быть не математической проблемой, а просто проблемой скорости обучения или известного размера угасающим весом проблема со сложными глубокими нейронными сетями. Такие слои, как ReLU, помогают предотвратить проблему.


Во-первых, я использовал следующий сигнал (только дублируя свой последний вход), чтобы сделать его 4 samples x 3 features так легче увидеть, что происходит с размерами.

>>> signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]] 
>>> signal.shape 
(4, 3) 

Далее вы хотите вычислить матрицу якобиана вашей функции softmax. Согласно указанной странице она определена как -hi * hj для недиагональных записей (большая часть матрицы для n_features > 2), поэтому давайте начнем там. В NumPy, можно эффективно вычислить, что матрица Якоби с помощью broadcasting:

>>> J = - signal[..., None] * signal[:, None, :] 
>>> J.shape 
(4, 3, 3) 

Первый signal[..., None] (эквивалент signal[:, :, None]) перестраивает сигнал на (4, 3, 1) в то время как второй signal[:, None, :] перестраивает сигнал на (4, 1, 3). Затем * просто умножает обе матрицы по элементам. Внутреннее вещание Numpy повторяет обе матрицы для формирования матрицы n_features x n_features для каждого образца.

Затем нам нужно зафиксировать диагональные элементы:

>>> iy, ix = np.diag_indices_from(J[0]) 
>>> J[:, iy, ix] = signal * (1. - signal) 

Вышеприведенные строки извлечь диагональные индексы для n_features x n_features матрицы. Это эквивалентно выполнению iy = np.arange(n_features); ix = np.arange(n_features). Затем заменяет диагональные записи вашим defitinion hi * (1 - hi).

Последнее, согласно источнику связи, вам нужно суммировать строки по каждому образцу. Это может быть сделано как:

>>> J = J.sum(axis=1) 
>>> J.shape 
(4, 3) 

Найти сильфонные обобщенный вариант:

if derivative: 
    J = - signal[..., None] * signal[:, None, :] # off-diagonal Jacobian 
    iy, ix = np.diag_indices_from(J[0]) 
    J[:, iy, ix] = signal * (1. - signal) # diagonal 
    return J.sum(axis=1) # sum across-rows for each sample 

Сравнение производных:

>>> signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]] 
>>> e_x = np.exp(signal) 
>>> signal = e_x/np.sum(e_x, axis = 1, keepdims = True) 

Yours:

>>> np.multiply(signal, 1 - signal) + sum(
     # handle the off-diagonal values 
     - signal * np.roll(signal, i, axis = 1) 
     for i in xrange(1, signal.shape[1]) 
    ) 
array([[ 2.77555756e-17, -2.77555756e-17, 0.00000000e+00], 
     [ -2.77555756e-17, -2.77555756e-17, -2.77555756e-17], 
     [ 2.77555756e-17, 0.00000000e+00, 2.77555756e-17], 
     [ 2.77555756e-17, 0.00000000e+00, 2.77555756e-17]]) 

Шахта:

>>> J = signal[..., None] * signal[:, None, :] 
>>> iy, ix = np.diag_indices_from(J[0]) 
>>> J[:, iy, ix] = signal * (1. - signal) 
>>> J.sum(axis=1) 
array([[ 4.16333634e-17, -1.38777878e-17, 0.00000000e+00], 
     [ -2.77555756e-17, -2.77555756e-17, -2.77555756e-17], 
     [ 2.77555756e-17, 1.38777878e-17, 2.77555756e-17], 
     [ 2.77555756e-17, 1.38777878e-17, 2.77555756e-17]]) 
+1

Во-первых, для суперинтуитивного ответа! При синхронизации кода: ваш, кажется, наиболее эффективен при высоком размере выборки, тогда как предоставленный фрагмент более эффективен, если набор данных имеет большое количество функций. Независимо от того, что вычисленные производные «всегда» находятся в диапазоне 10^-17 - другими словами, около 0. – jorgenkg

+0

@jorgenkg кажется, однако, что мы не получаем одни и те же производные. Обратите внимание, что ваши результаты для последнего образца (последние 2, они одинаковы) исчезают в отношении второй функции, а мины - нет (см. Редактирование). Попробуйте эту версию, чтобы увидеть, имеет ли она лучшую численную стабильность и не полностью исчезает ваши производные. –

+0

Вы окончательно верны, NumPy показывает намного лучшую точность с вашей реализацией - проблема точности в моем коде может возникнуть из встроенного вызова sum(). Я бы поддержал вас дважды – jorgenkg