레이블이 Spark인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Spark인 게시물을 표시합니다. 모든 게시물 표시

2017년 9월 7일 목요일

BigData와 결합한, 분산 Deep Learning 그 의미와 접근 방법에 대하여

딥러닝의 대부이신 제프리 힌튼 교수님은, 머신러닝의 수십년간 암흑기가 극복될 수 있었던 계기로 3대 난재가 풀렸기 때문이라고 언급하신 바 있다. 바로 아래와 같다.
  1. 알고리즘적 혁신 ( Deep Neural Net 이 가능해진,  Relu, DropOut, 발견 등등..)
  2. 하드웨어 혁신 ( GPU 를 이용한 컴퓨팅 파워 Scale Up )
  3. 소프트웨어 혁신 ( BigData Cluster 를 이용한 분산 컴퓨팅 파워 Scale Out )
이 중 1은 우리가 너무나 많이 알고 있고, 곁에서 접하고 있고, 심지어 사랑하고 있는 분야이다.
2는 약간 비싸지만, 회사에서 안사주면, 자비를 털어서라도 집 Desktop에 2개 정도 꼳아주고, 그 날개 돋힌 파워를 충분히 느껴 볼 수 있는 분야이다.

하지만, 3은 좀 말이 다르다. 힌튼 교수님이 말씀 하셨지만,  Lab 에서든 기업에서든, 섣불리(아직까지는) 시도되지 못하는 경향이 있고, 관련된 자료도 많지 않다. 무엇보다도, Popular 한 오픈 Data 를 통해 논문에서 통용되는 수십만 혹은 수백만건의 데이타는 그다지 Big 하지 않기 때문에, 1+2 만 가지고도 꾸엮 꾸엮 실험하고 돌려보고, 논문을 완성해갈 수도 있는 수준일 수 있다. 때문에, 구글, MS 등 몇몇 회사등을 제외하고는 수십층 짜리 Very Very Deep 류의 모델을 밑바닥부터 적합한 Neural Networks 구조를 발견하고, 이를 초기값부터 시작하여 새롭게 학습시키는 등의 작업은 일반 소규모 Lab 등에서는 하기가 매우 힘들고, 그러다 보니, 그에 대한 학계의 연구가 보편화 되어 있지는 않는 듯 보인다. 

그래서인지 3의 접근은 일부 공룡 기업들에 의하여 자체 구축 혹은 오픈소스화 되어 일부 만이 오픈된 상태이다. 

기업의 Production Deep Learning 프로젝트의 경우, 우선 보유 Data 의 크기가 논문에서 사용되는 Data 의 크기보다 수십배 ~ 수백배인 경우가 많다.(아닌경우도 많지만...) 그리고, 복수의 모델을 함께 쓰거나 좀더 Fine Tuning 을 위해, Online Learning , Transfer Learning  보다는  초기부터의 Learning 을 시도하는 경우가 훨씬 많다.(특히, 한글 Deep Learning Text NLP 등은 기 학습되어 있는 Pre training Set 도 존재하지 않는다.)


오늘은 그런 부분 즉, Deep Learning 을 Big Data Scale Data 를 가지고 수십대 수백대의 GPU 클러스터를 가지고서 병렬 Training 을 하기 위한 BigData Platform + deep Learning Platform 구성에 대하여 이야기 해보고자 한다.

[1] GPU 만으로 하는 Deep Learning Approach의 한계

가장 Popular 한 Deep Learning  프레임워크 중 하나인 Tensorflow 는 사실, 이미 멀티 GPU 를 지원하고, 멀티 노드도 지원하며, 아직 미흡하지만, 자체 Serving Layer 도 가지고 있다. 하지만, Tensorflow 가 지원하는 수준의 분산 컴퓨팅, 분산 GPU 는 마치, 분산 데이타 컴퓨팅을 Map/Reduce 로 하는 경우와 유사하게 너무 Low Level 접근을 필요로 하는 경우가 많다. 하나의 Simple 한 Neural Network 모델이 Data Parallel 이 되거나 Compute Parallel 이 되는것 특히, 그것이 High Level 로 저절로 되는 것은 아직 그 어떤 Deep Learning 프레임워크도 완벽하게 지원하고 있지는 않는 영역이다.
Caffe나 Tensorflow , Torch, CNTK 등의 deep learning  프레임워크는 그 자체만으로 은총알은 아니다. High Level Scale Up 된 장비 한두대로 논문에서 다루는 크기의 데이타를 처리하는데에는 문제가 되지 않으나, 실무 데이타, 특히 클릭 스트림을 RNN이나 LSTM 분석하는 정도의 시나리오만 되도, 데이타의 크기나 GPU 머신의 Memory 문제로 금방 문제가 드러나기 십상이다.
다중 사용자에게 Deep Learning Serving(Service Request 대응) 을 하는 도중에, Model traing 갱신이 일어나고, 실시간 업데이트가 되면서, 무중지로 Model 배포되는 시나리오는 또 다른 고급 기술을 요구하기도 한다.
GPU 만으로 하는 Single Scale Up, Deep Learning Approach의 한계를 정리해 보자면 아래정도 일 것이다. 
  1. Hyper Parameter Tunning 노가다
  2. Training 속도 한계있음
  3. 멀티 GPU 코딩의 거시기함.
  4. GPU의 협소한 메모리로 인한 ResourceExhaustedError. 
  5. 강제된 작은 Batch Size
  6. RealTime Inference, 다수 동접자 Serving Layer, 모델의 무중지 Rolling Upgrade
  7. BigData Scale Data 들을 접근 하기 위한 Data Pipe lining  
  8. 모델 태우기 전, Python 레벨 데이타 전처리와 후처리의 한세월...

[2] Open Source 진영 BigData Scale Distributed Deep Learning Approach 
하지만, 오픈소스 진영에서는 그러한 것들을 주로 hadoop + Spark (PySpark) 를 통해 해결 시도 하고 있고, 어느정도 Production 레벨까지 활용 가능한 수준의 결과물들이 나오고 있다. 즉, 서두에서 언급했던 3(BigData 혁신)의 시도가 되고 있는 것이다.
BigDL, elaphas , caffeOnSpark  등 그런류의 몇가지 오픈소스를 실제 Production Level Deep Learning 배치 시나리오를 이용 Training 해보고 테스트 해보았는데, 아래 소개하는 TensorflowOnSpakr (made by Yahoo) 오픈소스는 그중 가장 가능성이 보이는 Approach 중 하나라 할 수 있을 것이다. (참고로, Yahoo 는 TensorflowOnSpark 을 만들기 전 CaffeOnSpark을 먼저 만들었고 테스트 하였다.)
아래는 Inception-v3 모델을 spark 멀티 노드위에서 worker 노드 갯수를 달리해가면 분산 Training 한 속도 비교 이다.

위처럼 1대에서 48시간을 돌려도 정확도가 0.73 % 정도인게, 8대에서는 7시간만에 도달되고 이후로, 85%가 넘는 정확도에 훨씬 빠른 시간에 도달 되는 것을 확인 할 수 있다.
[3] TensorflowOnSpark 설치 및 세팅 사용방법 
아래는 그런 Producion Level 에서 BigData 분산 Traing 과 RealTime Traing 부분에 강한 강점을 가지고 있는 시스템 구성으로 Tensorflow + Spark + Hadoop 구성을 설정 하고 세팅 한 후, 간단하게 사용하는 방법이다. ( Hadoop 과 Spark  는 Yarn Cluster 모드로 이미 설정이 기 완료 되어 있다고 가정하고, 그 이후의 세팅 방법만 언급 하였다.)
  1. Python3 관련 설정 및 설치
    1. 우선 SSL 관련
      1. yum install openssl openssl-devel -y
    2. Anaconda 설치
      1. https://repo.continuum.io/archive/ 위치에서 Linux 버전 최신 설치 파일 Download
      2. 각 노드에 모두 복사
      3. chmod 755 Anaconda3-4.4.0-Linux-x86_64.sh
      4. sudo ./Anaconda3-4.4.0-Linux-x86_64.sh
      5. 설치 완료 시 화면
        1. 설치 완료 화면
      6. bin 경로에 심볼릭 링크 연결 (Source 경로는 각자의 경로를 따를 것!)
        1. sudo ln -s /home/moneymall/anaconda3/bin/python3 /bin/python3
      7. 기존 python 을 un link
        1. sudo unlink /bin/python
      8. 새로 설치한 python3 로 다시 link
        1. sudo ln -s /bin/python3 /bin/python
      9. pip 도 설정
        1. sudo ln -s /home/moneymall/anaconda3/bin/pip /bin/pip

  2. Anaconda 설치에 관하여.
    1. Tensorflow 등의 기본이 되는 Python Dev 환경을 설치하는 여러가지 방법이 있지만, 내가 선호하는 방법은 우선 anaconda 를 깔아주는 것이다. 공간차지 등등 부수적인 단점이 있긴 하지만, 여러가지 추가적인 기능들이나 필수 모듈들이 동시에 깔려서 편하고, 이후 anaconda 가 제공하는 여러가지 관리 도구들을 이용하여 다양한 장점을 꽤할 수 있다.
    2. Root 로 설치시 실제 사용하는 계정으로 수행하는데 불편함이 많음.
    3. 실제 사용하는 계정으로 설치하는 것이 더 편함.
  3. Tensorflow 및 TensroflowOnSpark 설치
    1. pip install tensorflow
      1. CPU 모드일때는 그냥 간단히 저렇게 해줘도 됨.
      2. virtual env 등에 설정할 수도 있고, conda create 한 다음 설정할 수도 있으나, spark 와 연동을 위해서는 바깥에서 전역적으로 저렇게 설치하는 것이 좋음.
      3. 설치 완료 화면
    2. pip install tensorflowonspark
      1. 설치 무지 빨리 끝남.
      2. 설치 완료 화면
    3. 참고로 위 설치 방법은 Simple 설치 방법을 공유하기 위한 목적의 설치 Guide 이므로, RDMA 를 사용하는 Advanced 설정 방법은 아님.
      1. RDMA 사용 시 훨씬 성능을 극대화 할 수 있음.
  4. Spark및 Hadoop 에서 Tensorflow 의 Records 를 직접 읽고 쓰기위 한 Jar 라이브러리 설치
    1. 아래 주소에 Spark 용 해당 Open Source 가 있음.
      1. https://github.com/tensorflow/ecosystem/tree/master/spark/spark-tensorflow-connector
      2. git clone 후 spark 디렉토리로 이동.
      3. git clone https://github.com/tensorflow/ecosystem.git
      4. sbt build
        1. build.sbt 파일이 존재하는 위치까지 이동.
        2. sbt clean assembly
        3. 위 빌드 명령어 입력 하면 아래처럼 sbt 빌드 완료 되고, target 디렉토리 하위에 jar 파일 생성 됨.
        4. Jar 파일 위치
    2. 아래는 Spark 이 아닌 Hadoop 용 InputFormat/OutputFormat 모듈 for Tensorflow Records Direct Read/Write
      1. https://github.com/tensorflow/ecosystem/tree/master/hadoop
      2. 위 git clone 소스에서 Hadoop 경로로 간다.
      3. $TF_SRC_ROOT 는 Tensorflow 소스 루트.
      4. protoc 가 설치되어 있지 않다면, https://developers.google.com/protocol-buffers/ 에서 3.3.0 버전 설치 필요 함.
        1. git clone https://github.com/google/protobuf.git
        2. cd protobuf
        3. ./autogen.sh
        4. ./configure
        5. make # 무지오래걸림.
        6. make check # 더 오래걸림. 커피 먹으러 갔다 오길 추천.
          1. make check 완료시 화면
        7. sudo make install # 금방 끝남.
        8. sudo ldconfig
        9. 완료 후 확인
          1. protoc 명령어를 수행하면 저런 화면이 떠야 함.
      5. protobuf java 버전도 설치 필요
        1. 소스 위치는 앞에서 다운받았던 protobuf 소스에서 java 경로 밑에 있음.
        2. mvn test (maven 이 기 설치 되어 있어야 함.)
          1. 완료시 이런 모양.
        3. mvn install
      6. protoc --proto_path=$TF_SRC_ROOT --java_out=src/main/java/ $TF_SRC_ROOT/tensorflow/core/example/{example,feature}.proto
        1. $TF_SRC_ROOT 는 tensorflow 소스가 미리 받아져 있어야 하고 해당 경로로 set 되어 있어야 함. (.bashrc 등에...)
        2. tensorflow 소스는 https://github.com/tensorflow/tensorflow.git 이곳에 있음.
      7. mvn clean package
        1. 처음 default pom.xml 로 수행시 아래같은 에러 잔뜩 남.
          1. Cannot find symbol 이라는 문구들이 error 로그에 보이는 걸로 봐서...이건 version 오류로 추정됨.
          2. pom.xml 을 열어서 보면... version 이 1.6 으로 되어 있음. 이걸 1.8로 수정(내 환경에서의 Java 는 1.8 이었으므로...)
            1. Java 버전 1.8로 수정했음.
            2. protobuf 버전도 3.3.1 에서 내가 설치했던 버전인 3.4.0  으로 수정해 주었음.
        2. jar 생성
          1. 위 pom.xml 수정 후 빌드 성공하면 jar 생성됨.
          2. 빌드 성공시의 화면
            1. jar 파일은 target 디렉토리 아래 존재
      8. 해당 jar 를 HDFS 에 업로드
        1. hadoop fs -put tensorflow-hadoop-1.0-SNAPSHOT.jar /user/moneymall/
  5. Parallel MNIST 돌려보기
    1. 우선 데이타 준비
      1. mkdir ${HOME}/mnist
        pushd ${HOME}/mnist >/dev/null
        curl -O "http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz"
        curl -O "http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz"
        curl -O "http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz"
        curl -O "http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz"
        zip -r mnist.zip *
        popd >/dev/null
      2. 위 mnist.zip 파일은 local 에 존재 시키고, spark-submit 할때 인자로 넘길 예정이다.
    2. Pyspark 를 이용한 Data 병렬 전처리 후 CSV 포맷으로 저장
      1. Pyspark 를 이용해서 MNIST 데이타를 traing 셑과 test 셑으로 나누는 부분을 병렬로 수행 해보자.
      2. 여기에 사용되는 example 코드는 tensroflowOnSpark github 에 존재하는 코드이다.
        1. https://github.com/yahoo/TensorFlowOnSpark/blob/master/examples/mnist/mnist_data_setup.py
        2. 위 코드를 사용하였다.
      3. pySpark  로 data 전처리를 병렬로 수행하고 CSV 결과는 Hadoop 에 쓸 예정이다.
      4. 수행 Script 는 아래와 같다.
      5. ${SPARK_HOME}/bin/spark-submit \
        --master yarn \
        --deploy-mode cluster \
        --queue ${QUEUE} \
        --num-executors 13 \
        --executor-memory 22G \
        --archives ./mnist/mnist/mnist.zip#mnist \
        ./mnist/mnist_data_setup.py \
        --output mnist/csv \
        --format csv
        
      6. 위는 official manual 문서와 다소 다르다. 처음 수행을 하면 에러가 나는데, spark yarn cluster 를 이용하였으므로, hadoop job 모니터링 페이지를 통해 에러 로그를 확인 할 수 있다.
        1.   즉, mnist 다운로드한 파일의 경로 참조를 정확히 해주지 못해서 에러가 발생하고 있음을 알수있다.
        2. 경로를 수행한 python 파일과의 상대경로로 정확히 지정하고 나면, 에러가 나지 않고, pyspark yarn cluster 모드로 잘 동작이 된다.
      7. 또 official manual 과 내가 수행한 스크립트의 다른 부분은 아래 옵션을 나는 삭제 했다는 점이다.
        1. --archives hdfs:///user/${USER}/Python
        2. 우리는 앞서 모든 노드에 동일하게 Python 환경을 이미 세팅해 주었고, Path 에도 포함시켜 줬다. 때문에, 굳이 Python을 배포를 통하여 공유되게 하지 않아도 에러없이 수행이 가능하다.
      8. 최종적인 결과 메시지는 아래와 같다.
      9. hadoop 에 CSV  파일이 잘 Write 되었는지도 확인해 보자.
        1. 하이라이트 한 부분 처럼 Hadoop 에 mnist 데이타에 대한 전처리 후 결과 output 이 train 과 test 데이타로 나뉘어 잘 적재 된 것을 확인 할 수 있다.
        2. 이는 앞으로 대용량 데이타에 대한 Python 전처리를 병렬로 수행할 수 있음을 확인 한 부분이다.
    3. Pyspark 를 이용한 Data 병렬 전처리 후 Tensroflow Records 포맷으로 저장
      1. CSV 로 파일을 저장하지 않고, Tenssorflow Records 포맷으로 바로 저장할 수도 있다.
      2. 우리는 이를 하기 위해 윗부분 설치 시점에, 4.2.8 에서 protobuf 까지 설치해가며, tensorflow ecosystem github 의 소스를 내려 받아 maven 빌드 후 tensorflow-hadoop-1.0-SNAPSHOT.jar 파일을 생성한 바 있다.
      3. 해당 jar 를 pyspark submit 시 인자로 넘겨주고 해당 jar 라이브러리를 이용 아래 내용을 수행 할 예정이다.
      4. 수행 스크립트는 아래와 같다.
      5. ${SPARK_HOME}/bin/spark-submit \
        --master yarn \
        --deploy-mode cluster \
        --queue ${QUEUE} \
        --num-executors 13 \
        --executor-memory 22G \
        --archives ./mnist/mnist/mnist.zip#mnist \
        --jars hdfs:///user/moneymall/tensorflow-hadoop-1.0-SNAPSHOT.jar \
        ./mnist/mnist_data_setup.py \
        --output mnist/tfr \
        --format tfr
        
      6. 앞서 CSV 로 HDFS에 저장해보았던 예제의 수행 스크립트 내용과 매우 유사하지만 대표적으로 --jars 옵션을 사용한것이 큰 차이점이다.
      7. 그리고, 소스 내부에서 format 인자로 로직이 분기하므로 --format 값으로tfr 을 넘겨 주었으며, output 경로 또한 csv 가 아닌 tfr 로 변경해 주었다.
      8. hadoop 에 tensorflow 포맷 records 가 잘 저장되었는지 살펴보자.
        1. 스크립트를 수행해주자 위처럼 tfr 폴더가 생성 되었다.
        2. tfr 디렉토리의 상세내용을 살펴보면 아래와 같다.
          1. 역시 test 와 train 으로 구분되었으며, spark job 이 생성하는 파일 명명 규칙으로 파일이 생성 되었다. (파일 내용은 TFRecords 포맷이다.)
    4. MNIST 모델 돌려보기
      1. 위 전처리 및 아래 Tensorlfow 병렬 모델 수행에서의 수행 스크립트는 Dev Zone 에서 테스트 된 관계로 GPU 관련 옵션이 빠져 있다. GPU 모드 병렬 수행을 위해서는 아래 옵션들이 추가되어야 한다.
        1. --queue GPU
        2. --conf spark.executorEnv.LD_LIBRARY_PATH=$LIB_CUDA:$LIB_JVM:$LIB_HDFS \
          --driver-library-path=$LIB_CUDA \
        3. #infiniband 에서의 성능 향상을 위해서는 아래 옵션도 추가하는 경우 
          # 큰 성능 향상을 가져올 수 있다. (설치시에도 옵션 설정 필요)
          --rdma 
      2. Training (using feed-dict)
        1. feed-dict 을 이용한 Traing 용 수행 스크립트는 아래와 같다.
        2. # 재 수행시에는 아래 주석을 풀어주어야 한다. Overwrite error 방지.
          # hadoop fs -rm -r mnist_model
          ${SPARK_HOME}/bin/spark-submit \
          --master yarn \
          --deploy-mode cluster \
          --queue ${QUEUE} \
          --num-executors 13 \
          --executor-memory 22G \
          --py-files ./mnist/spark/mnist_dist.py \
          --conf spark.dynamicAllocation.enabled=false \
          --conf spark.yarn.maxAppAttempts=1 \
          --conf spark.executorEnv.LD_LIBRARY_PATH=$LIB_JVM:$LIB_HDFS \
          ./mnist/spark/mnist_spark.py \
          --images mnist/csv/train/images \
          --labels mnist/csv/train/labels \
          --mode train \
          --model mnist_model
          # to use infiniband, add --rdma
          
        3. 아래는 위 병렬 Training 이 수행되는 동안의 Yarn Batch Job 모니터링 페이지의 모습이다. 일반 Spark Yarn Cluster Job 과 동일한 모양이다.

        4. 최중 수행 뒤 결과 메시지는 아래와 같다.

      3. inference (using feed-dict)
        1. training 이 끝났으므로, inference 즉, 모델의 학습 결과를 확인 해 보자.
        2. 수행 스크립트는 아래와 같다.
        3. # hadoop fs -rm -r mnist_model
          ${SPARK_HOME}/bin/spark-submit \
          --master yarn \
          --deploy-mode cluster \
          --queue ${QUEUE} \
          --num-executors 13 \
          --executor-memory 22G \
          --py-files ./mnist/spark/mnist_dist.py \
          --conf spark.dynamicAllocation.enabled=false \
          --conf spark.yarn.maxAppAttempts=1 \
          --conf spark.executorEnv.LD_LIBRARY_PATH=$LIB_JVM:$LIB_HDFS \
          ./mnist/spark/mnist_spark.py \
          --images mnist/csv/train/images \
          --labels mnist/csv/train/labels \
          --mode inference \
          --model mnist_model \
          --output predictions
          # to use infiniband, add --rdma
          
        4. mode 부분과 output 부분만 차이가 있다.
        5. 주의할 점은 앞에서 mnist_model 을 training 과정에서 이미 수행하였다면, 상단 hadoop 명령어로 mnist_model 을 지우고 수행해야 한다는 점이다. (hadoop 에서의 경로는 적절히 수정 할 것)
        6. 결과는 아래와 같다.
          1. 최종 결과는 output 으로 지정해준 predictions 안에 존재한다.
          2. 아래 명령어로 내용을 살펴보자.
            1. hadoop dfs -cat predictions/part-00000
            2. 결과는 아래와 같다.
      4. training (using queueRunners)
        1. feed-dict 대신 I/O 병목에 더 강점이 있는 queueRunners 모드를 사용해 보자. 여기서는 TFRecords 를 이용하여 좀더 Tensorflow Low Level training 을 할 예정이다.
        2. 수행 스크립트는 아래와 같다.
        3. # hadoop fs -rm -r mnist_model
          ${SPARK_HOME}/bin/spark-submit \
          --master yarn \
          --deploy-mode cluster \
          --queue ${QUEUE} \
          --num-executors 13 \
          --executor-memory 22G \
          --py-files ./mnist/spark/mnist_dist.py \
          --conf spark.dynamicAllocation.enabled=false \
          --conf spark.yarn.maxAppAttempts=1 \
          --conf spark.executorEnv.LD_LIBRARY_PATH=$LIB_JVM:$LIB_HDFS \
          --jars hdfs:///user/moneymall/tensorflow-hadoop-1.0-SNAPSHOT.jar \
          ./mnist/spark/mnist_spark.py \
          --images mnist/tfr/train \
          --format tfr \
          --mode train \
          --model mnist_model
          # to use infiniband, add --rdma
          
        4. CSV 대신 tfr 즉, TensorFlow Records 를 직접 사용할 예정이다.
        5. 기존 hadoop model output 을 제거한 후 수행하자.
        6. 여기서 주의할 점은 2017년 8월 기준 official manual 에 --jars 옵션이 빠져 있어 에러가 난다는 점이다. 위처럼 jars 옵션으로 앞서 만들었던 tensorflow-hadoop-1.0-SNAPSHOT.jar 파일을 포함해 주어야 위 스크립트가 정상 동작 한다.
        7. 최종 결과는 아래와 같다.
[결론]

우선,  PySpark 와 Tensorflow 를 섞어 쓸 수 있는게 매우 편하다. Python 전처리 수시간 걸리던게 PySpark 로 수분안에 끝나는 묘미를 맛보면, 이전으로 돌아가기가 쉽지 않다.
익숙한 Jupyter Notebook 이나 Zepellin Notebook 환경 모두 tensorflowOnSpark 연동이 가능한 것도 장점이다.


그리고, 대부분의 기존 Tensorflow 모델을 10줄 미만으로 수정하여, 병렬화 가능하다는 장점이 돋보인다. Spark 클러스터는 Yarn Cluster Mode 만을 지원하고 있다. 그리고, CPU 병렬 뿐만 아니라 GPU 병렬도 지원한다.

또한, Spark Streaming Mode 도 지원한다. 즉, 앞에 Kafka Cluster Queue 등을 놓으면, site 전체의 Click Stream 등 초 Heavy 트래픽에 대한 input 을 염두하며, 실시간 realtime inference가 되는 사이트 실시간 개인화 모델등을 만든다고 할때, 그러한 연산에 있어서의 Spark-streaming Layer 의 강점을 Deep Learning 모델에도 적용가능하리라 여겨진다.

설치는 Official Document 가 그런데로 상세히 나와 있는 편이지만, Legacy 가 최신 버전일때는 약간의 에러들이 발견되었고, 그래서 위처럼 설치 과정이 좀 길어졌다, 그 경우 소스 레벨 debub 및 log 를 확인해가며 설치를 해야 마무리가 되었던 부분은 약간 아쉬웠다. 즉, 너무 최신 Legacy 버전이 아닌 Official 버전을 사용하여 설치하면 좀더 쉽게 설치가 가능 할 것이다. 그리고, github 에 답변이나 issue 해결 commit 이 거의 매일 올라오고 갱신되는 편이라, 믿음이 간다. elaphs 는 설치해서 테스트 하다가 포기한게, issue 에 대한 대응이 너무 느렸다.

BigDL  역시 SparkML 과 유사한 패턴으로 딥러닝을 할 수가 있어, general 한 모델을 만들때는 더 편할 수 도 있다. 그리고, 자사의 BigData Cluster 가 아직 GPU 는 없고,  intel cpu 기반이라고 하다면, BigDL 이 tensorflowOnSpark 보다 좀더 성능이 잘 나올 수 있다.
하지만, 다양한 고급 모델을 지원하지 못한다는 단점이 분명히 존재한다.

BigData + Deep Learning 콜라보레이션 아키텍처에 대한 고민은 거의 반년 이상 했던거 같다. 여러가지를 실험해 보았고, 결론은 BigDL 과 TensorflowOnSpark 두개로 거의 확정하였다. 둘은 장단점이 극명하여, 어느 하나로 치우칠 필요 없이, 선택적으로 사용하려고 한다.

좀더 실무레벨에서 많은 경험을 한 후 다시 한번 포스팅 해볼 기회가 생겼으면 하는 주제였다.

2017년 8월 28일 월요일

Deep Learning Text NLP with Spark Collaboration

발표한지 좀 시간이 지나긴 했지만, 한 두달여 전 Korea Spark Summit Day 에서 내가 발표했던 내용의 슬라이더 전문이다.

주로 아래 내용을 다루었었다.

1. Machine Learning Approach vs Deep Learning Approach
2. 한글 Text Classification 문제를 전통적인 Machine Learning 으로 풀어보았을때의 장단점 및 성능 수치.
3. 동일한 문제를 Deep Learning  으로 풀어보았을때의 장단점 및 성능 수치.
4. 한글 Text Classification 문제를 다양한 알려진(좀 유명한) Approach 로 각각 접근 했을때, 실무 데이타 기준(IMDB 등 논문에서 등장하는 데이타보다 훨씬 양이 많고, 훨씬 어려운 문제(138지 Top 1 분류)) 성능 수치 비교.
5. Production Level , Real World 의 Big Data Scale Large Data Set 을 가지고 Deep Learning 프로젝트를 진행하는 경우에 접하게 되는 다양한 문제점들.
6. Spark 를 활용하여 Big Data Scale Deep Learning 을 하는 방법론 소개.

아래는 해당 내용의 전문이다.



2016년 12월 31일 토요일

MS R(구 Revolution R) on Spark - 설치 및 가능성 엿보기(feat. SparkR)

BigData Scale Data 분석 및 활용 업무를 하다보면, 도구나 사용 언어, 사용 기술 등에 있어 여러가지 선택의 기로에 서게 되는 경우가 종종 있다.

물론 혹자는 특정 언어나 특정 기술로 많은 것을 처리하는 것을 선호하기도 하지만(전문성 증대, 기술의 장인정신 측면에서 이 방식의 유리한 점이 존재한다.), 혹자는(나를 포함) 다양한 언어나 기술을 섞어 쓰면서, 처한 상황(Problem)에 가장 최적화된 기술이나 언어로 유연하게 접근하는 것을 선호하기도 한다.

그러한(후자) 측면에서 보았을때, R은 다양한 기술을 섞어 쓰는 BigData Scientist(혹은 BigData 개발자나 Machine learning 개발자) 에게는 참 계륵 같은 기술이었다. 소위 보편적인 Data 모델러들은, BigData Scientist 보다 R(SAS, Matlab, SPSS 포함)을 더 잘 다루는 것이 일반적이기도 하거니와, 그들과 Co-Work 접근을 해야 하는 경우는, 이미, R이나 SAS 로 무언가 문제가 직면되어 있는 경우가 많기 때문이다. BigData Scale을 다루는 경우이거나, Heavy 한 Machine Learning 작업이 필요하거나, Deep learning 접근이 필요한 경우가 그러한 경우의 일예라 할 수 있다.

이 경우 서로가 잘하는 영역을 분담하려고 하기 시작하면, R 이외의 다양한 특화 대체 기술을 선택하는 경우가 많은데, 그럼에도 불구하고 R을 버릴수 없는 이유는(앞서 계륵이라고 표현하였다.), R로 만들어진 정교한 모델들이(비록 셈플 데이타만 수행된다 손 치더라도) R이외의 기술들에서는 구현체가 완벽하지 않거나, R에서 쉽게 구현된 모델이 다른 기술에서는 구현이 녹록치 않은 경우가 너무나도 많기 때문이다.

BigData Scale 모델링 Project에서 자주 접하는 시나리오는 아래와 같다.

  1. 순수 모델러들이 R이나 SAS 로 최적화된 알고리즘을 찾아낸다.
  2. 셈플 데이타 혹은 전수로 데이타를 돌려보고, 각종 파라미터나 로직을 거의 완성한다.
  3. But, 모델이 도는데 3박 4일 이상이 걸리거나, 전수 데이타로 돌리는 것 자체가 불가능하다.
  4. BigData 팀으로 Co-Work 요청이 들어온다. 즉, R 모델을 다른 기술을 이용하여 포팅하는 협의가 시작된다.
  5. SparkML 으로 우선 포팅 가능성을 타진한다.
  6. 초대용량의 경우 수십~수백대의 Spark Cluster 위의 SparkML 로도 답이 안나오는 경우가 많은데 이때는 1차로 Mahout 의 구버전으로 Hadoop Map/Reduce  경유 수행이 가능한지 검토한다.
  7. SparkML 이나 Mahout 으로 구현이 쉽지 않은 복잡한 복합 모델의 경우는 제삼의 기술을 검토한다. Weka, PredictionIO, H2O 등등... 하지만 이 프레임워크들은 좀 난잡해서 별로 선호하진 않는다. 오히려 아래 방식을 좀더 먼저 검토한다.
  8. Spark, Mahout 계열에 없거나, Method 가 빈약한 경우 Python을 뒤져본다. SparkML 은 기본 메소드는 있는데, 고급 옵션들이 아예 구현이 안되어 있는 경우가 많다. 예를 들어 Spark ML의 Word2Vec 에는 기본 Method 외에 다양한 고급 활용 메소드들이 거의 존재하지 않아 일일이 구현을 해주어야 하고, FPGrowth 등은 Lift 값 관련 Method가 없어 이를 구할때, 개발자가 수식을 일일이 구현 해줘야 가능하다. 반면에 R이나 Python 들은 논문에 언급된 대부분의 기법들은 거의 구현이 되어 있는 경우가 많다. 이 시점에 R을 보지 않고, Python 을 보는 이유는, SAS 가 그러하듯이 Python 은 메모리에 올려놓고 계산하지 않고, 머신 1대가 오래 걸리더라도 꾸역 꾸역 계산을 하긴 해내기 때문이다.(뒤에 이 부분에 있어서의 R의 역습을 언급하겠다.) 머신 1대가 담기 힘든 용량이면 Hadoop 같은 공유 저장소에 올려 놓고 loop 를 돌리면, 어쨋든 무한 루프를 돌면서도 뻗지 않고 돌아는 간다.
  9. 8의 기술이 죽지는 않지만, 1대라 너무 너무 오래걸릴때는 GPU 카드를 꺼내 든다. 모든 알고리즘이 GPU 버전이 존재하는 것은 아니지만, 조금 뒤져보면 github 에 다양한 공유 구현체 들이 가끔 큰 희망을 줄때가 많다. C로 최적화된 Gensim 등의 Python 라이브러리는 굳이 GPU까지 동원하지 않더라도, 1대에서 돌려도 Spark 5~7 대의 성능과 맞먹는 경우가 종종 있다. 어쨋든 Python 에는 Spark 에 없는 무수히 많은 알고리즘 중 상당수가 이미 구현된 경우가 왕왕 있다. 그리고 대부분의 알고리즘들이 지원하는 고급 기법들이나 옵션들이 더 풍성하게 구현되어 있다.
  10. Python 이 너무 느릴때도 있다. 병렬 수행이 목마를때도 있다. 이 경우 PySpark 로의 Hybrid 사용 가능성을 타진한다.
  11. PySpark 로도 답이 없는 경우는 TensorFlow 카드를 꺼내 든다. Word2Vec 도 그렇지만, TensorFlow 에는 Deep Learning 류의 거의 대부분과, 상당수의 Machine Learning  알고리즘들이 기본 지원되거나, github 구현체가 이미 존재하는 경우가 왕왕 있다. (deeplearning4j 나 theano, caffe, cntk 등 다른 대안도 존재한다.)
  12. 이도 없을 때는 제일 마지막 방법으로 구글링 후 해당 알고리즘의 구현체, 그게 c가 되었건, java 가 되었건을 찾아 내어, scala on Spark, java on Map/Reduce, python on tensorflow 등으로 포팅 가능한지 그 타당성을 타진한다.


위 12번까지 간 적이 몇번 있었다. 몇해전 (지금은 FPGrowth 라는 걸출한 대안이 있지만..) Spark 초기 버전에서 SparkML에 Apriori 가 없어 Java 구현체를 Spark 에서 돌도록 알고리즘을 포팅했던, 별로 유쾌하지 못했던 경험이 있다. (모든 것에 은총알은 없다. SparkML의 FPGrowth 는 Apriori 알고리즘의 훌륭한 대안이지만, 중 규모의 Large 볼륨 데이타에서 빠르고 병렬성을 훌륭하게 지원하지만... Very High 규모의 데이타의 경우에, 그리고, confidence 를 조금 올려놓고 수행하는 경우, 변동성이 큰 데이타의 경우에, disk 를 쓰기 시작 하면서는 무한 loop 틱한 오랜 수행과 리소스 점유로 Production 에 큰 부하를 주는 경우가 왕왕 있다. 이런 Size 의 Data 에는 Hadoop 경유 연산 등 다른 대안을 찾아야 한다.)

이쯤에서 항상 드는 생각이, 거의 모든 알고리즘(Deep Learning 류만 빼고)들이 구현되어 있는 R이 병렬 수행되면 얼마나 좋을까 라는 점이다. (위 Spark ML 의 예에서 처럼 모든 경우의 은총알을 바라는 것은 아니다. 단, R on Spark 와 R on hadoop 으로 나누어져 있다는 것 자체가 좀더 유연할 것이라는 기대를 해본다.)

서두가 좀 길었는데, 아직은 미완의 진행형이긴 하지만, 이러한 측면에서 SparkR 과 MS R(구 Revolution R)의 병렬성 지원을 위한 최근의 행보는 매우 매우 응원을 하는 바이다.

사실 SparkR은 1.X에서는 실무에서 사용하기가 다소 힘든 정도였고, 2.X가 되면서는 다시 실무 활용이 가능한 정도까지 그 가능성이 열리고 있는데, 하지만 여전히 병렬성을 지원하는 알고리즘들이 극히 적다는 단점을 가지고 있다.(Spark 2.X가 되면서 SparkML과의 연동성 지원이 추가되었다.) 이 측면에 있어, MS R은 과거 상용 Revolution R 을 인수한 엔진을 사용하고 있어, 훨씬 다양한 병렬 수행 메커니즘과 훨씬 다양한 병렬 알고리즘 패키지를 이미 지원하고 있으며, 발전 속도도 매우 빨라진 느낌이다.(이 분야의 대부분 솔루션들이 그러하듯이, 아직 완성형은 아니고 진행형이다.)

SparkR 과 MS R이 접근하는 병렬성 메커니즘 접근 방식(SparkR은 Spark엔진 입장에서 R에 접근해가고 있고, MS R은 R입장에서 Hadoop 과 Spark로 다가가고 있다.)이 다소 틀리고 각각의 방식에 장단점이 있는데, 더욱 좋은 점은 둘을 Hybrid 하게 쓰는 것 또한 가능 하다는 점이다.

아래부터는 On Premise Hadoop ( Apache Hadoop 2.7.1 )과 On Premise Spark ( Apache Spark 2.0.2 )위에서 설치 구동 테스트한 설치 세팅 스크립트 이다. 설치 가이드는 MSDN문서를 참고하였으나, MSDN 을 그데로 따라 설치하면 많은 난관에 봉착하게 된다. 아래는 그 부분들의 해결책 까지를 포함하고 있다.

  1. Download
    1. Spark Cluster 를 이용하여 ScaleR을 돌림에 있어, Linux 버전 Install을 해야 하는지, Hadoop 버전 인스톨을 해야 하는지 좀 햇갈렸는데… Manual 을 잘 보다 보면, for Hadoop 을 설치해야 함을 알 수 있다. 즉 위에서 2번째 버전을 다운받아야 한다.
  2. 설치 참고 메뉴얼
    1. Linux 버전과 Hadoop 버전 설치 가이드는 아래 링크에 나와 있다.
      1. R Server Installation for Linux Systems
        1. https://msdn.microsoft.com/en-us/microsoft-r/rserver-install-linux-server
      2. Hadoop installation and configuration for Microsoft R Server
        1. https://msdn.microsoft.com/en-us/microsoft-r/rserver-install-hadoop
    2. Spark 위에서 구동하기 위한 Getting Started 문서는 아래 문서를 참고하면 된다.
      1. https://msdn.microsoft.com/en-us/microsoft-r/scaler-spark-getting-started
      2. 처음 설치하는 경우라면, scaler spark getting started 를 따라하기 전, Linux 버전이 아닌 Hadoop 버전의 MS R 이 기 설치 되어 있어야 한다.
  3. MS R on Spark 설치(본 설치에서 사용한 버전은  MS R Server for hadoop 9.0.1 임.)

  1.   MS R on Hadoop Package 다운로드 후 압축 풀기

 2.     Root 권한으로 install 스크립트 수행해야 함.

  3.     설치는 install.sh -p -p 옵션을 주고 실행 함.
A.      첫번째 에러 만남.

에러 log 에서 크게 단서를 찾을 수는 없었지만, MS R for Hadoop 인 관계로, Hadoop 관련 설치 설정 과정 중의 에러라고 여겨져, 설치하는 계정에 Hadoop Path 추가.
이후 수행하니 위 에러 없이 정상 Install 되었음.

  4.     모든 Hadoop 노드에 다 설치. (설치 과정은 위와 동일. dsh, pdsh, PyDSH, fabric등으로 이를 병렬 수행 할 수도 있기는 함.)
  5.     설치 이후 확인

  6. Hadoop 폴더 생성 및 퍼미션
A.     한쪽 노드에서만 수행. Hadoop 은 공유 되어 있으므로.

  7.     Node에 로컬 공유 폴더 생성
A.     전체 Node 에서 수행.

  8.     아래 경로의 파일을 Hadoop 에 업로드 하여, Test Code 수행 준비.
     A.     아래 경로는 설치 하고 나면, 자동 생성.
                         i.         /usr/lib64/microsoft-r/3.3/lib64/R/library/RevoScaleR/SampleData/AirlineDemoSmallSplit
                        ii.         But, MSDN의 설치 매뉴얼의 주소와 좀 다른 위치에 있어 주의 요망9.0.1 MS R for Hadoop 에서는 위 파일 형태로 Air Polution 셈플 데이터가 존재.
B.      하둡에 업로드할 임시 폴더 생성.
                         i.         hadoop fs -mkdir /tmp/msRsamples/
                        ii.         셈플 파일이 있는 로컬 경로로 이동하여, 해당 셈플 파일을 Hadoop 에 업로드.
                       iii.         hadoop fs -copyFromLocal *.csv /tmp/msRsamples/
C.      로컬의 동일 경로에도 해당 파일 복사 생성.
                         i.         뒤에가서 Hadoop 과 로컬의 비교를 해보기 위함 임.
                        ii.         mkdir /tmp/msRsamples/
cp /usr/lib64/microsoft-r/3.3/lib64/R/library/RevoScaleR/SampleData/AirlineDemoSmallSplit/*.csv /tmp/msRsamples/
  9.     Revo64 구동
A.     cd MRS90Hadoop
B.      Revo64
  10.  셈플 코드 수행 준비

  11.  Hadoop 경로 핸들링
A.     앞에서 생성한 경로 hadoop fs -ls 조회 잘 수행 됨.

  12.  셈플 코드 수행(For Local)
A.     input <- file.path("/tmp/msRsamples/AirlineDemoSmallPart1.csv")
B.      colInfo <- list(DayOfWeek = list(type = "factor", levels = c("Monday", "Tuesday", "Wednesday", "Thursday","Friday", "Saturday", "Sunday")))
C.      airDS <- RxTextData(file = input, missingValueString = "M", colInfo  = colInfo, fileSystem = RxHdfsFileSystem())
D.     adsSummary <- rxSummary(~ArrDelay+CRSDepTime+DayOfWeek, data = airDS)
E.      adsSummary


  13.  셈플 코드 수행(For Hadoop)
A.     rxSetComputeContext(RxHadoopMR(consoleOutput=TRUE))

    위와 같은 에러 발생. 

4. 에러 해결

위 내용 중 libhdfs.so 에 문제가 있는 듯 하여 아래처럼 ldd 로 로컬 로딩 분석해 보았음.
ldd 로 보면 좀 이해가 가지 않는 부분이 있음. , 모듈이 모두 정상 로딩 된 것으로 보인 다는 점 임.


root su 한 다음 root 권한으로도 수행해 보았음.


위 처럼 이제는 좀전의 에러가 보이지 않음. 대신 Hadoop 에서는 root 의 권한설정이 안되어 있어 이번에는 Hadoop 에서의 에러 메시지가 발생하였음.
다시 밖으로 나가서 root 와 사용자 user 계정 모두 Hadoop local User 에 대하여 공동의 SuperGrop 그룹에 속하도록 퍼미션 권한을 모두 맞추어 주고 재 시도 해 보았음.

앞선 에러 로그의 문제는 해결되었음. 하지만, 이번엔 또 아래 같은 에러 발생.



앞선 문제는 해결되는데… tmp/ 폴더가 또 문제.. (기존에 쓰던 Map/Reduce Job의 사용자와 다른 새로운 사용자로 R 을 병렬 수행하면서 새롭게 권한 문제 발생) 
hadoop fs -chmod -R 1777 /tmp tmp 폴더 권한 문제 해결. 하지만 여전히 libjvm.so 를 인식하지 못하는 문제가 계속되고 있음. 다시 ldd 를 해보아도 로딩 잘 되고 있고...

그렇다면 드는 생각. 현재 수행중인 노드는 ldd 로딩이 잘 되는데, 다른 cluster node 는 안되는게 아닐까? 답은 Yes.

최초 local 수행했던 MS R Master 노드에는 libjvm.so 가 심볼릭 링크 존재함. 그런데, 나머지, 인스톨만 했고, local 수행을 한번도 한적 없는 모든 노드는 libjvm.so 심볼릭 링크가 저 위치(/usr/lib64/microsoft-r/3.3/hadoop/)에 존재하지 않음 그래서 아래처럼 모든 노드에 수동 심볼링 링크 추가해줌. (이것 때문에 삽질 엄청 했음. 아마도 각 로컬에서 local mode를 한번이라도 수행해주면 최초 한번 자동으로 설정 될 것 같긴 함. 하지만, 수십대 수백대가 존재하는 Yarn Cluster 환경에서는 모든 노드에서 Local 모드 수행을 일일이 해주기 힘듦므로 아래 처럼 심볼릭 링크 수행을 distributed Shell 로 수행하는 것이 유리.)

cd /usr/lib64/microsoft-r/3.3/hadoop
su
ln -s /data01/java/jdk/jre/lib/amd64/server/libjvm.so

위 경로는 본인 JAVA_HOMEjdk 위치를 따라야 함. (이를 감지하는 코드가 /usr/lib64/microsoft-r/3.3/Hadoop 경로 안의 getHadoopEnvVars.py 인 것으로 사료 됨.)


위처럼 해주자 모든 문제가 해결되었고, 결과가 아래처럼 Hadoop 버전에서도 동일하게 나왔음.


아래는 Spark 를 통한 수행.(Spark Mode 수행 전 Spark 는 Hadoop 과 연동되어 잘 설정되어 있어야 한다.) Hadoop 을 통한 수행보다 약 4~5배 더 빠름 ^^. 유레카~~~~





5. 결론

우여곡절이 좀 있었지만, 일단, Hadoop Cluster + Spark Cluster 에 MS R 을 전체 노드에 설치하고, Local 수행, on Hadoop 수행, on Spark 수행이 정상적으로 잘 이루어짐을 확인하였다. 대용량 Data Handling 및 병렬 알고리즘 수행 에 대한 위 세가지 Mode 에서의 성능 수치 및 Spark R 과의 deep dive 비교는 다음번에 별도 블로그로 언급해 보도록 하겠다.

당연하겠지만, MS R on Hadoop 보다 MS R on Spark 가 더 수행속도가 빠르다. 그리고, 메모리에 순식간에 로딩되는 작은양의 Sample Data 는 Local 1대 수행이 가장 빠른것도 사실이다. Spark 수행은 그래서 대용량 처리에 있어, 약간 중간계 같은 느낌이다.

속도가 아닌 용량의 측면에서 보자면, local 보다는 spark memory cluster 가, 그리고 spark memory cluster 보다는 hadoop distributed File System cluster 가 훨씬 강력한 것이 사실이기 때문이다.

MS R 은 그 전신인 Revolution R 을 MS가 인수하여, 일부는 Open Community 버전으로 Free 버전으로 오픈되어, 다중 쓰레드, 다중 CPU, disk 기반 iteration 등 기존 Open R의 단점을 많이 보완한 새로운 feature 를 기본 포함하고 있다. 오늘 실험했던 Hadoop 위에서의 병렬 구동, Spark 위에서의 병렬 구동은 MSDN 라이센스가 필요하지만, Azure 위에서 종량제 HDInsight Hadoop + Spark 클러스터를 구동하면, 누구나 몇분만에 클라우드 상에서 유연하게 Node 갯수를 조정해 가며 사용 가능하여, 그다지 먼나라 기술도 아니라 할 수 있다.

모델러가 R 로 넘긴 소스를 BigData scale 로 포팅함에 있어, R 코드를 최대한 재 사용하면서 scale out 을 할 수 있다는 것 자체만으로도, 오늘 실험한 구성은 그 가능성이 무궁무진 하다 하겠다. R의 시각화 기능에 있어서의 막강함은 BigData 개발자들에게 선물같은 기능이 아닐 수 없다.

Spark 는 제트기 같은 민첩함을 제공하지만, 종합운동장 보다 큰 활주로가 있어야 한다. Hadoop 은 듬직하지만, 항공모함 역할 이상을 하긴 힘들었다. Hadoop 위에 Spark 는 항공모함 위에 제트기 처럼, 그 둘의 시너지로 막강한 화력을 제공해주는 것을 우리는 이미 경험한 바 있다. 우리는 가끔 육지에서 조용하게 정교한 작전을 수행하고 돌아오기 위해 Python 이라는 Hummer 고성능 군용 전처후 차량이 필요할 때가 있었다. 스파크보다 빠르지 않아도 된다. 대용량이 좀 힘들어도 상관 없다. 정교한 육지 수색을 할때 제트기 보다는 특공대를 태운 Hummer 차량이 더욱 믿음직한 것 처럼 그 자체로서 역할이 충분히 독보적이었다. 오늘 접한 분산 R 은 그런 측면에서 화력에 비유하자면, Apache 헬기 같은 존재라 할 수 있다. 제트기(Spark) 보다 빠르지 않아도 된다. 할주로가 없는 구석 구석 까지 수색을 하기 위해서 우리는 특공대를 태운 Hummer 차량밖에 대안이 없었으나, 이제는 Apache 헬기를 타고 한번에 더 빠르게 날라갈 수 있으며 그들을 지원사격해줄 수도 있다.

Local Area 에 있어서는, 람보 1인 이면, 모든게 해결 되기도 했었다. 홀로(1대로) 외롭게 싸우던 람보(R)를 Apache 헬기에 태워 여기저기 데리고 다닐 수 있다면...?! 여러곳에 독자적으로 나누어져 있던 람보를 수송헬기에 태워 때(때거지로, 즉, 대용량으로)로 다니게 할 수 있다면...?! 하는 즐거운 상상을 해본다... 람보에 날개 달기(분산 R) 프로젝트는 아직 진행형 이지만, 그 가능성이 심상치만은 않은 이유라 하겠다.