У темі 08 ми розібрали дошку оголошень про вживані телефони й знайшли там усе, що зазвичай знаходять: пропуски, дублікати, памʼять, записану текстом, і стовпець, яким користуватись не можна. Таблиця стала чистою. І тут виявляється незручне: чиста таблиця сама по собі майже нічого не знає про шахрайство.
Порахуємо. Кореляція кожного вихідного стовпця з таргетом на 1 100 оголошеннях, у яких ціна відома:
Найсильніший звʼязок — 0.116, і той у віку акаунта. Ціна, головне поле оголошення, дає рівно нуль. Але ж ми знаємо, що шахрай видає себе саме ціною: він ставить неймовірну знижку або, навпаки, захмарну суму під велику передоплату. Знання є, а в числах його немає.
Причина проста, і вона не в даних. Модель бачить рівно те, що ти їй поклав, і не здогадається ні про що. Стовпець «ціна» містить число 1 310 — і воно справді нічого не означає, поки не сказано, скільки коштує такий самий телефон. Вся ця тема — про те, як дописати в таблицю стовпці, у яких твоє розуміння задачі нарешті стає числом. Це й називається інженерія ознак (feature engineering).
groupby,
transform, merge, робота з датами — ми розібрали в
темі 34 курсу Python, а самі дати —
у темі 08 того ж курсу. Тут ми не
пояснюємо, як викликати метод: ми говоримо про те, який стовпець варто
ним зробити.01 / ПерекладОзнака як переклад
Уяви, що ти показуєш оголошення знайомому, який продає телефони десять років. Він дивиться дві секунди: «Beta 12 двадцять другого року за 1 310 — так не буває». Він не рахував кореляцій, він порівняв ціну з тим, що знає про цю модель.
Модель такого знання не має: слово «Beta 12» для неї — просто одна з шести категорій. Тому фразу «так не буває» треба перекласти в число: поділити ціну оголошення на типову ціну для цієї моделі й року. Для нашого оголошення це 1 310 ÷ 6 740 = 0.19. Ось тепер у таблиці є те, що знайомий побачив очима.
Звідси головна думка теми, і вона майже банальна: інженерія ознак — це переклад людського розуміння задачі на мову чисел. Ознака, про яку ми домовились ще в темі 02, не обовʼязково має бути стовпцем із файлу — її можна й треба зробити. Не пошук магічної формули, а послідовна відповідь на питання «що саме я бачу в цих даних такого, чого не видно в окремому стовпці».
Але спершу треба домовитись, як ми відрізнятимемо вдалий переклад від невдалого.
Три лінійки замість моделі
Кореляція (у нас — Пірсона) відповідає на питання «чи ростуть ці два числа разом». Ми користувались нею в темі 08. Її межа названа прямо в означенні: вона бачить лише монотонний звʼязок. Якщо шахрайськими є і дуже дешеві, і дуже дорогі оголошення, вона покаже нуль.
Розділюваність класів — наскільки розходяться розподіли ознаки в чесних і шахрайських оголошеннях. Зручна міра для цього в нас уже є: площа під ROC-кривою з теми 06. Візьмемо саму ознаку як оцінку ризику й порахуємо AUC: 0.5 — розподіли збігаються повністю, 1.0 — не перетинаються зовсім. Сліпа пляма та сама, що в кореляції: AUC рахує лише порядок.
Взаємна інформація (mutual information, MI) — це відповідь на питання
«наскільки менше невизначеності в таргеті лишається, якщо я знаю значення ознаки».
Вважай її кореляцією, яка бачить і нелінійне. Нуль означає «ознака й таргет незалежні»;
верхньої межі немає — числа порівнюють між собою, а не з одиницею. У
scikit-learn це mutual_info_classif.
02 / ВідношенняВідношення й різниці
Найпростіший прийом і найчастіше найсильніший: поділити одне число на інше. Сира величина майже завжди безглузда без масштабу: ціна без типової ціни, зарплата без медіани по місту, пальне без пробігу.
На нашій дошці напрошуються чотири переклади:
- ціна ÷ типова ціна для цієї моделі й року — «наскільки це дешевше або дорожче за нормальне»;
- ціна ÷ памʼять — ціна за гігабайт;
- вік телефона = поточний рік − рік випуску — різниця, а не відношення, але той самий прийом;
- ціна − медіана по моделі й року — та сама думка, але через віднімання.
«Типової ціни» в таблиці немає — її треба порахувати з даних: медіана ціни всередині групи «модель + рік». Для Beta 12 двадцять другого року таких оголошень 17, їхня медіана — 6 740 грн. Тепер увімкни всі чотири переклади по черзі.
Інтерактив 1 · Конструктор ознаки
Угорі — розподіл нової ознаки окремо для чесних і шахрайських оголошень. Унизу для порівняння — той самий розподіл для вихідної ціни. 1 100 оголошень, у яких ціна відома.
mutual_info_classif.Натисни «ціна ÷ типова» й подивись на кореляцію: +0.018. Нуль. Ознака, яку ми щойно назвали головною думкою теми, за кореляцією не варта нічого.
А тепер подивись на гістограму. Рожеві оголошення зібрались у два акуратні горби: один біля 0.3, другий біля 3 — шахрай ставить або занижену ціну, або завищену. Взаємна інформація це бачить: 0.336 проти 0.069 у сирої ціни. Кореляція не бачить, бо два горби по різні боки одиниці в сумі дають нахил нуль.
Це не вада ознаки, а вада лінійки. І виправляється це ще одним перекладом: якщо нам важлива не сторона, а відстань від норми, візьмемо модуль логарифма відношення.
Логарифм тут потрібен, щоб «удвічі дешевше» й «удвічі дорожче» дали однакову величину: ln(0.5) = −0.69, ln(2) = +0.69. Без нього дешева приманка виглядала б меншим відхиленням, ніж дорога. Модуль складає два горби в один. Натисни «відхилення»: кореляція +0.774, AUC 0.990.
Не чекай таких чисел на живих даних. Наша дошка синтетична: шахрайські ціни в ній задані як частка від типової, тому ознака, що міряє саме цю частку, майже ідеально відновлює правило, за яким дані створено. На справжніх оголошеннях правила ніхто не оголошує, ознака ловить його лише частково, і AUC 0.75 там уже вважається доброю здобиччю. Прийом справжній, а от розмір виграшу тут — тепличний.
Решта два переклади показують, що прийом не чарівний. «Ціна ÷ памʼять» дає взаємну інформацію 0.063 проти 0.069 у сирої ціни — тобто нічого: розкид цін пояснює модель, а не памʼять. «Ціна − медіана» дає 0.160 — удвічі менше за відношення, бо різниця в 500 грн для Alfa A5 це половина ціни, а для Gamma X Ultra округлення. Звідси правило: коли величини відрізняються на порядки, ділять; коли живуть в одному масштабі — віднімають.
А «вік телефона» не спрацював зовсім: AUC 0.535 при 0.5 у чесної монети. Логіка була правильна — шахраєві зручніше ховатись за старою моделлю. Дані сказали «ні». Так буває частіше, ніж хочеться, і саме тому ознаки міряють, а не проголошують.
03 / АгрегатиОзнаки, зібрані по групі
«Типова ціна» з попереднього розділу — це агрегатна ознака: ми розбили таблицю на групи, порахували щось усередині кожної й приклеїли результат назад до кожного рядка. У pandas це одна конструкція:
Різниця між agg і transform тут принципова. agg
віддає таблицю «одна група — один рядок»; transform віддає стовпець тієї
самої довжини, що й вихідна таблиця. Ознакою може стати лише друге.
Схема 1 · Що робить агрегатна ознака
Три кроки: розбити на групи, порахувати всередині групи, повернути число кожному рядку.
Групувати можна не тільки за моделлю. Ось що дали чотири різні агрегати:
| агрегатна ознака | кореляція | AUC | MI |
|---|---|---|---|
| середнє відхилення ціни в оголошеннях цього продавця | +0.611 | 0.928 | 0.151 |
| середня година публікації цього продавця | −0.293 | 0.703 | 0.089 |
| скільки оголошень подав цей продавець | +0.211 | 0.654 | 0.020 |
| медіанна ціна цієї моделі й року | −0.034 | 0.512 | 0.006 |
Дві речі в цій таблиці варті окремої уваги.
Найсильніший агрегат — не про ціну, а про продавця. «Середнє відхилення ціни в оголошеннях цього продавця» дає AUC 0.928. Одне дивне оголошення буває випадковістю, а коли в акаунта дивні всі — це вже профіль. Групувати за обʼєктом, який породжує рядки, — прийом, що працює майже завжди: продавець, користувач, магазин.
Найслабший агрегат — той, без якого нічого не вийшло б. Медіанна ціна моделі сама по собі марна: AUC 0.512, тобто монета. Дорогі моделі шахраї беруть не частіше за дешеві. Але саме вона стоїть у знаменнику найкращої ознаки всієї теми. Мораль: ознака може бути нічим окремо й усім у складі відношення, і саме тому відбирати ознаки по одній, дивлячись лише на її власну кореляцію, — небезпечно.
Техніка тут та сама, що в темі 07 і в темі 34 курсу Python. Нове тут одне питання: «за яким полем групувати, щоб вийшло осмислене порівняння».
04 / ЧасЩо дістати з дати
У повному вивантаженні дошки є стовпець, якого ми досі не чіпали:
опубліковано — момент публікації з точністю до хвилини, скажімо
2024-01-04 23:03.
Найпоширеніша дія з такою колонкою — і найгірша — перевести її в число: комп'ютер зберігає час як кількість секунд від 1970 року. Стовпець виходить пристойний на вигляд. Порахуємо його корисність: кореляція +0.044, AUC 0.539. Практично нуль.
Причина видна, щойно спитаєш, що це число означає: «наскільки пізно подано оголошення», і тільки. Структура доби, тижня й сезону в ньому є, але захована в молодших розрядах, і жодна модель не здогадається розкласти секунди на години. Сира дата як число марна не тому, що в даті мало інформації, а тому, що вся вона злиплась в одну величину.
Розібрати дату — означає видати кожну складову окремим стовпцем.
Схема 2 · Сира дата проти розібраної
Ліворуч — те, що дає перетворення в число. Праворуч — те саме після розбору на складові. Числа — кореляція з таргетом на нашій дошці.
Стандартний набір такий: рік, місяць, число, день тижня, година, «вихідний чи ні», «скільки днів минуло від опорної дати». Останнє особливо корисне, коли важлива не сама дата, а проміжок: «днів від реєстрації акаунта до публікації» — майже готова ознака ризику.
На нашій дошці більшість складових виявились порожніми. Рік у всіх однаковий — константа, викидаємо. Місяць дав +0.042, день тижня −0.091, взаємна інформація в обох нуль. А от година публікації дала кореляцію −0.332 і взаємну інформацію 0.291 — більше, ніж будь-яка вихідна колонка. Медіанна година чесного оголошення — 13-та, шахрайського — 3-тя.
Пастка, через яку година працює гірше, ніж могла б
Розділення на око разюче, а кореляція лише −0.332. Причина в тому, що година як число бреше про відстані: для моделі 23 і 0 — це два краї шкали, найдальші одна від одної величини, хоча між ними одна хвилина.
Ніч у нас саме там, де шкала розривається: 22, 23, 0, 1, 2, 3, 4. Половина нічних оголошень має найбільші номери годин, половина — найменші, і спроба сказати «ризик тим вищий, чим більша година» розсипається.
Інтерактив 2 · Година на прямій і година на колі
Постав дві години й порівняй, яку відстань між ними бачить модель на прямій шкалі та яку — після циклічного кодування.
Розвʼязок — покласти годину не на пряму, а на коло. Замість одного стовпця «година» робимо два:
Буквально: ми беремо циферблат і записуємо координати кожної години на ньому. У 23-ї це (−0.26, +0.97), у 0-ї — (0.00, +1.00): вони поруч, бо на циферблаті поруч. У 13-ї — (−0.26, −0.97): та сама перша координата, зовсім інша друга. Ось навіщо потрібні обидва стовпці — сам синус не відрізняє 23 від 13, сам косинус не відрізняє 23 від 1.
Що це дає в числах. Кореляція година_cos з таргетом — +0.727,
AUC — 0.982, замість −0.332 і 0.684 у сирої години. Одне й те саме поле, той самий
момент часу: змінилось тільки те, у якому вигляді його показали.
05 / ВзаємодіїДві ознаки, які працюють лише разом
Повернімось до головної ознаки теми й подивимось на неї збоку. Вона зроблена з двох чисел: ціни оголошення й типової ціни моделі. Порахуємо, чого варте кожне окремо.
Це і є взаємодія (interaction): комбінація двох ознак несе те, чого немає в жодній із них окремо. Ознака А не відрізняє класи, ознака Б майже не відрізняє — а їхнє відношення відрізняє майже ідеально.
Найкраще це видно на площині. Відклади типову ціну по одній осі, ціну оголошення — по другій, і подивись на ту саму хмару точок трьома способами.
Інтерактив 3 · Одна вісь, друга вісь, обидві разом
1 100 оголошень. Перемкни погляд: спершу подивись на кожну ознаку окремо, потім на площину.
На одній осі — дві криві, які лежать одна на одній. На площині — коридор чесних оголошень уздовж діагоналі й два згустки рожевого: під коридором ті, хто заманює знижкою, над ним — ті, хто ставить захмарну суму. Жодна вісь окремо їх не відділяє, бо вони відрізняються не значенням, а співвідношенням.
Практичний висновок: коли жодна ознака нічого не дає, це ще не означає, що даних мало. Часто це означає, що ти не поставив питання до пари ознак. Комбінують множенням (площа = довжина × ширина), діленням (щільність = маса ÷ обʼєм) і зіставленням категорій. Операцію диктує зміст задачі, а не перебір.
06 / СкількиСкільки ознак треба
Спокуса після перших вдалих ознак очевидна: наробити ще пʼятдесят. Логіка здається залізною — зайва ознака в найгіршому випадку буде проігнорована. Насправді кожна зайва ознака коштує, і ось найпростіша демонстрація ціни.
Додамо до нашої таблиці 50 стовпців чистого шуму — випадкових чисел, які не мають до шахрайства жодного стосунку за побудовою. Порахуємо кореляцію кожного з таргетом:
Прочитай останній рядок ще раз. Осмислена ознака «вік телефона» дала 0.040 — менше, ніж найкращий з пʼятдесяти випадкових стовпців. Чим більше ознак ти перебираєш, тим сильніший найкращий випадковий результат — і тим більша ймовірність, що ти візьмеш його за відкриття.
Друга біда має назву: прокляття розмірності (curse of dimensionality). На прямій сотня точок стоять щільно; на площині ті самі сто точок уже розкидані; у просторі з пʼятдесяти ознак кожна з них — самотній острів, і поняття «схожі обʼєкти» перестає працювати. Розгорнуто це буде далі в курсі, коли дійдемо до моделей, що спираються на відстані; поки досить відчуття: з кожною новою ознакою даних потрібно більше, а їх не додається.
Як відбирати
За кореляцією. Найдешевше: порахувати |r| кожної ознаки з таргетом і відкинути хвіст. Пастка вже названа двічі: кореляція не бачить нелінійного, і ознака «ціна ÷ типова» з її +0.018 полетіла б у смітник першою.
За взаємною інформацією. Те саме, але лінійкою, яка бачить більше. Коштує дорожче й теж не всесильна: MI рахує звʼязок однієї ознаки з таргетом, тому пари, які працюють лише разом, вона так само пропустить.
Здоровим глуздом. Спитати себе, чи можеш ти пояснити словами, чому ця ознака має щось знати про відповідь. Якщо пояснення немає, а число красиве — це привід не радіти, а шукати помилку. Наступний розділ якраз про такий випадок.
07 / ВитікВитік: ознака, що знає відповідь
У темі 08 ми вже зустрічали витік даних (data leakage): колонка «скарг» була звʼязана з таргетом майже ідеально, бо скарга зʼявляється після того, як шахрайство викрито. Там витік уже лежав у даних — лишалось його помітити. Інженерія ознак додає гірший різновид: витік, який ти створюєш власноруч.
Цільове кодування
Колонка «продавець» містить 440 різних значень, і звичайне кодування категорій дало б 440 нових стовпців. Тому напрошується інша ідея: замінити кожного продавця часткою шахрайських оголошень серед його власних. Це називається цільовим кодуванням (target encoding), і виглядає воно бездоганно:
Один рядок, жодних 440 стовпців, і ознака одразу показує чудову кореляцію. Проблема в тому, що серед 440 продавців 121 подав рівно одне оголошення, і для нього «середнє по його оголошеннях» — це сам таргет цього рядка. Не схожий на нього, не приблизно рівний — буквально він.
Схема 3 · Звідки береться витік у цільовому кодуванні
Один продавець, одне оголошення. Ліворуч — наївний розрахунок по всій таблиці. Праворуч — той самий розрахунок, зроблений лише на навчальній частині.
Числа з підпису варто перечитати. Ознака з чистої вигадки за наївного розрахунку виглядає сильнішою за майже все, що ми знайшли в розділах 02-04. Не тому, що вона щось знає, а тому, що ми поклали в неї відповідь і самі ж її звідти дістали.
На справжній колонці «продавець» картина м'якша, напрямок той самий: наївне кодування дає на тестовій частині 0.657, чесне — 0.303. Половина «сили» ознаки була позичена з відповідей.
Правило
Воно те саме, що й для будь-якої передобробки даних — масштабування, заповнення пропусків, кодування категорій: усе, що рахується з даних, рахується лише на навчальній частині. Медіани, середні, карти кодування, межі викидів — усе. Порядок дій такий:
Поділити таблицю на навчальну й тестову частини, як у темі 03 — до того, як ти порахуєш першу агрегатну ознаку.
Побудувати карту «категорія → число» тільки за навчальними рядками.
Застосувати цю карту й до навчальної, і до тестової частини. Категоріям, яких у навчальній частині не було, підставити загальне середнє.
Перевірити результат на тих рядках, яких карта не бачила. Якщо ознака стала вдесятеро слабшою, вона такою й була.
Навіть із цим порядком цільове кодування лишається небезпечним: усередині навчальної частини одне-єдине оголошення продавця знову кодує саме себе. Тому його рахують «із виключенням себе» або по складках перехресної перевірки — це матеріал наступних тем. Поки досить запам'ятати: цільове кодування — не просто ще один спосіб закодувати категорію, а прийом із вбудованою пасткою.
08 / ГлуздГлузд проти автоматики
Оскільки ознаки — це формули, напрошується їх генерувати. Найвідоміший спосіб —
поліноміальні ознаки: дописати до вихідних стовпців їхні квадрати й усі
попарні добутки. У scikit-learn це PolynomialFeatures, один
виклик.
Порахуймо, що вийде на нашій дошці. Чотири числові стовпці дають 4 квадрати й 6
попарних добутків — 10 нових ознак, у восьми з яких змісту немає взагалі. Потрібної нам
взаємодії — ціна ÷ медіана по моделі й року — серед них немає й бути не
може: у ній бере участь величина, якої в таблиці не існує, поки ти сам її не порахуєш.
Це і є межа автоматики: вона комбінує те, що вже є, а найкращі ознаки роблять із того, чого ще немає. Медіана по групі, коридор «удвічі дешевше», ознака «в цього продавця дивні всі оголошення» — усе це наслідки того, що хтось подумав про предметну область, а не перебрав добутки.
Друга проблема перебору — арифметична. Десять вихідних стовпців дають 55 поліноміальних ознак другого степеня, двадцять — 210. Ми щойно бачили, що серед 50 випадкових ознак знаходиться одна з |r| = 0.061; серед 210 знайдеться краща. Автоматична генерація видає багато сміття на одну корисну ознаку — і сміття це виглядає пристойно.
| що маємо | що робимо | навіщо |
|---|---|---|
| величина, у якої є природний масштаб | ділимо на цей масштаб | число стає порівнянним між різними обʼєктами |
| дві величини в одному масштабі | віднімаємо | різниця читається в тих самих одиницях |
| величина, важлива в обидва боки | беремо модуль логарифма відношення | «вдвічі більше» й «вдвічі менше» стають одним числом |
| категорія з багатьма рядками | агрегат по групі через groupby | рядок дізнається про свою групу |
| дата або час | розбираємо на складові | структура доби й тижня перестає бути захованою |
| циклічна величина | синус і косинус кута | кінець циклу стає сусідом його початку |
| дві ознаки, кожна безсила | добуток або відношення | ловимо те, чого немає в жодній окремо |
| категорія з високою кардинальністю | цільове кодування — тільки на train | 440 стовпців стискаються в один, ціною ризику витоку |
09 / ПідсумокЩо забрати з теми
Ми почали з таблиці, у якій найсильніша ознака давала кореляцію 0.116, а ціна — нуль. Не додавши жодного нового джерела даних, ми отримали дві ознаки з AUC 0.990 і 0.982 — обидві з полів, які лежали в таблиці від початку.
- Ознака — це переклад. Якщо ти щось розумієш про задачу, а в стовпцях цього немає, — модель цього не дізнається.
- Ділити й віднімати. Сира величина без масштабу майже завжди марна. Порядки різні — діли; масштаб один — віднімай.
- Групувати.
groupby+transformдає рядку знання про його групу. Найсильніші агрегати — по обʼєкту, що породжує рядки. - Дату розбирати. Секунди від 1970 року — це не ознака. Година, день тижня, «скільки минуло» — ознаки. Циклічне ставимо на коло.
- Міряти, а не проголошувати. Три лінійки, у кожної своя сліпа пляма. Ознака «вік телефона» здавалась розумною й не спрацювала.
- Боятись гарних чисел. Ознака, що зненацька дала блискучий результат, частіше протікає, ніж працює. Усе, що рахується з даних, рахується на train.
І одне, чого ця тема свідомо не робить. Ми жодного разу не сказали «ознака X підняла якість моделі на стільки-то» — бо моделей ми ще не проходили. Коли дійдемо до них, буде видно, що ознака такої сили змінює результат більше, ніж заміна алгоритму на складніший. Поки що це обіцянка, і перевіримо ми її числами пізніше.
Далі в блоці «Дані на практиці» лишається наскрізний проєкт: одна задача від сирого файлу до висновків, де розвідка, передобробка й інженерія ознак ідуть підряд, як у житті. А потім — моделі, і перше, що ти зробиш із кожною з них, — подаси їй саме ту матрицю ознак, яку навчився будувати тут.
practice.ipynb ти візьмеш ту
саму таблицю з теми 08; порахуєш кореляції вихідних стовпців як точку відліку; побудуєш
відношення й агрегати через groupby; поміряєш приріст трьома лінійками;
розбереш дату й закодуєш годину синусом із косинусом; і власноруч влаштуєш витік у
цільовому кодуванні.Далі в темі
Теорію прочитано. Тепер закріпи її на практиці.