ЛР3
Как измерить беспорядок

Задание 1 · основа дерева

Как измерить беспорядок

Дереву нужно число, которое говорит, насколько в куче объектов «всё перемешано». Пишем три таких числа: энтропию и индекс Джини для классов и MSE для чисел.

Идея словами

Дерево делит объекты на две кучки. Хорошее деление то, после которого в каждой кучке почти одинаковые ответы. Чтобы сравнивать деления, нужна мера беспорядка: 0, если в кучке всё одинаковое, и больше, чем сильнее перемешано.

На пальцах. Корзина с носками. Если в ней только чёрные носки, беспорядок 0: вытащишь не глядя и точно знаешь цвет. Если поровну чёрных и белых, беспорядок максимальный: угадать цвет невозможно.
Доля класса p
Какая часть кучки относится к классу: 5 чёрных из 6 носков, p = 5/6.
Энтропия
Мера неопределённости из теории информации. Измеряется в битах: 1 бит = один вопрос «да/нет», который ещё нужно задать.
Индекс Джини
Вероятность ошибиться, если взять случайный объект и приписать ему случайную метку из той же кучки.
MSE (для регрессии)
Средний квадрат отклонения от среднего, то есть дисперсия: насколько числа в кучке разбросаны.
log₂
Логарифм по основанию 2: в какую степень возвести 2, чтобы получить число. log₂ 8 = 3, потому что 2³ = 8.

Поиграй: корзина носков

Добавляй и убирай носки трёх цветов и смотри, как меняются энтропия и Джини. Попробуй: все одного цвета; поровну двух цветов; поровну трёх.

энтропия (от 0 до 1.585)

Джини (от 0 до 0.667)

Формулы с числами

Кучка [0, 0, 0, 0, 0, 1]: пять нулей и одна единица, доли 5/6 и 1/6.

энтропия = -Σ p·log₂(p) = -(5/6·log₂(5/6) + 1/6·log₂(1/6)) = 0.219 + 0.431 = 0.650 Джини = 1 - Σ p² = 1 - (5/6)² - (1/6)² = 1 - 0.694 - 0.028 = 0.278 кучка [0,1,2,0,1,2,0,1,2]: энтропия = log₂3 = 1.585, Джини = 1 - 3·(1/3)² = 0.667 MSE([1, 2, 3]): среднее 2, отклонения -1, 0, 1 → (1 + 0 + 1) / 3 = 0.667

Код (ячейка с функционалами)

def entropy(y):  # y - массив значений целевой переменной
    _, counts = np.unique(y, return_counts=True)
    p = counts / counts.sum()           # доли классов p_k
    return np.sum(p * np.log2(1 / p))   # то же, что -sum(p * log2(p)), но чистый узел даёт 0.0, а не -0.0


def gini_index(y):
    _, counts = np.unique(y, return_counts=True)
    p = counts / counts.sum()
    return 1 - np.sum(p ** 2)

def mse(y):
    y = np.asarray(y, dtype=float)
    return np.mean((y - y.mean()) ** 2)
  1. np.unique(y, return_counts=True) находит разные значения и сколько раз каждое встретилось. Для [0,0,0,0,0,1] это значения [0, 1] и счётчики [5, 1]. Значения нам не нужны, поэтому _.
  2. p = counts / counts.sum() делим счётчики на общее число: [5/6, 1/6]. Делится сразу весь массив, без цикла.
  3. np.sum(p * np.log2(1 / p)) формула энтропии. log₂(1/p) = -log₂(p), поэтому минус уходит внутрь. Зачем: для чистой кучки p = 1, и обычная формула даёт «-0.0», а тест сравнивает с 0.0 через ==. Так получается ровно 0.0.
  4. 1 - np.sum(p ** 2) Джини: единица минус сумма квадратов долей.
  5. np.asarray(y, dtype=float) превращаем список в массив numpy, чтобы вычитать из него число.
  6. np.mean((y - y.mean()) ** 2) среднее квадратов отклонений от среднего.
Ловушка: тесты почти ничего не проверяют. В тестах стоит rtol=1e5, то есть допустимая ошибка в 100 000 раз больше самого числа: пройдёт почти что угодно. Поэтому сразу после тестов добавлена ячейка, которая печатает настоящие значения: 0.6500, 1.5850, 0.2778, 0.6667, 0.6667, 66.6667. В тесте числа просто обрезаны до 0.65, 1.58, 0.27, 0.66.
Ловушка: «без циклов». Все три функции написаны одной формулой numpy. Цикл for по объектам тут был бы ошибкой стиля: numpy умеет делать операцию сразу над всем массивом.

В узле 10 объектов, все класса 1. Чему равна энтропия?

Что показывает MSE в узле регрессии?

Вопросы на защите

Почему энтропия чистой кучки равна 0?

Там один класс с долей p = 1, а log₂(1) = 0. Значит, 1 · 0 = 0: неопределённости нет.

Какой максимум у энтропии для 2 и 3 классов?

При равных долях: log₂2 = 1 бит для двух классов, log₂3 = 1.585 для трёх. У Джини максимум 0.5 и 0.667.

Чем энтропия отличается от Джини на практике?

Почти ничем: обе нулевые для чистой кучки и максимальные при равных долях. Джини чуть быстрее (нет логарифма). У нас на ирисах они дали почти одинаковую точность.

Почему MSE подходит для регрессии?

В регрессии нет классов, ответы это числа. «Порядок» означает, что числа близки друг к другу, а это и есть маленькая дисперсия (MSE от среднего).

Одна фразаЭнтропия, Джини и MSE измеряют беспорядок в кучке: 0, если ответы одинаковые; посчитаны одной формулой numpy через доли классов из np.unique.