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

Въпроси за интервю за инженер по класическо машинно обучение

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

Започнете AI интервю по класическо машинно обучениеНе се изисква кредитна карта. Налична е 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}")
Опитайте да отговорите на този въпрос с треньор с ИИ

2Какви предположения прави линейната регресия на обикновените най-малки квадрати (OLS) и как диагностиката на остатъците би разкрила нарушения на предположенията?

Линейната регресия на обикновените най-малки квадрати (OLS) разчита на няколко основни предположения: 1. Линейност: Връзката между предикторите и резултата е линейна по отношение на параметрите. 2. Независимост на грешките: Наблюденията и остатъчните грешки са взаимно независими (без автокорелация). 3. Хомоскедастичност: Грешките имат постоянна дисперсия във всички нива на предикторите. 4. Нормалност на остатъците: Грешките са нормално разпределени (необходимо за валидни доверителни интервали и тестове за хипотези). 5. Без мултиколинеарност: Предикторите не са линейно зависими (матрицата на дизайна има пълен ранг по колони). Диагностиката на остатъците разкрива нарушения, както следва: - Графика на остатъците спрямо прогнозираните стойности (Residuals vs. Fitted Values plot): Изкривявания или неслучайни модели разкриват нелинейност; форма на фуния или ветрило разкрива хетероскедастичност (непостоянна дисперсия). - Нормална Q-Q графика (Normal Q-Q plot): Систематично отклонение от правата диагонална линия (напр. S-образни криви или тежки опашки) разкрива ненормалност. - Графика на остатъците спрямо поръчката/времето (Residuals vs. Order/Time plot): Систематични тенденции или циклични модели разкриват автокорелирани грешки. - Графика на влияние (Leverage / Cook's Distance plot): Идентифицира отклонения с голямо влияние (high-leverage outliers) или влиятелни точки, които непропорционално изместват пригодения модел.

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.")
Опитайте да отговорите на този въпрос с треньор с ИИ

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

Логистичната регресия моделира бинарната класификация, като оценява апостериорната вероятност за клас $P(Y=1|X)$. За да се гарантира, че предвидените вероятности остават ограничени в интервала $(0, 1)$, логистичната регресия моделира лог-коефициентите (logit) на положителния клас като линейна функция на входовете: $\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}")
Опитайте да отговорите на този въпрос с треньор с ИИ

4Какво е L2 регуляризация и как Ридж регресията променя целта и оценките на коефициентите?

L2 регуляризацията (Ридж регресия) добавя наказание, пропорционално на сумата от квадратите на теглата, към функцията на загуба на обикновените най-малки квадрати (OLS): $$\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. Компромис между отклонение и дисперсия (Bias-Variance Trade-off): Увеличаването на $\lambda$ въвежда умишлено отклонение (bias) в оценките на коефициентите, като същевременно значително намалява дисперсията, което води до по-ниска очаквана грешка при обобщение на невидени данни. 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)
Опитайте да отговорите на този въпрос с треньор с ИИ

5Как дървото на решенията рекурсивно разделя пространството от характеристики и какви критерии се използват за избор на разделянията за класификация?

Дървото на решенията разделя пространството от характеристики чрез алгоритъм отгоре надолу, тип greedy, наречен **рекурсивно бинарно разделяне**. Започвайки от кореновия възел с всички данни за обучение, алгоритъмът търси сред всички характеристики и възможни прагови стойности, за да намери еднократното разделяне, подравнено по ос ($X_j \le t$), което максимизира намаляването на нечистотата. Наборът от данни се разделя на два дъщерни възела и тази процедура се прилага рекурсивно за всеки дъщерен възел, докато не се достигне критерий за спиране (напр. максимална дълбочина, минимален брой извадки на листо или чисти възли). Тъй като разделянията оценяват една характеристика едновременно спрямо даден праг, получените граници на решенията са ортогонални хиперравнини (правоъгълни области, подравнени по ос, в пространството от характеристики). За оценка и избор на най-доброто разделяне в класификационни дървета се използват два основни критерия за нечистота: 1. **Нечистота на Джини (Gini Impurity, използвана в CART)**: Измерва вероятността случайно избрана извадка да бъде погрешно класифицирана, ако е етикетирана на случаен принцип според разпределението на класовете на възела. За $K$ класа с пропорции $p_k$: $$I_G = 1 - \sum_{k=1}^K p_k^2$$ 2. **Ентропия и информационна печалба (Entropy and 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}")
Опитайте да отговорите на този въпрос с треньор с ИИ

6Какво представлява методът k-най-близки съседи (k-Nearest Neighbors, 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))
Опитайте да отговорите на този въпрос с треньор с ИИ

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]]))
Опитайте да отговорите на този въпрос с треньор с ИИ

Въпроси за Middle ниво

8Изведете или обяснете решението на OLS (Метода на най-малките квадрати) в затворена форма и посочете кога то съществува единствено.

Целевата функция на OLS (Ordinary Least Squares) минимизира сумата от квадратите на остатъците: $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}")
Опитайте да отговорите на този въпрос с треньор с ИИ

9Какво представлява оценката на максимално правдоподобие (MLE) и как води до целевата функция на кръстосана ентропия при логистичната регресия?

Оценката на максимално правдоподобие (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}")
Опитайте да отговорите на този въпрос с треньор с ИИ

10Как градиентното спускане оптимизира класическа цел за машинно обучение (ML objective) и как скоростта на обучение (learning rate), сходимостта (convergence) и изпъкналостта (convexity) влияят на обучението?

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

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
Опитайте да отговорите на този въпрос с треньор с ИИ

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

L1 (Lasso), L2 (Ridge) и 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}$). * Осигурява разредеността и избора на признаци на Lasso, като същевременно запазва групиращия ефект на Ridge, избирайки клъстери от корелирани предиктори заедно. Особено полезно е, когато $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_)
Опитайте да отговорите на този въпрос с треньор с ИИ

12Каква е разликата между Ридж регресията, регресията с главни компоненти и регресията на частични най-малки квадрати на високо ниво?

Ридж регресията, регресията с главни компоненти (Principal Component Regression - PCR) и регресията на частични най-малки квадрати (Partial Least Squares - PLS) са три линейни техники, използвани за справяне с мултиколинеарност и висока размерност, но те се различават по начина, по който намаляват дисперсията и дали намаляването е непрекъснато или контролирано: 1. **Ридж регресия (Ridge Regression):** Запазва всички оригинални $p$ признака и прилага непрекъснато свиване към стойностите на коефициентите чрез L2 наказание. Тя не конструира латентни компоненти с по-ниска размерност или отхвърля размерности на признаци; по-скоро свива дисперсията по посоките с ниски собствени стойности на $X^T X$. 2. **Регресия с главни компоненти (PCR):** Двустъпков, неконтролиран метод за намаляване на размерността. Първо прилага анализ на главни компоненти (Principal Component Analysis - PCA) строго към матрицата на предиктора $X$, за да намери ортогонални посоки на максимална дисперсия, запазва топ $k$ главни компоненти и прилага регресия на обикновени най-малки квадрати (Ordinary Least Squares - OLS) върху тези $k$ компоненти. Тъй като PCA игнорира целевата променлива $y$, PCR рискува да отхвърли компоненти, които имат ниска дисперсия в $X$, но висока предсказваща способност за $y$. 3. **Регресия на частични най-малки квадрати (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)
Опитайте да отговорите на този въпрос с треньор с ИИ

Въпроси за Senior ниво

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

Модерните GBDT фреймуърци се различават съществено в своите алгоритми за намиране на разделение, стратегии за растеж на дърветата и обработка на таблични/категорийни характеристики: 1. XGBoost: Традиционно разчита на точно жадно или приблизително квантилно скицово намиране на разделение (и по-късно Fast Hist), използва растеж на дървото по нива (по дълбочина) и обработва липсващи стойности, като научава оптимална посока на клона по подразбиране по време на търсенето на разделение. 2. LightGBM: Използва базирано на хистограми намиране на разделение (групиране на непрекъснати характеристики в дискретни групи, обикновено 256), растеж на дървото по листа (най-добър пръв) за по-бързо намаляване на загубите, Едностранно семплиране, базирано на градиент (GOSS), за да запази инстанции с голям градиент, докато подсемплира тези с малък градиент, и Exclusive Feature Bundling (EFB) за обединяване на взаимно изключващи се разредени характеристики. За категорийни променливи намира оптимални разделения чрез сортиране на категориите на хистограмните групи ($O(K \log K)$). 3. CatBoost: Използва безразлични (симетрични) дървета на решенията, при които всички възли на дадена дълбочина споделят едно и също разделение, позволявайки бързо векторизирано оценяване на 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'])
Опитайте да отговорите на този въпрос с треньор с ИИ

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

Вземането на решение дали да се приложи персонализирана функция на загуба в градиентно усилване при асиметрични бизнес разходи изисква да се оцени дали асиметрията може да бъде обработена надолу по веригата чрез калибриране на вероятностите и настройка на прага, или дали фундаментално променя оптимизационния пейзаж по време на индукцията на дърветата: 1. **Настройка на прага срещу персонализирана функция на загуба:** За задачи за класификация с асиметрични разходи за грешка (напр. фалшиви негативи струват $10\times$ повече от фалшиви позитиви), стандартната кръстосана ентропия е правилно правило за оценяване, което цели да оцени апостериорни вероятности $P(y=1|x)$, но калибрирането трябва да бъде проверено и, ако е необходимо, коригирано на валидационни данни. Преместването на прага за решение за класификация въз основа на матрицата на бизнес разходите $\tau = \frac{C_{FP}}{C_{FP} + C_{FN}}$ или прилагането на тегла на извадките (sample weights) често е по-чисто и избягва персонализирани производни. Въпреки това, за асиметрична регресия (напр. асиметрична pinball загуба за търсене на инвентар) или нелинейни бизнес наказания, където стандартните цели не могат да ръководят намирането на разделение, персонализирана функция на загуба е оправдана. 2. **Математически изисквания за GBDT:** При усилватели от втори ред (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)$). Хесианите трябва да са неотрицателни или безопасно апроксимирани/ограничени за числена стабилност; някои реализации поддържат цели от първи ред или приблизителни цели, така че изискването е специфично за рамката. Недиференцируеми или прекъснати бизнес метрики трябва да бъдат заменени с гладки сурогатни апроксимации (напр. Huberized или 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)
Опитайте да отговорите на този въпрос с треньор с ИИ

15Какво е LambdaMART и как адаптира градиентното усилване (gradient boosting) за цели за обучение за ранжиране (learning-to-rank objectives)?

LambdaMART е алгоритъм за обучение за ранжиране (LTR - Learning-to-Rank), който комбинира MART (Multiple Additive Regression Trees / Gradient Boosting) с рамката LambdaRank. При ранжирането, целеви метрики като NDCG (Normalized Discounted Cumulative Gain) и 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}$. Стандартните регресионни дървета в ансамбъла за усилване (boosting ensemble) след това пасват тези композитни ламбда градиенти за всеки документ при всяка итерация на усилване (boosting iteration), като директно оптимизират метрики за ранжиране по списък (listwise ranking metrics).

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)
Опитайте да отговорите на този въпрос с треньор с ИИ