2
$\begingroup$

I have troubles with my CNN. The code runs and my network learns something, but the performance is really poor. The goal is to play the game Connect 4. The network therefor receives a numpy array with the size (batch_size, row, cols, 2). The 2 stands for two channels. In channel one there are all places obtained by player one and on the other channels are all places obtained by player two.

It was up now for a few days but still doesn't manage to recognize horizontal lines. It knows four stones vertical is good, and it also stops the opponent from getting four vertically. But there is 0 awareness of playing four diagonally or horizontally. Do I maybe convolute over the wrong axis? This could be an explanation why it only recognizes those...

Here is the CNN part of my code:

    in_x = x = Input((self.observation_space[0], self.observation_space[1], 2))  # stack of own(6x7) and enemy(6x7) field

    x = Conv2D(128, 3, padding="same",
               kernel_regularizer=l2(1e-4))(x)
    x = BatchNormalization(axis=1)(x)
    x = Activation("relu")(x)

    for _ in range(2):
        x = self._build_residual_block(x)

    x = Conv2D(filters=1, kernel_size=1, kernel_regularizer=l2(1e-4))(x)
    x = BatchNormalization(axis=1)(x)
    x = Activation("relu")(x)
    x = Flatten()(x)
    policy_out = Dense(action_space, kernel_regularizer=l2(1e-4), activation="softmax", name="policy_out")(x)

    self.model = Model(in_x, policy_out, name="connect4_model")

    self.optimizer = SGD(lr=1e-2, momentum=0.9)
    self.model.compile(optimizer=self.optimizer, loss='mse')

def _build_residual_block(self, x):
    in_x = x
    x = Conv2D(filters=128, kernel_size=3, padding="same",
               kernel_regularizer=l2(1e-4))(x)
    x = BatchNormalization(axis=1)(x)
    x = Activation("relu")(x)
    x = Conv2D(filters=128, kernel_size=3, padding="same",
               kernel_regularizer=l2(1e-4))(x)
    x = BatchNormalization(axis=1)(x)
    x = Add()([in_x, x])
    x = Activation("relu")(x)
    return x

observation_space is (6,7), action space is (7)

EDIT 1: Just found one first huge mistake. I used the wrong loss. The author defined its own loss. I have used a MSE wich is definitely wrong. The author from the script I copied the network from has one interesting comment:

import keras.backend as K
...    
def objective_function_for_policy(y_true, y_pred):
        # can use categorical_crossentropy??
        return K.sum(-y_true * K.log(y_pred + K.epsilon()), axis=-1)

Is this actually completly the same as categorical_crossentropy? Looks like he wasn't sure either. The epsilon doesn't appear in the crossentropy I know.

$\endgroup$
2
  • 1
    $\begingroup$ That sure looks like categorical cross entropy to me. Epsilon is probably just a hack to prevent $log(0)$. $\endgroup$
    – Sycorax
    Commented Dec 16, 2018 at 20:01
  • $\begingroup$ Ou okay. Thought it's somehow the epsilon used in q-learning (exploration) but it comes from the Keras backend. So I'll replace this part with loss='categorical_crossentropy' $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 16, 2018 at 20:03

3 Answers 3

1
$\begingroup$

OP points out in an edit that s/he was not using the same loss function as was used in the reference code. Because OP’s code didn’t match the reference, it’s not surprising that the RL agent found a different result.

Incidentally, this is why I recommend unit tests and careful debugging as a core part of building neural networks specifically and programming generally.

$\endgroup$
4
  • $\begingroup$ I'm re-running it again right now. It looks like it performs better. I'll give an update if that eliminated the problem. $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 16, 2018 at 20:05
  • $\begingroup$ Do you might have some information about debugging and unit testing concerning NNs? That's something I don't see trough yet. $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 16, 2018 at 20:08
  • 1
    $\begingroup$ More information about that can be found in answers to What to do when my neural network doesn’t learn? $\endgroup$
    – Sycorax
    Commented Dec 16, 2018 at 21:25
  • $\begingroup$ Here is the link for the question mentioned in the above comment. $\endgroup$
    – Nuclear241
    Commented Sep 16, 2021 at 13:09
0
$\begingroup$

It's difficult to answer this without reproducible code. However, one thing that jumps out is you don't seem to be doing any pooling between convolutions. This might be making it difficult for your network to recognize longer patterns. So for example if you do a 3x3 convolution in the top left of the board, and a 3x3 on the top right, there doesn't seem to be any communication between the two, except through the dense layer. Consider adding a few pooling layers, or changing the padding="valid" in the last few convolutions to incur a contraction of spatial dimensions.

$\endgroup$
8
  • $\begingroup$ oh no thats necessary. in reinforcemnt learning one doesnt add pooling layers. the model structure is already prooven and inspired by alpha go / deepmind. I think its rather a small mistake, typo, swapt indices. Possibly a wrong input vector or something like that. $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 14, 2018 at 22:43
  • $\begingroup$ @Mr.Sh4nnon: What you're saying makes zero logical sense. This has nothing to do with reinforcement learning, but with the structure of convolutions. Try changing the last few convolution blocks to have padding="valid" to contract dimensions by 2. This will at least allow long-range convolution outputs to be processed, in lieu of pooling. For example: github.com/suragnair/alpha-zero-general/blob/master/othello/… $\endgroup$
    – Alex R.
    Commented Dec 14, 2018 at 23:03
  • $\begingroup$ I suggest reading the deepmind atari paper. Pooling isn‘t used there since positional information is crucial. Droupot is also often better not used due to the tricky RL optimization environment... I wish I had just asked this in the Stackoverflow section $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 14, 2018 at 23:06
  • $\begingroup$ @Mr.Sh4nnon: You need SOME FORM of dimensional contraction to link far away convolutions. This can be done EITHER with pooling OR by a consequence of non-padding your convolution input. $\endgroup$
    – Alex R.
    Commented Dec 14, 2018 at 23:08
  • $\begingroup$ Ok, the problem is just I copied this from a famous alpha go addaption. And I mean it. I copied exactly this way. And I dont know who do trust now. Sinde running the source I got it from has outstanding performance... $\endgroup$
    – Mr.Sh4nnon
    Commented Dec 14, 2018 at 23:11
0
$\begingroup$

The problem was as discovered in EDIT 1 the use of MSE. Theoretically MSE might would work if trained for 1000 of hours, but cross-entropy performs much better. After training my network over night it's still a rather dumb player. However, it now has an awareness of horizontal and vertical lines. At least sometimes...

In my implementation only the policy is part of the network. In the implementation I copied the network from, the policy's values are calculated too, which makes it smarter in the end. I have to do some more fine tuning to get similar performance.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.