Ahogrammer

Deep Dive Into NLP, ML and Cloud

TensorFlow Datasetsを使ってテキストの分かち書きとID化をする

自然言語処理で欠かせない前処理としてテキストの分かち書きとID化があります。分かち書きはテキストを分割するプロセスであり、文字や単語、サブワードといった単位でテキストを分割します。これらの分割後の要素はトークンと呼ばれます。一方、ID化はトークンを数値に変換するプロセスです。数値に変換することで、機械学習アルゴリズムトークンを与えることができるようになります。本記事では、これらのプロセスをTensorFlow Datasetsの機能を使って実現する方法について紹介します。

準備

この記事ではTensorFlow Datasetsを使って分かち書きとID化を行います。TensorFlow DatasetsはIMDbやMNISTなどの機械学習でよく使われるデータセットを簡単に使い始められる形で提供してくれるパッケージです。その中の一部の機能として、テキストの分かち書きとID化をするための機能を提供しています。使い始めるために、以下のようにしてTensorFlow Datasetsをインストールしておく必要があります。

$ pip install tensorflow-datasets

また、後で日本語を扱うために、Janomeもインストールしておきます。

$ pip install janome

英語の場合

ではまずは、英語で書かれたテキストの分かち書きとID化をやってみましょう。はじめに、Tokenizerを使ってテキストをトークンに分割します。得られたトークンからボキャブラリを作成し、それをTokenTextEncoderに与えてエンコーダを作成します。

import tensorflow_datasets as tfds

example_text = 'She sells seashells by the seashore'
tokenizer = tfds.features.text.Tokenizer()
tokenized_text = tokenizer.tokenize(example_text)
vocabulary_list = list(set(tokenized_text))
encoder = tfds.features.text.TokenTextEncoder(vocabulary_list,
                                              lowercase=True)

エンコーダを作成したら、encodeメソッドを呼んで、トークンをID化します。また、ID化したトークンを文字列に戻すためにdecodeメソッドも呼びます。

encoded_example = encoder.encode(example_text)
decoded_example = encoder.decode(encoded_example)

変換結果を表示するためのコードを書きます。

>>> print(tokenized_text)
>>> print(encoded_example)
>>> print(decoded_example)
>>> print(encoder.vocab_size)

以下のような結果が得られます。分かち書きとID化ができていることを確認できます。

['She', 'sells', 'seashells', 'by', 'the', 'seashore']
[3, 4, 6, 2, 1, 5]
she sells seashells by the seashore
8

作成したエンコーダはsave_to_fileメソッドを呼んで保存します。これにより、ボキャブラリを保存することができます。

filename_prefix = 'encoder'
encoder.save_to_file(filename_prefix)
encoder = tfds.features.text.TokenTextEncoder.load_from_file(filename_prefix)
print(encoder.encode(example_text))
# [3, 4, 6, 2, 1, 5]

日本語の場合

日本語の場合もID化するところまでは変わりません。トークナイザーをJanomeに変えるだけです。

from janome.tokenizer import Tokenizer

example_text = '太郎は花子に花束を渡した。'
tokenizer = Tokenizer(wakati=True)
tokenized_text = tokenizer.tokenize(example_text)
vocabulary_list = list(set(tokenized_text))
encoder = tfds.features.text.TokenTextEncoder(vocabulary_list, tokenizer=tokenizer)
encoded_example = encoder.encode(example_text)

変換結果を表示するためのコードを書きます。

>>> print(tokenized_text)
>>> print(encoded_example)
>>> print(decoded_example)
>>> print(encoder.vocab_size)

結果は以下のようになります。

['太郎', 'は', '花子', 'に', '花束', 'を', '渡し', 'た', '。']
[3, 1, 4, 6, 7, 8, 9, 5, 2]
太郎 は 花子 に 花束 を 渡し た 。
11

しかし、エンコーダーを保存するときに問題が起きます。

>>> encoder.save_to_file(filename_prefix)
AttributeError: 'Tokenizer' object has no attribute 'save_to_file'

これはJanomeのTokenizerにsave_to_fileメソッドが無いことにより起きる例外です。エンコーダーsave_to_fileメソッドを呼ぶと、その内部でTokenizersave_to_fileメソッドを呼び出します。しかし、Janomeにはsave_to_fileメソッドはありません。その結果、メソッドが存在しないというエラーが出るわけです。

対策として、tfds.features.text.Tokenizerクラスを継承したトークナイザーを用意します。そこに何もしないsave_to_fileメソッドをもたせます。また、用意したトークナイザーを読み込めるようにするために、tfdds.features.text.TokenTextEncoderを継承したエンコーダーを作成します。

class JapaneseTokenizer(tfds.features.text.Tokenizer):

    def __init__(self, alphanum_only=True, reserved_tokens=None):
        super().__init__(alphanum_only, reserved_tokens)
        self._t = Tokenizer(wakati=True)

    def tokenize(self, s):
        return self._t.tokenize(s)

    def save_to_file(self, filename_prefix):
        pass

    @classmethod
    def load_from_file(cls, filename_prefix):
        return cls()


class ExtendedEncoder(tfds.features.text.TokenTextEncoder):
    @classmethod
    def load_from_file(cls, filename_prefix):
        filename = cls._filename(filename_prefix)
        vocab_lines, kwargs = cls._read_lines_from_file(filename)
        has_tokenizer = kwargs.pop("has_tokenizer", False)
        if has_tokenizer:
            kwargs["tokenizer"] = JapaneseTokenizer.load_from_file(filename)
        return cls(vocab_list=vocab_lines, **kwargs)

定義したクラスを使うことで、作成したボキャブラリが保存されるようになります。

tokenizer = JapaneseTokenizer()
tokenized_text = tokenizer.tokenize(example_text)
vocabulary_list = list(set(tokenized_text))
encoder = ExtendedEncoder(vocabulary_list, tokenizer=tokenizer)
encoded_example = encoder.encode(example_text)
encoder.save_to_file('encoder')
encoder = ExtendedEncoder.load_from_file('encoder')
print(encoded_example)
# [3, 1, 4, 6, 7, 8, 9, 5, 2]

おわり。まだ様子見が必要。

参考文献