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を使って分かち書きをおこなうクラスを定義して、TfidfVectorizertokenizerに指定したうえで、pickleで保存してみます。 tokenizerMeCab.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とします。

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する際に再作成しています。


関連記事