Нещодавно з’явилися пристрої, здатні думати на такому ж рівні, що й людина. Сьогодні ж ти можеш спокійно написати дипломну роботу, не гортаючи тонни контенту і маючи під рукою всього-лише один сайт. Напевно, ти вже зрозумів, про що йдеться. Нейромережі буквально заповнюють наш світ. Але я думаю тобі завжди було цікаво, як насправді працює такий алгоритм, і які методи навчання лежать в основі штучного інтелекту. У цій статті я відкрию завісу і розповім тобі, як створити свою нейромережу без будь-яких особливих зусиль.
План роботи
Давай я коротко опишу план нашої роботи. Насамперед слід розібратися з тим, як взагалі працює нейромережа і що вона з себе представляє. Після цього ми скористаємося утилітами для її написання і спробуємо реалізувати всю цю справу. Для візуалізації всієї роботи я вирішив скористатися рушієм Unity, тому тобі слід його встановити.
Окрім цього ми створимо тестову кімнату і задамо мету для нашої нейронної мережі. Думаю, кількох рядків для опису всієї роботи буде достатньо, тому перейдемо в розділ встановлення необхідних компонентів.
Середовище для розробки
Як зазначалося раніше, тобі будуть потрібні базові знання мови програмування C#, а також наявність під рукою рушія Unity. Я використовую Unity версії 2021 року, але для роботи може стати в пригоді й новіше середовище. Також тобі потрібно встановити Python 3.10 і вище. До нього ми підключимо модулі глибокого навчання і бібліотеки для математичних обчислень. Весь список необхідних модулів я надав нижче:
- mlagents
- numpy (не вище 1.19.4)
- torch (не нижче 1.7.1)
- CUDA Toolkit
Додам трохи слів до вимог щодо заліза. Я тестував усе на процесорі Intel Core i7 4700MQ у поєднанні з давньою відеокартою NVIDIA GeForce GTX 765M. В основному підійде будь-який інший процесор. Відеокарту доведеться використовувати від зелених і я зараз не про позаземну цивілізацію, а компанію NVIDIA. Уся проблема полягає саме в утиліті CUDA, яка дає змогу використовувати відеочіпи на повну міць і від них залежатиме швидкість навчання твоєї нейромережі.
Також більш сучасні версії модуля Numpy не підійдуть у роботі та викликатимуть критичні помилки, а вся річ у тім, що розробники змінили метод, який дозволяє програмі присвоювати певні типи змінних. Тому якщо ти побачиш у логах слова float і йому подібні, будь упевнений у тому, що вся проблема криється у версії numpy. В іншому проблем виникнути не повинно. Також для встановлення основного компонента PyTorch потрібно використовувати цю команду:
Код:
pip3 install torch -f https://download.pytorch.org/whl/torch_stable.html
Встановлення займе досить багато часу, оскільки файл із пакетами і необхідними функціями важить 2.4 ГБ. Під час процедури не вимикай систему і не дозволяй їй йти в режим сну.
Як працюють нейромережі
Перед будь-якою роботою потрібно мати базові знання, чим ми зараз і займемося. Я постараюся максимально зрозуміло і детально розповісти про роботу нейронної мережі, зачіпаючи якомога менше математичних кривих та інших незрозумілих термінів. Насамперед давай визначимося з тим, що таке нейронна мережа:
Нейромережа – це поняття безпосередньо пов’язане з біологією і розуміється як послідовність нейронів, які з’єднуються між собою синапсами. Завдяки такій структурі, машина набуває здатності аналізувати і навіть запам’ятовувати різну інформацію. Нейронні мережі також здатні не тільки аналізувати вхідну інформацію, а й відтворювати її зі своєї пам’яті.
Якщо порівнювати з людиною, то нейронна мережа на основі певних подій робить висновок і записує його в тимчасову пам’ять. Всю її роботу можна буквально уявити у вигляді ваг, на яких лежать сприятливі та несприятливі умови. Наприклад, якщо нейромережа впаде в прірву, то шанс смерті від цієї події зросте. Повторивши процедуру кілька разів, вона зробить висновок про те, що сприятливих результатів такої події немає і її краще не повторювати надалі. Тепер давай розберемося зі складовими частинами слова нейромережа. Адже воно безпосередньо пов’язане з таким терміном, як нейрон.
Нейрон – обчислювальна одиниця, яка здатна отримувати й обробляти інформацію, а також передавати її. Здебільшого нейрони бувають трьох типів: вхідні, приховані та вихідні. Якщо в нейромережі є величезна кількість нейронів, то використовується термін шару, для більш точної класифікації.
Мовою машин нейрони здатні працювати тільки з трьома числами в діапазоні від -1 до 1. Якщо робота відбувається з більшими числами, то вони записуються у вигляді дробу, а точніше одиниця ділиться на це число. Для передачі інформації між нейронами використовується таке поняття як синапс. Він містить всього лише один параметр – вагу. Від нього залежить значущість інформації та швидкість її подальшої обробки. Що більша вага, то швидше інформація перейде від одного нейрона до іншого. На етапі ініціалізації нейронної мережі вся вага розподіляється в абсолютно випадковому порядку.
Щоб на виході отримувати потрібний діапазон, заведено використовувати функції активації, які нормалізують вхідну інформацію і на виході отримують необхідні значення. Загалом існує три основні типи функцій: лінійна, сигмоїд і гіперболічний тангенс. Їх я зачіпати не буду, більш детальну інформацію ти зможеш знайти на просторах інтернету.
Отже, після невеликого екскурсу у світ нейромереж варто приступити до її реалізації, чим ми зараз і займемося.
Створюємо нейронну мережу в Unity
На першому етапі відкриваємо сам Unity і створюємо 3D проєкт. Я вибрав цей рушій, щоб візуально показати тобі як працює і навчається нейромережа в режимі реального часу. Після створення додамо кілька об’єктів у наш проєкт. Це будуть стіни, кубик, простір і невеликий м’ячик. Основна ідея в тому, щоб навчити нейромережу добиратися до певного об’єкта.
Також не забуваємо накинути на Goal і Cube фізику, а точніше Rigidbody. У випадку з головним об’єктом усі фізичні властивості варто застосовувати до батьківського об’єкта (Agent). Таким чином ми створимо необхідні умови для того, щоб фігури могли взаємодіяти. Після цього варто додати до стіни та нашої кульки скрипти. У них ми нічого записувати не будемо, а всього лише ініціалізуємо для подальшої роботи. Щоб це зробити, тобі потрібно перейти в інспектор і додати компонент із назвою C# Script.
Чудово, тепер перейдемо до створення сенсорів, які відповідають за переміщення в просторі, і накинемо пульт керування для нашого кубика. Усе це робиться так само, як я описав трохи вище. Тепер перейдемо до написання скриптів. Перед цим варто перейти на вкладку Window -> Packet Manager і після на просторах Unity завантажити пакет ML-Agents.
Насамперед прибираємо всі зайві і залишаємо публічний клас з ідентифікатором Agent:
C#:
public class Cube_AI : Agent
{}
Сюди ми помістимо всі необхідні компоненти для управління. Перед цим у тебе мають бути імпортовані такі бібліотеки:
C#:
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
Після цього нашій нейромережі потрібен об’єкт, до якого вона має дійти. Для цього використовуємо таку команду:
C#:
public override void OnEpisodeBegin()
{
transform.position = Vector3.zero;
}
Переходимо до головного етапу, а саме сенсорів, які відповідають за координацію в просторі. Я не буду додавати зір і обмежуся об’єктами навколо нашої нейромережі. Простіше кажучи, сама нейронка буде сліпа, але її головним орієнтиром слугуватиме простір навколо, якраз для цього і будуть потрібні наші сенсори. Задаємо основний об’єкт і мету, до якої слід дістатися:
C#:
public override void CollectObservations(VectorSensor sensor)
{
sensor.AddObservation(transform.position);
sensor.AddObservation(targetTransform.position);
}
Чудово, орієнтація в просторі у нас є, але варто подумати про пересування, для цього задамо координати, за якими можна переміщатися, і швидкість пересування. Робиться це в кілька рядків коду:
C#:
public override void OnActionReceived(ActionBuffers actions)
{
float moveX = actions.ContinuousActions[0];
float moveZ = actions.ContinuousActions[1];float moveSpeed = 1f;
transform.position += new Vector3(moveX, 0, moveZ) * Time.deltaTime * moveSpeed;
}
Тепер наш об’єкт уміє рухатися, але поки що в нього немає інтелекту і мети, заради якої він буде це робити. Тому займемося цим, попутно додаючи вектори переміщення для більш точної координації:
C#:
public override void Heuristic(in ActionBuffers actionsOut)
{
ActionSegment<float> continuousActions = actionsOut.ContinuousActions;
continuousActions[0] = Input.GetAxisRaw(“Горизонталь”);
continuousActions[1] = Input.GetAxisRaw(“Vertical”);
}private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<Goal>(out Goal goal))
{
SetReward(+1f);
EndEpisode();
}if (other.TryGetComponent<Wall>(out Wall wall))
{
SetReward(-1f);
EndEpisode();
}
}
Щоб не морочитися, давай я коротко поясню, що тут відбувається. У першому циклі ми задаємо вектори пересування разом з їхнім індексом. Після цього створюємо умовні цикли, які заохочуватимуть нашу нейромережу за виконання мети або навпаки “лаятимуть” за спробу втечі. Таким чином ми робимо баланс для наших ваг, які дозволять нейронці надалі розуміти, що можна робити, а що краще не варто. Також додаємо функцію завершення епізоду і налаштування управління можна спокійно закривати.
Тепер до батьківської точки Agent підключаємо наш скрипт і додаємо також Decision Requester, його ти можеш знайти за шляхом Add Component -> ML Agents. Крім цього, у тебе має з’явитися параметр Target Transform. У нього ми поміщаємо нашу кулю, яка буде головною метою нейромережі. Усі налаштування залишаємо за замовчуванням і приступаємо до підключення мозку кубика.
Для цього тобі потрібно перейти в папку з проектом і в шляху до нього прописати команду cmd, щоб відкрити папку через консоль. Початок навчання відбувається безпосередньо через саме вікно терміналу. Ми всього лише даємо нейромережі мету і об’єкт, яким вона може керувати. Нейронка є найпростішим прототипом і має функцію глибокого навчання (на жаль, воно ще й довге). Щоб сталася магія і кубик почав подавати ознаки життя тобі слід ввести в консолі таку команду:
Код:
mlagents-learn
Через кілька секунд має з’явитися логотип Unity і фраза про те, що відкрився порт для прослуховування і підключення. Якщо у тебе з’явилася помилка, то слід перевстановити всі пакети або перевірити інспектор і правильність налаштованих параметрів. Тепер залишається натиснути кнопку “Пуск” у самому вікні проєкту і чекати результату.
У разі успіху ти побачиш метадані об’єкта і Behavior Name. Залишається тільки чекати і спостерігати за навчанням. Під час наступних запусків рекомендується додати прапор –force, щоб обнулити всю роботу і перезапустити нейромережу. Перед цим переконайся, що Python є в самому проєкті. Якщо його немає, то варто прописати такі команди:
Код:
py -m venv venv venv
venv\Scripts\activate
Результат роботи може бути непередбачуваний і абсолютно несподіваний, починаючи від звичайного виконання завдання методом проб і помилок і закінчуючи простою бездіяльністю. Також є кілька варіантів базових нейромереж, з якими можна ознайомитися в самому пакеті. Для їх експлуатації тобі доведеться додати зір об’єкту і кілька інших сенсорів для орієнтації. В іншому пісочниця і штучний інтелект у твоєму розпорядженні. Невеликий акваріум із примітивним мозком, який саморозвивається. Тепер варто підбити підсумки.
Підбиваємо підсумки
Написання нейронних мереж – це дуже трудомісткий і витратний процес, але спостерігати за їхнім навчанням дуже цікаво і заворожуюче. У цій статті я максимально коротко і зрозуміло розповів тобі, як візуалізувати роботу нейронної мережі і розмістити її прямо у себе на пристрої. Ти можеш натренувати її під свої потреби або використовувати надалі для розваги, але з точки зору безпеки такі “штучні мізки” можуть завдати величезної шкоди в невмілих руках або просто тих, хто хоче розважитися.
C#:
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
public class Cube_AI : Agent
{
[SerializeField] private Transform targetTransform;
public override void OnEpisodeBegin()
{
transform.position = Vector3.zero;
}
public override void CollectObservations(VectorSensor sensor)
{
sensor.AddObservation(transform.position);
sensor.AddObservation(targetTransform.position);
}
public override void OnActionReceived(ActionBuffers actions)
{
float moveX = actions.ContinuousActions[0];
float moveZ = actions.ContinuousActions[1];
float moveSpeed = 1f;
transform.position += new Vector3(moveX, 0, moveZ) * Time.deltaTime * moveSpeed;
}
public override void Heuristic(in ActionBuffers actionsOut)
{
ActionSegment continuousActions = actionsOut.ContinuousActions;
continuousActions[0] = Input.GetAxisRaw(“Horizontal”);
continuousActions[1] = Input.GetAxisRaw(“Vertical”);
}
private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent(out Goal goal))
{
SetReward(+1f);
EndEpisode();
}
if (other.TryGetComponent(out Wall wall))
{
SetReward(-1f);
EndEpisode();
}
}
}









