Розділ 10. Нейронні мережі
Людський мозок має 100 мільярдів нейронів,
кожен з’єднаний з 10 тисячами інших нейронів.
Те, що знаходиться на ваших плечах
є найскладнішим об’єктом
у відомому нам всесвіті.
— Мічіо Кайку
Кіпу (або вузол) — це стародавній пристрій інків, який використовувався для ведення записів і спілкування. Він являв собою складну систему мотузкового сплетіння й вузликів для кодування та передачі інформації. Кожна кольорова мотузка, тип і візерунок вузла представляли певні дані, наприклад записи перепису чи календарну інформацію. Перекладачі, відомі як кіпукамайоки, діяли як свого роду бухгалтери й декодували мотузкові записи у зрозумілу інформацію.
Я почав з неживих об’єктів, що живуть у світі сил і надав їм бажання, автономію та здатність діяти відповідно до системи правил. Далі я дозволив цим об’єктам, які тепер називаються створіннями, жити в популяції й еволюціонувати з часом. Тепер я хочу запитати, яким чином відбувається процес прийняття рішень у кожного створіння? Як воно може змінити свій вибір, навчаючись з часом? Чи може обчислювальний об’єкт обробляти своє середовище і приймати рішення?
Щоб відповісти на ці запитання, я знову звернусь до природи, а саме до людського мозку. Мозок можна описати як біологічну нейронну мережу — взаємопов’язану мережу нейронів, що передають складні моделі електричних сигналів. Усередині кожного нейрона дендрити отримують вхідні сигнали й на їх основі нейрон подає вихідний сигнал через аксон (див. малюнок 10.1). Питання як саме працює людський мозок це витончена та складна таємниця, яку я, звісно, не збираюся детально розгадувати в межах цього розділу.
На щастя, як ви бачили у цій книзі, розробка привабливих анімованих систем за допомогою коду не потребує наукової строгості чи точності. Розробка розумної ракети не є ракетобудуванням, а розробка штучної нейронної мережі не є наукою про мозок. Досить просто надихнутися ідеєю роботи мозку.
Цей розділ я почну з концептуального огляду властивостей і особливостей нейронних мереж й побудую найпростіший можливий приклад такої мережі, яка складатиметься з одного нейрона. Потім я познайомлю вас зі складнішими нейронними мережами із допомогою бібліотеки ml5.js. Це послужить основою для Розділу 11 — великого фіналу цієї книги, де я поєднаю ГА із нейронними мережами для фізичного моделювання.
Знайомство зі штучними нейронними мережами
Вчені з інформатики вже давно надихаються людським мозком. У 1943 році нейробіолог Воррен Маккалох і логік Волтер Піттс, розробили першу концептуальну модель штучної нейронної мережі. У їх статті “Логічне обчислення ідей, властивих нервовій діяльності” вони описують нейрон як єдину обчислювальну клітину, що живе в мережі клітин, яка отримує вхідні дані, обробляє ці вхідні дані та генерує вихідні дані.
Їхня робота, а також робота багатьох вчених і дослідників, які йшли за ними, не мала на меті точно описати, як працює біологічний мозок. Навпаки, штучна нейронна мережа (надалі просто нейронна мережа) була задумана як обчислювальна модель, заснована на мозку, розроблена для розв'язання певних проблем, які традиційно були складними для комп’ютерів.
Деякі проблеми неймовірно прості для вирішення комп’ютером, але складні для таких людей, як ми з вами. Прикладом є знаходження квадратного кореня з числа 964 324. Швидкий рядок коду видає значення 982, число, яке мій комп’ютер може обчислити менш ніж за мілісекунду, але якщо ви попросите мене обчислити це число самостійно, вам доведеться трохи почекати. З іншого боку, певні задачі неймовірно просто розв’язати вам або мені, але не так легко для комп’ютера. Покажіть будь-якому малюку зображення кошеня чи цуценя і вони швидко зможуть сказати вам, хто з них де. Прислухайтеся до розмови в галасливому кафе і зосередьтеся на голосі однієї людини й ви легко зможете зрозуміти її слова. Але як щодо потрібної машини для виконання одного з цих завдань? Вчені присвятили для цього цілі кар’єри на дослідження і впровадження складних рішень й нейронні мережі є одним з них.
Ось кілька застосувань для нейронних мереж у програмному забезпеченні, деякі з них на сьогодні є простими для людини, але складними для машин:
- Розпізнавання шаблонів: нейронні мережі добре підходять для завдань, коли метою є виявлення, інтерпретація і класифікація ознак або патернів у наборі даних. Це може включати багато завдань, починаючи від ідентифікації об’єктів на зображеннях (наприклад, обличчя) до оптичного розпізнавання символів і до складніших завдань, таких як розпізнавання жестів.
- Прогнозування часових рядів і виявлення аномалій: нейронні мережі використовуються як для прогнозування, наприклад, прогнозування тенденцій на фондових ринках або погодних умов, так і для розпізнавання аномалій, які можна застосовувати в таких галузях, як виявлення кібератак і запобігання шахрайству.
- Системи управління й адаптивного прийняття рішень: застосування цих систем варіюються від автономних транспортних засобів, таких як безпілотні автомобілі та дрони, до адаптивного прийняття рішень, що використовується в іграх, моделях ціноутворення і систем ах рекомендацій на медіаплатформах.
- Обробка природної мови: однією з найбільших подій за останні роки стало використання нейронних мереж для обробки й розуміння людської мови. Вони використовуються у різних завданнях, включаючи машинний переклад, аналіз настроїв і узагальнення тексту та є базовою технологією для багатьох цифрових помічників і чат-ботів.
- Обробка сигналів і обчислювальні датчики: нейронні мережі відіграють вирішальну роль у таких пристроях, як кохлеарні імплантати та слухові апарати, фільтруючи шум й посилюючи важливі звуки. Вони також використовуються в обчислювальних сенсорах — програмних системах, які обробляють дані з багатьох джерел для всебічного аналізу навколишнього середовища.
- Генеративні моделі: розвиток нових архітектур нейронних мереж зробив можливим генерацію нового контенту. Ці системи можуть синтезувати зображення, покращувати роздільну здатність зображення, передавати стиль малюнку між зображеннями й навіть створювати музику та відео.
Охоплення повного спектра застосувань для нейронних мереж заслуг овує на цілу книгу (або серію книг) і на той час, коли ця книга буде надрукована, вона, ймовірно, застаріє. Сподіваюсь, цей список дав вам загальне уявлення про їх функціональність та можливості.
Як працюють нейронні мережі
Нейронні мережі дещо відрізняються від інших комп’ютерних програм. Обчислювальні системи, про які я писав до цього, є процедурними: програма починається з першого рядка коду, виконує його і переходить до наступного, дотримуючись інструкцій у лінійному порядку. Напротивагу, справжня нейронна мережа не слідує лінійним шляхом. Натомість інформація обробляється спільно, паралельно, у мережі вузлів, кожен з яких представляє нейрон. У цьому сенсі нейронна мережа вважається конекціоністською системою.
З іншого боку, нейронні мережі не дуже відрізняються від деяких програм, які ви вже бачили. Нейронна мережа демонструє всі ознаки складної системи, схожої на клітинний автомат або зграю боїдів. Пам’ятаєте як кожен окремий боїд сам по собі був простим для розуміння, але введення лише трьох правил таких як розділення, вирівнювання і згуртованість, сприяло утворенню складної поведінки? Кожен окремий елемент нейронної мережі однаково простий для розуміння. Він зчитує вхідні дані (числа), обробляє їх та генерує вихідні дані (інші числа). Це все, що стосується окремого нейрона, однак мережа з багатьох нейронів може демонструвати надзвичайно багатогранну й розумну поведінку, повторюючи складну динаміку, яку можна спостерігати у зграї боїдів.
Насправді нейронна мережа — це не просто складна система, а складна адаптивна система, тобто вона може змінювати свою внутрішню структуру на основі інформації, що проходить через неї. Іншими словами, вона має здатність до навчання. Зазвичай, це досягається шляхом регулювання ваг. На малюнку 10.2 кожна стрілка позначає зв’язок між двома нейронами й вказує напрямок потоку інформації. Кожне з’єднання має вагу — число, яке контролює сигнал між двома нейронами. Якщо мережа генерує хороші в ихідні дані або, інакше кажучи, вихід (який я визначу пізніше), немає потреби коригувати ваги. Однак, якщо мережа генерує поганий результат, так би мовити, помилку, тоді система адаптується, змінюючи ваги з надією поліпшити наступні результати.
Нейронні мережі можуть використовувати різноманітні стратегії навчання і на одній із них я зосереджуся у цьому розділі:
- Кероване навчання або навчання з учителем (навчання під наглядом): по суті, ця стратегія передбачає вчителя, який розумніший за саму мережу. Візьмемо випадок розпізнавання обличчя. Учитель показує мережі купу облич, і вчитель уже знає імена, пов’язані з кожним обличчям. Мережа робить свої припущення, а потім вчитель надає мережі фактичні імена. Мережа може порівнювати свої відповіді з відомими правильними та вносити корективи відповідно до своїх помилок. Нейронні мережі у цьому розділі наслідують цю модель.
- Некероване навчання або навчання без учителя: ця техніка потрібна, коли у вас немає прикладу даних із відомими відповідями. Натомість мережа працює самостійно, щоб виявити у даних приховані закономірності. Прикладом застосуванням є кластеризація: набір елементів розподіляється на групи відповідно до невідомого шаблону. Я не буду наводити жодних прикладів неконтрольованого навчання, оскільки дана стратегія менш актуальна для прикладів у цій книзі.
- Навчання з підкріпленням: ця стратегія побудована на спостереженні де агент, що навчається, приймає рішення і шукає результати у своєму середовищі. Він винагороджується за правильні рішення і карається за погані, щоб з часом навчитися приймати кращі рішення. Я обговорю цю стратегію більш детально у Розділі 11.
Здатність нейронної мережі навчатися, вносячи корективи у свою структуру з часом — це те, що робить її такою корисною в галузі машинного навчання. Появу цього терміну можна знайти у статті “Деякі дослідження машинного навчання з використанням гри у шашки” 1959 року, в якій комп’ютерний науковець Артур Лі Семюель описує програму “самонавчання” для гри в шашки. Концепція алгоритму, який дозволяє комп’ютеру вчитися без явного програмування, є основою машинного навчання.
Подумайте про те, що ви робили протягом цієї книги: програмування! У традиційному програмуванні комп’ютерна програма приймає вхідні дані й на основі наданих вами правил генерує вихідні дані. Однак машинне навчання перевертає цей підхід з ніг на голову. Замість того, щоб ви писали правила, системі надаються приклади вхідних і вихідних даних й вона сама генерує правила! Для впровадження машинного навчання можна використовувати багато алгоритмів і нейронна мережа є лише одним із них.
Машинне навчання є частиною широкої й масштабної галузі штучного інтелекту (ШІ), хоча ці терміни іноді використовуються взаємозамінно. У своєму вдумливому і дружньому посібнику “Народний путівник з ШІ” Мімі Онуоха та Діана Нусера (також відома під псевдонімом Mother Cyborg) визначають ШІ як “теорію і розробку комп’ютерних систем, здатних виконувати завдання, які зазвичай потребують людського інтелекту”. Алгоритми машинного навчання є одним із підходів до цих завдань, але не всі системи ШІ мають компонент самонавчання.
Бібліотеки машинного навчання
Сьогодн і використання машинного навчання у творчому програмуванні та інтерактивних медіа не лише можливе, але і все більш поширене завдяки спеціальним бібліотекам від розробників, які вирішують у них багато деталей реалізації нейронної мережі. Хоча переважна більшість розробок і досліджень машинного навчання виконується на Python, у світі веброзробки з’явилися потужні інструменти на основі JavaScript. Дві бібліотеки, які варто відзначити, це TensorFlow.js і ml5.js.
TensorFlow.js — це бібліотека з відкритим вихідним кодом, яка дозволяє визначати, навчати й запускати нейронні мережі безпосередньо у браузері за допомогою JavaScript, без необхідності встановлювати чи налаштовувати складні середовища. Вона є частиною екосистеми TensorFlow, яку підтримує та розвиває Google. TensorFlow.js — потужний інструмент, але його низькорівневі операції та високотехнічний API можуть налякати новачків. Тут на допомогу приходить ml5.js — бібліотека, побудована на основі TensorFlow.js і розроблена спеціально для використання з p5.js. Її мета — бути зручною для початківців і зробити машинне навчання доступним для широкої аудиторії митців, творчих програмістів й студентів. Я покажу, як використовувати ml5.js у розділі “Машинне навчання за допомогою ml5.js”.
Перевага таких бібліотек, як TensorFlow.js і ml5.js, полягає в тому, що ви можете використовувати їх для запуску натренованих моделей. Модель машинного навчання — це конкретна конфігурація нейронів і зв’язків, а натренована модель — це та, яка була попередньо підготовлена для виконання певного завдання. Наприклад, популярні натреновані моделі використовуються для класифікації зображень, визначення поз тіла, розпізнавання точок обличчя чи положення рук і навіть для аналізу настрою, вираженого у тексті. Ви можете використовувати таку модель як є або розглядати її як відправну точку для додаткового навчання (зазвичай його називають трансферним навчанням).
Перш ніж я розгляну бібліотеку ml5.js, я хочу спробувати свої сили у створенні найпростішої з усіх нейронних мереж з нуля, використовуючи лише p5.js, щоб проілюструвати, як концепції нейронних мереж і машинного навчання реалізуються у коді.
Персептрон
Персептрон — це найпростіша нейронна мережа з м ожливих: обчислювальна модель одного нейрона. Винайдений у 1957 році Френком Розенблатом у лабораторії авіаційних досліджень Корнелського університету персептрон складається з одного чи кількох входів з даними, процесора та одного вихідного результату, як показано на малюнку 10.3.
Персептрон слідує моделі прямого поширення: дані проходять (подаються) через мережу в одному напрямку. Вхідні дані надсилаються в нейрон, обробляються і призводять до виходу. Це означає, що однонейронна мережа, зображена на малюнку 10.3, читається зліва направо (вперед): у процесор приходять вхідні дані й виходять вихідні.
Скажімо, у мене є персептрон з двома входами, значеннями 12 і 4. У машинному навчанні заведено позначати кожен вхід символом , тому я назву ці входи як та :
Вхід | Значення |
---|---|
12 | |
4 |
Кроки персептрона
Щоб перейти від цих вхідних даних до вихідних, персептрон виконує ряд кроків.
Крок 1: Зваження вхідних даних
Кожне вхідне значення, яке надсилається у нейрон, спочатку має бути зважене, тобто його помножують на певне значення, часто на число від -1 до +1. Під час створення персептрона вхідні дані зазвичай мають випадкову вагу. Я назву свої ваги як і :
Вага | Значення |
---|---|
0.5 | |
–1 |
Кожне вхідне значення потрібно помножити на відповідну вагу:
Вхідне значення | Вага | Вхідне значення Вага |
---|---|---|
12 | 0.5 | 6 |
4 | –1 | –4 |
Крок 2: Додавання вхідних даних
Потім зважені вхідні дані додаються разом:
Крок 3: Формування результату
Вихідне значення персептрона формується шляхом проведення суми через функцію активації, яка зменшує результат до одного з двох можливих значень. Можна уявити цей бінарний результат як світлодіод, який може бути або вимкненим або увімкненим, або як нейрон у реальному мозку, який або активний, або ні. Функція активації визначає, чи повинен персептрон бути активним.
Функції активації можуть бути складними. Якщо ви почнете читати про них у посібнику зі штучного інтелекту, то незабаром можете помітити, як тягнетеся за підручником з математики. Однак ваш новий друг простий персептрон пропонує простіший варіант, який все ще демонструє цю концепцію. Я зроблю функцію активації знаком суми. Якщо сума буде додатним числом, тоді результатом буде 1, а якщо сума від’ємна, тоді результатом буде -1:
Збираємо все разом
Поєднуючи попередні три частини разом, маємо наступні кроки для алгоритму персептрона:
- Кожне вхідне значення множиться на його вагу.
- Усі зважені дані додаються разом.
- Результівне значення персептрона обчислюється через проведення отриманої суми через функцію активації (у поточному прикладі результат залежатиме від знаку суми).
Я можу почати записувати цей алгоритм у коді, використовуючи два масиви значень, один для вхідних даних і один для їх ваги:
let inputs = [12, 4];
let weights = [0.5, -1];
Крок 1 передбачає використання циклу, який перемножує кожне вхідне значення на його відповідну вагу. Щоб отримати загальну суму, помножені результати можна скласти у цьому самому циклі:
let sum = 0;
for (let i = 0; i < inputs.length; i++) {
sum += inputs[i] * weights[i];
}
Кроки 1 і 2: додавання всіх зважених вхідних даних.
Маючи суму я можу обчислити вихідний результат:
let output = activate(sum);
Крок 3: проведення суми через функцію активації.
function activate(sum) {
Функція активації.
if (sum > 0) {
return 1;
} else {
return -1;
}
Повертає 1, якщо значення позитивне і -1 — якщо негативне.
}
Вам може бути цікаво, як у функції активації я обробляю значення 0. Значення 0 позитивне чи негативне? Не заглиблюючись у філософські особливості цього питання, я довільно вирішив повертати -1, коли маю 0, але я міг би легко піти іншим шляхом. Все залежить від особливостей програми де це рішення може бути важливим, але для цього прикладу я можу вибрати будь-яке.
Тепер, коли я пояснив процес обчислення персептрона, давайте подивимося на приклад його дії.
Просте розпізнавання шаблонів за допомогою персептрона
Я вже згадував, що нейронні мережі широко використовуються для розпізнавання шаблонів. Сценарії, описані раніше, вимагають складніших мереж, але навіть простий персептрон може продемонструвати фундаментальний тип розпізнавання шаблонів, у якому дані класифікуються як належні до однієї з двох груп. Наприклад, уявіть, що у вас є набір даних про рослини й ви хочете ідентифікувати їх як ксерофіти (рослини, які еволюціонували, щоб виживати в середовищі з невеликою кількістю води й великою кількістю сонячного світла, наприклад у пустелі), або як гідрофіти (рослини, які пристосувалися жити у воді з обмеженим освітленням). Саме так я використовуватиму свій персептрон у цьому розділі.
Один зі способів підійти до класифікації рослин це візуалізувати їхні дані на 2D графіку і розглядати проблему як просторову. На вісі відмічайте кількість щоденного сонячного світла, яке отримує рослина, а по осі — кількість води. Після нанесення усіх даних, легко провести лінію через графік, де всі ксерофіти будуть з одного боку, а всі гідрофіти з іншого, як на малюнку 10.4. (Я дещо спрощую тут. Дані з реального світу, ймовірно, будуть менш очевидними, тому чітку межу буде провести складніше.) Таким чином можна класифікувати кожну рослину. Якщо згідно з даними вона знаходиться нижче лінії, тоді це ксерофіт. А якщо вона знаходиться вище лінії? Тоді це гідрофіт.
Насправді щоб сказати мені чи знаходиться точка вище, чи нижче лінії, мені не потрібна нейронна мережа, навіть простий персептрон. Я можу побачити відповідь на власні очі або дати моєму комп’ютеру визначити це завдання за допомогою простої алгебри. Але так само як розв’язання проблеми з відомою відповіддю “to be or not to be” було зручним першим тестом для ГА у Розділі 9, навчання персептрона класифікувати точки як такі, що знаходяться по один або по інший бік лінії буде цінним способом продемонструвати алгоритм персептрона і переконатися, що він працює належним чином.
Щоб розв'язувати цю проблему, я дам своєму персептрону два вхідні значення: — це -координата точки, яка представлятиме кількість сонячного світла для рослини, а — це -координата цієї точки, що представлятиме кількість води для рослини. Потім персептрон вгадуватиме класифікацію рослини відповідно до знака зваженої суми цих вхідних даних. Якщо сума додатна, персептрон повертатиме +1, що означатиме гідрофіт (знаходиться над лінією). Якщо сума від’ємна, тоді повертатиметься -1, що означатиме ксерофіт (нижче лінії). На малюнку 10.5 показано цей персептрон (зверніть увагу на скорочення і для позначення ваги).
Однак ця схема має досить серйозну проблему. Що робити, якщо моя точка даних дорівнює , і я надсилаю цю точку в персептрон як вхідні дані і ? Незалежно від ваги, множення на 0 дорівнює 0. Тому зважені вхідні дані все ще дорівнюють 0 і їхня сума також дорівнюватиме 0. А знак 0 дорівнює... хм, знову глибока філософська дилема. Незалежно від того, як я ставлюся до цього, точка може бути або вище або нижче різних ліній у 2D-світі. Як саме персептрон має це інтерпретувати?
Щоб уникнути цієї дилеми, персептрон потребує третє вхідне значення, яке зазвичай називають похибкою або значенням баєса. Цей додатковим параметр завжди має значення 1 і також є зваженим. На малюнку 10.6 показаний персептрон з баєсом.
Як це впливає на точку ?
Вхідне значення | Вага | Результат |
---|---|---|
0 | 0 | |
0 | 0 | |
1 |
Результатом буде сума зважених результатів: . Таким чином, значення похибки саме по собі відповідає на питання, де знаходиться стосовно лінії. Якщо вага похибки додатна, точка буде знаходитися над лінією, а якщо від’ємна, то нижче. Додаткове значення bias і його вага змінюють розуміння персептроном положення лінії відносно координат !
Код персептрона
Тепер я готовий зібрати код для класу Perceptron
. Персептрону достатньо відстежувати лише ваги вхідних даних, які я можу зберегти за допомогою масиву:
class Perceptron {
constructor() {
this.weights = [];
}
Конструктор може приймати аргумент, який вказуватиме кількість вхідних даних (у цьому випадку трьох: , , і ) і створюватиме масив для ваг weights
відповідного розміру, заповнюючи його для початку випадковими значеннями:
constructor(n) {
this.weights = [];
for (let i = 0; i < n; i++) {
Аргумент n визначає кількість вхідних даних (включаючи значення баєса).
this.weights[i] = random(-1, 1);
Для початку значення ваг вибираються випадковим чином.
}
}
Робота персептрона полягає в тому, щоб отримувати вхідні дані й створювати вихідні. Ці вимоги можна об’єднати у методі feedForward()
. У цьому прикладі вхідними даними персептрона є масив (який має бути такої ж довжини, як і масив вагових коефіцієнтів), а вихідним результатом є число, +1 або -1, яке повертає функція активації на основі знака суми:
feedForward(inputs) {
let sum = 0;
for (let i = 0; i < this.weights.length; i++) {
sum += inputs[i] * this.weights[i];
}
return this.activate(sum);
Результатом є знак суми: -1 або +1. Тут персептрон робить припущення по яку сторону лінії знаходяться поточні дані.
}
}
Імовірно, тепер я можу створити об’єкт Perceptron
і попросити його зробити припущення для будь-якої точки, як на малюнку 10.7.
Ось код для створення припущення:
let perceptron = new Perceptron(3);
Створення персептрона.
let inputs = [50, -12, 1];
Вхідними даними є три значення: x, y та bias.
let guess = perceptron.feedForward(inputs);
Результат припущення!
Чи персептрон зробив правильне припущення? Можливо так, а можливо і ні. У цей момент персептрон має шанси отримати правильну відповідь не більше ніж 50/50, оскі льки кожна вага починається з випадкового значення. Нейронна мережа не є чарівним інструментом, який може самостійно правильно вгадати. Мені потрібно навчити його, як це робити!
Щоб навчити нейронну мережу відповідати правильно, я скористаюся методом навчання з учителем, який я описав раніше в цьому розділі. Пам’ятайте, що цей підхід передбачає надання мережі вхідних даних із відомими відповідями. Це дає змогу мережі перевірити, чи правильні вона робить припущення. Якщо ні, мережа може навчитися на своїй помилці та скорегувати свої ваги. Процес виглядає наступним чином:
- Надайте персептрону вхідні дані, для яких є відома відповідь.
- Попросіть персептрон вгадати відповідь.
- Обчисліть похибку. (Чи була відповідь правильною, чи неправильною?)
- Відкоригуйте всі ваги відповідно до помилки.
- Поверніться до кроку 1 і повторіть!
Цей процес можна запакувати у метод класу Perceptron
, але спершу мені потрібно детальніше розібратися з кроками 3 і 4. Як я можу визначити помилку персептрона? І як мені відкоригувати ваги відповідно до цієї помилки?
Помилка персептрона може бути визначена як різниця між бажаною відповіддю та припущенням щодо неї:
Ця формула виглядає знайомою? Згадайте формулу для керувальної сили рухомим об’єктом, яку ми проходили у Розділі 5:
Це теж розрахунок похибки! Поточна швидкість слугує припущенням, а помилка (керувальна сила) вказує, як відрегулювати швидкість у правильному напрямку. Налаштування шви дкості об’єкта для слідування за ціллю подібне до налаштування ваги нейронної мережі щодо правильної відповіді.
Для персептрона результат має лише два можливих значення: +1 або -1. Тому можливі лише три варіанти помилки. Якщо персептрон вгадає правильну відповідь, тоді припущення дорівнює бажаному результату, а помилка дорівнює 0. Якщо правильна відповідь -1, а персептрон видав +1, тоді помилка дорівнює -2. Якщо правильна відповідь +1, а персептрон видав -1, тоді помилка дорівнює +2. Цей процес узагальнено у наступній таблиці:
Бажаний результат | Здогадка | Помилка |
---|---|---|
–1 | –1 | 0 |
–1 | +1 | –2 |
+1 | –1 | +2 |
+1 | +1 | 0 |
Помилка є визначальним фактором у тому, як слід регулювати ваги персептрона. Для будь-якої заданої ваги я хочу обчислити зміну ваги, яку часто називають (або дельтою ваги, символ є грецькою буквою дельта):
Для розрахунку , мені потрібно помножити помилку на вхідні дані:
Отже, нова вага розраховується наступним чином:
Щоб зрозуміти, чому це працює, подумайте ще раз про керування. Зусилля керма — це, по суті, помилка у швидкості. Застосовуючи керувальну силу як прискорення (або ), швидкість корегується для руху в правильному напрямку. Це те, що я хочу зробити з вагами нейронної мережі. Я хочу налаштувати їх у правильному напрямку в залежності від помилки.
Однак із керуванням у мене була додаткова змінна, яка контролювала здатність до керування: максимальне значення сили. Висока максимальна сила дозволяла об’єкту прискорюватися і швидко повертати, тоді як менша сила призводила до повільнішого регулювання швидкості. Нейронна мережа використовуватиме подібну стратегію зі змінною під назвою константа навчання:
Висока константа навчання призводить до більш різких змін ваги. Це може допомогти персептрону швидше знайти рішення, але це також збільшує ризик перевищення оптимальної ваги. Невелика константа навчання регулюватиме ваги повільніше і потребуватиме більше часу на навчання, але дозволить мережі вносити невеликі коригування, які можуть підвищити загальну точність.
Припускаючи додану до класу Perceptron
властивість learningConstant
, я можу написати метод навчання для персептрона, дотримуючись кроків, які описав раніше:
train(inputs, desired) {
Крок 1: надамо вхідні дані та відому відповідь. Вони передаються як аргументи до методу train().
let guess = this.feedForward(inputs);
Крок 2: зробимо здогадку відповідно до введених даних.
let error = desired - guess;
Крок 3: обчислимо помилку (різниця між бажаним і припущеним значеннями).
for (let i = 0; i < this.weights.length; i++) {
this.weights[i] = this.weights[i] + error * inputs[i] * this.learningConstant;
}
Крок 4: Налаштуємо всі ваги відповідно до помилки та константи навчання.
}
Ось повний клас Perceptron
:
class Perceptron {
constructor(totalInputs) {
this.weights = [];
this.learningConstant = 0.01;
Персептрон зберігає свої ваги і константу навчання.
for (let i = 0; i < totalInputs; i++) {
this.weights[i] = random(-1, 1);
}
Напочатку ваги вибираються випадково.
}
feedForward(inputs) {
let sum = 0;
for (let i = 0; i < this.weights.length; i++) {
sum += inputs[i] * this.weights[i];
}
return this.activate(sum);
}
Повернення результату на основі вхідних даних.
activate(sum) {
if (sum > 0) {
return 1;
} else {
return -1;
}
}
Можливі результати: +1 або -1.
train(inputs, desired) {
let guess = this.feedForward(inputs);
let error = desired - guess;
for (let i = 0; i < this.weights.length; i++) {
Навчання мережі на основі відомих даних.
this.weights[i] = this.weights[i] + error * inputs[i] * this.learningConstant;
}
}
}
Щоб навчити персептрон, мені потрібен набір вхідних даних із відомими відповідями. Однак для сценарію з ксерофітами й гідрофітами я не маю реального набору даних (чи часу, щоб дослідити та зібрати його). Насправді мета цієї демонстрації не в тому, щоб показати вам, як класифікувати рослини. Йдеться про те, як персептрон може дізнатися, чи знаходяться точки вище, чи нижче лінії на графіку, тому підійде будь-який набір точок. Іншими словами, для даного прикладу я можу просто вигадати дані.
Те, що я описую називають синтетичними даними — штучно згенерованими даними, які часто використовуються у машинному навчанні для створення контрольованих сценаріїв для навчання і тестування. У цьому випадку мої синтетичні дані складатимуться з набору випадкових вхідних точок, кожна з яких має відому відповідь, яка вказує, чи знаходиться точка вище, чи нижче лінії. Щоб визначити лінію і згенерувати дані, я використаю просту алгебру. Такий підхід дозволяє мені наочно продемонструвати процес навчання та показати, як навчається персептрон.
Отже, постає питання, як мені вибрати точку і дізнатися, чи вона знаходиться вище або нижче лінії (без нейронної мережі)? Пряму можна описати як сукупність точок, де -координата кожної точки є функцією її -координати:
Для прямої лінії (зокрема, лінійної функції) залежність можна записати таким чином:
Тут — це нахил лінії, а — значення , коли дорівнює 0 (точка перетину ). Ось конкретний приклад із відповідним графіком на малюнку 10.8.
Я довільно виберу це рівняння для моєї лінії та напишу відповідну функцію:
function f(x) {
return 0.5 * x - 1;
}
Функція для обчислення y на основі x уздовж лінії.
Тепер питання у тому, що полотно p5.js за замовчуванням має початкову координату у верхньому лівому куті, а вісь спрямована вниз. Для поточного прикладу я додав у код зміщення початкової точки у центр полотна, щоб переорієнтувати його до більш традиційного вигляду декартового простору:
translate(width / 2, height / 2);
Переміщення початкових координат (0, 0) до центру.
scale(1, -1);
Переворот орієнтації для y-осі, щоб позитивні значення йшли вгору від початкової точки.
Тепер я можу вибрати випадкову точку у 2D просторі:
let x = random(-100, 100);
let y = random(-100, 100);
Як дізнатися, що ця точка вище або нижче лінії? Лінійна функція повертає значення на лінії для переданої -позиції. Я позначатиму це як :
let yline = f(x);
y-позиція на лінії.
Якщо значення , яке я досліджую, знаходиться над лінією, то воно буде більшим за , як на малюнку 10.9.
Ось код для цієї логіки:
let desired = -1;
if (y > yline) {
Почнемо зі значення -1.
desired = 1;
Якщо y знаходиться над рискою, відповідь стає +1.
}
Далі я можу створити масив вхідних даних для виведення вихідного значення desired
:
let trainingInputs = [x, y, 1];
Не забудьте включити похибку (значення баєса)!
Припускаючи, що у мене є змінна perceptron
, я можу навчити її, надаючи вхідні дані разом із бажаною відповіддю:
perceptron.train(trainingInputs, desired);
Якщо я навчаю персептрон на новій випадковій точці (та її відповіді) для кожної ітерації функції draw()
, він поступово покращуватиме свою класифікацію точок для кращого визначення їх над або під лінією.
let perceptron;
Персептрон.
let training = [];
Масив для навчальних даних.
let count = 0;
Лічильник для почергового відстеження точок тренувальних даних.
function f(x) {
return 0.5 * x + 1;
}
Формула лінії.
function setup() {
createCanvas(640, 240);
perceptron = new Perceptron(3, 0.0001);
Персептрон матиме три вхідні значення (включаючи похибку) і швидкість навчання 0.0001.
for (let i = 0; i < 2000; i++) {
Створення 2000 тренувальних точок.
let x = random(-width / 2, width / 2);
let y = random(-height / 2, height / 2);
training[i] = [x, y, 1];
}
}
function draw() {
background(255);
translate(width / 2, height / 2);
scale(1, -1);
Переорієнтація полотна відповідно до традиційної декартової площини.
stroke(0);
strokeWeight(2);
line(-width / 2, f(-width / 2), width / 2, f(width / 2));
Малювання лінії.
let x = training[count][0];
let y = training[count][1];
Отримання координат (x, y) для поточної точки даних навчання.
let desired = -1;
if (y > f(x)) {
desired = 1;
}
Який бажаний результат?
perceptron.train(training[count], desired);
Тренування персептрону.
count = (count + 1) % training.length;
Для ефекту анімації тренування відбувається на одній точці за раз.
for (let dataPoint of training) {
let guess = perceptron.feedForward(dataPoint);
if (guess > 0) {
fill(127);
} else {
fill(255);
}
strokeWeight(1);
stroke(0);
circle(dataPoint[0], dataPoint[1], 8);
}
Малювання всіх точок і розфарбування їх відповідно до вихідних результатів персептрона.
}
У прикладі 10.1 навчальні дані візуалізуються поруч із лінією цільового рішення. Кожн а точка представляє фрагмент навчальних даних, а її колір визначається поточною класифікацією персептрона — сірий для +1 або білий для -1. Я використовую невелику константу навчання (0.0001), щоб уповільнити з часом процес уточнення системою своїх класифікацій.
Цікавий аспект цього прикладу полягає у взаємозв’язку між вагами персептрона і характеристиками лінії, що розділяє точки, зокрема, кутом нахилу лінії та точкою перетину по вісі ( і із рівняння ). Ваги в цьому контексті не є просто довільними або “магічними” значеннями — вони безпосередньо пов’язані з геометрією набору даних. У цьому випадку я використовую лише 2D дані, але для багатьох застосувань машинного навчання дані існують у набагато більших вимірах. Вагові коефіцієнти нейронної мережі допомагають орієнтуватися у цих вимірах, визначаючи гіперплощини або межі рішень, які сегментують і кл асифікують дані.
Вправа 10.1
Змініть код із прикладу 10.1, щоб під час процесу навчання також малювати поточну межу рішення персептрона — його найкраще припущення щодо того, де має бути лінія. Підказка: використовуйте поточні ваги персептрона, щоб обчислити рівняння лінії.
Хоча цей приклад персептрона пропонує концептуальну основу, набори даних реального світу часто мають більш різноманітні та динамічні діапазони вхідних значень. Для спрощеного сценарію тут діапазон значень для більший, ніж для , через розмір полотна 640 на 240. П опри це, приклад все ще працює — зрештою, функція активації знака не покладається на конкретні вхідні діапазони даних і це таке доволі просте завдання бінарної класифікації.
Однак дані реального світу часто мають набагато більшу складність з точки зору вхідних діапазонів. Для цього у машинному навчанні критично важливим кроком є нормалізація даних. Нормалізація даних передбачає таке відображення навчальних даних, щоб гарантувати, що всі вхідні дані (і вихідні) відповідали єдиному діапазону — як правило, від 0 до 1 або, можливо, від -1 до 1. Цей процес може покращити ефективність навчання і запобігти домінуванню окремих вхідних даних у процесі тренування. У продовженні розділу я вбудую у процес навчання нормалізацію даних, використовуючи бібліотеку ml5.js.
Вправа 10.2
Чи можете ви замість використання навчання з учителем, навчити нейронну мережу знаходити правильні ваги за допомогою ГА?
Вправа 10.3
Додайте до прикладу нормалізацію даних. Чи покращує це ефективність навчання?
Розміщення “мережі” у нейронній мережі
Персептрон може мати кілька вхідних даних, але це все одно єдиний самотній нейрон. На жаль, це обмежує коло проблем, які він може вирішити. Справжня сила нейронних мереж проявляється у мережевій частині. З’єднайте разом кілька нейронів і ви зможете розв’язувати проблеми набагато більшої складності.
Якщо ви прочитаєте підручник зі штучного інтелекту, там буде сказано, що персептрон може розв’язувати лише задачі лінійної роздільності. Якщо набір даних є лінійно роздільним, ви можете побудувати його графік і класифікувати на дві групи, просто провівши пряму лінію (див. малюнок 10.10, ліворуч). Приклад класифікації рослин на ксерофіти або гідрофіти є лінійно роздільною проблемою.
А тепер уявіть, що ви класифікуєте рослини за кислотністю ґрунту (по вісі ) і температурою (по вісі ). Деякі рослини можуть процвітати на кислих ґрунтах, але лише у вузькому діапазоні температур, тоді як інші рослини віддають перевагу менш кислим ґрунтам, але витримують ширший діапазон температур. Між двома змінними існує складніший зв’язок, тому неможливо провести пряму лінію, щоб розділити дві категорії рослин на ацидофіти та базофіти (див. малюнок 10.10, праворуч). Самотній персептрон не може впоратися з таким типом нелінійно роздільної проблеми. (Застереження: я вигадую ці приклади, тож якщо ви ботанік, будь ласка, дайте мені знати, чи я близький до реальності.)
Одним із найпростіших прикладів нелінійно роздільної задачі є XOR (виключна диз’юнкція). Це логічний оператор, подібний до більш звичних логічних операцій AND (кон’юнкція) та OR (диз’юнкція). Щоб AND разом давали істину, і і повинні бути істинними. З використанням операції OR результат може бути істинним, коли істинним буде або (або обидва). Це обидві задачі, які можна розділити лінійно. Таблиці істинності на малюнку 10.11 показують їх просторові розв’язки. Кожне значення або у таблиці показує результат для певної комбінації вхідних даних або . Погляньте, як можна намалювати пряму лінію, що відокремить в такому випадку істинні результати від хибних.
Оператор XOR є еквівалентом сукупності операторів ((OR) AND (NOT AND)). Іншими словами результат операції XOR оцінюється як , лише якщо одне із вхідних даних є істинним. Якщо обидва значення хибні або обидва істинні, то результат буде . Для прикладу припустімо, що ви любите піцу з ананасами й любите піцу з грибами, але складіть їх разом і вийде таке собі! І звичайна піца теж не годиться!
Таблиця істинності для операції XOR на малюнку 10.12 не є лінійно роздільною. Спробуйте намалювати пряму ліні ю, щоб відокремити результати з від , але не вийде!
Той факт, що потенціал персептрона не здатний вирішити навіть щось таке просте, як XOR, може здатися надзвичайно обмеженим. Але що, якби я створив мережу з двох персептронів? Якщо один персептрон може вирішити лінійно роздільні результати операції OR, а інший один персептрон може розв’язати лінійно відокремлені результати операції NOT AND, тоді комбінація двох персептронів може розв’язати нелінійно відокремлені результати XOR.
Коли ви поєднуєте кілька персептронів, то отримуєте багатошаровий персептрон, мережу з багатьох нейронів (див. малюнок 10.13). Деякі з них є вхідними нейронами й отримують початкові вхідні дані, деякі можуть бути частиною так званого прихованого шару (оскільки вони безпосередньо не пов’язані ні з вхідними даними, ані з вихідними), а також є вихідні нейрони, з яких зчитуються результати.
Досі я візуалізував окремий персептрон одним кругом, що представляв нейрон, який обробляв свої вхідні сигнали. Тепер, коли я переходжу до більших мереж, типовіше представляти всі елементи (входи, нейрони, виходи) у вигляді кругів зі стрілками, які вказують на потік даних. На малюнку 10.13 ви можете побачити зображення вхідних даних і баєси, що надходять у прихований шар, який потім перетікає у вихід.
Навчання простого персептрона досить просте: ви пропускаєте дані та оцінюєте, як змінити ваги вхідних даних відповідно до помилки. Однак із багатошаровим персептроном процес навчання стає складнішим. Загальний вихід мережі все ще генерується по суті таким же чином, як і раніше: вхідні дані, помножені на ваги, додаються та передаються через різні рівні мережі. І ви все ще використовуєте припущення мережі, щоб обчислити помилку (бажаний результат мінус припущення). Але тепер існує досить багато зв’язків між рівнями мережі, кожен з яких має свою вагу. Як дізнатися, який внесок кожного нейрона або їх з’єднань має бути у загальній помилці мережі та як слід робити коригування?
Рішенням для оптимізації ваг багатошарової мережі є метод зворотного поширення помилки. Цей процес приймає помилку і передає її назад через мережу, щоб вона могла налаштувати ваги всіх з’єднань пропорційно до їх внеску у загальну помилку. Деталі зворотного поширення виходять за рамки цієї книги. Алгоритм використовує різноманітні функції активації (одним із класичних прикладів є сигмоїдна функція), а також деякі обчислення. Якщо вам цікаво продовжити цей шлях і дізнатися більше про те, як працює зворотне поширення, ви можете знайти мій проєкт “Toy Neural Network” із супровідними відеоуроками на вебсайті Coding Train. Вони показуються усі етапи розв’язання XOR, використовуючи багаторівневу мережу прямого поширення з методом зворотного поширення. Однак для продовження цього розділу я хочу отримати допомогу і зателефонувати другу.
Машинне навчання з ml5.js
Цей друг — ml5.js. Ця бібліотека машинного навчання може обробляти деталі складних процесів, як-от зворотне поширення, тож нам з вами не доведеться про них турбуватися. Як я вже згадував у цьому розділі, ml5.js має на меті надати дружню точку входу для тих, хто тільки починає знайомитися з машинним навчанням і нейронними мережами, водночас використовуючи за лаштунками потужність Google TensorFlow.js.
Щоб використовувати ml5.js у програмі, бібліотеку потрібно імпортувати через тег <script>
у вашому файлі index.html, подібно до того, як ви робили це для Matter.js і Toxiclibs.js у Розділі 6:
<script src="https://unpkg.com/ml5@l/dist/ml5.min.js"></script>
Моя мета до кінця цього розділу — показати роботу з ml5.js шляхом розробки системи, яка може розпізнавати рухи комп’ютерної миші. Це підготує вас до Розділу 11, де я додам “мозок” нейронної мережі до агента, що керуватиметься автономно, і зв’яжу машинне навчання з попередніми подіями цієї книги. Однак спочатку я хотів би більш загально поговорити про етапи навчання моделі багатошарової нейронної мережі за допомогою навчання під наглядом. Опис цих кроків висвітлить важливі рішення, які вам доведеться прийняти перед розробкою моделі навчання, представить синтаксис бібліотеки ml5.js і надасть вам контекст, який знадобиться перед навчанням власних моделей машинного навчання.
Життєвий цикл машинного навчання
Життєвий цикл моделі машинного навчання зазвичай розбивається на сім етапів:
- Збір даних. Дані складають основу будь-якого завдання машинного навчання. Цей етап може включати проведення експериментів, введення значень вручну, отримання загальнодоступних даних або безліч інших методів (наприклад, створення синтетичних даних).
- Підготовка даних. Необроблені дані часто не мають формату, придатного для алгоритмів машинного навчання. Вони також можуть мати якісь повторювані чи відсутні значення або містити викиди, що перекошують дані. Такі невідповідності можливо доведеться скоригувати вручну. Крім того, як я вже згадував раніше, нейронні мережі найкраще працюють із нормалізованими даними, які мають значення, масштабовані відповідно до стандартного діапазону. Іншою важливою частиною підготовки даних є розділення їх на окремі набори для навчання, валідації й тестування. Навчальні дані використовуються для навчання моделі (крок 4), тоді як дані валідації та тестування (різниця незначна — докладніше про це пізніше) відкладаються і резервуються для оцінки продуктивності моделі (крок 5).
- Вибір моделі. Розробка архітектури нейронної мережі. Різні моделі можуть бути більш підхожими для певних типів даних і відповідних виходів.
- Тренування моделі. Проведення навчальної частини даних через модель і регулювання моделлю ваг нейронної мережі на основі її помилок. Цей процес відомий як оптимізація: модель налаштовує ваги таким чином, щоб вони призводили до найменшої кількості помилок.
- Оцінка моделі. Пам’ятаєте валідаційні дані, які були відкладені на кроці 2? Оскільки ці дані не використовувалися під час навчання, вони дають змогу оцінити наскільки добре модель працює на нових, невідомих даних.
- Налаштування параметрів. На процес навчання впливає набір параметрів (часто називають гіперпараметрами), таких як швидкість навчання, яка визначає, наскільки сильно модель повинна коригувати свої ваги на основі помилок у своїх передбаченнях. У прикладі персептрона це була константа навчання. Тонко налаштувавши ці параметри й переглянувши крок 4 (навчання), 3 (вибір моделі) або навіть 2 (підготовка даних), ви часто зможете покращити продуктивність моделі.
- Розгортання моделі. Коли модель навчена і її продуктивність оцінена задовільно, настає час використовувати модель у реальному світі з новими даними!
Ці кроки є наріжним каменем керованого машинного навчання. Однак, попри те, що 7 – справді чудове число, я думаю, що я пропустив ще один важливий крок. Я назву його кроком 0.
- Визначення проблеми. Цей початковий крок визначає проблему, яку потрібно вирішити. Яка мета? Що ви намагаєтеся досягти або передбачити за допомогою своєї моделі машинного навчання?
Цей нульовий крок інформує всі інші кроки процесу. Зрештою, як ви можете збирати дані та вибирати модель, не знаючи, що ви намагаєтеся зробити? Ви прогнозуєте число? Категорію? Послідовність? Це бінарний вибір, чи є багато варіантів? Такого роду запитання часто зводяться до вибору між двома типами задач, до яких відноситься більшість програм машинного навчання: класифікації та регресії.
Класифікація і регресія
Класифікація — це тип задачі машинного навчання, яка включає прогнозування мітки (також називається категорією або класом) для частини даних. Якщо це звучить знайомо, це тому, що так і є: простий персептрон у прикладі 10.1 був навчений класифікувати точки як точки над або під лінією. Іншим прикладом може бути класифікатор зображень, який намагається вгадати, чи на фотографії зображено кота, чи собаку і присвоїти відповідну мітку (див. малюнок 10.14).
Класифікація не відбувається за допомогою чарівної палички. Спочатку моделі потрібно показати багато прикладів собак і котів з правильними мітками, щоб правильно налаштувати ваги всіх з’єднань. Це тренувальна частина контрольованого навчання.
Класичне “Hello, world!” у світі машинного навчання і навчання під наглядом є проблемою класифікації набору даних MNIST. MNIST, скорочення від Modified National Institute of Standards and Technology – це набір даних, який збирали й опрацьовували Янн ЛеКун, (Інститут Куранта, NYU), Корінна Кортес (Google Labs) і Крістофер Дж. К. Берджес (Microsoft Research). Цей набір даних, який широко використовується для навчання і тестування у сфері машинного навчання, складається з 70 000 рукописних цифр від 0 до 9. Кожне зображення зроблене з градацій сірого кольору і має розмір 28 на 28 пікселів (приклади дивіться на малюнку 10.15). Кожне зображення позначено відповідною цифрою.
MNIST є канонічним прикладом навчального набору даних для класифікації зображень: модель має дискретну кількість категорій на вибір (10, якщо бути точним, ні більше, ні менше). Після того, як модель натренована на 70 000 підписаних зображеннях, мета полягає в тому, щоб вона класифікувала нові зображення і призначала їм відповідну мітку, цифру від 0 до 9.
З іншого боку, регресія — це завдання машинного навчання, для якого прогноз є безперервним значенням, як правило, числом з рухомою крапкою. Регресійна задача може включати кілька вихідних результатів, але для початку простіше думати лише про одне. Наприклад, розглянемо модель машинного навчання, яка прогнозує щоденне споживання електроенергії будинку на основі вхідних факторів, таких як кількість мешканців, розмір будинку і температура на вулиці (див. малюнок 10.16).
Замість того, щоб вибирати з окремого набору вихідних параметрів, мета нейронної мережі тепер полягає в тому, щоб вгадати число — будь-яке число. Чи будинок використає цього дня 30.5 кіловат-годин електроенергії? Або 48.7 кВт·год? Або 100.2 кВт·год? Вихі дним прогнозом може бути будь-яке значення з безперервного діапазону.
Проєктування мережі
Знання того, яку проблему ви намагаєтеся вирішити (крок 0), також має значний вплив на дизайн нейронної мережі, зокрема, на її вхідний і вихідний рівні. Я продемонструю це на іншому класичному прикладі класифікації з галузі науки про дані й машинне навчання: набір даних про квітки ірису. Цей набір даних, який можна знайти у репозиторії Каліфорнійського університету машинного навчання, походить від роботи американського ботаніка Едгара Андерсона.
Андерсон збирав дані про квіти протягом багатьох років у багатьох регіонах Сполучених Штатів і Канади. Щоб дізнатися більше про походження цього знаменитого набору даних, дивіться “The Iris Data Set: In Search of the Source of Virginica” під авторством Ентоні Анвіна та Кіма Клейнмана. Після ретельного аналізу даних Андерсон створив таблицю для класифікації квітів ірисів на три різні види: ірис щетинистий, ірис строкатий та ірис віргінський (див. малюнок 10.17).
Андерсон включив чотири числові атрибути для кожної квітки: довжину чашолистка, ширину чашолистка, довжину пелюстки й ширину пелюстки, усі виміряні в сантиметрах. (Він також записав інформацію про колір, але ці дані, здається, були втрачені.) Кожен запис потім поєднується з відповідною категорією ірису:
Довжина чашолистка | Ширина чашолистка | Довжина пелюстки | Ширина пелюстки | Класифікація |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | Ірис щетинистий |
4.9 | 3.0 | 1.4 | 0.2 | Ірис щетинистий |
7.0 | 3.2 | 4.7 | 1.4 | Ірис строкатий |
6.4 | 3.2 | 4.5 | 1.5 | Ірис строкатий |
6.3 | 3.3 | 6.0 | 2.5 | Ірис віргінський |
5.8 | 2.7 | 5.1 | 1.9 | Ірис віргінський |
У цьому наборі даних перші чотири стовпці (довжина чашолистка, ширина чашолистка, довжина пелюстки, ширина пелюстки) слугують вхідними даними для нейронної мережі. Результатом є класифікація, наведена у п’ятому стовпці. На малюнку 10.18 зображено можливу архітектуру нейронної мережі, яку можна навчити на цих даних.
Зліва знаходяться чотири входи в мер ежу, що відповідають першим чотирьом стовпцям таблиці даних. Праворуч є три можливі виходи, кожен з яких представляє одну з міток для виду квітки ірису. Між ними знаходиться прихований рівень, який, як згадувалося раніше, додає складності архітектурі мережі, необхідної для обробки нелінійно розділених даних. Кожен вузол у прихованому шарі з’єднаний з кожним вузлом, який знаходиться перед і після нього. Це зазвичай називають повністю з’єднаним або щільним шаром.
Ви також можете помітити відсутність явних вузлів баєса на цій діаграмі. У той час як похибки відіграють важливу роль у виході кожного нейрона, вони часто залишаються поза візуальним відображенням, щоб зберегти діаграми чистими й зосередженими на первинному потоці даних. (Бібліотека ml5.js зрештою керуватиме цим самостійно.)
Мета нейронної мережі полягає в тому, щоб “активувати” правильний вихід для вхідних даних, подібно до того, як персептрон виводить +1 або -1 для своєї єдиної бінарної класифікації. У цьому випадку вихідні значення схожі на сигнали, які допомагають мережі вирішити, яку мітку виду ірису призначити. На йвище обчислене значення активується, щоб показати найкраще припущення мережі щодо класифікації.
Ключовий висновок тут полягає в тому, що мережа класифікації повинна мати стільки входів, скільки значень для кожного елемента в наборі даних, і стільки виходів, скільки є категорій. Що стосується прихованого шару, його дизайн менш фіксований. Прихований шар на малюнку 10.18 має п’ять вузлів, але це число цілком довільне. Архітектури нейронних мереж можуть сильно відрізнятися і кількість прихованих вузлів часто визначається методом проб та помилок або іншими методами вгадування (так звані евристики). У контексті цієї книги я буду покладатися на ml5.js для автоматичного налаштування архітектури на основі вхідних і вихідних даних.
А як щодо входів і виходів у регресійному сценарії, як-от з прикладом споживання електроенергії домогосподарствами, про який я згадував раніше? Я створю набір даних для цього сценарію зі значеннями, що представляють мешканців і розмір будинку, денну температуру та відповідне споживання електроенергії. Це дуже схоже на синтетичний набір даних, враховуючи, що вони не зібрані для сценарію реального світу. Але в той час, як синтетичні дані генеруються автоматично, тут я вручну вводжу числа зі своєї власної уяви:
Мешканці | Розмір (m²) | Температура на вулиці (°C) | Споживання електроенергії (кВт·год) |
4 | 150 | 24 | 25.3 |
2 | 100 | 25.5 | 16.2 |
1 | 70 | 26.5 | 12.1 |
4 | 120 | 23 | 22.1 |
2 | 90 | 21.5 | 15.2 |
5 | 180 | 20 | 24.4 |
1 | 60 | 18.5 | 11.7 |
Нейронна мережа для цієї задачі повинна мати три вхідні вузли, що відповідають першим трьом стовпцям (мешканці, розмір, температура). Тим часом вона повинна мати один вихідний вузол, що представляє четвертий стовпець — припущення мережі щодо споживання електроенергії. І я довільно вкажу, що прихований рівень мережі повинен мати чотири вузли, а не п’ять. На малюнку 10.19 показана архітектура цієї мережі.
На відміну від мережі класифікації ірисів, яка вибирає з трьох міток а, отже, має три виходи, ця мережа намагається спрогнозувати лише одне число, тому має лише один вихід. Однак я зауважу, що єдиний вихід не є вимогою регресії. Модель машинного навчання може також виконувати регресію, яка передбачає кілька значень і в такому випадку модель матиме кілька виходів.
Синтаксис ml5.js
Бібліотека ml5.js — це набір моделей машинного навчання, до яких можна отримати доступ за допомогою синтаксису ml5.functionName()
. Наприклад, щоб використати попередньо навчену модель, яка визначає положення рук, ви можете викор истовувати ml5.handPose()
. Для класифікації зображень можна використовувати ml5.imageClassifier()
. Хоча я закликаю вас вивчити все, що може запропонувати ml5.js (я згадаю деякі з цих попередньо підготовлених моделей у майбутніх ідеях для вправ), у цій главі я зосереджуся лише на одній функції ml5.neuralNetwork()
, яка створює порожню нейронну мережу для навчання.
Щоб скористатися цією функцією, ви повинні спочатку підготувати об’єкт JavaScript, який буде налаштовувати створювану модель. У властивостях можна задати деякі з аспектів загальної картини, які я щойно обговорював. Це завдання класифікації чи регресії? Скільки входів і виходів братиме участь у процесі? Я почну з визначення потрібного типу завдання, щоб модель виконувала "regression"
або "classification"
:
let options = { task: "classification" };
let classifier = ml5.neuralNetwork(options);
Це, однак, дає ml5.js мало можливостей для розробки мережевої архітектури. Додавання входів і виходів завершить решту головоломки. Класифікація квітки ірису має чотири входи й три можливі вихідні мітки. Це можна налаштувати як частину об’єкта options
з єдиним цілим числом для кількості вхідних даних і масиву рядків зі списком вихідних міток:
let options = {
inputs: 4,
outputs: ["iris-setosa", "iris-virginica", "iris-versicolor"],
task: "classification",
};
let digitClassifier = ml5.neuralNetwork(options);
У сценарії регресії споживання електроенергії було три вхідні значення (мешканці, розмір, температура) і одне вихідне значення (споживання кВт·год). У регресії немає вихідних міток-рядків, тому потрібне лише ціле число, що вказує кількість виходів:
let options = {
inputs: 3,
outputs: 1,
task: "regression",
};
let energyPredictor = ml5.neuralNetwork(options);
Через об’єкт options
ви можете налаштувати багато інших властивостей моделі. Наприклад, ви можете вказати кількість прихованих шарів між входами та виходами (зазвичай їх декілька), кількість нейронів у кожному шарі, які функції активації використовувати тощо. У більшості випадків, однак, ви можете не використовувати ці додаткові параметри й дозволити ml5.js зробити найкраще припущення про те, як спроєктувати модель на основі завдання і наявних даних.
Створення класифікатора рухів
Зараз я пройдуся по етапах життєвого циклу машинного навчання на прикладі проблеми, яка добре підходить для p5.js, створюючи весь код для кожного кроку за допомогою ml5.js. Я почну з кроку 0, сформулювавши проблему. Уявіть на мить, що ви працюєте над інтерактивною програмою, яка реагує на жести або певні рухи. Можливо, жести будуть призначені для створення записів на основі відстеження рухів тіла, але ви хочете почати з чогось набагато простішого — одного руху комп’ютерної миші (див. малюнок 10.20).
Кожен рух може бути записаний як вектор, що простягається від початку до кінцевої точки руху миші. Координати вектора і будуть вхідними даними моделі. Завдання моделі полягатиме в тому, щоб передбачити одну з чотирьох можливих міток для руху: вгору, вниз, вліво або вправо. З дискретним набором можливих результатів це звучить як проблема класифікації. Чотири мітки будуть виходами моделі.
Подібно до деяких демонстрацій ГА-мів у Розділі 9, і як приклад простого персептрона, наведеного раніше у цій главі, проблема, яку я вибираю тут, має відоме рішення і її можна вирішити легше та ефективніше без нейронної мережі. Напрямок вектора можна класифікувати за допомогою функції heading()
і ряду виразів з оператором if
! Однак, використовуючи цей, здавалося б, тривіальний сценарій, я сподіваюся пояснити процес тренування моделі машинного навчання зрозумілим і дружнім способом. Крім того, цей приклад допоможе легко перевірити, чи код працює належним чином. Коли я закінчу, я дам кілька ідей про те, як розширити класифікатор для сценарію, який не зможе обійтися простим використанням if
-операторів.
Збір і підготовка даних
Визначивши проблему, я можу перейти до кроків 1 і 2: збору і підготовки даних. У реальному світі ці кроки можуть бути виснажливими, особливо коли не оброблені дані, які ви збираєте, заплутані та потребують багато початкової обробки. Ви можете думати про це як про необхідність упорядкувати, помити й нарізати всі інгредієнти, перед приготуванням їжі.
Для простоти я хочу замість цього замовити “набір їжі” машинного навчання з уже розділеними та підготовленими інгредієнтами (даними). Таким чином я перейду безпосередньо до самого приготування, до процесу навчання моделі. Зрештою, це насправді лише закуска для того, що стане остаточною стравою у Розділі 11, коли я застосую нейронні мережі до керованих агентів.
З думкою про це, я закодую деякі приклади даних вручну і вручну збережу їх нормалізованими в діапазоні від -1 до +1. Я організую дані у масив об’єктів, об’єднавши -компоненти вектора з підписаною міткою. Я вибираю значення, які на мою думку, чітко вказують у певному напрямку, і призначаю відповідну мітку — по два приклади на кожну мітку:
let data = [
{ x: 0.99, y: 0.02, label: "right" },
{ x: 0.76, y: -0.1, label: "right" },
{ x: -1.0, y: 0.12, label: "left" },
{ x: -0.9, y: -0.1, label: "left" },
{ x: 0.02, y: 0.98, label: "down" },
{ x: -0.2, y: 0.75, label: "down" },
{ x: 0.01, y: -0.9, label: "up" },
{ x: -0.1, y: -0.8, label: "up" },
];
На малюнку 10.21 показані ті самі дані, позначені стрілкам.
У більш реалістичному сценарії я, ймовірно, мав би набагато більший набір даних, який би завантажувався з окремого файлу, а не записувався безпосередньо в код. Наприклад, JSON та CSV є двома популярними форматами для зберігання та завантаження даних. JSON зберігає дані у парах ключ-значення та має той самий формат, що й літерали об’єктів JavaScript. CSV — це формат файлу, який зберігає табличні дані (як електронна таблиця), де значення розділені комами. Ви можете використовувати й багато інших форматів даних, залежно від ваших потреб та середовища програмування з яким ви працюєте.
У реальному світі значення для цього більшого набору даних насправді мають якесь надходження. Можливо, я збирав би дані, попросивши користувачів виконувати певні рухи та записуючи їхні дані, або написавши алгоритм для автоматичного генерування більшої кількості синтетичних даних, які представляють ідеалізовані версії рухів, які потрібні для розпізнавання моделлю. У будь-якому випадку ключовим кроком буде збір різноманітного набору прикладів, які адекватно представляють варіації того, як можуть виконуватися рухи. Однак наразі подивимось як це працює з кількома порціями даних.
Вправа 10.4
Створіть програму p5.js, яка збирає дані рухів від користувачів і зберігає їх у файлі JSON. Для позначення початку і кінця кожного руху ви можете використовувати функції mousePressed()
і mouseReleased()
, а також функцію saveJSON()
, щоб зберегти дані у файл.
Вибір моделі
Тепер я підійшов до третього кроку життєвого циклу машинного навчання — вибору моделі. Ось тут я збираюся дозволити бібліотеці ml5.js виконати важку роботу за мене. Щоб створити модель за допомогою ml5.js, все що мені потрібно зробити, це вказати завдання, вхідні дані й бажані виходи:
let options = {
task: "classification",
inputs: 2,
outputs: ["up", "down", "left", "right"],
debug: true
};
let classifier = ml5.neuralNetwork(options);
Ось воно! Я все зробив! Завдяки ml5.js я можу обійти безліч складнощів, таких як кількість шарів і нейронів для кожного шару, тип функцій активації, які потрібно використовувати, і те, як налаштувати алгоритми для навчання мережі. Бібліотека прийматиме ці рішення за мене.
Звісно, типова архітектура моделі ml5.js може бути не ідеальною для всіх випадків. Раджу додатково прочитати документацію ml5.js, щоб дізнатися більше про те, як налаштувати модель. Я також зазначу, що ml5.js може визначати вхідні та вихідні дані з набору даних, тому ці властивості не обов’язково включати тут в об’єкт options
. Однак для ясності (і оскільки мені потрібно буде вказати їх для подальших прикладів), я включаю їх тут явним чином.
Якщо для властивості debug
встановлено значення true
, тоді вмикається візуальний інтерфейс для процесу навчання. Це корисний інструмент для виявлення потенційних проблем під час тренування і для кращого розуміння того, що відбувається за лаштунками. Як виглядає цей інтерфейс ви побачите далі у цьому розділі.
Навчання моделі
Тепер, коли у мене є дані у змінній data
і нейронна мережа, ініціалізована у змінній classifier
, я готовий навчати модель. Цей процес починається з додавання до моделі даних. І виявляється, що я для цього ще не закінчив їх підготовку.
Наразі мої дані акуратно організовано в масиві об’єктів, кожен з яких містить та -компоненти вектора і відповідну мітку. Це типовий формат для навчальних даних, але ml5.js безпосередньо його не використовує. (Звісно, я міг би спочатку організувати дані у форматі, який розпізнає ml5.js, але я включаю цей додатковий крок, оскільки він, імовірно, буде необхідним, коли ви використовуєте набір даних зібраний або отриманий з інших джерел.) Щоб додати дані до моделі, мені потрібно відокремити входи від виходів, щоб ця модель розуміла де з них які.
Бібліотека ml5.js пропонує достатню гнучкість щодо кількості форматів, які вона приймає, але я оберу використання масиву: один для входів — inputs
і один для ви ходів — outputs
. Щоб реорганізувати кожен елемент даних і додати його до моделі я можу використати цикл:
for (let item of data) {
let inputs = [item.x, item.y];
Масив із двох чисел для вхідних даних.
let outputs = [item.label];
Одна мітка для вихідного значення.
classifier.addData(inputs, outputs);
Додавання навчальних даних до класифікатора.
}
Що я тут зробив, так це встановив форму даних. У машинному навчанні цей термін описує розміри й структуру даних. Структура визначає організацію даних у вигляді рядків, стовпців і, потенційно, навіть глибше, з додатковими вимірами. Розуміння форми ваших даних має вирішальне значення, оскільки воно визначає спосіб структурування моделі.
Тут форма вхідних даних є 1D масивом, що містить два числа, які представляють і . Вихідні дані, так само, є 1D масивом, що містить лише одну мітку. Кожна частина даних, що надходить і виходить з мережі, буде відповідати цьому шаблону. Хоча це невеликий і простий приклад, він гарно відображає багато реальних сценаріїв, у яких вхідні дані представлені в масиві чисельними значеннями, а вихідні мітки є рядками.
Після передачі даних у класифікатор classifier
, ml5.js надає допоміжний метод для їх нормалізації. Як я вже зазначав, нормалізація даних (налаштування масштабу до стандартного діапазону) є критичним кроком у процесі машинного навчання:
classifier.normalizeData();
Normalize the data.
У цьому випадку закодовані вручну дані були обмежені діапазоном від -1 до +1 від початку, тому виклик методу normalizeData()
тут, ймовірно, зайвий. Проте цей виклик важливо продемонструвати. Завчасна нормалізація ваших даних як частина етапу попередньої підготовки безумовно спрацює, але наявність методу автоматичної нормалізації у ml5.js є хорошою підмогою!
Тепер серце процесу машинного навчання — власне навчання моделі. Ось код:
classifier.train(finishedTraining);
Метод train() ініціює процес навчання.
function finishedTraining() {
console.log("Training complete!");
}
Функція зворотного виклику, що спрацює після завершення навчання.
Так, це все! Адже важка робота вже завершена. Дані були зібрані, підготовлені та введені у модель. Все, що залишається, це викликати метод train()
, розслабитися і дозволити ml5.js робити свою справу.
Насправді це не настільки просто. Якби я запустив написаний код, а потім перевірив модель, результати, ймовірно, були б неадекватними. Ось де в гру вступає ще один ключовий аспект машинного навчання — епохи. Метод train()
говорить нейронній мережі почати процес навчання. Але як довго потрібно тренуватися? Ви можете розглядати епоху як один раунд практики, один цикл використання всього навчального набору даних для оновлення ваг нейронної мережі. Взагалі кажучи, чим більше епох ви проходите, тим краще працюватиме мережа, але в певний момент віддача буде зменшуватися. Кількість епох можна встановити, передавши у метод train()
об’єкт з відповідними опціями:
let options = { epochs: 25 };
Встановлення кількості епох навчання.
classifier.train(options, finishedTraining);
Кількість епох є прикладом гіперпараметра — глобального параметра для процесу навчання. Через об’єкт опцій ви можете встановлювати й інші параметри (наприклад, швидкість навчання), але я залишусь зі стандартними значеннями. Ви можете прочитати більше про параметри налаштування у документації ml5.js.
Другий аргумент методу train()
необов’язковий, але його добре включати. Він визначає функцію зворотного виклику, яка запускається після завершення процесу навчання — у цьому випадку це функція finshedTraining()
. Це корисно, щоб дізнатися, коли можна переходити до наступних кроків нашого коду. Інший додатковий зворотний виклик, який я зазвичай називаю whileTraining()
, запускається після кожної епохи. Однак для моїх цілей досить знати коли навчання закінчено повністю!
Зворотні виклики
Функція зворотного виклику у JavaScript — це фу нкція, яку ви насправді не викликаєте самі. Замість цього ви надаєте її як аргумент іншій функції, маючи намір автоматично викликати її пізніше (зазвичай це пов’язано з якоюсь подією, наприклад клацанням миші). Ви бачили це раніше під час роботи з Matter.js у Розділі 6, де ви вказували функцію для виклику щоразу, коли виявлялося зіткнення.
Зворотні виклики потрібні для асинхронних операцій, коли ви хочете, щоб ваш код продовжував анімацію або виконував інші дії, очікуючи на завершення певного завдання (наприклад, тренування моделі машинного навчання). Класичним прикладом такого підходу у p5.js є завантаження даних у програму за допомогою функції loadJSON()
.
JavaScript також надає більш сучасний підхід для обробки асинхронних операцій, відомих як promises. З промісами ви можете використовувати такі ключові слова, як async
і await
, щоб ваш асинхронний код виглядав як більш традиційний синхронний код. Хоча ml5.js також підтримує цей стиль, я буду використовувати зворотні виклики, щоб мій код залишався узгодженими зі стилем p5.js.
Оцінка моделі
Якщо при початковому виклику ml5.neuralNetwork()
, параметр debug
було встановлено як true
, то після запуску методу train()
повинен з’явитися візуальний інтерфейс, який перекриє більшу частину сторінки й полотна p5.js (див. малюнок 10.22). Цей інтерфейс, який називається Visor, представляє етап оцінювання.
Visor походить від TensorFlow.js (який лежить в основі ml5.js) і містить графік, який надає зворотний зв’язок у режимі реального часу щодо прогресу навчання. На цьому графіку на осі відкладаються втрати моделі від кількості епох уздовж осі . Втрати — це міра того, наскільки далекі прогнози моделі від правильних результатів, наданих навчальними даними. Він кількісно визначає загальну похибку моделі. Коли починається навчання, втрати зазвичай бувають великими, оскільки модель ще нічого не навчилася. В ідеалі, коли модель тренується через більшу кількість епох, вона має покращувати свої прогнози, а втрати мають зменшуватися. Якщо зі збільшенням епох графік опускається, це хороший знак!
Проведення навчання протягом 200 епох, зображених на малюнку 10.21, може здатися вам дещо надмірним. У реальному сценарії з більшими даними я б, ймовірно, використав менше епох, наприклад 25, які я вказав у вихідному фрагменті коду. Однак, оскільки набір даних тут дуже малий, більша кількість епох допомагає моделі отримати достатньо практики з даними. Пам’ятайте, що це іграшковий приклад, спрямований на розуміння концепцій, а не на створення складної моделі машинного навчання.
Під графіком Visor також показує підсумкову таблицю моделі з деталями архітектури моделі нижнього рівня, яку за лаштунками створив TensorFlow.js. Таблиця включає назви шарів, кількість нейронів на шар (у стовпці “Output Shape”) і кількість параметрів, тобто загальну кількість ваг, по одному для кожного з’єднання між двома нейронами. У цьому випадку dense_Dense1 — це прихований шар із 16 нейронами (кількість вибрана ml5.js), а dense_Dense2 — вихідний рівень із 4 нейронами, по одному для кожної категорії класифікації. (TensorFlow.js не розглядає вхідні дані як окремий рівень, вони радше є лише початковою точкою потоку даних.) Позначка “batch” у стовпці “Output Shape” не стосується конкретного числа, але вказує, що модель може обробляти змінну кількість навчальних даних (пакет) для будь-якого окремого циклу навчання моделі.
Коли я вперше окреслив етапи життєвого циклу машинного навчання, то зазначив, що для допомоги в процесі оцінювання, підготовка даних зазвичай передбачає поділ набору даних на три частини:
- Навчання (тренування): основний набір даних, який використовується для навчання моделі
- Валідація: підмножина даних, яка використовується для перевірки моделі під час навчання, зазвичай наприкінці кожної епохи
- Тестування: додаткові незмінні дані, які ніколи не розглядаються під час процесу навчання і потрібні для визначення кінцевої продуктивності моделі після завершення навчання
Можливо, ви помітили, що я ніколи цього не робив. Для простоти я замість цього використав увесь набір даних для навчання. Зрештою, мій набір даних містить лише вісім записів — це занадто мало, щоб розділити його на три набори! З великим набором даних цей трьохчастинний розподіл буде більш доцільним.
Однак використання такого невеликого набору може призвести до перенавчання моделі: модель стає занадто налаштованою на конкретні особливості навчальних даних, що робить її значно менш ефективною під час роботи з новими, небаченими раніше даними. Основною причиною використання валідаційного набору полягає у моніторингу моделі під час процесу навчання. Під час навчання, якщо точність моделі поліпшується на навчальних даних, але погіршується на валідаційних, це є вагомим показником того, що може статися перенавчання. (Тестовий набір зберігається виключно для остаточної оцінки, однієї з останніх можливостей оцінити ефективність моделі після завершення тренування.)
Для більш реалістичних сценаріїв ml5.js надає спосіб розділення даних, а також автоматичні функції для використання валідаційних даних. Якщо ви хочете піти далі, то можете ознайомитися з повним набором прикладів нейронної мережі на вебсайті ml5.js.
Налаштування параметрів
Після етапу оцінювання зазвичай відбувається ітеративний процес коригування гіперпараметрів і повторного проходження навчання для досягнення найкращої продуктивності моделі. Хоча ml5.js пропонує можливості для налаштування параметрів (про які ви можете дізнатися у довідці бібліотеки), він насправді не спрямований на внесення низькорівневих тонких коригувань моделі. Якщо ви хочете розібратися з цим кроком більш детально, найкращим вибором може бути безпосереднє використання TensorFlow.js, оскільки він пропонує ширший набір інструментів і дозволяє контролювати процес навчання на нижчому рівні.
У цьому випадку налаштовувати параметри не обов’язково. Графік у Visor показує втрати до 0.1, що досить точно для моїх цілей. Я готовий рухатися далі.
Розгортання моделі
Нарешті настав час розгорнути модель і побачити результати цієї важкої роботи. Зазвичай це передбачає інтеграцію моделі в окрему програму для прогнозування або прийняття рішень на основі нових, раніше невідомих даних. Для цього ml5.js пропонує зручну функцію save()
, щоб зберегти навчену модель у файлі з однієї програми та функцію load()
для її завантаження і використання в іншій програмі. Це позбавляє вас від необхідності перенавчати модель з нуля щоразу, коли вам це потрібно.
Хоча модель, як правило, використовується в іншій прог рамі від тої де її було навчено, для спрощення я збираюся розгорнути модель у тій самій програмі. Після завершення процесу навчання отримана модель, по суті, вже розгорнута і доступна у поточній програмі. Вона зберігається у змінній classifier
і може використовуватися для прогнозування, передаючи нові дані моделі через метод classify()
. Форма даних, яка передається у classify()
має збігатися з формою вхідних даних, які використовуються під час навчання — у цьому випадку це два числа з рухомою крапкою, що представляють і компоненти вектора напрямку:
let direction = createVector(1, 0);
Ручне створення вектора.
let inputs = [direction.x, direction.y];
Перетворення x і y компонентів у вхідний масив.
classifier.classify(inputs, gotResults);
Класифікування моделлю вхідних даних.
Другим аргументом методу classify()
є функція зворотного виклику, яка отримає доступ до результатів:
function gotResults(results) {
console.log(results);
}
Прогноз моделі передається у функцію зворотного виклику у вигляді аргументу, який у коді я назв ав results
. Усередині результату ви знайдете масив можливих міток, відсортованих за вірогідністю і значення ймовірності, яку модель присвоює кожній мітці. Ці ймовірності показують рівень вірогідності моделі у цьому конкретному передбаченні. Вони варіюються від 0 до 1, причому значення, ближчі до 1, вказують на вищу ймовірність, а значення ближчі до 0 — про нижчу:
[
{
"label": "right",
"confidence": 0.9669702649116516
},
{
"label": "up",
"confidence": 0.01878807507455349
},
{
"label": "down",
"confidence": 0.013948931358754635
},
{
"label": "left",
"confidence": 0.00029277068097144365
}
]
У цьому прикладі вихідних даних модель має високу впевненість (приблизно 96.7 відсотка), що правильна мітка це "right"
, тоді як для мітки "left"
вона має мінімальну впевненість (0.03 відсотка). Значення впевненості нормалізуються й у сумі складають 100 відсотків.
Усе, що залишилося, це доповнити програму кодом, щоб модель могла отримувати вхідні дані від миші у реальному часі. Першим кроком є сигнал про завершення процесу навчання, щоб користувач знав, що модель готова. Для відстеження процесу навчання й остаточного відображення передбаченої мітки на полотні я додам глобальну змінну status
. Спочатку змінна ініціалізується значенням "training"
, але оновлюється на "ready"
у функції зворотного виклику finishedTraining()
:
let status = "training";
Коли програма почнеться, статус матиме значення "training".
function draw() {
background(255);
textAlign(CENTER, CENTER);
textSize(64);
text(status, width / 2, height / 2);
}
function finishedTraining() {
status = "ready";
}
Коли навчання буде завершено, ця функція зворотного виклику змінить значення статус на "ready".
На завершення, я використаю функції p5.js для подій миші, щоб створити вектор руху під час перетягування миші й викличу метод classifier.classify()
, передавши в нього налаштований вектор.
function mousePressed() {
start = createVector(mouseX, mouseY);
}
Збереження початку руху після натискання миші.
function mouseDragged() {
end = createVector(mouseX, mouseY);
}
Оновлення кінця руху під час перетягування миші.
function mouseReleased() {
Коли миша відпущена, потрібний рух завершено.
let dir = p5.Vector.sub(end, start);
dir.normalize();
Обчислення і нормалізація вектора напрямку.
let inputs = [dir.x, dir.y];
classifier.classify(inputs, gotResults);
Перетворення напрямку у вхідний масив і класифікація.
}
function gotResults(error, results) {
status = results[0].label;
}
Збереження отриманої мітки у змінній status для відображення на пол отні.
Якщо для прогнозу мені достатньо використовувати одну мітку я можу отримати доступ до першого елемента масиву результатів results[0].label
, оскільки масив results
відсортовано за вірогідністю від більших значень до менших. Значення цієї мітки передається до змінної status
, яка буде відображена на полотні.
Вправа 10.5
Розділіть приклад 10.2 на три програми: одну для збору даних, другу для навчання і третю для використання моделі. Використовуйте ml5.neuralNetwork
і функції save()
та load()
для збереження та завантаження моделі у файл і відповідно зчитування з нього.
Вправа 10.6
Розширте модель розпізнавання рухів для точнішої класифікації послідовності векторів, які складають шлях довшого руху миші. Пам’ятайте, що ваші вхідні дані повинні мати відповідну форму, тому доведеться вирішити, скільки векторів використовувати для представлення руху та зберігати не більше і не менше для кожної точки даних. Хоча цей підхід може працювати, інші моделі машинного навчання (такі як рекурентні нейронні мережі) спеціально розроблені для обробки послідовних даних і можуть запропонувати більшу гнучкість та потенційну точність.
Вправа 10.7
Одна з попередньо натренованих моделей у ml5.js називається Handpose. Вхідними даними моделі є зображення, а прогнозом список із 21 ключової точки — -положень, відомих як орієнтири, які описують долоню.
Чи зможете ви використати вихідні дані моделі ml5.handPose()
як вхідні дані для ml5.neuralNetwork()
і класифікувати різні жести долонь (наприклад, піднятий вгору великий палець або опущений вниз)? Для отримання підказок, ви можете переглянути мій відеоурок, який продемонструє цей процес на прикладі з позами тіла на вебсайті Coding Train.
Проєкт “Екосистема”
Включіть у свою екосистему машинне навчання, щоб покращити поведінку істот. Як тут можна застосувати класифікацію або регресію?
- Чи зможете ви класифікувати істот вашої екосистеми на кілька категорій? Що, якщо ви використаєте початкову популяцію як навчальний набір даних, а при народженні нових істот, система класифікуватиме їх відповідно до їхніх особливостей? Які дані для вашої системи будуть вхідними й вихідними?
- Чи можете ви використати регресію, щоб передбачити тривалість життя істоти на основі її властивостей? Подумайте про те, як розмір і швидкість вплинули на тривалість життя блупів з Розділу 9. Чи можете ви проаналізувати, наскільки прогнози регресійної моделі узгоджуються з фактичними результатами?