##### Rozdział 7: sieci neuronowe i maszyny wektorów nośnych -------------------

##### Część 1: sieci neuronowe -------------------
## Przykład: modelowanie wytrzymałości betonu  ----

## Etap 2: badanie i przygotowywanie danych ----
# wczytujemy dane i badamy ich strukturę
concrete <- read.csv("concrete.csv")
str(concrete)

# niestandardowa funkcja normalizacji
normalize <- function(x) { 
  return((x - min(x)) / (max(x) - min(x)))
}

# stosujemy normalizację do całej ramki danych
concrete_norm <- as.data.frame(lapply(concrete, normalize))

# potwierdzamy, że teraz zakres wynosi od zera do jedności
summary(concrete_norm$strength)

# porównujemy z pierwotnym minimum i maksimum
summary(concrete$strength)

# tworzymy dane treningowe i testowe
concrete_train <- concrete_norm[1:773, ]
concrete_test <- concrete_norm[774:1030, ]

## Etap 3: trenowanie modelu na danych ----
# trenujemy model neuralnet
library(neuralnet)

# prosta sieć ANN z jednym neuronem ukrytym
set.seed(12345) # gwarantuje powtarzalność wyników
concrete_model <- neuralnet(strength ~ cement + slag +
                              ash + water + superplastic + 
                              coarseagg + fineagg + age,
                              data = concrete_train)

# wizualizujemy topologię sieci
plot(concrete_model)

## Etap 4: ewaluacja modelu ----
# uzyskujemy wyniki modelu
model_results <- compute(concrete_model, concrete_test[1:8])
# uzyskujemy przewidziane wartości wytrzymałości
predicted_strength <- model_results$net.result
# badamy korelację między wartościami przewidzianymi a rzeczywistymi
cor(predicted_strength, concrete_test$strength)

## Etap 5: poprawianie działania modelu ----
# bardziej złożona topologia sieci neuronowej z 5 neuronami ukrytymi
set.seed(12345) # gwarantuje powtarzalność wyników
concrete_model2 <- neuralnet(strength ~ cement + slag +
                               ash + water + superplastic + 
                               coarseagg + fineagg + age,
                               data = concrete_train, hidden = 5)

# kreślimy sieć
plot(concrete_model2)

# oceniamy wyniki tak, jak wcześniej
model_results2 <- compute(concrete_model2, concrete_test[1:8])
predicted_strength2 <- model_results2$net.result
cor(predicted_strength2, concrete_test$strength)

# JESZCZE BARDZIEJ złożona topologia sieci neuronowej z dwiema warstwami ukrytymi i niestandardową funkcją aktywacji

# tworzymy niestandardową funkcję aktywacji
softplus <- function(x) { log(1 + exp(x)) }

set.seed(12345) # gwarantuje powtarzalność wyników
concrete_model3 <- neuralnet(strength ~ cement + slag +
                               ash + water + superplastic + 
                               coarseagg + fineagg + age,
                             data = concrete_train, hidden = c(5, 5), act.fct = softplus)

# kreślimy sieć
plot(concrete_model3)

# oceniamy wyniki tak, jak wcześniej
model_results3 <- compute(concrete_model3, concrete_test[1:8])
predicted_strength3 <- model_results3$net.result
cor(predicted_strength3, concrete_test$strength)

# zauważ, że wartości przewidziane i  rzeczywiste mają różne skale
strengths <- data.frame(
  actual = concrete$strength[774:1030],
  pred = predicted_strength3
)

head(strengths, n = 3)

# normalizacja nie wpływa na korelację...
# ...ale zmiana skali wpływa na takie miary, jak błąd procentowy!
cor(strengths$pred, strengths$actual)
cor(strengths$pred, concrete_test$strength)

# tworzymy funkcję do odwracania normalizacji
unnormalize <- function(x) { 
  return(x * (max(concrete$strength) -
          min(concrete$strength)) + min(concrete$strength))
}

strengths$pred_new <- unnormalize(strengths$pred)
strengths$error_pct <- (strengths$pred_new - strengths$actual) / strengths$actual

head(strengths, n = 3)

# korelacja pozostaje taka sama pomimo odwrócenia normalizacji
cor(strengths$pred_new, strengths$actual)

##### Część 2: maszyny wektorów nośnych -------------------
## Przykład: optyczne rozpoznawanie znaków ----

## Etap 2: badanie i przygotowywanie danych ----
# wczytujemy dane i badamy ich strukturę
letters <- read.csv("letterdata.csv", stringsAsFactors = TRUE)
str(letters)

# dzielimy dane na treningowe i testowe
letters_train <- letters[1:16000, ]
letters_test  <- letters[16001:20000, ]

## Etap 3: trenowanie modelu na danych ----
# zaczynamy od wytrenowania prostej liniowej SVM
library(kernlab)
letter_classifier <- ksvm(letter ~ ., data = letters_train,
                          kernel = "vanilladot")

# wypisujemy podstawowe informacje o modelu
letter_classifier

## Etap 4: ewaluacja modelu ----
# prognozy na testowym zbiorze danych
letter_predictions <- predict(letter_classifier, letters_test)

head(letter_predictions)

table(letter_predictions, letters_test$letter)

# przyglądamy się tylko zgodności/niezgodności
# konstruujemy wektor TRUE/FALSE wskazujący poprawne/niepoprawne prognozy
agreement <- letter_predictions == letters_test$letter
table(agreement)
prop.table(table(agreement))

## Etap 5: poprawianie działania modelu ----

# zmieniamy funkcję jądrową na RBF
set.seed(12345)
letter_classifier_rbf <- ksvm(letter ~ ., data = letters_train, kernel = "rbfdot")
letter_predictions_rbf <- predict(letter_classifier_rbf, letters_test)

agreement_rbf <- letter_predictions_rbf == letters_test$letter
table(agreement_rbf)
prop.table(table(agreement_rbf))

# testujemy różne wartości parametru kosztu
cost_values <- c(1, seq(from = 5, to = 40, by = 5))

accuracy_values <- sapply(cost_values, function(x) {
  set.seed(12345)
  m <- ksvm(letter ~ ., data = letters_train,
            kernel = "rbfdot", C = x)
  pred <- predict(m, letters_test)
  agree <- ifelse(pred == letters_test$letter, 1, 0)
  accuracy <- sum(agree) / nrow(letters_test)
  return (accuracy)
})

plot(cost_values, accuracy_values, type = "b")
