Понимание того, почему tensorflow RNN не изучает игрушечные данные

#python #tensorflow #recurrent-neural-network

#питон #tensorflow #рекуррентная нейронная сеть

Вопрос:

Я пытаюсь обучить рекуррентную нейронную сеть, используя Tensorflow (r0.10, python 3.5) по проблеме классификации игрушек, но я получаю запутанные результаты.

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

 input sequence: [0,     0,     1,     0,     1,     1]
binary digits : [-, [0,0], [0,1], [1,0], [0,1], [1,1]]
target class  : [-,     0,     1,     2,     1,     3]
 

Похоже, что это то, что RNN должен быть в состоянии изучить довольно легко, но вместо этого моя модель способна различать только классы [0,2] из [1,3] . Другими словами, он способен отличать классы, текущая цифра которых равна 0, от тех, чья текущая цифра равна 1. Это наводит меня на мысль, что модель RNN неправильно учится смотреть на предыдущие значения последовательности.

Есть несколько руководств и примеров ([1], [2], [3]) которые демонстрируют, как создавать и использовать рекуррентные нейронные сети (RNN) в tensorflow, но после их изучения я все еще не вижу своей проблемы (не помогает то, что все примеры используют текст в качестве источникаданные).

Я ввожу свои данные в tf.nn.rnn() виде списка длины T , элементами которого являются [batch_size x input_size] последовательности. Поскольку моя последовательность одномерна, input_size равна единице, поэтому, по сути, я считаю, что ввожу список последовательностей длины batch_size документации мне неясно, какое измерение рассматривается как измерение времени).). Правильно ли это понимание?Если это так, то я не понимаю, почему модель RNN обучается неправильно.

Сложно получить небольшой набор кода, который может проходить через мой полный RNN, это лучшее, что я мог сделать (в основном это адаптировано из модели PTB здесь и модели char-rnn здесь):

 import tensorflow as tf
import numpy as np

input_size = 1
batch_size = 50
T = 2
lstm_size = 5
lstm_layers = 2
num_classes = 4
learning_rate = 0.1

lstm = tf.nn.rnn_cell.BasicLSTMCell(lstm_size, state_is_tuple=True)
lstm = tf.nn.rnn_cell.MultiRNNCell([lstm] * lstm_layers, state_is_tuple=True)

x = tf.placeholder(tf.float32, [T, batch_size, input_size])
y = tf.placeholder(tf.int32, [T * batch_size * input_size])

init_state = lstm.zero_state(batch_size, tf.float32)

inputs = [tf.squeeze(input_, [0]) for input_ in tf.split(0,T,x)]
outputs, final_state = tf.nn.rnn(lstm, inputs, initial_state=init_state)

w = tf.Variable(tf.truncated_normal([lstm_size, num_classes]), name='softmax_w')
b = tf.Variable(tf.truncated_normal([num_classes]), name='softmax_b')

output = tf.concat(0, outputs)

logits = tf.matmul(output, w)   b

probs = tf.nn.softmax(logits)

cost = tf.reduce_mean(tf.nn.seq2seq.sequence_loss_by_example(
    [logits], [y], [tf.ones_like(y, dtype=tf.float32)]
))

optimizer = tf.train.GradientDescentOptimizer(learning_rate)
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
                                  10.0)
train_op = optimizer.apply_gradients(zip(grads, tvars))

init = tf.initialize_all_variables()

with tf.Session() as sess:
    sess.run(init)
    curr_state = sess.run(init_state)
    for i in range(3000):
        # Create toy data where the true class is the value represented
        # by the current and previous value treated as binary, i.e.
        train_x = np.random.randint(0,2,(T * batch_size * input_size))
        train_y = train_x   np.concatenate(([0], (train_x[:-1] * 2)))

        # Reshape into T x batch_size x input_size
        train_x = np.reshape(train_x, (T, batch_size, input_size))

        feed_dict = {
            x: train_x, y: train_y
        }
        for j, (c, h) in enumerate(init_state):
            feed_dict[c] = curr_state[j].c
            feed_dict[h] = curr_state[j].h

        fetch_dict = {
            'cost': cost, 'final_state': final_state, 'train_op': train_op
        }

        # Evaluate the graph
        fetches = sess.run(fetch_dict, feed_dict=feed_dict)

        curr_state = fetches['final_state']

        if i % 300 == 0:
            print('step {}, train cost: {}'.format(i, fetches['cost']))

    # Test
    test_x = np.array([[0],[0],[1],[0],[1],[1]]*(T*batch_size*input_size))
    test_x = test_x[:(T*batch_size*input_size),:]
    probs_out = sess.run(probs, feed_dict={
            x: np.reshape(test_x, [T, batch_size, input_size]),
            init_state: curr_state
        })
    # Get the softmax outputs for the points in the sequence
    # that have [0, 0], [0, 1], [1, 0], [1, 1] as their
    # last two values.
    for i in [1, 2, 3, 5]:
        print('{}: [{:.4f} {:.4f} {:.4f} {:.4f}]'.format(
                [1, 2, 3, 5].index(i), *list(probs_out[i,:]))
             )
 

Конечный результат здесь

 0: [0.4899 0.0007 0.5080 0.0014]
1: [0.0003 0.5155 0.0009 0.4833]
2: [0.5078 0.0011 0.4889 0.0021]
3: [0.0003 0.5052 0.0009 0.4936]
 

что указывает на то, что он только учится отличать [0,2] от [1,3]. Почему эта модель не учится использовать предыдущее значение в последовательности?

Ответ №1:

Понял это с помощью этого сообщения в блоге (в нем есть замечательные диаграммы входных тензоров). Оказывается, я неправильно понимал форму входных данных tf.nn.rnn() :

Допустим, у вас есть batch_size количество последовательностей. Каждая последовательность имеет input_size размеры и длину T (эти имена были выбраны в соответствии с документацией tf.nn.rnn() here). Затем вам нужно разделить ваш ввод на список T длины, где каждый элемент имеет форму batch_size x input_size . Это означает, что ваша непрерывная последовательность будет распределена по элементам списка. Я думал, что непрерывные последовательности будут храниться вместе, чтобы каждый элемент списка inputs был примером одной последовательности.

Это имеет смысл в ретроспективе, поскольку мы хотим распараллелить каждый шаг последовательности, поэтому мы хотим выполнить первый шаг каждой последовательности (первый элемент в списке), затем второй шаг каждой последовательности (второй элемент в списке) и т.д.

Рабочая версия кода:

 import tensorflow as tf
import numpy as np

sequence_size = 50
batch_size = 7
num_features = 1
lstm_size = 5
lstm_layers = 2
num_classes = 4
learning_rate = 0.1

lstm = tf.nn.rnn_cell.BasicLSTMCell(lstm_size, state_is_tuple=True)
lstm = tf.nn.rnn_cell.MultiRNNCell([lstm] * lstm_layers, state_is_tuple=True)

x = tf.placeholder(tf.float32, [batch_size, sequence_size, num_features])
y = tf.placeholder(tf.int32, [batch_size * sequence_size * num_features])

init_state = lstm.zero_state(batch_size, tf.float32)

inputs = [tf.squeeze(input_, [1]) for input_ in tf.split(1,sequence_size,x)]
outputs, final_state = tf.nn.rnn(lstm, inputs, initial_state=init_state)

w = tf.Variable(tf.truncated_normal([lstm_size, num_classes]), name='softmax_w')
b = tf.Variable(tf.truncated_normal([num_classes]), name='softmax_b')

output = tf.reshape(tf.concat(1, outputs), [-1, lstm_size])

logits = tf.matmul(output, w)   b

probs = tf.nn.softmax(logits)

cost = tf.reduce_mean(tf.nn.seq2seq.sequence_loss_by_example(
    [logits], [y], [tf.ones_like(y, dtype=tf.float32)]
))

# Now optimize on that cost
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
                                  10.0)
train_op = optimizer.apply_gradients(zip(grads, tvars))

init = tf.initialize_all_variables()

with tf.Session() as sess:
    sess.run(init)
    curr_state = sess.run(init_state)
    for i in range(3000):
        # Create toy data where the true class is the value represented
        # by the current and previous value treated as binary, i.e.
        
        train_x = np.random.randint(0,2,(batch_size * sequence_size * num_features))
        train_y = train_x   np.concatenate(([0], (train_x[:-1] * 2)))
        
        # Reshape into T x batch_size x sequence_size
        train_x = np.reshape(train_x, [batch_size, sequence_size, num_features])
        
        feed_dict = {
            x: train_x, y: train_y
        }
        for j, (c, h) in enumerate(init_state):
            feed_dict[c] = curr_state[j].c
            feed_dict[h] = curr_state[j].h
        
        fetch_dict = {
            'cost': cost, 'final_state': final_state, 'train_op': train_op
        }
        
        # Evaluate the graph
        fetches = sess.run(fetch_dict, feed_dict=feed_dict)
        
        curr_state = fetches['final_state']
        
        if i % 300 == 0:
            print('step {}, train cost: {}'.format(i, fetches['cost']))
    
    # Test
    test_x = np.array([[0],[0],[1],[0],[1],[1]]*(batch_size * sequence_size * num_features))
    test_x = test_x[:(batch_size * sequence_size * num_features),:]
    probs_out = sess.run(probs, feed_dict={
            x: np.reshape(test_x, [batch_size, sequence_size, num_features]),
            init_state: curr_state
        })
    # Get the softmax outputs for the points in the sequence
    # that have [0, 0], [0, 1], [1, 0], [1, 1] as their
    # last two values.
    for i in [1, 2, 3, 5]:
        print('{}: [{:.4f} {:.4f} {:.4f} {:.4f}]'.format(
                [1, 2, 3, 5].index(i), *list(probs_out[i,:]))
             )