Применение Scikit-learn в задачах анализа данных

Раздел: Прочее -> scikit-learn

Работа с библиотекой Scikit-learn: от данных к готовой модели

Библиотека Scikit-learn содержит инструменты для решения задач классификации, регрессии, кластеризации и анализа данных. Основой работы является единый интерфейс оценщиков: fit обучает модель, predict формирует прогноз, transform преобразует данные.

Ниже описан порядок действий, который часто используется в небольшом проекте машинного обучения.


from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

data = load_wine()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=7, stratify=y
)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

clf = RandomForestClassifier(n_estimators=100, random_state=7)
clf.fit(X_train_scaled, y_train)

y_pred = clf.predict(X_test_scaled)
print(accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred, target_names=data.target_names))

библиотеки для машинного обучения python (библиотеки для машинного обучения в python (scikit-learn, tensorflow, pytorch))

0.9777777777777777
              precision    recall  f1-score   support

    class_0       1.00      1.00      1.00        13
    class_1       0.94      1.00      0.97        16
    class_2       1.00      0.92      0.96        16

    accuracy                           0.98        45
   macro avg       0.98      0.97      0.98        45
weighted avg       0.98      0.98      0.98        45

Feature names python (имена признаков в python)

Набор данных Wine содержит три класса. Разбиение с stratify=y сохраняет доли классов в обучающей и тестовой частях. Стандартизация выполняется после разделения, поэтому параметры масштабирования вычисляются только по обучающим данным.

Как объединить предобработку и модель в конвейере?

Конвейер позволяет выполнить несколько преобразований подряд и обучить модель одним вызовом fit.


from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import GradientBoostingRegressor

data = load_diabetes()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

numeric_columns = list(range(data.data.shape[1]))
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

preprocessor = ColumnTransformer(
    transformers=[('num', numeric_transformer, numeric_columns)]
)

pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('regressor', GradientBoostingRegressor(random_state=42))
])

pipeline.fit(X_train, y_train)
score = pipeline.score(X_test, y_test)
print(score)

Python model (модели в python (машинное обучение))

0.43

статистическая библиотека python (статистическая библиотека python (scipy, statsmodels))

В диабетическом наборе все признаки числовые, поэтому ColumnTransformer содержит один блок. В реальном проекте в этот же список добавляются категориальные колонки с OneHotEncoder.

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

Как перебрать гиперпараметры и выбрать лучший вариант?


from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

data = load_breast_cancer()
X, y = data.data, data.target

pipe = Pipeline(steps=[
    ('scaler', StandardScaler()),
    ('svc', SVC())
])

param_grid = {
    'svc__C': [0.1, 1, 10],
    'svc__gamma': ['scale', 0.01, 0.1]
}

search = GridSearchCV(pipe, param_grid, cv=5, scoring='f1', n_jobs=-1)
search.fit(X, y)
print(search.best_params_)
print(search.best_score_)

создание нейросетей на python (создание нейронных сетей на python (tensorflow, pytorch))

{'svc__C': 10, 'svc__gamma': 'scale'}
0.9776

Ai script python (скрипты для искусственного интеллекта на python)

Ключ svc__C состоит из имени шага и имени параметра. Такой подход работает и с конвейерами, и с отдельными моделями.

Важно: при использовании GridSearchCV с конвейером предобработка находится внутри пайплайна, поэтому она применяется отдельно на каждом фолде. Это предотвращает утечку данных.

Как работать с несбалансированными классами?


from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

X, y = make_classification(
    n_samples=4000, n_features=20,
    weights=[0.9, 0.1], random_state=11
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=11
)

rf = RandomForestClassifier(
    n_estimators=150, class_weight='balanced', random_state=11
)
rf.fit(X_train, y_train)

y_pred = rf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['majority', 'minority']))

Random forest python (random forest в python)

              precision    recall  f1-score   support

    majority       0.97      0.99      0.98      1080
    minority       0.91      0.80      0.85       120

    accuracy                           0.97      1200
   macro avg       0.94      0.90      0.92      1200
weighted avg       0.96      0.97      0.96      1200

нейросеть python (нейросеть на python)

Параметр class_weight='balanced' увеличивает штраф за ошибки на редких классах. Альтернативой становится отдельное взвешивание моделей или изменение порога классификации.

Частая ошибка: использование accuracy как единственной метрики при сильно несбалансированных данных. Точность может оставаться высокой, тогда как модель совсем не распознает редкий класс. Для оценки применяются precision, recall и F1-мера.

Как выполнить кластеризацию и оценить качество разбиения?


from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

X, true_labels = make_blobs(
    n_samples=800, centers=4, cluster_std=0.9, random_state=3
)

best_k = 1
best_score = -1
for k in range(2, 7):
    model = KMeans(n_clusters=k, random_state=3, n_init=10)
    labels = model.fit_predict(X)
    score = silhouette_score(X, labels)
    print(k, score)
    if score > best_score:
        best_score = score
        best_k = k

print('best_k', best_k)

нейросеть с помощью python (нейросети на python)

2 0.667
3 0.746
4 0.792
5 0.758
6 0.734
best_k 4

рекомендательная система python (разработка рекомендательной системы на python)

Метод силуэта возвращает значения от минус единицы до единицы. Чем ближе значение к единице, тем четче разделение кластеров.

Как уменьшить число признаков с помощью PCA?


from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

data = load_digits()
X = data.data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(pca.explained_variance_ratio_)
print(X_pca.shape)

библиотеки scikit learn python (библиотека scikit-learn для python)

[0.1208 0.0959]
(1797, 2)

Первые две главные компоненты объясняют часть дисперсии. При выборе числа компонент обычно ориентируются на суммарную объясненную дисперсию около 0.8 или 0.9.

Ошибка: применение PCA без стандартизации. Если признаки имеют разный масштаб, компоненты будут определяться в основном признаками с большой дисперсией.

Как сохранить обученную модель и конвейер?


from joblib import dump, load
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
dump(model, 'logistic_model.joblib')

loaded_model = load('logistic_model.joblib')
new_pred = loaded_model.predict(X_test)
print(loaded_model.score(X_test, y_test))
0.94

Сохранение всего конвейера удобнее, чем сохранение только модели, потому что при загрузке не нужно повторять шаги предобработки.

Типичная проблема: если pipeline содержит кастомный трансформатор, его класс должен быть доступен при загрузке. Файл joblib хранит ссылку на класс, а не код класса.

Пользовательский трансформатор для удаления коррелирующих признаков

Встроенные трансформаторы покрывают типовые операции. Для нестандартной логики можно определить собственный класс на основе BaseEstimator и TransformerMixin.

Пример

import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin

class DropCorrelated(BaseEstimator, TransformerMixin):
    def __init__(self, threshold=0.9):
        self.threshold = threshold
        self.drop_columns_ = []

    def fit(self, X, y=None):
        corr = np.corrcoef(X, rowvar=False)
        upper = np.triu(corr, k=1)
        drop = set()
        for i in range(corr.shape[1] - 1):
            for j in range(i + 1, corr.shape[1]):
                if abs(upper[i, j]) > self.threshold:
                    drop.add(j)
        self.drop_columns_ = sorted(drop)
        return self

    def transform(self, X):
        return np.delete(X, self.drop_columns_, axis=1)

Метод fit вычисляет корреляционную матрицу и сохраняет индексы колонок, которые следует исключить. В методе transform эти колонки удаляются из массива.

Пример

from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X, y = data.data, data.target

pipe = Pipeline([
    ('drop', DropCorrelated(threshold=0.95)),
    ('scaler', StandardScaler()),
    ('model', LogisticRegression(max_iter=200))
])
pipe.fit(X, y)
print(pipe.named_steps['drop'].drop_columns_)
print(pipe.score(X, y))
[5, 12, 17, 21, 22, 23, 24]
0.9824

Такой трансформатор удобно использовать внутри Pipeline. Обучающая и тестовая выборки проходят одинаковые преобразования.

Важно: если трансформатор применяется в ColumnTransformer, нужно добавить метод get_feature_names_out. Без него не будет работать вывод имен признаков после преобразования.

Многометочная перекрестная проверка с повторными разбиениями

Повторная стратифицированная кросс-валидация полезна для стабильной оценки качества на небольших выборках.

Пример

from sklearn.model_selection import cross_validate, RepeatedStratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine

data = load_wine()
X, y = data.data, data.target
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=1)
scoring = ['accuracy', 'f1_macro', 'roc_auc_ovr']
results = cross_validate(
    RandomForestClassifier(random_state=1),
    X, y, cv=cv, scoring=scoring
)
for metric in scoring:
    key = 'test_' + metric
    print(metric, round(results[key].mean(), 4))
accuracy 0.9857
f1_macro 0.9854
roc_auc_ovr 0.9982

Результат содержит несколько метрик одновременно, поэтому не нужно обучать модель по отдельности для каждой метрики.

Рекурсивный отбор признаков с перекрестной проверкой

Рекурсивное удаление признаков помогает найти небольшой набор переменных, которые сохраняют качество модели.

Пример

from sklearn.datasets import make_classification
from sklearn.feature_selection import RFECV
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler

X, y = make_classification(
    n_samples=500, n_features=15, n_informative=6,
    random_state=10
)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
model = LogisticRegression(max_iter=200)
cv = StratifiedKFold(5, shuffle=True, random_state=10)
selector = RFECV(model, cv=cv, scoring='accuracy')
selector.fit(X_scaled, y)
print(selector.n_features_)
6

Число признаков совпало с количеством информативных переменных в синтетическом наборе. На реальных данных RFECV выполняет отбор на основе внутренней важности признаков модели.

Калибровка вероятностей редких событий

Для оценки вероятностей классов часто применяется калибровка. Модель случайного леса может выдавать смещенные вероятности, поэтому используется CalibratedClassifierCV.

Пример

from sklearn.calibration import CalibratedClassifierCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import brier_score_loss

X, y = make_classification(
    n_samples=2000, n_features=10, n_informative=5,
    weights=[0.8, 0.2], random_state=5
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=5
)
base = RandomForestClassifier(n_estimators=100, random_state=5)
base.fit(X_train, y_train)
prob_before = base.predict_proba(X_test)[:, 1]
loss_before = brier_score_loss(y_test, prob_before)

calibrated = CalibratedClassifierCV(base, cv=5, method='sigmoid')
calibrated.fit(X_train, y_train)
prob_after = calibrated.predict_proba(X_test)[:, 1]
loss_after = brier_score_loss(y_test, prob_after)

print('brier before', round(loss_before, 4))
print('brier after', round(loss_after, 4))
brier before 0.0961
brier after 0.0724

Метрика Бриера показывает средний квадрат отклонения предсказанной вероятности от фактического события. Меньшее значение означает более точные вероятности.

Автоматический выбор колонок по типу данных

make_column_selector упрощает построение преобразователя, когда колонки выбираются по типу данных, а не по списку имен.

Пример

import pandas as pd
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.preprocessing import StandardScaler, OneHotEncoder

df = pd.DataFrame({
    'age': [25, 30, 35],
    'income': [50000, 60000, 70000],
    'city': ['Moscow', 'Kazan', 'Tomsk']
})
X = df.drop(columns=['income'])

preprocessor = ColumnTransformer([
    ('num', StandardScaler(), make_column_selector(dtype_include='int64')),
    ('cat', OneHotEncoder(), make_column_selector(dtype_include='object'))
])
transformed = preprocessor.fit_transform(X)
print(transformed.shape)
(3, 4)

В этом примере числовая колонка age стандартизируется, а город превращается в три бинарных признака. Метод автоматически определяет нужные колонки по их типу.

Библиотека Scikit-learn для Python - comments

En
библиотеки scikit learn python (python)