Уяви дошку оголошень про вживані телефони. Щодня зʼявляються сотні нових, і частина з них — приманки: людина побачить неймовірну ціну, переведе завдаток і більше нікого не знайде. Ти модеруєш цю дошку, і модель для підозрілих оголошень дуже потрібна. Але перш ніж її будувати, помітимо, що в тебе вже є архів: тисячі старих оголошень, про кожне з яких відомо, чим воно закінчилось.
І тут виникає думка, з якої починається вся тема. А що, як нічого не будувати? Що, як для нового оголошення просто знайти в архіві кілька найсхожіших і подивитись, чим виявились вони?
ПостановкаСхожі поводяться схоже
Це і є метод k найближчих сусідів (k-nearest neighbours, kNN), сформульований одним реченням: щоб оцінити новий обʼєкт, знайди k найсхожіших зі знайомих і подивись, чим вони виявились. Ніякої іншої ідеї всередині немає.
Наш архів — таблиця, у якій кожен рядок є одним оголошенням:
| колонка | що це | приклад |
|---|---|---|
| model | модель телефона | iPhone 12 |
| year | рік випуску | 2021 |
| condition | стан за пʼятибальною шкалою | 4 |
| memory_gb | обсяг памʼяті, ГБ | 128 |
| account_age_days | вік акаунта продавця, днів | 30 |
| price | запитувана ціна, грн | 12 600 |
| is_fraud | мітка: чи виявилось оголошення приманкою | 1 |
Головна задача теми — класифікація: за ознаками оголошення сказати, шахрайське воно чи ні. Щоб усе було видно на площині, візьмемо дві ознаки.
Перша — відносна ціна: запитувана ціна, поділена на типову ціну тієї самої моделі з такою самою памʼяттю й станом. Сирі 12 000 грн нічого не кажуть: для флагмана це дешево, для бюджетника — дорого. А от «55% від типової ціни» — уже сигнал. Типова ціна в нашій вибірці — 12 000 грн, тож відносна ціна 0.75 означає 9 000 грн. Друга ознака — вік акаунта продавця в днях: приманки майже завжди виставляють зі свіжозареєстрованих акаунтів.
У попередній темі ми вже вміли розвʼязувати таку задачу. Логістична регресія знаходить межу рішення — лінію, по один бік якої модель каже «шахрайство», по інший «чесне». Ця лінія завжди пряма: таке вже влаштування лінійного класифікатора. Подивись, що буде, якщо межу дозволити гнути.
Пряма межа проти ламаної
Ті самі 300 навчальних оголошень, та сама пара ознак, дві різні моделі. Точність поміряна на 260 відкладених оголошеннях.
Різниця не косметична. Шахрайство тут двох ґатунків: масові дешеві приманки зі свіжих акаунтів (велика область ліворуч унизу) і вузький згусток праворуч унизу — нові акаунти, що продають майже за ринковою ціною, аби не викликати підозри дешевизною. Одна пряма не обведе обидві області одночасно, тому логістична регресія жертвує меншою. kNN не жертвує нічим: кожна ділянка площини вирішується незалежно від інших.
НепараметричністьНавчання, якого немає
Тепер найважливіша особливість методу, і вона не про точність. Згадай, що означало «навчити модель» досі. У лінійній і логістичній регресії навчання — це підбір кількох чисел, коефіцієнтів. Після навчання дані можна викинути: уся вивчена інформація стиснулась у три-чотири числа, і саме вони й були моделлю.
У kNN підбирати нічого. Немає коефіцієнтів, немає рівняння, немає жодної величини, яку
алгоритм обчислив би на навчальних даних. Навчання kNN — це просто запамʼятати
таблицю. Буквально: fit() копіює дані в памʼять і на цьому
закінчується. Тому такі методи називають непараметричними (non-parametric),
а сам kNN — лінивим (lazy learner).
Що саме лишається після навчання
Ті самі 300 навчальних оголошень заходять у дві моделі. Ліворуч — те, що зберігає логістична регресія, праворуч — те, що зберігає kNN.
k у методі є, але це гіперпараметр — його не виводять з даних,
а обирають зовні, як поріг у логістичній регресії. Обсяг «вивченого» у kNN росте разом
із вибіркою: тисяча рядків — тисяча рядків моделі.Із цієї властивості випливає майже все далі. Модель нічого не спрощує й не має жодного припущення про форму межі. Але робота, якої не було під час навчання, нікуди не поділась — вона переїхала в момент передбачення. До цього повернемось у розділі 8.
ВідстаньЩо означає «схожий»
Слово «найсхожіший» треба перетворити на число, інакше алгоритму нема що порівнювати. Найпростіший спосіб — вважати кожне оголошення точкою в просторі ознак і взяти звичайну геометричну відстань між точками. Для двох ознак це шкільна теорема Піфагора:
Тут a і b — два оголошення, а нижній індекс нумерує ознаку:
a₁ — відносна ціна першого, a₂ — вік його акаунта. Різниці
показують, наскільки оголошення розходяться по кожній ознаці окремо; квадрат прибирає знак
і підсилює великі розходження; корінь повертає результат до масштабу ознак. Це
евклідова відстань — та сама, якою міряють відрізок лінійкою. Для
p ознак під коренем буде p доданків.
Порахуємо руками на двох конкретних оголошеннях:
Оголошення №1: відносна ціна 0.60 (це 7 200 грн), акаунту 30 днів.
Оголошення №2: відносна ціна 0.90 (10 800 грн), акаунту 210 днів.
Порівняй результат із доданками. Відстань дорівнює 180.0002, а різниця у віці — рівно 180. Ціна не вплинула ні на що: її внесок у підкореневу суму становить 0.00028%.
Це не арифметична помилка й не властивість цієї пари. Це наслідок одиниць вимірювання: вік акаунта живе в діапазоні 0…365, а відносна ціна — у 0.30…1.45. Дні просто більші за частки й у сумі квадратів розчавлюють усе інше. Розділ 6 присвячений саме цій пастці; поки що зрівняємо масштаби найпростішим способом — поділимо кожну ознаку на її розмах:
Розмах — це відстань від найменшого значення ознаки до найбільшого. У ціни це 1.45 − 0.30 = 1.15, у віку акаунта — 365 − 0 = 365. Ділення на розмах перекладає обидві різниці в одну спільну одиницю: «яку частку всього можливого діапазону ознаки займає це розходження».
Тепер ціна дає 21.9% підкореневої суми замість 0.00028% — вона нарешті бере участь у рішенні. Усі відстані далі в лекції рахуються саме так, у зрівняних одиницях.
Евклідова відстань — не єдина можлива. Друга за поширеністю — манхеттенська (Manhattan, вона ж L1): замість кореня із суми квадратів беруть просто суму модулів різниць.
Назва від сітки вулиць Мангеттена: пішки не можна йти навскіс, лише кварталами, тож шлях дорівнює сумі двох катетів, а не гіпотенузі. Беруть її тоді, коли не хочеться, щоб одна велика розбіжність вирішувала все: квадрат підсилює великі різниці, модуль — ні. Тому на різнорідних ознаках і в високій розмірності вона поводиться стійкіше.
Дві відстані між тими самими оголошеннями
Ліворуч — геометрія в зрівняних одиницях. Праворуч — внесок кожної ознаки у квадрат відстані до і після зрівнювання масштабів.
model не відняти від іншої моделі. Стандартний вихід — one-hot: кожна модель
стає окремою ознакою 0/1, і різниця між різними моделями дорівнює √2, а між однаковими —
нулю. Для суто категорійних даних беруть відстань Геммінга — частку неспівпадінь.ПрогнозГолосування і середнє
Відстань є — тепер сам прогноз. Він робиться однаково для обох типів задач, різниця тільки в останній дії.
Класифікація: сусіди голосують
Порахуй відстань від нового оголошення до всіх оголошень архіву, відсортуй за
зростанням, візьми перші k і подивись, яких міток серед них більше. Ця
мітка й буде відповіддю. Ось запит: оголошення за 9 000 грн (0.75 від типової ціни) від
акаунта віком 160 днів.
Голосування девʼяти сусідів
Один запит, девʼять найближчих сусідів з архіву, дві різні відповіді залежно від k.
Звідси видно дві речі. По-перше, k — не технічна дрібниця, а головне
налаштування методу: воно й тільки воно вирішило долю цього оголошення. По-друге, для двох
класів k беруть непарним, інакше голоси можуть розділитись
порівну.
scikit-learn це weights='distance', вага дорівнює 1/d.
Тоді сусід упритул важить більше за того, що ледве потрапив у коло, а модель менш чутлива
до вибору k.Регресія: сусіди усереднюються
Той самий метод відповідає й на інше питання дошки: скільки взагалі коштує телефон, схожий на цей? Ознаками будуть рік випуску, обсяг памʼяті й стан, а відповіддю — не мітка, а число. Змінюється рівно одна дія: замість голосування беремо середнє цін сусідів.
Нехай треба оцінити телефон 2021 року, 128 ГБ, стан 4. Пʼять найсхожіших оголошень з архіву:
| сусід | рік | памʼять | стан | відстань | ціна, грн |
|---|---|---|---|---|---|
| 1 | 2021 | 128 | 4 | 0.00 | 11 400 |
| 2 | 2021 | 128 | 5 | 0.25 | 12 200 |
| 3 | 2020 | 128 | 4 | 0.33 | 10 900 |
| 4 | 2022 | 128 | 4 | 0.33 | 12 600 |
| 5 | 2021 | 256 | 4 | 0.50 | 11 900 |
При k = 3 відповідь дорівнює (11 400 + 12 200 + 10 900) / 3 =
11 500 грн. При k = 5 — (11 400 + 12 200 + 10 900 + 12 600 +
11 900) / 5 = 11 800 грн. Знову ж таки, k змінює відповідь.
Вибір kСкільки сусідів питати
Настав час покрутити головне налаштування. Візьми повзунок і проведи його від краю
до краю — тут видно все, що варто знати про k.
Повзунок k і межа рішень
Ліворуч — область рішення на площині ознак і навчальні оголошення. Праворуч — точність на 260 відкладених оголошеннях для кожного k.
Три режими, які ти щойно побачив, варто назвати словами.
- k = 1. Межа рвана: навколо кожного поодинокого оголошення виростає власний острівець. Модель повторює не закономірність, а кожну випадковість архіву — зокрема й помилкові мітки. Це перенавчання (overfitting) у найчистішому вигляді: модель запамʼятала навчальні дані замість того, щоб вивчити правило, тому на знайомих оголошеннях вона бездоганна, а на нових — ні. Окрема тема про це — 17 «Перенавчання, недонавчання і розклад помилки».
- Помірне k. Голоси кількох сусідів гасять викиди, межа згладжується,
але зберігає форму. Найкраще працює
k = 9з точністю 0.942 проти 0.885 приk = 1. - Завелике k. У коло потрапляє півархіву, дрібні згустки зникають —
вузька область «преміум-шахрайства» перестає існувати для моделі. При
k = 51точність падає до 0.881, а взявшиkрівним усій вибірці, ми отримали б ту саму відповідь усюди: «чесне» з точністю 0.715.
Під цим компромісом лежить пара, яку варто назвати вже тут. Зміщення
(bias) — це помилка від того, що модель занадто груба, щоб уловити справжню форму
закономірності: велике k усереднює півархіву й стирає вузький згусток
«преміум»-шахрайства. Дисперсія (variance) — помилка від того, що модель
занадто чутлива й ганяється за випадковостями: k = 1 обростає острівцями
навколо кожного помилково поміченого оголошення. Роль складності моделі тут грає величина
1/k: маленьке k — мале зміщення й велика дисперсія, велике
k — навпаки, а десь між ними лежить мінімум сумарної помилки, і крива праворуч
на інтерактиві його показує. Розкладати помилку на ці дві частини системно вчить тема
17 «Перенавчання, недонавчання і розклад
помилки»; там та сама крива побудована по степені многочлена, а не по k.
k і взяли найкраще за тестовою вибіркою — тобто підглянули у
відповідь. Так робити не можна: обране так k буде оптимістично зміщеним.
Чесний спосіб — крос-валідація на
навчальній частині, а тестова чіпається один раз наприкінці. Про цей розподіл ролей —
тема Train / Validation / Test.
Крива тут показана лише тому, що ми вивчаємо поведінку методу, а не будуємо продукт.МасштабПастка, на якій ламається більшість
Повертаємось до розбіжності, помітної ще в розділі 3. Візьмемо не відносну ціну,
а дві сирі колонки таблиці як вони є: price у гривнях і
account_age_days у днях. Ціни в архіві коливаються від 3 600 до 17 300 грн,
вік акаунта — від 0 до 365 днів. Тобто найбільша можлива різниця у ціні дорівнює
приблизно 13 700, а у віці — 365, і у квадраті це розрив приблизно в
1 400 разів.
Наслідок жорсткий: у сирих одиницях kNN фактично шукає оголошення зі схожою ціною, а вік акаунта не впливає ні на що. Друга ознака формально в моделі є, а фактично її немає.
Вплив масштабу ознак
Ті самі дві ознаки в тих самих осях. Перемикач змінює тільки одне: у яких одиницях рахується відстань.
Числа за перемикачем такі. При k = 7 у сирих одиницях точність дорівнює
0.750, після зрівнювання — 0.931. Для порівняння:
тривіальна відповідь «усі оголошення чесні» дає 0.715. Тобто
незмасштабований kNN майже нічого не додає до відповіді «нічого не роби».
А позначене зірочкою оголошення міняє вирок. У сирих одиницях його сусідами стають сім оголошень зі схожою ціною й будь-яким віком акаунта: шахрайським серед них виявилось одне з семи — вирок «чесне», промах. У зрівняних одиницях сусіди схожі і за ціною, і за віком: шахрайських уже пʼять із семи — вирок «шахрайство», влучання.
Способів зрівняти масштаби два, обидва є в scikit-learn, і обидва вже
розібрані в темі 09 «Передобробка даних» —
тут вони потрібні тому, що для kNN це не гігієна, а питання життя й смерті:
- Min-max (
MinMaxScaler): відняти мінімум і поділити на розмах, ознака лягає у [0, 1]. Саме це ми робили руками в розділі 3. Просто, але один викид розтягує шкалу й підтискає решту даних у кут. - Стандартизація (
StandardScaler): відняти середнє й поділити на стандартне відхилення. Стійкіша до викидів, тому беруть зазвичай її.
make_pipeline(StandardScaler(), KNeighborsClassifier(k)) — тоді
fit усередині крос-валідації
масштабує кожну частину окремо й помилитись просто ніде.І думка, яку легко проґавити: зрівнювання не «прибирає викривлення», а задає, у якій пропорції ознаки впливають на схожість. Рівні ваги — теж рішення, просто прийняте за замовчуванням. Якщо ти знаєш, що вік акаунта вдвічі важливіший за ціну, помножити його стандартизовану колонку на 2 — законний хід, і саме так у kNN вводять експертне знання.
РозмірністьКоли «найближчий» втрачає сенс
Досі ознак було дві. Спокуса очевидна: додамо ще двадцять — модель же стане розумнішою. З kNN усе навпаки, і причина глибша за швидкодію.
У просторі багатьох вимірів відстані між випадковими точками стають майже однаковими: найближча точка до запиту виявляється майже такою самою далекою, як найдальша. А якщо найближчий сусід не ближчий за інших, слово «найближчий» перестає щось означати — і разом із ним перестає працювати весь метод. Це і є прокляття розмірності (curse of dimensionality). Перевіримо прямо: розкидаємо 400 випадкових точок у кубі зі стороною 1, поміряємо для кожного запиту відстань до найближчої й до найдальшої, а тоді почнемо додавати виміри до тих самих точок.
Прокляття розмірності
400 точок, рівномірно розкиданих у кубі. Повзунок додає виміри; відношення «найближча / найдальша» повзе до одиниці.
Числа з інтерактиву: при 2 вимірах відношення дорівнює 0.027 — найближча точка у 37 разів ближча за найдальшу. При 10 вимірах воно вже 0.308, при 50 — 0.620. Пучок стискається, і кожен наступний вимір робить «сусідство» дедалі умовнішим.
Пояснити це можна й без обчислень, самим обʼємом. Обʼєм кубика з ребром
r у просторі з d вимірами дорівнює r, помноженому
само на себе d разів: на площині це квадрат r·r, у трьох
вимірах — r·r·r, і так далі. Отже, щоб кубик займав 1% усього обʼєму, його
ребро має бути таким числом, яке після d перемножень само на себе дає 0.01.
Це число називають коренем d-го степеня з 0.01 і записують
0.01^(1/d): дріб у показнику означає корінь, а не звичайний степінь.
Перевір на двох вимірах: 0.1 × 0.1 = 0.01, тобто ребро 0.1 — маленький
квадратик у кутку, як і має бути. А при d = 50 потрібне ребро вже
0.912: щоб набрати той самий один відсоток сусідів, «околиця» має
розтягнутись майже на весь куб по кожній осі. Локальність зникає, а kNN — метод
суто локальний.
ЦінаНавчання безкоштовне, передбачення дороге
Тепер про рахунок, який приходить наприкінці. У лінійних моделей навчання коштує дорого (ітерації по всій вибірці), зате передбачення — кілька множень: модель уже стиснута в коефіцієнти. У kNN усе рівно навпаки.
kNN проти лінійних моделей
Одні й ті самі рядки таблиці читаються по-різному залежно від того, що для тебе дорого.
| kNN | лінійна / логістична регресія | |
|---|---|---|
| навчання | миттєве: скопіювати таблицю | дороге: ітерації по всій вибірці |
| передбачення | дороге: n·p дій на кожен запит | миттєве: p множень |
| памʼять | уся навчальна таблиця | p + 1 чисел |
| форма межі | будь-яка, задається даними | тільки пласка |
| масштаб ознак | критичний | бажаний |
| зайві ознаки | шкодять завжди | гасяться регуляризацією |
| пояснення | прикладами: ось схожі випадки | коефіцієнтами: ось внесок ознаки |
| оновлення даних | дописати рядок — і все | перенавчити модель |
Порахуємо на нашій дошці. Архів у 300 оголошень і два запити на секунду — 600 порівнянь на секунду, тобто нічого. Архів у 5 мільйонів і 1 000 запитів на секунду — 5 мільярдів обчислень відстані щосекунди, і у відповідь за 50 мілісекунд це не вкладеться ніяк.
Повний перебір, утім, не єдиний варіант. scikit-learn уміє будувати
просторові індекси — KD-дерево й ball tree, — які
відсікають цілі області, куди найближчий сусід точно не потрапить
(algorithm='auto' обирає сам). Але тут знову вспливає розмірність:
приблизно після 20 ознак дерева перестають відсікати хоч щось і вироджуються у той самий
перебір.
Коли kNN усе-таки доречний? Коли таблиця вміщається в памʼять; коли відповідь потрібна не миттєво, а пакетом уночі; коли дані постійно змінюються й перенавчати щось важке було б дорожче; коли задача формулюється як «знайди схоже» — рекомендації, пошук дублікатів, підказка ціни. І окремо — коли треба пояснення прикладом: «ми позначили це оголошення, бо ось три майже такі самі, і всі три виявились приманками».
ПідсумокЩо варто винести
kNN — перший у курсі метод, у якого немає моделі. Він не стискає дані в коефіцієнти й не робить припущень про форму межі: він зберігає всю таблицю й відповідає на кожен запит окремо. Тому межа виходить ламаною там, де лінійні моделі змушені проводити пряму, і тому ж уся ціна методу переїхала з навчання в передбачення.
| ідея | суть | чому важливо |
|---|---|---|
| сусідство | k найближчих із архіву | уся модель — це сам архів |
| відстань | евклідова або манхеттенська | означення слова «схожий» |
| класифікація | голосування сусідів | непарне k рятує від нічиї |
| регресія | середнє їхніх значень | прогноз не виходить за межі архіву |
| вибір k | 1/k — це складність моделі | маленьке k повторює шум, велике стирає деталі |
| масштаб | зрівняти обовʼязково | без цього одна ознака зʼїдає всі інші |
| розмірність | відстані сходяться | «найближчий» втрачає сенс |
Далі в курсі буде ще два способи обійтися без прямої межі, і обидва варто порівняти саме з kNN. Наївний Баєс піде протилежним шляхом: він теж навчається майже миттєво, але замість зберігання таблиці рахує кілька ймовірностей на кожну ознаку — і тому передбачає миттєво, а не дорого. А дерева рішень ріжуть той самий простір ознак іншим інструментом: не кулями навколо запиту, а прямокутниками, розставленими заздалегідь. Межа в них теж виходить ламаною — але, на відміну від kNN, її можна намалювати на папері й прочитати вголос.
І памʼятай, для чого ми взагалі рахували точність. На дошці шахрайських оголошень 28%,
і ціна двох помилок різна: пропущена приманка коштує грошей покупцю, хибна тривога —
репутації чесному продавцю. Точність такого не розрізняє, тож у справжній задачі метрику
беруть із теми Precision і Recall. Тут
вона слугує лише спільною лінійкою для порівняння k.