Накопление градиента по эпизодам вычислительно дорого - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть модель обучения с глубоким подкреплением градиента политики. Модель выбирает из действия 1 logits и action 2 logits при прямом распространении на каждом шаге. В конце эпизода я распространял информацию о потере. Я использую разные настройки среды (изучение учебного плана) в каждом эпизоде, и, следовательно, разница от каждого эпизода велика. Поэтому я решил использовать пакетное обучение, обновляя градиенты, например, один раз в 4 эпизодах. У меня всего 21 обучаемая переменная. Сначала я пытался вычислить градиент для каждого шага, умноженный на количество эпизодов (размер партии), и найти среднее значение всех градиентов для каждой обучаемой переменной и обратного распространения один раз в 4 эпизодах. Тем не менее, это оказалось тяжелым для памяти, так как у меня длина эпизода 2000 и размер пакета 4. Это 8000 градиентов умножить на 21 переменную, и многие из этих переменных, естественно, многомерны. Вот функция, которую я написал для того же

def average_gradients(self,tower_grads):

        average_grads = []
        count = 0
        for grad_and_vars in zip(*tower_grads):
            # Note that each grad_and_vars looks like the following:
            #   ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN))
            grads = []
            for g, _ in grad_and_vars:
                g = self.flat_gradients(g)
                # Add 0 dimension to the gradients to represent the tower.
                expanded_g = tf.expand_dims(g, 0)

                # Append on a 'tower' dimension which we will average over below.
                grads.append(expanded_g)

            # Average over the 'tower' dimension.
            grad = tf.concat(axis=0, values=grads)
            grad = tf.reduce_mean(grad, 0)

            # Keep in mind that the Variables are redundant because they are shared
            # across towers. So .. we will just return the first tower's pointer to
            # the Variable.
            # v = grad_and_vars[0][1]
            v = self._grad_placeholders[count]
            count += 1
            grad_and_var = (grad, v)
            average_grads.append(grad_and_var)
        return average_grads
# This is needed for tf.gather like operations.
    def flat_gradients(self,grads_or_idx_slices: tf.Tensor) -> tf.Tensor:
        '''Convert gradients if it's tf.IndexedSlices.
        When computing gradients for operation concerning `tf.gather`, the type of gradients 
        '''

        if not hasattr(grads_or_idx_slices, 'shape'):
            # if type(grads_or_idx_slices) == tf.IndexedSlices or type(grads_or_idx_slices) == tf.IndexedSlicesValue:
            return tf.scatter_nd(
                tf.expand_dims(grads_or_idx_slices.indices, 1),
                grads_or_idx_slices.values,
                grads_or_idx_slices.dense_shape
            )
        return grads_or_idx_slices

Таким образом, вместо сохранения градиентов в памяти, я решил вычислить его среднее значение, как и когда я получаю новый градиент из шага. Таким образом, у меня всегда будет только один градиент в памяти. Вот как я это реализовал:

if self.batch_size == 1:
     loss_, _, logits = sess.run([self.loss, self.train_op,self.action_distribution_0],                                               feed_dict=feed_dict)
else:                              
     loss, grads = sess.run([self.loss, self._grad_op], feed_dict=feed_dict)
     if(i == 0): # first step
          for grad,var in grads:
              grad = self.flat_gradients(grad)
              # print(type(grad))
              if not type(grad) == np.ndarray:
                  grad = grad.eval()
                  # print(grad)
                  self.gradient.append(grad)


              else:
                  for k,(grad,_) in enumerate(grads):
                       grad = self.flat_gradients(grad)
                       if not type(grad) == np.ndarray:
                            grad = grad.eval()
                            self.gradient[k] = np.stack([grad, self.gradient[k]]).mean(axis=0)

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

Этот метод, хотя и в вычислительном отношении, очень дорогой. У меня приличный 8 Гб оперативной памяти Ram и i7 Kaby Lake, но для вычисления среднего значения градиента из одного эпизода с 2000 шагов требуется до 30 минут.

Есть ли умный способ сделать это? Любая помощь будет оценена.

...