Вступ до Machine Learning: знайомство з моделями
Цю статтю створено у співавторстві з Анастасією Білоус.
Машинне навчання і штучний інтелект за останні кілька років стали дуже гарячими темами. В тих чи інших варіантах вони сьогодні є частиною величезної кількості продуктів, і мало хто не задумується над їхнім запровадженням. Приклади застосування ML (Machine Learning) — від автоматичного визначення важливих листів і швидких відповідей в Gmail, створення музики за допомогою машинного навчання до AlphaGo. Ця стаття також буде прочитана роботами швидше і більше разів, ніж людьми :)
У цьому матеріалі ми сфокусуємося виключно на темі машинного навчання і спробуємо на інтуїтивному рівні описати принципи його роботи. В Iнтернеті є кілька визначень машинного навчання різного ступеню формальності, але в кінці статті ми прийдемо до свого власного. Оскільки це наш перший матеріал про машинне навчання на DOU, ми почнемо нашу, можна сказати екскурсію, з основ.
Знайти рішення
Візьмемо за приклад таку історію. Всесвітньо відомий український політехнічний університет (скорочено ПТУ) запровадив платну перездачу екзаменів. І у фінансового директора університету Іван Івановича виникла проблема: обсяги цих надходжень дуже складно запланувати наперед. Складніше ніж погоду, яка і так корелює з успішністю студентів. А Ryanair і all inclusive треба ж бронювати наперед.
Думати Іван Івановичу не дуже хотілося, тому він поставив цю задачу студенту Колі в якості курсової роботи. Коля був програміст моторний, тому швидко сів писати відповідну програму на Пітоні:
def predict_num_failed_exams(): result = 0 for student in self.all_students: for exam in student.exams: if self.predict_exam_failure(student, exam): result = result + 1 return result
Він задумався над реалізацією методу predictExamFailure()
. Очевидно, що реалізація має брати до уваги попередні оцінки студентів, але як саме виразити це в коді — він не знав.
Тут йому і пригодилася база даних ПТУ, доступ до якої він отримав сумнівними способами ще минулого року, щоб дізнатися номер телефону Марічки. Коля швидко написав запит, який вивів минулорічну успішність студентів по предмету «Обчислювальні методи», який він дуже любив:
Рік | Ім’я студента | Предмет | Бали тест 1 | Бали тест 2 | Екзамен* |
2017 | Андрій | ОМ | 47 | 53 | 52 |
2017 | Аня | ОМ | 75 | 78 | 80 |
2017 | Борис | ОМ | 52 | 47 | 47 |
2017 | Марія | ОМ | 52 | 49 | 49 |
... |
* Для здачі екзамену необхідно набрати мінімум 51 бал
Розглянувши результати, Коля повернувся до коду:
def predict_exam_failure(student, exam): return student[exam.subject].test1_grade < 51 or student[exam.subject].test2_grade < 51
Але трохи подумавши, він зрозумів, що для запису «Андрій» з вищенаведеної таблиці цей метод передбачить нездачу, так як результат першого тесту — менше 50 балів. Тому він виправив реалізацію методу на:
return (student[exam.subject].test1_grade + student[exam.subject].test2_grade) < 100
Тепер прогноз для Андрія правильний, але виходить для Марії ми тепер передбачимо успішну здачу екзамену. Хоча Коля і так знав, що Марічка завалила екзамен через нього, вiн вирішив, що результат першого тесту важливіший, ніж другого, і знову переписав свою формулу:
return (0.6 * student[exam.subject].test1_grade + 1.4 * student[exam.subject].test2_grade) < 100
І щоб уже вкотре переконатися у своїй геніальності, він вирішив перевірити, чи зможе його код точно передбачити результати минулих екзаменів, для яких результати уже були відомі. Він швидко написав код, який рахує, у скількох відсотків випадків його передбачення збігається з фактичним результатом. Цю метрику називають точність (англ. accuracy).
def calculate_accuracy(students): num_correct = 0 num_total = 0 for student in students: for exam in student.exams: num_total += 1 if predict_exam_failure(student, exam) == exam.final_grade > 50: num_correct += 1 return 100.0 * num_correct / num_total
Точність
Запустивши цей метод на результатах екзаменів минулого літа, він отримав точність своєї функції — 54,8%. Тут наш герой почав усвідомлювати, що його халявна курсова суттєво ускладнилася. Іван Іванович навряд чи зрозуміє, як же це він полетить у Туреччину з імовірністю 55%.
Коля витратив дуже багато часу, підбираючи різні варіанти коефіцієнтів у своїй функції, тестуючи її кожен раз на точність. Десь під ранок у нього була функція, точність якої сягала 75%! Самооцінка Колі піднялася до такого ж рівня. Але перед тим як святкувати, він вирішив перевірити точність функції також на студентах інших факультетів. Після чого його самооцінка опустилася до 45%. Очевидно, що він так довго працював над своєю функцію, що «витиснув» з неї максимальну точність на екзаменах свого факультету, але це зробило її ще гіршою для інших. Цю ситуацію називають надмірне навчання або перенавчання (анг. overfitting).
Відіспавшись, наш Микола подумав, що він хоче змарнувати ще одну ніч, підбираючи всі можливі коефіцієнти знову. Врешті для цього він і вчиться на програміста — щоб писати код для автоматизації ручної роботи. Тоді чому б не автоматизувати процес підбору коефіцієнтів для його функції, тобто автоматизувати процес автоматизації! У нього почала вимальовуватися ідея для докторської дисертації...
Зобразивши результати екзамену точками різних кольорів, Коля отримав такий малюнок:
З нього стало очевидно, що простим правилом розділити ці приклади даних не вийде. І що його вчорашня функція, яка виглядала, як чорна лінія на наступному малюнку, дуже точно розділяла дані у його вибірці. Але через свою складну форму погано робила передбачення на даних, під які вона не була підібрана.
Помаранчева лінія на тому ж малюнку не наскільки точно відділяє наші групи успішних і не успішних екзаменів, але вона є простішою і відповідно не такою чутливою до винятків у даних. В екстремальному випадку функція, яка просто матиме перелік всіх, хто здав і хто не здав екзамен, буде дуже точно відображати наші вхідні дані, але приноситиме нульову користь на нових, не відомих їй результатах.
Функцію, яку машина буде писати під конкретну задачу, будемо називати модель.
Моделі
Оскільки функція (модель) передбачає провал на екзамені, результат Правда (True) означає, що ми передбачаємо провал, і навпаки, Неправда (False) означає, що вона передбачає успішно зданий екзамен. Надалі ми абстрагуємося від екзаменів і будемо називати записи відповіді позитивними i негативними. При застосуванні та оцінювані моделі ми будемо пам’ятати, що означає «позитивний» в нашому випадку. Метрика точність (accuracy) не залежить від того, що ми називаємо позитивним, а що — негативним, тому що ми рахуємо кількість правильних передбачень.
Тепер саме питання: як могла б виглядати така модель-функція з середини, враховуючи, що писати і читати її буде машина, а не людина. Ми, звичайно, можемо представляти її програмним кодом на довільній мові програмування, але перебір різних варіантів машиною буде значно ускладнений.
Розглянемо наступний приклад, для простоти запису якого приймемо Х1 = бали за перший тест, Х2 = бали за другий тест. Наша функція виглядатиме так: F(X1, X2: number) => Y: boolean — приймаємо два чисельні аргументи і повертаємо булевий результат. Цю функцію вище ми називали predict_exam_failure()
.
Для прикладу розглянемо одну з попередніх реалізацій нашої функції:
F(X1, X2) => 0.6 * X1 + 1.4 * X2 < 100
Виділені константи 0.6
та 1.4
наша машина і буде підбирати. Ми будемо називати їх параметрами моделі і позначатимемо W1 і W2, а Х1 і X2 — це властивості даних. Y — вихідна властивість моделі. Модель зазвичай має більше, ніж дві вхідні властивості, узагальнений запис функції-моделі виглядає так: F(X1, X2, ... Xn) => Y. Звернемо увагу, що реалізація цієї функції з нашого прикладу матиме N параметрів — W1, W2, ... Wn. Такі моделі називають лінійними. Є багато інших способів зображати моделі, які ми опишемо в наступній статті, якщо буде до них інтерес :)
Для того, щоб оцінювати точність функції, ми додамо модель як вхідний параметр (не плутати з параметрами моделі) у наш метод для обрахунку точності, а вхідний параметр students перейменуємо в data для універсальності:
def calculate_accuracy(students, model_func):
Реалізація її залишається такою ж, як вище.
Тренування моделі
У найпростішому варіанті машинного навчання ми будемо використовувати код, який перебере багато різних параметрів і поверне той набір, для яких наша функція демонструє найбільшу точність. Цей процес називають тренуванням (training) моделі. Алгоритм тренування відповідно учителем чи тренером (trainer). Цей код реалізовує тренера для нашого методу повного перебору:
def brute_force_train(data, min_w, max_w, train_step): best_model = None best_accuracy = 0 w1 = min_w while w1 <= max_w: w2 = min_w while w2 <= max_w: model = lambda datum: w1 * datum[0] + w2 * datum[1] accuracy = calculate_accuracy(data, model) if accuracy > best_accuracy: best_model = model; best_accuracy = accuracy w2 += train_step w1 += train_step return best_model
На практиці такий спосіб не використовується через його надмірну ресурсоємкість, i існує багато інших набагато більш оптимальних алгоритмів, які ми залишимо для наступних статей.
Параметри min_w
, max_w
, train_step
функції brute_force_train
називаються гіперпараметрами (hyperparameters). Набір параметрів залежить від тренера (алгоритму навчання), і їх вказують ті, хто тренує модель. На противагу параметрам W, які власне тренер сам і підбирає.
Вище ми згадували, що спосіб, яким ми рахуємо точність, буде схиляти алгоритм навчання до вибору тої моделі-функції, яка зможе найкраще запам’ятати всі записи з наших вхідних даних (списку всіх студентів у нашому випадку). В екстремальному випадку модель просто пам’ятатиме всіх студентів і їхню успішність поіменно — буде перенавчатися. Ця проблема не залежить ні від від типу моделі, ні від алгоритму тренера, хоча сама можливість перенавчання залежить від типу моделі.
Способи боротьби з перенавчанням залежать від алгоритму і полягають у правильних значеннях гіперпараметрів тренера. На практиці оцінка моделі не проводиться на тих же вхідних даних, які використовувалися для тренування моделі.
Інтуїтивно аналогія тут така ж, як і з навчанням в університеті: викладач розв’язує одні задачі зі студентами на парах, а інші, але схожі задачі, дає на екзамені. Тут важливим є (і в навчанні студентів, і моделей) те, що ці задачі рiзноманiтнi, i студенти не можуть просто запам’ятати відповіді, а ті, хто засвоїли матеріал (схожі задачі) зможуть повторити хід думок і відповісти правильно.
У машинному навчанні ми розділяємо два окремі набори: набір для оцінки ми використовуватимемо для оцінки кожної моделі, яку тренуватимемо, використовуючи різні підходи, алгоритми і типи моделей, щоб вибрати найкращу з них. Тобто для кожної моделі у нас буде два значення точності — точність на наборі даних для тренування і точність на наборі даних для оцінки. Нормальною є ситуація, коли перша вища за другу, але не значно. Велика різниця вказує на перенавчання.
Призначення набору для ратифікацї ми також розберемо в наступній статті.
Для того, щоб тренер працював успішно, йому необхідна достатня кількість даних. Залежно від складності проблеми — від сотень і тисяч до десятків і сотень тисяч. Більша кількість даних дозволяє будувати точніші і складніші моделі. На практиці — не рідкість набори даних з багатьма сотнями мільйонів записів. Але більш важливими факторами є якість (оскільки некоректні значення вихідної властивості призводять до того, що модель не може знайти різниці між позитивними і негативними записами) і «свіжість» даних (оскільки тренер може знайти в даних закономірності, які застарілі або помінялися, і засмітити цими «знаннями» пам’ять результуючої моделі, що неодмінно повпливає на її передбачення).
Розміри наборів для оцінки (і репрезентативність даних в них) є важливими для статистичної значущості метрик, які рахуються на їх основі. Вище ми використовували точність (accuracy) як спосіб оцінки нашої моделі, і для даної проблеми ця метрика підходить. Але давайте припустимо, що позитивні записи є рідкістю, наприклад, модель передбачає ймовірність рідкісної хвороби і тільки 0,1% усіх записів є позитивними. У такому випадку модель, яка всі записи класифікує як негативні незалежно від їхніх властивостей, матиме точність 99,9% — неймовірну високу для будь-якого способу прийняття рішень. Але, як ми розуміємо, користь такої моделі дуже обмежена :)
Висновки
Тепер ми вже можемо дати і визначення.
Машинне навчання — це застосування алгоритмів для автоматичного знаходження закономірностей в даних і використання їх для прийняття великої кількості однотипних рішень, для яких певний відсоток помилок є допустимими.
У цій статті ми спробували інтуїтивно описати, що собою являє машинне навчання, не заглиблюючись занадтно, оскільки це дуже широка дисципліна. Новачків у машинному навчанні запрошуємо писати в коментарях, про що вам було б цікаво почитати в наступних статтях. Подальші пригоди Колі і Марічки вам цікаві? :)