Пользовательская политика Градиент CNN не учится - PullRequest
0 голосов
/ 15 января 2020

Я новичок в stackoverflow, извините, если я что-то делаю неправильно или я не объясняю себя должным образом. Я пытаюсь сделать нейронную сеть, которая в основном берет за рамки упрощения PacMan. Сеть использует политику потери градиента, чтобы учиться, используя вознаграждение. Я думаю, что понимаю, как работает обычная потеря, когда ваша награда высока или потеря мала, тогда она продолжает это делать, в противном случае она выполнит другое действие. Это класс моей нейронной сети:

import numpy as np
import tensorflow as tf
from keras import optimizers
from keras.models import Model
from keras.layers import Input, Conv2D, Flatten, Dense

class PolicyGradientCNN:

    def __init__(self, state_space, action_space, epsilon, epsilon_min, epsilon_decay):
        self.action_space = action_space
        self.state_space = state_space
        self.epsilon = epsilon
        self.epsilon_min = epsilon_min
        self.epsilon_decay = epsilon_decay
        self.buildModel()
        adam = optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, decay=0.0)
        self.model.compile(loss = None, optimizer=adam, metrics=['mae'])
        self.model.summary()

    def buildModel(self):
        # We have 3 inputs, a state, an action, and a reward of that action in that state 
        last_state = Input(shape=self.state_space, name='input')
        last_action = Input(shape=(self.action_space,), name='last_action')
        last_reward = Input(shape=(1,), name='reward')
        # How we are using an image as an input we need convolutions
        f = Conv2D(32, 8, strides=(4, 4), activation = 'relu', input_shape=self.state_space, kernel_initializer='glorot_uniform')(last_state)
        f = Conv2D(64, 4, strides=(2, 2), activation = 'relu', input_shape=self.state_space, kernel_initializer='glorot_uniform')(f)
        f = Conv2D(64, 3, strides=(1, 1), activation = 'relu', input_shape=self.state_space, kernel_initializer='glorot_uniform')(f)
        f = Flatten()(f)
        f = Dense(1024, activation = 'relu', kernel_initializer='glorot_uniform')(f)
        f = Dense(512, activation = 'relu', kernel_initializer='glorot_uniform')(f)
        # We predict an action as an output with the size of the action_space
        action_pred = Dense(self.action_space, activation = 'softmax', kernel_initializer='glorot_uniform')(f)
        self.model = Model(inputs=[last_state, last_action, last_reward], outputs = [action_pred])
        self.model.add_loss(self.customLoss(action_pred, last_action, last_reward))


    # This loss function is a policy gradient loss function
    def customLoss(self, action_pred, last_action, last_reward):
        neg_log_prob = tf.nn.softmax_cross_entropy_with_logits(logits = action_pred, labels = last_action)
        loss = tf.reduce_mean(neg_log_prob * last_reward)
        return loss

    # To choose an action we need to have some exploration, we make this posible by an epsilon
    def chooseAction(self, state):
        self.epsilon *= self.epsilon_decay
        self.epsilon = max(self.epsilon_min, self.epsilon)
        print("Epsilon")
        print(self.epsilon)

        r = np.random.random()
        if r > self.epsilon:
            print(" ********************* CHOOSING A PREDICTED ACTION **********************")
            actions = np.ones((2, self.action_space))
            rewards = np.ones((2, 1))
            pred = self.model.predict([state, actions, rewards])
            action = pred
        else:
            print("******* CHOOSING A RANDOM ACTION *******")
            chose_action = np.random.choice(range(self.action_space))  # select action w.r.t the actions prob
            action = np.zeros((2,4))
            action[1][chose_action] = 1

        print("Chose action")
        print(action)
        return action


    # Update our target network
    def trainTarget(self, states, actions, discounted_episode_rewards):
        self.model.fit([states, actions, discounted_episode_rewards])

У меня есть 3 входа, last_state - это 2 кадра игры, в оттенках серого и в форме (256,256,1):

Fake PacMan

Примерно так, но в оттенках серого.
Last_action - это массив, например, такой:
[0,1,0,0]
Это будет перевести на [Вверх, вниз, вправо, влево].
Это последнее действие, которое выбрала Сеть или случайная политика.
И, наконец, last_reward, то есть вознаграждение, предоставляемое функцией вознаграждения, учитывая last_state для last_action.

У меня есть только один выход - действие, которое будет выбрано случайно или с помощью нейронной сети. У меня есть случайная политика для целей исследования.

Теперь, когда я думаю, что часть нейронной сети прояснена, давайте go с остальным кодом. Это мой класс агентов, который запускает игра:


from distanceCalculator import Distancer
from game import Actions
from game import Directions
import random, sys

import graphicsDisplay
import numpy as np
import cv2
from cnn import PolicyGradientCNN

'''Policy Gradients PacMan Agent'''
class PolicyGradientPAgent(BustersAgent):

    def registerInitialState(self, gameState):
        BustersAgent.registerInitialState(self, gameState)
        self.distancer = Distancer(gameState.data.layout, False)
        self.first_time = True
        self.frames = np.ones((2,256,256,1))
        self.last_actions = np.ones((2,4))
        self.last_rewards = np.ones((2,1))
        self.action_space = 4
        self.epsilon = 0.9
        self.epsilon_min = 0.05
        self.epsilon_decay = 0.99
        self.dpg = PolicyGradientCNN(self.frames.shape[1:], self.action_space, self.epsilon, self.epsilon_min, self.epsilon_decay)
        print("initialize")

    def getReward(self, state, nextState):
        """
              Return a reward value based on the information of state and nextState     
        """
        # The reward must be a positive number for the DNN
        # Time must have a negative impact, the faster, the better
        # Eating a ghost must have a positive impact
        # Winning the game must have a super positive impact #GOAL
        # I want for the action of eating a ghost to be as positive as negative is 10 time steps
        # I want for the action of winning the game to be as positive as negative is 100 time steps

        victory_reward = 10.0
        stay_the_same = 0.0
        killing_ghost_reward = (np.sum(state.getLivingGhosts()) - np.sum(nextState.getLivingGhosts())) * 1.0
        time_cost = 0.1 # multiply by a number between 0 and 1 makes our reward smaller
        reward = self.last_rewards[1] + (time_cost - killing_ghost_reward) - (victory_reward if nextState.isWin() else stay_the_same)
        return reward

    def chooseAction(self, gameState):

        # Here we are choosing an action
        # We need to get the actual state of the game
        # We as humans can learn and play just looking at the game
        # So the Deep Neural Network should be able to do the same
        # We get the frame of the state
        # To calculate movement of the ghost we will need 2 states/frames
        # We will use a queue for this purpose
        # We will make it with a numpy array, I know it is ineficient but we just have 2 elements
        # We could use a numpy array and just delete the oldest state by overwriting
        # But I want for my numpy array to have the first position as the oldest state
        # And the next position as the newest state

        last_frame_position = 0
        current_frame_position = 1

        newsize = (256, 256)
        image = graphicsDisplay.getFrame().resize(newsize).convert('L')
        state = np.expand_dims(np.array(image), axis=2)

        # The first iteration we won't have a reward so we shouldn't train until we have at least a reward
        if(not self.first_time):
            self.frames[last_frame_position] = self.frames[current_frame_position] 
            self.frames[current_frame_position] = state
            self.last_actions[last_frame_position] = self.last_actions[current_frame_position]
            self.last_actions[current_frame_position] = self.last_action
            self.last_rewards[last_frame_position] = self.last_rewards[current_frame_position]
            self.last_rewards[current_frame_position] = self.getReward(self.lastGameState, gameState)
            print('last_rewards')
            print(self.last_rewards)
            self.dpg.trainTarget(self.frames, self.last_actions, self.last_rewards)
        else:
            # We don't have 2 states when we start
            # If we would wait 1 frame to have 2 states we couldn't be optimal
            # So to avoid the waiting the first iteration we will have the same state for both frames
            self.frames[last_frame_position] = state 
            self.frames[current_frame_position] = state
            self.first_time = False

        # We choose an action
        ar_action = self.dpg.chooseAction(self.frames)[1]

        print("ACTION")
        print(ar_action)

        action = np.argmax(ar_action)
        # If we don't choose a legal position then we don't move
        move = Directions.STOP
        legal = gameState.getLegalActions(0) ##Legal position from the pacman
        if   ( action == 0 ) and Directions.WEST in legal:  
            move = Directions.WEST
        elif   ( action == 1 ) and Directions.EAST in legal: 
            move = Directions.EAST
        elif   ( action == 2 ) and Directions.NORTH in legal:   
            move = Directions.NORTH
        elif   ( action == 3 ) and Directions.SOUTH in legal: 
            move = Directions.SOUTH

        # We need to keep memory of the last state
        self.last_state = state
        self.last_action = ar_action
        self.lastGameState = gameState

        return move

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

NN выбирает действие, используя состояние (2 кадра). Я выбрал 2, потому что призрак может двигаться с 2 кадрами, он должен быть в состоянии предсказать движение. Это вернет массив с «счетом» каждого действия, и я хочу, чтобы действие получило лучший результат.

NN тренируется с использованием текущего состояния, last_action и last_reward. Конечно, он не может тренировать первую итерацию, потому что у меня пока нет вознаграждения.

Теперь, когда я все объяснил, давайте поговорим о моей проблеме.
Когда я тренируюсь, потери продолжают расти, но результат всегда один и тот же. Например, если на первой итерации лучший «счет» повышался, [0,9, 0,08, 0,02, 0,1]. Тогда следующие состояния будут [1.0, 0.0, 0.0, 0.0]. Независимо от того, какое действие выбрано случайной политикой или значением убытка. Почему это происходит?

Вы можете найти весь код здесь !.

...