3/20/23
Instalacja pakietów, z których będziemy korzystać. Lepiej to zrobić przed zajęciami :)
w R wiele modeli ma swoje oddzielne implementacje w osobnych bibliotekach
nawet meta-pakiety wykorzystują de facto pod spodem te mniejsze paczki
Takie rozdrobnienie ma też swój plus – pakiety są bardziej dopracowane, mają więcej funkcjonalności i parametrów.
Ogrom dostępnych bibliotek można zauważyć przeglądając CRAN Task View: Machine Learning & Statistical Learning.
GŁÓWNY CEL: Zapoznanie z …
Na jakich modelach się skupimy?
regresja liniowa,
regresja logistyczna,
drzewa decyzyjne,
lasy losowe,
modele boostingowe,
metoda k najbliższych sąsiadów.
flowchart TB A((UCZENIE MASZYNOWE)) -- mamy dane i odpowiedzi --> B(NADZOROWANE) A -- nie mamy danych, mamy środowisko --> D(ZE WZMOCNIENIEM) A -- mamy dane, nie mamy odpowiedzi --> C(NIENADZOROWANE) B -- przewidujemy liczbę --> E([regresja]) B -- przewidujemy kategorię --> F([klasyfikacja]) C -- dzielimy na grupy --> G([klasteryzacja]) C -- szukamy outlierów --> H([wykrywanie anomalii])
flowchart TB
A((UCZENIE MASZYNOWE)) -- mamy dane i odpowiedzi --> B(NADZOROWANE)
A -- mamy dane, nie mamy odpowiedzi --> C(NIENADZOROWANE)
A -- nie mamy danych, mamy środowisko --> D(ZE WZMOCNIENIEM)
subgraph
B -- przewidujemy liczbę --> E([regresja])
B -- przewidujemy kategorię --> F([klasyfikacja])
end
C -- dzielimy na grupy --> G([klasteryzacja])
C -- szukamy outlierów --> H([wykrywanie anomalii])
\(X\) – dane
(zmienne objaśniające/predyktory/zmienne niezależne)
\(Y\) – odpowiedzi
(zmienna objaśniana/target/zmienna zależna)
dla zadania regresji \(Y \in \mathbb{R}\) (odpowiedź to liczba)
dla zadania klasyfikacji \(Y \in \{1, 2, \ldots, g\}\) (odpowiedź to etykieta, kategoria)
…
…
dla zadania regresji:
dla zadania klasyfikacji:
titanic_dataset <- DALEX::titanic_imputed
titanic_dataset$survived <- as.factor(titanic_dataset$survived )
n <- nrow(titanic_dataset)
set.seed(42)
train_mask <- sample(n, 0.8*n)
titanic_class_train <- titanic_dataset[train_mask,]
titanic_class_test <- titanic_dataset[-train_mask,]
pima_class_train <- MASS::Pima.tr
pima_class_test <- MASS::Pima.tezbiór treningowy – trenowanie (uczenie) modelu
zbiór testowy – testowanie jakości modelu na niewidzianych danych (następne zajęcia)
Działa dla zadania regresji.
Dla \(p=1\) (jednej zmiennej objaśniającej) polega na dopasowaniu prostej (funkcji liniowej):
linreg_model_simple <- lm(m2.price ~ surface, data = apartments_regr_train)
plot(apartments_regr_train$surface, apartments_regr_train$m2.price, pch=16,
xlab="Powierzchnia [m^2]",
ylab="Cena za m^2")
abline(linreg_model_simple, col="red", lwd=3)(Intercept) surface
4523.13639 -12.10559
Ogólniej: nasz model \(F\) wyraża się jako: \(\hat{Y} = F(X) = X \hat{\beta}\), czyli dla \(i\)-tej obserwacji: \[\hat{y_i} = \hat{\beta_0} + \hat{\beta_1} \cdot x_{i1} + \hat{\beta_2} \cdot x_{i2} + \ldots + \hat{\beta_p} \cdot x_{ip}.\]
Na czym polega trening?
szukamy współczynników (wag modelu) \(\hat{\beta}\),
wybieramy takie \(\hat{\beta}\), które minimalizuje sumę kwadratów błędów predykcji na zbiorze treningowym \(L = \sum_{i=1}^{n} (y_i - \hat{y_i})^2\),
przy takiej funkcji straty \(L\) mamy wzór jawny na \(\hat{\beta}\).
W praktyce używamy wbudowanej w R funkcji lm() (od linear model):
linreg_model <- lm(m2.price ~ ., data = apartments_regr_train)
summary(linreg_model) # podsumowanie statystyczne
Call:
lm(formula = m2.price ~ ., data = apartments_regr_train)
Residuals:
Min 1Q Median 3Q Max
-247.5 -202.8 -172.8 381.4 469.0
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5020.1391 682.8721 7.352 4.11e-13 ***
construction.year -0.2290 0.3483 -0.657 0.5110
surface -10.2378 0.5778 -17.720 < 2e-16 ***
floor -99.4820 3.0874 -32.222 < 2e-16 ***
no.rooms -37.7299 15.8440 -2.381 0.0174 *
districtBielany 17.2144 40.4502 0.426 0.6705
districtMokotow 918.3802 39.4386 23.286 < 2e-16 ***
districtOchota 926.2540 40.5279 22.855 < 2e-16 ***
districtPraga -37.1047 40.8930 -0.907 0.3644
districtSrodmiescie 2080.6110 40.0149 51.996 < 2e-16 ***
districtUrsus 29.9419 39.7249 0.754 0.4512
districtUrsynow -18.8651 39.7565 -0.475 0.6352
districtWola -16.8912 39.6283 -0.426 0.6700
districtZoliborz 889.9735 40.4099 22.024 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 281.3 on 986 degrees of freedom
Multiple R-squared: 0.905, Adjusted R-squared: 0.9037
F-statistic: 722.5 on 13 and 986 DF, p-value: < 2.2e-16
[1] 4820.009 3292.678 2717.910 2922.751 2974.086 2527.043 3200.487 2539.230
[9] 2780.212 4529.007
ZALETY:
Istnieje wiele statystycznych metod testowania i diagnostyki.
Interpretowalność.
Łatwość optymalizacji (jawny wzór) i lekkość.
WADY:
“Odpowiednik” regresji liniowej dla zadania klasyfikacji.
Dla dwóch klas \(\{0, 1\}\): interesuje nas \(\mathbb{P}(y=1/x) = p_1(x)\) prawdopodobieństwo przynależności danej obserwacji \(x\) do klasy 1 (prawdopodobieństwo “sukcesu”).
Dla \(p=1\) (jednej zmiennej objaśniającej):
logreg_model_simple <- glm(type~glu, data=pima_class_train, family = "binomial")
plot(pima_class_train$glu, pima_class_train$type == "Yes", pch=16,
xlab="Stężenie glukozy",
ylab="Cukrzyca")
glu_sequence <- seq(min(pima_class_train$glu), max(pima_class_train$glu), 1)
predictions <- predict(logreg_model_simple, data.frame(glu=glu_sequence), type = "response")
lines(glu_sequence, predictions, type="l", lwd=3, col="red")(Intercept) glu
-5.50363574 0.03778372
Zamiast prawdopodobieństwa modelujemy liniowo logarytm szans (p-stwo jest z przedziału [0, 1], a logarytm szans nie).
Szansa to stosunek prawdopodobieństwa sukcesu do prawdopodobieństwa porażki: \(odds = \frac{p_1}{1-p_1}\).
Nasz model wyraża się jako: \[\log(odds) = \hat{\beta_0} + \hat{\beta_1} \cdot x_{i1} + \hat{\beta_2} \cdot x_{i2} + \ldots + \hat{\beta_p} \cdot x_{ip}.\]
Na czym polega trening?
szukamy współczynników (wag modelu) \(\hat{\beta}\),
wybieramy takie \(\hat{\beta}\), które maksymalizuje funkcję wiarogodności (likelihood), tzn.:
dla obserwacji z klasy 1 daje wartości \(p_1(x)\) możliwie bliskie 1,
dla obserwacji z klasy 0 daje wartości \(p_1(x)\) możliwie bliskie 0.
W praktyce używamy wbudowanej w R funkcji glm() (od generalized linear model):
logreg_model <- glm(survived~., data=titanic_class_train, family = "binomial")
summary(logreg_model)
Call:
glm(formula = survived ~ ., family = "binomial", data = titanic_class_train)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.1893 -0.7140 -0.4812 0.6022 2.6396
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 3.158385 0.466352 6.773 1.27e-11 ***
gendermale -2.689430 0.173749 -15.479 < 2e-16 ***
age -0.037343 0.005978 -6.246 4.20e-10 ***
class2nd -1.011603 0.279881 -3.614 0.000301 ***
class3rd -2.061504 0.275707 -7.477 7.59e-14 ***
classdeck crew 1.350311 0.394180 3.426 0.000613 ***
classengineering crew -0.846771 0.290912 -2.911 0.003606 **
classrestaurant staff -3.290541 0.774814 -4.247 2.17e-05 ***
classvictualling crew -0.820075 0.285759 -2.870 0.004107 **
embarkedCherbourg 0.921634 0.315933 2.917 0.003532 **
embarkedQueenstown 0.411062 0.375373 1.095 0.273484
embarkedSouthampton 0.302534 0.237490 1.274 0.202706
fare 0.001538 0.002082 0.739 0.460160
sibsp -0.339313 0.099823 -3.399 0.000676 ***
parch -0.059689 0.105761 -0.564 0.572497
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2216.2 on 1764 degrees of freedom
Residual deviance: 1640.4 on 1750 degrees of freedom
AIC: 1670.4
Number of Fisher Scoring iterations: 5
Domyślnie predykowane są log-oddsy. W celu uzyskania prawdopodobieństw należy zmienić parametr type na "response".
predictions_logodds <- predict(logreg_model, titanic_class_test[1:10, ])
as.numeric(predictions_logodds) [1] 0.8128850 -1.0437219 0.5110133 3.1599875 -1.3696115 -0.1041560
[7] -0.4402464 -1.2328536 0.8056224 0.8816578
ZALETY:
Istnieje wiele statystycznych metod testowania i diagnostyki.
Interpretowalność.
WADY:
Może być użyte zarówno do problemu regresji, jak i klasyfikacji – często nazywane CART (Classification And Regression Trees).
Działanie polega na podziale przestrzeni predyktorów na regiony o różnej wartości zmiennej odpowiedzi (dla klasyfikacji – prawdopodobieństwo przynależności do klasy).
Algorytm można opisać krokowo:
Zaczynamy od korzenia drzewa – pojedynczego węzła, w którym mamy cały zbiór danych.
Dla obecnego węzła szukamy odpowiedniego podziału zbioru danych. W tym celu rozważamy każdą zmienną i dla każdej z nich analizujemy:
Sprawdzamy, czy zostało spełnione kryterium stopu, np.
Miary jakości podziału:
dla regresji: minimalizujemy błąd średniokwadratowy patrząc na predykcję dla regionów \(R_1\) i \(R_2\):\(MSE = \sum_{i \in R_1} (y_i-\hat{y_{R_1}})^2 + \sum_{i \in R_2} (y_i-\hat{y_{R_2}})^2\)
dla klasyfikacji: Gini index, entropia.
W praktyce jest wiele różnych pakietów oferujących budowanie drzew decyzyjnych. Tutaj wykorzystamy bibliotekę partykit, która umożliwia tworzenie przejrzystych wizualizacji drzew oraz pozwala na specyfikację wielu parametrów (ctree_control).
Model formula:
survived ~ gender + age + class + embarked + fare + sibsp + parch
Fitted party:
[1] root
| [2] gender in female
| | [3] class in 1st, 2nd, restaurant staff, victualling crew: 1 (n = 206, err = 6.3%)
| | [4] class in 3rd
| | | [5] embarked in Cherbourg, Queenstown: 1 (n = 71, err = 35.2%)
| | | [6] embarked in Southampton
| | | | [7] fare <= 22: 0 (n = 86, err = 45.3%)
| | | | [8] fare > 22: 0 (n = 25, err = 4.0%)
| [9] gender in male
| | [10] class in 1st, 2nd, 3rd, engineering crew, restaurant staff, victualling crew
| | | [11] class in 1st, engineering crew, victualling crew
| | | | [12] age <= 32: 0 (n = 373, err = 29.0%)
| | | | [13] age > 32: 0 (n = 350, err = 18.0%)
| | | [14] class in 2nd, 3rd, restaurant staff
| | | | [15] age <= 4
| | | | | [16] sibsp <= 2: 1 (n = 17, err = 11.8%)
| | | | | [17] sibsp > 2: 0 (n = 8, err = 12.5%)
| | | | [18] age > 4: 0 (n = 577, err = 11.4%)
| | [19] class in deck crew: 1 (n = 52, err = 32.7%)
Number of inner nodes: 9
Number of terminal nodes: 10
regr_tree_model <- ctree(m2.price~.,
data=apartments_regr_train,
control = ctree_control(maxdepth=4)
)
regr_tree_model
Model formula:
m2.price ~ construction.year + surface + floor + no.rooms + district
Fitted party:
[1] root
| [2] district in Bemowo, Bielany, Praga, Ursus, Ursynow, Wola
| | [3] surface <= 87
| | | [4] floor <= 4
| | | | [5] surface <= 50: 3911.536 (n = 56, err = 5618743.9)
| | | | [6] surface > 50: 3499.829 (n = 76, err = 6474984.8)
| | | [7] floor > 4
| | | | [8] surface <= 48: 3399.257 (n = 74, err = 7342186.1)
| | | | [9] surface > 48: 3027.575 (n = 106, err = 10996837.9)
| | [10] surface > 87
| | | [11] floor <= 6
| | | | [12] surface <= 105: 3129.833 (n = 48, err = 5235614.7)
| | | | [13] surface > 105: 2709.107 (n = 121, err = 13335733.6)
| | | [14] floor > 6
| | | | [15] surface <= 122: 2485.000 (n = 61, err = 5443088.0)
| | | | [16] surface > 122: 2084.224 (n = 58, err = 4742256.1)
| [17] district in Mokotow, Ochota, Srodmiescie, Zoliborz
| | [18] district in Mokotow, Ochota, Zoliborz
| | | [19] surface <= 80
| | | | [20] floor <= 4: 4658.787 (n = 47, err = 7109963.9)
| | | | [21] floor > 4: 4070.956 (n = 90, err = 9931231.8)
| | | [22] surface > 80
| | | | [23] floor <= 5: 3799.507 (n = 73, err = 11517088.2)
| | | | [24] floor > 5: 3311.400 (n = 90, err = 12840189.6)
| | [25] district in Srodmiescie
| | | [26] surface <= 61
| | | | [27] floor <= 2: 6172.000 (n = 11, err = 635246.0)
| | | | [28] floor > 2: 5572.241 (n = 29, err = 2472955.3)
| | | [29] surface > 61
| | | | [30] floor <= 8: 4972.178 (n = 45, err = 7138790.6)
| | | | [31] floor > 8: 4336.000 (n = 15, err = 2065332.0)
Number of inner nodes: 15
Number of terminal nodes: 16
Predykcją jest średnia wartość zmiennej odpowiedzi dla obserwacji ze zbioru treningowego, które trafiły do tego samego liścia.
5 7 23 24 28 34 38 44 48 50
0 0 0 1 0 0 0 0 1 1
Levels: 0 1
0 1
5 0.5465116 0.4534884
7 0.8856153 0.1143847
23 0.7104558 0.2895442
24 0.0631068 0.9368932
28 0.8856153 0.1143847
34 0.9600000 0.0400000
38 0.9600000 0.0400000
44 0.8856153 0.1143847
48 0.0631068 0.9368932
50 0.0631068 0.9368932
ZALETY:
Interpretowalność.
Intuicyjność.
Obsługa zmiennych kategorycznych bez konieczności ich kodowania (np. one hot encodingu).
WADY:
Podobnie jak drzewo decyzyjne – może być użyty zarówno do problemu regresji, jak i klasyfikacji.
Idea: “mądrość tłumu” – połączeniu wielu różnych modeli w jeden poprawi jego własności.
Las tworzy się z wielu różnych od siebie drzew decyzyjnych, których predykcje są agregowane (ensembling).
Zróżnicowanie drzew wynika z losowości zastosowanej na dwóch poziomach:
trenujemy każde pojedyncze drzewo na nieco innym zbiorze danych (generujemy \(B\) zbiorów treningowych wykorzystując procedurę bootstrapu, czyli losując ze zwracaniem obserwacje z całego, pierwotnego zbioru treningowego),
na każdym poziomie budowanego drzewa rozważamy tylko losowo wybrany podzbiór zmiennych jako kandydatów do podziału węzła.
W praktyce są dwa bardzo popularne pakiety oferujące budowanie lasów losowych (ranger i randomForest). Tutaj wykorzystamy bibliotekę randomForest.
library(randomForest)
class_rf_model <- randomForest(survived~., data=titanic_class_train)
class_rf_model
Call:
randomForest(formula = survived ~ ., data = titanic_class_train)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 2
OOB estimate of error rate: 19.21%
Confusion matrix:
0 1 class.error
0 1126 72 0.06010017
1 267 300 0.47089947
Call:
randomForest(formula = m2.price ~ ., data = apartments_regr_train)
Type of random forest: regression
Number of trees: 500
No. of variables tried at each split: 1
Mean of squared residuals: 79103.29
% Var explained: 90.37
Predykcja jest dokonywana przez głosowanie pojedynczych drzew.
5 7 23 24 28 34 38 44 48 50
1 0 1 1 0 0 0 0 1 1
Levels: 0 1
0 1
5 0.344 0.656
7 0.842 0.158
23 0.304 0.696
24 0.270 0.730
28 0.968 0.032
34 0.770 0.230
38 0.958 0.042
44 0.986 0.014
48 0.254 0.746
50 0.054 0.946
attr(,"class")
[1] "matrix" "array" "votes"
0 1
5 172 328
7 421 79
23 152 348
24 135 365
28 484 16
34 385 115
38 479 21
44 493 7
48 127 373
50 27 473
attr(,"class")
[1] "matrix" "array" "votes"
Może być użyty zarówno do problemu regresji, jak i klasyfikacji.
Idea: “mądrość tłumu” – połączeniu wielu różnych modeli w jeden poprawi jego własności.
Budowanie modelu polega na sekwencyjnym tworzeniu “słabych modeli” (w przypadku wykorzystania drzew decyzyjnych – płytkich drzew).
Modele tworzone są po sobie w taki sposób, że każdy kolejny ma na celu redukować błędy poprzedniego. Pierwsze drzewo trenowane jest z wykorzystaniem oryginalnego \(Y\), a każde kolejne trenowane jest już w oparciu o rezydua (błędy) dotychczasowego modelu.
W praktyce idea boostingu wykorzystywana jest w wielu różnych frameworkach (np. XGBoost, LightGBM, AdaBoost, Catboost, …), tutaj wykorzystamy natomiast pakiet gbm.
library(gbm)
class_gbm_model <- gbm(as.character(survived)~., distribution="bernoulli", data=titanic_class_train)
class_gbm_modelgbm(formula = as.character(survived) ~ ., distribution = "bernoulli",
data = titanic_class_train)
A gradient boosted model with bernoulli loss function.
100 iterations were performed.
There were 7 predictors of which 7 had non-zero influence.
regr_gbm_model <- gbm(m2.price~., distribution="gaussian", data=apartments_regr_train)
regr_gbm_modelgbm(formula = m2.price ~ ., distribution = "gaussian", data = apartments_regr_train)
A gradient boosted model with gaussian loss function.
100 iterations were performed.
There were 5 predictors of which 5 had non-zero influence.
Predykcja jest dokonywana przez agregację predykcji poszczególnych drzew.
Może być użyta zarówno do problemu regresji, jak i klasyfikacji.
Bardzo prosta idea: dla nowej obserwacji wyszukujemy \(k\) (predefiniowaną liczbę) najbliższych pod względem odległości obserwacji ze zbioru treningowego i wyznaczamy predykcję na ich podstawie:
dla klasyfikacji: prawdopodobieństwa klas to frakcja etykiet danej klasy wśród \(k\) znalezionych sąsiadów,
dla regresji: średnia z \(Y\) wśród \(k\) znalezionych sąsiadów.
Można wykorzystać właściwie dowolną miarę odległości – kluczowe jest skalowanie zmiennych.
W praktyce wykorzystamy pakiet e1071.
library(e1071)
class_knn_model <- gknn(survived~., data=titanic_class_train, scale=TRUE, k=5)
class_knn_model$x[1:2,] genderfemale gendermale age class2nd class3rd classdeck crew
1177 0 1 3.5962996 0 1 0
1098 0 1 -0.9246944 0 1 0
classengineering crew classrestaurant staff classvictualling crew
1177 0 0 0
1098 0 0 0
embarkedCherbourg embarkedQueenstown embarkedSouthampton fare
1177 0 0 1 -0.2961186
1098 1 0 0 -0.2986197
sibsp parch
1177 -0.3551151 -0.33145
1098 -0.3551151 -0.33145
regr_knn_model <- gknn(m2.price~., data=apartments_regr_train, scale=TRUE, k=5)
regr_knn_model$x[1:2,] construction.year surface floor no.rooms districtBemowo
1 -0.4576968 -1.599744 -0.9045215 -1.708393 0
2 1.0520871 1.515784 1.1645326 1.187188 0
districtBielany districtMokotow districtOchota districtPraga
1 0 0 0 0
2 1 0 0 0
districtSrodmiescie districtUrsus districtUrsynow districtWola
1 1 0 0 0
2 0 0 0 0
districtZoliborz
1 0
2 0
Predykcje:
5 7 23 24 28 34 38 44 48 50
1 0 1 1 0 0 0 0 1 1
Levels: 0 1
0 1
5 2 3
7 3 2
23 2 3
24 1 4
28 5 2
34 3 2
38 3 2
44 5 0
48 2 3
50 0 5
J. Gareth, D. Witten, T. Hastie, R. Tibshirani. An Introduction to Statistical Learning
T. Hastie, R. Tibshirani, J. Friedman. The Elements of Statistical Learning
P. Biecek, A. Kozak, A. Zawada. The Hitchhiker’s Guide to Responsible Machine Learning
PoweR 2023