##### Rozdział 15: praca z big data -------------------

## Klasyfikowanie obrazów za pomocą wstępnie wytrenowanej sieci CNN ----
## WAŻNE: niektóre z poniższych czynności trzeba wykonać tylko raz.

# ten pakiet pozwala instalować pakiety z GitHuba 
library(devtools)

# instalujemy najnowszą wersję pakietu tensorflow
devtools::install_github("rstudio/tensorflow") # potrzebne tylko za pierwszym razem!

# instalujemy wszystkie zależności tensorflow (np. Python)
library(tensorflow)
install_tensorflow() # potrzebne tylko za pierwszym razem!

# instalujemy najnowszą wersję pakietu keras
devtools::install_github("rstudio/keras") # potrzebne tylko za pierwszym razem!

# Początek przykładu
# wczytujemy model ResNet50 z wagami wytrenowanymi na zbiorze ImageNet
library(keras)
m_resnet50 <- application_resnet50(weights = 'imagenet')

# wczytujemy obraz i przekształcamy go w tablicę
img <- image_load("ice_cream.jpg", target_size = c(224,224))
x <- image_to_array(img)

# x jest teraz tensorem 3d tensor: y, x i kanał koloru (r, g, b)
# uwaga: liczenie (y, x) zaczyna się od (1, 1) w lewym górnym rogu i (1, 224) w prawym górnym rogu
dim(x)
str(x)

# przykładowe piksele
x[1, 224, 1:3] # biały piksel w prawym górnym rogu
x[40, 145, 1:3] # czerwony piksel na zdjęciu loda

# tworzymy 4-wymiarowy tensor ze stałą wartością '1' w pierwszym wymiarze
# (pierwszy wymiar to numer 'partii' używany do przetwarzania wielu obrazów)
x <- array_reshape(x, c(1, dim(x)))
dim(x) # wyświetlamy nowe wymiary

# przekaształcamy kolory i środkujemy dane, aby przygotować je na użytek modelu ResNet-50
x <- imagenet_preprocess_input(x)
x[1, 40, 145, 1:3] # ponownie przyglądamy się czerwonemu pikselowi

# używamy modelu ResNet-50, aby przewidzieć zawartość zdjęcia
p_resnet50 <- predict(m_resnet50, x)
c_resnet50 <- imagenet_decode_predictions(p_resnet50, top = 10)

# wypisujemy prognozy
c_resnet50

# za pomocą funkcji lapply() stosujemy etapy przetwarzania obrazu na pozostałych dwóch zdjęciach
img_list <- list("cat.jpg", "pizza.jpg")
img_data <- lapply(img_list, image_load, target_size = c(224,224))
img_arrs <- lapply(img_data, image_to_array)
img_resh <- lapply(img_arrs, array_reshape, c(1, 224, 224, 3))
img_prep <- lapply(img_resh, imagenet_preprocess_input)
img_prob <- lapply(img_prep, predict, object = m_resnet50)

# za pomocą funkcji sapply() stosujemy funkcję dekodowania, aby uzyskać końcowe prognozy
img_classes <- sapply(img_prob, imagenet_decode_predictions, top = 3)
img_classes

## Używanie word2vec do rozumienia tekstu ----

# uwaga: kod w tej sekcji wymaga osadzeń słów wytrenowanych na archiwum
# Google News. Zanim przejdziesz dalej, pobierz plik GoogleNews-vectors-negative300.bin.gz
# ze strony https://code.google.com/archive/p/word2vec/ i wypakuj go do folderu projektu R.

library(word2vec)

# wczytujemy 300-wymiarowy model word2vec wytrenowany przez Google
m_w2v <- read.word2vec(file = "GoogleNews-vectors-negative300.bin",
                       normalize = TRUE)

# badamy strukturę modelu
str(m_w2v)

# uzyskujemy wektor dla kilku wyrazów
foods <- predict(m_w2v, c("cereal", "bacon", "eggs", "sandwich", "salad", "steak", "spaghetti"), type = "embedding")
meals <- predict(m_w2v, c("breakfast", "lunch", "dinner"), type = "embedding")

# badamy wektor jednego słowa
head(foods["cereal", ])

# badamy kilka pierwszych kolumn
foods[, 1:5]

# obliczamy podobieństwo między artykułami spożywczymi a posiłkami
word2vec_similarity(foods, meals)

# możemy też użyć podobieństwa kosinusowego (nie pokazano w książce)
word2vec_similarity(foods, meals, type = "cosine")

# tworzymy wektor hipotetycznych postów w serwisie społecznościowym
user_posts = c(
  "I eat bacon and eggs in the morning for the most important meal of the day!",
  "I am going to grab a quick sandwich this afternoon before hitting the gym.",
  "Can anyone provide restaurant recommendations for my date tonight?"
)

library(tm) # wczytujemy pakiet tm, żeby użyć funkcji removeWords() i stopwords()

user_posts_clean <- removeWords(user_posts, stopwords())
user_posts_clean <- txt_clean_word2vec(user_posts_clean)
user_posts_clean[1] # przyglądamy się pierwszemu oczyszczonemu postowi

# uzyskujemy wektory doc2vec dla postów
post_vectors <- doc2vec(m_w2v, user_posts_clean)
str(post_vectors)

# uzyskujemy wektory word2vec dla posiłków
meals <- predict(m_w2v, c("breakfast", "lunch", "dinner"), type = "embedding")

# porównujemy podobieństwo postów i wyrazów
word2vec_similarity(post_vectors, meals)

## Wizualizacja danych wysokowymiarowych ----

library(tidyverse)
sns_terms <- read_csv("snsdata.csv") |>
  select(basketball:drugs)

# znajdujemy dwie pierwsze składowe główne
library(irlba)
set.seed(123456)
sns_pca <- sns_terms |>
  prcomp_irlba(n = 2, center = TRUE, scale = TRUE) 

# tworzymy wykres punktowy
library(ggplot2)
as.data.frame(sns_pca$x) |>
  ggplot(aes(PC1, PC2)) + geom_point(size = 1, shape = 1)

## Wizualizacja naturalnych klastrów w danych z wykorzystaniem t-SNE ----

# bierzemy losową próbkę 5000 użytkowników
library(tidyverse)
set.seed(123)
sns_sample <- read_csv("snsdata.csv") |>
  slice_sample(n = 5000)

# wykonujemy t-SNE z parametrami domyślnymi
library(Rtsne)
set.seed(123)
sns_tsne <- sns_sample |>
  select(basketball:drugs) |>
  Rtsne(check_duplicates = FALSE)

# wizualizujemy wynik t-SNE
library(ggplot2)
data.frame(sns_tsne$Y) |>
  ggplot(aes(X1, X2)) + geom_point(size = 2, shape = 1)

# tworzymy cechę kategoryczną dla liczby użytych wyrazów
sns_sample_tsne <- sns_sample |>
  bind_cols(data.frame(sns_tsne$Y)) |> # dodajemy dane t-SNE
  rowwise() |> # operujemy na wierszach, a nie na kolumnach
  mutate(n_terms = sum(c_across(basketball:drugs))) |>
  ungroup() |> # usuwamy grupowanie wierszowe
  mutate(`Użyte wyrazy` = if_else(n_terms > 0, "1+", "0"))

# wizualizujemy wynik t-SNE wg liczby użytych wyrazów
sns_sample_tsne |>
  ggplot(aes(X1, X2, shape = `Użyte wyrazy`, color = `Użyte wyrazy`)) +
  geom_point(size = 2) +
  scale_shape(solid = FALSE)

## praca z bazami danych SQL ----

# Zarządzanie bazami danych w tidyverse
library(DBI)
library(RSQLite)

# tworzymy połączenie z bazą danych SQLite
con <- dbConnect(RSQLite::SQLite(), "credit.sqlite3")
dbListTables(con)

# tworzymy ramkę danych z wyniku kwerendy
res <- dbSendQuery(con, "SELECT * FROM credit WHERE age >= 45")
credit_age45 <- dbFetch(res)
summary(credit_age45$age)

# odłączamy się od bazy danych
dbClearResult(res)
dbDisconnect(con)

# Łączenie się z bazą danych za pomocą odbc
# (uwaga: przykład został zamieszczony tylko w celach demonstracyjnych,
# musisz odpowiednio zmodyfikować go na użytek Twojej bazy danych)
this is an example for illustration only and will need to be modified for your DB)
library(DBI)
con <- dbConnect(odbc:odbc(), "nazwa_mojego_źródła_danych")

library(DBI)
con <- dbConnect(odbc::odbc(),
         database = "moja_baza_danych",
         uid = "moja_nazwa_użytkownika",
         pwd = "moje_hasło",
         host = "adres_mojego_serwera",
         port = 1234)

# Tworzenie zaplecza bazodanowego dla pakietu dplyr
library(DBI)
library(dplyr)
con <- dbConnect(RSQLite::SQLite(), "credit.sqlite3")
credit_tbl <- con |> tbl("credit")

# obliczamy statystyki zbiorcze na wartościach wieku przefiltrowanych przez bazę danych
credit_tbl |>
  filter(age >= 45) |>
  select(age) |>
  collect() |>
  summary()

# wyświetlamy średnią kowotę pożyczki wg statusu spłaty pożyczki, wiek 45+
credit_tbl |>
  filter(age >= 45) |>
  group_by(default) |>
  summarize(mean_amount = avg(amount))

# wyświetlamy kod SQL użyty do poprzedniej analizy
credit_tbl |>
  filter(age >= 45) |>
  group_by(default) |>
  summarize(mean_amount = avg(amount)) |>
  show_query()

## Mierzenie czasu wykonania ----

system.time(rnorm(1000000))

## Przetwarzanie równoległe ----

library(parallel)
detectCores()

# uwaga: poniższe polecenia zadziałają tylko w systemie innym niż Windows (tzn. MacOSX lub Unix/Linux)
# potrzebna jest też wystarczająca liczba rdzeni procesora!

# generowanie liczb losowych z wykorzystaniem wielu rdzeni
# jeden rdzeń
system.time(l1 <- unlist(mclapply(1:10, function(x) {
  rnorm(10000000)}, mc.cores = 1)))

# dwa rdzenie
system.time(l2 <- unlist(mclapply(1:10, function(x) {
  rnorm(10000000)}, mc.cores = 2)))

# cztery rdzenie
system.time(l4 <- unlist(mclapply(1:10, function(x) {
  rnorm(10000000) }, mc.cores = 4)))

# osiem rdzeni
system.time(l8 <- unlist(mclapply(1:10, function(x) {
  rnorm(10000000) }, mc.cores = 8)))

# tworzenie 4-węzłowego klastra z wykorzystaniem pakietu snow
cl1 <- makeCluster(4)

# sprawdzamy, czy klaster działa
clusterCall(cl1, function() { Sys.info()["nodename"] })

# wykonujemy tę samą funkcję w każdym węźle (nie pokazano w książce)
clusterCall(cl1, function() { print("gotowy!") })

# wykonujemy inną operację na każdym węźle
clusterApply(cl1, c('A', 'B', 'C', 'D'),
             function(x) { paste("Klaster", x, "gotowy!") })

# zamykamy klaster (WAŻNY KROK!)
stopCluster(cl1)

## Równoległe wykonywanie pętli z wykorzystaniem pakietu foreach ----

# generujemy 100 milionów liczb losowych
system.time(l1 <- rnorm(100000000))

# łączymy cztery zbiory 25 milionów liczb losowych
library(foreach)
system.time(l4 <- foreach(i = 1:4, .combine = 'c')
            %do% rnorm(25000000))

# sprawdzamy liczbę rdzeni
detectCores()

# paralelizujemy powyższą pętlę foreach
library(doParallel)
registerDoParallel(cores = 4)
system.time(l4p <- foreach(i = 1:4, .combine = 'c')
            %dopar% rnorm(25000000))

stopImplicitCluster()

## Przetwarzanie równoległe z wykorzystaniem pakietu caret ----

# trenujemy las losowy, nie zezwalając na przetwarzanie równoległe
library(caret)
credit <- read.csv("credit.csv")
system.time(train(default ~ ., data = credit, method = "rf",
                  trControl = trainControl(allowParallel = FALSE)))

# równolegle trenujemy ten sam las losowy (8 rdzeni)
library(doParallel)
registerDoParallel(cores = 8)
system.time(train(default ~ ., data = credit, method = "rf"))
stopImplicitCluster()

## Przetwarzanie równoległe w chmurze z wykorzystaniem Apache Spark

library(sparklyr)
spark_install() # wystarczy wykonać raz przy pierwszym użyciu Sparka
spark_cluster <- spark_connect(master = "local")

credit_spark <- spark_read_csv(spark_cluster, "credit.csv")

# dzielimy zbiór danych credit_spark na część treningową i testową
splits <- sdf_random_split(credit_spark,
                           train = 0.75, test = 0.25,
                           seed = 123)

# budujemy model lasu losowego z wykorzystaniem Sparka
credit_rf <- splits$train |>
  ml_random_forest(default ~ .)

# dokonujemy prognoz na zbiorze testowym
pred <- ml_predict(credit_rf, splits$test)

ml_binary_classification_evaluator(pred, metric_name = "areaUnderROC")

spark_disconnect(spark_cluster)

## Szybsze modelowanie z wykorzystaniem h2o

library(h2o)
h2o_instance <- h2o.init()
credit.hex <- h2o.uploadFile("credit.csv")

h2o.randomForest(y = "default",
                 training_frame = credit.hex,
                 ntrees = 500,
                 seed = 123)
