Простой пример ИИ для управления роботом. TensorFlow + Node Js
- пятница, 27 октября 2023 г. в 00:00:16
Немного слов обо мне: мое хобби это робототехника. На данный момент экспериментирую с шагающим роботом на базе SunFounder PiCrawler.
Последнее время тема искусственного интеллекта (ИИ) приобретает все большую популярность. Причиной этому служит в том числе совершенствование мобильных устройств и компьютеров - они становятся мощнее и компактнее.
В данной статье я постараюсь простыми словами объяснить, как можно применить ИИ для управления роботом, используя готовую библиотеку TensorFlow.
Краткими словами - это инструмент для обработки данных, построенный на базе нейросети. Для создания собственного ИИ мы решаем, какие входные параметры обрабатываем и какие ожидаем на выходе. Потом собираем огромный объем этих данных, будь то изображения или что-то другое и "тренируем" нейросеть на их основе. В итоге получаем модель, которая будет обрабатывать наши значения.
Это девайс, приобретенный на 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]
В виде картинки эту модель можно представить следующим образом:
И так, что мы имеем:
Входные данные в формате [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)
Вот и все! Теперь, наклоняя робота в разные стороны он будет автоматически двигать свои ноги.
Как видно из примера, использование ИИ не выглядит каким-то сложным. Тут важнее понимать, какую задачу он должен решать.
В моих планах продолжить экспериментирование с обработкой данных гироскопа и различных моделях поведения робота.