Как отлаживать и векторизовать частные производные сети радиальных базисных функций при использовании softmax loss?

Я пытаюсь запустить (стохастический или пакетный) градиентный спуск, когда используется стандартная перекрестная энтропия (softmax loss):

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

при использовании в качестве модели сети Radial Basis Function (RBF) (вы можете посмотреть форму лекции caltech здесь, если хотите) при расширении до мультиклассовой классификации (легко расширяется путем простого ввода вывод сети RBF на уровень softmax. Обратите внимание, что P(y=l|x) просто вычисляется путем передачи вывода сети RBF через уровень softmax для каждой метки l следующим образом:

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

где \theta_l индексирует параметры, отвечающие за выполнение прогнозов для метки l.

В связи с этим я хотел оптимизировать свою модель, вычисляя производные по параметрам. Напомним, что параметрами для оптимизации в сети радиальных базисных функций являются веса c на последнем слое и центры t на первом слое. Я реализовал и отладил, как вычислить производную по весам c. Код работает так, как ожидалось, потому что частные производные соответствуют числовым производным. Вы можете найти код модульного теста здесь .

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

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

где h_{\theta_l} соответствует выходу RBF, который отвечает за прогнозирование метки l. На самом деле h_{\theta_l} очень просто выразить:

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

Моя основная проблема связана с вычислением производной J по t_k (уравнение выше). Для этого я реализовал следующую функцию который наивно вычисляет его без векторизации:

function [ dJ_dt ] = compute_dJ_dt(z,x,y,t,c)
%Computes dJ_dc
%   Input:
%       z = (K x 1)
%       x = data point (D, 1)
%       y = labels (1 x 1)
%       t = centers (D x K)
%       c = weights (K x L)
%   Output:
%       dJ_dc = (D x K)
[D,K] = size(t);
[~, L] = size(c);
dJ_dt = zeros(D, K);
for k=1:K
    dJ_dt_k = zeros(D, 1);
    for l=1:L
        c_l = c(:,l);
        dh_dt_l = compute_dh_dt(z,x,t,c_l); %(D x K)
        delta = (y==l);
        dJ_dt_k = dJ_dt_k + dh_dt_l(:,k) * delta;
    end
    dJ_dt(:,k) = -dJ_dt_k;
end
end

и он не соответствует числовому коду производных >.

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

  1. Во-первых, естественный вопрос: верен ли мой математический вывод производной, которую я пытаюсь реализовать? Несмотря на то, что я явно не проверял математический вывод с кем-то, я очень уверен, что он правильный, потому что вывод для частной производной по c и t в модели идентичен, и вы только меняете символ \theta на любой параметр, который у вас есть обсуждаемый. Поскольку я уже реализовал производную по отношению к c и она проходит все мои производные тесты, я предполагаю, что производная по отношению к t или любому параметру \theta должна быть правильной. Мой вывод этого уравнения можно увидеть в math.stack exchange здесь.
  2. Один из вариантов может заключаться в том, что compute_dJ_dt на самом деле не реализует уравнение, которого я ожидаю. Это действительно могло быть так, и чтобы убедиться, что я независимо реализовал немного более векторизованная версия этого кода, чтобы увидеть, действительно ли я реализую уравнение, которое у меня было на бумаге. Поскольку две версии уравнения выводят одни и те же производные значения, я уверен, что они вычисляют, действительно, уравнение, которое я подозреваю (также, если у кого-то есть способ дальнейшей векторизации этого уравнения, это было бы потрясающе! настолько тривиален, что не кажется таким уж интересным или большим приростом производительности, хотя он удаляет один цикл for).

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

  1. числовой производный код настолько смехотворно прост что трудно проверить, что же, черт возьми, с этим может быть не так. Единственное, что мне пришло в голову, что могло быть неправильно, это то, что моя реализация softmax cost J неверен, но я очень сомневаюсь в этом, так как ... Я уже написал для него модульный тест! Кроме того, я использую его для проверки числовых производных относительно c и тех, которые для c ВСЕГДА проходят, поэтому я не могу представить, что J ошибается.
  2. Последняя нетривиальная вещь, которую нужно проверить, - это то, что compute_dh_dt вычисляется правильно. Я написал тесты модулей для dh_dt и поскольку они соответствуют своим соответствующим числовым производным при каждом запуске, я подозреваю, что код правильный.

На данный момент я не уверен на 100%, что еще попробовать. Я надеюсь, что, может быть, у кого-то есть хорошая идея или, может быть, укажет на мою глупость? Я не знаю, что думать прямо сейчас. Спасибо за помощь и время сообществу!


person Charlie Parker    schedule 19.10.2015    source источник


Ответы (1)


Это своего рода антиклиматическое решение, но я предполагаю, что этого следовало ожидать, потому что этот код, казалось, был построен из рабочих компонентов, поэтому это была небольшая глупая ошибка. Ошибка заключалась в том, что в приведенном выше коде, который я вставил, я должен был использовать delta в качестве разницы между указанием метки и вероятностью этой метки, но я забыл вычесть вероятность. Итак, приведенный выше код:

    delta = (y==l);

когда это должно было быть:

    prob_y_x_h_x = prob_y_x(h_x); % (L x 1)
    ind_y_l = (y==l);
    delta = ind_y_l - prob_y_x_h_x(l);

Итак, исправленный код теперь проходит численные тесты и выглядит следующим образом:

function [ dJ_dt ] = compute_dJ_dt(h_x,z,x,y,t,c)
%Computes dJ_dc
%   Input:
%       z = (K x 1)
%       x = data point (D, 1)
%       y = labels (1 x 1)
%       t = centers (D x K)
%       c = weights (K x L)
%   Output:
%       dJ_dc = (D x K)
[D,K] = size(t);
[~, L] = size(c);
dJ_dt = zeros(D, K);
for k=1:K
    dJ_dt_k = zeros(D, 1);
    for l=1:L
        c_l = c(:,l);
        dh_dt_l = compute_dh_dt(z,x,t,c_l); %(D x K)
        prob_y_x_h_x = prob_y_x(h_x); % (L x 1)
        ind_y_l = (y==l);
        delta = ind_y_l - prob_y_x_h_x(l);
        dJ_dt_k = dJ_dt_k + dh_dt_l(:,k) * delta;
    end
    dJ_dt(:,k) = -dJ_dt_k;
end
end

Я до сих пор не знаю, как дополнительно векторизовать приведенный выше код, поэтому я все еще рад получить отзывы по этой части вопроса! Вот уже имеющаяся у меня векторизация:

function [ dJ_dt ] = compute_dJ_dt_vec(h_x,z,x,y,t,c)
%Computes dJ_dc
%   Input:
%       z = (K x 1)
%       x = data point (D, 1)
%       y = labels (1 x 1)
%       t = centers (D x K)
%       c = weights (K x L)
%   Output:
%       dJ_dc = (D x K)
[D,K] = size(t);
[~, L] = size(c);
dJ_dt = zeros(D, K);
for l=1:L
    c_l = c(:,l);
    dh_dt = compute_dh_dt(z,x,t,c_l); %(D x K)
    ind_y_l = (y==l);
    prob_y_x_h_x = prob_y_x(h_x); % (L x 1)
    dJ_dh = repmat( ind_y_l - prob_y_x_h_x(l) , D, K); %(D x K)
    dJ_dt = dJ_dt + dJ_dh.*dh_dt;
end
dJ_dt = -dJ_dt;
end
person Charlie Parker    schedule 19.10.2015