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

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

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

def backward(self, cache, target,c=leastsquares_cost, dc=leastsquares_dcost):
        '''
        cache is from forward pass

        c is a cost function
        dc is a function used as dc(output, target) which gives the gradient dc/doutput 
        '''
        XdotW = cache['XdotW'] #num_time_steps x hidden_size
        Hin = cache['Hin'] # num_time_steps x hidden_size
        T = Hin.shape[0]
        Hout = cache['Hout']
        Xin = cache['Xin']
        Xout = cache['Xout']

        Oin = cache['Oin'] # num_time_steps x output_size
        Oout=cache['Oout']

        dcdOin = dc(Oout, target) # this will be num_time_steps x num_outputs. these are dc/dO_j


        dcdWho = np.dot(Hout.transpose(), dcdOin) # this is the sum of outer products for all time

        # bias term is added at the end with coefficient 1 hence the dot product is just the sum
        dcdbho = np.sum(dcdOin, axis=0, keepdims=True) #this sums all the time steps

        dcdHout = np.dot(dcdOin, self.Who.transpose()) #reflects dcdHout_ij should be the dot product of dcdoin and the i'th row of Who; this is only for the outputs
        # now go back in time
        dcdHin = np.zeros(dcdHout.shape)
        # for t=T we can ignore the other term (error from the next timestep). self.df is derivative of activation function (here, tanh):
        dcdHin[T-1] = self.df(Hin[T-1]) * dcdHout[T-1] # because we don't need to worry about the next timestep, dcdHout is already corrent for t=T

        for t in reversed(xrange(T-1)):
            # we need to add to dcdHout[t] the error from the next timestep
            dcdHout[t] += np.dot(dcdHin[t], self.Whh.transpose())
            # now we have the correct form for dcdHout[t]
            dcdHin[t] = self.df(Hin[t]) * dcdHout[t]
        # now we've gone through all t, and we can continue
        dcdWhh = np.zeros(self.Whh.shape)
        for t in range(T-1): #skip T bc dHdin[T+1] doesn't exist
            dcdWhh += np.outer(Hout[t], dcdHin[t+1])
        # and we can do bias as well
        dcdbhh = np.sum(dcdHin,axis=0, keepdims=True)


        # now we need to go back to the embeddings
        dcdWxh = np.dot(Xout.transpose(), dcdHin)

        return {'dcdOout': dcdOout, 'dcdWxh': dcdWxh, 'dcdWhh': dcdWhh, 'dcdWho': dcdWho, 'dcdbhh': dcdbhh, 'dcdbho': dcdbho, 'cost':c(Oout, target)}

И вот код theano (в основном скопированный из другой реализации, которую я нашел в Интернете. Я инициализирую веса рандомизированными весами моего чистого Python rnn, чтобы все было одинаково):

# input (where first dimension is time)
u = TT.matrix()
# target (where first dimension is time)
t = TT.matrix()
# initial hidden state of the RNN
h0 = TT.vector()
# learning rate
lr = TT.scalar()
# recurrent weights as a shared variable
W = theano.shared(rnn.Whh)
# input to hidden layer weights
W_in = theano.shared(rnn.Wxh)
# hidden to output layer weights
W_out = theano.shared(rnn.Who)

# bias 1
b_h = theano.shared(rnn.bhh[0])
# bias 2
b_o = theano.shared(rnn.bho[0])


# recurrent function (using tanh activation function) and linear output
# activation function
def step(u_t, h_tm1, W, W_in, W_out):
    h_t = TT.tanh(TT.dot(u_t, W_in) + TT.dot(h_tm1, W) + b_h)
    y_t = TT.dot(h_t, W_out) + b_o
    return h_t, y_t

# the hidden state `h` for the entire sequence, and the output for the
# entrie sequence `y` (first dimension is always time)
[h, y], _ = theano.scan(step,
                        sequences=u,
                        outputs_info=[h0, None],
                        non_sequences=[W, W_in, W_out])
# error between output and target
error = (.5*(y - t) ** 2).sum()
# gradients on the weights using BPTT
gW, gW_in, gW_out, gb_h, gb_o = TT.grad(error, [W, W_in, W_out, b_h, b_o])
# training function, that computes the error and updates the weights using
# SGD.

Теперь вот сумасшедшая вещь. Если я запускаю следующее:

fn = theano.function([h0, u, t, lr],
                     [error, y, h, gW, gW_in, gW_out, gb_h, gb_o],
                     updates={W: W - lr * gW,
                             W_in: W_in - lr * gW_in,
                             W_out: W_out - lr * gW_out})

er, yout, hout, gWhh, gWhx, gWho, gbh, gbo =fn(numpy.zeros((n,)), numpy.eye(5), numpy.eye(5),.01)
cache = rnn.forward(np.eye(5))
bc = rnn.backward(cache, np.eye(5))

print "sum difference between gWho (theano) and bc['dcdWho'] (pure python):"
print np.sum(gWho - bc['dcdWho'])
print "sum differnce between gWhh(theano) and bc['dcdWho'] (pure python):"
print np.sum(gWhh - bc['dcdWhh'])
print "sum difference between gWhx (theano) and bc['dcdWxh'] (pure pyython):"
print np.sum(gWhx - bc['dcdWxh'])

print "sum different between the last row of gWhx (theano) and the last row of bc['dcdWxh'] (pure python):"
print np.sum(gWhx[-1] - bc['dcdWxh'][-1])

Я получаю следующий вывод:

sum difference between gWho (theano) and bc['dcdWho'] (pure python):
-4.59268040265e-16
sum differnce between gWhh(theano) and bc['dcdWhh'] (pure python):
0.120527063611
sum difference between gWhx (theano) and bc['dcdWxh'] (pure pyython):
-0.332613468652
sum different between the last row of gWhx (theano) and the last row of bc['dcdWxh'] (pure python):
4.33680868994e-18

Итак, я получаю производные матрицы весов между скрытым слоем и выводом справа, но не производные матрицы весов скрыты -> скрыты или введены -> скрыты. Но эта безумная вещь заключается в том, что я ВСЕГДА получаю ПОСЛЕДНЮЮ СТРОКУ ввода матрицы веса -> скрыто правильно. Это безумие для меня. Я понятия не имею, что здесь происходит. Обратите внимание, что последняя строка входной матрицы весов -> скрытая НЕ соответствует последнему временному шагу или чему-то еще (это можно объяснить, например, тем, что я правильно вычислил производные для последнего временного шага, но не правильно распространяется обратно во времени). dcdWxh - это сумма dcdWxh по всем временным шагам -- так как же я могу получить правильную одну строку, но не другую???

Кто-нибудь может помочь? У меня закончились идеи.


person user21414253    schedule 18.12.2014    source источник


Ответы (1)


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

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

Также похоже, что нотация матриц по строкам и по столбцам перепутана, как в

gWhx - bc['dcdWxh']

что читается как вес от «скрытый до х» напротив «от х до скрытого».

Я бы предпочел опубликовать это как комментарий, но мне не хватает репутации для этого. простите!

person Sven    schedule 11.11.2015