Подготовка к собеседованию по классическому машинному обучению

Вопросы для собеседования инженера по классическому машинному обучению

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

Начать ИИ-собеседование по классическому MLКредитная карта не требуется. Доступна 1 бесплатная сессия.
Практика технических собеседований на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Вопросы для Junior-специалистов

1В чём разница между параметром модели и гиперпараметром в обучении с учителем?

В машинном обучении с учителем параметры модели — это внутренние переменные, которые изучаются непосредственно из обучающих данных с помощью алгоритма оптимизации (такого как градиентный спуск или нормальные уравнения). Примеры включают веса регрессии и смещение в линейных моделях или пороги разбиения в деревьях решений. Напротив, гиперпараметры — это внешние параметры конфигурации, заданные до обучения, которые управляют процессом обучения, сложностью модели или архитектурой. Их нельзя изучать напрямую путём минимизации функции потерь при стандартном обучении, потому что оптимизатор в противном случае просто переобучился бы (например, установив бесконечную глубину дерева). Примеры включают скорость обучения, силу регуляризации (лямбда/C), количество деревьев в лесу и максимальную глубину дерева. Гиперпараметры настраиваются с использованием валидационных данных или кросс-валидации.

from sklearn.linear_model import Ridge
import numpy as np

X = np.array([[1.0], [2.0], [3.0]])
y = np.array([2.0, 4.0, 6.0])

# Hyperparameter: alpha (regularization strength set beforehand)
model = Ridge(alpha=1.0)

# Fitting optimizes internal parameters on training data
model.fit(X, y)

# Learned parameters
print(f"Weight (Parameter): {model.coef_[0]:.4f}")
print(f"Intercept (Parameter): {model.intercept_:.4f}")
Попробовать ответить на вопрос AI-тренеру

2Какие предположения делает обычная линейная регрессия методом наименьших квадратов (ОЛС) и как диагностика остатков может выявить нарушения этих предположений?

Обычная линейная регрессия методом наименьших квадратов (ОЛС) основывается на нескольких ключевых предположениях: 1. **Линейность:** Взаимосвязь между предикторами и откликом является линейной по параметрам. 2. **Независимость ошибок:** Наблюдения и остаточные ошибки взаимно независимы (отсутствие автокорреляции). 3. **Гомоскедастичность:** Члены ошибок имеют постоянную дисперсию для всех уровней предикторов. 4. **Нормальность остатков:** Члены ошибок нормально распределены (требуется для действительных доверительных интервалов и проверки гипотез). 5. **Отсутствие мультиколлинеарности:** Предикторы не являются линейно зависимыми (матрица плана имеет полный столбцовый ранг). Диагностика остатков выявляет нарушения следующим образом: - **График «Остатки против предсказанных значений»:** Кривизна или неслучайные паттерны выявляют нелинейность; форма воронки или веера выявляет гетероскедастичность (непостоянную дисперсию). - **График квантиль-квантиль (Q-Q plot) нормального распределения:** Систематическое отклонение от прямой диагональной линии (например, S-образные кривые или тяжелые хвосты) выявляет ненормальность. - **График «Остатки против порядка/времени»:** Систематические тренды или циклические паттерны выявляют автокоррелированные ошибки. - **График рычага / Расстояния Кука (Cook's Distance):** Идентифицирует выбросы с высоким рычагом или влиятельные точки, которые непропорционально смещают подогнанную модель.

import numpy as np
import statsmodels.api as sm

np.random.seed(42)
X = np.linspace(1, 10, 50)
# Quadratic underlying pattern creates a linearity violation
y = 2 * X + 0.5 * (X ** 2) + np.random.normal(0, 2, 50)

X_with_const = sm.add_constant(X)
model = sm.OLS(y, X_with_const).fit()
residuals = model.resid

print(f"Mean Residual: {np.mean(residuals):.4f}")
print(f"Curvature in residuals indicates model misspecification.")
Попробовать ответить на вопрос AI-тренеру

3Как логистическая регрессия моделирует бинарную классификацию и какова роль сигмоидальной функции?

Логистическая регрессия моделирует бинарную классификацию путем оценки апостериорной вероятности класса $P(Y=1|X)$. Чтобы предсказанные вероятности оставались в диапазоне $(0, 1)$, логистическая регрессия моделирует логарифм шансов (логит) положительного класса как линейную функцию входных данных: $\ln\left(\frac{p}{1-p}\right) = w^T x + b$. Сигмоида (логистическая функция), $\sigma(z) = \frac{1}{1 + e^{-z}}$, выступает в качестве функции связи, которая монотонно отображает любую вещественнозначную линейную оценку $z = w^T x + b \in (-\infty, +\infty)$ в допустимую вероятность $p \in (0, 1)$. Дискретные решения о классе принимаются путем применения порога принятия решения $\tau$ (обычно 0.5): $\hat{y} = 1$, если $P(Y=1|X) \ge \tau$, в противном случае $0$. Поскольку $\sigma(z) = 0.5$ возникает точно при $z = 0$, разделяющая поверхность в пространстве признаков представляет собой линейную гиперплоскость $w^T x + b = 0$, что делает стандартную логистическую регрессию линейным классификатором.

import numpy as np

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

w = np.array([1.5, -2.0])
b = 0.5
x = np.array([2.0, 1.0])

z = np.dot(w, x) + b
prob = sigmoid(z)
threshold = 0.5
pred = int(prob >= threshold)

print(f"Log-odds (z): {z:.2f}")
print(f"Probability: {prob:.4f}")
print(f"Class Prediction: {pred}")
Попробовать ответить на вопрос AI-тренеру

4Что такое L2-регуляризация и как гребневая регрессия изменяет целевую функцию и оценки коэффициентов?

L2-регуляризация (гребневая регрессия) добавляет штраф, пропорциональный сумме квадратов весов, к функции потерь метода наименьших квадратов (МНК): $$\min_w \|y - Xw\|_2^2 + \lambda \|w\|_2^2$$ Аналитически, гребневая регрессия изменяет нормальные уравнения, добавляя $\lambda I$ к матрице Грама перед обращением: $$w_{\text{ridge}} = (X^T X + \lambda I)^{-1} X^T y$$ Ключевые воздействия на целевую функцию и оценки коэффициентов: 1. **Сжатие**: Коэффициенты сжимаются к нулю пропорционально дисперсии и корреляции признаков, уменьшая сложность модели без принуждения их к точному нулю. 2. **Мультиколлинеарность и обратимость**: Когда признаки коллинеарны или $p > N$, $X^T X$ является вырожденной или плохо обусловленной. Добавление $\lambda I$ гарантирует, что $(X^T X + \lambda I)$ будет строго положительно определенной и обратимой, стабилизируя оценки параметров. 3. **Компромисс смещения и дисперсии**: Увеличение $\lambda$ вводит намеренное смещение в оценки коэффициентов, значительно уменьшая при этом дисперсию, что приводит к более низкой ожидаемой ошибке обобщения на новых данных. 4. **Требование масштабирования признаков**: Поскольку штраф относится ко всем весам одинаково, признаки с большими масштабами будут регуляризованы непропорционально. Признаки должны быть стандартизированы (нулевое среднее, единичная дисперсия) перед обучением модели.

import numpy as np

def ridge_regression(X, y, alpha):
    X_std = (X - np.mean(X, axis=0)) / np.std(X, axis=0)
    n_features = X_std.shape[1]
    
    I = np.eye(n_features)
    beta = np.linalg.inv(X_std.T @ X_std + alpha * I) @ X_std.T @ y
    return beta

X = np.array([[1.0, 2.0], [2.0, 4.1], [3.0, 5.9], [4.0, 8.2]])
y = np.array([2.1, 4.0, 6.2, 8.1])
weights = ridge_regression(X, y, alpha=1.0)
print('Ridge Weights:', weights)
Попробовать ответить на вопрос AI-тренеру

5Как дерево решений рекурсивно разбивает пространство признаков и какие критерии используются для выбора разбиений при классификации?

Дерево решений разбивает пространство признаков с помощью жадного алгоритма, идущего сверху вниз, называемого **рекурсивным бинарным разбиением**. Начиная с корневого узла со всеми обучающими данными, алгоритм ищет по всем признакам и возможным пороговым значениям такое одиночное осевое разбиение ($X_j \le t$), которое максимизирует уменьшение неоднородности. Набор данных разделяется на два дочерних узла, и эта процедура рекурсивно применяется к каждому дочернему узлу до тех пор, пока не будет достигнут критерий останова (например, максимальная глубина, минимальное количество образцов на лист или чистые узлы). Поскольку разбиения оценивают один признак за раз относительно порога, результирующие границы решений представляют собой ортогональные гиперплоскости (осевые прямоугольные области в пространстве признаков). Для оценки и выбора наилучшего разбиения в деревьях классификации используются два основных критерия неоднородности: 1. **Неоднородность Джини (Gini Impurity) (используется в CART)**: Измеряет вероятность того, что случайно выбранный образец будет неправильно классифицирован, если его случайно пометить согласно распределению классов в узле. Для $K$ классов с долями $p_k$: $$I_G = 1 - \sum_{k=1}^K p_k^2$$ 2. **Энтропия и прирост информации (Information Gain) (используются в ID3, C4.5)**: Энтропия измеряет неопределённость в узле: $H = -\sum_{k=1}^K p_k \log_2(p_k)$. Разделение выбирается таким образом, чтобы максимизировать **прирост информации**, который представляет собой энтропию родительского узла минус взвешенное среднее энтропий дочерних узлов: $$IG = H(\text{parent}) - \sum_{c \in \{\text{left, right}\}} \frac{N_c}{N} H(c)$$ Обе метрики достигают 0, когда узел полностью чист (все образцы принадлежат одному классу), и достигают своего максимума, когда классы распределены равномерно.

import numpy as np

def gini(labels):
    _, counts = np.unique(labels, return_counts=True)
    p = counts / len(labels)
    return 1.0 - np.sum(p ** 2)

def entropy(labels):
    _, counts = np.unique(labels, return_counts=True)
    p = counts / len(labels)
    return -np.sum(p * np.log2(p + 1e-12))

# Evenly split node (impure) vs single-class node (pure)
impure_node = np.array([0]*10 + [1]*10)
pure_node = np.array([0]*20)

print(f"Impure - Gini: {gini(impure_node):.2f}, Entropy: {entropy(impure_node):.2f}")
print(f"Pure   - Gini: {gini(pure_node):.2f}, Entropy: {entropy(pure_node):.2f}")
Попробовать ответить на вопрос AI-тренеру

6Что такое метод k ближайших соседей (kNN) и как он делает предсказания для классификации и регрессии?

Метод k ближайших соседей (kNN) — это непараметрический, основанный на экземплярах (ленивый) алгоритм обучения с учителем. Он не тренирует явную параметрическую модель; вместо этого он хранит обучающий набор данных и выполняет все вычисления во время вывода (инференса). Рабочий процесс предсказания: 1. Вычисление расстояния: При оценке запрашиваемого экземпляра алгоритм вычисляет его расстояние до всех хранящихся обучающих экземпляров, используя заданную метрику (например, евклидово, манхэттенское или минковского расстояние). 2. Выбор соседей: Он выбирает $k$ обучающих экземпляров с наименьшими расстояниями до запрашиваемого экземпляра. 3. Агрегация: - Классификация: Он присваивает класс посредством голосования по большинству (моды) среди $k$ соседей (или взвешенного по расстоянию голосования). - Регрессия: Он предсказывает непрерывную целевую переменную, принимая локальное среднее (среднее арифметическое или медиану) целевых значений $k$ соседей (или взвешенное по расстоянию среднее). Поскольку вычисления расстояний напрямую зависят от масштабов признаков, нормализация или стандартизация признаков имеет важное значение для предотвращения доминирования признаков с большой величиной в вычислениях расстояний.

from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
import numpy as np

X_train = np.array([[1000.0, 1.0], [2000.0, 2.0], [1500.0, 1.5], [5000.0, 5.0]])
y_cls = np.array([0, 0, 0, 1])
y_reg = np.array([10.0, 20.0, 15.0, 50.0])

# Feature scaling is mandatory for distance-based algorithms
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)

# 1. Classification (Majority Vote)
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_scaled, y_cls)

# 2. Regression (Local Average)
reg = KNeighborsRegressor(n_neighbors=3)
reg.fit(X_scaled, y_reg)

query = scaler.transform([[1800.0, 1.8]])
print('Classification:', clf.predict(query))
print('Regression:', reg.predict(query))
Попробовать ответить на вопрос AI-тренеру

7Сформулируйте предположение об условной независимости наивного Байеса и объясните, почему наивный Байес всё ещё может хорошо работать, когда это предположение нарушается.

Предположение об условной независимости наивного Байеса гласит, что при заданной метке класса Y = y все признаки X_1, X_2, ..., X_d взаимно независимы: P(X_1, ..., X_d | Y = y) = \prod_{j=1}^d P(X_j | Y = y). Используя теорему Байеса, апостериорная вероятность: P(Y = y | X) \propto P(Y = y) \prod_{j=1}^d P(X_j | Y = y), где P(Y = y) — это априорная вероятность класса, а P(X_j | Y = y) — это правдоподобие, обусловленное классом (например, Гауссовское для непрерывных признаков, Мультиномиальное для счетных признаков). Наивный Байес часто хорошо работает на практике, несмотря на нарушения независимости, потому что классификация основывается на правиле принятия решения argmax (argmax_y P(Y=y | X)), а не на точной калибровке вероятностей. Даже если корреляции признаков приводят к тому, что предсказанные вероятности становятся слишком уверенными или искаженными, правильный класс часто сохраняет наивысший относительный ранг. Пока корреляция не изменяет порядок ранжирования правдоподобий классов, решение о классификации 0-1 остаётся точным.

from sklearn.naive_bayes import GaussianNB
import numpy as np

X = np.array([[1.0, 1.1], [1.2, 0.9], [-1.0, -1.2], [-0.8, -1.1]])
y = np.array([1, 1, 0, 0])

model = GaussianNB()
model.fit(X, y)
# Prediction uses argmax over class posterior scores
print("Predicted class:", model.predict([[1.1, 1.0]]))
Попробовать ответить на вопрос AI-тренеру

Вопросы для Middle-специалистов

8Выведите или объясните решение OLS (метода наименьших квадратов) в замкнутой форме и укажите, когда оно существует единственным образом.

Цель метода наименьших квадратов (OLS) — минимизировать сумму квадратов остатков: $S(\beta) = \|y - X\beta\|^2 = (y - X\beta)^T (y - X\beta) = y^T y - 2\beta^T X^T y + \beta^T X^T X \beta$. Приравнивая градиент по $\beta$ к нулю: $$\nabla_\beta S(\beta) = -2 X^T y + 2 X^T X \beta = 0 \implies X^T X \beta = X^T y$$ Это нормальные уравнения. Когда $X^T X$ невырождена (обратима), единственное решение в замкнутой форме выглядит так: $$\hat{\beta} = (X^T X)^{-1} X^T y$$ Геометрически, $\hat{y} = X\hat{\beta} = X(X^T X)^{-1} X^T y = H y$ представляет собой ортогональную проекцию целевого вектора $y$ на столбцовое пространство матрицы признаков $X$, где $H$ — это проекционная матрица (матрица «шляпы»). Решение существует единственным образом тогда и только тогда, когда $X^T X$ обратима, что требует, чтобы матрица признаков $X$ размером $N \times P$ имела полный столбцовый ранг ($Rank(X) = P$). Это означает, что $N \ge P$ и отсутствует точная мультиколлинеарность (т.е. ни один признак не является линейной комбинацией других). Если $X$ является дефектной по рангу, то $X^T X$ является вырожденной, что приводит к бесконечному множеству решений. Эта проблема часто решается с помощью регуляризации или псевдообратной матрицы Мура-Пенроуза $X^+ y$.

import numpy as np

# Design matrix X (with intercept column) and target y
X = np.array([[1, 1], [1, 2], [1, 3], [1, 4]])
y = np.array([2.1, 3.9, 6.2, 8.0])

# Normal equations: (X^T X)^(-1) X^T y
beta_hat = np.linalg.inv(X.T @ X) @ X.T @ y
H = X @ np.linalg.inv(X.T @ X) @ X.T
y_hat = H @ y

print(f"Beta: {beta_hat}")
print(f"Predictions: {y_hat}")
Попробовать ответить на вопрос AI-тренеру

9Что такое оценка максимального правдоподобия, и как она приводит к целевой функции перекрёстной энтропии логистической регрессии?

Оценка максимального правдоподобия (MLE) — это метод оценки параметров модели $\theta$ путём выбора значений, которые максимизируют правдоподобие $L(\theta) = P(\mathcal{D}|\theta)$ наблюдаемого набора данных. В бинарной логистической регрессии каждая метка $y_i \in \{0, 1\}$ моделируется как независимая случайная величина Бернулли, обусловленная $x_i$, с вероятностью успеха $p_i = \sigma(w^T x_i + b)$. Функция вероятности массы для наблюдения $i$ имеет вид $P(y_i|x_i) = p_i^{y_i} (1 - p_i)^{1 - y_i}$. Предполагая независимые и одинаково распределенные (i.i.d.) выборки, совместное правдоподобие выглядит так: $$L(w, b) = \prod_{i=1}^N p_i^{y_i} (1 - p_i)^{1 - y_i}$$ Взятие натурального логарифма преобразует произведение в вычислительно удобную сумму логарифмов правдоподобия: $$\ell(w, b) = \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Поскольку алгоритмы оптимизации обычно формулируются как задачи минимизации, мы инвертируем знак логарифма правдоподобия и нормализуем по размеру выборки $N$, получая отрицательный логарифм правдоподобия (NLL), который в точности является целевой функцией бинарной кросс-энтропии (логарифмической потери): $$J(w, b) = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Эта целевая функция выпукла относительно линейных логитов/весов, поэтому подходящие численные решатели оптимизируют глобальную целевую функцию. Строгая выпуклость и конечное, уникальное MLE требуют дополнительных условий, таких как достаточный ранг признаков, регуляризация и отсутствие идеального разделения классов.

import numpy as np

y_true = np.array([1, 0, 1, 1])
y_prob = np.array([0.9, 0.2, 0.8, 0.4])

# Binary cross-entropy (Negative Log-Likelihood)
epsilon = 1e-15  # prevent log(0)
y_prob = np.clip(y_prob, epsilon, 1 - epsilon)
bce_loss = -np.mean(y_true * np.log(y_prob) + (1 - y_true) * np.log(1 - y_prob))

print(f"Binary Cross-Entropy Loss: {bce_loss:.4f}")
Попробовать ответить на вопрос AI-тренеру

10Как градиентный спуск оптимизирует целевую функцию классического машинного обучения (ML) и как скорость обучения, сходимость и выпуклость влияют на процесс обучения?

Градиентный спуск минимизирует эмпирическую функцию потерь, итеративно обновляя параметры модели в направлении, противоположном градиенту целевой функции по отношению к этим параметрам: $\theta_{t+1} = \theta_t - \eta \nabla L(\theta_t)$. Ключевые факторы, влияющие на обучение, включают: 1. Скорость обучения ($\eta$): Контролирует размер шага. Если она слишком мала, сходимость будет чрезмерно медленной, и обучение может застопориться. Если она слишком велика, обновления будут промахиваться мимо минимума, вызывая осцилляции или численное расхождение. 2. Сходимость: Определяется мониторингом критериев остановки, таких как малая норма градиента ($||\nabla L(\theta)|| \le \epsilon$), минимальное изменение параметров или выход функции потерь на плато на последовательных итерациях. 3. Выпуклость: В выпуклых целевых функциях (например, стандартная линейная регрессия методом наименьших квадратов (OLS) или логистическая регрессия) любой локальный минимум гарантированно является глобальным минимумом, что позволяет градиентному спуску надежно сходиться при соответствующем размере шага. В невыпуклых целевых функциях (например, многослойных нейронных сетях) ландшафт функции потерь содержит множество локальных минимумов, седловых точек и плато, что делает окончательное решение чувствительным к инициализации. 4. Оптимизация против обобщения: Сходимость по потерям на обучающей выборке отражает успех оптимизации, тогда как потери на валидационной выборке оценивают обобщающую способность. Достижение низких потерь на обучающей выборке при высокой ошибке на валидационной выборке указывает на переобучение, а не на сбой оптимизации.

import numpy as np

def gradient_descent(X, y, lr=0.01, max_iters=1000, tol=1e-6):
    n_samples, n_features = X.shape
    theta = np.zeros(n_features)
    prev_loss = float('inf')
    
    for i in range(max_iters):
        predictions = X @ theta
        error = predictions - y
        loss = (1 / (2 * n_samples)) * np.dot(error, error)
        
        if abs(prev_loss - loss) < tol:
            print(f'Converged at iteration {i}')
            break
        prev_loss = loss
        
        grad = (1 / n_samples) * (X.T @ error)
        theta -= lr * grad
        
    return theta
Попробовать ответить на вопрос AI-тренеру

11Сравните регуляризацию L1, L2 и ElasticNet с точки зрения разреженности, коррелированных признаков и практического выбора модели.

Регуляризация L1 (Лассо), L2 (Ридж) и ElasticNet различаются формулировкой штрафа, геометрией ограничения, разреженностью и обработкой коррелированных предикторов: 1. **Разреженность и геометрия:** - L1 использует штраф по абсолютному значению ($\lambda \|w\|_1$). Граница его ограничения представляет собой ромб/политоп с острыми вершинами на координатных осях. Когда контуры функции потерь пересекаются с этими углами, веса обнуляются, выполняя автоматический отбор признаков. - L2 использует штраф по квадрату евклидовой нормы ($\lambda \|w\|_2^2$). Граница его ограничения представляет собой гладкую гиперсферу без углов, асимптотически уменьшая веса до нуля, но редко обнуляя их. 2. **Коррелированные признаки:** - При сильной коллинеарности L1, как правило, произвольно выбирает один признак из группы коррелированных предикторов и обнуляет остальные коэффициенты, что приводит к нестабильным оценкам при перевыборке. - L2 сохраняет все коррелированные признаки, распределяя веса между ними и сжимая их вместе. 3. **ElasticNet:** - Объединяет оба штрафа: $\lambda_1 \|w\|_1 + \lambda_2 \|w\|_2^2$ (часто параметризуется с $\alpha$ и $l_1\_\text{ratio}$). - Обеспечивает разреженность и отбор признаков, характерные для Лассо, при этом сохраняя эффект группировки Риджа, отбирая кластеры коррелированных предикторов вместе. Он особенно полезен, когда $p > N$ или при сильной мультиколлинеарности.

from sklearn.linear_model import Ridge, Lasso, ElasticNet
import numpy as np

np.random.seed(42)
X1 = np.random.randn(100, 1)
X2 = X1 + np.random.randn(100, 1) * 0.01  # highly correlated
X = np.hstack([X1, X2])
y = 3 * X1.ravel() + np.random.randn(100) * 0.1

ridge = Ridge(alpha=1.0).fit(X, y)
lasso = Lasso(alpha=0.1).fit(X, y)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(X, y)

print('Ridge coefs:', ridge.coef_)
print('Lasso coefs:', lasso.coef_)
print('ElasticNet coefs:', elastic.coef_)
Попробовать ответить на вопрос AI-тренеру

12В чем заключается высокоуровневое различие между гребневой регрессией (Ridge regression), регрессией на главных компонентах (Principal Component Regression, PCR) и методом частичных наименьших квадратов (Partial Least Squares, PLS)?

Гребневая регрессия (Ridge Regression), регрессия на главных компонентах (Principal Component Regression, PCR) и метод частичных наименьших квадратов (Partial Least Squares, PLS) — это три линейных метода, используемых для борьбы с мультиколлинеарностью и высокой размерностью, но они различаются тем, как они уменьшают дисперсию и является ли это уменьшение непрерывным или контролируемым: 1. **Гребневая регрессия (Ridge Regression):** Сохраняет все исходные $p$ признаков и применяет непрерывное сжатие величин коэффициентов с помощью штрафа L2. Она не строит низкоразмерные латентные компоненты и не отбрасывает размерности признаков; вместо этого она сжимает дисперсию вдоль направлений с низкими собственными значениями $X^T X$. 2. **Регрессия на главных компонентах (Principal Component Regression, PCR):** Двухэтапный метод понижения размерности без учителя. Сначала он применяет метод главных компонент (Principal Component Analysis, PCA) строго к матрице предикторов $X$, чтобы найти ортогональные направления максимальной дисперсии, сохраняет $k$ главных компонент и подгоняет регрессию методом наименьших квадратов (Ordinary Least Squares, OLS) на этих $k$ компонентах. Поскольку PCA игнорирует целевую переменную $y$, PCR рискует отбросить компоненты, которые имеют низкую дисперсию в $X$, но высокую предсказательную силу для $y$. 3. **Метод частичных наименьших квадратов (Partial Least Squares, PLS):** Метод понижения размерности с учителем. Он строит $k$ ортогональных латентных компонент, находя линейные комбинации $X$, которые максимизируют ковариацию между $X$ и откликом $y$. Явно включая информацию о целевой переменной, PLS идентифицирует компоненты, которые объясняют как дисперсию признаков, так и вариацию отклика.

from sklearn.linear_model import Ridge, LinearRegression
from sklearn.decomposition import PCA
from sklearn.cross_decomposition import PLSRegression
from sklearn.pipeline import make_pipeline

# 1. Ridge: Regularized full feature space
ridge = Ridge(alpha=1.0)

# 2. PCR: Unsupervised PCA followed by OLS
pcr = make_pipeline(PCA(n_components=2), LinearRegression())

# 3. PLS: Supervised latent component projection and regression
pls = PLSRegression(n_components=2)
Попробовать ответить на вопрос AI-тренеру

Вопросы для Senior-специалистов

13Как современные реализации градиентного бустинга, такие как XGBoost, LightGBM и CatBoost, оптимизируют обучение или по-разному обрабатывают табличные признаки?

Современные фреймворки GBDT (Gradient Boosting Decision Tree) существенно различаются по своим алгоритмам поиска разбиений (сплитов), стратегиям роста деревьев и обработке табличных/категориальных признаков: 1. **XGBoost**: Традиционно полагается на точный жадный или приближенный поиск разбиений на основе квантильных скетчей (а позже Fast Hist), использует поуровневый (глубинный) рост деревьев и обрабатывает пропущенные значения, изучая оптимальное направление ветви по умолчанию во время поиска разбиения. 2. **LightGBM**: Использует поиск разбиений на основе гистограмм (биннинг непрерывных признаков в дискретные корзины, обычно 256), полистный (best-first) рост деревьев для более быстрого снижения функции потерь, Gradient-based One-Side Sampling (GOSS) для сохранения экземпляров с большим градиентом при подвыборке экземпляров с малым градиентом, и Exclusive Feature Bundling (EFB) для объединения взаимоисключающих разреженных признаков. Для категориальных переменных он находит оптимальные разбиения, сортируя бины категориальных гистограмм ($O(K \log K)$). 3. **CatBoost**: Использует симметричные деревья решений (oblivious decision trees), где все узлы на заданной глубине используют одно и то же разбиение, что обеспечивает быстрый векторизованный скоринг на CPU/GPU. Его основное нововведение — Ordered Target Statistics и упорядоченный бустинг (ordered boosting), который вычисляет статистику целевой переменной по случайным перестановкам обучающих данных для предотвращения утечки целевой переменной и сдвига предсказаний.

from catboost import CatBoostClassifier
import lightgbm as lgb
import pandas as pd

df = pd.DataFrame({
    'city': ['NY', 'LDN', 'NY', 'PAR', 'LDN', 'TOK'],
    'age': [25, 42, 30, 22, 55, 38],
    'target': [1, 0, 1, 0, 1, 0]
})
cat_cols = ['city']
df['city'] = df['city'].astype('category')

# LightGBM handles pandas 'category' dtype natively via integer binning
lgb_clf = lgb.LGBMClassifier(max_depth=3, n_estimators=10)
lgb_clf.fit(df[['city', 'age']], df['target'])

# CatBoost handles categorical column names natively with ordered TS
cb_clf = CatBoostClassifier(iterations=10, cat_features=cat_cols, verbose=False)
cb_clf.fit(df[['city', 'age']], df['target'])
Попробовать ответить на вопрос AI-тренеру

14Как бы вы решили, уместна ли пользовательская функция потерь для модели градиентного бустинга в условиях асимметричных бизнес-издержек?

Решение о том, следует ли реализовывать пользовательскую функцию потерь в градиентном бустинге в условиях асимметричных бизнес-издержек, требует оценки, может ли асимметрия быть обработана на последующих этапах с помощью калибровки вероятностей и настройки порогов, или же она фундаментально изменяет оптимизационный ландшафт во время построения деревьев: 1. **Настройка порога против пользовательской функции потерь**: Для задач классификации с асимметричными издержками на ошибки (например, ложноотрицательные результаты обходятся в 10 раз дороже ложноположительных) стандартная кросс-энтропия является правильной оценочной функцией, которая направлена на оценку апостериорных вероятностей $P(y=1|x)$, но калибровка должна быть проверена и, при необходимости, скорректирована на валидационных данных. Смещение порога классификационного решения на основе матрицы бизнес-издержек $\tau = \frac{C_{FP}}{C_{FP} + C_{FN}}$ или применение весов выборки часто чище и позволяет избежать пользовательских производных. Однако для асимметричной регрессии (например, асимметричная функция потерь pinball для спроса на товары) или нелинейных бизнес-штрафов, где стандартные целевые функции не могут направлять поиск разбиений, пользовательская функция потерь оправдана. 2. **Математические требования для GBDT (Gradient Boosting Decision Trees)**: В бустерах второго порядка (XGBoost, LightGBM) пользовательская функция потерь $L(y, \hat{y})$ обычно требует вычислимых градиентов первого порядка ($g_i = \partial L / \partial \hat{y}_i$) и валидных значений кривизны второго порядка/Гессиана ($h_i = \partial^2 L / \partial \hat{y}_i^2$) для расчетов прироста при разбиении и весов листьев ($w^* = -\sum g_i / (\sum h_i + \lambda)$). Гессианы должны быть неотрицательными или безопасно аппроксимированы/обрезаны для численной стабильности; некоторые реализации поддерживают целевые функции первого порядка или аппроксимированные, поэтому требование зависит от фреймворка. Недифференцируемые или разрывные бизнес-метрики следует заменять гладкими суррогатными аппроксимациями (например, варианты Хубера или log-cosh).

import numpy as np
import xgboost as xgb

def asymmetric_mse_objective(preds, dtrain):
    labels = dtrain.get_label()
    residual = preds - labels
    # Penalize underestimation (residual < 0) 5x more heavily than overestimation
    penalty = np.where(residual < 0, 5.0, 1.0)
    grad = 2.0 * penalty * residual
    hess = 2.0 * penalty
    return grad, hess

# Usage:
# model = xgb.train(params, dtrain, obj=asymmetric_mse_objective)
Попробовать ответить на вопрос AI-тренеру

15Что такое LambdaMART и как он адаптирует градиентный бустинг для задач обучения ранжированию?

LambdaMART — это алгоритм обучения ранжированию (LTR), который сочетает MART (множественные аддитивные деревья регрессии / градиентный бустинг) с фреймворком LambdaRank. В задачах ранжирования целевые метрики, такие как NDCG (нормализованный дисконтированный кумулятивный выигрыш) и MAP, зависят от дискретного порядка сортировки (рангов), что делает их практически плоскими везде и недифференцируемыми по отношению к непрерывным оценкам модели. LambdaMART обходит эту проблему, конструируя виртуальные градиенты, называемые «лямбда-градиентами» ($\lambda_{ij}$), для пар элементов $(i, j)$ в рамках одного запроса. Базовый попарный градиент происходит от логистической функции потерь на разницах оценок ($s_i - s_j$). LambdaMART масштабирует этот градиент точным изменением в целевой метрике ранжирования ($|\Delta \text{NDCG}_{ij}|$), которое произошло бы, если бы позиции документа $i$ и документа $j$ были бы поменяны местами: $$\lambda_{ij} = \frac{-\sigma}{1 + e^{\sigma(s_i - s_j)}} |\Delta \text{NDCG}_{ij}|$$ Для каждого отдельного документа $i$ суммарный градиент рассчитывается путем агрегирования попарных лямбд по всем парам, включающим документ $i$: $\lambda_i = \sum_{j: j \succ i} \lambda_{ij} - \sum_{k: i \succ k} \lambda_{ki}$. Стандартные деревья регрессии в ансамбле бустинга затем подгоняют эти составные лямбда-градиенты для каждого документа на каждой итерации бустинга, напрямую оптимизируя списочные метрики ранжирования.

import lightgbm as lgb
import numpy as np

# Simulated query-grouped data: 2 queries with 3 docs each
X = np.random.randn(6, 10)
y = np.array([3, 1, 0, 2, 0, 1])  # Relevance grades (0-3)
group = [3, 3]                     # Query group sizes

train_data = lgb.Dataset(X, label=y, group=group)
params = {
    'objective': 'lambdarank',
    'metric': 'ndcg',
    'ndcg_eval_at': [1, 3],
    'learning_rate': 0.1,
    'n_estimators': 50
}

ranker = lgb.train(params, train_data)
Попробовать ответить на вопрос AI-тренеру