시작하기전 정품 비트세이버가 설치 되어있어야지 설치가 가능하다.

 

비트세이버 커스텀 곡을 플레이 해보고 싶다면 BMBF를 반드시 설치를 해야하는데 이 apk를 어떻게 설치를 해야할지가 좀 막막했다.

 

핸드폰이면 그냥 apk다운받아서 설치하면 되지만 오큘러스는 그리 쉽게 허락을 하지 않았으니;;

 

알아보니 여러가지 방법이 있었고 그 중에서 sideloader로 오큘러스에 apk를 설치하는 방법이 있어서 이를 활용해보려고 한다.

 

1. sideloader를 다운받기 위해서 간단한 양식을 작성한다.

headjack.io/tutorial/sideload-install-app-apk-oculus-go-quest/#tool

 

How to Sideload Apps on Oculus Go & Quest + FREE sideloading tool

This article explains three ways to install APKs on an Oculus Go / Quest, and also includes a FREE sideloader tool to make your life easier.

headjack.io

위 사이트에 들어가서 이름하고 이메일을 작성하고 SEND ME THE TOOL을 누르면 confirm메일이 오고 confirm을 하면 다운로드 링크가 담긴 메일이 하나 더 온다. (메일이 안왔으면 스팸이나 프로모션 항목을 확인해보면 있다.)

 

다운로드받고 압축을 풀고 실행을 하면 셋업이 진행되고 그 동안 스텝 2를 진행한다.

 

2. BMBF를 다운로드한다. (BMBF는 불법이라서 자동 업데이트가 안된다, 만약 업데이트를 하고 싶다면 기존의 버전을 삭제하고 새로운 버전을 다시 다운로드해서 설치하는 방법을 사용해야한다.)

bmbf.dev/stable

 

BMBF Stable Releases

Update to Beat Saber 1.12.1 and 1.12.2! Lots of changes in this update, with more to come... What's new?: Modloader updates Better game detection (should solve some com.beatgames.beatsaber-1 issues, or failure to recognize that the game has been uninstalle

bmbf.dev

위 사이트에서 BMBF 안정된 버전의 apk를 다운로드 한다.

 

3. 오큘러스 개발자모드 활성화

오큘러스 앱을 설치한 핸드폰에서 쉽게 설정이 가능하다. (같은 와이파이 사용해야지 설정이 가능함)

앱을켜고 -> 설정(맨 하단 우측) -> 연결된 오큘러스 디바이스 클릭 -> 그 외 설정 클릭 -> 개발자모드 클릭 및 활성화

 

4. 오큘러스의 USB 디버깅 허용 및 파일 엑세스 허용 그리고 인스톨

sideloader가 다 셋업이 된것 같다면 USB로 오큘러스를 연결하고 USB 디버깅 허용 및 파일 엑세스 허용을 하고(오큘러스를 착용하고 허용해야 함) 다 되었으면 컴퓨터로 돌아가 다운 받은 apk를 드래그 및 드롭을 해서 인스톨을 진행한다.

 

마지막으로 오큘러스를 쓰고 메뉴판에서 을 클릭하고 맨 상단 오른쪽에 알수없는 출처라고 적힌 항목을 클릭해서 설치하면 된다.

'취미' 카테고리의 다른 글

2021년 4월 2일 NHK 뉴스 번역  (0) 2021.04.03
2021년 4월 1일 NHK 뉴스 번역  (0) 2021.04.01
2021년 3월 31일 NHK 뉴스 번역  (0) 2021.03.31
2021년 3월 30일 NHK 뉴스 번역  (0) 2021.03.31
2021년 3월 29일 NHK 뉴스 번역  (0) 2021.03.31

이글은 반복되는 Ubuntu 세팅 및 CUDA 설치가 번거로워 한번에 모아보기 위해 참고용으로 작성하였다.

 

주의, Nvidia 드라이버를 따로 설치하지 말고 GUI 사용자는 1번 항목을 따라하지 말고 개별 1. 항목을 따라야한다.

 

1. GUI 기능 끄기(옵션)

$ sudo systemctl set-default multi-user

 

개별 1. Nouveau 비활성화 및 재부팅[1]

$ sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
$ sudo bash -c "echo options nouveau modeset=0 >> /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
$ sudo reboot now

 

2. 패키지 추가 및 업데이트, CUDA 설치 그리고 재부팅[2]

$ wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
$ sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
$ wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb
$ sudo dpkg -i cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb
$ sudo apt-key add /var/cuda-repo-ubuntu2004-11-0-local/7fa2af80.pub
$ sudo apt-get update
$ sudo apt-get -y install cuda
$ sudo reboot now

 

 

그림 1. 과 같이 nvidia-smi를 입력해 잘 나오는지 확인한다.

 

그림1. CUDA설치후 CUDA버전 및 GPU가 잘 잡히는지 nvidia-smi로 확인할 수 있다.

 

3. 아나콘다 설치 및 파이토치 설치[3, 4]

# 아나콘다 설치(설치 마지막 물음인 conda init 항목은 yes로 해야한다.)
$ wget https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh
$ chmod +x ./Anaconda3-2020.11-Linux-x86_64.sh
$ ./Anaconda3-2020.11-Linux-x86_64.sh
$ source .bashrc

# 만약 아나콘다 설치중 마지막 conda init 물음에 no로 답한 경우만 진행
$ ./anaconda3/bin/conda init
$ source .bashrc

# 파이토치 설치
$ conda install pytorch torchvision torchaudio cudatoolkit=11.0 -c pytorch

 

 

최종적으로 파이토치에서 GPU사용가능 여부를 확인한다.

$ python
>>> import torch
>>> torch.cuda.is_available()

 

참고문헌

1. How to disable/blacklist Nouveau nvidia driver on Ubuntu 20.04 Focal Fossa Linux, Available Online: linuxconfig.org/how-to-disable-blacklist-nouveau-nvidia-driver-on-ubuntu-20-04-focal-fossa-linux 15 Feb 2021.

2. CUDA Tollkit 11.0 Download, Available Online: developer.nvidia.com/cuda-11.0-download-archive 15 Feb 2021.

3. Anaconda Individual Edition Installer, Available Online: www.anaconda.com/products/individual 15 Feb 2021.

4. Pytorch Get Start Locally, Available Online: pytorch.org/get-started/locally/ 15 Feb 2021.

처음에는 당황했으나 아래 메세지를 읽어보니 힌트를 얻을 수 있었다.

 

Tell CMake where to find the compiler by setting either the environment
variable "CUDACXX" or the CMake cache entry CMAKE_CUDA_COMPILER to the full
path to the compiler, or to the compiler name if it is in the PATH.

 

cmake가 쿠다 컴파일러를 찾지 못해서 발생한는 에러로 [1]을 참고해 아래와 같이 쿠다 경로만 지정해 주면 끝난다.

 

export CUDA_HOME=/usr/local/cuda 
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda/extras/CUPTI/lib64 
export PATH=$PATH:$CUDA_HOME/bin

 

쿠다가 여러개 깔려있다면 위의 경로말고 원하는 쿠다버전 경로로 설정해야한다. 그렇지 않으면 최근에 깔린 버전이 걸린다.

 

참고문헌

1. No CMAKE_CUDA_COMPILER could be found, Available online: github.com/jetsonhacks/buildLibrealsense2TX/issues/13 24 Dec 2020.

PCIe ssd하고 SATA ssd 두개를 혼합해서 사용하다가 SATA ssd를 1개 더 추가해서 RAID로 묶어서 사용하려고 했다.

그런데 BIOS에서 묶는데는 성공했으나 ubuntu installation 화면에서 PCIe ssd가 안잡히는 일이 발생하였다.

 

슬롯 문제인가 했는데 아니였다.

 

계속 삽질하다가 우연히 나랑 비슷한 일을 겪은 사람의 글[1]을 발견하고 힌트를 얻어 RAID를 사용하기 위해서 켜놓은 intel rapid storage technology 기능을 혹시나 해서 꺼봤더니 되었다.

 

참고문헌

1. M.2 Samsung SM951 NVME SSD Not Recognized on Linux, Avaiable online: superuser.com/questions/1022849/m-2-samsung-sm951-nvme-ssd-not-recognized-on-linux 24 Dec 2020.

SSH로만 접속해서 GUI를 사용하지 않는데 켤 때 마다 GPU를 조금 잡아먹음(14MB) 눈에 거슬려서 다음과 같이 함

 

끄기

$ sudo systemctl set-default multi-user

켜기

$ sudo systemctl set-default graphical

 

임차권등기명령 신청전 나의 상황은 아래와 같다.

신청날짜: 2020.12.07 (계약 해지일(2020.12.06) 이후에(2020.12.07 부터) 신청이 가능하다.)

계약기간: 2018.12.07 ~ 2020.12.06

보증금: 이억이천삼오백만원

소명자료: 녹취록(사용안함), 의사표시 공시송달(단, 공시송달 확정일이 계약 해지일(2020.12.06)로부터 최소 1개월 전(최소 2020.11.06 이전에 확정이 되어야함) 이여야지 인정이 됨)

주택도시공사 전세보증에 가입된 상태(이거 없었으면 답이 없었을수도...)

 

사건내용은 이렇다.

약 2020.08.15쯤(확실하지 않음) 임대인에게 계약해지를 통보하기 위해 임대인에게 전화를 했지만, 전화에 응하지 않았다. 그 뒤 몇번 더 시도를 했지만 역시 전화를 받지 않았으며 어쩔 수 없이 내용증명을 우편으로 임대인 주소지로 전달하였는데, 반송이 되었다. 처음에는 주소가 잘못 되었나 생각했는데 집배원의 말에 의하면 집에 개소리가 들렸고 사람이 없는 집은 아닌것 같다고 했다. 그래서 반송된 내용증명을 바탕으로 의사표시 공시송달을 신청하였으며 그 후 공시송달(공시송달은 법무사를 통해서 신청을 하였다.)이 끝나기 전에 다행이 임대인이랑 연락이 되어서 급하게 녹취를 진행하였다. (임대인이 못 믿겨워서 공시송달은 그냥 두었다...) 임대인 사정을 들어보니 코로나때문에 임대사업이 망했다나 뭐라나... 그래서 빚이좀 있어서 연락을 잘 안받고 도망다니는? 신세로 보였다;; 아무튼 이런저런 말을 하는데 결론은 돈이 없어서 보증금을 못 돌려주겠다는 거고... 하지만 다행이 주택도시공사 전세보증에 가입이 된 상태이고, 보증금을 받으려면 진행절차 중 가장 중요한 임차권등기명령을 신청해야 한다고 해서 이렇게 신청하기에 이르게 되었다. (살면서 이런일도 다 있더라...)

 

처음에는 인터넷으로 신청을 하려고 했으나 집에 프린터기, 스캐너(사진으로 찍으면 되지만 제일 중요한 문서출력이 안된다.)도 없고 이상하게 전사소송사이트를 접속하려고 하면 보안시스템이 충돌나는지 보안프로그램을 설치해도 계속 문제가 생겨서 그냥 직접 지방법원에 가서 서류를 작성하기로 마음먹었다...

 

처음에는 복잡할 줄 알았지만 1시간 내로 금방 할 수 있는 내용이고 인터넷으로 혼자 모르는 용어랑 시름하는 것 보다는 직원에게 물어봐서 정확하고 빠르게 진행을 할 수 있어서 인터넷보다는 그냥 시간좀 내서 관할 지방법원에 찾아가는게 더 좋다고 생각한다. (물론 돈만있으면 가장 좋은건 법무사에게 맏기면 되지만...)

 

준비해야할 서류는 다음과 같다.

1. 등본 또는 초본(주소가 나와야함)

2. 부동산 등기부등본(이건 법원가서 해도되고 아니면 동사무소 또는 인터넷 또는 무인기기에서 출력이 가능하다.)

3. 임대차계약서

4. 소명자료(나의경우 공시송달관련 자료를 첨부함)

5. 신분증

 

이정도면 되고 나머지는 직접가서 펜으로 작성하면 된다. 작성하는 항목도 어렵지않고 모르면 그냥 물어보면 된다. 그리고 등록세, 송달료등 결제가 필요하기 때문에 5만원 정도 적당히 준비해서 가면 된다.

 

신청에는 무리가 없었고(법원거리가 멀어서 좀 고생했지만...) 다른 게시글을 보니깐 도면을 첨부하는 사람도 있던데 도면은 필수가 아니라 선택이라서 없어도 상관 없다고 한다. 그리고 처음에는 공시송달처럼 법무사에게 맞기려고 했는데 가격이 좀 생각보다 비싸서;; 막상해보면 그 가격이 나올리가 없는데,,, 이번일로 다음에 비슷한 일이 터지면 그냥 직접하는게 좋다고 생각한다.

 

추가로 주택보증공사에서는 계약일이 끝나고 1개월뒤 보증금을 못받을 때 보증사고로 본다고 하는데 이때 법원에서 온 임차권등기명령 서류를 가지고 있다가 1개월 뒤 이행청구를 진행하면 된다고 한다.

 

추가로 신청을하고 하루정도 지난다음 나의 사건검색 에서 등기명령 신청후 법원에서 받은 사건번호를 검색하면 접수가 잘 되었는지 확인이 가능하다. 사건번호를 잊어버렸다면 송달료를 결제한 영수증에 있는 은행번호로 송달료 조회 로 사건번호를 찾을 수 있다.

'기타' 카테고리의 다른 글

마취 없이 손톱 제거 후기  (0) 2021.02.18

코사인 유사도(cosine similarity)는 두 벡터간의 방향 유사도를 나타내며 코사인 값으로 -1 ~ 1 사이의 값이 나온다.

1에 가깝다면 두 벡터는 같은 방향을 바라보고 있다는 의미이고 0에 가까우면 두 벡터는 직교 그리고 -1에 가까우면 두 벡터는 반대 방향을 바라보고 있다는 의미가 된다.

 

수식으로 살펴보면 아래와 같다.

$$similarity(V_{a}, V_{b}) = \frac{ V_{a} \cdot V_{b} }{ \|V_{a}\|_{2} \times \|V_{b}\|_{2} } = V_{a(normalized)} \cdot V_{b(normalized)}$$

 

계산을 진행 할 때는 맨 마지막 항 처럼 정규화된 두 벡터의 내적만 계산하면 되고, 이를 numpy를 사용하여 그대로 구현하면 아래와 같이 작성을 할 수 있다.

 

코드에는 eps를 사용하여 만약 L2 norm 값이 1e-12보다 작은 경우(사실 원래 목적은 0이 되는 걸 방지하려고 넣은거임)를 제거하기 위해서 추가하였다.

 

사실 pytorch나 scikit-learn에 있는 코사인 유사도를 사용하면 편하지만, numpy로 구현한 이유는 속도 측면에서 이득이 있기 때문에 이렇게 구현했다. 간단하게 scikit-learn과 numpy를 비교한 결과는 아래 그림1과 같다.

 

그림1 어레이 크기에 따른 scikit-learn과 numpy의 간단한 속도 비교결과

 

비교에 각 2차원 어레이를 사용하였다. 예로 가로축 값이 10이면 10x10 행렬을 계산에 사용했다는 의미가 되며 세로축은 해당 행렬을 계산하는데 걸린 시간을 의미한다.

 

실험을 진행한 코드는 아래와 같다.

결론은 큰 차이를 보기 힘들엇지만 시간이 중요하다면 이렇게 만들어서 사용하는 것도 괜찮을 것 같다.

지금 쓰고있는 내용은 [1]에서 발생하는 노트북 문제에 대해서 서술하는 내용이다. 처음 제작할때는 pyspark를 고려하지 않았다.

 

jupyter에서 pyspark를 처음사용할때 그냥 !pip install pyspark만 하면 끝나는줄 알았지만 작동구조상 버전에 맞지않게 설치하게되면 작동이 그림 1 처럼 안된다..(그냥 설치하면 pyspark 3.0.1(최신버전)로 설치됨을 확인했다.)

 

그림 1. jupyter에서는 그냥 pip 로 설치해서 바로 사용이 안된다.

해결방법은 내가 알고있는 선에서는 2가지 있다. 버전을 맞추거나 그래도 안되면 스파크파일 내부에 setup.py가 있는데 이로 설치를 진행하면 된다.

 

1. 버전에 맞게 설치

큰 문제는 버전이 맞지 않기 때문이며 그림 2 처럼 버전을 맞게 설치하면 대부분 해결된다.

그림 2. 버전에 맞게 설치하면 의외로 간단히 해결된다.

2. setup.py로 설치

사실 pip 패키지 배포에 문제가 없는 이상 대부분 잘 작동이 되겠지만 혹시 모르니,, 안되면 spark 바이너리를 다운받으면 내부에 보통 python 디렉토리에 파이썬 설치파일이 들어있다. 이를 이용해서 그림 3 처럼 설치하면 깔끔하게 설치가 가능하다.

그림 3. pyspark 설치를 진행하기전 설치 명령어를 보여준다.
그림 4. !pip 과정 없이 바로 실행이 가능하다.

비교적 간단한 내용이지만 나중에 또 시간 낭비할까봐 메모로 남겨둔다.

 

참고문헌

1. Available online: github.com/titania7777/SparkNotebook 13 Oct 2020.

이 글은 ray 도큐먼트(document)[1]를 바탕으로 요약 정리 및 코드 실행 결과를 적어놓은 글이다.

 

ray 프레임워크(framework)는 분산 어플리케이션을 구축할 수 있도록 도와주는 API이다. 내가 ray를 공부하기 시작한 이유는 당연히 분산처리를 해야할 데이터가 생겼기 때문이다. 분산처리 프레임워크로 윈도우에서는 현재 이슈가 좀 있지만 공식적으로 윈도우도 지원하며 언어는 파이썬과 자바를 지원한다.

 

분산처리를 도와주는 프레임워크이기에 분산처리를 이용할 수 있는 곳이면 어디든지 사용이 가능하다 예로

  1. 하이퍼파라메터(hyperparameter)를 튜닝할때 그리드 서치(grid serach)를 이용한다고 가정 한다면 찾고자 하는 값의 범위나 모델의 크기에 따라서 다르지만 보통 많은 시간을 투자해야한다. 이때 병렬로 처리를 하면 무척 빠르게 값을 찾는 게 가능할 것이다[2].

  2. 강화학습(reinforcement learning)에서는 설정한 환경에 따라서 많은 탐색(exploration)을 해야하는 경우가 있고 때에따라 보상(rewad)을(를) 제대로 받지 못할 수도 있다. (주로 상황에따른 보상을 제대로 설정하지 않아서 의도치않은 상황이 발생하는 경우도 있다.) 그러면 학습속도가 굉장히 느려지는 경우가 발생하는데 이때 조금 억지스럽지만 환경 및 액션 설정들을 바꾸기가 힘들고 이대로 학습을 계속 진행해야 하는 상황 이라면 A3C같은 병렬처리 강화학습 알고리즘을 고려할 수 있다.

  3. tensorflow 나 pytorch로 모델을 만들고 학습을 진행할때도 병렬로 학습을 진행할수도 있다. 이경우 만약 그래픽카드가 한컴퓨터에 여러개 붙어있는 경우라면 유명한 apex[3]를 사용해 GPU를 효율적으로 활용 할 수도 있지만 여러 컴퓨터가 물리적으로 분리가 되어있는 상황에서 ray를 활용하면 여러 컴퓨터에서 병렬처리를 할 수가 있다.

  4. tensorflow나 pytorch로 모델을 만들었다면 이제 서빙(serving)을 해야한다. 이때도 ray를 활용하여 병렬적으로 처리를 진행할 수도 있다. 아니면 상황에 따라서 도커를 이용해 컨테이너화를 진행하고 오케스트레이션을 통해서 진행하는 방법도 있다.

일반적으로 ray를 사용한다면 다음 6가지 함수를 기억하고 넘어가면 좋다.

 

  1. ray.init()
    => ray 컨텍스트(context) 초기화를 진행한다.
  2. @ray.remote
    => 병렬처리를 위한 ray만의 파이썬 데코레이터(decorator)로 기본적으로 함수에 붙여서 사용하며 클래스(class)에 붙여서 사용하면 액터(actor)라고 부른다.
  3. .remote()
    => @ray.remote 데코레이터를 붙였다면 언제든지 호출을 할수가 있으며 .remote()를 호출한 함수에 인자(argument)를 던져줄때 .remote()에 대신 던져준다.
  4. ray.put()
    => 오브젝트 저장소(object store)에 받은 인자값을 저장하며 이때 오브젝트의 특정한 ID값을 던져준다. 이 기능은 같은 인자를 반복적으로 넘길때 오버헤드를 줄여주는 특별한 기능을한다.
  5. ray.get()
    => 오브젝트의 ID값에 해당하는 값을 돌려준다. 이 함수가 호출될 때 비로소 분산처리가 시작되며 연속적으로(sequential) ray.get()을 지정했다면 선 ray.get() 작업이 끝날때 까지 뒤 작업들은 기다리게 된다.
  6. ray.wait()
    => .remote()로 지정된 오브젝트들 중에서 준비가 된 오브젝트의 ID를 돌려준다.

*지금부터 시작할 모든 실습은 모두 도커에서 진행하였다.

팁 1: ray.get의 사용의 주의

ray.get()을 잘못 사용하는 경우 발생할 수 있는 문제에 대해서 다룬다.

시작하기전 간단한 지연 함수를 만들어서 테스트를 해본다. 아래 코드1 은 파이썬의 지연함수 time.sleep(1)를 사용해서 do_some_work(x)를 부를때마다 1초 지연하는 코드이다.

# 코드 1
import time
from datetime import datetime

def do_some_work(x):
    time.sleep(1) # 1초 지연.
    return x

start = datetime.now()
results = [do_some_work(x) for x in range(4)]
print("duration =", datetime.now() - start)
print("results = ", results)

코드 1의 결과 대략 4초가 걸린다

코드 1의 결과를 보면 예상대로 대략 총 4초가 나왔다. 이제 ray를 통한 병렬처리로 시간을 단축해보겠다. 시작전 잘못된 사례를 살펴보겠다. 아래 코드 2는 ray.get()의 사용의 잘못된 예의 코드이다.

# 코드 2
import time
from datetime import datetime
import ray

ray.init() # ray 컨텍스트 초기화

@ray.remote
def do_some_work(x):
    time.sleep(1) # 1초 지연.
    return x

start = datetime.now()
results = [ray.get(do_some_work.remote(x)) for x in range(4)]
print("duration =", datetime.now() - start)
print("results = ", results)

코드 2의 결과 대략 4초가 걸린다(위의 오류는 도커에서 실행해서 발생한는 오류로 지금 진행할 내용들은 전부 단순 튜토리얼 이니 신경쓸필요 없다.)

코드 2의 결과는 코드 1의 결과랑 비슷함을 알수가 있다. 그러나 만약 병렬 처리를 하고싶다면 이런 결과는 원치 않을것 이다. 문제는 ray.get()을 호출할때 연속적으로 for문을 통해서 호출을 해준것이 문제이다. 이렇게 연속적으로 ray.get()을 호출하게 되면 나중에 호출된 ray.get()은 앞서 호출된 ray.get()의 작업이 끝날때 까지 기다리게 된다. 그래서 순차적으로 ray.get()을 호출하지말고 병렬처리하고 싶은 함수나 클래스에 .remote()를 불러주고 ray.get()은 한번만 호출한다고 생각하면 된다. 아래 코드 3이 올바른 ray.get()의 사용법이다.

# 코드 3
import time
from datetime import datetime
import ray

ray.init() # ray 컨텍스트 초기화

@ray.remote
def do_some_work(x):
    time.sleep(1) # 1초 지연.
    return x

start = datetime.now()
results = [do_some_work.remote(x) for x in range(4)]
results = ray.get(results) # ray.get()은 한번만 불러준다.
print("duration =", datetime.now() - start)
print("results = ", results)

코드 3의 결과 대략 1초가 걸린다.

코드 3의 결과를 보면 대략 1초안에 4초짜리 프로세스를 병렬로 진행하였다.

 

팁 2: 매우 작은 작업에는 ray는 오히려 독이된다.

ray는 기본적으로 분산처리를 위한 프레임워크이다. 이 말은 여러개의 프로세서들을 통제하기 위해서 스케줄링, 프로세스간 커뮤니케이션, 시스템 상태 업데이트등 많은 추가적인 작업이 들어간다. 그래서 만약 저 위해서 했던 간단한 작업은 1초짜리지만 0.1밀리세컨드로 줄인다면(해야할 작업이 ray의 준비 작업시간보다 빠르다면)이는 오버헤드가 걸렸다고 하며 옳지못한 결과를 보게 된다. 코드 4는 코드1의 1초 부분을 0.0001초로 바꾼 것 이다.

# 코드 4
import time
from datetime import datetime

def do_some_work(x):
    time.sleep(0.0001) # 0.1밀리세컨드 지연.
    return x

start = datetime.now()
results = [do_some_work(x) for x in range(4)]
print("duration =", datetime.now() - start)
print("results = ", results)

코드 4의 결과 0.6밀리세컨드가 걸린다.

코드 4의 결과를 보면 실행에 대략 0.6밀리세컨드가 걸렸다. 과연 이 작업을 ray를 이용해 병렬로 처리할때 더 빠르게 처리를 할 수 있을까? 코드 5는 코드 3의 1초 지연을 0.0001초로 바꾼 것 이다.

# 코드 5
import time
from datetime import datetime
import ray

ray.init() # ray 컨텍스트 초기화

@ray.remote
def do_some_work(x):
    time.sleep(0.0001) # 0.1밀리세컨드 지연.
    return x

start = datetime.now()
results = [do_some_work.remote(x) for x in range(4)]
results = ray.get(results) # ray.get()은 한번만 불러준다.
print("duration =", datetime.now() - start)
print("results = ", results)

코드 5의 결과 대략 19.3밀리세컨드가 걸린다.

코드 5의 결과를 보면 실행에 대략 19.3밀리세컨드로 오히려 더 오래걸린다. 이 처럼 가벼운 작업에 있어서는 그냥 ray없이 그냥 사용하는 편이 더 나을수도 있다.

 

팁 3: 같은 오브젝트를 .remote()에 반복해서 넘길때는 ray.put()을 명시적으로 사용하자.

병렬처리를 하는데 있어서 같은 오브젝트를 동시에 실행하고 싶다고 가정 해본다. 그러면 자연스럽게 처리에 사용할 오브젝트를 복사해서 동시에 실행해야 되지 않을까 하는데 쓰는 작업이 있다면 모를까 읽기 작업에 있어서는 사용할 오브젝트를 복사해서 전달해줄 필요가 전혀없고 하나의 오브젝트만 전달해서 공유해 사용을 할수가 있다. 이때 ray.put()이 힘을 발휘한다. 코드 6은 numpy로 만든 비교적 큰 오브젝트를 연속해서 넘기고 있다.

# 코드 6
from datetime import datetime
import numpy as np
import ray

ray.init() # ray 컨텍스트 초기화

@ray.remote
def no_work(x):
    return

start = datetime.now()
x = np.zeros((5000, 5000))
results = [no_work.remote(x) for x in range(20)]
results = ray.get(results)
print("duration =", datetime.now() - start)

코드 6의 결과 대략 2초가 걸린다.

일반적으로 코드 6과 같이 작성을 하면 ray는 내부적으로 ray.put()을 여러번 부르게 되고 병렬처리에 있어서 오브젝트를 계속 복사하는데 많은 시간을 사용하게된다. 이때 이를 방지하기 위해서 코드 7과 같이 명시적으로 ray.put()을 한번만 호출하여 리턴된 오브젝트 ID값을 .remote()의 인자로 넘겨주면 된다.

# 코드 7
from datetime import datetime
import numpy as np
import ray

ray.init() # ray 컨텍스트 초기화

@ray.remote
def no_work(x):
    return

start = datetime.now()
x = ray.put(np.zeros((5000, 5000))) # 오브젝트 ID x를 .remote()에 넘긴다.
results = [no_work.remote(x) for x in range(20)]
results = ray.get(results)
print("duration =", datetime.now() - start)

코드 7의 결과 대략 89.8밀리세컨드가 걸린다.

코드 7에서 단순히 명시적으로 ray.put()을 호출해준것 밖에 없는데 확실히 성능이 눈에띄게 좋아졌다.

 

팁 4: ray.wait()을 적극 활용해 병렬 파이프라인 처리의 특징을 최대한 활용하자.

팁 1 에서는 모든 프로세스가 같은 처리 시간을 갖는다고 가정을 했고 ray.get()을 한뒤 그 후 처리에 있어서는 언급을 안했다. 만약 모든 프로세스가 다른 처리 시간을 갖는다고 가정하면 팁 1에서는 빠르게 동작 할지라도 마지막 가장 늦은 프로세스가 끝날때 까지는 기다려야 한다. 그후 값을 돌려받고 다음 처리를 진행하게 되는데 이때 처리가 빨리 끝나는 프로세스값을 먼저 받아서 먼저 처리를 하면 그 성능을 더욱 극대화 할수가 있다. 빨래를 3번 걸쳐서 진행한다고 할 때 첫 번째 세탁물의 세탁이 끝나고 건조 중일 때 다음 세탁물은 이전 세탁물의 건조가 끝날 때 까지 기다 지리 않고 계속해서 세탁을 계속 진행할 수가 있듯이 말이다. 이 과정에서 누가 먼저 끝났는지 알기 위해서 ray.wait()을 사용한다. 먼저 코드 8은 연속균등분포(uniform distribution)에서 0 ~ 4 사이 랜덤으로 샘플링해 지연을 한다. 그리고 병렬 파이프라인 처리 활용의 효과를 보이기 그 뒤 지연 작업인 process_results를 추가로 넣는다.

# 코드 8
from datetime import datetime
import time
import random
import ray

ray.init()

@ray.remote
def do_some_work(x):
    time.sleep(random.uniform(0, 4)) # 0 ~ 4초 사이 램덤으로 지연
    return x

def process_results(results):
    sum = 0
    for x in results:
        time.sleep(1)
        sum += x
    return sum

start = datetime.now()
datas = ray.get([do_some_work.remote(x) for x in range(10)]) # 이 작업은 대략 4초 안으로 걸린다.
results = process_results(datas) # 이 작업은 대략 10초 정도 걸린다.
print("duration = ", datetime.now() - start)
print("results = ", results)

코드 8의 결과 대략 13초가 걸린다.

코드 8의 결과는 do_some_work의 4초 안으로 걸리는 결과와 process_results의 10초 정도 걸리는 결과를 생각해 볼때 대략 13초 정도가 나온다. 하지만 이 결과는 최선의 결과가 아니다. 왜냐하면 do_some_work가 전부 4초대를 내놓는 작업이 아니라 1초보다 빠른 작업을 내놓는 작업이 있을수도 있다. 그러면 우리가 할 일은 단순 합을 구하는 작업이니 모든 작업이 끝날때까지 기다릴 필요가 전혀없고 끝난 프로세스먼저 결과를 가져와 더하면 된다. process_results는 1초 지연 작업이 있기에 이는 현명한 방법이다. 이를 위해서 코드 9처럼 누가 먼저 끝났는지 ray.wait()으로 수시로 확인을하고 값을 가져오고 처리하면 된다.

# 코드 9
from datetime import datetime
import time
import random
import ray

ray.init()

@ray.remote
def do_some_work(x):
    time.sleep(random.uniform(0, 4)) # 0 ~ 4초 사이 램덤으로 지연
    return x

def process_results(sum, result):
    time.sleep(1)
    sum += result
    return sum

start = datetime.now()
datas = [do_some_work.remote(x) for x in range(10)]
sum = 0
while len(datas):
    done, datas = ray.wait(datas) # 다된 작업은 done으로 넘긴다.
    sum = process_results(sum, ray.get(done[0]))
print("duration = ", datetime.now() - start)
print("results = ", sum)

코드 9의 결과 대략 10초가 걸린다.

코드 8과 코드 9는 거의 같지만 코드 9처럼 먼저 끝나는 작업을 먼저 처리하는 방식이 더욱더 효율적임을 확인할 수 있다.

 

참고문헌

1. Tips for first-time users, Available online: docs.ray.io/en/master/auto_examples/tips-for-first-time.html 15 Sep 2020.

2. Tune Distributed Experiments, Available online: docs.ray.io/en/master/tune/tutorials/tune-distributed.html#tune-distributed 15 Sep 2020.

3. NVIDIA apex GitHub Repository, Available online: github.com/NVIDIA/apex 15 Sep 2020.

요즘 윈도우에서 windows terminal 앱을 사용하니깐 너무 좋다. 추가로 vim만 있으면 터미널에서 바로 텍스트 편집이 가능하니 좋을 것 같다.

 

vim을 윈도우에 설치하려면 이곳 에서 최신버전의 설치파일을 다운받은 후 설치를 진행한다.

 

그리고 "윈도우키 + Pause" 를 누른후 고급 시스템 설정에 들어가 환경변수에 vim이 설치된 경로를 추가해준다.

 

환경변수에 vim 이 설치된 경로를 추가해준다.

 

그러면 이제 파워 셸(powershell) 에서 vim을 치면 vim을 사용할수 있고, 추가적으로 나는 우분투에서 vi를 쳐서 실행하는 습관 때문에 익숙하지 않아서 별칭(alias)을 추가로 설정하기로 한다.

 

그런데 New-Alias 또는 Set-Alias 는 새로운 세션의 터미널을 열때 적용이 되지 않았다. 이를 해결하기 위해서는 profile.ps1을 수정하면 된다[1]. 이 파일은 우분투 bash의 .bashrc 랑 비슷하다.

 

그래서 변수인 $profile을 확인하여 profile.ps1 파일 위치를 확인하고[2] vim 을 이용해서 즉석으로 수정한다.

 

vim을 이용해 profile.ps1을 수정한다.
profile.ps1을 수정한다. 이 파일은 .bashrc와 비슷하게 파워 셸 세션이 시작할 때 마다 자동으로 수행된다.

 

그리고 새로운 세션을 여고 vi를 입력해 잘 되는지 확인해 본다.

 

새로운 세션을 연 후 vi alias가 적용됨을 확인한다.

참고문헌

1. "How to create permanent PowerShell Aliases", Jul 2014, stackoverflow.com/questions/24914589/how-to-create-permanent-powershell-aliases

2. "Understanding the Six PowerShell Profiles", May 2012, web.archive.org/web/20121105003839/blogs.technet.com/b/heyscriptingguy/archive/2012/05/21/understanding-the-six-powershell-profiles.aspx

 

 

 

 

아나콘다(anaconda)는 유명한 파이썬(python) 가상환경 제공 및 기타 패키지 의존 관리 자동설치 툴이다. 아나콘다가 쿠다 툴킷(cuda toolkit)도 알아서 맞추어 설치해 가상환경을 제공하기에 나는 자주 사용하는 편이다. 그런데 우분투만 사용하다가 갑자기 윈도우에서 사용하려니 좀 앞뒤가 안맞는게 많았다.

 

윈도우에 아나콘다를 설치하고 파워 셸(powershell 5.1 버전, 나는 windows terminal 이라는 윈도우 앱을 사용하는데 이게 일반 윈도우 프롬프트가 아니라 파워 셸로 켜진다.)에서 환경을 만들고 활성화를 해보았다.

그런데 아무 반응이 없고 활성화가 안된다.

 

다행이 해결법을 아나콘다 깃 저장소 이슈에서 찾을 수 있었고 원인은 파워 셸 실행 정책에 의해서 아나콘다 스크립트 실행이 막힌것이 원인이다[1].

 

파워 셸을 관리자 권한으로 실행해서 다음 명령어를 입력한다.

> Set-ExecutionPolicy -ExecutionPolicy Unrestricted

다음 아나콘다를 다시 초기화 해준 후 창을 닫는다.

> conda init

그리고 다시 파워 셸을 열고 아나콘다를 활성화 하면 잘 된다.

 

*추가로 자신의 파워 셸 버전별 set-executionpolicy는 여기서 확인이 가능하다.

참고문헌

1. "conda environment activation not working in powershell", Mar 2019, github.com/conda/conda/issues/8428

나는 살면서 한번도 웹을 건드릴 일이 없을줄 알았는데, 지인의 부탁으로 어쩔 수 없이 떠맞게 되었다.

디자인은 bootstrap(부트스트랩)으로 하였는데 중요한 데이터 베이스도 슬슬 구축할 필요성이 보여서 구축을 해보려고 한다. 참고로 이 분야는 내 전공이 아니다, 그래서 잘못된 내용이 있을수도 있으니 이점 양해를 구하고 시작한다.

 

MySQL은 오픈소스 데이터 베이스로 유명하다. 나는 학부때 MSSQL을 배워서 그나마 MSSQL를 사용하면 빨리 구축할수 있을거라 예상했으나, 지인의 부탁으로 플랫폼(windows server에 ASP를 예상했으나 ubuntu로 바뀜)이 바뀌어서 MySQL을 사용한다.

 

먼저 패키지 업데이트부터 진행후 설치를 진행한다.

$ sudo apt update
$ apt install mysql-server

그냥 mysql-server 라고 치면 기본적으로 5.7.x 버전이 설치가 된다.

 

* 만약 8 버전을 설치하고 싶다면 다음 저장소(Repository)설정 데비안 파일을 다운로드하고 패키지파일을 실행후 설정을 마치고 $ sudo apt install mysql-server 를 쳐주면 설치가 된다.

 

다음 보안 설정을 해준다.(5.7.x 버전)

$ sudo mysql_secure_installation

참고로 나는 다음과 같이 진행하였다.

$ sudo mysql_secure_installation

Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Please set the password for root here.

New password:

Re-enter new password:

Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!

위 처럼 데비안 패키지 저장소로부터 설치를 진행했다면 보통 데이터 디렉토리(data directory)는 자동으로 초기화 된다고 한다[1].

 

만약 다음처럼 데이터 디렉토리가 조회되지 않는다면 mysqld --initialize 를 쳐서 초기화를 해준다[1, 2].

$ sudo mysql -uroot -p -e 'SHOW VARIABLES WHERE Variable_Name LIKE "%dir"'

Enter password:
+---------------------------+----------------------------+
| Variable_name             | Value                      |
+---------------------------+----------------------------+
| basedir                   | /usr/                      |
| character_sets_dir        | /usr/share/mysql/charsets/ |
| datadir                   | /var/lib/mysql/            |
| innodb_data_home_dir      |                            |
| innodb_log_group_home_dir | ./                         |
| innodb_tmpdir             |                            |
| lc_messages_dir           | /usr/share/mysql/          |
| plugin_dir                | /usr/lib/mysql/plugin/     |
| slave_load_tmpdir         | /tmp                       |
| tmpdir                    | /tmp                       |
+---------------------------+----------------------------+

그리고 다음과 같이 데이터 베이스를 만든다음 유저를 만들고 권한을 주고, 그 다음에 만든 유저로 테스트용 테이블을 만들었다.

$ sudo mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.31-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database test;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| test               |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'Pass12!@#';
Query OK, 0 row affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON test.* TO 'testuser'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> exit
Bye

$ mysql -utestuser -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.7.31-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use test
Database changed

mysql> CREATE TABLE test(
    -> id INT(5) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> title VARCHAR(255))CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)

test 테이블이 잘 되는지 확인해본다.

mysql> INSERT INTO test (title) VALUES ('스바루'), ('에밀리아'), ('렘');
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM test;
+----+--------------+
| id | title        |
+----+--------------+
|  1 | 스바루       |
|  2 | 에밀리아     |
|  3 | 렘           |
+----+--------------+
3 rows in set (0.00 sec)

mysql> exit
Bye

자 이제 마지막으로 제일 중요한 python과의 연동을 확인해본다.

먼저 다음과 같이 MySQL connector(커넥터)를 설치해주어야 한다.(나는 테스트중인 python 버전이 3.6.9 이다.)

$ pip3 install mysql-connector-python

* mysql-connector-python 버전이 8 버전이면 MySQL 경우 8.0, 5.7, 5.6, 5.5 사용이 가능하고 python 경우 3.6, 3.5, 3.4, 2.7이 지원된다[3].

 

설치를 완료했다면 다음과 같이 간단히 db.py 란라는 커넥터를 만들고 실행해본다.

import mysql.connector
from mysql.connector import Error
from datetime import datetime

# configuration for connect to MySQL
host = 'localhost'
database = 'test'
user = 'testuser'
password = 'Pass12!@#'

# logging option
logging = True

# simple connector
def connect():
    conn = mysql.connector.connect(
                host = host,
                database = database,
                user = user,
                password = password
            )

    if logging and conn.is_connected():
        print("Connected to MySQL host: '{}', database: '{}', user: '{}', time: '{}'".format(
            host, database, user, datetime.now()))

    return conn

if __name__ == '__main__':
    connect()

실행해본다.

$ python3 db.py
Connected to MySQL host: 'localhost', database: 'test', user: 'testuser', time: '2020-08-30 00:43:09.697235'

잘 작동됨이 확인이 되었고 다음 test 테이블에 맞는 기본적인 CRUD(create, read, update and delete)를 아래와 같이 만들고 실행해본다.

import mysql.connector
from mysql.connector import Error

from datetime import datetime

host = 'localhost'
database = 'test'
user = 'testuser'
password = 'Pass12!@#'

logging = True

# simple connector
def connect():
    conn = mysql.connector.connect(
                host = host,
                database = database,
                user = user,
                password = password
            )

    if logging and conn.is_connected():
        print("Connected to MySQL host: '{}', database: '{}', user: '{}', time: '{}'".format(
            host, database, user, datetime.now()))

    return conn

# insert datas into test table
def create(table, fields, values):
    # check fields is innumerable or not
    if isinstance(fields, str):
        fields = (fields, )
    # check values is innumerable or not
    if isinstance(values, str):
        values = (values, )

    fields, values = ','.join(fields), "'"+"','".join(values)+"'"
    query = "INSERT INTO {} ({}) VALUES ({})".format(table, fields, values)

    return query

# read datas from test table
def read(table, fields, where=None, orderby=None):
    # check fields is innumerable or not
    if isinstance(fields, str):
        fields = (fields, )

    fields = ','.join(fields)
    if where is None and orderby is None:
        query = "SELECT {} FROM {}".format(fields, table)
    elif where is None:
        query = "SELECT {} FROM {} ORDER BY {}".format(fields, table, orderby)
    elif orderby is None:
        query = "SELECT {} FROM {} WHERE {}".format(fields, table, where)
    else:
        query = "SELECT {} FROM {} WHERE {} ORDER BY {}".format(fields, table, where, orderby)
    return query

# change datas into test table
def update(table, set, where):
    query = "UPDATE {} SET {} WHERE {}".format(table, set, where)
    return query

# delete datas into test table
def delete(table, where):
    query = "DELETE FROM {} WHERE {}".format(table, where)
    return query

if __name__ == '__main__':
    # connect
    conn = connect()
    cursor = conn.cursor()

    # create
    cursor.execute(create('test', ('title'), ('람')))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())

    # read
    cursor.execute(read('test', ('id', 'title')))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), orderby='id DESC'))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), where='id = 2'))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), where='id >= 2', orderby='id DESC'))
    print(cursor.fetchall())

    # update
    cursor.execute(update('test', "title = '베아트리스'", 'id = 4'))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())

    # delete
    cursor.execute(delete('test', 'id = 1'))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())
    
    conn.commit()
    conn.close()

실행해본다.

$ python3 db.py
Connected to MySQL host: 'localhost', database: 'test', user: 'testuser', time: '2020-08-30 02:57:01.585607'
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '람')]
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '람')]
[(4, '람'), (3, '렘'), (2, '에밀리아'), (1, '스바루')]
[(2, '에밀리아')]
[(4, '람'), (3, '렘'), (2, '에밀리아')]
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '베아트리스')]
[(2, '에밀리아'), (3, '렘'), (4, '베아트리스')]

잘 됨을 확인할 수 있다.

추가적으로 SQL injection(인젝션) 대비와 태그가 들어가는거 및 몇 이상한 문자가 흘러 들어가는 것 정도만 막아주면 그럭저럭 사용할수 있지 않을까 생각한다.

참고문헌

1. "How To Install MySQL on Ubuntu 18.04", April 2020, www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-18-04

2. "How to find the mysql data directory from command line in windows", Jul 2013, stackoverflow.com/questions/17968287/how-to-find-the-mysql-data-directory-from-command-line-in-windows

3. "Getting Started with MySQL python Connector", www.mysqltutorial.org/getting-started-mysql-python-connector/

큰 용량의 데이터셋을 크롤러(crawler)로 다운받거나 아니면 어떠한 모델을 학습할때 즉시 로그를 확인할 필요는 없지만 학습기간이 오래걸릴때 등등 경우에는 터미널(terminal)을 계속 열어두면 많이 불편하다.

 

특히 ssh(secure shell)로 다른곳에서 열어두고 학습하다가 갑자기 인터넷이라도 끊기면 다시 시작해야하는 번거로움도 발생한다.

 

이러한 경우 터미널이 종료되어도 백그라운드 작업을 nohup로 진행할 수 있다. nohup은 posix 커맨드로 HUP(signal hang up) 신호를 무시한다[1].

 

기본적인 사용은 다음과 같다[2].

$ nohup test.py &

그리고 많은 경우가 로그를 보기를 원하는데 위 같은 간단한 명령은 실행했던 디렉토리에 nohup.out 이라는 로그파일을 남기게 된다. 그러나 이는 모두가 원하는 경우는 아니다.

 

그래서 다음과 같이 사용하면 원하는 디렉토리에 원하는 로그파일을 남길 수 있다.

$ nohup python -u ./test.py > ./test.log &

python의 -u 플래그는 --help 에서도 확인이 가능한데, 강제로 stdout 과 strerr 스트림 버퍼링을 해제하는 옵션이다.(단 stdin에는 적용안됨) 다시 요약하면 실시간으로 로그를 남기고 싶다면 -u를 아니면 -u를 빼도 된다[3].

 

종료는 해당 프로세서가 종료가 되면 자동으로 종료가 되나 강제로 종료를 하고싶다면 실행한 프로세서의 pid를 확인한후 kill 명령어로 프로세서를 강제로 종료하는 방법이 있다.

참고문헌

1. "nohup", May 2020, en.wikipedia.org/wiki/Nohup

2. "리눅스 nohup 사용법", Mar 2019, zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_nohup_%EC%82%AC%EC%9A%A9%EB%B2%95

3. "Nohup is not writing log to output file", Oct 2012, stackoverflow.com/questions/12919980/nohup-is-not-writing-log-to-output-file

일반적인 분류(classification)문제를 다루다보면 항상 마지막에 linear layer가 들어가게 된다.

이는 우리가 생각할수 있는 가장 간단한 방식이고, 이는 말이 된다.

 

그런데 만약 입력 텐서의 크기가 고정되지 않다면 출력 텐서의 크기도 마찬가지로 다르게 나온다, 여기서 문제가 보통 사용하는 linear layer는 입력 크기가 고정되기에 입력 사이즈가 변화되면 에러가 발생한다.

 

그래서 이를 위해서 입력에 관계없이 출력을 고정하도록 설계된 Adaptive Pooling을 사용할 수 있다.[1, 2]

 

단순히 출력을 지정한 튜플값으로 맞춰주는 pooling이라고 생각하면 된다.

 

예로 다음과 같은 텐서(b, c, h, w)를 만들어서 Max pooling을 했다고 가정한다.

>>> a = torch.randn(1, 2, 5, 5)
>>> b = F.max_pool2d(a, stride=1, kernel_size=2)
>>> b.size()
torch.Size([1, 2, 4, 4])
>>> b
tensor([[[[ 1.4379,  1.4379,  1.0259,  1.0259],
          [ 1.8803,  0.7414,  0.0418,  0.8152],
          [ 1.8803,  0.7414,  0.0418, -0.0604],
          [ 1.3686,  0.2596,  1.2160,  1.2160]],

         [[ 1.5899,  0.6822,  1.4687,  1.4687],
          [ 1.5899,  0.6822,  1.4687,  1.4687],
          [-0.1199,  0.7847,  0.7847,  0.7320],
          [-0.2405,  0.7847,  0.7847,  0.6991]]]])

그리고 위와 같은 텐서는 Adaptive Max Pooling을 사용하면 다음과 같이 표현할수 있다.

>>> c = F.adaptive_max_pool2d(a, (4, 4))
>>> c.size()
torch.Size(1, 2, 4, 4)
>>> c
tensor([[[[ 1.4379,  1.4379,  1.0259,  1.0259],
          [ 1.8803,  0.7414,  0.0418,  0.8152],
          [ 1.8803,  0.7414,  0.0418, -0.0604],
          [ 1.3686,  0.2596,  1.2160,  1.2160]],

         [[ 1.5899,  0.6822,  1.4687,  1.4687],
          [ 1.5899,  0.6822,  1.4687,  1.4687],
          [-0.1199,  0.7847,  0.7847,  0.7320],
          [-0.2405,  0.7847,  0.7847,  0.6991]]]])

Adaptive Max Pooling의 매개변수로 위 코드처럼 튜플값이 들어가는데 이는 2d Max pooling을 적용하고 나올 결과의 (h, w)를 의미한다. 위 코드 경우에는 (b, c, 4, 4)로 나오기를 원해서 (4, 4)라는 튜플을 넘겨준 것 이다.

 

*참고로 torchvision의 resnet은 Adaptive Avg Pool2d가 적용되었다. pre-trained weight은 이미지넷 224사이즈로 학습이 된걸로 보인다.[3]

>>> import torchvision.models as models
>>> models.resnet18()
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

references

1. "Contiguous vs non-contiguous tensor", Nov 2018, discuss.pytorch.org/t/contigious-vs-non-contigious-tensor/30107

2. "Adaptive_avg_pool2d vs avg_pool2d", Oct 2018, discuss.pytorch.org/t/adaptive-avg-pool2d-vs-avg-pool2d/27011

3. "TORCHVISION.MODELS", pytorch.org/docs/stable/torchvision/models.html

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 호스트 이름에 맞는 호스트 키가 없어서 발생함.

 

자신의 호스트 이름으로 ssh를 한번만 접속해주면 해결됨을 확인함.

$ ssh hyeok@hyeok -p 2200

 

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 JAVA_HOME을 못찾기에 발생함.

 

그러나 JAVA_HOME을 .bashrc에 설정이 되어있고 자바 버전 확인도 잘 작동됨.

 

이는 hadoop이 실행되면서 ssh 접속을 할때 자바 코드에서 접속을 시도하기에 .bashrc 관련 설정 및 현재 터미널에서 사용하던 환경변수는 모두 값을 잃어버린다, 그래서 JAVA_HOME 정보를 전달하기 위해 /etc/hadoop/hadoop-env.sh의 JAVA_HOME을 설정해준다.[1]

$ export JAVA_HOM="/home/hyeok/JAVA/jdk-8"

*참고로 ~/JAVA/jdk-8은 안된다.

references

1. "JAVA_HOME is not set in Hadoop", Dec 2017, stackoverflow.com/questions/20628093/java-home-is-not-set-in-hadoop

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 기존 사용중인 ssh의 포트가 맞지 않기에 발생함.

 

사용중인 ssh포트를 22로 변경하거나, HADOOP_SSH_OPTS 환경변수를 알맞게 변경해주면 됨.[1]

$ export HADOOP_SSH_OPTS="-p 2200"

아니면 /etc/hadoop/hadoop-env.sh 의 HADOOP_SSH_OPTS를 수정해준다.

$ export HADOOP_SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=10s -p 2200"

references

1. "Change ssh default port in hadoop multi cluster [closed]", Feb 2016, stackoverflow.com/questions/35224304/change-ssh-default-port-in-hadoop-multi-cluster

 

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 pdsh의 설정 문제로 발생함.

=> pdsh is a variant of the rsh command. unlike host, pdsh can run multiple remote commands in parallel.[1]

 

pdsh의 rcmd type은 기본으로 rsh로 되어있다 이를 ssh로 바꾸어 주면 됨.[2]

 

먼저 rsh로 되어있는지 확인한다.

$ pdsh -q -w localhost

Generic options항목의 Rcmd type을 확인후 rsh로 되어있다면 ssh로 바꾸어줌

$ export PDSH_RCMD_TYPE=ssh

references

1. "pdsh(1) - Linux man page", linux.die.net/man/1/pdsh

2. "Permission Denied error while running start-dfs.sh", Mar 2017, stackoverflow.com/questions/42756555/permission-denied-error-while-running-start-dfs-sh

 

pytorch를 사용하다보면 코드속에 .contiguous()가 가끔 보일때가 있다. 이는 연속적인 메모리 텐서를 반환하는 메서드로 만약 어떤 연산을 사용할때 이를 사용하지 않으면 에러가 발생하는 경우가 생긴다.

 

예로 아래와 같이 a라는 텐서를 만들고 텐서의 형태(shape)와 보폭(stride)을 살펴보면 다음과 같다.[1]

>>> a = torch.randn(2, 3, 4)
>>> a.size()
torch.Size([2, 3, 4])
>>> a.stride()
(12, 4, 1)

여기서 보폭은 해당하는 차원의 원소에 접근할때 건너 뛰어야 할 원소들의 수(보폭)를 의미한다. 위의 예로 0차원 에서 건너 다음 원소를 가져올때는 12개의 원소를 뛰어 넘어야 한다는 의미이다.

이는 말이 된다. 하지만 메모리에 연속적으로 값이 들어있지 않는 경우에는 말이좀 달라진다.

 

예로 아래와 같이 a텐서의 차원 0과 1을 바꾸어 보았다. 그리고 텐서의 형태가 바뀐 a와 같은 형태의 b를 만들어서 비교 해보면 다음과 같다.[1]

>>> a = a.transpose(0, 1)
>>> a.size()
torch.Size([3, 2, 4])
>>> a.stride()
(4, 12, 1)
>>> a.is_contiguous()
False
>>> b = torch.randn(3, 2, 4)
>>> b.stride()
(8, 4, 1)
>>> b.is_contiguous()
True

여기서 눈여겨 볼점은 a와 b가 같은 형태로(3, 2, 4) 유지되나 실제 원소에 접근하기 위한 보폭을 살펴보면 값이 다르다는 점(a: [4, 12, 1], b: [8, 4, 1])을 확인할 수 있다.

이런 이유는 텐서 a의 형태가 변화될때 실제로 해당 원소에 접근할때는(메모리에서는 원소들의) 위치가 변화되지 않았고 접근 인덱스만 변화되었기 때문이다. 이는 pytorch에서 일부 텐서 연산을 수행함에 있어서 성능향상을 위한것으로 생각된다.(만약 텐서의 형태가 바뀔때마다 메모리에 있는 그래도 배치하려면 재 할당을 해주어야 하는데 이러한 연산이 잦으면 오히려 성능을 떨어뜨리는 원인이 될수있다.)  그래서 형태가 바뀐 a의 보폭을 해석해보면 차원 0 에서 다음 원소에 접근할때 8개가 아닌 4개의 원소만 건너면 b와 같은 텐서처럼 사용을 할수가 있다는 의미로, 실제 메모리에는 a와 b가 다른 순서로 배열이 되어있으나 메모리 재할당을 할필요 없이 접근 인덱스만 바꾸면 b처럼 사용을 할수가 있다는 의미가 된다.

 

그리고 .contiguous() 메서드는 다음과 같이 이러한 a와 같은 비연속적인 텐서를 연속적으로 만들어주는 역할을 한다.

>>> a = a.contiguous()
>>> a.stride()
(8, 4, 1)

이제 텐서 b랑 같아졌다.

 

아래의 예는 contiguous하지 않은 텐서를 펴서 사용할 때 접할 수 있는 에러이다.

>>> a = a.transpose(0, 1)
>>> a = a.view(-1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: view size is not compatible with input tensor`s size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

 

이를 해결하려면 텐서를 연속적으로 만들어주면 된다.

>>> a = a.contiguous()
>>> a = a.view(-1)
>>> a.size()
torch.Size([24])

 

또는 .contiguous() 없이 아래와 같이 사용이 가능하다.

>>> a = torch.randn(3, 2, 4)
>>> a = a.transpose(0, 1)
>>> a = a.reshape(-1) # view() 대신 reshape() 사용
>>> a.size()
torch.Size([24])

위의 예 처럼 연속을 고려하는 메서드(reshape)와 고려하지 않는 텐서변환 메서드(view)가 존재하니 알맞게 사용하면 에러를 예방할수 있다.

참고문헌

1. "Contiguous vs non-contiguous tensor", Nov 2018, discuss.pytorch.org/t/contigious-vs-non-contigious-tensor/30107

+ Recent posts