ハカセノオト

moon indicating dark mode
sun indicating light mode

Mac に MeCab と NEologd 環境の構築と辞書のカスタマイズ

July 21, 2020

環境

macOS Catalina version 10.15.5

インストール方法

neologd の wiki に MeCab とその依存ライブラリ含め、よくまとまっていた。

neologd/mecab-ipadic-neologd README.ja.md

動作に必要なライブラリのインストール

参考: https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md#%E4%BE%8B-%E5%8B%95%E4%BD%9C%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

$ brew install mecab mecab-ipadic git curl xz

mecab-ipadic-NEologd のインストール

参考: https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md#mecab-ipadic-neologd-%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B%E6%BA%96%E5%82%99

以下は、インストールの際に -a オプションを指定していて、レポジトリに含まれる辞書をすべてインストールしている。

$ git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
$ ./bin/install-mecab-ipadic-neologd -n -a

私の環境の場合デフォルトで、以下の path にインストールされた。

/usr/local/lib/mecab/dic/mecab-ipadic-neologd

以下のコマンドで、設定されている path の確認ができる。

mecab-config --dicdir

動作確認

shell 上で動作確認する。

mecab コマンドの -d オプションで辞書の path を指定する。

# 通常の実行結果
$ echo "インスタ映え" | mecab
インスタ 名詞,一般,*,*,*,*,*
映え 名詞,一般,*,*,*,*,映え,ハエ,ハエ
EOS
# neologdによる実行結果
$ echo "インスタ映え" | mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd
インスタ映え 名詞,固有名詞,一般,*,*,*,インスタ映え,インスタハエ,インスタハエ
EOS

デフォルトの辞書を変更する場合は、mecabrcdicdir を編集する。

参考: MacにMeCabを利用できる環境を整える - Qiita

python から使えるようにする

インストール

$ pip install mecab-python3

動作確認

参考: https://github.com/neologd/mecab-ipadic-neologd/wiki/ProgrammingLanguage.ja#python

以下のように、MeCab Tagger の引数で辞書の path を指定する。

MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/")

#!/usr/bin/env python
# coding: utf-8
import Mecab
input = u'すもももももももものうち'
mt = MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/")
input = input.encode("utf-8")
node = mt.parseToNode(input)
while node:
print node.surface, node.feature
node = node.next

MeCab でカスタム辞書を使う

今回、辞書に登録されていない記号を任意の品詞として登録するしたいというユースケースがあった。 以下に記載したリンクと同じことがしたい。

つまり、

IPA 辞書を使って、MeCab に記号を食わせた時に

+ 名詞,サ変接続,*,*,*,*,*
EOS

みたいになってしまうものを

+ 記号,一般,*,*,*,*,*
EOS

と出力されるようにしたいということである。

unk.def の以下の箇所を

SYMBOL,1283,1283,17585,名詞,サ変接続,*,*,*,*,*

以下のように変更すればよい。

SYMBOL,1283,1283,17585,記号,一般,*,*,*,*,*

この次に、バイナリ形式の unk.dic へ変換するために make する必要がある。

今回は、 MeCab の公式サイト から IPA 辞書をダウンロードして make する方法を記載する。

辞書のダウンロードと辞書の make は以下の手順に従う。

MeCab: Yet Another Part-of-Speech and Morphological Analyzer

ただし、辞書のインストール先をデフォルトから変えたい(オリジナルは残したい)かつ、utf-8 の文字コードを使いたいので ./configure 時にオプションを設定する。 (utf-8 を指定する理由は後述)

% tar zxfv mecab-ipadic-2.7.0-XXXX.tar.gz
% mecab-ipadic-2.7.0-XXXX
% ./configure --with-dicdir=/usr/local/lib/mecab/dic/ipadic-20200721custom --with-charset=utf-8
% make
% su
# make install

動作確認

#!/usr/bin/env python
# coding: utf-8
import Mecab
input = u'すもももももももものうち'
mt = MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/ipadic-20200721custom/")
input = input.encode("utf-8")
node = mt.parseToNode(input)
while node:
print node.surface, node.feature
node = node.next

utf-8 を指定しなかった場合に出たエラー

なお、utf-8 を指定しなかった場合、python スクリプト内で使用する際に、以下のエラーが出た。

Exception in callback BaseAsyncIOLoop._handle_events(33, 1)
handle: <Handle BaseAsyncIOLoop._handle_events(33, 1)>
Traceback (most recent call last):
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/tornado/platform/asyncio.py", line 138, in _handle_events
handler_func(fileobj, events)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/zmq/eventloop/zmqstream.py", line 456, in _handle_events
self._handle_recv()
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/zmq/eventloop/zmqstream.py", line 486, in _handle_recv
self._run_callback(callback, msg)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/zmq/eventloop/zmqstream.py", line 438, in _run_callback
callback(*args, **kwargs)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/ipykernel/iostream.py", line 120, in _handle_event
event_f()
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/ipykernel/iostream.py", line 382, in _flush
parent=self.parent_header, ident=self.topic)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/jupyter_client/session.py", line 746, in send
to_send = self.serialize(msg, ident)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/jupyter_client/session.py", line 634, in serialize
content = self.pack(content)
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/jupyter_client/session.py", line 89, in <lambda>
ensure_ascii=False, allow_nan=False,
File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/zmq/utils/jsonapi.py", line 43, in dumps
s = s.encode('utf8')
UnicodeEncodeError: 'utf-8' codec can't encode characters in position 51-52: surrogates not allowed

TIPS

mecab-ipadic-neologd の wiki は情報がよくまとまっているので、1通り目を通したほうが良い。

特に、解析の質に直結するであろう形態素解析する前処理としての正規化は、実施したほうが良いと思われる。

参考: 解析前に行うことが望ましい文字列の正規化処理

# encoding: utf8
from __future__ import unicode_literals
import re
import unicodedata
def unicode_normalize(cls, s):
pt = re.compile('([{}]+)'.format(cls))
def norm(c):
return unicodedata.normalize('NFKC', c) if pt.match(c) else c
s = ''.join(norm(x) for x in re.split(pt, s))
s = re.sub('-', '-', s)
return s
def remove_extra_spaces(s):
s = re.sub('[  ]+', ' ', s)
blocks = ''.join(('\u4E00-\u9FFF', # CJK UNIFIED IDEOGRAPHS
'\u3040-\u309F', # HIRAGANA
'\u30A0-\u30FF', # KATAKANA
'\u3000-\u303F', # CJK SYMBOLS AND PUNCTUATION
'\uFF00-\uFFEF' # HALFWIDTH AND FULLWIDTH FORMS
))
basic_latin = '\u0000-\u007F'
def remove_space_between(cls1, cls2, s):
p = re.compile('([{}]) ([{}])'.format(cls1, cls2))
while p.search(s):
s = p.sub(r'\1\2', s)
return s
s = remove_space_between(blocks, blocks, s)
s = remove_space_between(blocks, basic_latin, s)
s = remove_space_between(basic_latin, blocks, s)
return s
def normalize_neologd(s):
s = s.strip()
s = unicode_normalize('0-9A-Za-z。-゚', s)
def maketrans(f, t):
return {ord(x): ord(y) for x, y in zip(f, t)}
s = re.sub('[˗֊‐‑‒–⁃⁻₋−]+', '-', s) # normalize hyphens
s = re.sub('[﹣-ー—―─━ー]+', 'ー', s) # normalize choonpus
s = re.sub('[~∼∾〜〰~]', '', s) # remove tildes
s = s.translate(
maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~。、・「」',
'!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」'))
s = remove_extra_spaces(s)
s = unicode_normalize('!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜', s) # keep =,・,「,」
s = re.sub('[’]', '\'', s)
s = re.sub('[”]', '"', s)
return s
if __name__ == "__main__":
assert "0123456789" == normalize_neologd("0123456789")
assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ" == normalize_neologd("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
assert "abcdefghijklmnopqrstuvwxyz" == normalize_neologd("abcdefghijklmnopqrstuvwxyz")
assert "!\"#$%&'()*+,-./:;<>?@[¥]^_`{|}" == normalize_neologd("!”#$%&’()*+,-./:;<>?@[¥]^_`{|}")
assert "=。、・「」" == normalize_neologd("=。、・「」")
assert "ハンカク" == normalize_neologd("ハンカク")
assert "o-o" == normalize_neologd("o₋o")
assert "majikaー" == normalize_neologd("majika━")
assert "わい" == normalize_neologd("わ〰い")
assert "スーパー" == normalize_neologd("スーパーーーー")
assert "!#" == normalize_neologd("!#")
assert "ゼンカクスペース" == normalize_neologd("ゼンカク スペース")
assert "おお" == normalize_neologd("お お")
assert "おお" == normalize_neologd(" おお")
assert "おお" == normalize_neologd("おお ")
assert "検索エンジン自作入門を買いました!!!" == \
normalize_neologd("検索 エンジン 自作 入門 を 買い ました!!!")
assert "アルゴリズムC" == normalize_neologd("アルゴリズム C")
assert "PRML副読本" == normalize_neologd("   PRML  副 読 本   ")
assert "Coding the Matrix" == normalize_neologd("Coding the Matrix")
assert "南アルプスの天然水Sparking Lemonレモン一絞り" == \
normalize_neologd("南アルプスの 天然水 Sparking Lemon レモン一絞り")
assert "南アルプスの天然水-Sparking*Lemon+レモン一絞り" == \
normalize_neologd("南アルプスの 天然水- Sparking* Lemon+ レモン一絞り")

hnishi のブログ


技術系の記事など。
綺麗に書こうとすると続かない気がするので、割と雑に、備忘録的に書いていく方針。
内容に誤りがあった場合などのご連絡は、Twitter の DM などで頂けると幸いです。