20ニュースグループのテキストデータを読み込んでみる

職場にはテキストデータは大量にあるのですが、自宅での学習で自然言語処理にについて学ぼうとすると途端にデータ不足に悩まされます。
そんなわけで青空文庫からデータを持ってくるような記事をこのブログに書いているのですが、
実はscikit-learnの付属のデータセットにもテキストデータはあります。(ただし英語)

Tfidfや、word2vecを動かしてみるには十分なので、それの取得方法を紹介します。
ドキュメントはこちら。
sklearn.datasets.fetch_20newsgroups

インポートして、引数でsubsetを指定することで訓練データとテストデータを入手できます。未指定だと訓練データのみです。両方一度に入手するためにはsubset="all"を指定する必要があります。
僕は始めはそれを知らず引数なしで実行して、訓練データを2つに分けて使ってました。


from sklearn.datasets import fetch_20newsgroups
twenty_train = fetch_20newsgroups(subset="train") # 引数省略可能。
twenty_test = fetch_20newsgroups(subset="test")
# trainとtestを同時に入手したい時は  subset="all" を指定。

# 件数の確認
print(len(twenty_train.data)) # 11314
print(len(twenty_test.data)) # 7532

初回実行時にダウンロードされ、data_home で指定したパスか、デフォルトでは~/scikit_learn_dataにデータが保存されます。
そのため、1回目だけは時間がかかりますが、2回目からは高速です。

twenty_train と、 twenty_test には辞書型で各情報が入ります。
twenty_train.keys()を実行すると、
dict_keys(['data', 'filenames', 'target_names', 'target', 'DESCR'])
と出ます。

DESCR にデータの説明、 target_namesに各ニュースグループの名前が入っているので、それぞれ一度は見ておくことをお勧めします。


# 出力
['alt.atheism',
 'comp.graphics',
 'comp.os.ms-windows.misc',
 'comp.sys.ibm.pc.hardware',
 'comp.sys.mac.hardware',
 'comp.windows.x',
 'misc.forsale',
 'rec.autos',
 'rec.motorcycles',
 'rec.sport.baseball',
 'rec.sport.hockey',
 'sci.crypt',
 'sci.electronics',
 'sci.med',
 'sci.space',
 'soc.religion.christian',
 'talk.politics.guns',
 'talk.politics.mideast',
 'talk.politics.misc',
 'talk.religion.misc']

普段は、 data に入ってるテキストデータの配列と、正解ラベルであるtargetをつかえばOKだと思います。

このほか、BoWに変換済みの、 fetch_20newsgroups_vectorized というのもありますが、
あまり使う機会はないのではないかなぁと思います。
学習目的であれば、前処理なども自分で経験したほうがいいと思うので。

ちなみに、scikit-learnのチュートリアルには、
このデータセットを用いて、tfidfとナイーブベイズでモデルを作るものがあります。
Working With Text Data
(このページは見ていませんでしたが、)僕も自然言語処理を始めたばかりの頃、全く同じような内容で勉強をスタートしたので、非常に懐かしく思いました。
その時の試したファイルはあるので、そのうちこのブログにもまとめ直します。

scikit-learnでテキストをBoWやtfidfに変換する時に一文字の単語も学習対象に含める

本当に初めて自然言語処理をやった頃のメモから記事化。

テキストを分かち書きしたあと、BoW(Bag of Words) や tfidfに変換するとき、
scikit-learnを使うと便利です。
sklearn.feature_extraction.text に次の二つのクラスが定義されていて、
それぞれ語彙の学習と BoW /tfidfへの変換を行ってくれます。

CountVectorizer
TfidfVectorizer

ただ、これらのクラスはデフォルトパラメーターに少し癖があり注意していないと一文字の単語を拾ってくれません。
TfidfVectorizer の方を例にやってみましょう。


from sklearn.feature_extraction.text import TfidfVectorizer

model = TfidfVectorizer()
text_list = [
    "すもも も もも も もも の うち",
    "隣 の 客 は よく 柿 食う 客 だ",
]
model.fit(text_list)
print(model.vocabulary_)

これの出力結果が、下記になります。
これらが学習した単語です。
{‘すもも’: 1, ‘もも’: 2, ‘うち’: 0, ‘よく’: 3, ‘食う’: 4}

“も” や “の” が入ってないのはまだ許容範囲としても、
“隣”や”客”がvocabulary_に含まれないのは困ります。

これらは TfidfVectorizer のインスタンスをデフォルトのパラメーターで作ったことに起因します。

modelの内容を表示してみましょう。(jupyterで model とだけ入力して実行した結果)


>>> model
TfidfVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), norm='l2', preprocessor=None, smooth_idf=True,
        stop_words=None, strip_accents=None, sublinear_tf=False,
        token_pattern='(?u)\\b\\w\\w+\\b', tokenizer=None, use_idf=True,
        vocabulary=None)

注目するべきはここです。
token_pattern='(?u)\\b\\w\\w+\\b’
\\ は \をエスケープしたものなので、以下の説明中では\\ は \と書きます。
正規表現で、\b は単語の境界 、 \wは、単語構成文字、\w+ は1文字以上の単語構成文字の連続 を意味します。
そのため、 \w\w+ は2文字以上の単語構成文字の連続を意味し、
\b\w\w+\b は “単語の境界、2文字以上の単語構成文字、単語の境界” と続く文字列を単語のパターンとして採用するという指定になります。
そのため、 一文字の単語は学習対象から抜けています。

英語なら a や I が抜けるだけですが日本語では多くの漢字が抜けてしまうので、これは困りますね。

ということで、最初に TfidfVectorizer のオブジェクトを作る時には token_pattern を指定しましょう。
1文字以上を含めたいだけなので、 ‘(?u)\\b\\w+\\b’ にすれば大丈夫です。


from sklearn.feature_extraction.text import TfidfVectorizer

model = TfidfVectorizer(token_pattern='(?u)\\b\\w+\\b')
text_list = [
    "すもも も もも も もも の うち",
    "隣 の 客 は よく 柿 食う 客 だ",
]
model.fit(text_list)
print(model.vocabulary_)

これで出力は下記のようりなり、”隣”も”客”も含まれます。
{‘すもも’: 1, ‘も’: 5, ‘もも’: 6, ‘の’: 3, ‘うち’: 0, ‘隣’: 10, ‘客’: 8, ‘は’: 4, ‘よく’: 7, ‘柿’: 9, ‘食う’: 11, ‘だ’: 2}

これ以外にも “-” (ハイフン) などが単語の境界として設定されていて想定外のところで切られたり、
デフォルトでアルファベットを小文字に統一する設定になっていたり(lowercase=True)と、注意する時に気をつけないといけないことが、結構あります。

norm,smooth_idf,sublinear_tf などの影響でよくある自然言語処理の教科書に載っている数式と実装が違うのも注意ですね。
この辺はまた別の機会にまとめようと思います。