[Python] Snake DQN issues

Discussão em 'Python' iniciado por Stack, Maio 28, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to program a DQN with python that learns to play the snake game. I would like to program it without using neither tensorflow nor pytorch. I've looked everywhere in my code and I can't find where my error is and why it isn't learning. Could someone take a look and revise it please?

    I've tried a couple attempts to fix the program, but I can't really tell why it doesn't work.

    import pygame as pg
    from time import time
    from random import randint, random, sample
    from copy import deepcopy


    def argMax(vals):

    return vals.index(max(vals))


    class ReplayBuffer:

    def __init__(self):

    self.memory = []


    def remember(self, prevstate, action, reward, newstate, done):

    self.memory.append((prevstate, action, reward, newstate, done))


    class DQN:

    def __init__(self, dim, weights = None, biases = None):

    self.dim = dim
    self.weights = deepcopy(weights) if weights else [[[2 * random() - 1 for k in range(self.dim[l])] for j in range(self.dim[l + 1])] for l in range(len(self.dim) - 1)]
    self.biases = deepcopy(biases) if biases else [[2 * random() - 1 for j in range(self.dim[l + 1])] for l in range(len(self.dim) - 1)]
    self.e = 2.7182818284
    self.lr = .001


    def forwardProp(self, inputl):

    zs = [inputl]
    activations = [[self.sigmoid(zs[0][k]) for k in range(self.dim[0])]]
    for l in range(len(self.dim) - 1):
    lz = []
    la = []
    for j in range(self.dim[l + 1]):
    z = self.biases[l][j]
    for k in range(self.dim[l]):
    z += self.weights[l][j][k] * activations[-1][k]
    lz.append(z)
    if l == len(self.dim) - 2:
    la.append(z)
    else:
    la.append(self.sigmoid(z))
    zs.append(lz)
    activations.append(la)
    return zs, activations


    def backProp(self, loss, idx, activations):

    dz = 2 * loss[idx]
    newdz = [0 for k in range(self.dim[-2])]
    for k in range(self.dim[-2]):
    self.weights[-2][idx][k] -= self.lr * dz * activations[-2][k]
    newdz[k] += self.weights[-2][idx][k] * dz
    self.biases[-2][idx] -= self.lr * dz
    dz = [newdz[k] * self.dsigmoid(activations[-2][k]) for k in range(self.dim[-2])]
    for l in range(len(self.dim) - 3, -1, -1):
    newdz = [0 for k in range(self.dim[l])]
    for j in range(self.dim[l + 1]):
    for k in range(self.dim[l]):
    self.weights[l][j][k] -= self.lr * dz[j] * activations[l][k]
    newdz[k] += self.weights[l][j][k] * dz[j]
    self.biases[l][j] -= self.lr * dz[j]
    dz = [newdz[k] * self.dsigmoid(activations[l][k]) for k in range(self.dim[l])]


    def sigmoid(self, z):

    return 1 / (1 + self.e ** (-z))


    def dsigmoid(self, z):

    return self.e ** (-z) / (1 + self.e ** (-z))


    class Agent:

    def __init__(self, env):

    self.env = env

    self.epsilon = 60
    self.gamma = .1
    self.maxlen = 1000
    self.dim = 11, 20, 20, 3

    self.memory = ReplayBuffer()
    self.policy = DQN(self.dim)
    self.sync()


    def sync(self):

    self.target = DQN(self.dim, self.policy.weights, self.policy.biases)


    def getAction(self, state):

    action = [0, 0, 0]
    if randint(0, 100) <= self.epsilon:
    arg = randint(0, 2)
    else:
    Qval = self.policy.forwardProp(state)[1][-1]
    print(Qval)
    arg = argMax(Qval)
    action[arg] = 1
    return tuple(action)


    def train(self, prevstate, action, reward, newstate, done):

    for i in range(len(done)):
    activations = self.policy.forwardProp(prevstate)[1]
    Qval = activations[-1]
    target = deepcopy(Qval)
    target[argMax(action)] = reward if done else reward + self.gamma * max(self.target.forwardProp(newstate)[1][-1])
    loss = [Qval[_] - target[_] for _ in range(self.dim[-1])]
    self.policy.backProp(loss, argMax(action), activations)


    def trainLong(self):

    minibatch = sample(self.memory.memory, self.maxlen) if len(self.memory.memory) >= self.maxlen else self.memory.memory
    prevstate, action, reward, newstate, done = zip(*minibatch)
    self.train(prevstate, action, reward, newstate, done)


    class Env:

    def __init__(self):

    self.border = 10
    self.tilesize = 20
    self.w, self.h = 10, 10
    self.dimx, self.dimy = 2 * self.border + self.w * self.tilesize, 2 * self.border + self.h * self.tilesize
    self.screen = pg.Surface((self.dimy, self.dimx), pg.SRCALPHA)
    self.ratio = self.dimx / self.dimy
    self._resizeDisplay((640, 360))
    self.window = pg.display.set_mode((self.winx, self.winy), pg.RESIZABLE)

    self.paused = True
    self.speed = .05
    self.highscore = 0
    self.pressedkeys = {
    pg.K_SPACE: False
    }

    self.agent = Agent(self)

    self._reset()


    def _resizeDisplay(self, size):

    self.winx, self.winy = size
    if size[0] / size[1] < self.ratio:
    self.scx = self.winx
    self.scy = self.winx / self.ratio
    else:
    self.scx = self.winy * self.ratio
    self.scy = self.winy


    def _reset(self):

    self.timesum = 0
    self.envstate = 0
    self.score = 0

    self.direction = 1, 0
    self.wastedsteps = 0
    self.head = self.w // 2 - 1, self.h // 2 - 1
    self.body = []
    self._placeFood()


    def _placeFood(self):

    self.food = randint(0, self.w - 1), randint(0, self.h - 1)
    if self.food in self.body or self.food == self.head:
    self._placeFood()


    def mainLoop(self):

    self.running = True
    prevtime = time()
    while self.running:
    dt = time() - prevtime
    prevtime = time()
    self._checkInput()
    self._update(dt)
    self._updateUI()


    def _checkInput(self):

    for event in pg.event.get():
    if event.type == pg.QUIT:
    self.running = False
    elif event.type == pg.VIDEORESIZE:
    self._resizeDisplay(event.size)
    elif event.type == pg.KEYDOWN or event.type == pg.KEYUP:
    for key in self.pressedkeys.keys():
    if key == event.key:
    self.pressedkeys[key] = event.type == pg.KEYDOWN


    def _update(self, dt):

    if self.pressedkeys[pg.K_SPACE]:
    self.pressedkeys[pg.K_SPACE] = False
    self.paused = not self.paused

    if not self.paused:
    self.timesum += dt
    while self.envstate == 0 and self.timesum >= self.speed:
    self.timesum -= self.speed
    self._step()
    if self.envstate == 1 and self.timesum >= 1:
    self._reset()


    def _step(self):

    prevstate = self._getState()
    action = self.agent.getAction(prevstate)
    reward = self._playStep(action)
    newstate = self._getState()
    self.agent.memory.remember(prevstate, action, reward, newstate, self.envstate)
    self.agent.train([prevstate], [action], [reward], [newstate], [self.envstate])
    if self.envstate == 1:
    self.agent.trainLong()
    self.agent.sync()


    def _getState(self):

    return (
    1 if self.direction[0] == 1 else 0,
    1 if self.direction[1] == 1 else 0,
    1 if self.direction[0] == -1 else 0,
    1 if self.direction[1] == -1 else 0,

    1 if (self.direction[0] == 1 and not self._isCollision((self.head[0], self.head[1] - 1))) or
    (self.direction[1] == 1 and not self._isCollision((self.head[0] + 1, self.head[1]))) or
    (self.direction[0] == -1 and not self._isCollision((self.head[0], self.head[1] + 1))) or
    (self.direction[1] == -1 and not self._isCollision((self.head[0] - 1, self.head[1]))) else 0,
    1 if (self.direction[0] == 1 and not self._isCollision((self.head[0] + 1, self.head[1]))) or
    (self.direction[1] == 1 and not self._isCollision((self.head[0], self.head[1] + 1))) or
    (self.direction[0] == -1 and not self._isCollision((self.head[0] - 1, self.head[1]))) or
    (self.direction[1] == -1 and not self._isCollision((self.head[0], self.head[1] - 1))) else 0,
    1 if (self.direction[0] == 1 and not self._isCollision((self.head[0], self.head[1] + 1))) or
    (self.direction[1] == 1 and not self._isCollision((self.head[0] - 1, self.head[1]))) or
    (self.direction[0] == -1 and not self._isCollision((self.head[0], self.head[1] - 1))) or
    (self.direction[1] == -1 and not self._isCollision((self.head[0] + 1, self.head[1]))) else 0,

    1 if self.food[0] > self.head[0] else 0,
    1 if self.food[1] > self.head[1] else 0,
    1 if self.food[0] < self.head[0] else 0,
    1 if self.food[1] < self.head[1] else 0
    )


    def _isCollision(self, head):

    if head[0] < 0 or head[0] >= self.w or head[1] < 0 or head[1] >= self.h or head in self.body[:-1]:
    return True
    return False


    def _playStep(self, action):

    self._changeDirection(action)
    newhead = self.head[0] + self.direction[0], self.head[1] + self.direction[1]
    if self._isCollision(newhead) or self.wastedsteps == 100:
    self.envstate += 1
    return -1
    elif newhead == self.food:
    self.wastedsteps = 0
    self.score += 1
    if self.score > self.highscore:
    self.highscore = self.score
    print(self.highscore)
    self.body.append(self.head)
    self.head = newhead
    self._placeFood()
    return 10
    else:
    self.wastedsteps += 1
    self.body.append(self.head)
    self.body.pop(0)
    self.head = newhead
    return -.05


    def _changeDirection(self, action):

    if action[0]:
    self.direction = self.direction[1], -self.direction[0]
    elif action[2]:
    self.direction = -self.direction[1], self.direction[0]


    def _updateUI(self):

    self.screen.fill((0, 0, 0, 0))
    for partx, party in self.body:
    pg.draw.rect(self.screen, (0, 0, 255), (self.border + partx * self.tilesize, self.border + party * self.tilesize, self.tilesize, self.tilesize))
    pg.draw.rect(self.screen, (0, 255, 0), (self.border + self.head[0] * self.tilesize, self.border + self.head[1] * self.tilesize, self.tilesize, self.tilesize))
    pg.draw.rect(self.screen, (255, 0, 0), (self.border + self.food[0] * self.tilesize, self.border + self.food[1] * self.tilesize, self.tilesize, self.tilesize))
    for x in range(self.w + 1):
    pg.draw.line(self.screen, (255, 255, 255), (self.border + x * self.tilesize, self.border), (self.border + x * self.tilesize, self.border + self.h * self.tilesize))
    for y in range(self.h + 1):
    pg.draw.line(self.screen, (255, 255, 255), (self.border, self.border + y * self.tilesize), (self.border + self.w * self.tilesize, self.border + y * self.tilesize))

    self.window.fill((55, 55, 55))
    scsurface = pg.transform.smoothscale(self.screen, (self.scx, self.scy))
    offset = (self.winx - self.scx) / 2, (self.winy - self.scy) / 2
    self.window.blit(scsurface, offset)
    pg.display.flip()


    if __name__ == "__main__":

    pg.init()
    app = Env()
    app.mainLoop()
    pg.quit()

    Continue reading...

Compartilhe esta Página