#はじめに
@Suguru_Toyoharaさんがtf.data.Dataset
の使い方をわかりやすくまとめていただいていますが、このエントリでは自然言語処理でよくやるコーパスを分かち書きしてデータセットを作る、というフローをtd.data
を使って行います。
#livedoor コーパス
livedoorコーパスはNHN Japan株式会社が運営する「livedoor ニュース」のうち、下記のクリエイティブ・コモンズライセンスが適用されるニュース記事を収集したもので、こちらのリンクからダウンロードができます。
https://www.rondhuit.com/download.html
このページのldcc-20140209.tar.gzをダウンロードして解凍します。
$wget https://www.rondhuit.com/download/ldcc-20140209.tar.gz
$ tar zfx ldcc-20140209.tar.gz
解凍したファイルは以下のようなディレクトリ構造になっています。
$tree text -L 1
text
├── CHANGES.txt
├── dokujo-tsushin
├── it-life-hack
├── kaden-channel
├── livedoor-homme
├── movie-enter
├── peachy
├── README.txt
├── smax
├── sports-watch
└── topic-news
# tree text/dokujo-tsushin
text/dokujo-tsushin
├── dokujo-tsushin-4778030.txt
├── dokujo-tsushin-4778031.txt
├── dokujo-tsushin-4782522.txt
├── dokujo-tsushin-4788357.txt
...
├── dokujo-tsushin-6915005.txt
└── LICENSE.txt
#分かち書きデータセット作成
解凍したコーパスをもとにtf.data
とMecab
を使ってテキストの分かち書きを行っていきたいと思います。
※Mecab本体はインストール済みの状態とします。
fugashiインストール
mecab-python3
はメンテされていないとのことなので、今回はMecabラッパーにfugashi
を選択しました。
$ pip install fugashi
データセット作成
tensorflow_datasets
とfugashi
などを読み込み、Mecabのtaggerを生成します。
import os
import tensorflow as tf
import tensorflow_datasets as tfds
import fugashi
tagger = fugashi.Tagger('-Owakati')
Tensorflowではテキストファイルのコーパスを操作するAPIが数多く揃っており、tf.compat.v1.gfile.ListDirectory
を使うと指定したディレクトリ配下のリストを取得できます。
text_dir = os.path.join(os.getcwd(),"text")
tf.compat.v1.gfile.ListDirectory(text_dir)
['CHANGES.txt',
'dokujo-tsushin',
'it-life-hack',
'kaden-channel',
'livedoor-homme',
'movie-enter',
'peachy',
'README.txt',
'smax',
'sports-watch',
'topic-news']
tf.compat.v1.gfile.ListDirectory
をネストしてファイル一覧を取得し、tf.data.TextLineDataset
APIを使って各カテゴリ内のファイルを取得します。
text_datasets = []
text_dir = os.path.join(os.getcwd(),"text")
for d in tf.compat.v1.gfile.ListDirectory(text_dir):
data_dir = os.path.join(text_dir,d)
if os.path.isdir(data_dir):
for file_name in tf.compat.v1.gfile.ListDirectory(data_dir):
text_dataset = tf.data.TextLineDataset(os.path.join(data_dir,file_name))
text_datasets.append(text_dataset)
作成したデータセットの中身を見てみましょう。
plane_dataset = text_datasets[0]
iter_plane_dataset = iter(plane_dataset)
print(next(iter_plane_dataset).numpy().decode('utf-8'))
print(next(iter_plane_dataset).numpy().decode('utf-8'))
print(next(iter_plane_dataset).numpy().decode('utf-8'))
http://news.livedoor.com/article/detail/4778030/
2010-05-22T14:30:00+0900
友人代表のスピーチ、独女はどうこなしている?
tf.compat.v1.gfile.ListDirectory
とtf.data.TextLineDataset
を組み合わせることで、コーパスファイル群から簡単にデータセットを作成することができました。
この手順だとtext_datasets[0]
にはdokujo-tsushin-4778030.txtのテキストが、text_datasets[1]
にはdokujo-tsushin-4778031.txtというように、ファイル単位で分割されてしまうため、文章単位となるようにconcatenate
する必要がありました。
parsed_dataset = text_datasets[0]
for text_dataset in text_datasets[1:]:
parsed_dataset = parsed_dataset.concatenate(text_dataset)
これでデータセット作成の完了です。
分かち書きデータセットを作成
分かち書きデータセットを用意する場合、このデータセットに対してさらにfugashiのTaggerを当てる必要がありますが、tf.data.TextLineDataset
のmap
を使えば、データセット作成時にまとめて分かち書きすることができます。
def parse(text):
'''
データセットのテキストを1行ごとに分かち書き
'''
return tagger.parse(text.numpy().decode('utf-8')).split("\n")[0]
@tf.function
def parse_text(text):
parsed = tf.py_function(parse,[text],[tf.string])
return parsed[0]
text_datasets = []
text_dir = os.path.join(os.getcwd(),"text")
for d in tf.compat.v1.gfile.ListDirectory(text_dir):
data_dir = os.path.join(text_dir,d)
if os.path.isdir(data_dir):
for file_name in tf.compat.v1.gfile.ListDirectory(data_dir):
text_dataset = tf.data.TextLineDataset(os.path.join(data_dir,file_name)).map(parse_text) # mapでテキストをパース処理に渡す
text_datasets.append(text_dataset)
parsed_dataset = text_datasets[0]
for text_dataset in text_datasets[1:]:
parsed_dataset = parsed_dataset.concatenate(text_dataset)
今度は分かち書きされた結果がきちんと返っています。
parsed_dataset = text_datasets[0]
iter_parsed_dataset = iter(parsed_dataset)
print(next(iter_parsed_dataset).numpy().decode('utf-8'))
print(next(iter_parsed_dataset).numpy().decode('utf-8'))
print(next(iter_parsed_dataset).numpy().decode('utf-8'))
http :// news . livedoor . com / article / detail / 4778030 /
2010 - 05 - 22 T 14 : 30 : 00 + 0900
友人 代表 の スピーチ 、 独 女 は どう こなし て いる ?
テキストエンコード
機械学習で自然言語を扱う場合、分かち書き結果を個々のキーワードに分割し、数字にエンコードする必要があります。
Tensorflow 2.0のテキストチュートリアルを参考にtfds.features.text.Tokenizer
とtfds.features.text.TokenTextEncoder
を使ってテキストをエンコードしてみます。
まずはトークナイザを生成します。
tokenizer = tfds.features.text.Tokenizer()
トークナイザはデフォルトで半角記号などを排除するので、それらが必要な場合は生成時にreserved_tokens
で指定しておく必要があります。
tokenizer = tfds.features.text.Tokenizer(reserved_tokens=["/",":"])
生成したトークナイザを使い、分かち書きデータセットを分割してボキャブラリを構築します。
vocabulary_set = set()
for line in parsed_dataset:
some_tokens = tokenizer.tokenize(line.numpy())
vocabulary_set.update(some_tokens)
ボキャブラリからエンコーダを生成します。
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set, tokenizer=tokenizer)
結果を確認します。
sample_text = next(iter_parsed_dataset)
encoded_example = encoder.encode(sample_text.numpy().decode('utf-8'))
for enc in encoded_example:
print("{} -> {}".format(enc, encoder.decode([enc])))
9058 -> もうすぐ
3331 -> ジューン
31397 -> ブライド
27220 -> と
2397 -> 呼ば
22334 -> れる
5673 -> 6月
31920 -> 独
32073 -> 女
20415 -> の
28229 -> 中
18191 -> に
986 -> は
8138 -> 自分
20415 -> の
10202 -> 式
986 -> は
36484 -> まだ
10551 -> な
26617 -> のに
2397 -> 呼ば
34 -> れ
27080 -> て
27726 -> ばかり
27509 -> という
6682 -> お祝い
903 -> 貧乏
5111 -> 状態
20415 -> の
35344 -> 人
4722 -> も
21766 -> 多い
20415 -> の
22018 -> で
986 -> は
31505 -> ない
12417 -> だろ
7199 -> う
23655 -> か
35100 -> さらに
22067 -> 出席
16090 -> 回数
18014 -> を
1399 -> 重ね
27080 -> て
27779 -> いく
27220 -> と
6523 -> こんな
6851 -> お願い
6769 -> ごと
18014 -> を
32709 -> さ
22334 -> れる
30766 -> こと
4722 -> も
3435 -> 少なく
31505 -> ない
作成したエンコーダを保存しておくことで、他のタスクにも使うことができます。
encoder.save_to_file('sample/encoded.model')
エンコードファイルの中身はこのようになっています。
### TokenTextEncoder
### Metadata: {"has_tokenizer": true, "lowercase": false, "oov_buckets": 1, "oov_token": "UNK"}
一番
相談
取り入れ
幸恵
ばかり
多い
\できる
よう
みる
どう
何
回数
なら
話す
いっ
あげれ
者
って
他
よ
サプライズ
保存したエンコーダはtfds.features.text.TokenTextEncoder.load_from_file
から呼び出すことができます。
loaded_encoder = tfds.features.text.TokenTextEncoder.load_from_file('sample/encoded.model')
encoded_example = loaded_encoder.encode(sample_text.numpy().decode('utf-8'))
for enc in encoded_example:
print("{} -> {}".format(enc, encoder.decode([enc])))
保存前とワードのIDが変わっていないことが確認できます。
9058 -> もうすぐ
3331 -> ジューン
31397 -> ブライド
27220 -> と
2397 -> 呼ば
22334 -> れる
5673 -> 6月
31920 -> 独
32073 -> 女
20415 -> の
28229 -> 中
18191 -> に
986 -> は
8138 -> 自分
20415 -> の
10202 -> 式
986 -> は
36484 -> まだ
10551 -> な
26617 -> のに
2397 -> 呼ば
34 -> れ
27080 -> て
27726 -> ばかり
27509 -> という
6682 -> お祝い
903 -> 貧乏
5111 -> 状態
20415 -> の
35344 -> 人
4722 -> も
21766 -> 多い
20415 -> の
22018 -> で
986 -> は
31505 -> ない
12417 -> だろ
7199 -> う
23655 -> か
35100 -> さらに
22067 -> 出席
16090 -> 回数
18014 -> を
1399 -> 重ね
27080 -> て
27779 -> いく
27220 -> と
6523 -> こんな
6851 -> お願い
6769 -> ごと
18014 -> を
32709 -> さ
22334 -> れる
30766 -> こと
4722 -> も
3435 -> 少なく
31505 -> ない
まとめ
tf.data
とmap
を組み合わせることで、簡単に分かち書きしたデータセットを作ることができます。
またtensorflow_datasets
のトークナイザとテキストエンコーダを使うことで、エンコーダも簡単に構築でき、自然言語の機械学習を行う際の前処理がすごく楽になるのでとてもよいと思います。