Уяви дошку оголошень про вживані телефони. Щодня зʼявляються сотні нових, і частина з них — приманки: людина побачить неймовірну ціну, переведе завдаток і більше нікого не знайде. Ти модеруєш цю дошку, і модель для підозрілих оголошень дуже потрібна. Але перш ніж її будувати, помітимо, що в тебе вже є архів: тисячі старих оголошень, про кожне з яких відомо, чим воно закінчилось.
І тут виникає думка, з якої починається вся тема. А що, як нічого не будувати? Що, як для нового оголошення просто знайти в архіві кілька найсхожіших і подивитись, чим виявились вони?
01 / ПостановкаСхожі поводяться схоже
Це і є метод 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 грн. Друга ознака — вік акаунта продавця в днях: приманки майже завжди виставляють зі свіжозареєстрованих акаунтів.
У попередній темі ми вже вміли розвʼязувати таку задачу. Логістична регресія знаходить межу рішення — лінію, по один бік якої модель каже «шахрайство», по інший «чесне». Ця лінія завжди пряма: таке вже влаштування лінійного класифікатора. Подивись, що буде, якщо межу дозволити гнути.
Схема 1 · Пряма межа проти ламаної
Ті самі 300 навчальних оголошень, та сама пара ознак, дві різні моделі. Точність поміряна на 260 відкладених оголошеннях.
Різниця не косметична. Шахрайство тут двох ґатунків: масові дешеві приманки зі свіжих акаунтів (велика область ліворуч унизу) і вузький згусток праворуч унизу — нові акаунти, що продають майже за ринковою ціною, аби не викликати підозри дешевизною. Одна пряма не обведе обидві області одночасно, тому логістична регресія жертвує меншою. kNN не жертвує нічим: кожна ділянка площини вирішується незалежно від інших.
02 / НепараметричністьНавчання, якого немає
Тепер найважливіша особливість методу, і вона не про точність. Згадай, що означало «навчити модель» досі. У лінійній і логістичній регресії навчання — це підбір кількох чисел, коефіцієнтів. Після навчання дані можна викинути: уся вивчена інформація стиснулась у три-чотири числа, і саме вони й були моделлю.
У kNN підбирати нічого. Немає коефіцієнтів, немає рівняння, немає жодної величини, яку
алгоритм обчислив би на навчальних даних. Навчання kNN — це просто запамʼятати
таблицю. Буквально: fit() копіює дані в памʼять і на цьому
закінчується. Тому такі методи називають непараметричними (non-parametric),
а сам kNN — лінивим (lazy learner).
k у методі є, але це гіперпараметр — його не виводять з даних,
а обирають зовні, як поріг у логістичній регресії. Обсяг «вивченого» у kNN росте разом
із вибіркою: тисяча рядків — тисяча рядків моделі.Із цієї властивості випливає майже все далі. Модель нічого не спрощує й не має жодного припущення про форму межі. Але робота, якої не було під час навчання, нікуди не поділась — вона переїхала в момент передбачення. До цього повернемось у розділі 8.
03 / ВідстаньЩо означає «схожий»
Слово «найсхожіший» треба перетворити на число, інакше алгоритму нема що порівнювати. Найпростіший спосіб — вважати кожне оголошення точкою в просторі ознак і взяти звичайну геометричну відстань між точками. Для двох ознак це шкільна теорема Піфагора:
Тут 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 присвячений саме цій пастці; поки що зрівняємо масштаби найпростішим способом — поділимо кожну ознаку на її розмах:
Тепер ціна дає 21.9% підкореневої суми замість 0.00028% — вона нарешті бере участь у рішенні. Усі відстані далі в лекції рахуються саме так, у зрівняних одиницях.
Евклідова відстань — не єдина можлива. Друга за поширеністю — манхеттенська (Manhattan, вона ж L1): замість кореня із суми квадратів беруть просто суму модулів різниць.
Назва від сітки вулиць Мангеттена: пішки не можна йти навскіс, лише кварталами, тож шлях дорівнює сумі двох катетів, а не гіпотенузі. Беруть її тоді, коли не хочеться, щоб одна велика розбіжність вирішувала все: квадрат підсилює великі різниці, модуль — ні. Тому на різнорідних ознаках і в високій розмірності вона поводиться стійкіше.
Схема 2 · Дві відстані між тими самими оголошеннями
Ліворуч — геометрія в зрівняних одиницях. Праворуч — внесок кожної ознаки у квадрат відстані до і після зрівнювання масштабів.
model не відняти від іншої моделі. Стандартний вихід — one-hot: кожна модель
стає окремою ознакою 0/1, і різниця між різними моделями дорівнює √2, а між однаковими —
нулю. Для суто категорійних даних беруть відстань Геммінга — частку неспівпадінь.04 / ПрогнозГолосування і середнє
Відстань є — тепер сам прогноз. Він робиться однаково для обох типів задач, різниця тільки в останній дії.
Класифікація: сусіди голосують
Порахуй відстань від нового оголошення до всіх оголошень архіву, відсортуй за
зростанням, візьми перші k і подивись, яких міток серед них більше. Ця
мітка й буде відповіддю. Ось запит: оголошення за 9 000 грн (0.75 від типової ціни) від
акаунта віком 160 днів.
Схема 3 · Голосування дев'яти сусідів
Один запит, дев'ять найближчих сусідів з архіву, дві різні відповіді залежно від 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 змінює відповідь.
05 / Вибір kСкільки сусідів питати
Настав час покрутити головне налаштування. Візьми повзунок і проведи його від краю
до краю — тут видно все, що варто знати про k.
Інтерактив 1 · Повзунок k і межа рішень
Ліворуч — область рішення на площині ознак і навчальні оголошення. Праворуч — точність на 260 відкладених оголошеннях для кожного k.
Три режими, які ти щойно побачив, варто назвати словами.
- k = 1. Межа рвана: навколо кожного поодинокого оголошення виростає власний острівець. Модель повторює не закономірність, а кожну випадковість архіву — зокрема й помилкові мітки. Це перенавчання в найчистішому вигляді.
- Помірне k. Голоси кількох сусідів гасять викиди, межа згладжується,
але зберігає форму. Найкраще працює
k = 9з точністю 0.942 проти 0.885 приk = 1. - Завелике k. У коло потрапляє півархіву, дрібні згустки зникають —
вузька область «преміум-шахрайства» перестає існувати для моделі. При
k = 51точність падає до 0.881, а взявшиkрівним усій вибірці, ми отримали б ту саму відповідь усюди: «чесне» з точністю 0.715.
Це той самий компроміс, якому присвячена тема
Bias-Variance, просто в інших позначеннях.
Роль складності моделі грає величина 1/k: маленьке k — мале
зміщення й велика дисперсія, велике k — навпаки. Криву праворуч на інтерактиві
ти вже бачив у тій темі, тільки по горизонталі там був степінь многочлена.
k і взяли найкраще за тестовою вибіркою — тобто підглянули у
відповідь. Так робити не можна: обране так k буде оптимістично зміщеним.
Чесний спосіб — крос-валідація на
навчальній частині, а тестова чіпається один раз наприкінці. Про цей розподіл ролей —
тема Train / Validation / Test.
Крива тут показана лише тому, що ми вивчаємо поведінку методу, а не будуємо продукт.06 / МасштабПастка, на якій ламається більшість
Повертаємось до розбіжності, помітної ще в розділі 3. Візьмемо не відносну ціну,
а дві сирі колонки таблиці як вони є: price у гривнях і
account_age_days у днях. Ціни в архіві коливаються від 3 600 до 17 300 грн,
вік акаунта — від 0 до 365 днів. Тобто найбільша можлива різниця у ціні дорівнює
приблизно 13 700, а у віці — 365, і у квадраті це розрив приблизно в
1 400 разів.
Наслідок жорсткий: у сирих одиницях kNN фактично шукає оголошення зі схожою ціною, а вік акаунта не впливає ні на що. Друга ознака формально в моделі є, а фактично її немає.
Інтерактив 2 · Вплив масштабу ознак
Ті самі дві ознаки в тих самих осях. Перемикач змінює тільки одне: у яких одиницях рахується відстань.
Числа за перемикачем такі. При k = 7 у сирих одиницях точність дорівнює
0.750, після зрівнювання — 0.931. Для порівняння:
тривіальна відповідь «усі оголошення чесні» дає 0.715. Тобто
незмасштабований kNN майже нічого не додає до відповіді «нічого не роби».
А позначене зірочкою оголошення міняє вирок. У сирих одиницях його сусідами стають сім оголошень зі схожою ціною й будь-яким віком акаунта: шахрайським серед них виявилось одне з семи — вирок «чесне», промах. У зрівняних одиницях сусіди схожі і за ціною, і за віком: шахрайських уже п'ять із семи — вирок «шахрайство», влучання.
Способів зрівняти масштаби два, обидва є в scikit-learn:
- Min-max (
MinMaxScaler): відняти мінімум і поділити на розмах, ознака лягає у [0, 1]. Саме це ми робили руками в розділі 3. Просто, але один викид розтягує шкалу й підтискає решту даних у кут. - Стандартизація (
StandardScaler): відняти середнє й поділити на стандартне відхилення. Стійкіша до викидів, тому беруть зазвичай її.
make_pipeline(StandardScaler(), KNeighborsClassifier(k)) — тоді
fit усередині крос-валідації
масштабує кожну частину окремо й помилитись просто ніде.І думка, яку легко проґавити: зрівнювання не «прибирає викривлення», а задає, у якій пропорції ознаки впливають на схожість. Рівні ваги — теж рішення, просто прийняте за замовчуванням. Якщо ти знаєш, що вік акаунта вдвічі важливіший за ціну, помножити його стандартизовану колонку на 2 — законний хід, і саме так у kNN вводять експертне знання.
07 / РозмірністьКоли «найближчий» втрачає сенс
Досі ознак було дві. Спокуса очевидна: додамо ще двадцять — модель же стане розумнішою. З kNN усе навпаки, і причина глибша за швидкодію.
У просторі багатьох вимірів відстані між випадковими точками стають майже однаковими: найближча точка до запиту виявляється майже такою самою далекою, як найдальша. А якщо найближчий сусід не ближчий за інших, слово «найближчий» перестає щось означати — і разом із ним перестає працювати весь метод. Це і є прокляття розмірності (curse of dimensionality). Перевіримо прямо: розкидаємо 400 випадкових точок у кубі зі стороною 1, поміряємо для кожного запиту відстань до найближчої й до найдальшої, а тоді почнемо додавати виміри до тих самих точок.
Інтерактив 3 · Прокляття розмірності
400 точок, рівномірно розкиданих у кубі. Повзунок додає виміри; відношення «найближча / найдальша» повзе до одиниці.
Числа з інтерактиву: при 2 вимірах відношення дорівнює 0.027 — найближча точка у 37 разів ближча за найдальшу. При 10 вимірах воно вже 0.308, при 50 — 0.620. Пучок стискається, і кожен наступний вимір робить «сусідство» дедалі умовнішим.
Пояснити це можна й без обчислень. Щоб у кубі розмірності d охопити околицю
з 1% усього обʼєму, її ребро має дорівнювати 0.01^(1/d). При d = 2
це 0.1 — маленький квадратик у кутку. При d = 50 уже
0.912: щоб набрати той самий відсоток сусідів, «околиця» має розтягнутись
майже на весь куб по кожній осі. Локальність зникає, а kNN — метод суто локальний.
08 / ЦінаНавчання безкоштовне, передбачення дороге
Тепер про рахунок, який приходить наприкінці. У лінійних моделей навчання коштує дорого (ітерації по всій вибірці), зате передбачення — кілька множень: модель уже стиснута в коефіцієнти. У kNN усе рівно навпаки.
Схема 4 · kNN проти лінійних моделей
Одні й ті самі рядки таблиці читаються по-різному залежно від того, що для тебе дорого.
| kNN | лінійна / логістична регресія | |
|---|---|---|
| навчання | миттєве: скопіювати таблицю | дороге: ітерації по всій вибірці |
| передбачення | дороге: n·p дій на кожен запит | миттєве: p множень |
| памʼять | уся навчальна таблиця | p + 1 чисел |
| форма межі | будь-яка, задається даними | тільки пласка |
| масштаб ознак | критичний | бажаний |
| зайві ознаки | шкодять завжди | гасяться регуляризацією |
| пояснення | прикладами: ось схожі випадки | коефіцієнтами: ось внесок ознаки |
| оновлення даних | дописати рядок — і все | перенавчити модель |
Порахуємо на нашій дошці. Архів у 300 оголошень і два запити на секунду — 600 порівнянь на секунду, тобто нічого. Архів у 5 мільйонів і 1 000 запитів на секунду — 5 мільярдів обчислень відстані щосекунди, і у відповідь за 50 мілісекунд це не вкладеться ніяк.
Повний перебір, утім, не єдиний варіант. scikit-learn уміє будувати
просторові індекси — KD-дерево й ball tree, — які
відсікають цілі області, куди найближчий сусід точно не потрапить
(algorithm='auto' обирає сам). Але тут знову вспливає розмірність:
приблизно після 20 ознак дерева перестають відсікати хоч щось і вироджуються у той самий
перебір.
Коли kNN усе-таки доречний? Коли таблиця вміщається в памʼять; коли відповідь потрібна не миттєво, а пакетом уночі; коли дані постійно змінюються й перенавчати щось важке було б дорожче; коли задача формулюється як «знайди схоже» — рекомендації, пошук дублікатів, підказка ціни. І окремо — коли треба пояснення прикладом: «ми позначили це оголошення, бо ось три майже такі самі, і всі три виявились приманками».
09 / ПідсумокЩо варто винести
kNN — перший у курсі метод, у якого немає моделі. Він не стискає дані в коефіцієнти й не робить припущень про форму межі: він зберігає всю таблицю й відповідає на кожен запит окремо. Тому межа виходить ламаною там, де лінійні моделі змушені проводити пряму, і тому ж уся ціна методу переїхала з навчання в передбачення.
| ідея | суть | чому важливо |
|---|---|---|
| сусідство | k найближчих із архіву | уся модель — це сам архів |
| відстань | евклідова або манхеттенська | означення слова «схожий» |
| класифікація | голосування сусідів | непарне k рятує від нічиї |
| регресія | середнє їхніх значень | прогноз не виходить за межі архіву |
| вибір k | 1/k — це складність моделі | маленьке k повторює шум, велике стирає деталі |
| масштаб | зрівняти обовʼязково | без цього одна ознака зʼїдає всі інші |
| розмірність | відстані сходяться | «найближчий» втрачає сенс |
Далі в курсі буде ще два способи обійтися без прямої межі, і обидва варто порівняти саме з kNN. Наївний Баєс піде протилежним шляхом: він теж навчається майже миттєво, але замість зберігання таблиці рахує кілька ймовірностей на кожну ознаку — і тому передбачає миттєво, а не дорого. А дерева рішень ріжуть той самий простір ознак іншим інструментом: не кулями навколо запиту, а прямокутниками, розставленими заздалегідь. Межа в них теж виходить ламаною — але, на відміну від kNN, її можна намалювати на папері й прочитати вголос.
І памʼятай, для чого ми взагалі рахували точність. На дошці шахрайських оголошень 28%,
і ціна двох помилок різна: пропущена приманка коштує грошей покупцю, хибна тривога —
репутації чесному продавцю. Точність такого не розрізняє, тож у справжній задачі метрику
беруть із теми Precision і Recall. Тут
вона слугує лише спільною лінійкою для порівняння k.
practice.ipynb ти порахуєш матрицю
відстаней на NumPy й знайдеш k сусідів вручну, звіриш свої прогнози з
KNeighborsClassifier до останнього обʼєкта, проженеш k від 1 до 50
й побудуєш криву точності, побачиш стрибок від StandardScaler і наприкінці
оціниш ціну телефона через KNeighborsRegressor.Далі в темі
Теорію прочитано. Тепер закріпи її на практиці.