複数の形態素解析器と辞書を入れた Docker コンテナを作って Python で使う

# はじめに
いくつかの形態素解析器（と辞書）を比較する機会があったので、まとめて動かせる環境を Docker コンテナにして Python で使えるようにしました。

# やったこと
今回使ったのは、[MeCab](https://taku910.github.io/mecab/) と [spaCy](https://spacy.io) と [GiNZA](https://megagonlabs.github.io/ginza/) の3つ、辞書は以下の通り MeCab 用の6つと spaCy 用の3つです。

- MeCab で使った辞書
    - [IPAdic](https://ja.osdn.net/projects/ipadic/)
    - [UniDic](https://unidic.ninjal.ac.jp)（現代書き言葉版）
    - [JUMAN](http://nlp.ist.i.kyoto-u.ac.jp/?JUMAN) 辞書
    - [NAIST-jdic](https://ja.osdn.net/projects/naist-jdic/)
    - [mecab-ipadic-NEologd](https://github.com/neologd/mecab-ipadic-neologd)
    - [mecab-unidic-NEologd](https://github.com/neologd/mecab-unidic-neologd)
- spaCy で使った辞書
    - [ja_core_news_sm](https://spacy.io/models/ja#ja_core_news_sm)
    - [ja_core_news_md](https://spacy.io/models/ja#ja_core_news_md)
    - [ja_core_news_lg](https://spacy.io/models/ja#ja_core_news_lg)

## Docker コンテナを作る
これらをまとめて動かせる環境を Docker コンテナにできたら便利、というわけで、ネット上の情報[^ref]を参考にしながら、[Ubuntu 20.04 の Docker イメージ](https://hub.docker.com/_/ubuntu) をベースに必要なものをインストールする形で Dockerfile を書きました。

[^ref]: 「[dockerで動く mecab + python3 の環境を作る - Qiita](https://qiita.com/robitan/items/fac2d5ce1113392665d2)」や「[dockerでubuntu16+python3.6+mecab(neolog-ipadic)を構築する - メモ帳](https://tksmml.hatenablog.com/entry/2019/04/21/000000)」などを参考にさせていただきました。ありがとうございます。
[^ubuntu]: [ubuntu - Docker Hub](https://hub.docker.com/_/ubuntu)

```Dockerfile:Dockerfile
FROM ubuntu:20.04

# basic libs
RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install -y sudo vim wget curl git file unzip xz-utils
RUN apt-get install -y build-essential zlib1g-dev gcc make

# locale
RUN apt-get install -y locales
RUN locale-gen ja_JP.UTF-8

# python
RUN apt-get install -y python3 python3-pip
ENV PYTHONIOENCODING "utf-8"
WORKDIR /usr/local/bin/
RUN ln -s `which python3` python
RUN ln -s `which pip3` pip

# mecab
RUN apt-get install -y mecab libmecab-dev
RUN apt-get install -y mecab-ipadic-utf8 unidic-mecab

# mecab-ipadic-NEologd
WORKDIR /usr/local/src
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
RUN cd mecab-ipadic-neologd && ./bin/install-mecab-ipadic-neologd -n -y

# mecab-unidic-NEologd
WORKDIR /usr/local/src
RUN git clone --depth 1 https://github.com/neologd/mecab-unidic-neologd.git
RUN cd mecab-unidic-neologd && ./bin/install-mecab-unidic-neologd -n -y

# mecab-naist-jdic
WORKDIR /usr/local/src
RUN wget https://ja.osdn.net/projects/naist-jdic/downloads/53500/mecab-naist-jdic-0.6.3b-20111013.tar.gz
RUN tar xzvf mecab-naist-jdic-0.6.3b-20111013.tar.gz
RUN cd mecab-naist-jdic-0.6.3b-20111013 && \
    ./configure --with-charset=utf8 --with-mecab-config=/usr/bin/mecab-config && \
    make && make install

# mecabrc
WORKDIR /etc
RUN sed -i -e 's/dicdir = .*/dicdir = \/usr\/lib\/x86_64-linux-gnu\/mecab\/dic\/mecab-ipadic-neologd/g' mecabrc
RUN cp mecabrc /usr/local/etc/mecabrc

# python libs
RUN pip install --upgrade pip
RUN pip install mecab-python3

# python libs (spaCy & GiNZA)
RUN pip install spacy ginza
RUN python -m spacy download ja_core_news_sm
RUN python -m spacy download ja_core_news_md
RUN python -m spacy download ja_core_news_lg
```

辞書のサイズが大きく（特に unidic 系）、イメージを作るのに結構な時間がかかります[^size]。

[^size]: ネットワークなどの環境次第ですが、イメージの生成に数十分以上かかる場合もあると思います。また、生成されるイメージのサイズは 17GB ほどになります。

## Python で使う
立ち上げたコンテナに入れば、以下のような Python コードが動きます。

```Python:test.py
# MeCab (辞書6パターン)
import MeCab
m_juman  = MeCab.Tagger('-d /var/lib/mecab/dic/juman-utf8')
m_ipadic = MeCab.Tagger('-d /var/lib/mecab/dic/ipadic-utf8')
m_unidic = MeCab.Tagger('-d /var/lib/mecab/dic/unidic')
m_naist_jdic = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/naist-jdic')
m_ipadic_neologd = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')
m_unidic_neologd = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-unidic-neologd')

# spaCy & GiNZA
import spacy
s_sm = spacy.load('ja_core_news_sm')
s_md = spacy.load('ja_core_news_md')
s_lg = spacy.load('ja_core_news_lg')
s_ginza = spacy.load('ja_ginza')

def parse_mecab(m, text):
    arr = []
    node = m.parseToNode(text)
    while node:
        if len(node.surface) > 0:
            arr.append(node.surface)
        node = node.next
    return ' '.join(arr)

def parse_spacy(s, text):
    arr = []
    for token in s(text):
        arr.append(token.orth_)
    return ' '.join(arr)

def parse(text):
    print('mecab-juman         : {}'.format(parse_mecab(m_juman, text)))
    print('mecab-ipadic        : {}'.format(parse_mecab(m_ipadic, text)))
    print('mecab-unidic        : {}'.format(parse_mecab(m_unidic, text)))
    print('mecab-naist-jdic    : {}'.format(parse_mecab(m_naist_jdic, text)))
    print('mecab-ipadic-NEologd: {}'.format(parse_mecab(m_ipadic_neologd, text)))
    print('mecab-unidic-NEologd: {}'.format(parse_mecab(m_unidic_neologd, text)))
    print('spaCy(news_sm)      : {}'.format(parse_spacy(s_sm, text)))
    print('spaCy(news_md)      : {}'.format(parse_spacy(s_md, text)))
    print('spaCy(news_lg)      : {}'.format(parse_spacy(s_lg, text)))
    print('GiNZA               : {}'.format(parse_spacy(s_ginza, text)))

parse('複数の形態素解析エンジンを切り替えて使えるコンテナが便利です')
```

出力は以下の通り。

```
mecab-juman         : 複数 の 形態 素 解析 エンジン を 切り替えて 使える コンテナ が 便利です
mecab-ipadic        : 複数 の 形態素 解析 エンジン を 切り替え て 使える コンテナ が 便利 です
mecab-unidic        : 複数 の 形態 素 解析 エンジン を 切り替え て 使える コンテナ が 便利 です
mecab-naist-jdic    : 複数 の 形態素 解析 エンジン を 切り替え て 使える コンテナ が 便利 です
mecab-ipadic-NEologd: 複数 の 形態素解析 エンジン を 切り替え て 使える コンテナ が 便利 です
mecab-unidic-NEologd: 複数 の 形態素解析 エンジン を 切り替え て 使える コンテナ が 便利 です
spaCy(news_sm)      : 複数 の 形態 素 解析 エンジン を 切り 替え て 使える コンテナ が 便利 です
spaCy(news_md)      : 複数 の 形態 素 解析 エンジン を 切り 替え て 使える コンテナ が 便利 です
spaCy(news_lg)      : 複数 の 形態 素 解析 エンジン を 切り 替え て 使える コンテナ が 便利 です
GiNZA               : 複数 の 形態素 解析 エンジン を 切り替え て 使える コンテナ が 便利 です
```

# おわりに
また形態素解析したくなったときには、即コンテナ化して使い始められます。