Uvod
U ovom seminarskom radu istražujemo razvoj i karakteristike
balističkih raketa kroz analizu podataka prikupljenih iz ruskih i
kineskih sistema naoružanja.
Pri rešavanju zadatih problema, koristićemo podatke iz dosutpnog fajla rakete.ods. U zadatoj bazi podataka imamo sledeća obeležja raketa (čije je značenje jasno po samom nazivu):
- rusko ime - nepravo kategoričko nominalno (tekstualni podatak)
- NATO ime - kategoričko nominalno (tekstualni podatak)
- masa (u kilogramima) - numeričko kontinuirano
- dužina (u metrima) - numeričko kontinuirano
- kalibar (u metrima) - numeričko kontinuirano
- broj podglava - numeričko diskretno
- payload (korisni teret, u kilogramima) - numeričko kontinuirano
- tip goriva - kategoričko nominalno
- maksimalan domet - numeričko kontinuirano
- način lansiranja - kategoričko nominalno
- broj faza motora - numeričko diskretno
- klasa po dometu - kategoričko ordinalno
NAPOMENA:
rusko ime koristimo samo kao klasičan tekstualni podatak pa nam ovo nije
pravo kategoričko obeležje (u teoriji jeste, ali u praksi ga ne
koristimo praveći bilo kakve klase rakete), dok je NATO ime kodno te
preko njega možemo podeliti rakete na ruske i kineske (prefiks SS- za
ruske, a CSS- za kineske, biće detaljno objašnjeno u 6.
zadatku) pa stoga možemo reći da je u pitanju kategoričko
obeležje koje pored navedenog značaja koristimo kao i rusko tj. kao
klasičan tekstualni podatak
Zadate podatke koristićemo u cilju analiziranja kako pomenuta obeležja utiču jedna na druga, da li to ima ikakve veze sa zemljom porekla raketa ili pak njihovom starošću, da li možemo da uočimo nekakve značajne pravilnosti u podacima, podelimo ih ili predvidimo. Naš pristup će biti matematički u prvih 5 zadataka, dok ćemo se u 6. zadatku orijentisati na istorijski i politički pristup zadatoj bazi.
Prvi zadatak (učitavanje podataka)
Naš prvi zadatak je zapravo priprema podataka, tačnije učitavanje
istih u .ods formatu, te pretvaranje u data.frame.
Koristićemo paket “readODS” da ih učitamo, i funkciju
“read_ods” iz tog paketa da ih pretvorimo u data.frame.
Dajemo i primer naših podataka u obliku tabele prvih 5 opservacija.
#install.packages("readODS")
library(readODS)
library(knitr)
rakete_pocetna_baza = read_ods("C:\\Users\\sarac\\Downloads\\rakete.ods",
range = NULL, col_names = TRUE)
attach(rakete_pocetna_baza)
kable(head(rakete_pocetna_baza, n = 5),
format = "html",
table.attr = "style='font-size: 12px; width: 70%'"
)| rusko_ime | NATO_ime | masa_kg | duzina_m | kalibar_m | broj_podglava | payload_kg | tip_goriva | max_domet_km | nacin_lansiranja | broj_faza_motora | klasa_po_dometu |
|---|---|---|---|---|---|---|---|---|---|---|---|
| R-36M2 Voevoda | SS-18 Satan | 209600 | 32.2 | 3.05 | 10 | 8800 | Tecno | 16000 | Silos | 2 | L |
| UR-100N | SS-19 Stiletto | 105600 | 27.0 | 2.50 | 6 | 3355 | Tecno | 10000 | Silos | 2 | L |
| RT-2PM Topol | SS-25 Sickle | 45100 | 21.5 | 1.80 | 1 | 1000 | Cvrsto | 11000 | RM TEL | 3 | L |
| RT-2PM2 Topol-M | SS-27 Mod 1 | 47200 | 22.7 | 1.90 | 1 | 1200 | Cvrsto | 11000 | RM TEL | 3 | L |
| RS-24 Yars | SS-27 Mod 2 | 49600 | 23.0 | 2.00 | 3 | 1200 | Cvrsto | 12000 | RM TEL | 3 | L |
Priprema podataka za ostale zadatke
Za dalji rad (uključivaće pravljenje statističkih modela, razne podele podataka i funkcije primenjivane nad njima) biće nam potrebni unapred spremljeni podaci što podrazumeva:
- izbacujemo nerelevantna obeležja, u našem slučaju nazive raketa (rusko_ime i NATO_ime), koja nećemo koristiti pri matematički orijentisanom rešavanju problema u prvih 5 zadataka, ali svakako čuvamo početnu bazu sa svim podacima jer će nam biti potrebni pri istorijskoj analizi u 6. zadatku
- skaliramo numerička obeležja za slučaj da će biti potreno da pravimo neki model čije su performanse bolje nad skaliranim podacima (nije uvek neophodan korak, ali svakako nije na odmet)
- pretvaramo sva preostala kategorička obeležja u numerička, radi lakšeg rada sa podacima
rakete = rakete_pocetna_baza[, -c(1, 2)]
numerical_col = sapply(rakete, is.numeric)
rakete[numerical_col] = scale(rakete[numerical_col])
tip_goriva = factor(tip_goriva, levels = c("Cvrsto", "Tecno"), labels = c(1,2))
nacin_lansiranja = factor(nacin_lansiranja, levels = c("RM TEL", "Silos", "Mornaricki", "Avion"), labels = c(1,2,3,4))
klasa_po_dometu = factor (klasa_po_dometu, levels = c("S", "M", "L"), labels = c(1,2,3))
rakete$tip_goriva = as.numeric(tip_goriva)
rakete$nacin_lansiranja = as.numeric(nacin_lansiranja)
rakete$klasa_po_dometu = as.numeric(klasa_po_dometu)Pogledajmo kako sada izgleda naša baza na primeru prvih 5 opservacija.
| masa_kg | duzina_m | kalibar_m | broj_podglava | payload_kg | tip_goriva | max_domet_km | nacin_lansiranja | broj_faza_motora | klasa_po_dometu |
|---|---|---|---|---|---|---|---|---|---|
| 3.1509705 | 2.2001557 | 2.0859515 | 1.5184599 | 3.4927289 | 2 | 2.0273365 | 2 | 0.0916496 | 3 |
| 1.2170642 | 1.5490973 | 1.3247589 | 0.5363071 | 0.8048411 | 2 | 0.8804534 | 2 | 0.0916496 | 3 |
| 0.0920514 | 0.8604778 | 0.3559684 | -0.6913839 | -0.3576889 | 1 | 1.0716006 | 1 | 1.2830942 | 3 |
| 0.1311014 | 1.0107221 | 0.4943671 | -0.6913839 | -0.2589602 | 1 | 1.0716006 | 1 | 1.2830942 | 3 |
| 0.1757300 | 1.0482831 | 0.6327657 | -0.2003075 | -0.2589602 | 1 | 1.2627478 | 1 | 1.2830942 | 3 |
Drugi zadatak (popunjavanje NA vrednosti)
U drugom zadatku cilj nam je popuniti NA vrednosti. Prvo pogledajmo koja obeležja ih imaju i koliko ih je, kako bi se odlučili za najsmilseniji način popunjavanja istih.
| x | |
|---|---|
| masa_kg | 1 |
| duzina_m | 1 |
| kalibar_m | 0 |
| broj_podglava | 1 |
| payload_kg | 0 |
| tip_goriva | 0 |
| max_domet_km | 0 |
| nacin_lansiranja | 0 |
| broj_faza_motora | 0 |
| klasa_po_dometu | 0 |
Vidimo da NA vrednosti nema puno tj. postoji samo po jedna za
obeležja masa_kg, duzina_m, broj_podglava.
Ako pogledamo sve varijable koje imamo, intuitivno nam deluje kao da
su sve zavisne u manjoj ili većoj meri. Stoga ćemo ove vrednosti
popuniti na sledeći način: koristićemo funkciju “mice” iz
paketa “mice” koja popunjava ove vrednosti koristeći veze sa
ostalim dostupnim poljima svih varijabli i na taj način nam omogućava
realistično popunjavanje NA vrednosti.
NAPOMENA: sva
obeležja koja sadrže NA vrednosti su numerička i već skalirana u
pripremi podataka, ali nam ovo neće praviti problem pri imputaciji, samo
je važno napomenuti da ni rezultati neće biti u stvarnom opsegu
vrednosti.
- koristićemo se metodom PMM (Predicted Mean Matching) koja je dobra i najčešće korišćena za imputaciju numeričkih obeležja, ona unosi najbližu stvarnu vrednost predviđenoj za nedostajuće polje
- biramo prvu imputaciju (iako smo generisali 5 različitih setova imputacije postavljanjem m = 5), prosleđivanjem argumenta 1 funkciji “complete”, što je obično dovoljno dobro i reprezentativno
- prethodno ne znači da treba generisati jednu imputaciju samo jer jednu trenutno koristimo, jer bi to bilo kao da pretpostavljamo da postoji tačno jedan ispravan način za popunjavanje NA vrednosti, što nije dobro
#install.packages("mice")
library(mice)
imputacija = mice(rakete, m = 5, method = 'pmm', seed = 500)
rakete = complete(imputacija, 1) # biramo prvu imputacijuTreći zadatak (procena max dometa)
U trećem zadatku želimo da predvidimo maksimalan domet rakete (dakle to će biti naša ciljna promenljiva) na osnovu drugih prediktora, modelom po izboru. Nama su poznate vrednosti obeležja max_domet, ali praivćemo se da ih ne znamo, predvidićemo ih, a onda naše predviđene vrednosti uporediti sa stvarnim da bi videli da li smo uradili dobar posao.
Podela podataka
U procesu formiranja modela možemo koristiti sledeće skupove za podelu podataka:
1. Trening skup (na njemu se model “uči”)
2. Validacioni skup (međukorak za procenu performansi
modela na neviđenim podacima i podešavanje hiperparametara modela, nije
uvek neophodan)
3. Test skup (završna evaluacija
tj. procena performansi modela na neviđenim podacima)
Za pouzdaniju procenu performansi modela, umesto posebnog validacionog skupa može se koristiti kros-validacija (unakrsna validacija). Unakrsna validacija sa k grupa podrazumeva sledeće korake:
- Podaci se dele na k foldova
- Za svaki od k koraka, jedan fold se koristi kao test skup, a preostalih k-1 kao trening skup
- Prosečna performansa modela na k test skupova daje konačnu ocenu perfromansi (dakle ovime smo evaluirali naš model)
U ovom zadatku podatke ćemo deliti samo na trening i test skup (bez
validacionog). Razlog za ovo je jednostavan pristup problemu, nemamo
puno podataka a modeli koje planiramo da koristimo (SVM i linearna
regresija) su dovoljno jednostavni modeli da nam optimizacija
hiperparametara (koje SVM ima) nije neophodna.
Kako je baza mala, nećemo rizikovati potpuno nasumičnom podelom, već ćemo koristiti funkciju “createDataPartition” iz paketa “caret”. Ona podelu vrši nasumično ali obezbeđuje da se zadrži približno ista distribucija ciljne promenljive u oba skupa.
library(caret)
set.seed(123)
train_indexes = createDataPartition(rakete$max_domet_km, p = 0.7, list = F)
train_set = rakete[train_indexes, ]
test_set = rakete[-train_indexes, ]Sada kada imamo podelu na trening i test skup možemo se odlučiti za prvi model koji ćemo isprobati.
Linearna regresija
Prvo koristimo tradicionalni model linearne regresije, kao najjednostavniji pristup. On je često, pa tako i sada, prvi intuitivni izbor, kada su očigledne direktne (i po našoj proceni linearne) veze među obeležjima (npr. u našem slučaju max_domet_km ima jasnu direktnu vezu sa masom, dužinom itd.).
Sada nam je cilj da proverimo koliko je svaki od prediktora značajan za naš model i predviđanje ciljne promenljive max_domet. Proveru ćemo uraditi pregledom p-vrednosti za svaki od prediktora na trening skupu, jer p-vrednosti na test skupu nisu relevantne za treniranje modela niti za procenu značaja, a test skup zadržavamo kao potpuno neviđeni za validaciju performansi modela.
##
## Call:
## lm(formula = max_domet_km ~ ., data = train_set)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.49455 -0.09629 0.00787 0.16668 0.64291
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.27931 0.41787 -0.668 0.5123
## masa_kg -0.23053 0.50396 -0.457 0.6528
## duzina_m 0.24045 0.24668 0.975 0.3426
## kalibar_m 0.28976 0.28306 1.024 0.3196
## broj_podglava -0.02212 0.13764 -0.161 0.8741
## payload_kg 0.42993 0.34230 1.256 0.2252
## tip_goriva -0.37295 0.19439 -1.919 0.0710 .
## nacin_lansiranja -0.06711 0.07262 -0.924 0.3676
## broj_faza_motora 0.10651 0.11349 0.939 0.3604
## klasa_po_dometu 0.43676 0.17097 2.555 0.0199 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.3128 on 18 degrees of freedom
## Multiple R-squared: 0.9386, Adjusted R-squared: 0.9079
## F-statistic: 30.59 on 9 and 18 DF, p-value: 4.102e-09
## masa_kg duzina_m kalibar_m broj_podglava
## 68.790177 15.962495 20.337554 5.821232
## payload_kg tip_goriva nacin_lansiranja broj_faza_motora
## 41.464484 2.647774 1.501146 3.736724
## klasa_po_dometu
## 6.187825
Vidimo da su p-vrednosti većine prediktora visoke, osim za tip_goriva
i klasa_po_dometu, što bi moglo sugerisati da su ostali prediktori manje
značajni.
Međutim, s obzirom na to da smo uvideli jaku korelisanost među prediktorima (multikolinearnost) koristeći funkciju “vif” i uočavajući njene pretežno visoke vrednosti, zaključujemo da visoke p-vrednosti mogu biti rezultat te međusobne povezanosti. Model tada teško razlikuje doprinos pojedinačnih prediktora, što može „prigušiti“ njihove statističke značajnosti.
Zbog toga ćemo se voditi intuicijom, smatrajući da svaki od prediktora može imati, manji ili veći, značaj za predviđanje max_domet, te ćemo ih sve zadržati u modelu. Ovakav pristup ne bi trebalo da mnogo naškodi našem modelu te nećemo rizikovati izbacivanjem prediktora.
lm_model = lm(max_domet_km ~., data = train_set)
lm_predictions = predict(lm_model, newdata = test_set)
lm_predictions## 2 5 11 18 21 22 24
## 0.9705408 1.0155622 0.5995547 -1.0883919 0.3406234 -0.9769980 -1.1350237
## 30 31 33 35
## -0.4461397 -0.1986037 0.1945715 1.2633674
Vidimo da smo zaista dobili predikcije maksimalnog dometa za 15 raketa koje se nalaze u test skupu. Pre nego što se upustimo u razmatranje koliko su ove predikcije zaista dobre, pokušajmo da uradimo isto drugim, nešto kompleksnijim modelom: Random Forest
Random Forest
Naš drugi pristup biće Random Forest. On je skup velikog broja
stabala odlučivanja (decision trees) koje zajedno donose konacnu odluku.
Biramo ga jer je jak model koji može da uhvati složenije odnose, a
ipak nije komplikovan i ne zahteva nužno korišćenje validacionog
skupa.
library(randomForest)
rf_model = randomForest(max_domet_km ~ ., data = train_set, ntree = 100)
rf_predictions = predict(rf_model, newdata = test_set)
rf_predictions## 2 5 11 18 21 22
## 0.94888566 0.97089657 0.47360932 -0.98102477 0.50966764 -0.92652807
## 24 30 31 33 35
## -0.85104518 -0.76670817 -0.42529189 0.02011261 1.71177480
Poređenje modela
Sada kada imamo dva različita modela koja imaju isti cilj možemo ih uporediti nekim od metrika koje se često koriste za procenu i upoređivanje modela (MSE, MAE, RMSE, \(R^2\)). Bolji je onaj model koji ima manju grešku i/ili veći koeficijent determinacije.
rmse_rf = sqrt(mean((test_set$max_domet_km - rf_predictions)^2))
rmse_lm = sqrt(mean((test_set$max_domet_km - lm_predictions)^2))
print(c(rmse_rf, rmse_lm))## [1] 0.2692947 0.3245652
mae_rf = mean(abs(rf_predictions - test_set$max_domet_km))
mae_lm = mean(abs(lm_predictions - test_set$max_domet_km))
print(c(mae_rf, mae_lm))## [1] 0.2094464 0.2576249
mae_rf = mean(abs(test_set$max_domet_km - rf_predictions))
mae_lm = mean(abs(test_set$max_domet_km - lm_predictions))
print(c(mae_rf, mae_lm))## [1] 0.2094464 0.2576249
r2_rf = cor(test_set$max_domet_km, rf_predictions)^2
r2_lm = cor(test_set$max_domet_km, lm_predictions)^2
print(c(r2_rf, r2_lm))## [1] 0.9372715 0.8946334
Nešto bolji je Random Forest model, mada su oba sličnih i ujedno
dovoljno dobrih performansi.
Intuitivno ne uočavamo mnogo nelinearnih odnosa u podacima, pa je
zato linearna regresija bila dobar izbor koji je imao i solidne
performanse, jer je upravo za ovakve slučajeve i dizajnirana. Zato
Random Forest nema izraženu prednost (mada ona postoji) nad linearnom
regresijom. Tako je sasvim očekivano da su rezultati slični, a da je
Random Forest eventualno malo bolji zbog svoje fleksibilnosti.
Ove modele možemo predstaviti i grafički. Očekujemo isti zaključak.
par(mfrow = c(1, 2))
plot(test_set$max_domet_km, rf_predictions, main = "RF: Stvarne vs Predviđene",
xlab = "Stvarne vrednosti", ylab = "Predviđene vrednosti", col = "blue")
abline(0, 1, col = "red")
plot(test_set$max_domet_km, lm_predictions, main = "LM: Stvarne vs Predviđene",
xlab = "Stvarne vrednosti", ylab = "Predviđene vrednosti", col = "darkgreen")
abline(0, 1, col = "red")
Četvrti zadatak (klasifikator pokretnosti lansera)
U četvrtom zadatku naš cilj je, za početak, napraviti klasifikator
(model kojim se podaci svrstavaju u jednu od kategorija) koji pogađa da
li raketa ima pokretan ili fiksan lanser.
Nakon toga treba da uočimo overfitting ukoliko je do njega došlo i u
tom slučaju modifikujemo naš model.
Naše obeležje način_lansiranja uzima 4 vrednosti:
- Silos - podzemni objekti za skladištenje i lansiranje raketa (FIKSAN)
- RM TEL - mobilna platforma (vozilo) za transport i lansiranje raketa (POKRETAN)
- Avion - lanseri montirani na vojne avion, služe za lansiranje iz vazduha (POKRETAN)
- Mornarički - lanseri na ratnim brodovima ili podmornicama (FIKSAN i POKRETAN)
Mornarički tip lansera može biti i fiksan i pokretan u zavisnosti od toga da li se koristi u cilju lansiranja rakete sa broda/podmornice u pokretu, ili samo kako bi se došlo do udaljene, nekopnene tačke za stacionarno lansiranje. Kako bi olakšali dalji radi, opredelimo se za jedan, npr. fiksan tip mornaričkog lansera.
| Pokretan | Fiksan | |
|---|---|---|
| Silos | X | |
| RM TEL | X | |
| Avion | X | |
| Mornarički | X |
Klasifikacija može biti višeklasna ili binarna. Stoga postoje dva pristupa našem problemu:
- radićemo sa trenutim podacima (4 klase kategoričke, ciljne promenljive: način_lansiranja), pogađati koji od ova 4 načina lansiranja raketa ima, a onda takve predikcije dodeljivati pokretnim/fiksnim lanserima
- odmah pravimo novu kategoričku promenljivu (postaje naša ciljna promenljiva) koja je binarna (uzima 0 ako je lanser fiksan i 1 ako nije, dakle poznate su nam njene stvarne vrednosti) i pogađamo njene vrednosti, dakle direktno da li je lanser fiksan ili pokretan
Radi lakše implementacije, opredelićemo se za drugi pristup, binarnu klasifikaciju.
Ciljna promenljiva
Na gore objašnjen način prvo formiramo novo obeležje koje ćemo
nazvati tip_lansiranja i koje će ujedno biti i naša ciljna promenljiva.
Pretvorićemo ga u faktor kako bi njegovo značenje bilo očigledno.
Nakon toga pogledajmo našu bazu na primeru prvih 5 opservacija nakon
dodavanja kolone tip_lansiranja i naravno uklanjanja nacin_lansiranja s
obzirom da nam on više nije potreban (sve što želimo da znamo o
lanserima je trenutno to da li su fiksni ili pokretni, a znanje o tome
nam je sačuvano u varijabli tip_lansiranja). Ipak, sačuvaćemo ga u
jednoj promenljivoj kako bi kasnije mogli da vratimo bazu na staro.
rakete$tip_lansiranja = ifelse(rakete$nacin_lansiranja %in% c(2, 3), 0,
ifelse(rakete$nacin_lansiranja %in% c(1, 4), 1, 0))
nacin_lansiranja_kopija = rakete$nacin_lansiranja
rakete = rakete[ ,-8]
kable(head(rakete, n = 5),
format = "html",
table.attr = "style='font-size: 12px; width: 70%'")| masa_kg | duzina_m | kalibar_m | broj_podglava | payload_kg | tip_goriva | max_domet_km | broj_faza_motora | klasa_po_dometu | tip_lansiranja |
|---|---|---|---|---|---|---|---|---|---|
| 3.1509705 | 2.2001557 | 2.0859515 | 1.5184599 | 3.4927289 | 2 | 2.0273365 | 0.0916496 | 3 | 0 |
| 1.2170642 | 1.5490973 | 1.3247589 | 0.5363071 | 0.8048411 | 2 | 0.8804534 | 0.0916496 | 3 | 0 |
| 0.0920514 | 0.8604778 | 0.3559684 | -0.6913839 | -0.3576889 | 1 | 1.0716006 | 1.2830942 | 3 | 1 |
| 0.1311014 | 1.0107221 | 0.4943671 | -0.6913839 | -0.2589602 | 1 | 1.0716006 | 1.2830942 | 3 | 1 |
| 0.1757300 | 1.0482831 | 0.6327657 | -0.2003075 | -0.2589602 | 1 | 1.2627478 | 1.2830942 | 3 | 1 |
Sada kada smo odlučili kako ćemo pristupiti problemu i formirali našu novu ciljnu promenljivu, treba da podelimo naše podatke pre nego što počnemo pravljenje modela za koji se odlučimo.
Podela podataka
Za razliku od prošlog zadatka, ovde ćemo podatke podeliti u 3 skupa:
trening(50%), validacioni(20%) i test(30%). Za uvođenje još i
validacionog skupa opredeljujemo se jer nam on omogućava lakše
prepoznavanje overfittinga (što nam je ovde potrebno) kao i modifikaciju
modela u tom slučaju.
Takođe ćemo razdvojiti prediktore i ciljnu promenljivu radi lakše organizacije i fleksibilnosti, a i ukoliko dođe do overfittinga koristićemo funkciju “glmnet” za koju su nam potrebni ovako razdvojeni podaci.
validation_size = 0.2
test_size = 0.3
train_size = 0.5
set.seed(70)
train_val_idx = createDataPartition(rakete$tip_lansiranja, p = train_size + validation_size,
list = FALSE)
train_val_data = rakete[train_val_idx,]
test_data = rakete[-train_val_idx,]
x_train_val = train_val_data[, -10]
y_train_val = train_val_data[, 10]
x_test = test_data[, -10]
y_test = test_data[, 10]
set.seed(70)
train_idx = createDataPartition(train_val_data$tip_lansiranja,
p = train_size/(validation_size + train_size),
list = FALSE)
train_data = train_val_data[train_idx,]
val_data = train_val_data[-train_idx,]
x_train = train_data[,-10]
y_train = train_data[,10]
x_val = val_data[,-10]
y_val = val_data[,10]Logistička regresija
Sada konačno možemo preći na pravljenje modela za koji smo se
odlučili.
Logistička regresija je jedan od najjednostavnijih i najkorišćenijih klasifikacionih modela. Koristimo je kada je naša ciljna promenljiva kategoričkog tipa, kao u ovom slučaju.
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Da li je došlo do overfittinga?
Dobijamo upozorenja da je model predvideo verovatnoće jednake 0 ili
1. To znači da je model “previše siguran u svoje odluke” i ne prepoznaje
nijanse u podacima tj. previše je dobro naučio o podacima trening skupa.
Zato je ovo alarm da je verovatno došlo do overfittinga
(preprilagođavanja) - pojava da se nauče obrasci sa trening skupa, ali
se ne ispoljavaju dobro na novim podacima.
Proverimo na još neki način da li je ovo zaista slučaj.
Izračunaćemo tačnost modela na skupu podataka na kom je treniran (train_data) i na validacionom skupu koji nije još viđen (val_data). Koristićemo funkciju “mean”.
NAPOMENA:
nećemo trenirati model na trening + validacija skupu i gledati njegove
performanse na uniji treninga i validacije, pa na test skupu odvojeno da
bi uočili overfitting (što bismo verovatno uspeli i tim putem). U tom
slučaju bi koristili test skup da odlučimo koliko je naš model dobar ili
nije, a želimo da ga sačuvamo kao konačni, neviđeni skup podataka za
finalnu evaluaciju
train_pred = predict(logit_model, newdata = train_data, type = "response")
train_pred = as.numeric(train_pred > 0.5)
train_acc = mean(train_pred == train_data$tip_lansiranja)
train_acc## [1] 1
val_pred = predict(logit_model, newdata = val_data, type = "response")
val_pred = as.numeric(val_pred > 0.5)
val_acc = mean(val_pred == val_data$tip_lansiranja)
val_acc## [1] 0.75
Vidimo da je tačnost modela na skupu na kom je treniran baš 1. Iako
to na prvi pogled deluje logično (model savršeno predviđa podatke jer ih
je već video) to nam zapravo ne odgovara. Cilj nije bio da model
savršeno nauči podatke već da ih razume, da predvodi inteligencijom a ne
memorijom. Stoga je nama zapravo pouzdaniji onaj model koji ima tačnost
npr. 0.9 na skupu na kom je treniran a ne 1.
Dodatno, model je lošijih performansi na skupu neviđenih podataka tj.
na validacionom skupu i ovaj zaključak ide u prilog tezi da naš model
nije dubinski razumeo podatke i njihovu vezu već ih je samo naučio
napamet.
Konačno, možemo zaključiti da je došlo do overfittinga.
Zbog svega navedenog odlučujemo se da model poboljšamo koliko možemo. Rešavamo overfitting regularizacijom što često ujedno dovodi i do smanjenja overconfidence-a.
Regularizacija
Overfitting ćemo prebroditi regularizacijom - tehnikom za smanjenje složenosti modela. Postoje 3 tipa regularizacije (u zavisnosti od argumenta alpha):
- L1 (Lasso)
- L2 (Ridge) - mi ćemo je koristiti
- Elastic Net (kombinacija)
Važno je napomenuti da funkcija za pravljenje regularizovanog modela
prima kao argument parametar regularizacije lambda. Da bi dobili što
bolji model, ovaj parametar potrebno je optimizovati. Zato, umesto da
pristupimo rešavanju problema overfittinga funkcijom “glmnet” (trenira
model i primenjuje regularizaciju), primenićemo funkciju
“cv.glmnet” (dodatno uključuje i unakrsnu validaciju na uniji
trening skupa i validacionog skupa za pronalaženje najbolje vrednosti
parametra lambda).
Postupak:
- Unakrsna validacija i optimizacija lambda (na validacionom skupu) korišćenjem funkcije “cv.glmnet” (Napomena: prosleđujemo funkciji eksplicitno opseg iz kog da traži optimalno lambda, nazvali smo ga lambdas i generisali ga kao jedan od najpoznatijih, dovoljno širokih opsega za ovaj postupak)
- Pravimo finalni, regularizovani model sa najboljim lambda iz 1. dela
- Taj model treniramo (na uniji trening i validacionog skupa)
- Evaluiramo model (na test skupu)
library(glmnet)
lambdas = exp(seq(-10,3, length.out = 100))
regularized_model_cv_L2 = cv.glmnet(as.matrix(x_train_val), as.matrix(y_train_val),
family="binomial",
type.measure = "class",
standardize = FALSE,
nfolds = 10, lambda = lambdas)
plot(regularized_model_cv_L2)Na grafiku vidimo dve važne linije: prva nam predstavlja lambda za koje je greška (tj Binomial Deviance - metrika greške kod klasifikatora) najmanja, a druga nam predstavlja lambda sa 1 standardnom greškom.
Izabraćemo ono lambda koje minimizuje funkciju gubitka i napraviti i evaluirati naš finalni model na test skupu.
optimal_lambda = regularized_model_cv_L2$lambda.min
final_model_L2 = glmnet(x_train_val, y_train_val,
lambda = optimal_lambda, family = "binomial", standardize = FALSE)
y_pred_reg_test = predict(final_model_L2, s = optimal_lambda, newx = as.matrix(x_test), type = "class")
test_acc_reg = mean(y_pred_reg_test == y_test)
test_acc_reg ## [1] 0.6363636
y_pred_reg_val = predict(final_model_L2, s = optimal_lambda, newx = as.matrix(x_val), type = "class")
val_acc_reg = mean(y_pred_reg_val == y_val)
c(val_acc_reg, val_acc)## [1] 0.875 0.750
y_pred_reg_train = predict(final_model_L2, s = optimal_lambda, newx = as.matrix(x_train), type = "class")
train_acc_reg = mean(y_pred_reg_train == y_train)
c(train_acc_reg, train_acc)## [1] 0.95 1.00
Uspeli smo da smanjimo prilagođenost modela na skupu na kom je
treniran i time smatramo da smo uspešno izvršili regularizaciju.
Sada kada konačno imamo naš finalni, regularizovan model, možemo
pogledati šta “pogađa” koji tip lansera imaju naše rakete i to uporediti
sa stvarnim vrednostima.
Takođe, možemo pogledati i kakve su performanse našeg modela preko nekih osnovnih metrika.
library("caret")
conf_matrix = confusionMatrix(factor(test_data$tip_lansiranja), factor(y_pred_reg_test))
conf_matrix## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 3 3
## 1 1 4
##
## Accuracy : 0.6364
## 95% CI : (0.3079, 0.8907)
## No Information Rate : 0.6364
## P-Value [Acc > NIR] : 0.6322
##
## Kappa : 0.2903
##
## Mcnemar's Test P-Value : 0.6171
##
## Sensitivity : 0.7500
## Specificity : 0.5714
## Pos Pred Value : 0.5000
## Neg Pred Value : 0.8000
## Prevalence : 0.3636
## Detection Rate : 0.2727
## Detection Prevalence : 0.5455
## Balanced Accuracy : 0.6607
##
## 'Positive' Class : 0
##
library(pROC)
roc_curve = roc(as.numeric(test_data$tip_lansiranja), as.numeric(y_pred_reg_test))
plot(roc_curve, main = "Roc kriva", col = "red", lwd = 3)
auc_value = auc(roc_curve)
text(0, 0.4, paste("AUC = ", round(auc_value, 2)), col = "navy", cex = 1.5)Dodatak - unakrsna validacija
Kako tačnost na test skupu nakon evaluacije modela nije bila
zadovoljavajuća (0.6), odlučili smo da primenom unakrsne validacije
steknemo precizniji uvid u stvarne performanse naše regularizacije na
nevidjenim podacima. Pretpostavljamo da je relativno niska početna
tačnost mogla biti posledica malog broja opservacija u skupu podataka.
Koristićemo ugneždenu unakrsnu validaciju (nested cross-validation), pri čemu spoljašnja petlja svaki put izdvaja jedan fold kao test skup, dok se preostalih k - 1 folda koristi za trening i validaciju. Unutrašnja petlja zatim na tom trening + validacionom skupu izvodi unakrsnu validaciju pomoću funkcije “cv.glmnet”, kojom se bira optimalna vrednost regularizacionog parametra lambda. Performanse modela sa izabranim lambda na spoljašnjem test skupu beleže se u svakoj iteraciji, a najrealnija procena konačnih performansi dobija se kao prosečna tačnost na svim spoljašnjim test skupovima.
set.seed(123)
X = as.matrix(rakete[, -10]) # pretpostavljam da je 10. kolona ciljna
y = rakete[, 10]
k_outer = 10
outer_folds = createFolds(y, k = k_outer, list = TRUE)
lambdas = exp(seq(-10, 3, length.out = 100))
outer_accuracies = numeric(k_outer)
for (i in 1:k_outer) {
test_idx = outer_folds[[i]]
X_test = X[test_idx, ]
y_test = y[test_idx]
X_train_val = X[-test_idx, ]
y_train_val = y[-test_idx]
cv_model = cv.glmnet(X_train_val, y_train_val,
family = "binomial",
type.measure = "class",
standardize = FALSE,
nfolds =10,
lambda = lambdas,
alpha = 0)
best_lambda = cv_model$lambda.min
final_model = glmnet(X_train_val, y_train_val,
family = "binomial",
lambda = best_lambda,
alpha = 0,
standardize = FALSE)
preds_test = predict(final_model, newx = X_test, type = "class", s = best_lambda)
outer_accuracies[i] = mean(preds_test == y_test)
}
mean(outer_accuracies)## [1] 0.775
Sada vidimo da je naša regularizacija bila uspešna - ne samo da smo prebrodili overfitting već model dobijen istom ima sasvim solidne performanse.
NAPOMENA: još
jednom naglašavamo da je unakrsna validacija korišćena isključivo za
dobijanje realnije procene performansi regularizovanog modela, uz
maksimalno iskorišćenje dostupnih opservacija. Ona nije služila za
dobijanje konačnog modela — u praktičnoj primeni koristili bismo
inicijalni model sa nešto nižom tačnošću. Međutim, unakrsna validacija
nam je pokazala da je zbog ograničenog broja opservacija prvobitno
izmerena tačnost bila „lažno niska“
NAPOMENA:
nećemo se sada detaljno baviti metrikama dobijenim unakrsnom
validacijom, dovoljno nam je bilo da znamo da je naš model
zadovoljavajući (ako bi hteli morali bi u svakoj iteraciji unutrašnje
petlje zabeležiti i sve metrike dobijene na trenutnom test skupu,
izračunati i njihovu srednju vrednost, pa prikazati takve)
Sada ćemo izbaciti prediktor koji smo sami pravili, tip_lansiranja i vratiti prediktor način_lansiranja. Vraćamo bazu na staro jer nam nije više potrebna modifikovana pri rešavanju novih problema.
rakete = subset(rakete, select = -c(tip_lansiranja))
rakete$nacin_lansiranja = nacin_lansiranja_kopijaPeti zadatak(klasterizacija)
U petom zdatku treba izbaciti sve rakete srednjeg dometa kao i prediktore max_domet i klasa_po_dometu, a zatim bez ikakvog znanja o daljem dometu raketa (sem što znamo da sada imamo samo one kratkog i dugog dometa) izvršiti klasterizaciju po istom.
Izbacivanje raketa srednjeg dometa i prediktora max_domet i klasa_po_dometu
Prediktor klasa_po_dometu uzima vrednosti S, M i L (koje smo mi
kodirali redom u 1, 2 i 3). Pretpostavka je da ove vrednosti zapravo
predstavljaju Small, Medium i Large domet, te da je dovoljno izbaciti iz
baze rakete čije obeležje klasa_po_dometu uzima vrednost M (2 kod nas).
Ipak, kako nemamo jasno utvrđen smisao ovog obeležja, možemo proveriti
da li smo u pravu.
Proveravamo da li postoje jasne kategorije podataka tj. da li zaista sve rakete klase S imaju mali max_domet, one klase M osrednji i one klase L najveći. Koristićemo se “anova” testom za proveru.
- H0: Nema razlike u srednjim vrednostima zavisne varijable (max_domet) između grupa.
- H1: Postoji barem jedna grupa sa srednjom vrednošću koja se značajno razlikuje.
## Df Sum Sq Mean Sq F value Pr(>F)
## as.factor(klasa_po_dometu) 2 28.621 14.311 54.93 1.15e-11 ***
## Residuals 36 9.379 0.261
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Kako je p-vrednost izuzetno mala, to odbacujemo nultu hipotezu i
mozemo zaključiti da je mala verovatnoća da su razlike u srednjim
vrednostima zavisne varijable (max_domet) između grupa nezavisne
varijable (L, S, M) nastale slučajno.
Pogledajmo kako ovo izgleda i grafički.
boxplot(max_domet_km ~ klasa_po_dometu, data = rakete,
main = "Distribucija max_domet_km po klasa_po_dometu",
xlab = "Klasa po dometu", ylab = "Max domet", col = "red")Odavde se jasno vidi da SKORO sve rakete klase S imaju maksimalan
domet veoma mali, one klase M srednji, a one klase L najveći. Vizuelno
deluje kao da postoje neke opservacije koje mogu poremetiti naš
zaključak o značenju klasa obeležja klasa_po_dometu (postoji preplitanje
max_domet -a klase M i L iz klasa_po_dometu).
Proverimo ovo numerički.
opseg_S = c(min(subset(rakete, klasa_po_dometu == 1)$max_domet), max(subset(rakete, klasa_po_dometu == 1)$max_domet))
opseg_M = c(min(subset(rakete, klasa_po_dometu == 2)$max_domet), max(subset(rakete, klasa_po_dometu == 2)$max_domet))
opseg_L = c(min(subset(rakete, klasa_po_dometu == 3)$max_domet), max(subset(rakete, klasa_po_dometu == 3)$max_domet))
opseg_S## [1] -1.0176382 -0.9354449
## [1] -0.91633023 -0.07528259
## [1] -0.1708562 2.4096309
## [1] -0.17085618 0.07763517 0.21143820 0.21143820 0.49815899 0.55550314
## [7] 0.55550314 0.55550314 0.68930618 0.88045337 1.07160056 1.07160056
## [13] 1.20540359 1.26274775 1.45389494 1.83618933 2.02733652 2.40963090
NAPOMENA:
prisetimo se da su ove vrednosti skalirane, mada nam to ne pravi problem
pri zaključivanju jer nam nisu važne realne brojke o kojima pričamo, već
samo opseg svake klase i njihovo preplitanje.
Dakle, pravi nam problem što postoji jedna raketa klase L ciji je
max_domet -0.17085618 (što ulazi u opseg za klasu M, jer -0.17085618
< -0.07528259 što je gornja granica opsega klase M).
Kako postoji samo jedno odstupanje od pravila koje smo uvideli,
uvažićemo ga kao ispravno i zaključiti da su rakete srednjeg dometa one
koje pripadaju klasi “M” obeležja klasa_po_dometu.
Konačno, upravo ovakve rakete izbacujemo, a nakon toga i prediktore klasa_po_dometu i max_domet.
indeksi_klasa_M = which(klasa_po_dometu == 2)
rakete_izbaceno = rakete[-indeksi_klasa_M, ]
rakete_izbaceno = subset(rakete_izbaceno, select = -c(max_domet_km, klasa_po_dometu))Dalje je plan je da podacima pristupimo na 2 načina tj. da se koristimo sa 2 tipa klasterizacije. Nakon toga ćemo ih uporediti kako bi zaključili nešto više o pravom pristupu podacima i opredelili se za bolju klasterizaciju.
K - means klasterizacija
1. Odabir broja klastera K
Prvo, treba se oprediliti za broj klastera prema kom ćemo izvršiti
podelu. Naš cilj je da uočimo pravilnost u dobijenim grupama, te da
zaključimo koji klaster/klasteri predstavljaju rakete kratkog, a koji
dugog dometa.
NAPOMENA:
Zahtev za klasterizaciju prema dometu koji uzima 2 vrednosti (kratki i
dugi) ne znači nužno da su nam potrebna samo 2 klastera. Moguće je da se
desi da je najoptimalniji broj klastera veći i da je deo dobijenih
klastera jedna “celina” koja predstavlja rakete kratkog, a drugi deo
dugog dometa. Do ovoga može doći usled specifičnosti podataka ili nekih
podgrupa koje postoje unutar raketa jednog određenog dometa.
Postoji nekoliko metoda za odabir broja K:
- Elbow metoda
Y - osa: suma kvadrata unutar klastera - “WCSS” (pokazuje koliko su podaci kompaktni unutar klastera za svako K)
Odabir K: Mesto gde se kriva “lomi” (smanjenje WCSS se usporava) tj. dodavanje više klastera prestaje da značajno smanjuje WCSS - Silhouette analiza
Y - osa: Silhouette koeficijent koji ocenjuje koliko su klasteri kompaktni i razdvojeni
Odabir K: što veći koeficijent, ako je više K sličnih vrednosti koeficijenata, izabrati najmanje radi jednostavnosti - Gap Statistic
Y - osa: Gap vrednost (razlika između WCSS za stvarne i nasumične podatke), pokazuje značajnost klasterizacije
Odabir K: Prvo K za koje vrednost naglo poraste i postaje stabilna
2. K - means algoritam
Najbolji broj klastera K biraćemo jednom od navedenih metoda pa neka
to bude i najpoznatija - Elbow metoda. Koristićemo se sa dva algoritma
klasterizacije i odabrati nama intuitivniji. Za početak će to biti K -
means algoritam.
K - means algoritam funkcioniše tako što se prvo (nasumično) odabere K centara klastera (“centroida”). Zatim se svaka opservacija dodeljuje klasteru čijoj je centroidi najbliža. Tada se računa centar novodobijenog klastera i u njega se premešta centroida.
library(factoextra)
library(cluster)
library(gridExtra)
elbow_plot = fviz_nbclust(rakete_izbaceno, kmeans, method = "wss") +
ggtitle("Elbow metoda - Odabir K")
elbow_plotZa K = 3 kriva se “lomi” te je to lakat koji smo tražili. Za K = 8
dolazi do blagog skoka, ali ovo nije ništa značajno što može da poremeti
naš zaključak već je najverovatnije samo posledica specifičnosti nekog
podatka.
set.seed(42)
final_model_cluster_kmeans = kmeans(rakete_izbaceno, centers = 3, nstart = 10)
# dodeljeni klasteri svakoj opservaciji
clusters_kmeans = final_model_cluster_kmeans$cluster
clusters_kmeans## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 18 20 22 23 24 27 28 29 34 35
## 3 2 2 2 2 2 2 2 2 2 2 3 2 1 1 1 1 2 1 1 1 2 1 1 2 3
## 36 38 39
## 2 2 1
## clusters_kmeans
## 1 2 3
## 10 16 3
Hierarchical Clustering
Hijerarhijsko klasterovanje je dobar alat za proveru i dodatno razumevanje strukture podataka. Gradi se “stablo” klastera (dendogram). Svaka iteracija počinje kao zaseban klaster, a zatim se iterativno spajaju najbliži klasteri dok ne ostane jedan najveći. Ovo možemo prikazati i vizuelno.
hc_result = hclust(dist(rakete_izbaceno), method = "ward.D2")
plot(hc_result, main = "Dendrogram", sub = "", xlab = "Opservacije", ylab = "Udaljenost")
Sada treba izabrati broj klastera K. Vizuelno uočavamo da postoje tri jasne grupe na dendogramu (tri jasne “grane”) pa pogledajmo kako izgleda dendogram ako bi broj klastera bio isti kao za K - means tj. K = 3. Na grafiku crvene granice pedstavljaju tri krajnja klastera koja smo zapravo i tražili. Nakon vizuelizacije, možemo se poslužiti i “elbow” metodom radi provere.
plot(hc_result, main = "Dendrogram", sub = "", xlab = "Opservacije", ylab = "Udaljenost")
rect.hclust(hc_result, k = 3, border = "darkred")“Elbow” metoda nam potvrđuje da je naš vizuelni zaključak bio
ispravan jer je lakat ponovo na K = 3.
Kao i kod K - means metode, vršimo ponovo podelu na 3 klastera i možemo pogledati koja opservacija pripada kom.
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 18 20 22 23 24 27 28 29 34 35
## 1 1 2 2 2 2 2 2 2 2 2 1 2 2 3 3 3 2 3 3 3 2 3 3 2 1
## 36 38 39
## 2 2 3
## clusters_hc
## 1 2 3
## 4 16 9
Poređenje klasterizacija
Sada kada znamo kom klasteru je dodeljena koja opservacija putem K -
means i Hierarchical algoritma, možemo uporediti rezultate.
Pošto algoritmi klasterizacije nemaju “pravu” ciljnu varijablu ne možemo koristiti standardne metrike. Umesto toga koristićemo funkciju “silhouette” koja računa Silhouette score koji nam pokazuje koliko su objekti unutar klastera slični, a različiti od objekata u drugim klasterima. Ova mera uzima vrednosti od -1 do 1. Rezultat blizu 1 označava da je tačka dobro uklopljena u svoj klaster, rezultat blizu 0 da je na granici i rezultat blizu -1 da je tačka loše pozicionirana. Nakon što izračunamo srednju vrednost svih rezultata dobijamo procenu.
library(cluster)
sil_kmeans <- silhouette(clusters_kmeans, dist(rakete_izbaceno))
sil_hclust <- silhouette(clusters_hc, dist(rakete_izbaceno))
mean(sil_kmeans[, 3])## [1] 0.4547102
## [1] 0.4446867
Vidimo da su obe klasterizacije bile uspešne (rezultati oko 0.5 su solidno dobri), kao i da su gotovo jednako dobre. Prema ličnoj preferenci i boljem intuitivnom shvatanju izabraću Hijerarhijsko klasterovanje za dalji rad.
Da li možemo da zaključimo nešto o podeli raketa na kratak i dug domet?
Jasno, nama ništa ne garantuje da smo uspeli da dođemo do bilo kakve jasne podele na rakete prema njihovom dometu, koji je sve vreme nepoznat. Klasterizacija se koristila sličnostima i razlikama između raketa prema njihovim ostalim, poznatim obeležjima. Ono što mi intuitivno znamo jeste da ostala obeležja utiču i na domet raketa. Npr. ukoliko uvidimo jasno da su u nekim od 3 klastera koja imamo isključivo rakete slične po masi i dužini, recimo velike mase i dužine, to će nam omogućiti da neformalno zaključimo da upravo ovi klasteri predstavljaju ujedno i rakete dugog dometa (jer nam je jasno da baš takve rakete imaju dug domet). Na taj način nam homogenost klastera prema ostalim karakteristikama kao i njihova međusobna različitost omogućuju da zaključimo da svaki klaster predstavlja jedan “tip” raketa i da se najverovatnije ti tipovi odnose i na njihov domet (kratak ili dug, u zavisnost od vrednosti ostalih obeležja povezanih sa dometom).
Kako poznata obeležja utiču na domet (intuitivno shvatanje):
- masa - veće (teže) rakete imaju veći domet jer mogu nositi jače motore i više goriva (ZNAČAJAN UTICAJ)
- dužina - kao i za masu, duže rakete imaju veći domet (ZNAČAJAN UTICAJ)
- kalibar - povezan je sa veličinom motora, pa veći kalibar znači i veći domet (ZNAČAJAN UTICAJ)
- tip goriva - rakete sa tečnim gorivom imaju veći domet jer su efikasnije u korišćenju energije (SREDNJI UTICAJ)
- način lansiranja - nije presudan (MALI UTICAJ)
- payload (korisni teret) - veća težina tereta smanjuje kapacitet za gorivo pa smanjuje domet (SREDNJI UTICAJ)
- broj podlgava - rakete sa više glava imaju veću stabilnost pa i domet (SREDNJI UTICAJ)
- broj faza motora - višefazni motori povećavaju brzinu i efikasnost
raketa pa i domet (SREDNJI UTICAJ)
Sada ćemo grafički prikazati rakete po 2 atributa, kako bi uvideli
pravilnost u podeli na klastere i zaključili nešto više o
dometu.
NAPOMENA: Prikazaćemo i legendu koja nam pokazuje koji od gore dodeljenih brojeva klastera (1,2,3) označava koju boju. Radi jednostavnosti koda uradićemo to samo prvi put, znajući da isto važi i za svaki naredni grafik.
clusters_hc_colors = as.factor(clusters_hc)
plot(rakete_izbaceno$masa_kg, rakete_izbaceno$duzina_m,
pch = 16, cex = 1.5, col = clusters_hc_colors)
colors_assigned = levels(clusters_hc_colors)
legend(x = 2.3, y = 0.3,
legend = paste("Klaster", 1:length(colors_assigned)),
fill = colors_assigned,
title = "Klasteri",
xpd = TRUE)Svaka boja predstavlja jedan klaster (u legendi vidimo i koja koji), a svaka tačka jednu opservaciju. Vidimo da su opservacije prilično pravilno raspodeljene po klasterima u smislu njihove mase i dužine - rakete najmanje mase i dužine čine jedan klaster i tako redom.
Setimo se da na prethodnom grafiku, tačke crne boje bile su rakete
velike masei dužine. Sada vidimo da isti klaster (zovimo ga “Crni
klaster”) karakteriše i čvrsto gorivo (omogućava veći domet). Možemo
zaključiti da Crni klaster predstavlja rakete dugog dometa. Što se tiče
druga dva klastera (“Zeleni klaster” i “Roze klaster”) ne oučavamo jasnu
pravilnost prema tipu goriva (algoritam ih je “sekao” prema masi).
Zato pogledajmo još jedan grafik klasterizacije prema 2 atributa.
Još jednom potvrđujemo da Crni klaster predstavlja rakete dugog dometa (rakete velike mase, dužine, kalibra i sa čvrstim gorivom). Vidimo da Zeleni klaster karakterišu rakete male mase, dužine i kalibra (setimo se da za tip goriva nismo uvideli jasnu pravilnost), te možemo zaključiti da on predstavlja rakete kratkog dometa.
Šta da zaključimo o Roze klasteru?
U ovom klasteru nalaze se
rakete osrednje mase, dužine i kalibra (o tipu goriva nismo zaključili
ništa) od raketa koje su ostale u bazi nakon izbacivanja raketa srednjeg
dometa.
Za početak pogledajmo koje su to rakete u Roze klasteru (prisetimo se legende sa prvog grafika: Roze klaster je zapravo klaster br. 2).
#koristimo početnu bazu jer smo samo u njoj sačuvali nazive raketa
rakete_izbaceno_pocetna_baza = rakete_pocetna_baza[!rakete_pocetna_baza$klasa_po_dometu == "M", ]
rakete_izbaceno_pocetna_baza[clusters_hc == 2, "NATO_ime"]## # A tibble: 16 × 1
## NATO_ime
## <chr>
## 1 SS-25 Sickle
## 2 SS-27 Mod 1
## 3 SS-27 Mod 2
## 4 SS-N-18 Stingray Mod 1
## 5 SS-N-18 Stingray Mod 2
## 6 SS-N-18 Stingray Mod 3
## 7 SS-N-23 Sineva Mod 2
## 8 SS-N-23 Liner
## 9 SS-N-32
## 10 SS-N-27A Sizzler
## 11 SS-N-27B Sizzler
## 12 SS-X-31 Frontier
## 13 SS-N-17 Snipe
## 14 CSS-10
## 15 CSS-N-14
## 16 CSS-20
Vidimo da su nam u Roze klasteru pretežno sovjetske/ruske rakete (čak
11 njih, a svega 3 kineske). Stoga možemo zaključiti da nam i Roze
klaster, kao i Crni, predstavlja rakete dugog dometa, a Zeleni rakete
kratkog dometa.
NAPOMENA:
prethodni zaključak biće detaljno objašnjen pred kraj 6.
zadatka, kada se detaljnije uputimo u istoriju razvoja raketa i
opravdamo zaključak da je klaster pretežno sovjetskih raketa
najverovatnije klaster raketa dugog dometa
Takođe, u cilju donošenja (neformalnog) zaključka, možemo pogledati i
masu raketa u Roze klasteru.
Dizajn raketa i njihov domet zavise od mnogo faktora, ali masa je definitivno veoma bitno obeležje koje utiče na domet raketa. Prema nezvaničnim podacima (dolazi često do preklapanja brojki, ali može se uhvatiti neka pravilnost) rakete dugog dometa obično teže između 30000 i 120000 kilograma, dok su one kratkog dometa između 1000 i 8000 kilograma.
## # A tibble: 16 × 1
## masa_kg
## <dbl>
## 1 45100
## 2 47200
## 3 49600
## 4 35300
## 5 35300
## 6 38000
## 7 40300
## 8 40300
## 9 36800
## 10 1920
## 11 1570
## 12 36000
## 13 26900
## 14 42000
## 15 42000
## 16 80000
Vidimo da gotovo sve rakete iz Roze klastera teže preko 30000 kg. Ovaj podatak nam ide u prilog prethodnom zaključku o tome da se u ovom klasteru nalaze rakete dugog dometa. Naravno, dolazi do nekih odstupanja, ali to smo mogli i očekivati s obzirom na to smo znali da ovakva klasterizacija ne može savršeno podeliti rakete prema njihovom dometu kao nepoznatom obeležju.
Šesti zadatak
U poslednjem zadatku cilj nam je da razumemo naše podatke i pojmove
koji se korite u njima na još neki način pored matematičkog. Primarni
pristup biće istorijski i politički. Posebno ćemo, kako zadatak i
nalaže, istražiti koliko se često javljaju rakete srednjeg dometa, koje
su one starosti, kome pripadaju i zašto.
Prisetimo se kako je izgledala naša baza na samom početku (na primeru prvih 5 opservacija).
kable(head(rakete_pocetna_baza, n = 5),
format = "html",
table.attr = "style='font-size: 12px; width: 70%'"
)| rusko_ime | NATO_ime | masa_kg | duzina_m | kalibar_m | broj_podglava | payload_kg | tip_goriva | max_domet_km | nacin_lansiranja | broj_faza_motora | klasa_po_dometu |
|---|---|---|---|---|---|---|---|---|---|---|---|
| R-36M2 Voevoda | SS-18 Satan | 209600 | 32.2 | 3.05 | 10 | 8800 | Tecno | 16000 | Silos | 2 | L |
| UR-100N | SS-19 Stiletto | 105600 | 27.0 | 2.50 | 6 | 3355 | Tecno | 10000 | Silos | 2 | L |
| RT-2PM Topol | SS-25 Sickle | 45100 | 21.5 | 1.80 | 1 | 1000 | Cvrsto | 11000 | RM TEL | 3 | L |
| RT-2PM2 Topol-M | SS-27 Mod 1 | 47200 | 22.7 | 1.90 | 1 | 1200 | Cvrsto | 11000 | RM TEL | 3 | L |
| RS-24 Yars | SS-27 Mod 2 | 49600 | 23.0 | 2.00 | 3 | 1200 | Cvrsto | 12000 | RM TEL | 3 | L |
Zemlje porekla raketa srednjeg dometa, godine proizvodnje
Dakle naš rad se bazirao ne raketama kojima znamo rusko ime i NATO ime. Međutim, nemamo eksplicitno naveden podatak u vlasništvu koje sile je svaka od njih. Ono što možemo da uradimo je da pogledamo NATO imena naših raketa.
| NATO_ime |
|---|
| SS-18 Satan |
| SS-19 Stiletto |
| SS-25 Sickle |
| SS-27 Mod 1 |
| SS-27 Mod 2 |
| SS-N-18 Stingray Mod 1 |
| SS-N-18 Stingray Mod 2 |
| SS-N-18 Stingray Mod 3 |
| SS-N-23 Sineva Mod 2 |
| SS-N-23 Liner |
| SS-N-32 |
| SS-X-30 Satan 2 |
| SS-N-27A Sizzler |
| SS-N-27B Sizzler |
| SS-26 Stone |
| Dagger |
| SS-21 Scarab A |
| SS-21 Scarab B |
| SS-N-6 Serb |
| SS-X-31 Frontier |
| SS-20 Saber |
| SS-23 Spider |
| SS-1B Scud A |
| SS-1C Scud B |
| SS-1D Scud C |
| SS-1E Scud D |
| SS-N-17 Snipe |
| CSS-7 |
| CSS-X-15 |
| CSS-6 |
| CSS-11 |
| CSS-5 |
| Guam Killer (unofficial) |
| CSS-10 |
| CSS-4 |
| CSS-N-14 |
| CSS-5 |
| CSS-20 |
| CSS-? |
Zašto gledamo baš NATO ime raketa?
Možemo uočiti da su NATO imena
raketa kodna, tačnije sva imena počinju ili prefiksom SS- ili CSS-
(izuzev dve rakete koje ćemo istražiti odvojeno a to su, prema NATO
imenu, “Dagger” i “Guam Killer”).
NATO je sovjetske/ruske rakete
imenovao prefiksom SS- (Surface-to-Surface), a kineske sa CSS- (Chinese
Surface-to-Surface). Postojali su i drugi prefeksi NATO kodnog
imenovanja, ali nam trenutno nisu od ključnog značaja. Izuzeci u NATO
imenovanju su određene rakete čiji nazivi označavaju neke njihove
posebne karakteristke ili su jednostavno nastala zbog medijskog
interesa. Tako, imamo rusku raketu “Dagger” (jedna od najmodernijih
oružja ruske vojske) i kinesku “Guam Killer” (poznata po dometu taman
dovoljnom za gađanje američke baze na ostrvu Guam).
Prvo, dodajmo u našu bazu podataka još jedno obeležje koje će nam
pomoći u daljem radu, a to je sila kojoj svaka raketa pripada (to ćemo
uraditi prema gore objašnjenim NATO kodnim imenima). Ostavljamo prostora
da postoje neke rakete pored “Dagger” i “Guam Killer” koje ne počinju sa
SS- ili CSS-, a nismo ih uvideli (dodavanjem difoltne vrednosti
“Nepoznato”).
Zatim, želimo da vidimo koliko ima ruskih, a koliko
kineskih, raketa srednjeg dometa (one koje za klasa_po_dometu uzimaju
vrednost “M”, što znamo iz prethodnog zadatka) i koje su to rakete, kako
bi istražili kada su pravljene.
library(dplyr)
rakete_pocetna_baza = rakete_pocetna_baza %>%
mutate(
zemlja = case_when(
grepl("^SS-", NATO_ime) ~ "Rusija",
grepl("^CSS-", NATO_ime) ~ "Kina",
NATO_ime == "Dagger" ~ "Rusija",
NATO_ime == "Guam Killer (unofficial)" ~ "Kina",
TRUE ~ "Nepoznato"
)
)
rakete_rusija_m = rakete_pocetna_baza %>%
filter(zemlja == "Rusija", klasa_po_dometu == "M")
rakete_kina_m = rakete_pocetna_baza %>%
filter(zemlja == "Kina", klasa_po_dometu == "M")
kable(rakete_rusija_m$NATO_ime,
format = "html",
table.attr = "style='font-size: 12px; width: 70%'"
)| x |
|---|
| Dagger |
| SS-N-6 Serb |
| SS-20 Saber |
| SS-1D Scud C |
| SS-1E Scud D |
| x |
|---|
| CSS-6 |
| CSS-11 |
| CSS-5 |
| Guam Killer (unofficial) |
| CSS-5 |
Pogledajmo sada kada su proizvedene sve ove rakete.
NAPOMENA: Imamo dve kineske rakete CSS-5 ali one nisu iste već je samo njihovo NATO ime isto (to su rakete Dong Feng 21 i Dong Feng 4, redom, prema ruskom imenu)
1. Dagger (R) - 2018
2. SS-N-6 Serb
(R) - 1960-1961
3. SS-20 Saber (R) -
1976-1979
4. SS-1D Scud C (R) - 1970-1972
5. SS-1E Scud D (R) - 1976-1979
6. CSS-6 (K) - 1990
7. CSS-11
(K) - 1970 - 1971
8. CSS-5 (K) - 1985
9. Guam Killer (K) - 2010 - 2015 (nezvanično)
10. CSS-5 (K) - 1975
Vidimo da ne postoji nijedna ruska raketa srednjeg dometa proizvedena u periodu 1988-2018. Ovo nije slučaj kod kineskih raketa. Razmotrimo zašto.
Reagan - Gorbačev (INF sporazum)
1970-ih godina Sovjetski Savez je stavio u upotrebu raketu SS-20
Saber koju smo već pominjali. Ovo je bila raketa srednjeg dometa koja je
lako mogla da pogodi Zapadnu Evropu sa sovjetskih teritorija, što je
predstavljalo veliki izazov za NATO jer je povećalo nuklearnu
pretnju.
Zato je NATO, pod uticajem Nemačke, doneo 1979. “Double Track
Decision” što uključuje:
- uklanjaje 1000 nuklearnih bojevih glava (ekplozivni uređaj na vrhu rakete zadužen za uništenje na cilju) iz Evrope
- ako pregovori sa Sovjetskim Savezom ne uspeju, modernizacija i povećanje nuklearnih snaga NATO-a
Upravo je ova strategija i zabrinutost zbog velikog sovjetskog i
kineskog nuklearnog rasta bila preduslov za INF sporazum potpisan 1987.
između SAD-a (Ronald Reagan) i Sovjetskog Saveza (Mikhail Gorbachev).
Sporazum je strogo zabranio proivodnju i raspoređivanje raketa
srednjeg dometa, čime su SAD i Sovjetski Savez ukinuli ovu pretnju i
smanjili mogućnost nuklearnog sukoba u Evropi. Bio je značajan korak ka
smanjenju napetosti u Hladnom ratu i stabilizaciji evropske sigurnosti,
kao i smanjenju nuklearne opasnosti u svetu. Time je Sovjetski Savez bio
u obavezi da sve postojeće rakete srednjeg dometa uništi i da ne
proizvodi nove.
Za razliku od njih, Kina nije bila potpisnica i time nije bila deo ovog sporazuma. Nije bila ni u kakvoj obavezi. Kina je za to vreme usavršavala i povećala proivodnju raketa srednjeg dometa i tako stekla stratešku prednost, pogotovo u kontekstu njenog rastućeg uticaja u Aziji.
Bolton - Šoigu (sastanak 2018.)
Uprkos svim dogovorima, već od 2014. godine SAD optužuju Rusiju da
krši INF sporazum. Naime, sporazum je zabranjivao isključivo kopnene
balističke i krstareće rakete srednjeg dometa i nije se odnosio na
vazdušne i pomorske. Prema našim podacima to bi značilo da su zabranjene
sve rakete srednjeg dometa koje za tip_lansiranja uzimaju “Silos” ili
“RM TEL”, ali ne i one koje uzimaju “Mornarički” i “Avion”. Na taj način
Rusija je uspevala da doskoči INF sporazumu tako što je ipak proizvodila
rakete srednjeg dometa koje su predstavljale pretnju, ali bez formalnog
kršenja sporazuma.
Npr. raketa “Dagger” o kojoj je već bilo reči je prvi put javno
predstavljena u martu 2018. ali je već krajem 2017. godine ušla u
operativnu upotrebu. U pitanju je raketa srednjeg dometa koja bi bila
zabranjena sporazumom da je lansirana sa kopna, ali je Rusija formalno
kršenje istog preduhitrila tako što je “Dagger” raketa lanisirana sa
aviona te nije pod INF zabranom.
Pored ovakvih doskočica Rusije postojao i konkretan problem, a to je
bila SSC-8 (surface-to-Surface Cruise) raketa koju nemamo u bazi. U
pitanju je kopnena krstareća raketa koja bi kao takva bila zabranjena
INF-om ako bi bila srednjeg dometa. Problem je nastao jer su SAD tvrdile
da je domet rakete SSC-8 2500km, a Rusija da ne prelazi 480km (ali nisu
pružili dovoljno dokaza za ovu tvrdnju). Dodatno, Rusija je imala i
altrenativu. To je bila raketa “Kalibr” koju takođe nemamo u bazi, a
koja je preteča rakete SSC-8 samo što joj je lanser sa mornarice. Tako
je izgledalo kao da Rsuija pronalazi “rupu” u INF sporazumu kako bi
uspela da proizvede kopnenu krstareću raketu srednjeg dometa (SSC-8).
Zbog svega navedenog, u oktobru 2018. tadašnji američki savetnik za
nacionalnu bezbednost J.Bolton sastao se sa ruskim ministrom odbrane
S.Soigu i drugim zvaničnicima u Moskvi. Tema sastanka bila je INF
sporazum, a Bolton je jasno preneo američku nameru da se povuče iz tog
sporazuma, upravo zbog navodnog ruskog kršenja. Neposredno nakon toga,
20. oktobra 2018. je Donald Trump najavio zvanično povlačenje SAD iz INF
sporazuma. Dodao je kao razlog i to što Kina (koja, podsetimo se, nije
bila ograničena sporazumom) postaje ozbiljna pretnja američkim
interisima na Pacifiku.
Konačno, 1. februara 2019. SAD su zvanično suspendovale svoje obaveze prema INF-u, a Rusija je učinila isto već dan kasnije. Do avgusta 2019. sporazum je u potpunosti prestao da važi. I SAD i Rusija ponovo su započe proizvodnju raketa srednjeg dometa svih tipova lansera.
Objašnjenje zaključka u prethodnom zadatku
Sada kada znamo više o istoriji razvoja raketa možemo bolje opravdati
zaključak da je Roze klaster u 5. zadatku predstavljao rakete dugog, a
ne kratkog dometa. Naime, još tada smo uvideli da se on sastoji pretežno
od sovjetskih/ruskih raketa. Prisetimo se da smo u tom zadatku radili
nad bazom u kojoj su bile izbačene sve rakete srednjeg dometa. Sada kada
znamo da je Sovjetski Savez dugo imao zabranu proizvodnje raketa
srednjeg dometa (tokom važenja INF sporazuma), možemo se osloniti na
činjenicu da su u tom periodu proizvodili mnogo više raketa dugog dometa
koje su imale bolje i značajnije performanse od onih kratkog dometa.
Za razliku od Kine koja je razvijala i rakete kratkog dometa (jer je
bila fokusirana na regionalne sukobe u Pacifiku i jugoistočnoj Aziji),
Rusija je više težila nečemu drugačijem. Cilj im je bio održavanje vojne
moći sa SAD-om (to su postizali raketama dugog dometa jer su tako mogli
da pokriju velike geografske udaljenosti) i nuklearno odvraćanje od
napada (rakete dugog dometa bile su osnovni element ruske nuklearne
strategije).
Takođe, Rusija je generalno kroz istoriju proizvodila više raketa
dugog dometa (i pre INF sporazuma) u skladu sa njihovom strategijom i
potrebama za nuklearnim odvraćanjem i globalnom dominacijom.
Stoga, veća brojnost ruskih raketa u Roze klasteru daje nam podlogu da (neformalno) zaključimo da se radi o raketama dugog dometa.