Przygotowanie do rozmów rekrutacyjnych z klasycznego uczenia maszynowego

Klasyczne Pytania Rekrutacyjne dla Inżynierów Uczenia Maszynowego

15 wybranych pytań rekrutacyjnych z klasycznego uczenia maszynowego pogrupowanych według poziomu doświadczenia. Użyj ich, aby powtórzyć podstawy, praktyczne kompromisy i rozumowanie na poziomie seniorskim dotyczące produkcji.

Rozpocznij wywiad AI z klasycznego uczenia maszynowegoKarta kredytowa nie jest wymagana. Dostępna 1 darmowa sesja.
Trening technicznych rozmów rekrutacyjnych po angielskuTryb dla osób, dla których angielski nie jest językiem ojczystym, do ćwiczenia rozmów technicznych.

Pytania dla Juniorów

1Jaka jest różnica między parametrem modelu a hiperparametrem w uczeniu nadzorowanym?

W uczeniu maszynowym nadzorowanym parametry modelu to zmienne wewnętrzne uczone bezpośrednio z danych treningowych za pomocą algorytmu optymalizacji (takiego jak spadek gradientowy lub równania normalne). Przykładami są wagi regresji i bias w modelach liniowych lub progi podziału w drzewach decyzyjnych. Natomiast hiperparametry to zewnętrzne ustawienia konfiguracyjne określone przed treningiem, które kierują procesem uczenia, pojemnością modelu lub architekturą. Nie mogą być one uczone bezpośrednio poprzez minimalizację standardowej funkcji straty treningowej, ponieważ optymalizator trywialnie przetrenowałby model (np. ustawiając głębokość drzewa na nieskończoność). Przykładami są współczynnik uczenia (learning rate), siła regularyzacji (lambda/C), liczba drzew w lesie i maksymalna głębokość drzewa. Hiperparametry są dostrajane przy użyciu danych walidacyjnych lub walidacji krzyżowej.

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}")
Odpowiedz na to pytanie z trenerem AI

2Jakie założenia przyjmuje regresja liniowa metodą najmniejszych kwadratów (OLS) i w jaki sposób diagnostyka reszt ujawnia naruszenia tych założeń?

Regresja liniowa metodą najmniejszych kwadratów (OLS) opiera się na kilku kluczowych założeniach: 1. Liniowość: Zależność między predyktorami a wynikiem jest liniowa w parametrach. 2. Niezależność błędów: Obserwacje i błędy resztowe są wzajemnie niezależne (brak autokorelacji). 3. Homoskedastyczność: Składniki błędu mają stałą wariancję na wszystkich poziomach predyktorów. 4. Normalność reszt: Składniki błędu są rozłożone normalnie (wymagane dla prawidłowych przedziałów ufności i testów hipotez). 5. Brak współliniowości: Predyktory nie są liniowo zależne (macierz projektowa ma pełny rząd kolumn). Diagnostyka reszt ujawnia naruszenia w następujący sposób: - Wykres Reszty kontra Wartości Dopasowane: Krzywizny lub nielosowe wzorce ujawniają nieliniowość; kształt lejka lub wachlarza ujawnia heteroskedastyczność (niestanowczą wariancję). - Wykres kwantylowo-kwantylowy (Q-Q) normalny: Systematyczne odchylenie od prostej linii diagonalnej (np. krzywe S lub grube ogony) ujawnia nienormalność. - Wykres Reszty kontra Kolejność/Czas: Systematyczne trendy lub cykliczne wzorce ujawniają autokorelowane błędy. - Wykres Dźwignia / Odległość Cooka: Identyfikuje wartości odstające o wysokim wpływie (high-leverage outliers) lub punkty wpływowe, które nieproporcjonalnie przesuwają dopasowany model.

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.")
Odpowiedz na to pytanie z trenerem AI

3Jak regresja logistyczna modeluje klasyfikację binarną i jaka jest rola funkcji sigmoidalnej?

Regresja logistyczna modeluje klasyfikację binarną poprzez estymację prawdopodobieństwa a posteriori przynależności do klasy $P(Y=1|X)$. Aby zapewnić, że przewidywane prawdopodobieństwa pozostaną w zakresie $(0, 1)$, regresja logistyczna modeluje logarytm szans (logit) klasy pozytywnej jako liniową funkcję danych wejściowych: $\ln\left(\frac{p}{1-p}\right) = w^T x + b$. Funkcja sigmoidalna (logistyczna), $\sigma(z) = \frac{1}{1 + e^{-z}}$, służy jako funkcja łącząca, która mapuje dowolną rzeczywistą liniową ocenę $z = w^T x + b \in (-\infty, +\infty)$ monotonicznie na prawidłowe prawdopodobieństwo $p \in (0, 1)$. Dyskretne decyzje klasyfikacyjne podejmowane są przez zastosowanie progu decyzyjnego $\tau$ (zazwyczaj 0.5): $\hat{y} = 1$ jeśli $P(Y=1|X) \ge \tau$, w przeciwnym razie $0$. Ponieważ $\sigma(z) = 0.5$ występuje dokładnie wtedy, gdy $z = 0$, granica decyzyjna w przestrzeni cech jest liniową hiperpłaszczyzną $w^T x + b = 0$, co sprawia, że standardowa regresja logistyczna jest klasyfikatorem liniowym.

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}")
Odpowiedz na to pytanie z trenerem AI

4Czym jest regularyzacja L2 i jak regresja grzbietowa (Ridge regression) zmienia funkcję celu i estymacje współczynników?

Regularyzacja L2 (regresja grzbietowa) dodaje karę proporcjonalną do sumy kwadratów wag do funkcji straty metody najmniejszych kwadratów (OLS - Ordinary Least Squares): $$\min_w \|y - Xw\|_2^2 + \lambda \|w\|_2^2$$ Analitycznie, regresja grzbietowa modyfikuje równania normalne poprzez dodanie $\lambda I$ do macierzy Grama przed inwersją: $$w_{\text{ridge}} = (X^T X + \lambda I)^{-1} X^T y$$ Główne wpływy na funkcję celu i estymacje współczynników: 1. **Skurczenie (Shrinkage)**: Współczynniki są ściągane w kierunku zera proporcjonalnie do wariancji cech i korelacji, co redukuje złożoność modelu bez wymuszania ich do dokładnie zera. 2. **Wielokoliniowość i Odwracalność**: Gdy cechy są współliniowe lub $p > N$, $X^T X$ jest osobliwa lub źle uwarunkowana. Dodanie $\lambda I$ zapewnia, że $(X^T X + \lambda I)$ jest ściśle dodatnio określona i odwracalna, stabilizując estymacje parametrów. 3. **Kompromis Między Obciążeniem a Wariancją (Bias-Variance Trade-off)**: Zwiększenie $\lambda$ wprowadza celowe obciążenie (bias) w estymacje współczynników, jednocześnie znacząco redukując wariancję, co skutkuje niższym oczekiwanym błędem generalizacji na niewidocznych danych. 4. **Wymóg Skalowania Cech**: Ponieważ kara traktuje wszystkie wagi jednakowo, cechy o większej skali byłyby nieproporcjonalnie regularyzowane. Cechy muszą być standaryzowane (średnia zero, wariancja jednostkowa) przed dopasowaniem.

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)
Odpowiedz na to pytanie z trenerem AI

5W jaki sposób drzewo decyzyjne rekurencyjnie dzieli przestrzeń cech i jakie kryteria są używane do wyboru podziałów klasyfikacyjnych?

Drzewo decyzyjne dzieli przestrzeń cech za pomocą algorytmu zachłannego, działającego od góry do dołu, zwanego **rekurencyjnym podziałem binarnym (recursive binary partitioning)**. Zaczynając od węzła korzenia ze wszystkimi danymi treningowymi, algorytm przeszukuje wszystkie cechy i możliwe wartości progowe, aby znaleźć pojedynczy podział wyrównany do osi ($X_j \le t$), który maksymalizuje redukcję nieczystości. Zbiór danych jest dzielony na dwa węzły potomne, a procedura ta jest stosowana rekurencyjnie do każdego węzła potomnego, aż do osiągnięcia kryterium zatrzymania (np. maksymalna głębokość, minimalna liczba próbek na liść lub czyste węzły). Ponieważ podziały oceniają jedną cechę naraz względem progu, wynikowe granice decyzyjne są ortogonalnymi hiperpłaszczyznami (prostokątnymi obszarami wyrównanymi do osi w przestrzeni cech). Do oceny i wyboru najlepszego podziału w drzewach klasyfikacyjnych stosuje się dwa główne kryteria nieczystości: 1. **Nieczystość Giniego (Gini Impurity) (używana w CART)**: Mierzy prawdopodobieństwo, że losowo wybrana próbka zostałaby błędnie sklasyfikowana, gdyby była etykietowana losowo zgodnie z rozkładem klas w węźle. Dla $K$ klas o proporcjach $p_k$: $$I_G = 1 - \sum_{k=1}^K p_k^2$$ 2. **Entropia i Zysk Informacji (Information Gain) (używane w ID3, C4.5)**: Entropia mierzy niepewność w węźle: $H = -\sum_{k=1}^K p_k \log_2(p_k)$. Podział jest wybierany tak, aby zmaksymalizować **Zysk Informacji**, który jest entropią węzła nadrzędnego minus ważona średnia entropii węzłów potomnych: $$IG = H(\text{parent}) - \sum_{c \in \{\text{left, right}\}} \frac{N_c}{N} H(c)$$ Obie metryki osiągają 0, gdy węzeł jest całkowicie czysty (wszystkie próbki należą do jednej klasy) i osiągają maksimum, gdy klasy są równomiernie rozłożone.

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}")
Odpowiedz na to pytanie z trenerem AI

6Czym jest k-najbliższych sąsiadów (kNN) i jak dokonuje predykcji w klasyfikacji i regresji?

k-Najbliższych Sąsiadów (kNN) to nieparametryczny, oparty na instancjach (leniwy) algorytm uczenia nadzorowanego. Nie szkoli jawnego modelu parametrycznego; zamiast tego przechowuje zbiór danych treningowych i wykonuje wszystkie obliczenia podczas wnioskowania. Przebieg Predykcji: 1. **Obliczanie Odległości:** Gdy oceniana jest instancja zapytania, algorytm oblicza jej odległość do wszystkich przechowywanych instancji treningowych, używając określonej metryki (takiej jak odległość euklidesowa, manhattanowska lub Minkowskiego). 2. **Wybór Sąsiadów:** Wybiera $k$ instancji treningowych o najmniejszych odległościach do instancji zapytania. 3. **Agregacja:** * **Klasyfikacja:** Przypisuje klasę poprzez głosowanie większościowe (dominanta) wśród $k$ sąsiadów (lub głos ważony odległością). * **Regresja:** Przewiduje ciągłą wartość docelową, biorąc lokalną średnią (średnią arytmetyczną lub medianę) wartości docelowych $k$ sąsiadów (lub średnią ważoną odległością). Ponieważ obliczenia odległości zależą bezpośrednio od skal cech, normalizacja lub standaryzacja cech jest niezbędna, aby zapobiec dominowaniu obliczeń odległości przez cechy o dużej wartości.

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))
Odpowiedz na to pytanie z trenerem AI

7Podaj założenie warunkowej niezależności naiwnego klasyfikatora Bayesa i wyjaśnij, dlaczego naiwny klasyfikator Bayesa może nadal działać dobrze, gdy to założenie jest naruszone.

Założenie warunkowej niezależności naiwnego klasyfikatora Bayesa mówi, że dla danej etykiety klasy Y = y wszystkie cechy X_1, X_2, ..., X_d są wzajemnie niezależne: P(X_1, ..., X_d | Y = y) = \prod_{j=1}^d P(X_j | Y = y). Korzystając z twierdzenia Bayesa, prawdopodobieństwo a posteriori wynosi: P(Y = y | X) \propto P(Y = y) \prod_{j=1}^d P(X_j | Y = y), gdzie P(Y = y) jest prawdopodobieństwem a priori klasy, a P(X_j | Y = y) jest wiarygodnością warunkową klasy (np. rozkład Gaussa dla cech ciągłych, rozkład wielomianowy dla liczników). Naiwny klasyfikator Bayesa często działa dobrze w praktyce, pomimo naruszeń założenia niezależności, ponieważ klasyfikacja opiera się na regule decyzyjnej argmax (argmax_y P(Y=y | X)), a nie na dokładnej kalibracji prawdopodobieństw. Nawet jeśli korelacje cech powodują, że przewidywane prawdopodobieństwa stają się nadmiernie pewne lub zniekształcone, prawidłowa klasa często zachowuje najwyższe względne miejsce w rankingu. Dopóki korelacja nie odwróci kolejności w rankingu wiarygodności klas, decyzja klasyfikacyjna 0-1 pozostaje dokładna.

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]]))
Odpowiedz na to pytanie z trenerem AI

Pytania dla Midów

8Wyprowadź lub wyjaśnij zamknięte rozwiązanie OLS (Ordinary Least Squares) i określ, kiedy istnieje ono jednoznacznie.

Funkcja celu metody najmniejszych kwadratów (OLS - Ordinary Least Squares) minimalizuje sumę kwadratów reszt: $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$. Przyrównując gradient względem $\beta$ do zera: $$\nabla_\beta S(\beta) = -2 X^T y + 2 X^T X \beta = 0 \implies X^T X \beta = X^T y$$ Są to równania normalne. Gdy $X^T X$ jest nieosobliwa (odwracalna), jednoznaczne rozwiązanie w formie zamkniętej to: $$\hat{\beta} = (X^T X)^{-1} X^T y$$ Geometrycznie, $\hat{y} = X\hat{\beta} = X(X^T X)^{-1} X^T y = H y$ reprezentuje rzut ortogonalny wektora docelowego $y$ na przestrzeń kolumnową macierzy projektowej $X$, gdzie $H$ jest macierzą rzutu (hat matrix). Rozwiązanie istnieje jednoznacznie wtedy i tylko wtedy, gdy $X^T X$ jest odwracalna, co wymaga, aby macierz projektowa $X$ o wymiarach $N \times P$ miała pełny rząd kolumnowy ($Rank(X) = P$). Wymaga to $N \ge P$ i braku dokładnej współliniowości (żadna cecha nie jest liniową kombinacją innych). Jeśli $X$ ma niepełny rząd, $X^T X$ jest osobliwa, co prowadzi do nieskończenie wielu rozwiązań, często rozwiązywanych za pomocą regularyzacji lub pseudoodwrotności Moore’a-Penrose’a $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}")
Odpowiedz na to pytanie z trenerem AI

9Czym jest estymacja maksymalnej wiarygodności (MLE) i w jaki sposób prowadzi do funkcji celu entropii krzyżowej w regresji logistycznej?

Estymacja Maksymalnej Wiarygodności (MLE) to metoda szacowania parametrów modelu $\theta$ poprzez wybór wartości, które maksymalizują wiarygodność $L(\theta) = P(\mathcal{D}|\theta)$ obserwowanego zbioru danych. W binarnej regresji logistycznej, każda etykieta $y_i \in \{0, 1\}$ jest modelowana jako niezależna zmienna losowa Bernoulliego, uwarunkowana $x_i$, z prawdopodobieństwem sukcesu $p_i = \sigma(w^T x_i + b)$. Funkcja masy prawdopodobieństwa dla obserwacji $i$ to $P(y_i|x_i) = p_i^{y_i} (1 - p_i)^{1 - y_i}$. Zakładając niezależne i identycznie rozłożone (i.i.d.) próbki, łączna wiarygodność wynosi: $$L(w, b) = \prod_{i=1}^N p_i^{y_i} (1 - p_i)^{1 - y_i}$$ Przyjęcie logarytmu naturalnego przekształca iloczyn w łatwą do obliczenia sumę log-wiarygodności: $$\ell(w, b) = \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Ponieważ algorytmy optymalizacji są standardowo formułowane jako problemy minimalizacji, negujemy log-wiarygodność i normalizujemy ją przez rozmiar próbki $N$, uzyskując Ujemną Log-Wiarygodność (NLL), która jest dokładnie funkcją celu binarnej entropii krzyżowej (strata logarytmiczna): $$J(w, b) = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Ta funkcja celu jest wypukła względem liniowych logitów/wag, więc odpowiednie solwery numeryczne optymalizują globalny cel. Ścisła wypukłość i skończone, unikalne MLE wymagają dodatkowych warunków, takich jak wystarczający rząd cech, regularyzacja i brak idealnej separacji klas.

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}")
Odpowiedz na to pytanie z trenerem AI

10Jak spadek gradientowy (gradient descent) optymalizuje klasyczny cel uczenia maszynowego (ML objective) i jak współczynnik uczenia (learning rate), zbieżność (convergence) i wypukłość (convexity) wpływają na proces uczenia?

Spadek gradientowy minimalizuje empiryczną funkcję straty poprzez iteracyjne aktualizowanie parametrów modelu w kierunku przeciwnym do gradientu funkcji celu względem tych parametrów: $\theta_{t+1} = \theta_t - \eta \nabla L(\theta_t)$. Kluczowe czynniki wpływające na uczenie to: 1. Współczynnik uczenia ($\eta$): Kontroluje rozmiar kroku. Jeśli jest zbyt mały, zbieżność jest niezwykle wolna, a uczenie może utknąć. Jeśli jest zbyt duży, aktualizacje przekroczą minimum, powodując oscylacje lub numeryczną rozbieżność. 2. Zbieżność: Określana przez monitorowanie kryteriów zatrzymania, takich jak mała norma gradientu ($||\nabla L(\theta)|| \le \epsilon$), minimalna zmiana parametrów lub spłaszczanie się funkcji straty w kolejnych iteracjach. 3. Wypukłość: W przypadku funkcji celu wypukłych (np. standardowa regresja liniowa OLS lub regresja logistyczna), każde minimum lokalne jest gwarantowanym minimum globalnym, co pozwala spadkowi gradientowemu zbiegać się niezawodnie przy odpowiednich rozmiarach kroków. W przypadku funkcji celu niewypukłych (np. wielowarstwowe sieci neuronowe), krajobraz funkcji straty zawiera wiele minimów lokalnych, punktów siodłowych i płaskowyżów, co sprawia, że ostateczne rozwiązanie jest wrażliwe na inicjalizację. 4. Optymalizacja a generalizacja: Zbieżność na stratach treningowych odzwierciedla sukces optymalizacji, natomiast straty walidacyjne oceniają zdolność generalizacji. Osiągnięcie niskich strat treningowych z wysokim błędem walidacji wskazuje na przeuczenie, a nie na błąd optymalizacji.

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
Odpowiedz na to pytanie z trenerem AI

11Porównaj regularyzację L1, L2 i ElasticNet pod względem rzadkości (sparsity), cech skorelowanych i praktycznego wyboru modelu.

Regularyzacja L1 (Lasso), L2 (Ridge) i ElasticNet różnią się w sformułowaniu kary, geometrii ograniczeń, rzadkości i sposobie traktowania skorelowanych predyktorów: 1. **Rzadkość i Geometria:** * L1 wykorzystuje karę wartości bezwzględnej ($\lambda \|w\|_1$). Jej granica ograniczenia to romb/politop z ostrymi wierzchołkami na osiach współrzędnych. Gdy kontury funkcji straty przecinają te rogi, wagi są redukowane do dokładnie zera, co prowadzi do automatycznego wyboru cech. * L2 wykorzystuje karę w postaci kwadratowej normy euklidesowej ($\lambda \|w\|_2^2$). Jej granica ograniczenia to gładka hipersfera bez rogów, która asymptotycznie redukuje wagi do zera, ale rzadko ustawia je na dokładnie zero. 2. **Cechy Skorelowane:** * Przy silnej kolinearności, L1 ma tendencję do arbitralnego wybierania jednej cechy z grupy skorelowanych predyktorów i ustawiania pozostałych współczynników na zero, co skutkuje niestabilnymi estymatami w różnych próbkach. * L2 zachowuje wszystkie skorelowane cechy, rozkładając między nie wagi i jednocześnie je redukując. 3. **ElasticNet:** * Łączy obie kary: $\lambda_1 \|w\|_1 + \lambda_2 \|w\|_2^2$ (często parametryzowane za pomocą $\alpha$ i $l_1\_\text{ratio}$). * Zapewnia rzadkość i wybór cech charakterystyczny dla Lasso, jednocześnie zachowując efekt grupowania Ridge, wybierając razem klastry skorelowanych predyktorów. Jest to szczególnie przydatne, gdy $p > N$ lub w przypadku silnej współliniowości.

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_)
Odpowiedz na to pytanie z trenerem AI

12Jaka jest w ogólnym zarysie różnica między regresją Ridge, regresją z komponentami głównymi (PCR - Principal Component Regression) a częściowymi najmniejszymi kwadratami (PLS - Partial Least Squares)?

Regresja Ridge, regresja z komponentami głównymi (PCR - Principal Component Regression) i częściowe najmniejsze kwadraty (PLS - Partial Least Squares) to trzy liniowe techniki używane do radzenia sobie z wielokolinearnością i wysoką wymiarowością, ale różnią się sposobem redukcji wariancji oraz tym, czy redukcja jest ciągła czy nadzorowana: 1. **Regresja Ridge:** Zachowuje wszystkie oryginalne $p$ cech i stosuje ciągłe kurczenie (shrinkage) wartości współczynników za pomocą kary L2. Nie konstruuje komponentów ukrytych o niższej wymiarowości ani nie odrzuca wymiarów cech; raczej zmniejsza wariancję wzdłuż kierunków o niskiej wartości własnej macierzy $X^T X$. 2. **Regresja z Komponentami Głównymi (PCR):** Dwustopniowa, nienadzorowana metoda redukcji wymiarowości. Najpierw stosuje analizę komponentów głównych (PCA - Principal Component Analysis) ściśle do macierzy predyktorów $X$, aby znaleźć ortogonalne kierunki maksymalnej wariancji, zachowuje $k$ najważniejszych komponentów głównych i dopasowuje regresję OLS (Ordinary Least Squares) na tych $k$ komponentach. Ponieważ PCA ignoruje zmienną docelową $y$, PCR ryzykuje odrzucenie komponentów, które mają niską wariancję w $X$, ale wysoką siłę predykcyjną dla $y$. 3. **Częściowe Najmniejsze Kwadraty (PLS):** Nadzorowana metoda redukcji wymiarowości. Konstruuje $k$ ortogonalnych komponentów ukrytych poprzez znajdowanie liniowych kombinacji $X$, które maksymalizują kowariancję między $X$ a zmienną odpowiedzi $y$. Poprzez jawne uwzględnienie informacji o zmiennej docelowej, PLS identyfikuje komponenty, które wyjaśniają zarówno wariancję cech, jak i wariancję odpowiedzi.

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)
Odpowiedz na to pytanie z trenerem AI

Pytania dla Seniorów

13W jaki sposób nowoczesne implementacje wzmacniania gradientowego (gradient boosting), takie jak XGBoost, LightGBM i CatBoost, optymalizują proces uczenia lub inaczej obsługują cechy tabelaryczne?

Nowoczesne frameworki GBDT (Gradient Boosting Decision Tree) znacząco różnią się pod względem algorytmów znajdowania podziału, strategii wzrostu drzewa oraz obsługi cech tabelarycznych/kategorycznych: 1. **XGBoost**: Tradycyjnie opiera się na dokładnym zachłannym (exact greedy) lub przybliżonym szkicu kwantylowym (approximate quantile sketch) do znajdowania podziału (a później Fast Hist), wykorzystuje wzrost drzewa poziomami (depth-wise), a brakujące wartości obsługuje poprzez uczenie optymalnego kierunku domyślnej gałęzi podczas wyszukiwania podziału. 2. **LightGBM**: Wykorzystuje znajdowanie podziału oparte na histogramie (dzieląc cechy ciągłe na dyskretne przedziały, zazwyczaj 256), wzrost drzewa od liścia (best-first) dla szybszej redukcji straty, próbkowanie jednostronne oparte na gradiencie (GOSS - Gradient-based One-Side Sampling), aby zachować instancje z dużym gradientem przy jednoczesnym próbkowaniu instancji z małym gradientem, oraz łączenie cech wykluczających się (EFB - Exclusive Feature Bundling) do łączenia wzajemnie wykluczających się rzadkich cech. Dla zmiennych kategorycznych, znajduje optymalne podziały poprzez sortowanie kategorii histogramu ($O(K \log K)$). 3. **CatBoost**: Używa obojętnych (symetrycznych) drzew decyzyjnych, gdzie wszystkie węzły na danej głębokości współdzielą ten sam podział, co umożliwia szybkie wektoryzowane punktowanie na CPU/GPU. Jego główną innowacją są Uporządkowane Statystyki Celu (Ordered Target Statistics) i uporządkowane wzmacnianie (ordered boosting), które oblicza statystyki celu na losowych permutacjach danych treningowych, aby zapobiec wyciekowi celu (target leakage) i przesunięciu predykcji (prediction shift).

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'])
Odpowiedz na to pytanie z trenerem AI

14Jak zdecydować, czy niestandardowa funkcja straty jest odpowiednia dla modelu wzmacniania gradientowego (gradient boosting) w przypadku asymetrycznych kosztów biznesowych?

Decyzja o tym, czy wdrożyć niestandardową funkcję straty we wzmacnianiu gradientowym (gradient boosting) w warunkach asymetrycznych kosztów biznesowych, wymaga oceny, czy asymetria może być obsłużona na późniejszym etapie poprzez kalibrację prawdopodobieństwa i strojenie progu, czy też fundamentalnie zmienia ona przestrzeń optymalizacji podczas indukcji drzewa: 1. Strojenie progu a niestandardowa funkcja straty: W przypadku zadań klasyfikacji z asymetrycznymi kosztami błędów (np. fałszywie negatywne kosztują 10-krotnie więcej niż fałszywie pozytywne), standardowa entropia krzyżowa jest właściwą funkcją oceny (proper scoring rule), która ma na celu oszacowanie prawdopodobieństw a posteriori $P(y=1|x)$, ale kalibracja powinna być sprawdzona i, jeśli to konieczne, skorygowana na danych walidacyjnych. Przesunięcie progu decyzji klasyfikacyjnej w oparciu o macierz kosztów biznesowych $\tau = \frac{C_{FP}}{C_{FP} + C_{FN}}$ lub zastosowanie wag próbek jest często czyściejsze i pozwala uniknąć niestandardowych pochodnych. Jednak w przypadku regresji asymetrycznej (np. asymetryczna funkcja straty pinball dla popytu na inwentarz) lub nieliniowych kar biznesowych, gdzie standardowe funkcje celu nie mogą kierować znajdowaniem podziałów, niestandardowa funkcja straty jest uzasadniona. 2. Wymagania matematyczne dla GBDT (Gradient Boosting Decision Trees): W boosterach drugiego rzędu (XGBoost, LightGBM) niestandardowa funkcja straty $L(y, \hat{y})$ zazwyczaj wymaga obliczalnych gradientów pierwszego rzędu ($g_i = \partial L / \partial \hat{y}_i$) oraz ważnych wartości krzywizny drugiego rzędu/hesjanu ($h_i = \partial^2 L / \partial \hat{y}_i^2$) do obliczeń zysku z podziału i wag liści ($w^* = -\sum g_i / (\sum h_i + \lambda)$). Hesjany powinny być nieujemne lub bezpiecznie aproksymowane/przycięte dla stabilności numerycznej; niektóre implementacje obsługują funkcje celu pierwszego rzędu lub aproksymowane, więc wymaganie jest specyficzne dla frameworka. Nieróżniczkowalne lub nieciągłe metryki biznesowe powinny zostać zastąpione gładkimi aproksymacjami zastępczymi (np. warianty Huberized lub 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)
Odpowiedz na to pytanie z trenerem AI

15Czym jest LambdaMART i w jaki sposób dostosowuje wzmocnienie gradientowe (gradient boosting) do celów uczenia się do rankingu (learning-to-rank)?

LambdaMART to algorytm uczenia się do rankingu (LTR), który łączy MART (wielokrotne addytywne drzewa regresyjne / wzmocnienie gradientowe) z ramami LambdaRank. W rankingu metryki docelowe, takie jak NDCG (Znormalizowany Zdyskontowany Zysk Skumulowany) i MAP, zależą od dyskretnej kolejności sortowania (rang), co sprawia, że są one płaskie niemal wszędzie i nieróżniczkowalne względem ciągłych wyników modelu. LambdaMART omija ten problem, konstruując wirtualne gradienty, zwane 'gradientami lambda' ($\lambda_{ij}$), dla par elementów $(i, j)$ w ramach tego samego zapytania. Podstawowy gradient parowy pochodzi ze straty logistycznej na różnicach wyników ($s_i - s_j$). LambdaMART skaluje ten gradient przez dokładną zmianę w docelowej metryce rankingowej ($|\Delta \text{NDCG}_{ij}|$), która miałaby miejsce, gdyby pozycje dokumentu $i$ i dokumentu $j$ zostały zamienione: $$\lambda_{ij} = \frac{-\sigma}{1 + e^{\sigma(s_i - s_j)}} |\Delta \text{NDCG}_{ij}|$$ Dla każdego pojedynczego dokumentu $i$ gradient wypadkowy jest obliczany poprzez agregację parowych gradientów lambda we wszystkich parach obejmujących dokument $i$: $\lambda_i = \sum_{j: j \succ i} \lambda_{ij} - \sum_{k: i \succ k} \lambda_{ki}$. Standardowe drzewa regresyjne w zespole wzmacniającym dopasowują następnie te złożone gradienty lambda dla poszczególnych dokumentów w każdej iteracji wzmacniania, bezpośrednio optymalizując metryki rankingowe na poziomie listy.

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)
Odpowiedz na to pytanie z trenerem AI