📖

Rails × ruby-spacy 環境を Docker で構築して自然言語処理に入門する

2024/03/22に公開

Rails で構築しているアプリケーションで自然言語処理を行いたかったので、Ruby で自然言語処理を行えるライブラリの ruby-spacy の検証を行うために docker で環境構築を行うことにしました。
後述しますが、単なる gem ではなくある程度準備が必要なもので、はまった個所もいくつかあったので、備忘録として残します。

ruby-spacy とは

ruby-spacy とは Yoichiro Hasebe さんによって開発されたライブラリで、Python 用の自然言語処理ライブラリである spaCy を Ruby で利用できるようにしたライブラリです。

https://github.com/yohasebe/ruby-spacy

spaCy とは、Python/Cython で構築された自然言語処理を行うためのライブラリで、訓練済みの統計モデルを使用することができます。

参考: https://spacy.io/
参考: https://ja.wikipedia.org/wiki/SpaCy

また、日本語のテキストを解析するための言語モデルであり、リクルートと国立国語研究所の共同研究によって開発された GiNZA を spaCy でも使用することができます。

参考: https://www.recruit.co.jp/newsroom/2019/0402_18331.html

上記のように、ruby-spacy では Python ライブラリである spaCy を呼び出しています。
内部で PyCall という Ruby から Python を呼び出すための gem を利用しているので、Rails 環境の中に Python 環境を同居させる必要があります。

サンプルリポジトリ

https://github.com/ndjndj/rails-spacy

ディレクトリ構造

こんな感じでプロジェクトディレクトリに空のファイル群を作成しておきます。

.
├─ compose.yml
└─ rails
    ├─ Dockerfile
    ├─ Gemfile
    ├─ Gemfile.lock
    └─ entrypoint.sh

データベースに postgres を使用していますが、今回は使用しないので任意で大丈夫です。
Gemfile.lock は空のままで大丈夫です。
entrypoint.sh には、サーバーのプロセスが残っている場合、新たにサーバーを起動できないため、それを防ぐために起動時にプロセスをいったん削除するコマンドが記述されています。

compose.yml
version: '3'
services: 
  db: 
    container_name: postgres-spacy-sample
    image: postgres:16.0
    ports: 
      - "5432:5432"
    volumes: 
      - pg-data:/var/lib/postgresql/data
    environment: 
      - POSTGRES_DATABASE=postgres 
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password 
      - POSTGRES_ROOT_PASSWORD=root
  api: 
    container_name: api-spacy-sample
    build: 
      context: ./rails
    command: /bin/bash
    volumes:
      - ./rails:/usr/src/app
    ports: 
      - 3000:3000
    depends_on:
      - db 
    tty: true 
    stdin_open: true
volumes: 
  pg-data:
  bin: 
    driver: local
Dockerfile
FROM ruby:3.3.0
RUN apt-get update -qq && apt-get install -y vim 

RUN apt-get install -y build-essential
RUN apt-get install -y curl wget zip git

RUN git clone https://github.com/pyenv/pyenv.git ~/.pyenv
RUN [ \
    "/bin/bash", "-c", \
    " \
        source ~/.bashrc \
        && echo 'export PYENV_ROOT=\"$HOME/.pyenv\"' >> ~/.bashrc \
        && echo 'command -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"' >> ~/.bashrc \
        && echo 'eval \"$(pyenv init -)\"' >> ~/.bashrc \
        && source ~/.bashrc \
        && export CONFIGURE_OPTS=\"--enable-shared\" \
        && pyenv install 3.10.6 \
        && pyenv global 3.10.6 \
        && pip install spacy==3.6.0 \
        && pip install ginza==5.1.3 \
        && python -m spacy download ja_core_news_sm \
    "] 

RUN mkdir /usr/src/app 
WORKDIR /usr/src/app
COPY Gemfile /usr/src/app/Gemfile 
COPY Gemfile.lock /usr/src/app/Gemfile.lock 

RUN gem update --system 
RUN bundle update --bundler 

RUN bundle install 
COPY . /usr/src/app 

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh 
ENTRYPOINT ["entrypoint.sh"]
Gemfile
source "https://rubygems.org"
git_source(:github) {|repo| "https://github.com/#{repo}.git" }

ruby "3.3.0"

gem "rails", "~> 7.1.2"
entrypoint.sh
#!/bin/bash
set -e

rm -f /usr/src/app/tmp/pids/server.pid 

exec "$@"

Rails アプリケーションの作成

下記コマンドを実行して、Rails アプリケーションを作成します。

docker compose run --rm api rails new . --skip --database=postgresql --api --skip-bundle

Gemfile, database.yml を書き換える

以下のように Gemfile を書き換えます。
また、config/database.yml も作成した環境に合わせて書き換えます。

Gemfile
source "https://rubygems.org"
git_source(:github) {|repo| "https://github.com/#{repo}.git" }

ruby "3.3.0"

gem "bootsnap", require: false

gem "pg", "~> 1.1"

gem "puma", "~> 6.4.0"

gem "rails", "~> 7.1.2"

gem "ruby-spacy", "~> 0.2.2"

gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: postgres
  password: password 
  host: db
  port: 5432

development:
  <<: *default
  database: postgres_dev
  password: password

test:
  <<: *default
  database: postgres_test
  password: password

gem をインストールする

下記コマンドを実行して、gem をインストールします。

docker compose run --rm api bundle install

Docker コンテナを立ち上げる

コンテナをビルドして立ち上げます。

docker compose build --no-cache
docker compose up -d

Postgresql の DB を作成する

現状だと DB が存在しないため作成する必要があります。
rails コンテナ環境に入ります。

docker compose exec api /bin/bash

rails コンテナ内で db:create して DB を作成します。

rails db:create

spaCy の導入について

先述したように ruby-spacy は、Python 用ライブラリの spaCy のラッパーなので利用するためには、Python の環境を構築する必要があります。
Dockerfile で該当する部分を以下に記します。

# Dockerfile
# ( 中略 )
RUN apt-get install -y build-essential
RUN apt-get install -y curl wget zip git

RUN git clone https://github.com/pyenv/pyenv.git ~/.pyenv
RUN [ \
    "/bin/bash", "-c", \
    " \
        source ~/.bashrc \
        && echo 'export PYENV_ROOT=\"$HOME/.pyenv\"' >> ~/.bashrc \
        && echo 'command -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"' >> ~/.bashrc \
        && echo 'eval \"$(pyenv init -)\"' >> ~/.bashrc \
        && source ~/.bashrc \
        && export CONFIGURE_OPTS=\"--enable-shared\" \
        && pyenv install 3.10.6 \
        && pyenv global 3.10.6 \
        && pip install spacy==3.6.0 \
        && pip install ginza==5.1.3 \
        && python -m spacy download ja_core_news_sm \
    "]
# ( 中略 )

bash を使う

後述する理由により、直接 Python をインストールせずに、Pyenv を用いて Python の仮想環境を構築します。
dockerfile で RUN コマンドを書くと /bin/sh が使われますが、sh だと、pyenv をインストールすることができなかったので、bash を使うようにしています。

RUN [ \
    "/bin/bash", "-c", \
    ( 中略 )

pyenv のインストール

直接 Python をインストールしてそこにライブラリを pip install すると、PEP 668 エラーが発生します。

RUN apt-get install -y curl wget zip git

RUN git clone https://github.com/pyenv/pyenv.git ~/.pyenv
RUN [ \
    "/bin/bash", "-c", \
    " \
        source ~/.bashrc \
        && echo 'export PYENV_ROOT=\"$HOME/.pyenv\"' >> ~/.bashrc \
        && echo 'command -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"' >> ~/.bashrc \
        && echo 'eval \"$(pyenv init -)\"' >> ~/.bashrc \
        && source ~/.bashrc \

ruby-spacy の案内にしたがう

ruby-spacy の README に従い、enable-shared オプションを有効にし、spaCy と 言語モデルをインストールしていきます。今回は言語モデルとして ja_core_news_sm を導入しています。

        && export CONFIGURE_OPTS=\"--enable-shared\" \
        && pyenv install 3.10.6 \
        && pyenv global 3.10.6 \
        && pip install spacy==3.6.0 \
        && pip install ginza==5.1.3 \
        && python -m spacy download ja_core_news_sm \
    "]

rails console で確認する

docker build と docker up が完了したら、rails コンテナに入り、Rails コンソールを利用して、ruby-spacy 経由で、spaCy を呼び出せることを確認します。
サンプルコードは ruby-spacy の README に記載されているものです。

rails c
require "ruby-spacy"
require "terminal-table"

nlp = Spacy::Language.new("ja_core_news_sm") # インストールした言語モデルを指定
doc = nlp.read("任天堂は1983年にファミコンを14,800円で発売した。")

headings = ["text", "lemma", "pos", "tag", "dep"]
rows = []

doc.each do |token|
  rows << [token.text, token.lemma, token.pos, token.tag, token.dep]
end

table = Terminal::Table.new rows: rows, headings: headings
puts table

さいごに

spaCy を呼び出す ruby-spacy の導入を行いました。
また、Python を呼び出せる PyCall の存在も今回初めて知ったので今後使ってみたいと思います。

参考

https://www.rubydoc.info/gems/ruby-spacy/0.2.2
https://github.com/yohasebe/ruby-spacy
https://spacy.io/
https://ja.wikipedia.org/wiki/SpaCy
https://www.recruit.co.jp/newsroom/2019/0402_18331.html
https://peps.python.org/pep-0668/

その他学習の参考になったページ
https://qiita.com/ryoma_kochi/items/55d015ed9cb0a61b55a6
https://qiita.com/nikotan/items/47d8c73712a8d985c454
https://kantaro-cgi.com/blog/docker/dockerfile-build-by-bash.html

脚注
  1. という認識です。間違ってたら教えてください。。。 ↩︎

Discussion