Немного слов обо мне: мое хобби это робототехника. На данный момент экспериментирую с шагающим роботом на базе SunFounder PiCrawler.

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

В данной статье я постараюсь простыми словами объяснить, как можно применить ИИ для управления роботом, используя готовую библиотеку TensorFlow.

PiCrawler
PiCrawler

Что такое ИИ ?

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

Немного о роботе PiCrawler

Это девайс, приобретенный на AliExpress. Для его управления необходим мини компьютер Raspberry Pi 4. Робот имеет четыре ноги, плату расширения, подключаемую по i2c интерфейсу, камеру, ультразвуковой датчик и разные выходы.

Я подключил к этому роботу мини-гироскоп WT901 (можно любой другой), также приобретенный на китайском маркетплейсе и подключаемый по интерфейсу i2c. Имеется ПО для управления ногами.

Постановка задачи

В качестве примера я возьму следующую задачу управления роботом - придерживаться горизонтального положения в зависимости от угла наклона опорной поверхности.

Так как внутри робота имеется гироскоп, с него мы и будем считывать углы наклона x, y и уже на основе этих данных определять положение ног y1, y2, y3, y4,

где x, y, y1, y2, y3, y4 - числа.

API модели в качестве входных переменных использует Tensor-ы - массивы с числовыми значениями. На выходе аналогичная ситуация.

В нашем случае формат входных данных будет [x, y], а выходных [y1, y2, y3, y4]

В виде картинки эту модель можно представить следующим образом:

Пишем код модели TensorFlow

И так, что мы имеем:

  • Входные данные в формате [x, y]

  • Выходные данные в формате [y1, y2, y3, y4]

На основе этого можно уже построить простую layers-модель в Tensorflow - в качестве входных параметров массив двух элементов - это inputShape: [2], выходные данные - units: 4 что соответствует массиву из четырех элементов.

const tf = require("@tensorflow/tfjs-node");
const model = tf.sequential();

model.add(tf.layers.dense({ inputShape: [2], units: 32, activation: "relu" }));
model.add(tf.layers.dense({ units: 32, activation: "relu6" }));
model.add(tf.layers.dense({ units: 32, activation: "relu6" }));
model.add(tf.layers.dense({ units: 4, activation: "relu" }));

model.summary();
model.compile({
  optimizer: tf.train.sgd(0.0001),
  loss: "meanSquaredError",
});

И так, наша модель готова! Не просто ли?

Сбор тренировочных данных. Обучение модели.

Что такое обучение? Это процесс обработки различных переменных и их дальнейший анализ для выявления каких-либо закономерностей. Звучит немного запутанно, но давайте разберемся на нашем примере.

Какие данные мы будем собирать и обрабатывать? Это все те же параметры модели - углы наклона и координаты У для ног робота. Но при сборе данных мы действуем наоборот - устанавливаем соответствующие координаты ног робота и записываем углы наклона. Другими словами - наклоняем робот, изменяя его положения ног и запоминаем углы наклона. Тут важно проанализировать, как и в какую сторону его наклонять.

Давайте обусловимся, что мы имеем объекты:

  • gyroScope - для получения данных с гироскопа. Пусть будет иметь метод getData() который возвращает объект { X, Y } с углами наклона.

  • leftArm, rightArm, leftArmBack, rightArmBack - объекты с методом setCoords для установки координат ног робота. Для нас интересна координата по оси Y

Запишем наши первые данные!

Тут все просто - это нейтрально положение.

Y - координаты ног [-80, -80, -80, -80] - Данные гироскопа [0, 0].

Далее, меняя координаты передних ног, наклоняем робота, например с шагом 10-20 мм.

Координаты ног - [-50, -50, -80, -80], данные гироскопа [8, 0].

Координаты ног - [-30, -30, -80, -80], данные гироскопа [14, 0].

Теперь наклоняем в другую сторону.

Для этого изменяем координаты противоположных ног.

Координаты ног - [-80, -80, -50, -50], данные гироскопа [-7, 0].

Координаты ног - [-80, -80, -30, -30], данные гироскопа [-12, 0].

И так далее, меняя координаты ног, записываем получившиеся при этом углы наклона.

Сохраняем эти данные в отдельный файл в виде массива

const x_train = [
  [0, 0],
  [8, 0],
  [14, 0],
  [-7, 0],
  [-12, 0]
  //...... и множество остальных данных
];

const y_train = [
  [-80, -80, -80, -80],
  [-50, -50, -80, -80],
  [-30, -30, -80, -80],
  [-80, -80, -50, -50],
  [-80, -80, -30, -30],
  //...... и множество остальных данных
];

Таким мы имеем набор тренировочных данных.

Приступаем к тренировке модели.

Чтобы выполнить тренировку модели, необходимо записать следующий код:

model
  .fit(tf.tensor2d(x_train), tf.tensor2d(y_train), {
    epochs: 300,
    batchSize: 4,
    callbacks: {
      onEpochEnd(e, l) {
        console.log(e, l);
      },
    },
  });

Здесь я добавил колбэк

onEpochEnd(e, l) {
        console.log(e, l);
      },

чтобы можно было видеть в логах текущую потерю точности. Это пригодится дальше.

И так, запускаем наш скрипт node model.js

В консоли можно наблюдать следующие логи:

Epoch 298 / 300
eta=0.0 ===========================================================> 
22ms 1380us/step - loss=245.84 
297 { loss: 245.83786010742188 }
Epoch 299 / 300
eta=0.0 ===========================================================> 
23ms 1466us/step - loss=245.56 
298 { loss: 245.56163024902344 }
Epoch 300 / 300
eta=0.0 ===========================================================> 
23ms 1457us/step - loss=245.76 
299 { loss: 245.76048278808594 }

Как видно, loss параметр слишком большой. Почему это происходит? Ответ прост - наших данных слишком мало. В данном случае я просто расширю их, добавив те же самые значения несколько раз:

const x_train = [
  [0, 0],
  //....те же самые строчки х10
  [0, 0],
  [8, 0],
  //....те же самые строчки х10
  [8, 0],
  [14, 0],
  //....те же самые строчки х10
  [14, 0],
  [-7, 0],
  //....те же самые строчки х10
  [-7, 0],
  [-12, 0]
  //....те же самые строчки х10
  [-12, 0]
  //...... и множество остальных данных
];

const y_train = [
  [-80, -80, -80, -80],
  //....те же самые строчки х10
  [-80, -80, -80, -80],
  [-50, -50, -80, -80],
  //....те же самые строчки х10
  [-50, -50, -80, -80],
  [-30, -30, -80, -80],
  //....те же самые строчки х10
  [-30, -30, -80, -80],
  [-80, -80, -50, -50],
  //....те же самые строчки х10
  [-80, -80, -50, -50],
  [-80, -80, -30, -30],
  //....те же самые строчки х10
  [-80, -80, -30, -30],
  //...... и множество остальных данных
];

Запустим скрипт повторно. Последние логи в консоли выглядят следующим образом:

296 { loss: 0.3628617525100708 }
Epoch 298 / 300
eta=0.0 ===========================================================> 
61ms 358us/step - loss=0.363 
297 { loss: 0.3628309965133667 }
Epoch 299 / 300
eta=0.0 ===========================================================> 
57ms 338us/step - loss=0.364 
298 { loss: 0.36354658007621765 }
Epoch 300 / 300
eta=0.0 ===========================================================> 
58ms 341us/step - loss=0.363 
299 { loss: 0.3632044792175293 }

Отлично! Теперь добавим код для сохранения модели:

model
  .fit(tf.tensor2d(x_train), tf.tensor2d(y_train), {
    epochs: 300,
    batchSize: 4,
    callbacks: {
      onEpochEnd(e, l) {
        console.log(e, l);
      },
    },
  }).then(() => model.save('file://model-js'));

После запуска скрипта наша модель будет сохранена в данной папке:

Файлы модели
Файлы модели

Используем созданную модель для управления роботом

Настала практическая часть. Мы можем загрузить нашу модель и использовать ее для определения положения ног в зависимости от углов. Для загрузки модели напишем следующий код:

const tf = require("@tensorflow/tfjs-node");

async function initModel() {
  const model = await tf.loadLayersModel("file://model-js/model.json");
  return model;
}

initModel().then(model => {
/////// После загрузки модели можно передать в нее данные с гироскопа X и Y
const result = model.predict(tf.tensor2d([[X, Y]])).dataSync();
});

Метод model.predict() используется для получения координат ног.
Как работает алгоритм - гироскоп считывает углы несколько раз в секунду и передает их в model.predict(). Полученный результат, в виде массива из четырех элементов, используем для установки координат ног робота.

Абстрактный код может выглядеть так -

const tf = require("@tensorflow/tfjs-node");

async function initModel() {
  const model = await tf.loadLayersModel("file://model-js/model.json");
  return model;
}

initModel().then(model => {
  setInterval(async () => {
    const { X, Y } = await gyroScope.getData();
    const result = await model.predict(tf.tensor2d([X, Y])).dataSync();

    leftArm.setCoords(defaultX, result[0], defaultZ);
    rightArm.setCoords(defaultX, result[1], defaultZ);
    leftArmBack.setCoords(defaultX, result[2], defaultZ);
    rightArmBack.setCoords(defaultX, result[3], defaultZ);
  }, 30);
});

(Здесь я не привожу реализацию объектов gyroScope и leftArm, rightArm, leftArmBack, rightArmBack)

Вот и все! Теперь, наклоняя робота в разные стороны он будет автоматически двигать свои ноги.

Заключение

Как видно из примера, использование ИИ не выглядит каким-то сложным. Тут важнее понимать, какую задачу он должен решать.

В моих планах продолжить экспериментирование с обработкой данных гироскопа и различных моделях поведения робота.

Комментарии (9)


  1. ilmar1
    26.10.2023 02:51
    +1

    Спасибо за статью, очень интересно!

    Если несложно - проясните, пожалуйста. У меня складывается ощущение, что именно для решения данной задачи лучше подходошли бы вычислительные методы (например, на основе линейно квадратичного регулятора, тут была такая статья).

    Или у нейросети есть какое-то преимущество?

    Потому что у меня было представление, что нейросеть нужна если либо зависимости слишком сложные/неочевидные, либо избыточно сложное численное решение.

    В любом случае - классное упражнение!)


    1. don_alex_88 Автор
      26.10.2023 02:51

      Да, вы абсолютно правы. Целью задачи было посмотреть, как нейросеть будет работать. Ну и привести пример для читателя, как проводить тренировку, какие данные собирать.

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


    1. Botchal
      26.10.2023 02:51

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


      1. don_alex_88 Автор
        26.10.2023 02:51

        Зачем же сразу палками бить )))


        1. Botchal
          26.10.2023 02:51

          Ну, бить роботов палками - безопасно и весело + зависимости сложные/неочевидные, разве нет?


  1. KazNik
    26.10.2023 02:51

    Возможно, вам будет интересно на хакатоне Цифровой Прорыв был такой кейс https://hacks-ai.ru/hackathons.html?eventId=969082&caseEl=1033127&tab=1. И еще есть такие соревнование https://embodied-ai.org/


  1. ProLimit
    26.10.2023 02:51

    Не называйте пожалуйста простейшую нейросеть ИИ. Да, она может строить линейные зависимости, а также можно написать пару формул и получить тот же результат. Но при чем тут ИИ?


    1. don_alex_88 Автор
      26.10.2023 02:51
      +2

      Нейронки можно назвать подмножеством ИИ


  1. Botchal
    26.10.2023 02:51

    Хорошо когда на хобби есть время!