どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

日本語のテキストコーパスから辞書を作るライブラリを作りたい②

前々回の記事でTensorFlowを使ってテキスト分類をした結果、あまりうまくいきませんでした。原因として助詞や助動詞といった、テキストを分類する上であまり意味のなさそうなものがあるからかなと感じました。

そこで今回は以前作ったJapaneseTextEncoderに指定した品詞を取り除く機能を追加したうえで、もう一度TensorFlowを使ったテキスト分類をやってみます。

 

関連リンク

TensorFlowを使ってテキスト分類をしてみる。 - どん底から這い上がるまでの記録

日本語のテキストコーパスから辞書を作るライブラリを作りたい - どん底から這い上がるまでの記録

JapaneseTextEncoderの改良

この前作ったJapaneseTextEncoderはMeCabのオプションに”-Owakati"を指定していました。品詞を取り除くにはオプションを”-Ochasen"に変更します。

        self.tagger = MeCab.Tagger("-Ochasen")

また、テキストを形態素に分割するメソッドtokenizeを以下のように変更します。

# 変更前
    def tokenize(self, sentence):
        return self.tagger.parse(sentence).strip().split()
# 変更後
    def tokenize(self, sentence):
        tag = self.tagger.parseToNode(sentence)
        tokens = []
        while tag:
            features = tag.feature.split(",")
            pos = features[0]
            token = tag.surface
            if pos in self.filters:
                tag = tag.next
                continue
            tokens.append(token)
            tag = tag.next
        return tokens if tokens else None

tokenizeには変更前と同じように文字列を渡します。戻り値は要素が形態素のリストを返します。self.filtersには除外する品詞が要素になったリストになっています。

e.g. self.filters = ["助詞", "助動詞"]

改良したところは以上です。

TensorFlowを使ったテキスト分類

filtering

今回は助詞と助動詞をテキストから取り除くので、以前書いたdatasets.pyを以下のように変更します。

    encoder = JapaneseTextEncoder(
        sentences,
        min_occurrences=1,
        filters=["助詞", "助動詞"],
        append_eos=False
    )

学習

他に変更点はないので、前に書いたプログラムで学習をします。

結果

results = model.evaluate(test_x, test_t)
print(results)

分類精度は75%となりました。前は73%だったので、2%上がりましたね。

[0.5709238412396219, 0.7516778555492427]
f:id:pytry3g:20180723073813p:plain

Epochを増やす

助詞と助動詞を取り除く前処理をしただけでは前と結果は変わりませんでした。、分類精度が悪いのはただ単にepoch数が短いからなのでは、と思ったのでepoch数を40から100に前処理をしないで再度学習をしてみました。

結果

results = model.evaluate(test_x, test_t)
print(results)

結果は、、、

[0.26587492907607313, 0.9127516786524114]

f:id:pytry3g:20180723074754p:plain

f:id:pytry3g:20180723074806p:plain

結論

今回のケースは前処理が問題ではなく、エポック数が少ないのが原因でした。学習するために使ったデータセットによっては、助詞や助動詞を取り除くといった前処理が必要になると思いたい。

ゼロから作るDeepLearning

自然言語処理を勉強したい方へのおすすめの本です。

tensorflow, chainerやPyTorchといったフレームワークを使わずにゼロからnumpyを使ってディープラーニングの実装をしています。

扱っている内容はword2vec, RNN, GRU, seq2seqやAttentionなど、、、

ソースコード

text_encoder.py

from collections import Counter
from reserved_tokens import SOS_INDEX
from reserved_tokens import EOS_INDEX
from reserved_tokens import UNKNOWN_INDEX
from reserved_tokens import RESERVED_ITOS


class JapaneseTextEncoder:
    """ Encodes the text using a tokenizer.

    Args:
        corpus (list of strings): Text strings to build dictionary on.
        min_occurrences (int, optional): Minimum number of occurences for a token to be
            added to dictionary.
        append_sos (bool, optional): If 'True' append SOS token onto the begin to the encoded vector.
        append_eos (bool, optional): If 'True' append EOS token onto the end to the encoded vector.
        reserved_tokens (list of str, optional): Tokens added to dictionary; reserving the first
            'len(reserved_tokens') indices.

    Example:
        >>> corpus = ["セネガルつええ、ボルト三体くらいいるわ笑笑", \
                      "しょーみコロンビアより強い", \
                      "それなまちがいないわ"]

        >>> encoder = JapaneseTextEncoder(corpus)
        >>> encoder.encode("コロンビア強い")
        [18, 20, 2]
        >>> encoder.vocab
        ['<pad>', '<unk>', '</s>', '<s>', 'セネガル', 'つえ', 'え', '、', 'ボルト', '三', '体', 'くらい', ' いる', 'わ', '笑', 'しょ', 'ー', 'み', 'コロンビア', 'より', '強い', 'それ', 'な', 'まちがい', 'ない']
        >>> encoder.decode(encoder.encode("コロンビア強い"))
        コロンビア強い</s>

    """
    def __init__(self,
                 corpus,
                 min_occurrences=1,
                 append_sos=False,
                 append_eos=True,
                 filters=None,
                 reserved_tokens=RESERVED_ITOS):
        try:
            import MeCab
        except ImportError:
            print("Please install MeCab.")
            raise

        if not isinstance(corpus, list):
            raise TypeError("Corpus must be a list of strings.")

        self.tagger = MeCab.Tagger("-Ochasen")
        self.append_sos = append_sos
        self.append_eos = append_eos
        self.tokens = Counter()
        self.filters = ["BOS/EOS"]
        if filters is not None:
            if not isinstance(filters, list):
                raise TypeError("Filters must be a list of POS.")
            self.filters += filters

        for sentence in corpus:
            tokens = self.tokenize(sentence)
            if tokens:
                self.tokens.update(tokens)

        self.itos = reserved_tokens.copy()
        self.stoi = {token: index for index, token in enumerate(reserved_tokens)}
        for token, cnt in self.tokens.items():
            if cnt >= min_occurrences:
                self.itos.append(token)
                self.stoi[token] = len(self.itos) - 1

    @property
    def vocab(self):
        return self.itos

    @property
    def word2id(self):
        return self.stoi

    @property
    def id2word(self):
        return {index: token for token, index in self.stoi.items()}

    def encode(self, sentence, sos_index=SOS_INDEX, eos_index=EOS_INDEX, unknown_index=UNKNOWN_INDEX):
        tokens = self.tokenize(sentence)
        if tokens is None:
            raise TypeError("Invalid type None...")
        indices = [self.stoi.get(token, unknown_index) for token in tokens]
        if self.append_sos:
            indices.insert(0, sos_index)
        if self.append_eos:
            indices.append(eos_index)
        return indices

    def decode(self, indices):
        tokens = [self.itos[index] for index in indices]
        return "".join(tokens)

    def tokenize(self, sentence):
        tag = self.tagger.parseToNode(sentence)
        tokens = []
        while tag:
            features = tag.feature.split(",")
            pos = features[0]
            token = tag.surface
            if pos in self.filters:
                tag = tag.next
                continue
            tokens.append(token)
            tag = tag.next
        return tokens if tokens else None