Logistieke Robots/les 6
Les 6 - Robots van elkaar laten leren
Voordat we naar les 6 gaan, bespreken we een aantal verbeteringen in de huidige code zodat we er vervolgens makkelijker mee kunnen werken. Deze verandering moet je ook doorvoeren als je door wilt werken met je huidige bestand. Je mag ook les 6 starten met de file warehouse_team_collission.blend
Verbetering code na les 5
We gaan een functie set_articles() maken zodat er een array aangemaakt wordt met de artikelen.
def get_order():
# get articles from central controller
set_articles()
print(robot["articles"])
# set game properties
robot["article_number"] = 1
robot["target_desk"] = False
# distance variables
robot["distance"] = 0.0
robot["location1"] = robot.position[0]
robot["location2"] = robot.position[1]
robot["location3"] = robot.position[2]
Vervang ook de declaratie van robot[“robot_id”] door onderstaande. De central controller hoeft zo niet meer te vertellen om welke robot het gaat.
# get the current controller
cont = bge.logic.getCurrentController()
robot = cont.owner
robot["robot_id"] = robot.name[6:]
De functie set_articles() ziet er als volgt uit:
def set_articles():
message = start_sens.bodies[0][1:]
order_length = len(re.findall(r'[^\s ]+', message))
robot["articles"] = [""]*order_length
for i in range(order_length):
robot["articles"][i] = re.findall(r'[^\s ]+', message)[i]
De main() functie ziet er nu overzichtelijker uit:
def main():
if robot["target_desk"] == False:
shelf_sens.propName = "Shelf"
if robot["article_number"] <= len(robot["articles"]):
track_act.target = robot["articles"][robot["article_number"]-1]
else:
shelf_sens.propName = "Desk"
track_act.target = "Desk_" + str(robot["robot_id"])
if shelf_sens.positive:
if robot["target_desk"] == True:
robot["article_number"] += 1
if robot["article_number"] == len(robot["articles"]) +1:
cont.deactivate(track_act)
print("done robot "+ str(robot["robot_id"]))
GameLogic.sendMessage("Done", robot["robot_id"] , "Ground", "")
GameLogic.sendMessage("Done", str(robot["distance"]) , "Ground", "")
robot["target_desk"]= not robot["target_desk"]
We veranderen ook de physics van de robot zodat de robot niet om kan vallen. Ga op de robot staan en ga in het rechter window naar:
Verander dan de form factor in 0.0:
Doe dit voor beide robots.
Hervat les 6 - Robots van elkaar laten leren
Nu we weten hoe we een team aan kunnen sturen en we weten dat er efficiëntie winst te bepalen viel, willen we dit natuurlijk op grote schaal toepassen. Wanneer we meer teams in gaan zetten lopen we wel tegen het probleem aan dat ze elkaar voor de voeten kunnen lopen. Dit probleem gaan we ontdekken en een manier vinden om dit op te lossen. We kunnen zo’n “botsingen probleem” op 2 manieren op lossen. We kunnen de robot vertellen wat hij moet doen of we kunnen de robot een manier aangeven waardoor de robot zelf kan leren wat een optimaal ontwijkingsmechanisme is. De robots worden nu zelf lerend. Dit laatste is natuurlijk veel leuker en daarom gaan we dat doen. Het eindresultaat kun je zien op https://www.youtube.com/watch?v=LwOjSqbDy0g
Observeren van botsingen
Open het bestand “warehouse_team_collission.blend” en voeg hier nog twee Morsy robots aan toe op dezelfde manier als in les 5 met de namen Robot_9 en Robot_8. De centrale controller moet ook veranderd worden omdat we nu twee teams hebben. Doe daarvoor de volgende aanpassingen:
if sens.positive:
robot_team = ["10","11"]
central["active_robots"] = [0,0,0,0,0,0,0,0,0,0,0]
central["distance"] =0
central["time"] = 0.0
Send_order(robot_team)
if message.positive:
central["active_robots"][int(message.bodies[0])-1]=0
central["distance"] += float(message.bodies[1])
if central["boxes_done"] < central["boxes_tot"]-1 and central["active_robots"][10-1]+central["active_robots"][11-1]==0:
central["boxes_done"] +=1
print("number of boxes done:")
print(central["boxes_done"])
Send_order(["10","11"])
if central["boxes_done"] < central["boxes_tot"]-1 and central["active_robots"][9-1]+central["active_robots"][8-1]==0:
central["boxes_done"] +=1
print("number of boxes done:")
print(central["boxes_done"])
Send_order(["8","9"])
if sum(central["active_robots"]) == 0:
print("Total walked distance:")
print(central["distance"])
print(central["time"])
Wanneer je nu de robots de orders laat verzamelen zie je dat de paden van de robots elkaar kunnen kruizen. Wanneer ze elkaar tegemoet lopen reageren ze niet op elkaar en botsen ze dus tegen elkaar op. Omdat ze allebei naar voren bewegen, komen ze allebei niet voorruit. We kunnen, zoals eerder genoemd, nu twee aanpakken kiezen om hier mee om te gaan:
- We kunnen gedragsregels opstellen voor de robots. De robots weten dat wanneer ze een artikel opgehaald hebben, ze weer terug naar tafel moeten en op deze manier kunnen we ze ook vertellen wat ze moeten doen als ze een andere robot zien. Bijvoorbeeld: doe altijd een stap naar links als je dicht bij een andere robot bent.
- We kunnen de robots ook een methode geven om van elkaar te leren hoe ze om moeten gaan met elkaar. Dit kun je doen als je zelf ook niet precies weet hoe de robots zich moeten gedragen. Deze methode die we hiervoor kunnen gebruiken heet een evolutionair algoritme. Voor een voorbeeld kun je kijken op : https://www.youtube.com/watch?v=AnuLXVp-Zf0. In dit filmpje zie je een e-puck robot aan het leren om obstakels te ontwijken. Dit is ook wat wij willen bereiken.
We kiezen voor algoritme 2 om te laten zien hoe je een robot van slimme kennis voorziet.
Kunnen reageren op andere robots
We gaan er in deze paragraaf voor zorgen dat de robots elkaar kunnen herkennen en op elkaar kunnen reageren. Hiervoor moeten we veranderingen doorvoeren in de opzet van het pakhuis en veranderingen doorvoeren in de controller van de robot.
Veranderen pakhuis omgeving
Omdat we de controller van de robot gaan veranderen, mag je robot 9, 10 en 11 weer verwijderen. En de volgende stappen uitvoeren:
Stap 1: Voeg aan de robot een string game property toe met de naam “Robot”. Dit is om elkaar te kunnen herkennen met een Radar
Stap 2: Zet de Physics Type op Character en vink de box Actor aan:
Stap 3: Omdat we de robots nu niet meer helemaal kunnen controleren, ze kunnen immers afwijken van de directe route tussen de tafel en de kast, moeten we het pakhuis iets veranderen. Verander de physics van de kasten op de volgende manier:
Creëer extra wanden door de bestaande wanden te kopiëren, te verplaatsen en om hun as te draaien zodat we er doorheen kunnen blijven kijken. Verwijder Desk 1 t/m Desk 5 en verdeel Desk 6 t/m Desk 11 over de ruimte. De arena ziet er nu als volgt uit, waarbij er bij jou maar 1 robot staat bij Desk 8:
Veranderen robot controller
De robots moeten elkaar nu herkennen, hier op reageren en vervolgens weer doorgaan met het ophalen van artikelen. We moeten veel gaan veranderen aan de controller. We nemen dit stap voor stap door:
Stap 1: We hebben een extra Radar nodig om een ontwijkactie te starten en we moeten kunnen meten of de robot een goede actie gedaan heeft. Dit doen we met een collission sensor. De always sensor is nodig om de taak weer op te pakken na de ontwijkactie:
Stap 2: De ontwijkactie houdt in dat de robot, na het detecteren van een andere robot, een aantal seconden rondjes draait, weg loopt van de robot en daarna zijn taak weer oppakt. Dit definiëren we in een extra state:
Stap 3: Voeg extra game properties toe:
Stap 4: Het script moet ook onder handen genomen worden. Er moet een extra package geïmporteerd worden omdat we de robot een willekeurige waarde geven voor het draaien om zijn eigen as en het weglopen van de radar locatie:
# import necessary packages
import bge
import GameLogic
import re
import random
De extra sensors moeten gedefinieerd worden:
# get sensors and actuators
start_sens = cont.sensors["Start"]
shelf_sens = cont.sensors["Shelf"]
track_act = cont.actuators["TrackTo"]
robot_radar = cont.sensors["RobotRadar"]
robot_collission = cont.sensors["RobotCollission"]
Wanneer de robot voor de eerste keer begint met de order, worden de parameters gedefinieerd die te maken hebben met het ontwijken van de andere robot:
if start_sens.positive:
cont.activate(track_act)
print("Start new order:")
get_order()
# set avoidance parameters and evaluation variables
print("begin")
if robot["active"] == False:
robot["sec_rot"] = random.uniform(0,2)
robot["sec_walk"] = random.uniform(0,2)
robot["active"] =True
Wanneer een robot iemand op de radar heeft, moet de robot de ontwijkactie uit gaan voeren in de andere state. Ook moet bijgehouden worden of de robot alsnog tegen een ander aan stoot. Wanneer de robot een slechte ontwijkactie heeft en vaak tegen iemand op botst, willen we dat deze robot een nieuwe ontwijkreactie krijgt. Dit noemen we een “reset”. Dit is nodig als de robot ergens vast zit:
if robot_radar.positive == True:
cont.deactivate(track_act)
robot["radar"] +=1
robot["time"] = 0.0
robot.state = 2
if robot_collission.positive:
robot["collission"] +=1
if robot["collission"] >=20:
#reset parameters because its stuck
robot["sec_rot"] = random.uniform(0,2)
robot["sec_walk"] = random.uniform(0,2)
In de extra state hebben we gezien dat er nog een “avoiding.py” gemaakt moet worden. Deze ziet er als volgt uit:
# import necessary packages
import bge
import GameLogic
import re
# get the current controller
cont = bge.logic.getCurrentController()
robot = cont.owner
robot["robot_id"] = robot.name[6:]
# get sensors and actuators
rot_act = cont.actuators["Rotate"]
walk_act = cont.actuators["Walk"]
robot_collission2 = cont.sensors["RobotCollission2"]
robot_radar2 = cont.sensors["RobotRadar2"]
robot["time"] += 1/60
if robot["time"] < robot["sec_rot"]:
cont.activate(rot_act)
if robot["time"] > robot["sec_rot"] and robot["time"] < (robot["sec_rot"] + robot["sec_walk"]):
cont.deactivate(rot_act)
cont.activate(walk_act)
if robot["time"] > (robot["sec_rot"] + robot["sec_walk"]):
robot.state =1
if robot_collission2.positive:
robot["collission"] +=1
if robot_radar2.positive:
robot["radar"] +=1
robot["time"] = 0.0
Wanneer je de robots nu laat starten met het ophalen van bestellingen, zie je dat ze op elkaar reageren en hun ontwijkactie uitvoeren. De acties die ze uitvoeren zijn hetzelfde maar hoelang ze deze actie uit voeren kan verschillen. Sommige robots zullen beter kunnen ontwijken en sneller door kunnen gaan met hun bestelling dan andere robots. De robots die een goede ontwijkstrategie hebben kunnen hun strategie doorgeven aan de andere robots.
Optimaliseren van reactie
Wanneer de robots met hun eigen ontwijkingsstrategie orders gaan verzamelen, zul je verschillen zien in de aanpak. De ene robot draait lang een rondje terwijl de andere robot bijna gelijk weer verder gaat met de bestelling. Je wilt dat de robots van elkaar leren en de beste strategieën nadoen zodat ze totaal zo min mogelijk botsen. Botsen kan er namelijk voor zorgen dat de robot kapot gaat of dat een artikel op de grond valt. Omdat we 6 robots hebben beginnen we met 6 verschillende strategieën (ieder tweetal met het aantal seconden rondjes draaien en rechtdoor lopen is een strategie). Na een bepaalde tijd moeten we weten hoe goed de strategie van iedere robot is zodat de goede robots dit door kunnen geven aan de andere robots. Het uitwisselen van deze informatie en het veranderen van de strategie gebeurd in de centrale controller op de volgende manier:
Stap 1: Importeer extra package numpy:
# import necessary packages
import bge
import GameLogic
import random
import numpy
Stap 2: Voeg een extra team toe met robot 6 en 7. Je weet van de vorige les al hoe dit moet.
Stap 3: Voeg een extra tijd meting toe om de strategieën te kunnen updaten:
central["time"]+= 1/60
central["time_minute"]+= 1/60
Stap 4: Per minuut gaan we de strategieën van de robots herzien en veranderen op de volgende manier:
if central["time_minute"] >=60:
central["time_minute"] = 0
robots = ["Robot_6", "Robot_7", "Robot_8", "Robot_9", "Robot_10", "Robot_11"]
result = [[0,0,0,""] for i in range(len(robots))]
alg_result = 0
for i in range(len(robots)):
robot = scene.objects[robots[i]]
result[i][0] = robot["collission"]
result[i][1] = robot["sec_rot"]
result[i][2] = robot["sec_walk"]
result[i][3] = robot
alg_result += robot["collission"]
robot["collission"] = 0
robot["radar"]=0
central["algorithm"].append(alg_result)
# print number of collissions per minute
print(central["algorithm"])
result = sorted(result, key=lambda a_entry: a_entry[0])
print(result)
print("Total walked distance:")
print(central["distance"])
print(central["time"])
# update values
for i in range(len(robots)):
robot = scene.objects[robots[i]]
take_from = random.choice([0,1])
robot["sec_rot"] = max(0,result[take_from][1] + random.gauss(0, 0.1))
robot["sec_walk"] = max(0,result[take_from][2] + random.gauss(0, 0.1))
Wat we in deze functie doen is het evalueren van de prestatie van de robots. Met prestatie bedoelen we het aantal keer opbotsen tegen de andere robots. Hoe minder dit gebeurd, hoe beter. Voor iedere robot wordt dit aantal opgeslagen samen met de twee waarden die hiervoor zorgden. Deze prestatie wordt opgeteld bij het algehele resultaat van de groep en de waarden worden weer op 0 gezet voor de volgende minuut. Vervolgens worden de strategieën van de robots aangepast. We nemen de strategieën van de 2 beste robots, de robots met het minste aantal botsingen, om door te geven aan de anderen. Op deze waarden passen we nog een kleine variatie toe om te kijken of we in de volgende minuut nog een betere strategie tegen gaan komen. Zo kunnen de robots blijven leren.
Experimenten
Wanneer je de robots nu aan het werk zet zie je per minuut een update van de prestatie van het algoritme uitgeprint in de Terminal. Het is moeilijk te analyseren of deze methode echt werkt als je alleen naar deze geprinte waarden. Je kan de robots aan het werk zetten en een aantal boxen laten vullen en deze cijfers in de gaten houden maar je kan er pas echt wat over zeggen als je een goed experiment uitvoert. Dit gaan we doen in Les 7.
Plusopdracht
Denk alvast na over hoe je kan testen of dit algoritme werkt.