Help us understand the problem. What is going on with this article?

Mecabとtf.dataを使ってlivedoorニュースコーパスを分かち書きする

はじめに

@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

解凍したファイルは以下のようなディレクトリ構造になっています。

textの中身
$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
text/dokujo-tsushinの中身
# 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.dataMecabを使ってテキストの分かち書きを行っていきたいと思います。
※Mecab本体はインストール済みの状態とします。

fugashiインストール

mecab-python3はメンテされていないとのことなので、今回はMecabラッパーにfugashiを選択しました。

$ pip install fugashi

データセット作成

tensorflow_datasetsfugashiなどを読み込み、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を使うと指定したディレクトリ配下のリストを取得できます。

livedoorコーパス内のファイルとディレクトリ一覧を取得
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.ListDirectorytf.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.TextLineDatasetmapを使えば、データセット作成時にまとめて分かち書きすることができます。

分かち書きデータセット作成
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.Tokenizertfds.features.text.TokenTextEncoderを使ってテキストをエンコードしてみます。

まずはトークナイザを生成します。

トークナイザを生成
tokenizer = tfds.features.text.Tokenizer()

トークナイザはデフォルトで半角記号などを排除するので、それらが必要な場合は生成時にreserved_tokensで指定しておく必要があります。

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.datamapを組み合わせることで、簡単に分かち書きしたデータセットを作ることができます。

またtensorflow_datasetsのトークナイザとテキストエンコーダを使うことで、エンコーダも簡単に構築でき、自然言語の機械学習を行う際の前処理がすごく楽になるのでとてもよいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした