Укрепление обучения не работает для этой очень простой игры, почему? Q Learning - PullRequest
0 голосов
/ 11 апреля 2020

Я запрограммировал очень простую игру, которая работает следующим образом:

Учитывая поле квадратов 4x4, игрок может двигаться (вверх, вправо, вниз или влево).

  • Проходя по клетке, агент, который никогда не посещал, дает награду 1.

  • Заступая на "мертвое поле", награждается -5 и тогда игра будет сброшена.

  • Движение на поле, которое уже было посещено, вознаграждается -1

  • За победу -field "(есть ровно один) дает вознаграждение 5, и игра также будет сброшена.


Теперь я хочу, чтобы ИИ научился играть в эту игру через Q- Обучение.

Как я организовал Inputs / Feature Engineering:

Вход для net - это массив с формой 1x4, где arr [0] представляет поле выше (при движении вверх), arr [1] представляет поле справа, arr [2] ниже, arr [3] слева.

Возможные значения, которые может содержать массив: 0, 1, 2, 3

0 = "мертвое поле", поэтому наихудший случай

1 = это будет за пределами поля 4x4 (поэтому вы не можете перейти туда), или поле уже было посещено

2 = непосещаемое поле (так что это что-то хорошее)

3 = "поле выигрыша", поэтому лучший случай

Как видите, я приказал им по награде.

Поскольку игра принимает входные данные одинаково (0 = движение вверх, 1 = движение вправо, 2 = движение вниз, 3 = движение влево), единственное, что ИИ должен был бы выучить в основном: выберите индекс массива, который содержит наибольшее значение.

Но, к сожалению, он не работает, net просто не учится, даже после 30 000 эпизодов.


Вот мой код (включая игру в начале):

import numpy as np
import random
Import tensorflow as tf
import matplotlib.pyplot as plt

from time import sleep

episoden = 0

felder = []
schon_besucht = []

playerx = 0
playery = 0

grafik = False

def gib_zustand():
    # besonderes feature engineering:
    # input besteht nur aus einer richtung, die one-hot-encoded ist; also 4 inputneuronen
    # (glut, wand/besucht, unbesucht, sieg)
    #
    # es ist die richtung, die bewertet werden soll (also 1 outputneuron fuer eine richtung)

    # rueckgabe hier: array, shape: 4x4 (s.o.)

    global playerx
    global playery

    # oben 
    if playery == 0:
        oben = 1
    else:
        oben = felder[playery-1][playerx]

    # rechts
    if playerx == 4:
        rechts = 1
    else:
        rechts = felder[playery][playerx+1]

    # unten
    if playery == 4:
        unten = 1
    else:
        unten = felder[playery+1][playerx]

    # links
    if playerx == 0:
        links = 1
    else:
        links = felder[playery][playerx-1]

    return np.array([oben, rechts, unten, links])

def grafisch():
    if grafik:

        # encoding:
        # glut = G, besucht = b, unbesucht = , sieg = S, Spieler = X
        global felder
        global playerx
        global playery

        print('')

        for y in range(0,5):
            print('|', end='')
            for x in range(0,5):
                if felder[y][x] == 0:
                    temp = 'G'
                if felder[y][x] == 1:
                    temp = 'b'
                if felder[y][x] == 2:
                    temp = ' '
                if felder[y][x] == 3:
                    temp = 'S'
                if y == playery and x == playerx:
                    temp = 'X'

                print(temp, end='')
                print('|', end='')
            print('')

def reset():
    print('--- RESET ---')

    global playery
    global playerx
    global felder
    global schon_besucht

    playerx = 1
    playery = 3

    # anordnung
    # glut = 0, wand/besucht = 1, unbesucht = 2, sieg = 3

    felder = [[2 for x in range(0,5)] for y in range(0,5)]
    # zwei mal glut setzen
    gl1 = random.randint(1,3)
    gl1_1 = random.randint(2,3) if gl1==3 else (random.randint(1,2) if gl1==1 else random.randint(1,3))
    felder[gl1][gl1_1] = 0 # glut

    # zweites mal
    gl1 = random.randint(1,3)
    gl1_1 = random.randint(2,3) if gl1==3 else (random.randint(1,2) if gl1==1 else random.randint(1,3))
    felder[gl1][gl1_1] = 0 # glut

    # pudding
    felder[1][3] = 3

    # ruecksetzen
    schon_besucht = []

    grafisch()

    return gib_zustand()

def step(zug):
    # 0 = oben, 1 = rechts, 2 = unten, 3 = links
    global playerx
    global playery
    global felder
    global schon_besucht

    if zug == 0:
        if playery != 0:
            playery -= 1
    if zug == 1:
        if playerx != 4:
            playerx += 1
    if zug == 2:
        if playery != 4:
            playery += 1
    if zug == 3:
        if playerx != 0:
            playerx -= 1

    # belohnung holen
    wert = felder[playery][playerx]

    if wert==0:
        belohnung = -5
    if wert==1:
        belohnung = -1
    if wert==2:
        belohnung = 1
    if wert==3:
        belohnung = 5

    # speichern wenn nicht verloren
    if belohnung != -5:
        schon_besucht.append((playery,playerx))
        felder[playery][playerx] = 1

    grafisch()

    return gib_zustand(), belohnung, belohnung==5, 0 # 0 damits passt

episoden = 0

tf.reset_default_graph()

#These lines establish the feed-forward part of the network used to choose actions
inputs1 = tf.placeholder(shape=[1,4],dtype=tf.float32)
#W1 = tf.Variable(tf.random_uniform([16,8],0,0.01))
W2 = tf.Variable(tf.random_uniform([4,4],0,0.01))
#schicht2 = tf.matmul(inputs1,W1)
Qout = tf.matmul(inputs1,W2)
predict = tf.argmax(Qout,1)

#Below we obtain the loss by taking the sum of squares difference between the target and prediction Q values.
nextQ = tf.placeholder(shape=[1,4],dtype=tf.float32)
loss = tf.reduce_sum(tf.square(nextQ - Qout))
trainer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
updateModel = trainer.minimize(loss)

init = tf.initialize_all_variables()

# Set learning parameters
y = .99
e = 0.1
num_episodes = 10_000
#create lists to contain total rewards and steps per episode
jList = []
rList = []
with tf.Session() as sess:
    sess.run(init)
    for i in range(num_episodes):             
        #Reset environment and get first new observation
        s = reset()
        rAll = 0
        d = False
        j = 0
        #The Q-Network        
        while j < 99:
            j+=1
            #Choose an action by greedily (with e chance of random action) from the Q-network
            a,allQ = sess.run([predict,Qout],feed_dict={inputs1:s.reshape(1,4)}) # berechnet prediction fuer input (input scheint hier one hot encoded zu sein)
            if np.random.rand(1) < e:
                a[0] = random.randint(0,3)                 

            #Get new state and reward from environment
            s1,r,d,_ = step(a[0])
            #Obtain the Q' values by feeding the new state through our network
            Q1 = sess.run(Qout,feed_dict={inputs1:s1.reshape(1,4)})
            #Obtain maxQ' and set our target value for chosen action.
            maxQ1 = np.max(Q1)


            targetQ = allQ
            targetQ[0,a[0]] = r + y*maxQ1
            #Train our network using target and predicted Q values

            _,W1 = sess.run([updateModel,W2],feed_dict={inputs1:s.reshape(1,4),nextQ:targetQ})
            rAll += r
            s = s1

            if r == -5 or r == 5:
                if r == 5:
                    episoden+=1

                reset()

                #Reduce chance of random action as we train the model.
                e = 1./((i/50) + 10)
                break
        jList.append(j)
        #print(rAll)
        rList.append(rAll)
print("Percent of succesful episodes: " + str((episoden/num_episodes)*100) + "%")
plt.plot(rList)
plt.plot(jList)

Я прочитал в симуляционном вопросе, что причина слишком высоких Q-значений может быть, что это на самом деле возможно для агент, чтобы получить неограниченные высокие общие награды в игре. Это было бы так, если бы агент мог наступить на уже посещенные поля и получить вознаграждение, равное 1. Тогда, конечно, возможное общее вознаграждение будет бесконечным. Но это не тот случай: игрок получает плохую награду (-1), когда делает это. Небольшой расчет: выигрышное поле вознаграждается 5. Непосещенным полем 1. Есть хотя бы одно мертвое поле. Всего 16 полей. Максимально возможное общее вознаграждение: 14 * 1 + 1 * 5 = 19

1 Ответ

0 голосов
/ 12 апреля 2020

Я наконец нашел решение для этого, это заняло у меня почти неделю.

Ключ был в том, чтобы мои входные данные были с горячим кодированием . Это заставляет меня использовать 16 входных нейронов вместо 4, но теперь это работает. После 1.000 эпизодов у меня почти 91% успешных эпизодов.

Я все еще удивляюсь тому факту, что это не сработало, когда вход не был горячо закодирован. Я знаю, что ИНС будет автоматически использовать отношения между входами, которые получает нейрон, в большем и меньшем размерах, что может быть недостатком. Но так как я упорядочил свои входные данные таким образом, что если один входной сигнал больше другого, это также означает, что выходные данные должны быть одинаковыми. Таким образом, здесь нет недостатка, если ANN, наоборот, использует отношения, что должно быть преимуществом.

Поэтому я подумал, что было бы хорошо не кодировать входные данные одним способом, потому что таким образом я уменьшаю размерность очень (4 вместо 16).

Очевидно, что эта идея не сработала.

Однако, как я уже сказал, с 16 входами это работает сейчас.

...