---
# System prepended metadata

title: 大數據分析平台

---

大數據分析平台
===
**各服務連結如下**
* Hadoop叢集
    * [Jupyter PC cluster](https://ba.cm.nsysu.edu.tw:9999/hub/login)
    * [RStudio PC cluster](https://ba.cm.nsysu.edu.tw:8787/)
* 新 Hadoop 叢集 (2020年後申請)
    * [Jupyter PC cluster](https://ba.cm.nsysu.edu.tw:9997/hub/login)
    * [RStudio PC cluster](https://ba.cm.nsysu.edu.tw:8888/)
* GPU伺服器(**使用GPU環境必須要透過中山大學的ip位置才可以連線**)
    * [Jupyter with GPU](https://ba.cm.nsysu.edu.tw:9998/hub/login)
    * [RStudio with GPU](https://ba.cm.nsysu.edu.tw:8788/)



大數據分析平台的服務有兩項，分別是由**18台主機共有136個核心數和1.5TB記憶體組成的Hadoop的叢集(Hadoop cluster)** 以及 **備載GPU運算能力的伺服器。** 而本院提供的開發環境為RStudio和Jupyter，使用者可以透過R和Python兩種語言來撰寫程式。


在這個頁面我們提供幾項簡易的教學，分別是
* Hadoop連線設定
* Spark連線設定
* Spark實作巨量資料範例
* GreenPlum實作範例
* GPU伺服器的環境變數設定

# Hadoop
Hadoop是什麼呢？
簡單來講，想像如果有個檔案大小超過你的個人電腦可以儲存的容量，那絕對無法被放進你的電腦對吧？這時候我們就需要Hadoop了！
**Hadoop是一個能儲存並管理大量資料的雲端平台**，除了能儲存超過一個伺服器所能容納的檔案外，還**能夠同時儲存、處理、分析**幾千萬份這種超大的檔案，所以每當講到大數據，便會提到Hadoop這套技術。
這段影音及文字教學，目的是要讓使用者可以連線上管院伺服器上的Hadoop，並對HDFS(Hadoop File System)上的檔案做些簡單的操作。
## 影音教學
{%youtube 60K8oHsIzw8 %}

---

1. **連線設定**
    - 在連線到Hadoop前，必須先設定環境變數。
    - 請各位使用者直接複製底下兩行程式碼在程式的最一開始，如果沒有執行這兩行程式的話會無法連線上Hadoop。
        ``` R
        Sys.setenv("HADOOP_CMD" = "/home/hadoop/hadoop/bin/hadoop")
        Sys.setenv("HADOOP_STREAMING"="/home/hadoop/hadoop/share/hadoop/tools/lib/hadoop-streaming-2.8.1.jar")
        ```
    


2. **載入packages**
    - rmr2, plyrmr, rhdfs 這些packages提供許多R的函式，讓使用者可以直接對HDFS系統上的任何檔案執行些指令，像是存取或是做資料分析。
    - **hdfs.init()** 這句的意思是連線HDFS系統。
   
        ```R
        library(rmr2)
        library(plyrmr)
        library(rhdfs)
        hdfs.init()
        ```
    
3. **與HDFS系統互動**
    - **to.dfs( )**： 寫入檔案至HDFS。
    - **from.dfs( )**：讀取HDFS上的檔案。
    
        ``` R
        # 將mtcars這個dataframe上傳至hdfs檔案處理系統
        to.dfs(mtcars, "/home/yourAccountName/mtcars.csv", format = "csv")
        # 從hdfs檔案處理系統上讀取檔案
        from.dfs("/home/yourAccountName/mtcars.csv", format = "csv")
        ```
        
    - **hdfs.ls()**：列出HDFS上的所有檔案。
    - **hdfs.del()**：刪除HDFS上的所有檔案。

        ``` R
        hdfs.ls("/home/yourAccountName")
        hdfs.del("/home/yourAccountName/mtcars.csv")
        ```
    - 上述語法的 **/yourAccountName/** 部分，請使用者自行將其改成自己的目錄名稱。



**以上就是對於如何連線上Hadoop以及與HDFS互動的介紹。
當然，使用者可以透過"MapRudeuce"的方法去對Hadoop上的資料做更多操作。**

---

# Spark
既然處理大數據我們有了Hadoop這個強大的工具了，那為何還有Spark的出現呢？而Spark與Hadoop又有什麼不同呢？

Hadoop在執行MapReduce的運算時，會將中間產生的數據，存儲在硬碟中，也就說任何的資料存取都是在執行I/O，而I/O往往是效能的瓶頸，因此會有讀寫資料延遲的問題。但Spark比Hadoop晚4年問世，卻能以100倍快的速度執行MapReduce是為什麼呢？

**Spark是基於記憶體**內的運算框架，在運算時，會將中間產生的數據暫存在記憶體中，因此可以大大地加快運算速度，尤其是反覆執行越多次時，所需讀取的資料量就越大，越能看出Spark的效能。而Spark同時也與Hadoop相容，所以同樣可以透過HDFS存儲檔案。
## 影音教學
{%youtube FvBRkEtJZ98 %}

---

1. **載入所需的package並連線上HDFS**
    - 該步驟與前面的Hadoop連線是一樣的，目的是要連線上HDFS。
        ```R
        Sys.setenv("HADOOP_CMD" = "/home/hadoop/hadoop/bin/hadoop")
        Sys.setenv("HADOOP_STREAMING"="/home/hadoop/hadoop/share/hadoop/tools/lib/hadoop-streaming-2.8.1.jar")
        library("rhdfs")
        hdfs.init()
        ```
2. **設定Spark的連線資訊**
    - 由於Spark是基於記憶體的運算，所以在連線前必須了解連線的伺服器提供多大的資源，並設定合理的資源消耗，例如使用多少的記憶體、核心數等等。 
        ```R
        # the following code are fixed setting
        library(dplyr) 
        library(sparklyr)
        Sys.setenv(SPARK_HOME="/usr/local/spark/spark-2.1.0-bin-hadoop2.7/")
        config <- spark_config()
        config$spark.executor.memory = "32G"
        config$spark.cores.max = "50"
        config$spark.driver.memory = "16G" 
        config$spark.yarn.executor.memoryOverhead = "4096"
            
            # create a connection to Spark
        sc <- spark_connect(master = "spark://hnamenode:7077", config = config)
        ```

    執行完以上的程式後就可以連線上Spark了。
    如果連線成功的話，會在RStudio的畫面右上角看到一個新的頁籤叫做"connection"。如果連線失敗則會在console的頁面跳出錯誤訊息。

3.  **在HDFS上存取檔案**
    - 先前提到過，Spark跟HDFS是相容的，所以連線上Spark時也可以對HDFS上的檔案做存取。
    - 底下程式碼示範如何從HDFS上讀出檔案和把R的物件儲存至HDFS。
        ```R
        hdfs.ls("/home/yourAccountName/")
        mySDF = spark_read_csv(sc, name = "mtcars", path = "hdfs:/home/yourAccountName/mtcars.csv", header = T)

        # you can also move R dataframe to Spark.
        movies_sdf = copy_to(sc, df = ggplot2movies::movies, name = "movies",overwrite = T)  
        ```
4. **中斷連線**
    - 若使用者在執行完畢Spark的操作卻沒有中斷連線的話，會導致集群上環的運算資源被特定使用者佔用住，導致其他使用者無法使用群集上資源，所以，當使用完Spark時務必要養成斷線的好習慣。
        ```R
        spark_disconnect(sc)
        ```

---


# 範例：如何在Spark上操作巨量資料
接下來的範例會示範如何在Spark上操作巨量資料。
範例中我們會用到 babynames 資料集，資料集紀錄一百八十多萬的新生兒姓名資料。選用這個資料集的目的是要讓使用者感受在操作巨量資料時，Spark的運算能力有多強大。
## 影音教學
{%youtube ZADM9UEjOaM %}


1. **設定連線資訊＆讀取資料**
    - Spark 連線
        ```R
            # the following code are fixed setting
            Sys.setenv("HADOOP_CMD" = "/home/hadoop/hadoop/bin/hadoop")
            Sys.setenv("HADOOP_STREAMING"="/home/hadoop/hadoop/share/hadoop/tools/lib/hadoop-streaming-2.8.1.jar")
            library("rhdfs")
            hdfs.init()
            
            library(dplyr) 
            library(sparklyr)
            Sys.setenv(SPARK_HOME="/usr/local/spark/spark-2.1.0-bin-hadoop2.7/")
            config <- spark_config()
            config$spark.executor.memory = "32G"
            config$spark.cores.max = "50"
            config$spark.driver.memory = "16G" 
            config$spark.yarn.executor.memoryOverhead = "4096"

            # create a connection to Spark
            sc <- spark_connect(master = "spark://hnamenode:7077", config = config)
        ```

    - 資料集讀取
        - 上述的資料集已經放在叫做 **sample** 資料夾裡面了，透過底下的程式碼就可以將資料讀出來了。
        - babynames是美國新生兒的姓名資料，資料筆數大約有 **一百八十多萬** 多筆。
        ```R
            data_file_1 = "hdfs:/home/sample/babynames.csv"
            # 透過spark_read_csv 至 hdfs上 讀取babynames 資料集
            baby = spark_read_csv(sc, "babynames", data_file_1)
            # babynames 資料集總共有一百八十多萬筆  
            baby %>% summarise(n = n()) 
        ```
    
2. **簡單查詢句：以 babynames 資料為例**
使用者可以想像當資料筆數超級多的時候，一句查詢句至少要搜尋過全部資料一次，那會需要花多少時間去執行這個查詢句，況且實務上我們不會只做單一條件的查詢句，也就是說我們的條件可能會很複雜包含不只一個變數，那表示一定會搜尋全部的資料不少次，可想而知，這必定是很耗資源的一段程式。但透過Spark，執行底下的程式最多只要花5~7秒的時間就可以完成查詢，這就是Spark強大的地方。
    - 以babynames資料為例，底下查詢句用來查詢各欄位中 **不重複的資料** 有幾筆。
    - 執行結果可以看出prop欄位的不重複資料有16萬多筆，卻能夠很快就完成查詢句。
        ```R
        # 設定起始時間
        start_time <- proc.time()
        baby %>% 
            summarise(n_year = n_distinct(year),
                      n_sex = n_distinct(sex),
                      n_name = n_distinct(name),
                      n_n = n_distinct(n),
                      n_prop = n_distinct(prop)) %>% 
                      data.frame
            #   n_year n_sex  n_name  n_n   n_prop
            #     136    2    95025  13604  162480
                      
        # 用程式執行完的時間減去起始時間
        proc.time() - start_time
            #   user   system  elapsed  
            #   0.059   0.025   0.769  
        ```
    - 如果是直接在執行相同的查詢句，而不是透過spark做運算的話，其執行速度沒有辦法來得像spark這麼快。
        ```R
        # 設定起始時間
        start_time <- proc.time()
        babynames::babynames %>% 
            summarise(n_year = n_distinct(year),
                      n_sex = n_distinct(sex),
                      n_name = n_distinct(name),
                      n_n = n_distinct(n),
                      n_prop = n_distinct(prop)) %>%
                      data.frame
            #   n_year n_sex  n_name  n_n   n_prop
            #     136    2    95025  13604  162480
        
        proc.time() - start_time
            #   user   system  elapsed  
            #   0.578   0.013   0.658 
        ```



3. **資料視覺化：以 babynames 這個資料集為範例**
babynames這個資料集紀錄從1880年開始，在美國每年出生的新生兒取哪些名字，每個名字總共被用了幾次等資訊，在資料集裡面共有5個columns，分別是年份、性別、姓名、總數、佔總出生人數的比重。

    在範例中，寫了一個函式用來查詢特定一年中，哪些名字最常被使用來命名。
另外我們也附上[官方教學文件](https://beta.rstudioconnect.com/content/1813/babynames-dplyr.nb.html#)供使用者參考。
    - 載入會用到的packages
        ```R
            library(ggplot2)  #The following are the tools for graphing
            library(dygraphs)
            library(rbokeh)
            library(RColorBrewer)
            library(plotly)
        ```
    - 先找出1986以後且被用來命名過超過一千次的資料，列出姓名跟性別這個兩個變數，並以topNames這個變數儲存起來。
 
        ```R
            topNames = baby %>% 
                filter(year > 1986) %>%
                group_by(name, sex) %>%
                summarize(count = sum(n)) %>%
                filter(count > 1000) %>%
                select(name, sex)
        ```
    - 接著用原始資料對topNames做inner_join，並且group_by年份這個變數，表示出來的結果會是各年中最代表的姓名
        ```R
            yearlyName = baby %>%
                filter(year > 1986) %>%
                inner_join(topNames) %>%
                group_by(year, name, sex) %>%
                summarize(count = sum(n)) %>%
                sdf_copy_to(sc, ., "yearlyname", T, overwrite=T) 
        ```
    
    - 再來我們將上面做過的事情，寫成一個函式，以便未來想要做查詢的時候，不需要重複打這麼多程式碼，只要傳入年份變數就可以得到我們想要的結果，還可以把結果以圖形化的方式呈現出來。


        ```R
            MostPopularNames <- function(year) {
                topNames <- baby %>% 
                    filter(year >= 1986) %>%
                    group_by(name, sex) %>%
                    summarize(count = sum(n)) %>%
                    filter(count > 1000) %>%
                    select(name, sex)
                
                yearlyName <- baby %>%
                    filter(year >= 1986) %>%
                    inner_join(topNames) %>%
                    group_by(year, name, sex) %>%
                    summarize(count = sum(n)) %>%
                    sdf_copy_to(sc, ., "yearlyname", T, overwrite=T)
              
                TopNm <- yearlyName %>%
                    filter(year == year) %>%
                    group_by(name,sex) %>%
                    summarize(count=sum(count)) %>%
                    group_by(sex) %>%
                    mutate(rank = min_rank(desc(count))) %>%
                    filter(rank < 5) %>%
                    arrange(sex, rank) %>%
                    select(name,sex,rank) %>%
                    sdf_copy_to(sc,.,"topNames",T,overwrite=TRUE)

                topNamesYearly <- yearlyName %>% 
                    inner_join(select(TopNm, sex, name)) %>% 
                    collect
                    
                #兩種畫法可以選一種畫就好
                #第一種圖
                ggplot(topNamesYearly,
                        aes(year, count, color = name)) + 
                        facet_grid(~sex) + 
                        geom_line() + 
                        ggtitle(paste0("Most Popular Names of ", year))

                #第二種圖
                # 拿出名字欄位之後要做factor化
                names <- TopNm %>% 
                    select(name) %>% 
                    collect %>% 
                    as.data.frame
                    
                names <- names[, 1, T]
                
                topNamesYearly$name <- factor(topNamesYearly$name, levels = names, labels = names)
                
                p = ggplot(topNamesYearly, aes(year, count, color=name)) + 
                    theme_light() + 
                    facet_wrap(~sex, nrow=2) +
                    geom_vline(xintercept = year, col='yellow', lwd=1.2) +
                    geom_line() +
                    ggtitle(sprintf('Most Popular Names of %d',year)) +
                    scale_colour_brewer(palette = "Paired")
                    
                plotly_build(p)
          }
        ```
    
    - 透過函式給定特定年份，就可以得到該年中最常被使用的姓名是哪個。
        ```R
            MostPopularNames(2015)
            MostPopularNames(2000) 
        ```
    
    ![](https://i.imgur.com/xxKQ122.png)
    
    
    - 將男生姓名及女生姓名在資料中的分佈狀況畫出來。 
        ```R
            sharedName = baby %>%
              mutate(male=ifelse(sex == "M", n, 0),
                     female=ifelse(sex == "F", n, 0)) %>%
              group_by(name) %>%
              summarize(Male = sum(male),
                        Female = sum(female),
                        count = sum(n),
                        AvgYear = round(sum(year * n) / sum(n),0)) %>%
              filter(Male > 10000 & Female > 10000) %>% collect

            figure(width = NULL, height = NULL,
                   xlab = "Log10 Number of Males",
                   ylab = "Log10 Number of Females",
                   title = "Top shared names (1880 - 2014)") %>%
              ly_points(log10(Male), log10(Female), data = sharedName,
                        color = AvgYear, size = scale(sqrt(count)),
                        hover = list(name, Male, Female, AvgYear), legend = FALSE)
        ```
    
    ![](https://i.imgur.com/xTmj9BJ.png)

4. **使用完Spark後，如果暫時沒有要再使用，務必要將其中斷連線**
        
    ```R
    spark_disconnect(sc)
    ```
    
---    

# Greenplum 實作範例
## 影音教學
{%youtube  7KDRd2W5fjM %}
Greenplum database是一種開源的分散式資料庫，它提供PB級別數據量的快速分析能力及對超大資料表做快速查詢的能力。
過去，當使用者要分析儲存在資料庫上的結構化資料的時候，必須先將資料表讀入分析工具中，例如將一個table讀進RStudio中，再進行資料分析、建立模型等等，但如果資料表很大的話，會造成在讀取資料的階段就耗盡了大量的運算資源。而現在有了Greenplum這個擁有資料分析能力的資料庫，使用者可以直接在資料庫中對資料表做資料分析、做查詢句甚至是訓練模型，並將結果直接輸出至我們的分析工具中，如此一來，就可以避免在讀取資料時大量消耗運算資源的問題，使用者可以直接對資料庫中的資料表做更快速、更彈性的操作。

底下的範例會教使用者如何用RStudio連線上資料庫(本院提供的資料庫為Greenplum)以及對資料表做些簡單的查詢句。

1. 載入packages
    - PivotalR、DBI、RPostgreSQL都是與資料庫操作有關的packages，使用者可以根據自己的需求決定要載入哪些，範例中使用的語法都只有用到RPostgreSQL package裡面的function。
    - 載入ggplot2 package是因為範例中會用到diamonds資料集。
        ```R
        library(PivotalR)
        library(ggplot2)
        library(DBI)
        library(RPostgreSQL)
        ```
2. 設定資料庫的連線資訊
    - 為了方便起見，在申請帳號時本院會將**dbname**設定成使用者的帳號名稱。而**user**和**password**則是大數據分析平台的帳號和密碼。
    - **host**為192.168.1.100。
        ```R
        pgConn = dbConnect("PostgreSQL",
                       dbname = "帳號名稱",
                       user="帳號名稱",
                       password="密碼",
                       host = "192.168.1.100")
        ```
3. 常用基本語法
    - dbListTables()：列出所有資料表
    - dbWriteTable()：建立資料表
    - dbReadTable()：讀取資料表
    - dbRemoveTable()：刪除資料表
    - dbListFields()：讀取資料表欄位
        ```R
        ##List tables in database
        dbListTables(pgConn)

        diamonds = as.data.frame(diamonds)
        ##create table 
        dbWriteTable(pgConn, "diamonds", diamonds)
    
        ##Read data in table
        dbReadTable(pgConn, "diamonds")

        ##List column name of table
        dbListFields(pgConn, "diamonds")
        
        dbRemoveTable(pgConn, "diamonds")
        ```
4. 執行查詢句
    - 第一種方式為dbSendQuery()，使用者下SQL去執行，執行完畢後產生一個resultset用來儲存結果，再透過fetch() function讀取resultset裡面儲存的資訊。
    - 第二種方式為dbGetQuery()，直接取得使用者所下的SQL地執行結果。
        ```R
        ## send query to database and create resultset object 
        ## use fetch() function to read the resultset
        rs = dbSendQuery(pgConn, statement = "SELECT count(*) FROM diamonds")
        fetch(rs)
        
        dbClearResult(rs)
        rm(rs)
        
        
        df = dbGetQuery(pgConn, "SELECT color, avg(price) as AvgPrice FROM diamonds group by color")
        # color variable of diamonds dataset says that from "J" to "D" represents worst to best 
        df$color = factor(df$color,
                  order = T,
                  levels = c("J","I","H","G","F","E","D"))
        
        df[order(df$color), ]
        ```
        
**以上是連線上資料庫與簡易的資料庫操作範例，使用可以依照自己的需求去使用一開始提到的幾個package內的功能，在這邊只簡單示範了幾個基本語法而已**

---
# GPU環境變數設定
GPU的環境變數設定是很重要的步驟，因為在管院商業大數據平台上，運算資源是由中山大學管理學院的學生共同享有的，若沒有適當地設定GPU環境變數的話，可能會造成系統資源被特定使用者佔用，導致其他使用者無法共享平台上的資源。以下我們會分別教學在Python和R中要如何設定GPU的環境變數。

### Python
1. GPU 指定
    - GPU指定的意思就是告訴電腦你要使用第幾張GPU。
    - GPU的索引位置是從0開始並透過逗號分隔，也就是說如果要使用第一張和第三張GPU，則可以透過底下這段程式碼完成設定。

        ```python
        import os
        # use first and third GPU
        os.environ["CUDA_VISIBLE_DEVICES"] = "0,2"
        ```
        
2. 限制GPU用量
    - 一次只使用一張。
    
        ```python
        # use any one GPU
        import tensorflow as tf
        config = tf.ConfigProto(device_count={'GPU': 1})
        sess = tf.Session(config=config)
        ```

3. 限制GPU的記憶體使用量
    - 限制使用者只能使用20%的記憶體
        ```python
        # use 20% memory of GPU
        gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.2)
        sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))

        # if using Keras
        import tensorflow as tf
        from keras.backend.tensorflow_backend import set_session
        config = tf.ConfigProto()
        config.gpu_options.per_process_gpu_memory_fraction = 0.2
        set_session(tf.Session(config=config))
        ```

### R
1. GPU 指定
    - GPU指定的意思就是告訴電腦你要使用第幾張GPU。
    - GPU的索引位置是從0開始並透過逗號分隔，也就是說如果要使用第一張和第二張GPU，則可以透過底下這段程式碼完成設定。
    
        ```R
        Sys.setenv(CUDA_HOME="/usr/local/cuda")
        Sys.setenv(PATH=paste(Sys.getenv("PATH"), "/usr/local/cuda/bin", sep = ":"))

        # Use both card #0 and #1
        Sys.setenv(CUDA_VISIBLE_DEVICES="0,1")
        ```
2. 限制GPU用量
    - 一次只使用一張。
    
    
        ```R
        library(keras); library(tensorflow)
        config = tf$ConfigProto(device_count={'GPU': 1})
        sess = tf$Session(config=config)

        # if using keras please add the following
        k_set_session(sess)
        ```
3. 限制GPU的記憶體使用量
    - 限制使用者只能使用20%的記憶體
        
        ```R
        gpu_options=tf$GPUOptions(per_process_gpu_memory_fraction=0.2)
        sess = tf$Session(config=tf$ConfigProto(gpu_options=gpu_options))

        # if using keras please add the following
        k_set_session(sess)
        ```
    