Домашнє завдання

k найближчих сусідів

Усі три рівні незалежні — можна робити будь-який. Здавати одним зошитом, у якому кожна відповідь супроводжується числом або графіком, а не лише словами.


рівень 1Рівень 1 — База

Повтори головний сюжет лекції на своїх даних: покажи, що масштабування ознак для kNN — не косметика.

Візьми будь-який табличний датасет із sklearn.datasets — підійдуть load_breast_cancer() або load_wine(). Обидва зручні тим, що ознаки в них навмисне різного масштабу. Розбий на train/test через train_test_split(..., random_state=0) і побудуй дві криві точності для k від 1 до 40 на одному графіку: без масштабування й з StandardScaler.

Зроблено, якщо: - на графіку дві криві, підписані осі й легенда; - у тексті виписано чотири числа: точність при k = 1 і при найкращому k — для кожної з двох кривих; - названо, у скільки разів розмах найбільшої ознаки датасету перевищує розмах найменшої (X.max(axis=0) - X.min(axis=0)), і одним реченням пояснено, чому саме ця ознака визначала сусідів до масштабування.


рівень 2Рівень 2 — Плюс

Виміряй прокляття розмірності на живій задачі, а не на випадкових точках.

Візьми load_wine() (13 ознак), обовʼязково зі StandardScaler, і фіксуй k = 5. Далі дописуй до матриці ознак колонки чистого шуму: np.random.default_rng(0).normal(0, 1, (len(X), скільки)) — по 0, 5, 10, 20, 50, 100 зайвих колонок. Щоразу міряй точність на тесті.

Поруч зроби те саме для LogisticRegression(max_iter=5000) на тих самих даних.

Зроблено, якщо: - побудовано графік «точність проти кількості шумових ознак» із двома лініями (kNN і логістична регресія); - у таблиці зведено точність обох моделей для 0 і для 100 шумових колонок; - сформульовано висновок, чому шум б'є по kNN сильніше: у поясненні має бути згадано, що шумова ознака входить у відстань нарівні з корисною, тоді як лінійна модель може дати їй коефіцієнт, близький до нуля; - додатково: заміряно %timeit на predict для kNN при 0 і при 100 ознаках і показано, що час зростає.


рівень 3Рівень 3 — Виклик

Напиши kNN з нуля так, щоб він збігся з бібліотечним і працював за прийнятний час на всій тестовій вибірці одразу.

  1. Реалізуй матриця_відстаней(A, B), яка повертає масив розміром len(A) × len(B) без жодного циклу Python. Підказка: розкрий квадрат різниці — ‖a − b‖² = ‖a‖² − 2·a·bᵀ + ‖b‖², і всі три доданки рахуються матричними операціями.
  2. Реалізуй передбачити(X_запитів, X_навч, y_навч, k, ваги), де ваги набуває значень "uniform" або "distance". Для "distance" вага сусіда дорівнює 1 / відстань, а прогноз — клас із найбільшою сумою ваг.
  3. Окремо оброби випадок нульової відстані: якщо запит збігається з навчальним обʼєктом, бібліотека повертає мітку саме цього обʼєкта, ігноруючи решту сусідів.

Зроблено, якщо: - проходять обидві перевірки на даних із практики: assert np.array_equal(наші, KNeighborsClassifier(n_neighbors=7).fit(...).predict(...)) та те саме з weights="distance"; - окремою клітинкою виведено час роботи твоєї векторизованої версії й версії з циклом for із практики на тих самих 280 тестових обʼєктах — із числами; - у тексті названо, скільки чисел твоя реалізація тримає в памʼяті одночасно для вибірки 520 × 280, і чому на мільйоні рядків цей підхід уже не спрацює.


Підказки