среда, 29 августа 2018 г.

Муравей учится ходить

Наш проект продолжает серию проектов, связанных с простейшими нейронными сетями и снова связан с самообучением. Робот-муравей ANTY, собранный по инструкции из книги Laurens Valk "The LEGO MINDSTORMS EV3 Discovery Book" будет обучаться ходить, не имея изначально ни малейшего представления о том, как это делать. Нейронная сеть робота будет оценивать попытки робота-муравья двигаться, оценивая их с точки зрения скорости движения и сохранения его направления и выстроит в итоге оптимальную взаимосвязь между движениями ног.


Когда мы идем, делаем шаг, другой, то не задумываемся над тем, как синхронизированы наши ноги, какое движение в каждый момент времени должна выполнять одна нога по отношению к другой. Дело в том, что нейронные связи, ответственные за ходьбу у нас давно сформировались и постоянно подкрепляются ежедневными тренировками. Маленький ребенок, который еще только учится ходить находится на этапе формирования таких нейронных связей, он делает шаг за шагом, падает, поднимается, учитывает свои ошибки и снова пробует идти. Несомненно огромную роль в формировании таких взаимосвязей играет его наблюдение за тем, как ходят те, кто уже это умеет, однако научиться ходить он может и без этой визуальной информации.
Давайте попробуем поставить себя на его место. Встаньте на месте и попытайтесь на время забыть, как это - идти. Формально мы знаем, что когда одна наша нога делает шаг, вторая выполняет роль опоры, одновременно немного сгибаясь как пружина, готовая повторить движение другой ноги. Попробуйте сделать несинхронизированные друг с другом шаги, например когда одна нога делает шаг и сделала его на 50% (находится в воздухе), начните такое же движение другой ногой. Скорее всего за такой попыткой последует падение или движение, направленное на то, чтобы его избежать. Варьюируя степень синхронизации ног (0..100%) можно убедиться, что часть таких попыток будет приводить к "топтанию на месте", часть - к движению с поворотом в одну из сторон и только один, узкий диапазон синхронизации ног, приведет к прямолинейной ходьбе.


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


Нейронная сеть робота-муравья, которую мы будем обучать в этом проекте, будет действовать по следующему алгоритму:
  • Пара моторов, которые управляют движениями ног робота-муравья могут быть рассинхронизированы в диапазоне 0..360 градусов. Разобьем этот интервал на 36 отрезков. Это мы сделаем только из экономии времени на обучение сети. можно работать и с 360 различными углами.
  • Каждый из 36 возможных углов между ходовыми моторами свяжем с нейроном сети. Так как изначально нейронная сеть не обучена, каждый из вариантов синхронизации ног равноценен. Запишем в каждый нейрон начальный вес - 1.
  • Цикл обучения нейронной сети начинается со случайного выбора нейрона с учетом его веса.
  • Выбрав нейрон, синхронизируем ноги робота-муравья в соответствии с углом, связанным с выбранным нейроном
  • После синхронизации запоминаем расстояние до препятствия впереди и угол разворота на гироскопе
  • Делаем несколько шагов вперед
  • Оцениваем, изменился ли угол, если он изменился несущественно (робот не отклонился от курса), проверяем сократилось ли расстояние до препятствия и насколько
  • Если робот продвинулся вперед - увеличиваем вес нейрона 
  • Если робот не продвинулся вперед, то есть топчется на месте или пошел назад - уменьшаем вес нейрона, однако не менее чем до 1
  • Повторяем цикл обучения до тех пор, пока робот не будет выполнять шаги правильно и двигаться прямолинейно
Программу для робота мы написали на языке Python, выглядит она следующим образом:

Исходный код:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFont
from ev3dev.ev3 import *
from time import sleep
import random
motorC = LargeMotor('outC')
motorB = LargeMotor('outB')
btn = Button()
summ=0
rand=0
stopc=0
stopb=0
motorB.reset()
motorC.reset()
maxn=0
def PID(B,C):
    trig=0
    triger=0
    es1 = 0
    es2 = 0
    u1 = 0
    u2 = 0
    Pk = 10
    PD = 20

    stopB = 0
    stopC = 0
    
    while(stopB!=1 or stopC!=1):
        if  motorB.position>=0:
            e1 = motorB.position%360 - B            
        else:
            e1 = -1*(abs(motorB.position)%360) - B
            
        if  motorC.position>=0:
            e2 = motorC.position%360 - C
        else:
            e2 = -1*(abs(motorC.position)%360) - C
             
        u1 = e1 * Pk + PD * (e1 - es1)
        u2 = e2 * Pk + PD * (e2 - es2)
        
        if(u1 > 900): u1 = 900
        if(u2 > 900): u2 = 900
        if(u1 < -900): u1 = -900
        if(u2 < -900): u2 = -900
        if abs(e1) <= 5:
            motorB.stop(stop_action='brake')
            stopB=1
        else:
            motorB.run_forever(speed_sp=u1*(-1))
      
        if abs(e2) <= 5:
            motorC.stop(stop_action='brake')
            stopC=1
        else:
            motorC.run_forever(speed_sp=u2*(-1))
        es1 = e1
        es2 = e2

gy = GyroSensor('in2') 
ir = InfraredSensor('in3') 
ir.mode = 'IR-PROX' 
motorC = LargeMotor('outC') 
motorB = LargeMotor('outB') 
ts = TouchSensor('in1')

brain = []
for i in range(36):
    brain.append(1)
while True:
    summ=0
    for i in range(36):
        summ += brain[i]
    rand = random.randint(0,summ*1000)/1000
    summ = 0
    for i in range(36):
       
        if summ < rand < summ + brain[i]:
            neuron = i
            break
        summ+=brain[i]
    sleep(1)

    PID(0,neuron*10)    
    inf=ir.value()
    gir=gy.value()
    sleep(1) 
    b=motorB.position
    c=motorC.position

    if(ir.value()>=10 and ir.value()<45):
        motorB.run_forever(speed_sp=200)
        motorC.run_forever(speed_sp=200)
        stopb=0
        stopc=0   
        while stopc==0 or stopb==0:
    
            if motorB.position>=b+360:
                stopb=1
            if motorC.position>=c+360:
                stopc=1
        motorB.stop(stop_action='brake')        
        motorC.stop(stop_action='brake')    
        if abs(gy.value()-gir)<=10:
             if  ir.value()-int(inf/2)>2:
                 brain[neuron]+=1
        else:
            brain[neuron]-=1
        if brain[neuron]<1:
            brain[neuron]=1
        print(neuron, brain[neuron]) 
        maxn=0
        max=0
        for i in range(len(brain)):
            if brain[i]>max:
                max=brain[i]
                maxn=i
        lcd = Screen()
        f =ImageFont.truetype('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', 75)
        lcd.draw.text((3,0), str(maxn), font=f)

        lcd.update()
    elif ir.value() <10:        
        motorB.run_forever(speed_sp= -400)
        motorC.run_forever(speed_sp= -400)
        stopb=0
        stopc=0
        while stopc==0 or stopb==0:    
            if motorB.position<=b-360:
                stopb=1
            if motorC.position<=c-360:
                stopc=1
        print("stop")
        motorB.stop(stop_action='brake')
        motorC.stop(stop_action='brake')
        if abs(gy.value()-gir)<=5:
            if ir.value()-int(inf/2):
                brain[neuron]+=1
        else:
            brain[neuron]-=3
        if brain[neuron]<1:
            brain[neuron]=1
        print(neuron, brain[neuron])
        maxn=0
        max=0
        for i in range(len(brain)):
            if brain[i]>max:
                max=brain[i]
                maxn=i
        lcd = Screen()
        f =ImageFont.truetype('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', 75)
        lcd.draw.text((3,0), str(maxn), font=f)

        lcd.update()

    if ir.value()>45:
      motorB.run_forever(speed_sp=300)
      motorC.run_forever(speed_sp=300)
    
    if(btn.backspace):
      motorB.stop()
      motorC.stop()
      exit()
    if(btn.enter):
      motorB.stop()
      motorC.stop()
      break
      
sleep(5)
PID(0,maxn*10)
while (not (btn.enter)): pass
      
print('dimonstracia')
for i in range(len(brain)):
    print(i,brain[i])
motorB.run_forever(speed_sp=200)
motorC.run_forever(speed_sp=200)
while (not (btn.enter)): pass
motorB.run_forever(speed_sp=-200)
motorC.run_forever(speed_sp=-200)

while (not (btn.backspace)):
    pass
motorB.stop()
motorC.stop()
exit()


Самое популярное