Показаны сообщения с ярлыком нейросети. Показать все сообщения
Показаны сообщения с ярлыком нейросети. Показать все сообщения

воскресенье, 1 октября 2017 г.

НейроБашня

В 94-м проекте мы построим робота, который будет играть с нами в известную игру "11 палочек". В этот раз мы снова коснемся темы нейросетевых технологий. Нейронная сеть робота сможет обучаться как в результате игры с человеком, реагируя на его стратегию, так и в результате "игры в уме", играя в памяти с себе подобной сетью.

Суть игры такова: в кучке 11 палочек, два игрока ходят по очереди. За каждый ход можно взять 1 или 2 палочки. Выигрывает тот, кто взял последнюю палочку.

Вместо палочек наш робот будет работать с лего-кубиками. 11 блоков из кубиков, сложенных друг на друга, образуют высокую башню, что и дало название нашему проекту, "НейроБашня".



В этот раз мы построили конструкцию из старого доброго LEGO Mindstorms NXT. Это связано с тем, что параллельно с Python мы изучаем C/C++, а для платформы NXT есть замечательная свободно распространяемая среда разработки BricxCC, Си-компилятором в которой мы и воспользуемся для программирования робота.

Инструкцию по сборке нашего робота в формате LEGO Digital Designer можно скачать по ссылке. В конструкции мы используем средний EV3-мотор в силу его компактности, однако вам ничего не помешает использовать NXT-мотор, изменив способ его крепления, изменений в коде это не потребует.


Робот может работать в двух режимах:

  • Игра на двоих. В этом режиме играют два человека. В башню заряжено 11 лего-кубиков. Игроки по очереди нажимают 1 или 2 раза на кнопку со своей стороны, забирая себе 1 или 2 кубика. Выигрывает игрок, который забрал последний кубик.
  • Игра человека против нейронной сети робота. В этом режиме игрок использует правую кнопку, за левого игрока думает робот.
Для объяснения алгоритма обучения нейронной сети робота давайте снова воспользуемся терминологией коробков и камушков, как доступной для пониманию робототехниками любого возраста.

Так как у нашего робота 11 состояний (по количеству оставшихся кубиков в башне), представим 11 коробков, в каждом из которых в начале игры по 1 камню белого и черного цветов. Когда наступает ход нейронной сети, она "смотрит" сколько кубиков осталось в башне и выбирает соответствующий коробок, затем наугад вытаскивает из него камень. Если он белый - она берет 2 кубика, если черный, то 1 кубик, при этом запоминая сколько она взяла в каждый ход. После того как камень вытащен и его цвет определен, он помещается назад в коробок.
По окончанию игры наступает пора поощрений и наказаний:

  • Если нейронная сеть выиграла, она добавляет в каждый задействованный ею коробок камень того цвета, который она вытащила, делая соответствующий ход. 
  • Если нейронная сеть проиграла, то она убирает из последнего задействованного ею коробка камень того цвета, который она вытащила. При этом, если камень этого цвета последний, то он остается в коробке.
Этот способ был опубликован в серии книг «Берсеркер» (издавались в рамках «Боевая фантастика») Фреда Саберхагена в 1995 году. По сюжету, главный герой должен был замаскировать своё отсутствие на космокорабле. Он обучил методу «спичечных коробков» свою мартышку, которая и имитировала игру с жутким космическим монстром. 
Ранее, в 70-x годах задача «Самообучающаяся машина из спичечных коробков» была опубликована известным математиком Мартином Гарднером в книге "Математические досуги".

В самом начале игры наша нейронная сеть не обучена, ее ходы случайны, как как в каждом "коробке" у нее 50% белых и 50% черных камней. Каждый такой коробок - это нейрон сети.
По мере игры с человеком нейронная сеть набирается опыта, подстраивается под стратегии игрока, не давая реализовывать ему выигрышные комбинации. Такой обучение эффективно в плане выстраивания качественных взаимосвязей между нейронами сети, так как сеть играет с думающим соперником, однако длится оно может очень долго. Сеть начнет играть на уровне игрока только через несколько часов игры. Мы реализовали такой метод обучения нейронной сети самым первым, но использование только его нас не устроило в силу трудоемкости обучения робота.
Второй способ обучения, который мы попробовали, стало обучение игре с виртуальным игроком, делающим случайные ходы. Параллельно игре с человеком в памяти робота проводятся непрерывная последовательность игр нейронной сети, которую нужно обучить, с генератором чисел 1 и 2. Этот способ малоэффективен в силу того, что генератор "не думает", однако из-за того что таких игр проводится много (более сотни каждую секунду), сумма различных игровых ситуаций и исходов игр в итоге приводят к образованию связей, необходимых для выигрыша нейронной сети. Такая сеть начинает играть сильнее человека через полчаса, но нас результат снова не устроил и мы двинулись дальше.
Третьим способом, который мы применили, явилось создание еще одной нейронной сети и проведение непрерывной последовательности фоновых игр между сетями. Обе сети обучаются одновременно, по каждый ведется статистика побед.. Этот способ оказался самым эффективным, правда удвоенная вычислительная нагрузка снизила количество фоновых игр (теперь их 70-80 в секунду), однако качество обучения заметно выросло.

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

Интересно, что в процессе обучения явный перевес получает сеть, которая ходит первой. Если право первого хода отдавать всегда одной и той же нейронной сети, то она будет "заточена" под победу в играх, где она ходит первой, в то время как вторая сеть будет сильно играть в партиях, где она ходит второй. Этим фактом мы воспользовались, и теперь, в играх, где право первого хода выпадает человеку, он играет со второй сетью, а в партиях, где человек ходит вторым, играет первая нейросеть.



Исходный код программы приведен ниже. Скачать программу можно по ссылке. Мотор подключается в порт А, датчики кнопки - в порты 1 и 2. Чтобы озвучить игры мы записали набор звуков в формате RSO, которые нужно поместить в память робота, их вы тоже найдете по ссылке.

//время ожидания второго нажатия
long timeToPress=0;

//переменные с количеством выигрышей нейросетей
long winNN1=0;
long winNN2=0;

//количество игр NN против NN (здесь и далее NN - нейросеть)
long game=0;

//сколько осталось кубиков в игре с человеком
int box1 = 0;
//сколько осталось кубиков в игре NN с NN
int box2=0;

//win это переменная обозначает чей-то выйгрыш
//win2 это тоже самое, только в подпроцессе  learn
int win1=0;
int win2=0;

//7 строк обозначают 7 строк экрана, мы в них храним то, что надо вывести
string line1,line2,line3,line4,line5,line6,line7;

//buttonPress это количество нажатий на кнопку
int buttonPress=0;

//ошибка и старая ошибка PD-регулятора
int ERR = 0;
int ERRo=0;

//управляющее воздействие
int u = 10;

//коФф-ты PD регулятора
float P = 0.5;
float D = 1.5;

//точка, которой мы хотим достичь при повороте мотора
int target=0;

//время для поворота влево или вправо
long timeToMove=0;

//два массива пуговиц NN1
int white1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int black1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

//два массива пуговиц NN2
int white2[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int black2[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

//три временных массива 2 для learn и 1 для main
int tmp1[11];
int tmp2[11];
int tmp[11];

//блок для поворота по регулятору налево
sub Left (int raz)
{
  //устонавливаем точку, которой хотим достичь
  target=MotorRotationCount(OUT_A)+360*raz;

  //засекаем время
  timeToMove=CurrentTick();

  //цикл, работоющий пока мы не достигнем точки или времени
  while(abs(MotorRotationCount(OUT_A)-target)>5 && CurrentTick()-timeToMove < 1000){
    //с помощью регулятора подаем нагрузку на мотор
    ERR=target-MotorRotationCount(OUT_A);
    u=P*ERR+D*(ERR-ERRo);
    if(u>100)u=100;
    if(u<-100)u=-100;
    OnFwd(OUT_A,u);
    ERRo=ERR;
  }

  //выключаем мотор
  Off(OUT_A);
}

//блок для поворота по регулятору направо
sub Right(int raz)
{
  //устанавливаем точку, которой хотим достичь
  timeToMove=CurrentTick();
  
  //засекаем время
  target=MotorRotationCount(OUT_A)-360*raz;

  //цикл, работающий пока мы не достигнем точки или времени
  while(abs(MotorRotationCount(OUT_A)-target)>5 && CurrentTick()-timeToMove < 1000){
    //с помощью регулятора подаем нагрузку на мотор
    ERR=target-MotorRotationCount(OUT_A);
    u=P*ERR+D*(ERR-ERRo);
    if(u>100)u=100;
    if(u<-100)u=-100;
    OnFwd(OUT_A,u);
    ERRo=ERR;
  }

  //выключаем мотор
  Off(OUT_A);
}

//функция для подсчета нажатий на кнопки
int touch(int sensor) {
  //переменная, в которой мы храним количевство нажатий
  int countPress=0;
  //условие, проверяющее на каком датчике ждать нажатия
  if(sensor==1){
    //ожидание первого нажатия
    while(Sensor(S1)==0){
      Wait(1);
    }
    //защина от шума
    Wait(100);
    //ожидаем отжатия
    while(Sensor(S1)!=0);
    long timeToPress=CurrentTick();
    //цикл, ожидающий второго нажатия заданое время
    while(Sensor(S1)==0 && CurrentTick()-timeToPress < 1000);
    //если вылетели не по времени, а по нажатию
    if(CurrentTick()-timeToPress < 1000) {
      //зашита от шума
      Wait(100);
      //устанавливаем количество нажатий в 2(0 - одно нажитие, 1 - два нажатие)
      countPress=1;
      //ожидаем отжатия
      while(Sensor(S1)!=0);
    }
    //возвращаем значение
    return countPress+1;
  }
  //если проверяется второй датчик
  if(sensor==2){
    //ожидаем нажатия
    while(Sensor(S2)==0){
      Wait(1);
    }
    //защита от шума
    Wait(100);
    //ожидание отжатия
    while(Sensor(S2)!=0);
    long timeToPress=CurrentTick();
    //цикл, ожидающий второго нажатия заданое время
    while(Sensor(S2)==0 && CurrentTick()-timeToPress < 1000);
    //если вылетели не по времени, а по нажатию
    if(CurrentTick()-timeToPress < 1000) {
      //защита от шума
      Wait(100);
      //устанавливаем количество нажатий в 2
      countPress=1;
      //ожидаем отжатия
      while(Sensor(S2)!=0);
    }
    //возвращаем значение
    return countPress+1;
  }

// параллельный процесс для обучения нейронных сетей
}
task learn(){
  Wait(2000);
  //говорим "начало обучения нейронной сети"
  PlayFile("start.rso");
  Wait(2000);
  //запоминаем время
  long timeToLearn = CurrentTick();
  //главный цикл
  while(true)
  {
    //обнуляем tmp1 и tmp2
    for(int i=0;i<=10;i++){
      tmp1[i]=0;
      tmp2[i]=0;
    }
    //если прошла секунда? (каждую секунду)
    if(CurrentTick()-timeToLearn>=1000){
      //вывод на экран  количевства игр и выигрышей
      line1=NumToStr(game)+" "+NumToStr(winNN1)+" "+NumToStr(winNN2);
      //вывод на экран выигрывающей нейросети
      if(winNN1>=winNN2){
        line2="HC1";
      }
      else{
        line2="NC2";
      }
      //вывод строк
      ClearScreen();
      TextOut(0,LCD_LINE1,line1);
      TextOut(0,LCD_LINE2,line2);
      TextOut(0,LCD_LINE3,line3);
      TextOut(0,LCD_LINE4,line4);
      TextOut(0,LCD_LINE5,line5);
      TextOut(0,LCD_LINE6,line6);
      TextOut(0,LCD_LINE7,line7);
      //очистка строк
      line1 = "";
      line2 = "";
      line4 = "";
      line6 = "";
      line7 = "";
      //перезаписываем время
      timeToLearn = CurrentTick();
    }
    //увеличиваем число игр
    game++;
    //переменная кто выиграл
    win2=0;
    //сколько кубиков осталось
    box2=11;

    //цикл игры
    while(true){

      //ход НС2
      //вытягиваем камень
      if(Random(100) < white2[box2-1]*1.0/(white2[box2-1]+black2[box2-1])*100.0){
        //запоминаем какой использовали коробок
        tmp2[box2-1]=2;
        //уменьшаем количество оставшихся кубиков в кучке
        box2-=2;
      }
      else{
        //запоминаем какой использовали коробок
        tmp2[box2-1]=1;
        //уменьшаем количество оставшихся кубиков в кучке
        box2-=1;
      }
      //проверка на выигрыш
      if(box2<=2){
        win2=1;
        break;
      }

      //ход НС1
      //вытягиваем камень
      if(Random(100) < white1[box2-1]*1.0/(white1[box2-1]+black1[box2-1])*100.0){
        //запоминаем какой использовали коробок
        tmp1[box2-1]=2;
        //уменьшаем количество оставшихся кубиков в кучке
        box2-=2;
      }
      else{
        //запоминаем какой использовали коробок
        tmp1[box2-1]=1;
        //уменьшаем количество оставшихся кубиков в кучке
        box2-=1;
      }
      //проверка на выигрыш
      if(box2<=2){
        win2=2;
        break;
      }

    }
    //поощрение-наказание
    //NN1 выиграла
    if(win2==1){
      winNN1++;
      for(int i = 0;i<=10;i++){
        if(tmp1[i]==1) black1[i]++;
        if(tmp1[i]==2) white1[i]++;
      }
    }
    else{
      for(int i = 0;i<=10;i++){
        if (tmp1[i] > 0)
        {
          if(tmp1[i]==1 && black1[i]>1) black1[i]--;
          if(tmp1[i]==2 && white1[i]>1) white1[i]--;
          break;
        }
      }
    }
    //NN2
    if(win2==2){
      winNN2++;
      for(int i = 0;i<=10;i++){
        if(tmp2[i]==1) black2[i]++;
        if(tmp2[i]==2) white2[i]++;
      }
    }
    else{
      for(int i = 0;i<=10;i++){
        if (tmp2[i] > 0)
        {
          if(tmp2[i]==1 && black2[i]>1) black2[i]--;
          if(tmp2[i]==2 && white2[i]>1) white2[i]--;
          break;
        }
      }
    }
  }
}

task main()
{
  //подключаем датчики
  SetSensorTouch(IN_1);
  SetSensorTouch(IN_2);
  ClearScreen();
  line1 = "";
  line2 = "";
  line3 = "";
  line4 = "";
  line5 = "";
  line6 = "";
  line7 = "";

  //делаем случайные числа действительно случайными
  for(int i=0; i<=CurrentTick()%1000; i++) { int tmp_r = Random(); }

  //сбрасываем tmp
  for(int i=0;i<=10;i++){
    tmp1[i]=0;
    tmp2[i]=0;
    tmp[i]= 0;
  }
  
  //запуск параллельной задачи learn для обучения нейросетей
  start learn;


  //Ожидание нажатия на кнопки
  line5="left 2 right 1";
  while (!ButtonPressed(BTNRIGHT, true) && !ButtonPressed(BTNLEFT, true));
  line5 = "";
  // если нажат левый датчик, игра человек-человек
  if(ButtonPressed(BTNLEFT, true))
  {
    //бесконечный цикл игры
    while(true)
    {
      //сброс tmp
      for(int i=0;i<=10;i++){
        tmp[i]= 0;
      }
      //Кубиков 11
      box1=11;
      while(true)
      {
        //ход игрока1
        buttonPress=touch(2);
        //выкидываем столько кубиков, сколько нажатий
        Left(buttonPress);
        //запоминаем сколько выкинули
        box1-=buttonPress;

        line4=NumToStr(box1);
        //проверка на выигрыш
        if(box1<=2){
          win1=1;
          line3="Player1 Win";
          break;
        }
        //ход игрока2
        buttonPress=touch(1);
        //выкидываем столько кубиков, сколько нажатий
        Right(buttonPress);
        //запоминаем сколько выкинули
        box1-=buttonPress;

        line4=NumToStr(box1);
        if(box1<=2){
          //проверка на выигрыш
          win1=2;
          line3="Player Win";
          break;
        }
      }
      //ожидания нажатия для новой игры(загрузка кубиков)
      while (!ButtonPressed(BTNCENTER, true));
      line3 = "";
    }
  }

  //если нажат правый датчик, игра человек-NN
  if(ButtonPressed(BTNRIGHT, true))
  {
    while(true)
    {
      //переменная кто выйграл
      win1=0;
      //сколько кубиков осталось
      box1=11;
      //рандомно определям первый ход
      if (Random(100)>50)
      {
        //первый ходит человек
        while(true){
          //ход игрока
          PlayFile("playerh.rso");
          Wait(1000);
          buttonPress=touch(2);
          if(buttonPress==1) {
            PlayFile("player1.rso");
            Wait(2000);
          }
          else{
            PlayFile("player2.rso");
            Wait(2000);
          }
          //выкидываем кубики по количеству нажатий
          Left(buttonPress);
          box1-=buttonPress;
          line4=NumToStr(box1);

          //проверка на выигрыш
          if(box1<=2){
            PlayFile("networkw.rso");
            Wait(2000);
            win1=1;
            line3="Robot Win";
            break;
          }
          PlayFile("networkh.rso");
          Wait(2000);
          PlayFile("networkd.rso");
          Wait(Random(10000)+2000);
          
          //ход НС
          if(Random(100) < white1[box1-1]*1.0/(white1[box1-1]+black1[box1-1])*100.0){
            PlayFile("network2.rso");
            Wait(2000);
            //берёт 2 кубика
            Right(2);
            box1-=2;
          }
          else{
            PlayFile("network1.rso");
            Wait(2000);
            //берёт 1 кубик
            Right(1);
            box1-=1;
          }

          line4=NumToStr(box1);
          
          //проверка на выигрыш
          if(box1<=2){
            PlayFile("playerw.rso");
            Wait(2000);
            win1=2;
            line3="Player Win";
            break;
          }
        }
      }
      // если первой ходит NN
      else
      {
        while(true){
          //ход НС
          if(Random(100) < white2[box1-1]*1.0/(white2[box1-1]+black2[box1-1])*100.0){
            PlayFile("network2.rso");
            Wait(2000);
            //берёт 2 кубика
            Right(2);
            box1-=2;
          }
          else{
            PlayFile("network1.rso");
            Wait(2000);
            // берёт 1 кубик
            Right(1);
            box1-=1;
          }

          line4=NumToStr(box1);
          // проверка на выигрыш
          if(box1<=2){
            PlayFile("playerw.rso");
            Wait(2000);
            win1=2;
            line3="Player Win";
            break;
          }

          //ход игрока
          PlayFile("playerh.rso");
          Wait(1000);
          buttonPress=touch(2);
          if(buttonPress==1) {
            PlayFile("player1.rso");
            Wait(2000);
          }
          else{
            PlayFile("player2.rso");
            Wait(2000);
          }
          // выкидываем по количеству нажатий
          Left(buttonPress);
          box1-=buttonPress;
          line4=NumToStr(box1);

          //проверка на выигрыш
          if(box1<=2){
            PlayFile("networkw.rso");
            Wait(2000);
            win1=1;
            line3="Robot Win";
            break;
          }
          PlayFile("networkh.rso");
          Wait(2000);
          PlayFile("networkd.rso");
          Wait(Random(10000)+2000);
        }
      }

      // одидания нажатия для продолжения игры
      PlayFile("networkl.rso");
      Wait(3000);
      PlayFile("next.rso");
      Wait(2000);
      while (!ButtonPressed(BTNCENTER, true));
        line3 = "";
      }
    }
  }
}



воскресенье, 17 сентября 2017 г.

НейроКачели

Почти сотня роботов, которых мы построили в рамках нашего проекта, до сих пор действовали строго по заложенным в них алгоритмам. Конечно, со временем и благодаря наработанному опыту поведение наших роботов становилось все сложнее, стало все более напоминать поведение разумных машин, но весь этот разум - зачастую витиеватые взаимосвязи условных конструкций и структур данных - не позволяли им выйти за рамки заложенных в программы реакций на те или иные события. Иными словами - изменились условия, роботу нужно действовать по другому чтобы сохранить целевой результат - извольте, правьте код, выстраивайте взаимосвязи, описывайте их в условных конструкциях - робот послушно их выполнит.
Бывают задачи, в которых эти взаимосвязи не так-то просто выстроить и заложить в код или они могут быть вообще не известны. Давайте рассмотрим одну из таких задач, упростив ее решение настолько, насколько это возможно с тем, чтобы уловить суть непривычных пока для нас алгоритмов.


Наверное все мы в детстве любили качаться на качелях. Как вы думаете, если ребенка впервые посадить на качели и не показать ему как на них раскачиваться (и не давать соответственно подсмотреть как это делают другие), получится у него их раскачать, сохранять и контролировать скорость раскачивания? В его мозге нет устойчивых взаимосвязей и образов того как ему это сделать. Он начнет пробовать совершать произвольные действия, если качели начнут раскачиваться в мозг пойдет визуальная и вестибулярная обратная связь и по тем действиям, которые он только что предпринимал, начнут формироваться новые нейронные цепочки, связанные с "раскачать качели". Если какие то действия наоборот начнут тормозить раскачавшиеся качели, например ребенок отклонился не в ту сторону - он "запомнит" что так делать не нужно, соответствующие связи будут ослаблены или созданы новые.
Похожим образом работает и искусственная нейросеть, которую мы создадим в нашем сегодняшнем проекте.
Для начала соберем конструкцию. Качели мы построили на базе LEGO Mindstorms EV3, использовав несколько дополнительных деталей из ресурсных наборов. Инструкцию по сборке наиболее сложной части конструкции -  качающихся человечков - в формате LEGO Digital Designer вы можете скачать по ссылке. Остальную часть качелей каждый без труда сможет достроить из тех деталей, которые будут у него под рукой.


Человечки из больших EV3-моторов способны наклоняться, смещая центр тяжести качели. Качели закреплены на раме свободно, поэтому важно чтобы они были как можно более симметричными. Для робота потребуется гироскоп, на основе его показаний будет происходить самообучение нейронной сети.

Посмотрим на качели сбоку. изначально человечки "не знают", в какую сторону им нужно отклоняться (влево или вправо) чтобы качели начали раскачиваться. Хороший способ объяснять вероятности их наклона в ту или иную сторону  используя терминологию спичечных коробков и камушков черного и белого цвета. Пусть у нас есть два коробка, это два нейрона нашей нейронной сети. в каждом из них в случае необученной сети, лежит по 1 камушка каждого цвета. Таким образом  вытащить камушек белого и черного цвета случайным образом можно с вероятностью 50%, соответственно не обученная сеть с равной вероятностью будет отклонять человечков влево и вправо в любой ситуации.
Первый коробок соответствует состоянию когда качели качаются влево, второй - вправо. Если "вытащили" белый камень, человечкам нужно отклониться влево, если черный - вправо. Камень после того как мы его вытащили помещается на место, где он и был.

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

Алгоритм таков:
1) Смотрим куда качаются качели (по данным гироскопа) - влево или вправо и выбираем коробок - первый или второй. Следует заметить что мы не рассматриваем ситуацию когда качели имеют нулевую скорость, например при старте, развивая алгоритм дальше нужно добавить третью коробку с камушками, но пока обойдемся без нее.
2) Вытаскиваем наугад камень из выбранной коробки. Смотрим его цвет и кладем обратно. если камень белый - даем человечкам команду отклониться влево, если черный - вправо.
3) Оцениваем скорость качелей. Если она выросла - кладем в коробку, из которой мы брали камень еще один камень такого же цвета. Если скорость упала - убираем из коробки камень то го цвета, который был вытащен. Следует заметить что последний камень каждого цвета лучше не убирать, пусть остается.
4) Переходим к п.1

В процессе обучения возможна интересная ситуация - неподвижные качели можно раскачать даже неправильными движениями, но только до какого-то предела, если попытки предпринимать и дальше, то они наоборот замедлят раскачивание. В реальной работе робота ситуация действительно проявляется довольно часто, робот раскачивается до какого-то предела (небольшого), но дальше процесс не идет. Дальнейшие попытки раскачать качели приводят к их полной остановке и далее процесс обучение начинается снова, уже в "верном" направлении. И заметьте, это все без вмешательства человека - вполне разумная машина!


Программировать качели мы будем на Python.  Давайте посмотрим код:

# импортируем модуль для работы с железом EV3
from ev3dev.ev3 import *
# импортируем модуль математики
from math import *
# импортируем модуль для работы со временем
from time import *
# импортируем модуль генератора случайных чисел
from random import random
# импортируем модуль многопоточности
import threading

# начальная скорость моторов ("человечков")
speedB = 0
speedC = 0

# текущая средняя скорость движения качелей
moving_average = 0
# предыдущая средняя скорость движения качелей
moving_average_old = 0

# Позиция человечков -100..100
pos = 0

# направление движения качелей
state_gyro = 0

# описание нейрона сети
class brain():
    def __init__(self):
        self.Black = 1
        self.White = 1

# создаем пару нейронов
BrainLeft = brain()
BrainRight = brain()

# объект для отслеживания кнопок на блоке
btn = Button()

# объекты для моторов
B = LargeMotor('outB')
C = LargeMotor('outC')

# объект для работы с гироскопом
gyro = GyroSensor("in4")

# устраняем дрейф гироскопа и ставим его в режим измерения скорости
gyro.mode = "GYRO-RATE"
gyro.mode = "GYRO-ANG"
gyro.mode = "GYRO-RATE"

# человечков - в исходную позицию sleep(1)
B.run_forever(speed_sp=80)
C.run_forever(speed_sp=80)
sleep(3)

B.stop(stop_action="brake")
C.stop(stop_action="brake")
sleep(1)

B.reset()
C.reset()

B.run_to_rel_pos(position_sp=-120, speed_sp=80)
C.run_to_rel_pos(position_sp=-120, speed_sp=80)
while any(C.state): sleep(0.1)

B.stop(stop_action="brake")
C.stop(stop_action="brake")

sleep(1)
B.reset()
C.reset()

# готов к работе!
Sound.speak('Ready').wait()

stop = False

# П-регулятор для управления человечками
def reg():
    while not stop:
        speedB = ((-1*pos)-B.position)*8
        speedC = (pos-C.position)*8

        if(speedB > 900): speedB = 900
        if(speedB < -900): speedB = -900
        if(speedC > 900): speedC = 900
        if(speedC < -900): speedC = -900

        B.run_forever(speed_sp=speedB)
        C.run_forever(speed_sp=speedC)

        sleep(0.01)

# Запускаем регулятор в параллельном потоке
t = threading.Thread(target=reg)
t.daemon = True
t.start()

# Максимальная достигнутая скорость
max_speed = 0

while not stop:

    # вылет по кнопке "назад" на блоке     
    stop = btn.backspace
    
    # скорость по показаниям гироскопа
    state_gyro_speed = gyro.value()
        
    if(state_gyro_speed != 0): 
        # текущее направление движения качелей
        state_gyro = state_gyro_speed/abs(state_gyro_speed)
        if(state_gyro > 0):
            # если влево, и вытащили черный камень
            if(random() <= (BrainLeft.Black/(BrainLeft.Black + BrainLeft.White))): pos = -100
            # если белый 
            else: pos = 100
        else:
            # если вправо, и вытащили черный камень
            if(random() <= (BrainRight.Black/(BrainRight.Black + BrainRight.White))): pos = -100
            # если белый
            else: pos = 100
            
    moving_average_old = moving_average
       
    # ждем перед оценкой
    sleep(0.25)

    moving_average = abs(state_gyro_speed)*0.1 + moving_average*0.9

    if(state_gyro > 0):
        # если скорость не упала при движении влево
        if(moving_average >= moving_average_old): 
            if(pos<0): BrainLeft.Black += 1
            else: BrainLeft.White += 1
        else:
            # если скорость упала при движении влево
            if(pos<0 and BrainLeft.Black > 1): BrainLeft.Black -= 1
            elif(pos>0 and BrainLeft.White > 1): BrainLeft.White -= 1

    elif(state_gyro < 0):
        # если скорость не упала при движении вправо
        if(moving_average >= moving_average_old): 
            if(pos<0): BrainRight.Black += 1
            else: BrainRight.White += 1
        else:
        # если скорость упала при движении вправо
            if(pos<0 and BrainRight.Black > 1): BrainRight.Black -= 1
            elif(pos>0 and BrainRight.White > 1): BrainRight.White -= 1
    # выводим состояние нейронной сети     print("[[ " + str(BrainLeft.Black) + ", " + str(BrainLeft.White) + "][ " + str(BrainRight.Black) + ", " + str(BrainRight.White) + "]]",moving_average)

    # если скорость выросла на 25, фиксируем новый рекорд    
    if moving_average > max_speed + 25:
        print("[[ " + str(BrainLeft.Black) + ", " + str(BrainLeft.White) + "][ " + str(BrainRight.Black) + ", " + str(BrainRight.White) + "]]",moving_average)
        max_speed = moving_average
        # если скорость выросла до 200 - обучение завершено
        if max_speed < 200:
            Sound.speak('New Record')
        else:
            Sound.speak('Learning Complete')

Sound.beep().wait() 
B.stop(stop_action="brake")
C.stop(stop_action="brake")

# завершение работы
Sound.speak('Stop').wait()


print("[[ " + str(BrainLeft.Black) + ", " + str(BrainLeft.White) + "][ " + str(BrainRight.Black) + ", " + str(BrainRight.White) + "]]")

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