nykergoto’s blog

機械学習とpythonをメインに

機械学習なdockerfileを書くときに気をつけとくと良いこと

みなさん機械学習系の環境構築はどうやってますか? 僕は最近は Docker を使った管理を行っています。

特に師匠も居なかったので、ぐぐったり人のイメージを見たり手探りで docker をつかいつかいしている中で、最初からやっとけばよかったなーということがいくつかあるのでメモとして残しておきます。

大きく2つです。

  1. キャッシュは消す
  2. テストを書く

キャッシュは消す

ライブラリをいろいろと install すると大抵の場合ダウンロードしたファイルを保存されている場合が多いです。何かのタイミングで再びそのライブラリをインストールする際にはダウンロードしたファイルを使って、素早くインストールすることができます (この仕組みがキャッシュです)。

キャッシュがあると容量が重くなるという欠点があります。重たいイメージは pull に単に時間がかかりますから、システムとしてデプロイする時にトラフィックが多く発生してしまう + 更新に時間がかかるといったデメリットがあります。

ですのでライブラリを入れたあとにはキャッシュを削除すると吉です。

機械学習系だと基本的に ubuntu を base image として使うことが多いと思いますので、以下ではすべて ubuntu を使用していることを想定しています。 また、機械学習に必要なライブラリを入れるために使うコマンドは基本的に

  • apt-get (or apt 絶対使う)
  • pip (基本使う. conda only なら別.)
  • conda (場合によって使う)

3つだと思いますので、それぞれ述べていきます。

apt-get の場合

ubuntu では apt-get (もしくは単に apt) でライブラリを入れますが、コマンドとしてキャッシュを消す機能がついていますので、それを使うのが便利です。

RUN apt-get autoremove -y &&\
  apt-get clean &&\
  rm -rf /usr/local/src/*

順番に

  1. autoremove で必要がないライブラリを削除して
  2. clean でキャッシュされているすべてのライブラリを消して
  3. インストールに使ったソースコードがある場合消す

ということをやっています。

pip の場合

pip には残念ながらキャッシュディレクトリを消すコマンドが用意されていません。一番簡単なのは pip install する時には cache を作らないようにする方法です。pip>=6 であれば no-cache-dir option をつけるとキャッシュせずに install が行われます。

pip install --no-cache-dir scipy

pip を最新版にしてからインストールすることが多いと思いますのでワンライナーで以下のように記述するといい感じです。

RUN pip install -U pip &&\
  pip install --no-cache-dir scipy

インストールするライブラリはたくさんでてきくるので pip freeze で作れる requirements.txt で管理する場合も多いと思います。その場合にも単に --no-cache-dir をつけるだけです。

COPY /path/to/requirements.txt requirements.txt
# あらかじめ requirements.txt を ADD or COPY などで
# docker 内部に送っておくことが必要

RUN pip install -U pip &&\
  pip install --no-cache-dir -r requrements.txt

もしくはインストールしたあとに cache directory を消すという方法もあります。Ubuntu なら場所は ~/.cache/pip ですので全部ふっ飛ばせばよいです。

RUN pip install -r requirements.txt &&\
  rm -rf ~/.cache/pip

Conda の場合

conda にはキャッシュ削除の clean が用意されています。https://docs.conda.io/projects/conda/en/latest/commands/clean.html いろいろインストールした最後に呼び出しましょう。

RUN conda install -y \
  numpy \
  scipy &&\
  conda clean -i -t -y

その他

バイナリからインストールしたりする場合には使わないソースコードは消すようにしましょう。例えば noto font をインストールしたあとに元の .zip を消すとかそういうことです。

テストを書く

スゴーク簡単でも良いので、いつも使うライブラリを import + 定型的な処理を行なうコードを .py で書いておいて build 時にテストすると精神的に良いです。

例えばですが Mecab を使って文字列パースするような処理を書いていると、pip でインストールされていることはもちろんですが apt の方でもちゃんと mecab が入っていることが確認できます。

def test_mecab():
    import MeCab
    tokenizer = MeCab.Tagger('-Ochasen')
    tokenizer.parse('おはようせかい')

具体的にはこれを build したあとのイメージに入れて pytest を実行してあげます。

docker run -v ./test.py:/analysis/ my-image pytest ./

僕の場合はイメージの build は gitlab CI 上で行っているので CI で build したあとに registry に登録する前にテストを動かすような構成にしています。(gitlab ci の yaml 形式ですが script にあるコマンドが順番に実行されるということだけわかれば雰囲気はわかるかと思います。)

.build-docker:
  stage: build
  before_script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
  script:
    - echo ${REGISTORY_URL}
    - export TAG=${REGISTORY_URL}/${IMAGE_NAME}
    - docker build -t ${TAG}:latest -f ${DOCKERFILE} ${CONTEXT}
    - docker tag ${TAG}:latest ${TAG}:${CI_COMMIT_SHA}
    - docker run -v ${PWD}:/analysis ${TAG}:latest pytest tests/${IMAGE_NAME} # < ここがそう
    - docker push ${TAG}:latest
    - docker push ${TAG}:${CI_COMMIT_SHA}
  variables:
    IMAGE_NAME: 
    DOCKERFILE:
    # default values. if you use own values, override in variables scope.
    CONTEXT: .
    # ex.) registry.gitlab.com/atma_inc/analysis-template
    REGISTORY_URL: ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}

こうしておくとテストに失敗すると登録自体が行われないので、登録されている == テストは通っているという安心感を得られます😃

自分がいつも使っているイメージ: https://gitlab.com/nyker510/analysis-template/-/blob/master/docker/cpu.Dockerfile

このリポジトリ analysis-template では dockerfile のコード管理 + master merge と tag を切った時に CI でビルド & registry に push ということをやっています。 内容に関しては昔詳しく書いたことがあるので、こちらも参考にしてみてください。

nykergoto.hatenablog.jp

ドキュメントもあるよ!

nyker510.gitlab.io

こうしたほうがいい! 自分はこういうことをやっている とかあればコメント貰えると嬉しいですmm