tf-idfについて勉強したのでざっくりまとめ_pythonでやってみた

概要

こんにちは、データインテグレーション部のyoshimです。この記事は機械学習アドベントカレンダー5日目のものとなります。 本日は、先日ご紹介した「tf-idf」を実際に計算するところまでをPythonの「scikit-learnのチュートリアル」に沿って実際にやってみたので、ご紹介します。

基本はチュートリアルに沿って実装しているのですが、一部チュートリアルにない部分も追加で記述しているのでご注意ください。 (追加している部分は、主にデータの中身の確認のためのコードです)

目次

やること

今回のエントリでやることは、「tf-idfの計算をpythonでとりあえずやってみる」というものです。 目的は、アドベントカレンダー4日目にてご紹介した「tf-idfの理論への理解をより深める」、です。

また、今回Pythonでtf-idfの計算を行うにあたり、「scikit-learnのチュートリアル」に沿って実装しています。 このチュートリアルでは「ニュース」に対して「カテゴリ」が割り当てられているデータセットを使ってtf-idfを計算する、というものです。 (カテゴリは、「macのハード」や「オートバイ」など20カテゴリ存在しています) このデータセットから、「各ニュースとニュースに出てくる単語からtf-idf値を計算」し、下記のようなデータとして保有する、というところが今回のゴールです。

[ [0, 0.1, 0.15,・・・・, 0.05], [0.1, 0.2, 0.05,・・・・, 0.15], [0, 0.3, 0.1,・・・・, 0.05], [0.2, 0.04, 0.1,・・・・, 0.05] ]

これは各ニュース(横)、単語(縦)単位でtf-idf値が格納されているデータです。

作業環境

  • macOS High Sierra 10.13.1
  • python 3.6.3
  • sklearn 0.19.1

手順概要

この後、ソースコードをご紹介するのですが、その前にざっくりと「今回の処理の概要」についてご説明します。 今回、「tf-idf」を計算するまでを6つの段階に分けて処理させています。

  • 1.データの取得 sklearnに用意されているデータセットを利用します。 コードとしては2行で終わります。

  • 2.取得したデータの中身を確認 実際にtf-idfの計算を始める前に「これからどんなデータを処理するのか」ということを把握するために、データの中身を確認します。 チュートリアルに記述していないことも色々確認しています。

  • 3.データの絞り込み sklearnのデータセットには20カテゴリ用意されているのですが、今回は処理対象を「4カテゴリ」に絞り込みます。

  • 4.カテゴリ名の確認 4カテゴリに絞り込んだ後、残ったニュースデータのカテゴリを確認しています。

  • 5.tf-idfを計算する前の事前処理(トークン化、BoW) tf-idfを計算するために必要な前処理をしています。 具体的には、sklearnの「CountVectorizer」にて、「文書の分割」と「BoWの計算」を行なっています。

上記による「文書の分割」について、少し調べてみました。sklearnのチュートリアル(特徴抽出_概要)を見ると、デフォルトの設定だと「2文字以上の単語を抽出して文字列をトークン化」とあります(4.2.3.3をご覧ください)。 ただ、これは英語のデータに対して有効ではあるものの、日本語のデータに対しては同じやり方ではうまくいきません。もし、日本語のドキュメントを処理する際は「MeCab」や「Janome」が有名です。

もしsklearnによる特徴抽出について興味がある方は、sklearnのチュートリアル(特徴抽出_詳細版)をご覧ください。

  • 6.tf-idfの計算 5でBoWを計算しているので、その計算結果から「tf-idf」を計算し、その計算結果を確認します。 BoWでは、単語の出現頻度に基づくシンプルな計算しかしていないので、tf-idf計算をする事で、よりニュースの特徴を抽出するために有用な集計値にします。

コード

ここからはコードの解説をします。 実際のコードや、より詳細な解説については、scikit-learnのチュートリアルをご参照ください。 処理内容については、## 手順概要 とコード内のコメントをご参照ください。

"""
sklearnのチュートリアルを通して「tf-idf」と「ナイーブベイズ」を実際にやってみます。
基本は下記のURLの通りに実行していくのですが、一部コードを追加しています。(主にデータの中身がどうなっているかの確認)
http://scikit-learn.org/0.18/tutorial/text_analytics/working_with_text_data.html
このチュートリアルでは「ニュース」のデータセットを利用します。
このデータセットでは各ニュースを20カテゴリに分類しています。
"""

# 1.データセットの取得
from sklearn.datasets import fetch_20newsgroups # 1-1.モジュールのインポート
twenty_train = fetch_20newsgroups(subset='train',shuffle=True, random_state=42) # 1-2.twenty_trainという変数にデータセット(トレーニング用)を格納

# 2.データの確認(チュートリアルには記載なし)
print("=====2.データの確認=====")

print(twenty_train.target_names) # 2-1.カテゴリの確認(どんなカテゴリがあるのか。)
print (len(twenty_train.data)) # 2-2.何本のニュースデータがあるのか(11,314本のニュースデータ)
print(type(twenty_train.data)) # 2-3.データ型を確認(list)
print(type(twenty_train.data[0])) # 2-4.リストの中のデータ型を確認(str)

print("実際にデータの中身を確認してみます")
print(twenty_train.target_names[twenty_train.target[10]]) # 2-5.実際に1本ニュースを確認する(カテゴリ名)
print("\n".join(twenty_train.data[10].split("\n")[:15])) # 2-6.実際に1本ニュースを確認する(ニュースの中身)

'''備考
今回はこのデータセットを、「twenty_train」という変数に格納します。
このデータセットから、さらに「target_names」、「data」、「target」の3つが確認できます。それぞれのデータの中身については下記の通り。
target_names: 各ニュースカテゴリ名
data: ニュースデータ
target: ニュースカテゴリのIDのようなもの。target_namesに格納されているニュースカテゴリ名を引き出す際に利用。
'''

# 3.絞り込み
print("=====3.絞り込み=====")

categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] # 3-1.今回分析対象とするカテゴリーを絞り込む
twenty_train = fetch_20newsgroups(subset='train',categories=categories, shuffle=True, random_state=42) # 3-2.上記で絞り込んだカテゴリーのデータのみを変数に入れる

print(twenty_train.target_names) # 3-3.カテゴリの確認(ちゃんと絞り込めているか。)
print (len(twenty_train.data)) # 3-4.絞り込んだ結果、ニュースデータが何本になったか(2,257本のニュースデータ)
print("\n".join(twenty_train.data[0].split("\n")[:3])) #3-5.データの内容を確認
print(twenty_train.target_names[twenty_train.target[0]]) #3-6.データのカテゴリーを確認

# 4.カテゴリ名を確認
print('=====4.カテゴリ名を確認=====')

print(twenty_train.target[:10]) # 4-1.上から10個のデータのカテゴリーを確認する(カテゴリーID)
for t in twenty_train.target[:10]: # 4-2.カテゴリーの意味がわからないので確認する(target_namesに格納されている)
print(twenty_train.target_names[t])

# 5.テキストのトークン化とBoW計算(単語の出現頻度をカウント)
print("=====5.テキストのトークン化とBoW計算=====")

from sklearn.feature_extraction.text import CountVectorizer # 5-1.モジュールのインポート
count_vect = CountVectorizer() # 5-2.モジュールを使うための準備
X_train_counts = count_vect.fit_transform(twenty_train.data) # 5-3.単語の出現頻度をカウント(BoW)!!!!!

print(type(X_train_counts)) #5-4.BoWの計算結果を保持した変数のデータを確認
print(X_train_counts.shape) #5-5.BoWの計算結果を保持した変数のデータを確認
print(X_train_counts.todense()) # 5-6.BoWの計算結果を保持した変数のデータを確認(疎行列)
# print(count_vect.get_feature_names()) # 5-7.どのような形態素があったのかを見る(こういうこともできるよ、という紹介です。実行すると大量の形態素が表示されるので実行する際はご注意ください)
print(X_train_counts[0]) # 5-8.疎行列の中の実際の値を確認
print(count_vect.vocabulary_.get(u'algorithm')) # 5-9.'algorithm'という単語のインデックス値を確認

# 6.tf-idfの計算
print("=====6.tf-idfの計算=====")

from sklearn.feature_extraction.text import TfidfTransformer
tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts) # 6-1.BoWの計算結果から、tf-idfを計算!!!!!

print(type(X_train_tfidf)) # 6-2.計算結果がどんな形になっているかを確認
print(X_train_tfidf.shape) # 6-3.計算結果がどんな形になっているかを確認
print(X_train_tfidf.todense()) # 6-4.計算の結果、どのようになったのかを見てみる(5-6と値のみが異なる)
print(X_train_tfidf[0]) # 6-5.BoWからtf-idfっぽい数字になっているかを確認

まとめ

長くなってしまいましたが、自然言語処理における「前処理」でよく行われる「tf-idf」について、実際にPythonで実装してみた内容のご説明でした。 今回はsklearnのチュートリアルに沿って進めたのですが、Pythonは全体的にチュートリアルが豊富で素晴らしいので、何かをPythonで実装する際にはライブラリのチュートリアルから勉強を始めてみるのもいいかもしれません。

明日は「ナイーブベイズ」についてご紹介いたします。