【Python】MeCabのTaggerオブジェクトを持つ単語分割器をpickleで保存する方法
scikit-learnのTfidfVectorizerではテキストを単語分割するためのtokenizerを与えることができます。 日本語テキストを対象とする場合、日本語の形態素解析器であるMeCabのPythonラッパーが提供するTaggerを利用したオブジェクトをtokenizerと指定することがあるのではないでしょうか。 tokenizerにTaggerオブジェクトを指定したTfidfVectorizerをpickleで保存するとエラーが出てしまい、ファイルに書き出すことができません。 本記事ではMeCabのTaggerオブジェクトを活用したtokenizerによってテキストを単語分割するTfidfVectorizerをpickle化するための方法を紹介します。 本記事を読むことで独自に定義したクラスをpickleするための方法について理解できます。
目次
本記事では以下のバージョンを用いています。
scikit-learn==0.23.2
mecab-python3==0.996.5
pickleで保存するときにエラーとなるプログラム
試しにMeCabb.Tagger
を使って分かち書きをおこなうクラスを定義して、TfidfVectorizer
のtokenizer
に指定したうえで、pickle
で保存してみます。
tokenizer
にMeCab.Tagger
を利用したオブジェクトを指定したTfidfVectorizer
は、pickle.dump
で保存しようとすると以下のようなエラーが起きます。
import pickle
from sklearn.feature_extraction.text import TfidfVectorizer
import MeCab
class T:
def __init__(self, option='-Owakati'):
self.option = option
self.tagger = MeCab.Tagger(option)
def __call__(self, text):
ret = self.tagger.parse(text).rstrip()
return ret
if __name__ == '__main__':
v = TfidfVectorizer(tokenizer=T())
texts = [
'すもももももももものうち'
]
v.fit(texts)
with open('test.pickle', 'wb') as f:
pickle.dump(v, f)
Traceback (most recent call last):
File "main.py", line 41, in <module>
pickle.dump(t, f)
TypeError: cannot pickle 'Tagger' object
これはMeCabのPythonラッパーはSWIGで作成されており、MeCabのTaggerはpickle化がサポートされていないためです。
pickle/unpickle時の挙動を記述するための情報
上記を読んで、本記事で必要な情報の要点を以下にまとめます。
- pickle時には
__reduce_ex__
メソッドが呼ばれ、unpickle時に必要な情報を返す __reduce_ex__
メソッドは5つ組のタプルを返す- タプルの各要素の説明
- 1つ目はunpickle時に呼び出される関数。今回は単語分割器に相当するクラスオブジェクトを作成したいので
func = T
とします。 - 2つ目は1つ目の関数を呼び出す際に与える引数。
- 3つ目はオブジェクトの状態 (属性など) 。unpickle時にオブジェクトの状態として読み込まれます。
- 4つ目はdictライクなオブジェクト。pickle対象のオブジェクトが保持するデータをunpickleする際に読み込むためのものだと思いますが今回は不要なので
None
とします。 - 5つ目はlistライクなオブジェクト。pickle対象のオブジェクトが保持するデータをunpickleする際に読み込むためのものだと思いますが今回は不要なので
None
とします。
- 1つ目はunpickle時に呼び出される関数。今回は単語分割器に相当するクラスオブジェクトを作成したいので
pickle化可能なサンプル
import pickle
import MeCab
class T:
def __init__(self, option=''):
self.option = option
self.tagger = MeCab.Tagger(option)
def __getstate__(self):
return {'option': self.option}
def __setstate__(self, state):
for k, v in state.items():
setattr(self, k, v)
def __getnewargs__(self):
return self.option,
def __reduce_ex__(self, proto):
func = T
args = self.__getnewargs__()
state = self.__getstate__()
listitems = None
dictitems = None
rv = (func, args, state, listitems, dictitems)
return rv
def __call__(self, text):
ret = self.tagger.parse(text).rstrip()
return ret
if __name__ == '__main__':
text = 'すもももももももものうち'
with open('test.pickle', 'wb') as f:
t = T('-Owakati')
pickle.dump(t, f)
print(t(text))
with open('test.pickle', 'rb') as f:
t2 = pickle.load(f)
print(t2(text))
上記サンプルはpickle時にTagger
オブジェクトをpickleの対象から除き、unpickleする際に同じ設定で再度Taggerオブジェクトを作成するようにしています。こうすることで、pickle時にTaggerを保持している影響でエラーとなることを避けています。
結果は以下のようになります。
すもも も もも も もも の うち
すもも も もも も もも の うち
t2 = pickle.load(f)
で作成したオブジェクトが、pickleで保存したt
と同様の結果を出力していることがわかります。
おわり
本記事ではMeCabのTaggerオブジェクトを活用した単語分割用のクラスをpickleするための方法について紹介しました。 正確に言うと、Taggerオブジェクトはpickle時にはpickleの対象から除き、unpickleする際に再作成しています。