Mac に MeCab と NEologd 環境の構築と辞書のカスタマイズ
July 21, 2020
環境
macOS Catalina version 10.15.5
インストール方法
neologd の wiki に MeCab とその依存ライブラリ含め、よくまとまっていた。
neologd/mecab-ipadic-neologd README.ja.md
動作に必要なライブラリのインストール
$ brew install mecab mecab-ipadic git curl xz
mecab-ipadic-NEologd のインストール
以下は、インストールの際に -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
動作確認
mecab コマンドの -d オプションで辞書の path を指定する。
# 通常の実行結果$ echo "インスタ映え" | mecabインスタ 名詞,一般,*,*,*,*,*映え 名詞,一般,*,*,*,*,映え,ハエ,ハエEOS# neologdによる実行結果$ echo "インスタ映え" | mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologdインスタ映え 名詞,固有名詞,一般,*,*,*,インスタ映え,インスタハエ,インスタハエEOS
デフォルトの辞書を変更する場合は、mecabrc
の dicdir
を編集する。
参考: 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-8import Mecabinput = 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.featurenode = 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-8import Mecabinput = 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.featurenode = 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 _runself._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_eventshandler_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_eventsself._handle_recv()File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/zmq/eventloop/zmqstream.py", line 486, in _handle_recvself._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_callbackcallback(*args, **kwargs)File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/ipykernel/iostream.py", line 120, in _handle_eventevent_f()File "/Users/user_name/.pyenv/versions/anaconda3-2019.03/lib/python3.7/site-packages/ipykernel/iostream.py", line 382, in _flushparent=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 sendto_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 serializecontent = 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 dumpss = 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: utf8from __future__ import unicode_literalsimport reimport unicodedatadef unicode_normalize(cls, s):pt = re.compile('([{}]+)'.format(cls))def norm(c):return unicodedata.normalize('NFKC', c) if pt.match(c) else cs = ''.join(norm(x) for x in re.split(pt, s))s = re.sub('-', '-', s)return sdef 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 ss = remove_space_between(blocks, blocks, s)s = remove_space_between(blocks, basic_latin, s)s = remove_space_between(basic_latin, blocks, s)return sdef 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 hyphenss = re.sub('[﹣-ー—―─━ー]+', 'ー', s) # normalize choonpuss = re.sub('[~∼∾〜〰~]', '', s) # remove tildess = s.translate(maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~。、・「」','!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」'))s = remove_extra_spaces(s)s = unicode_normalize('!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜', s) # keep =,・,「,」s = re.sub('[’]', '\'', s)s = re.sub('[”]', '"', s)return sif __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+ レモン一絞り")