декабря 21, 2025
14 мин. чтения

Вопросы на собеседовании Junior Machine Learning Engineer

interview
career-advice
job-search
entry-level
Вопросы на собеседовании Junior Machine Learning Engineer
Milad Bonakdar

Milad Bonakdar

Автор

Подготовьтесь к собеседованию Junior ML Engineer: Python, оценка моделей, утечка данных, развертывание, мониторинг и основы MLOps.


Вопросы на собеседовании Junior Machine Learning Engineer

На собеседовании Junior Machine Learning Engineer важно уметь объяснить, как вы пишете надежный Python-код, обучаете и оцениваете модели, предотвращаете утечку данных, готовите модель к развертыванию и отслеживаете предсказания после релиза. Сильный ответ показывает не только алгоритм, но и допущения о данных, выбор метрик и производственные компромиссы.

Используйте этот гид, чтобы потренировать типичные вопросы для стартовой ML engineering роли: Python, классические ML-алгоритмы, валидация, несбалансированные данные, model serving, Docker, мониторинг и основы CI/CD.

Python и программирование (5 вопросов)

1. Как вы обрабатываете большие наборы данных, которые не помещаются в память?

Ответ: Существует несколько методов обработки данных, размер которых превышает доступную оперативную память:

  • Пакетная обработка (Batch Processing): Обработка данных частями
  • Генераторы (Generators): Выдача данных по требованию
  • Dask/Ray: Фреймворки для распределенных вычислений
  • Запросы к базе данных (Database Queries): Загрузка только необходимых данных
  • Memory-Mapped Files: Доступ к диску, как если бы он находился в памяти
  • Потоковая передача данных (Data Streaming): Обработка данных по мере их поступления
import pandas as pd
import numpy as np

# Плохо: Загрузка всего набора данных в память
# df = pd.read_csv('large_file.csv')  # Может привести к сбою

# Хорошо: Чтение по частям
chunk_size = 10000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # Обработка каждой части
    processed = chunk[chunk['value'] > 0]
    # Сохранение или агрегация результатов
    processed.to_csv('output.csv', mode='a', header=False)

# Использование генераторов
def data_generator(filename, batch_size=32):
    while True:
        batch = []
        with open(filename, 'r') as f:
            for line in f:
                batch.append(process_line(line))
                if len(batch) == batch_size:
                    yield np.array(batch)
                    batch = []

# Dask для распределенных вычислений
import dask.dataframe as dd
ddf = dd.read_csv('large_file.csv')
result = ddf.groupby('category').mean().compute()

Распространенность: Очень часто Сложность: Средняя


2. Объясните, что такое декораторы в Python и приведите пример использования в ML.

Ответ: Декораторы изменяют или расширяют функциональность функций, не меняя их код.

  • Примеры использования в ML:
    • Замер времени выполнения функции
    • Логирование предсказаний
    • Кэширование результатов
    • Проверка входных данных
import time
import functools

# Декоратор для замера времени
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f} seconds")
        return result
    return wrapper

# Декоратор для логирования предсказаний
def log_predictions(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        predictions = func(*args, **kwargs)
        print(f"Made {len(predictions)} predictions")
        print(f"Prediction distribution: {np.bincount(predictions)}")
        return predictions
    return wrapper

# Использование
@timer
@log_predictions
def predict_batch(model, X):
    return model.predict(X)

# Декоратор для кэширования результатов (мемоизация)
def cache_results(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@cache_results
def expensive_feature_engineering(data_id):
    # Дорогостоящие вычисления
    return processed_features

Распространенность: Часто Сложность: Средняя


3. В чем разница между @staticmethod и @classmethod?

Ответ: Оба определяют методы, которые не требуют экземпляра класса.

  • @staticmethod: Не имеет доступа к классу или экземпляру
  • @classmethod: Получает класс в качестве первого аргумента
class MLModel:
    model_type = "classifier"
    
    def __init__(self, name):
        self.name = name
    
    # Обычный метод - нужен экземпляр
    def predict(self, X):
        return self.model.predict(X)
    
    # Статический метод - служебная функция
    @staticmethod
    def preprocess_data(X):
        # Нет доступа к self или cls
        return (X - X.mean()) / X.std()
    
    # Метод класса - паттерн "фабрика"
    @classmethod
    def create_default(cls):
        # Есть доступ к cls
        return cls(name=f"default_{cls.model_type}")
    
    @classmethod
    def from_config(cls, config):
        return cls(name=config['name'])

# Использование
# Статический метод - не нужен экземпляр
processed = MLModel.preprocess_data(X_train)

# Метод класса - создает экземпляр
model = MLModel.create_default()
model2 = MLModel.from_config({'name': 'my_model'})

Распространенность: Средне Сложность: Средняя


4. Как вы обрабатываете исключения в ML-конвейерах?

Ответ: Правильная обработка ошибок предотвращает сбои конвейера и помогает в отладке.

import logging
from typing import Optional

# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ModelTrainingError(Exception):
    """Пользовательское исключение для сбоев при обучении модели"""
    pass

def train_model(X, y, model_type='random_forest'):
    try:
        logger.info(f"Starting training with {model_type}")
        
        # Проверка входных данных
        if X.shape[0] != y.shape[0]:
            raise ValueError("X and y must have same number of samples")
        
        if X.shape[0] < 100:
            raise ModelTrainingError("Insufficient training data")
        
        # Обучение модели
        if model_type == 'random_forest':
            from sklearn.ensemble import RandomForestClassifier
            model = RandomForestClassifier()
        else:
            raise ValueError(f"Unknown model type: {model_type}")
        
        model.fit(X, y)
        logger.info("Training completed successfully")
        return model
        
    except ValueError as e:
        logger.error(f"Validation error: {e}")
        raise
    except ModelTrainingError as e:
        logger.error(f"Training error: {e}")
        # Можно вернуться к более простой модели
        return train_fallback_model(X, y)
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        raise
    finally:
        logger.info("Training attempt finished")

# Контекстный менеджер для управления ресурсами
class ModelLoader:
    def __init__(self, model_path):
        self.model_path = model_path
        self.model = None
    
    def __enter__(self):
        logger.info(f"Loading model from {self.model_path}")
        self.model = load_model(self.model_path)
        return self.model
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        logger.info("Cleaning up resources")
        if self.model:
            del self.model
        return False  # Не подавлять исключения

# Использование
with ModelLoader('model.pkl') as model:
    predictions = model.predict(X_test)

Распространенность: Часто Сложность: Средняя


5. Что такое генераторы в Python и почему они полезны в ML?

Ответ: Генераторы выдают значения по одному, экономя память.

  • Преимущества:
    • Эффективное использование памяти
    • Отложенные вычисления
    • Бесконечные последовательности
  • Примеры использования в ML:
    • Загрузка данных
    • Пакетная обработка
    • Аугментация данных
import numpy as np

# Генератор для пакетной обработки
def batch_generator(X, y, batch_size=32):
    n_samples = len(X)
    indices = np.arange(n_samples)
    np.random.shuffle(indices)
    
    for start_idx in range(0, n_samples, batch_size):
        end_idx = min(start_idx + batch_size, n_samples)
        batch_indices = indices[start_idx:end_idx]
        yield X[batch_indices], y[batch_indices]

# Использование при обучении
for epoch in range(10):
    for X_batch, y_batch in batch_generator(X_train, y_train):
        model.train_on_batch(X_batch, y_batch)

# Генератор для аугментации изображений
def augment_images(images, labels):
    for img, label in zip(images, labels):
        # Оригинал
        yield img, label
        # Отражение
        yield np.fliplr(img), label
        # Поворот
        yield np.rot90(img), label

# Бесконечный генератор для обучения
def infinite_batch_generator(X, y, batch_size=32):
    while True:
        indices = np.random.choice(len(X), batch_size)
        yield X[indices], y[indices]

# Использование с steps_per_epoch
gen = infinite_batch_generator(X_train, y_train)
# model.fit(gen, steps_per_epoch=100, epochs=10)

Распространенность: Часто Сложность: Средняя


ML Алгоритмы и теория (5 вопросов)

6. Объясните разницу между бэггингом и бустингом.

Ответ: Оба являются ансамблевыми методами, но работают по-разному:

  • Бэггинг (Bootstrap Aggregating):
    • Параллельное обучение на случайных подмножествах
    • Уменьшает дисперсию
    • Пример: Random Forest
  • Бустинг:
    • Последовательное обучение, каждая модель исправляет ошибки предыдущей
    • Уменьшает смещение
    • Примеры: AdaBoost, Gradient Boosting, XGBoost
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score

# Генерация данных
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)

# Бэггинг - Random Forest
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf_scores = cross_val_score(rf, X, y, cv=5)
print(f"Random Forest CV: {rf_scores.mean():.3f} (+/- {rf_scores.std():.3f})")

# Бустинг - Gradient Boosting
gb = GradientBoostingClassifier(n_estimators=100, random_state=42)
gb_scores = cross_val_score(gb, X, y, cv=5)
print(f"Gradient Boosting CV: {gb_scores.mean():.3f} (+/- {gb_scores.std():.3f})")

# XGBoost (продвинутый бустинг)
import xgboost as xgb
xgb_model = xgb.XGBClassifier(n_estimators=100, random_state=42)
xgb_scores = cross_val_score(xgb_model, X, y, cv=5)
print(f"XGBoost CV: {xgb_scores.mean():.3f} (+/- {xgb_scores.std():.3f})")

Распространенность: Очень часто Сложность: Средняя


7. Как вы обрабатываете несбалансированные наборы данных?

Ответ: Несбалансированные данные могут смещать модели в сторону преобладающего класса.

  • Методы:
    • Пересэмплинг (Resampling): SMOTE, undersampling
    • Веса классов (Class weights): Штрафовать за неправильную классификацию
    • Ансамблевые методы (Ensemble methods): Balanced Random Forest
    • Оценка (Evaluation): Использовать F1, precision, recall (а не accuracy)
    • Настройка порога (Threshold adjustment): Оптимизировать порог принятия решений
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from collections import Counter

# Создание несбалансированного набора данных
X, y = make_classification(
    n_samples=1000, n_features=20,
    weights=[0.95, 0.05],  # 95% класс 0, 5% класс 1
    random_state=42
)

print(f"Original distribution: {Counter(y)}")

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 1. Веса классов
model_weighted = RandomForestClassifier(class_weight='balanced', random_state=42)
model_weighted.fit(X_train, y_train)
print("\nWith class weights:")
print(classification_report(y_test, model_weighted.predict(X_test)))

# 2. SMOTE (увеличение выборки миноритарного класса)
smote = SMOTE(random_state=42)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
print(f"After SMOTE: {Counter(y_train_smote)}")

model_smote = RandomForestClassifier(random_state=42)
model_smote.fit(X_train_smote, y_train_smote)
print("\nWith SMOTE:")
print(classification_report(y_test, model_smote.predict(X_test)))

# 3. Настройка порога
y_proba = model_weighted.predict_proba(X_test)[:, 1]
threshold = 0.3  # Более низкий порог благоприятствует миноритарному классу
y_pred_adjusted = (y_proba >= threshold).astype(int)
print("\nWith adjusted threshold:")
print(classification_report(y_test, y_pred_adjusted))

Распространенность: Очень часто Сложность: Средняя


8. Что такое кросс-валидация и почему она важна?

Ответ: Кросс-валидация оценивает производительность модели более надежно, чем однократное разделение на обучающий и тестовый наборы.

  • Типы:
    • K-Fold: Разделение на k частей
    • Stratified K-Fold: Сохраняет распределение классов
    • Time Series Split: Учитывает временной порядок
  • Преимущества:
    • Более надежная оценка производительности
    • Использует все данные для обучения и валидации
    • Обнаруживает переобучение
from sklearn.model_selection import (
    cross_val_score, KFold, StratifiedKFold,
    TimeSeriesSplit, cross_validate
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Загрузка данных
data = load_iris()
X, y = data.data, data.target

model = RandomForestClassifier(random_state=42)

# Стандартная K-Fold
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kfold)
print(f"K-Fold scores: {scores}")
print(f"Mean: {scores.mean():.3f} (+/- {scores.std():.3f})")

# Stratified K-Fold (сохраняет распределение классов)
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
stratified_scores = cross_val_score(model, X, y, cv=stratified_kfold)
print(f"\nStratified K-Fold: {stratified_scores.mean():.3f}")

# Time Series Split (для временных рядов)
tscv = TimeSeriesSplit(n_splits=5)
ts_scores = cross_val_score(model, X, y, cv=tscv)
print(f"Time Series CV: {ts_scores.mean():.3f}")

# Несколько метрик
cv_results = cross_validate(
    model, X, y, cv=5,
    scoring=['accuracy', 'precision_macro', 'recall_macro', 'f1_macro'],
    return_train_score=True
)

print(f"\nAccuracy: {cv_results['test_accuracy'].mean():.3f}")
print(f"Precision: {cv_results['test_precision_macro'].mean():.3f}")
print(f"Recall: {cv_results['test_recall_macro'].mean():.3f}")
print(f"F1: {cv_results['test_f1_macro'].mean():.3f}")

Распространенность: Очень часто Сложность: Легкая


9. Объясните, что такое precision, recall и F1-score.

Ответ: Метрики классификации для оценки производительности модели:

  • Precision (Точность): Из предсказанных положительных, сколько правильных
    • Формула: TP / (TP + FP)
    • Использовать, когда: Ложноположительные результаты дорого обходятся
  • Recall (Полнота): Из фактических положительных, сколько было найдено
    • Формула: TP / (TP + FN)
    • Использовать, когда: Ложноотрицательные результаты дорого обходятся
  • F1-Score: Гармоническое среднее между precision и recall
    • Формула: 2 × (Precision × Recall) / (Precision + Recall)
    • Использовать, когда: Нужен баланс между precision и recall
from sklearn.metrics import (
    precision_score, recall_score, f1_score,
    classification_report, confusion_matrix
)
import numpy as np

# Пример предсказаний
y_true = np.array([0, 1, 1, 0, 1, 1, 0, 1, 0, 0])
y_pred = np.array([0, 1, 0, 0, 1, 1, 0, 1, 1, 0])

# Расчет метрик
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)

print(f"Precision: {precision:.3f}")  # 0.800
print(f"Recall: {recall:.3f}")        # 0.800
print(f"F1-Score: {f1:.3f}")          # 0.800

# Матрица ошибок
cm = confusion_matrix(y_true, y_pred)
print(f"\nConfusion Matrix:\n{cm}")
# [[4 1]
#  [1 4]]

# Отчет о классификации (все метрики)
print("\nClassification Report:")
print(classification_report(y_true, y_pred))

# Пример компромисса
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt

# Получение вероятностных предсказаний
y_proba = model.predict_proba(X_test)[:, 1]

# Расчет precision-recall при разных порогах
precisions, recalls, thresholds = precision_recall_curve(y_test, y_proba)

# Нахождение оптимального порога (максимизация F1)
f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-10)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
print(f"Optimal threshold: {optimal_threshold:.3f}")

Распространенность: Очень часто Сложность: Легкая


10. Что такое регуляризация и когда ее следует использовать?

Ответ: Регуляризация предотвращает переобучение, штрафуя модель за сложность.

  • Типы:
    • L1 (Lasso): Добавляет абсолютное значение коэффициентов
    • L2 (Ridge): Добавляет квадрат коэффициентов
    • Elastic Net: Комбинирует L1 и L2
  • Когда использовать:
    • Высокая дисперсия (переобучение)
    • Много признаков
    • Мультиколлинеарность
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Генерация данных с большим количеством признаков
X, y = make_regression(
    n_samples=100, n_features=50,
    n_informative=10, noise=10, random_state=42
)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# Без регуляризации
lr = LinearRegression()
lr.fit(X_train, y_train)
lr_score = r2_score(y_test, lr.predict(X_test))
print(f"Linear Regression R²: {lr_score:.3f}")

# L2 Регуляризация (Ridge)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
ridge_score = r2_score(y_test, ridge.predict(X_test))
print(f"Ridge R²: {ridge_score:.3f}")

# L1 Регуляризация (Lasso)
lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)
lasso_score = r2_score(y_test, lasso.predict(X_test))
print(f"Lasso R²: {lasso_score:.3f}")
print(f"Lasso non-zero coefficients: {np.sum(lasso.coef_ != 0)}")

# Elastic Net (L1 + L2)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic.fit(X_train, y_train)
elastic_score = r2_score(y_test, elastic.predict(X_test))
print(f"Elastic Net R²: {elastic_score:.3f}")

# Настройка гиперпараметра alpha
from sklearn.model_selection import GridSearchCV

param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0]}
grid = GridSearchCV(Ridge(), param_grid, cv=5)
grid.fit(X_train, y_train)
print(f"\nBest alpha: {grid.best_params_['alpha']}")
print(f"Best CV score: {grid.best_score_:.3f}")

Распространенность: Очень часто Сложность: Средняя


Обучение и развертывание моделей (5 вопросов)

11. Как вы сохраняете и загружаете модели в production-среде?

Ответ: Сериализация моделей позволяет развертывать и повторно использовать их.

import joblib
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Обучение модели
X, y = load_iris(return_X_y=True)
model = RandomForestClassifier()
model.fit(X, y)

# Метод 1: Joblib (рекомендуется для scikit-learn)
joblib.dump(model, 'model.joblib')
loaded_model = joblib.load('model.joblib')

# Метод 2: Pickle
with open('model.pkl', 'wb') as f:
    pickle.dump(model, f)

with open('model.pkl', 'rb') as f:
    loaded_model = pickle.load(f)

# Для TensorFlow/Keras
import tensorflow as tf

# Сохранение всей модели
# keras_model.save('model.h5')
# loaded_model = tf.keras.models.load_model('model.h5')

# Сохранение только весов
# keras_model.save_weights('model_weights.h5')
# new_model.load_weights('model_weights.h5')

# Для PyTorch
import torch

# Сохранение словаря состояния модели
# torch.save(model.state_dict(), 'model.pth')
# model.load_state_dict(torch.load('model.pth'))

# Сохранение всей модели
# torch.save(model, 'model_complete.pth')
# model = torch.load('model_complete.pth')

# Контроль версий для моделей
import datetime

model_version = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
model_path = f'models/model_{model_version}.joblib'
joblib.dump(model, model_path)
print(f"Model saved to {model_path}")

Распространенность: Очень часто Сложность: Легкая


12. Как вы создаете REST API для обслуживания моделей?

Ответ: REST API делают модели доступными для приложений.

from flask import Flask, request, jsonify
import joblib
import numpy as np

app = Flask(__name__)

# Загрузка модели при запуске
model = joblib.load('model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        # Получение данных из запроса
        data = request.get_json()
        features = np.array(data['features']).reshape(1, -1)
        
        # Предсказание
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        # Возврат ответа
        return jsonify({
            'prediction': int(prediction[0]),
            'probability': probability[0].tolist(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': str(e),
            'status': 'error'
        }), 400

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'healthy'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

# FastAPI альтернатива (современная, быстрее)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class PredictionRequest(BaseModel):
    features: list

class PredictionResponse(BaseModel):
    prediction: int
    probability: list
    status: str

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
    try:
        features = np.array(request.features).reshape(1, -1)
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        return PredictionResponse(
            prediction=int(prediction[0]),
            probability=probability[0].tolist(),
            status="success"
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

# Использование:
# curl -X POST "http://localhost:5000/predict" \
#      -H "Content-Type: application/json" \
#      -d '{"features": [5.1, 3.5, 1.4, 0.2]}'

Распространенность: Очень часто Сложность: Средняя


13. Что такое Docker и почему он полезен для развертывания ML?

Ответ: Docker-контейнеры упаковывают приложения со всеми зависимостями.

  • Преимущества:
    • Воспроизводимость
    • Согласованность между средами
    • Простота развертывания
    • Изоляция
# Dockerfile для ML модели
FROM python:3.9-slim

WORKDIR /app

# Копирование зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Копирование модели и кода
COPY model.joblib .
COPY app.py .

# Открытие порта
EXPOSE 5000

# Запуск приложения
CMD ["python", "app.py"]
# Сборка Docker-образа
docker build -t ml-model:v1 .

# Запуск контейнера
docker run -p 5000:5000 ml-model:v1

# Docker Compose для настройки нескольких контейнеров
# docker-compose.yml
version: '3.8'
services:
  model:
    build: .
    ports:
      - "5000:5000"
    environment:
      - MODEL_PATH=/app/model.joblib
    volumes:
      - ./models:/app/models

Распространенность: Часто Сложность: Средняя


14. Как вы отслеживаете производительность модели в production-среде?

Ответ: Мониторинг обнаруживает деградацию модели и обеспечивает надежность.

  • Что отслеживать:
    • Метрики предсказаний: Accuracy, задержка
    • Data drift (Сдвиг данных): Изменения распределения входных данных
    • Model drift (Сдвиг модели): Ухудшение производительности
    • Системные метрики: CPU, память, ошибки
import logging
from datetime import datetime
import numpy as np

class ModelMonitor:
    def __init__(self, model):
        self.model = model
        self.predictions = []
        self.actuals = []
        self.latencies = []
        self.input_stats = []
        
        # Настройка логирования
        logging.basicConfig(
            filename='model_monitor.log',
            level=logging.INFO,
            format='%(asctime)s - %(message)s'
        )
    
    def predict(self, X):
        import time
        
        # Отслеживание статистики входных данных
        self.input_stats.append({
            'mean': X.mean(),
            'std': X.std(),
            'min': X.min(),
            'max': X.max()
        })
        
        # Измерение задержки
        start = time.time()
        prediction = self.model.predict(X)
        latency = time.time() - start
        
        self.predictions.append(prediction)
        self.latencies.append(latency)
        
        # Логирование предсказания
        logging.info(f"Prediction: {prediction}, Latency: {latency:.3f}s")
        
        # Предупреждение, если задержка слишком высока
        if latency > 1.0:
            logging.warning(f"High latency detected: {latency:.3f}s")
        
        return prediction
    
    def log_actual(self, y_true):
        self.actuals.append(y_true)
        
        # Расчет accuracy, если у нас достаточно данных
        if len(self.actuals) >= 100:
            accuracy = np.mean(
                np.array(self.predictions[-100:]) == np.array(self.actuals[-100:])
            )
            logging.info(f"Rolling accuracy (last 100): {accuracy:.3f}")
            
            if accuracy < 0.8:
                logging.error(f"Model performance degraded: {accuracy:.3f}")
    
    def check_data_drift(self, reference_stats):
        if not self.input_stats:
            return
        
        current_stats = self.input_stats[-1]
        
        # Простое обнаружение дрифта (сравнение средних)
        mean_diff = abs(current_stats['mean'] - reference_stats['mean'])
        if mean_diff > 2 * reference_stats['std']:
            logging.warning(f"Data drift detected: mean diff = {mean_diff:.3f}")

# Использование
monitor = ModelMonitor(model)
prediction = monitor.predict(X_new)
# Позже, когда фактическая метка доступна
monitor.log_actual(y_true)

Распространенность: Часто **

Newsletter subscription

Еженедельные советы по карьере, которые действительно работают

Получайте последние идеи прямо на вашу почту

Создайте резюме, которое поможет вам устроиться на 60% быстрее

За несколько минут создайте персонализированное резюме, совместимое с ATS, которое доказано увеличивает количество собеседований в 6 раз.

Создать лучшее резюме

Поделиться этим постом

Устройтесь на Работу на 50% Быстрее

Соискатели, использующие профессиональные резюме с улучшением ИИ, находят работу в среднем за 5 недель по сравнению со стандартными 10. Перестаньте ждать и начните проходить собеседования.