Я новичок в 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):
Примерно так, но в оттенках серого.
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]. Независимо от того, какое действие выбрано случайной политикой или значением убытка. Почему это происходит?
Вы можете найти весь код здесь !.