PR TIMES開発本部インターンの土屋です。私は現在データ分析インターンとしてプレスリリースデータをはじめとする社内のデータ解析を行なっています。
今回は、機械学習関連の開発環境を構築したいと思います。後述しますが、日本語の機械学習の開発環境には複数のソフトウェアをセットアップする必要があります。これらの設定をDockerやShell Scriptで実行することにより、セットアップのスピードを向上させます。また、日本語の前処理である正規化は主に正規表現を用いますが、中には文字コードを指定する必要があるものがあります。これらに対してPythonのPackageを作成し、コード作成者の負担を減らします。これらの施策によって開発環境構築のスピードを向上させることが今回の目標です。
開発環境を作成したきっかけ
PR TIMESには現状統一的な機械学習環境が存在しない状況です。これは開発速度に大きな影響を及ぼしています。
機械学習というと多くの方はモデリングの部分を想像されると思いますが、実際には前処理の方が大変であることが多いです。日本語は言語の中でも特殊であり、他の言語とは異なった前処理をする必要があります。ですので、一人で環境構築することは極めて難しく時間がかかります。
現在、機械学習のプロジェクトは人数が少ないため問題は起こっていません。しかし、今後機械学習・自然言語処理のプロジェクトや人数を増やしていく場合、環境面での時間のロスが大きくなると考えられます。そこで、挑戦することを決定しました。
日本語で自然言語処理をするにあたって必要な処理
日本語の自然言語処理の前処理では以下のような処理をします。
- 正規化: 文章の形式を一定形式に統一する処理
- 形態素解析: 文章を単語に分割する処理
これまでの実験からPR TIMESのプレスリリースデータには以下のような処理をするとよいことがわかってきました。
正規化
- HTMLタグの除去
- 句読点を用いた記事の抽出
- リンクの削除
- 日本語と英数字以外の文字の削除
- 括弧文字の削除 (一つ前の項目では削除できない)
- 全角文字・半角文字の統一
- 旧字体・新字体の統合
形態素解析
- 辞書の拡張
手法
これらの処理を簡単にするためにはコンテナ技術が必要だと考えました。そこで以下のように実行することにしました。
処理内容
- Dockerfileで以下の処理を実行
- MeCabをinstall
- neologdのinstallとMeCabと対応の設定
- 追加辞書用のipadicとモデルファイルをダウンロード、文字コード変更、ファイル書き換え
- Pythonのインストール
- コンテナに入ったあとの処理
- Shell ScriptでMeCabとPythonの接続確認
- Shell Scriptでuser辞書の登録
- その他
- 正規化のための自作package(pandas, reのみ依存)を作成(pandasはcsvを読み込むだけの利用)
- CIをGithub Actionsで導入: dockerのbuildと自作packageのテスト
MeCabのセットアップ
MeCabは日本語形態素解析器の一つで最もポピュラーです。MeCabが選ばれる理由として処理速度の速さが上げられます。同じ日本語形態素解析器でポピュラーなものにjanomeがあります。MeCabはjanomeの約10倍の速さで処理ができます。
MeCabのセットアップですが以下の3工程があります
- MeCabをインストールする
- MeCabのneologd辞書をダウンロードする
- MeCabのユーザー辞書を登録する
1, 2は以下のように行なっています
# MeCab,ipadicのinstallとsetup
RUN apt-get install mecab -y && \\
apt-get install libmecab-dev -y && \\
apt-get install mecab-ipadic-utf8 -y
# neologdのinstallとsetup
RUN git clone <https://github.com/neologd/mecab-ipadic-neologd.git> && \\
cd mecab-ipadic-neologd && \\
bin/install-mecab-ipadic-neologd -y &&\\
mv /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd /var/lib/mecab/dic
RUN rm /etc/mecabrc
COPY opt/mecab_setup/mecabrc /etc/mecabrc
COPY opt/mecab_setup/mecabrc /usr/local/etc/
3は煩雑な作業が必要になります。具体的には以下の工程が必要です。
- iapdic辞書のダウンロード
- ipadic辞書の文字コードの変更と設定ファイルの書き換え
- configコマンドの実行
- modelファイルのダウンロード
- modelファイルの文字コードの変更と設定ファイルの書き換え
- csvファイルからコスト付きcsvファイルを作成
- コスト付きcsvファイルからユーザー辞書(user.dic)を作成
- ユーザー辞書を登録
こちらの記事が詳細に説明をされていますので。詳しく知りたい方は適宜ご参照ください。
MeCab + neologd 辞書環境でユーザ辞書を追加する
上記の項目のうちmodelファイルの文字コードの変更まではDockerfileで行い、以後はシェルスクリプトにまとめることにしました。
- Dockerfile
# ipadicのinstallと文字コードの変更、ファイルの書き換え
RUN apt-get install nkf && \\
wget -O mecab-ipadic-2.7.0-20070801.tar.gz "mecab ipadic url" && \\
tar xvf mecab-ipadic-2.7.0-20070801.tar.gz && \\
rm mecab-ipadic-2.7.0-20070801.tar.gz && \\
nkf --overwrite -w mecab-ipadic-2.7.0-20070801/*
sed -i -e "s|EUC-JP|UTF-8|g" mecab-ipadic-2.7.0-20070801/dicrc
# settingのconfigure
RUN cd /mecab-ipadic-2.7.0-20070801 && \\
/usr/lib/mecab/mecab-dict-index -f utf-8 -t utf-8 &&\\
./configure --with-charset=utf8
# model fileのインストールと文字コードの変更、ファイルの書き換え
RUN wget -O mecab-ipadic-2.7.0-20070801.model.bz2 "model file url" &&\\
bzip2 -d mecab-ipadic-2.7.0-20070801.model.bz2 &&\\
nkf --overwrite -w mecab-ipadic-2.7.0-20070801.model &&\\
sed -i "s/euc-jp/utf-8/g" mecab-ipadic-2.7.0-20070801.model
- Shell Script
# -m mecab-ipadic-2.7.0-20070801.model \\ # モデルファイル
# -d mecab-ipadic-2.7.0-20070801 \\ # 辞書
# -u foo-cost.csv \\ # コスト付き辞書(作成するファイル)の名前
# -f utf-8 \\ # 参照元のCSVファイルの文字コード
# -t utf-8 \\ # 辞書の文字コード
# -a user_dic.csv # 参照元のCSVファイル
cd ../../../ #wdを移動
cp root/opt/mecab_setup/user_dic.csv user_dic.csv
/usr/lib/mecab/mecab-dict-index -m mecab-ipadic-2.7.0-20070801.model -d mecab-ipadic-2.7.0-20070801 -u user_dic_cost.csv -f utf-8 -t utf-8 -a user_dic.csv
# /usr/lib/mecab/mecab-dict-index \\
# -d mecab-ipadic-2.7.0-20070801 \\ # 辞書
# -u user.dic \\ # 生成するユーザー辞書(名前はuser.dicである必要はない)
# -f utf-8 \\ # user_dict_cost.csvの文字コード
# -t utf-8 \\ # mecab-ipadic-2.7.0-20070801の文字コード
# user_dict_cost.csv # 参照元のCSVファイル
/usr/lib/mecab/mecab-dict-index -d mecab-ipadic-2.7.0-20070801 -u user.dic -f utf-8 -t utf-8 user_dic_cost.csv
cp user.dic root/opt/mecab_setup/
Pythonの推奨設定とMeCabとの接続テスト
PythonからMeCab, neologd, ユーザー辞書を呼び出すテストを行うスクリプトを作成しました。テストはそれぞれ独立に書いたためこの設定を使わない場合でも実行することができます。
neologdが入っている場合「鬼滅の刃」という単語をMeCabが一形態素として抽出します。これができているかでneologdが動いているか確認します。
import MeCab
TOKENIZER = MeCab.Tagger("-Owakati")
def confirm_mecab_neologd():
print('MeCabとneologdが接続されているか確認')
words = TOKENIZER.parse('鬼滅の刃の映画を見に行った').split()
print('形態素解析の結果', words)
assert "鬼滅の刃" in words, "neologdの接続の失敗: 出力に鬼滅の刃という単語があるか確認して下さい"
print('ok: neologdとMeCabの接続が確認できました')
return
if __name__ == "__main__":
confirm_mecab_neologd()
デフォルトのuser.csvにはユーザ設定という単語が登録されています。この単語をMeCabが一形態素として抽出できるかでユーザー辞書が設定できているか確認します。
import MeCab
TOKENIZER = MeCab.Tagger("-Owakati")
def confirm_mecab_userdic():
print('MeCabとneologdとuser辞書が接続されているか確認')
words = TOKENIZER.parse('ユーザ設定は難しい').split()
print('形態素解析の結果', words)
assert "ユーザ設定" in words, "user辞書の接続の失敗: make_dict.pyの出力を確認してください"
print('ok: user辞書の接続が確認できました')
return
if __name__ == "__main__":
confirm_mecab_userdic()
上記の二つのPython Scriptを実行するShell Scriptです。ユーザー辞書に関してはデフォルトで入れていないためユーザーにテストを実行するか確認するようにしました。
python3.9 neologd_install_confirm.py
read -p "Did you add userdic to neologd ? (y/n): " DATA
case "$DATA" in
[yY]) python3.9 userdic_install_confirm.py ;;
[nN]) echo "SKIP testing connection: ユーザー辞書のテストをスキップしました" ;;
*) echo "Push y or n key."
esac
echo finish!
自作パッケージの作成
自作のパッケージには正規化用の以下の関数を作成しました。テストはpytestをローカルとGithub Actionsで行います。remove.pyでは以下を行う関数をまとめています。
- 括弧の削除
- HTML Tagの削除
- 参照文字の削除
- リンクの削除
- 日本語と英語と数字以外の文字の削除
import re
import pandas as pd
# 鉤括弧1文字の正規表現のstr型objectを作成
# reference: <https://ja.wikipedia.org/wiki/%E6%8B%AC%E5%BC%A7>
# 鉤括弧のunicodeを読み込みstr型の正規表現にする
kakko_data = pd.read_csv('normtext/csv/kakko.csv', index_col=0)
tmp = ""
for kakko in kakko_data["unicode"].values:
tmp += kakko
REGEX_KAKKO = "[" + tmp + "]+"
# 正規表現として使用するものをcompileする
REMOVE_KAKKO = re.compile(REGEX_KAKKO)
REMOVE_HTML_TAG = re.compile(r'<("[^"]*"|\\'[^\\']*\\'|[^\\'">])*>')
REMOVE_REFER_CHAR = re.compile(r'[&].+[;]')
REMOVE_LINKS = re.compile(
r"(http|https|ftp)(:\\/\\/[-_\\.!~*\\'()a-zA-Z0-9;\\/?:\\@&=\\+\\$,%#]+)")
REMOVE_SYMBOL = re.compile(
r'[^\\u3001-\\u303F\\u3040-\\u309F\\u30A0-\\u30FF\\u4E00-\\u9FFF\\u3005\\d\\w\\s]')
def remove_kakko(text: str) -> str:
text = REMOVE_KAKKO.sub("", text)
return text
def remove_html_tag(text: str) -> str:
text = REMOVE_HTML_TAG.sub("", text)
return text
def remove_refer_char(text: str) -> str:
text = REMOVE_REFER_CHAR.sub("", text)
return text
def remove_links(text: str) -> str:
text = REMOVE_LINKS.sub("", text)
return text
def remove_symbol(text: str) -> str:
text = REMOVE_SYMBOL.sub("", text)
return text
ほとんどの関数が正規表現で処理をするプログラムですが、remove_kakko関数はcsvを参照しています。この関数は括弧に使われる単語を全て削除する関数なのですが、該当するunicodeの番号が多くPython Scriptに載せてしまうと可読性が低下するので分割しました。
translate.pyでは以下を行う関数をまとめています。
- 旧字体から新字体へ変更
- 全角文字から半角文字へ変更
import pandas as pd
# 新旧字体の統一のための辞書を定義
JITAI_DF = pd.read_csv('csv/新旧字体表.csv')
OLD_KANJI = list(JITAI_DF['old_jikei'])
NEW_KANJI = list(JITAI_DF['new_jikei'])
JITAI_DICT = dict(zip(OLD_KANJI, NEW_KANJI))
# 全角と半角を統一するための関数を定義
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
ZEN2HAN = str.maketrans(ZEN, HAN)
def kyujitai_to_shinjitai(text: str) -> str:
encoded_text = text.translate(str.maketrans(JITAI_DICT))
return encoded_text
def zen_to_han(text: str) -> str:
text = text.translate(ZEN2HAN)
return text
なお、旧字体のcsvは以前以下の記事で作成したものです。

CIの作成
これらのコードはGitHubで管理をするので、push時にbuildテストと自作パッケージのテストを行なっています。
name: test
on:
push:
branches:
- "main"
- "dev"
jobs:
test:
name: Building Test
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Checkout And Build
run: docker-compose up -d --build
- name: Pip install
run: |
docker-compose exec -T jp-nlp-env bash
cd opt
pip install pandas pytest
- name: Pytest
run: |
docker-compose exec -T jp-nlp-env bash
cd opt
pytest
結果
今回の取り組みによって、複雑な環境構築をコマンド数回で終了させることに成功し、環境構築の負担を減らすことができました。
以下は作成後のdocker imageの写真です。

まとめ
機械学習の開発環境を作成したことによって環境構築速度が向上しました。特にMeCabの追加辞書の部分はミスを起こしやすいのでこの部分を自動化できたことは大きな前進だと感じています。
これからはより一層早いサイクルでの機械学習の実験や開発を進め、プロダクトに貢献していきたいと考えています。