한국어 NLP와 딥러닝을 위한 도커이미지 만들기

한국어 NLP와 딥러닝을 위한 도커이미지 만들기

딥러닝 + 도커?

딥러닝 프로젝트를 진행할 때 귀찮은 것 중 하나는 여러 라이브러리를 관리하고 어떤 버전을 설치했는지를 매번 체크하는 것이다.

Tensorflow나 PyTorch의 경우 매 시즌별로 버전 업데이트가 이뤄지며 동시에 api가 이전 버전과 달라져 어떤 것을 사용해야 하는지 선택이 곤란해지는 때가 있다.

한편 위 문제는 양반일 정도로 귀찮은 것이 하나 더 있다. 바로 CUDA와 cuDNN, APEX등을 버전을 맞춰 설치하고 PATH를 잡아서 진행하는 부분은 정말 끔찍하다.

다만 딥러닝만이 아닌 웹 개발을 진행한다 하더라도 버전 관리와 재현성이 제공되는 개발환경은 필수이기 때문에 도커를 사용해서 관리하는 것은 사실상 기본이 되어가고 있다.

귀여운 도커 아이콘을 찾아보았다

딥러닝 도커 이미지

딥러닝에 도커를 사용하는 이유는 또 다른 이유도 있다.

딥러닝 라이브러리들에서 GPU 가속을 사용하기 위해서는 앞서 말했던 것과 같이 CUDA와 여러 GPU 관련 라이브러리를 잡아줘야 한다. 하지만 이 작업이 생각보다 귀찮은 것은 차치하더라도, Tensorflow나 PyTorch가 (특히 TF가..!!!) 요구하는 CUDA/cuDNN을 버전 세트를 맞춰주는 것도 한세월.

하지만 최근 몇년 사이 딥러닝을 위한 도커 이미지가 넘쳐나고 있다.

당장 Tensorflow나 PyTorch의 공식 이미지부터 시작해 deepo, h2o.ai등 굉장히 다양한 이미지들이 넘쳐나고 있다. 이들을 이용해 추가적인 라이브러리를 설치해 입맛에 맞는 도커 이미지를 만들면 가장 편안하게 연구 개발을 할 수 있다.

Deepo 이미지로 띄우기

여러가지를 사용해 보았지만 현재(2019.11.27)시점 체감상 가장 안정적이고 잘 동작하고 나름 설정이 잘 되어있는 - 혹은 개인적 취향에 맞는 - 라이브러리는 바로 deepo 였다.

deepo

Deepo 바로가기: https://github.com/ufoym/deepo

Deepo는 처음부터 도커기반 딥러닝 이미지 프로젝트였고, CPU와 GPU 모든 경우이고 동시에 현재 자주 사용중인 딥러닝 라이브러리 대부분을 지원한다.

Deepo 도커 이미지 tag들

https://github.com/ufoym/deepo#available-tags 에서 볼 수 있는 Deepo에서 제공하는 태그들.

웬만한 것들은 다 지원하는 셈이다.

따라서 이 도커 이미지 위에 우리가 원하는 추가 패키지들을 설치하고 세팅을 진행하면 보다 편안-한 마음으로 개발을 할 수 있다.

Deepo 이미지 중 어떤 것을 사용하나?

개인적으로는 PyTorch를 가장 많이 사용하고 종종 Tensorflow도 사용하기 때문에 위 이미지 중 전체가 다 설치되어있는 all 시리즈를 사용한다.

그 중에서도 CUDA버전을 명시적으로 지정하는 all-jupyter-py36-cu100 (혹은 all-jupyter-py36-cu101)을 이용해서 진행하면 좋다.

최신버전을 사용하려면 all-jupyter 을 사용하면 된다.

이 버전에 따라서 Pre installed 라이브러리들의 버전이 달라진다.

도커 이미지 만들기

도커 이미지는 보통 아래 요소들로 만들어진다.

  1. FROM: 어떤 이미지에서 받아서 진행할지

  2. RUN: 어떤 명령어를 실행해서 이미지로 만들지

  3. WORKDIR: 현재 있는 폴더에서 명령을 실행할지

각 요소 하나하나가 실행될 때 마다 도커 이미지의 layer가 된다. = 이미지가 무거워진다!

따라서 apt-get 와 같이 한번만 실행하면 되는 경우에는 한 세트로 넣어서 돌리는 것이 유리하다.

Ubuntu 기반인 deepo

deepo는 ubuntu 기반으로 이미지를 제작하기 때문에 apt 등을 보다 편안하게 사용할 수 있다.

RUN 을 사용할 경우, 상위 이미지(deepo)에서 지정한 root 유저로 실행되기 때문에 apt 와 같은 명령어를 sudo 권한 없이도 실행할 수 있다.

1) Ubuntu 기반으로 불러오기

FROM 을 이용해 Deepo 이미지를 불러오자.

1
FROM ufoym/deepo:all-jupyter

2) KoNLPy를 사용하기 위해 JVM을 설치해준다. 이와 함께 python, curl, wget등을 같이 설치해주자.

1
2
3
4
5
6
# Install JVM for Konlpy
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y \
openjdk-8-jdk wget curl git python3-dev \
language-pack-ko

3) 언어팩 설정을 하기 위해 가장 많이 지원되는 en_US.UTF-8 을 시스템 언어로 지정해주자.

1
2
RUN locale-gen en_US.UTF-8 && \
update-locale LANG=en_US.UTF-8

4) zsh를 설치한다. (Optional, 하지만 나중에 작업 편함)

1
2
3
# Install zsh
RUN apt-get install -y zsh && \
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

5) Kakao에서 발표한 CNN기반 토크나이저인 Khaiii를 설치한다.

  • 빌드 시간이 조금 오래걸린다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Install Khaiii
WORKDIR /deps
RUN git clone https://github.com/kakao/khaiii.git
WORKDIR /deps/khaiii

RUN pip install cython && \
pip install --upgrade pip && \
pip install -r requirements.txt && \
mkdir build
WORKDIR /deps/khaiii/build

RUN cmake .. && make all && make resource && make install && make package_python

WORKDIR /deps/khaiii/build/package_python
RUN pip install .

6) Jupyter Notebook 환경을 좀더 편리하게 사용하기 위해 Extension들을 설치해준다.

  • 추가적으로 필요한 Extension이 있다면 이 단계에서 설치하는 것을 추천한다.
1
2
3
4
5
6
7
# Setup Jupyter extensions
RUN pip install jupyter_nbextensions_configurator jupyter_contrib_nbextensions && \
jupyter nbextensions_configurator enable && \
jupyter contrib nbextension install

RUN pip install jupyter_http_over_ws && \
jupyter serverextension enable --py jupyter_http_over_ws

7) 기타 Python라이브러리와 mecab을 설치한다.

  • 기타 라이브러리를 설치해준다. 간단히 소개하자면…
    • Pandas_explode는 pandas Dataframe에 .explode('column') 등을 사용 가능하게 만들어준다. (속도가 빠르진 않다.)
    • autopep8은 PEP8을 자동으로 맞춰준다. (ipynb파일도 지원함)
    • s3fs - s3:// 주소를 사용 가능하도록 만들어준다. (pd.read_csv 등에서 유용)
    • fastparquet - parquet 파일 형식을 읽고 쓸 수 있도록 해준다.
    • soynlp / konlpy - 한국어 토크나이저
    • dask - Pandas와 비슷하지만 Distributed Computing을 지원하는 라이브러리
    • python-snappy - parquet 파일 사용시 snappy 알고리즘을 사용하는데 이때 필요함
  • mecab-ko: KoNLPy에서 Mecab 클래스 사용을 위해서 필요
    • mecab은 초당 3~5000개 처리 / 1core
    • 다른것은 초당 100-300개 처리 / 1core
    • (라이젠 1800x 기준)
1
2
3
4
5
6
7
8
9
10
11
# Install another packages
RUN pip install -e git+https://github.com/kanth989/pandas_explode#egg=pandas_explode
RUN pip install \
autopep8 pytorch_pretrained_bert \
s3fs fastparquet soynlp konlpy \
randomcolor pynamodb plotly
RUN pip install "dask[complete]"
RUN pip install python-snappy

# Add Mecab-Ko
RUN curl -L https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh | bash

8) Docker 이미지를 root아닌 유저로 쓰도록 지정하는 유저 생성하기 (Optional)

  • user 라는 이름을 가진 유저가 생성된다.
1
2
3
4
5
# Add non-root user
RUN adduser --disabled-password --gecos "" user

# Reset Workdir
WORKDIR /code

유저를 root 가 아닌 유저로 생성하는 경우의 이점과 단점

  • 장점

    • 이후 Volume 마운트 시 권한이 꼬이지 않을 수 있고, 조금 더 안전하다.
  • 단점

    • 이와 같이 유저를 생성할 경우, sudo 권한을 명시적으로 주지 않는 한 sudo 명령을 쓰지 못한다.

    • apt-get 등으로 설치가 필요한 경우 Dockerfile을 새로 빌드해야 한다.

하지만 간단한 pip install 등은 User 디렉토리에 설치하는 방법을 통해 조금 환경 제약을 줄일 수도 있다.

최종본 Dockerfile

최종본 파일은 https://github.com/Beomi/deepo-nlp/blob/master/Dockerfile 에서 받을 수 있다.

위와 같이 Dockerfile을 작성한 뒤, 아래 명령어를 통해 도커 이미지를 빌드하면 된다.

1
docker build -t beomi/deepo:all-jupyter-py36-cu100-konlp .

도커 띄우기 RUN!

위와 같이 도커이미지를 만든 뒤, Jupyter Notebook 환경을 원활하게 사용하기 위해서는 여러 세팅이 필요하지만, 그 중에서 몇가지 지원을 하는 방식을 보자.

아래 파일은 내가 위 도커 이미지를 실행할때 쓰는 Shell Script다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker run --gpus=all -it --rm --ipc=host \
-p 28888:8888 \
-p 28787:8787 \
-u $(id -u ${USER}):$(id -g ${USER}) \
-v $(pwd)/.dockervm/tmp:/tmp \
-v $(pwd)/.dockervm:/home/user \
-v $(pwd)/code:/code \
-v /home/${USER}:/home/${USER}:ro \
beomi/deepo:all-jupyter-py36-cu100-konlp \
zsh -c '
export SHELL=zsh;
export PATH=$PATH:/root/.local/bin:/root/.local/lib/python3.6/site-packages;
jupyter notebook \
--no-browser \
--ip=0.0.0.0 \
--notebook-dir=/code'

하나씩 뜯어보자.

1) GPU설정 & ipc=host

1
docker run --gpus=all -it --rm --ipc=host \

nvidia-docker 를 사용해야 도커 환경 내에서 CUDA가속을 사용할 수 있다.

Nvidia Docker 공식 레포: https://github.com/NVIDIA/nvidia-docker

도커 이미지 실행시 어떤 GPU를 사용할지 지정해야 하는데, --gpus=all 로 사용할 경우 해당 시스템에 있는 모든 GPU를 사용하도록 지정하고, -gpus '"device=1,2"' 와 같이 지정시 PCIe Bus기준 1,2번에 해당하는 것만 사용하도록 지정할 수 있다.

2) 도커 이미지로 연결할 포트 지정

1
2
-p 18888:8888 \
-p 18787:8787 \

-p 명령어를 통해 어떤 외부포트 를 어떤 내부포트 로 연결할지 지정할 수 있다.

위와같이 -p 18888:8888 로 지정할 경우 해당컴퓨터IP:18888 로 접속시 Jupyter Notebook 포트인 내부 8888에 접근할 수 있게 된다.

3) 유저명으로 접근

1
-u $(id -u ${USER}):$(id -g ${USER}) \

위와 같이 사용시 현재컴퓨터에 로그인한 유저의 ID와 Group ID를 알 수 있다.

만약 첫 유저라면 보통은 1000:1000 속성을 가지게 된다.

앞서 Dockerfile에서 지정했던 유저는 1000번이기 때문에 만약 혼자 사용하는 컴퓨터라면 유저 권한을 맞춰줄 수 있다.

만약 여럿이서 사용하는 서버라면 Dockerfile 빌드시 유저 id를 다르게 생성하면 된다.

4) 로컬의 임의의 폴더(.dockervm)을 연결

1
2
3
-v $(pwd)/.dockervm/tmp:/tmp \
-v $(pwd)/.dockervm:/home/user \
-v $(pwd)/code:/code \

위와 같이 임의의 폴더에 도커 내부의 폴더와 연결하면, pip install --user 와 같은 명령어를 통해 설치한 패키지는 도커 이미지를 재시작 할 경우에도 여전히 설치된 상태를 유지할 수 있고, JupyterNotebook의 패스워드 역시 매번 새로운 토큰 대신 고정된 패스워드를 사용할 수 있다.

또한 로컬의 code 폴더와 내부의 code 폴더를 맞춰 쉽게 액세스 할 수 있도록 실제 작업환경 공간을 맞춰준다.

5) 로컬의 유저 폴더를 내부에 ReadOnly로 연결

1
-v /home/${USER}:/home/${USER}:ro \

Docker의 Volume Mount시 :ro 옵션을 붙이면 ReadOnly 모드로 동작한다. 특정 파일들을 접근할 때 매번 도커 이미지 내에 업로드 하는 대신, 로컬의 파일을 손쉽게 가져다 쓸 수 있도록 만들어 준다.

6) ZSH를 이용한 JupyterNotebook 실행

1
2
3
4
5
6
7
8
beomi/deepo:all-jupyter-py36-cu100-konlp \
zsh -c '
export SHELL=zsh;
export PATH=$PATH:/home/user/.local/bin:/home/user/.local/lib/python3.6/site-packages;
jupyter notebook \
--no-browser \
--ip=0.0.0.0 \
--notebook-dir=/code'

마지막 단계인 도커 이미지 실행 단계이다.

단순하게 jupyter notebook 이라고 실행하면 문제가 발생한다. PATH 지정이 되지 않아 앞서 설치한 패키지가 인식되지 않을 수 있기 때문이다.

따라서 PATH 를 오버라이딩해줘 Python이 인식하도록 만들어줄 수 있다.

이후 No browser(CLI인 경우) 옵션, 그리고 모든 hostname을 통해 접근 가능하도록 0.0.0.0 으로 지정하고, JupyterNotebook이 실행될 기본 폴더를 /code 로 지정해주면 끝이 난다.

정리

딥러닝, 혹은 자연어 처리 등을 위해 Docker 이미지를 직접 만들어서 쓰는건 사실 당연한 일이다. 아무리 기존의 딥러닝 이미지들이 잘 되어있다고 하더라도 특히 한국어를 위한 이미지는 많이 부족한 것이 사실이다.

시스템을 많이 건드리지 않으면서도 커스터마이징을 좀 더 쉽고 빠른 연구가 가능하도록 자신만의 환경을 쌓아가는 것도 중요한 부분이지 않을까 생각해 본다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×