Классификация и вероятности

LogisticRegression: fit, predict и classes_

Содержание курса

В прошлом уроке мы разобрались, что метка класса — это дискретное значение 0 или 1, а не непрерывное число. Теперь задача: взять DataFrame с такими метками и обучить на нём классификатор. Интерфейс окажется до боли знакомым — тот же fit, тот же predict, что и у LinearRegression, — но есть несколько деталей, которые важно зафиксировать сразу.

Импорт LogisticRegression и разделение данных на X и y

Импорт выглядит так:

from sklearn.linear_model import LogisticRegression

clf = LogisticRegression()

Объект создан, но он пока не знает ничего о данных: никаких весов, никакой границы решения. Это просто контейнер с настройками по умолчанию, пригодными для числовых признаков.

Теперь — данные. Создадим игрушечный DataFrame df с четырьмя столбцами: distance_km, weight_kg, warehouse_zone и is_delayed. Последний — целевой, содержит 0 и 1.

import pandas as pd

df = pd.DataFrame({
    'distance_km':    [30, 45, 60, 75, 90, 35, 50, 65, 80, 95,
                       40, 55, 70, 85, 100, 25, 110, 120, 130, 140],
    'weight_kg':      [1.0, 1.6, 2.2, 2.8, 3.4, 1.2, 1.8, 2.4, 3.0, 3.6,
                       1.4, 2.0, 2.6, 3.2, 3.8, 0.8, 4.0, 4.5, 5.0, 5.5],
    'warehouse_zone': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1,
                       2, 3, 1, 2, 3, 1, 2, 3, 1, 2],
    'is_delayed':     [1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
                       1, 0, 1, 0, 1, 0, 1, 1, 0, 1]
})

Первым делом отделяем целевой столбец и разбиваем данные на train и test:

from sklearn.model_selection import train_test_split

X_raw = df.drop(columns=['is_delayed'])
y = df['is_delayed']

X_train_raw, X_test_raw, y_train, y_test = train_test_split(
    X_raw, y, test_size=0.2, random_state=42
)

Только после разбиения кодируем warehouse_zone. Это важный порядок действий: если закодировать категориальный признак до разбиения — на полном df, — то набор dummy-столбцов определяется по всем строкам сразу, включая тестовые. В результате кодировщик «знает» о категориях из тестовой части ещё до того, как мы сделали вид, что этих данных не существует. Если в тесте окажется категория, которой нет в трейне, кодировщик всё равно создаст для неё столбец — и это искусственно меняет представление признаков. Чтобы тестовые данные никак не влияли на то, как устроено признаковое пространство, кодировщик нужно обучать только на X_train_raw.

warehouse_zone — категориальный признак: зона 1, зона 2, зона 3. Если оставить его числом, модель воспримет номера зон как количественную упорядоченную шкалу с равными интервалами — то есть будет считать, что между зоной 1 и зоной 2 такое же расстояние, как между зоной 2 и зоной 3, и что зоны вообще можно сравнивать по величине. На деле это просто идентификаторы без какого-либо числового смысла. Используем OneHotEncoder через ColumnTransformer, обучаем только на X_train_raw:

from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer

preprocessor = ColumnTransformer(transformers=[
    ('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False), ['warehouse_zone'])
], remainder='passthrough')

X_train = preprocessor.fit_transform(X_train_raw)
X_test = preprocessor.transform(X_test_raw)

fit_transform на X_train_raw запоминает, какие категории существуют. transform на X_test_raw применяет ту же зафиксированную схему — без «подглядывания» в тестовые данные.

Параметр handle_unknown='ignore' определяет поведение кодировщика при встрече с незнакомой категорией: вместо ошибки он запишет нули во все dummy-столбцы для этой строки. Важно понимать, что это лишь способ не упасть с ValueError — не более. Ни один обучающий пример не кодировался таким образом, поэтому модель никогда не «видела» подобного вектора при обучении. Прогноз для строки с неизвестной зоной остаётся экстраполяцией: модель что-то вернёт, но это значение нельзя считать надёжным. Если неизвестные зоны реально возможны в боевых данных, правильные варианты — отклонять такие строки или явно помечать прогноз как ненадёжный.

В итоге X_train и X_test — NumPy-массивы с числовыми столбцами, готовые для передачи в clf.fit. y_train и y_test — Series с бинарными метками 0 и 1.

Принципиальный момент, который не меняется по сравнению с регрессией: is_delayed не должен попасть в X. Модель предсказывает y по X, а не ищет y внутри X. Состав и порядок признаков фиксируются здесь — всё, что модель получит при predict, должно в точности соответствовать этой схеме: те же столбцы, тот же порядок, прошедшие через тот же обученный preprocessor.