【问题标题】:Problems porting Tensorflow code to Keras将 TensorFlow 代码移植到 Keras 的问题
【发布时间】:2019-12-28 06:52:53
【问题描述】:

在过去的几天里,我一直在用头撞墙——我根本想不通。 你们中的一些好人也许会让我知道我做错了什么?

我正在尝试将代码从 https://github.com/simoninithomas/Deep_reinforcement_learning_Course/blob/master/Deep%20Q%20Learning/Doom/Deep%20Q%20learning%20with%20Doom.ipynb(用 Tensorflow 编写)移植到 Keras。这是代码的原始部分:

class DQNetwork:
    def __init__(self, state_size, action_size, learning_rate, name='DQNetwork'):
        self.state_size = state_size
        self.action_size = action_size
        self.learning_rate = learning_rate

        with tf.variable_scope(name):
            self.inputs_ = tf.placeholder(tf.float32, [None, *state_size], name="inputs")
            self.actions_ = tf.placeholder(tf.float32, [None, 3], name="actions_")

            self.target_Q = tf.placeholder(tf.float32, [None], name="target")

            #First convnet: CNN => BatchNormalization => ELU; Input is 84x84x4
            self.conv1 = tf.layers.conv2d(inputs = self.inputs_,
                filters = 32, kernel_size = [8,8],strides = [4,4],padding = "VALID",
                kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(), name = "conv1")

            self.conv1_batchnorm = tf.layers.batch_normalization(self.conv1,training = True,
                epsilon = 1e-5,name = 'batch_norm1')

            self.conv1_out = tf.nn.elu(self.conv1_batchnorm, name="conv1_out")
            ## --> [20, 20, 32]


            #Second convnet: CNN => BatchNormalization => ELU
            self.conv2 = tf.layers.conv2d(inputs = self.conv1_out,
                filters = 64,kernel_size = [4,4],strides = [2,2],padding = "VALID",
                kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),name = "conv2")

            self.conv2_batchnorm = tf.layers.batch_normalization(self.conv2,training = True,
                epsilon = 1e-5,name = 'batch_norm2')

            self.conv2_out = tf.nn.elu(self.conv2_batchnorm, name="conv2_out")
            ## --> [9, 9, 64]


            #Third convnet: CNN => BatchNormalization => ELU
            self.conv3 = tf.layers.conv2d(inputs = self.conv2_out,
                filters = 128,kernel_size = [4,4],strides = [2,2],padding = "VALID",
                kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),name = "conv3")

            self.conv3_batchnorm = tf.layers.batch_normalization(self.conv3,training = True,
                epsilon = 1e-5,name = 'batch_norm3')

            self.conv3_out = tf.nn.elu(self.conv3_batchnorm, name="conv3_out")
            ## --> [3, 3, 128]


            self.flatten = tf.layers.flatten(self.conv3_out)
            ## --> [1152]


            self.fc = tf.layers.dense(inputs = self.flatten,
                units = 512, activation = tf.nn.elu,
                kernel_initializer=tf.contrib.layers.xavier_initializer(),name="fc1")


            self.output = tf.layers.dense(inputs = self.fc, kernel_initializer=tf.contrib.layers.xavier_initializer(),
                units = 3, activation=None)


            # Q is our predicted Q value.
            self.Q = tf.reduce_sum(tf.multiply(self.output, self.actions_), axis=1)


            # The loss is the difference between our predicted Q_values and the Q_target
            # Sum(Qtarget - Q)^2
            self.loss = tf.reduce_mean(tf.square(self.target_Q - self.Q))

            self.optimizer = tf.train.RMSPropOptimizer(self.learning_rate).minimize(self.loss)

# farther below...

Qs_next_state = sess.run(DQNetwork.output, feed_dict = {DQNetwork.inputs_: next_states_mb})

# Set Q_target = r if the episode ends at s+1, otherwise set Q_target = r + gamma*maxQ(s', a')
for i in range(0, len(batch)):
    terminal = dones_mb[i]

    # If we are in a terminal state, only equals reward
    if terminal:
        target_Qs_batch.append(rewards_mb[i])
    else:
        target = rewards_mb[i] + gamma * np.max(Qs_next_state[i])
        target_Qs_batch.append(target)

targets_mb = np.array([each for each in target_Qs_batch])

loss, _ = sess.run([DQNetwork.loss, DQNetwork.optimizer],
                    feed_dict={DQNetwork.inputs_: states_mb,
                               DQNetwork.target_Q: targets_mb,
                               DQNetwork.actions_: actions_mb})

这是我的转换:


class DQNetworkA:
    def __init__(self, state_size, action_size, learning_rate):
        self.state_size = state_size
        self.action_size = action_size
        self.learning_rate = learning_rate

        self.model = keras.models.Sequential()
        self.model.add(keras.layers.Conv2D(32, (8, 8), strides=(4, 4), padding = "VALID", input_shape=state_size))#, kernel_initializer='glorot_normal'))
        self.model.add(keras.layers.BatchNormalization(epsilon = 1e-5))
        self.model.add(keras.layers.Activation('elu'))

        self.model.add(keras.layers.Conv2D(64, (4, 4), strides=(2, 2), padding = "VALID"))#, kernel_initializer='glorot_normal'))
        self.model.add(keras.layers.BatchNormalization(epsilon = 1e-5))
        self.model.add(keras.layers.Activation('elu'))

        self.model.add(keras.layers.Conv2D(128, (4, 4), strides=(2, 2), padding = "VALID"))#, kernel_initializer='glorot_normal'))
        self.model.add(keras.layers.BatchNormalization(epsilon = 1e-5))
        self.model.add(keras.layers.Activation('elu'))

        self.model.add(keras.layers.Flatten())
        self.model.add(keras.layers.Dense(512))
        self.model.add(keras.layers.Activation('elu'))

        self.model.add(keras.layers.Dense(action_size))

        self.model.compile(loss="mse", optimizer=keras.optimizers.RMSprop(lr=self.learning_rate))

        print(self.model.summary())

# farther below...

Qs = DQNetwork.predict(states_mb)
Qs_next_state = DQNetwork.predict(next_states_mb)

# Set Q_target = r if the episode ends at s+1, otherwise set Q_target = r + gamma*maxQ(s', a')
for i in range(0, len(batch)):
    terminal = dones_mb[i]
    t = np.copy(Qs[i])
    a = np.argmax(actions_mb[i])

    # If we are in a terminal state, only equals reward
    if terminal:
        t[a] = rewards_mb[i]
    else:
        t[a] = rewards_mb[i] + gamma * np.max(Qs_next_state[i])

    target_Qs_batch.append(t)
    dbg_target_Qs_batch.append(t[a])

targets_mb = np.array([each for each in target_Qs_batch])

loss = DQNetwork.train_on_batch(states_mb, targets_mb)

其他一切都是一样的。我什至尝试过使用自定义损失函数来最小化代码中的差异——但它根本不起作用!虽然原始代码很快收敛,但我的 Keras 涂鸦似乎根本不想工作!

有人知道吗?任何提示或帮助将不胜感激......

再稍微解释一下:

这是一个简单的 DQN 玩 Doom - 所以在大约 100 集(游戏)之后,模型似乎能够在每一集都毫无问题地射击目标。损失下降,每场比赛的奖励上升 - 正如人们所期望的那样......然而,在 Keras 模型中,损失图是平坦的,奖励图是平坦的 - 它几乎似乎无法学习任何东西。 (参见下面链接的图表)

这是它的工作原理。在 TF 代码中,模型输出一个张量 [a, b, c],其中 a、b 和 c 给出主角可能采取的每个动作的概率(即:[left, right, shoot])。然后模型为每个动作提供奖励,因此它被传递一个目标值(target_mb,f.ex. 10)以及这是针对哪个动作(one-hot 编码在 actions_mb 中,即 [0,1,0] - 如果这是向右移动的目标)。然后使用简单的 MSE 计算给定动作的模型目标值和预测值之间的差值。

我做了两件事:

1) 我尝试使用在其他此类模型中看到的标准“mse”损失。为了使损失表现相同,我将模型自己的输入与目标值分开。因此,如果模型预测 [3,4,5] 并且目标是 [0,1,0] 的 10 - 我们将 [3,10,5] 作为真值传递给模型。这应该等同于 TF 模型的动作。即,10 和 4 之间的差异,平方,然后是批次中所有差异的平均值。

2) 当 1) 不起作用时,我尝试制作一个自定义损失函数,该函数基本上试图尽可能地模仿 TF 模型的行为。因此,如果模型预测 [3,4,5] 并且 [0,1,0] 的目标是 10(如上) - 我们将 [0,10,0] 作为真值传递给模型。然后自定义损失函数通过一些挑剔的乘法和除法得到 10 和 4 之间的差值 - 平方它并取所有平方误差的平均值,如下所示:

def custom_loss(y_true, y_pred):
    isolated_truths = tf.reduce_sum(y_true, axis=1)
    isolated_predictions = tf.divide(tf.reduce_sum(tf.multiply(y_true, y_pred), axis=1), isolated_truths)
    delta = isolated_predictions - isolated_truths
    return tf.reduce_mean(tf.square(delta))

# when training, this small modification is made to targets:
loss = DQN_Keras.train_on_batch(states_mb, targets_mb.reshape(len(targets_mb),1) * actions_mb)

它仍然不起作用(尽管您可以在图表上看到损失的行为似乎更加合理!)。

看一下图表:

tf 模型:https://pasteboard.co/IN1b5MN.png

具有 mse 损失的 keras 模型:https://pasteboard.co/IN1kH6P.png

具有自定义损失的 keras 模型:https://pasteboard.co/IN17ktg.png

编辑 #2 - 可运行代码

原始 TF 代码 - 从上面的教程复制粘贴,工作: => https://pastebin.com/QLb7nWZi

我的代码完全自定义损失: => https://pastebin.com/3HiYg6t7

【问题讨论】:

  • “它根本行不通”是什么意思?
  • 嗯,TF 模型似乎收敛得很好。这是一个简单的 DQN 玩 Doom - 所以在大约 100 集(游戏)之后,该模型似乎能够在每一集都毫无问题地射击目标。损失下降,每场比赛的奖励上升 - 正如人们所期望的那样......但是,在 Keras 模型中,损失图是平坦的,奖励图是平坦的 - 它几乎似乎什么都学不到!
  • 在tensorflow代码中,输入有states_mb、targets_mb、actions_mb三种。但是在您的 keras 代码中,只有 states_mb 作为输入输入到模型中。有什么解释吗?
  • 是的,这是因为 TF 模型使用了自定义损失函数。模型被传递给某个动作的目标,它基本上计算预测目标与给定目标的 MSE。 states_mb 是模型的输入(来自游戏屏幕的图像序列),targets_mb 是 Q 目标值 - 而 action_mb 是 one-hot 编码动作
  • 这有点难以解释,所以我会在编辑中发布上面的解释:)

标签: python tensorflow keras


【解决方案1】:

好吧,我已经完成了 - 通过删除 BatchNormalization 层。现在我完全被迷惑了……那么批量标准化在 Keras 和 Tensorflow 中的工作方式是否不同?还是 TF 中这个神秘的“training=True”参数缺失的线索(Keras 中不存在)?

附言。 在深入研究这个问题时,我还发现这篇非常有用的文章描述了如何创建具有多个输入(如掩码)的高级 Keras 模型(就像在原始 TF 代码中一样!):

https://becominghuman.ai/lets-build-an-atari-ai-part-1-dqn-df57e8ff3b26

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 2013-10-13
    • 2015-03-18
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多