# Домашнє завдання · Пошук аномалій

> Лекція: [lecture.html](lecture.html) · Практика: [practice.ipynb](practice.ipynb) · Тест: [quiz.html](quiz.html)

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

---

## 🟢 Рівень 1 — База

Головний важіль LOF — кількість сусідів `n_neighbors`. У практиці ми взяли 20 і не
пояснили чому. Перевір це руками.

Порахуй `LocalOutlierFactor` із `n_neighbors` = 5, 20, 50 і 200. Для кожного значення
візьми топ-20 найаномальніших оголошень і порахуй `precision@20` проти колонки
`шахрайське`. Зведи все в таблицю з чотирьох рядків: `k`, скільки шахрайських у топ-20,
`precision@20`.

Потім подивись очима на топ-5 при `k = 5` і при `k = 200` — це різні списки, і різниця
змістовна.

**Зроблено, якщо:** є таблиця з чотирьох рядків із реальними числами **і** два-три
речення про те, що ламається при замалому `k` (кожна випадкова пара точок оголошує
себе окремою щільною групою) і що — при завеликому (сусідство розповзається на пів
хмари, і «локальний» перестає бути локальним).

---

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

У лекції ми з'ясували, що шахрая на цій дошці видає не ціна, а **відношення** ціни до
типової — і що жоден із трьох методів придумати таку колонку не може. Перевір, що
станеться, якщо принести її методам на блюдечку.

1. Додай до семи ознак восьму: `np.log(ціна / типова_ціна)`.
2. Стандартизуй усі вісім і перерахуй усі три методи — `IsolationForest`,
   `LocalOutlierFactor` і помилку відновлення після PCA.
3. Побудуй таблицю `precision@20` **до** й **після** для кожного з трьох методів.

**Зроблено, якщо:** є таблиця 3 × 2 з реальними числами і письмова відповідь на два
питання: (1) який метод виграв найбільше і чому саме він, (2) чому жоден метод не міг
знайти цю залежність сам, маючи обидві вихідні колонки. Друга відповідь має посилатись
на те, що всі три методи працюють із сумами й відстанями, а відношення — операція
нелінійна.

---

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

Реалізуй спрощений LOF з нуля, на самому `numpy`, без `sklearn.neighbors`.

Алгоритм на трьох кроках, як у розділі 4 лекції:

1. Для кожного оголошення знайди `k = 20` найближчих сусідів у стандартизованих
   ознаках і порахуй **середню відстань** до них.
2. Для кожного оголошення візьми середнє тієї самої величини **по його сусідах**.
3. Оцінка — відношення першого числа до другого.

Далі порівняй свій рейтинг із бібліотечним: скільки спільних рядків у топ-50 і яка
кореляція рангів. Розбіжність буде — знайди її причину.

**Зроблено, якщо:**

* твоя функція повертає оцінку для всіх 1 100 рядків і не використовує
  `LocalOutlierFactor`;
* у топ-50 твого рейтингу і бібліотечного щонайменше 35 спільних рядків;
* є письмове пояснення розбіжності, яке називає **відстань досяжності**
  (reachability distance) і те, навіщо вона потрібна: щоб оцінка не стрибала
  через одну випадково близьку пару точок.

---

## Підказки

* Для рівня 1: `LocalOutlierFactor` рахує оцінку лише після виклику `fit_predict`;
  сама оцінка лежить у `negative_outlier_factor_`, і вона тим **менша**, чим точка
  аномальніша, — не забудь про знак.
* Для рівня 2: логарифм відношення зручніший за саме відношення, бо робить
  «удвічі дешевше» і «удвічі дорожче» симетричними; порівняй розподіли обох варіантів,
  перш ніж вибирати.
* Для рівня 3: матрицю попарних відстаней на 1 100 точках можна порахувати одним
  рядком через `np.linalg.norm(Z[:, None, :] - Z[None, :, :], axis=2)` — це матриця
  1 100 × 1 100, памʼяті вистачить. Не забудь поставити на діагональ нескінченність,
  щоб точка не була сусідом самій собі.
* Якщо результат виглядає дивно — спершу перевір, чи стандартизував ти ознаки.
  Без цього вік акаунта в днях перекриє стан у балах повністю.
