ニュース記事の分類を機械学習で予測してみた!

私は今、Aidemy Premium Planという、未経験者が3か月で機械学習、ディープラーニング、データ分析、AIアプリ開発まで最先端技術を幅広く学べるオンライン学習サービスで勉強しています。

受講した自然言語処理コースにおいて、『ニュース記事の分類を機械学習で予測する』ということを学習したのですが、あまり理解できませんでした。。。

そこで、自然言語処理と機械学習の理解のために、自分の中で整理してみることにしました。今回は、『ニュース記事の分類を機械学習で予測する』際の手順について紹介していきます。

と〜げ

Aidemyのカリキュラムの中で学んだ内容の復習的な内容の記事になってます。

データ(livedoor ニュースコーパス)

今回は、livedoorニュースコーパスを使用しました。livedoorニュースコーパスとは、livedoor ニュースのニュース記事を収集し、可能な限りHTMLタグを取り除いて作成したものです。ロンウィットという会社のWebサイトからダウンロードできます。

  • ldcc-20140209.tar.gz:txtファイル形式(私はこちらをダウンロードしました!)
  • livedoor-news-data.tar.gz:xmlファイル形式

と〜げ

自然言語処理の学習用として代表的なものみたいです。

ファイルの解凍は以下で出来ました。

import tarfile
tar = tarfile.open("ldcc-20140209.tar.gz")
tar.extractall()
tar.close()

解凍したフォルダの中身はこんな感じです。ldcc-20140209フォルダの中にtextフォルダがあり、その中に9つのニュースカテゴリのフォルダがあり、それぞれに記事のtxtファイルがあります。

  • text
    1. dokujo-tsushin
      • dokujo-tsushin-4778030.txt
      • dokujo-tsushin-4778031.txt
      • ・・・
    2. it-life-hack
      • it-life-hack-6292880.txt
      • it-life-hack-6292881.txt
      • ・・・
    3. kaden-channel
    4. livedoor-homme
    5. movie-enter
    6. peachy
    7. smax
    8. sports-watch
    9. topic-news

記事がこれらのどのカテゴリーに属するかを予測します(ニュース記事分類)。

データ整理

まず、記事の文章をdocsというリストに、それに対応する記事のカテゴリー(1 ~ 9)をlabelsというリストに格納します。各カテゴリ記事が800程度なので、リストサイズ(len(docs), len(labels))は7367になります。

  • docs = ['文書~~1', '文書~~2', '文書~~3', ... ]
  • labels = [1, 1, ... 2, 2, ... 9, 9, ... ]
# live door news コーパスのカテゴリー(ラベル)と記事リストを返す関数
import glob

def load_livedoor_news_corpus():
    # カテゴリー辞書を定義
    category = {
        "dokujo-tsushin": 1,
        "it-life-hack":2,
        "kaden-channel": 3,
        "livedoor-homme": 4,
        "movie-enter": 5,
        "peachy": 6,
        "smax": 7,
        "sports-watch": 8,
        "topic-news":9
    }

    # 空配列を準備
    docs  = []
    labels = []
    
    
    # 全てのカテゴリーのディレクトリについて実行
    for c_name, c_id in category.items():
        # ファイルのパスを取得
        files = glob.glob("./ldcc-20140209/text/{c_name}/{c_name}*.txt".format(c_name=c_name))
        # カテゴリー名とファイル数を表示
        print("category: ", c_name, ", ",  len(files))

        # 各記事について、URL、 日付、タイトル、 本文の情報を以下のようにして取得
        for file in files:
            with open(file, "r", encoding="utf-8") as f:
                # 改行文字で分割してリストで返す
                lines = f.read().splitlines()
                # url, 日付、タイトル、本文を取得
                url = lines[0]  
                datetime = lines[1]   
                subject = lines[2] 
                # 記事中の本文を1行にまとめる
                body = "".join(lines[3:])
                # タイトルと本文をまとめる
                text = subject + body
  
            # textをdocsに追加
            docs.append(text)
            # c_idをlabelsに追加
            labels.append(c_id)

    return docs, labels
  

docs, labels = load_livedoor_news_corpus()

トレイニングデータとテストデータに分割

scikit-learnのmodel_selectionモジュールのtrain_test_split()を使用して、docsとlabelsを20%のテストデータと80%のトレーニングデータにランダムに分割しています。

import random
from sklearn.model_selection import train_test_split

# データをトレイニングデータとテストデータに分割
train_data, test_data, train_labels, test_labels = train_test_split(docs, labels, test_size=0.2, random_state=0)

ベクトル化(tf-idf

train_dataやtest_dataは、['文書~~1', '文書~~2', '文書~~3', ... ]という日本語の文章のリストなので、このままではモデルの学習をすることが出来ません。そこで、train_dataやtest_dataをベクトル(数字のリスト)化する必要があります。

文書中の各単語の出現回数は表現する Bag of Words(BOW)というベクトル表現手法を使います。

BOWにはカウント表現という文書中にある単語の出現回数を要素として使用したベクトルに変換する方法があります。しかし、カウント表現の問題点としては、「です」や「ます」のような、カテゴリ分類において重要ではない単語の値が大きくなってしまうことがあります。

そこで、一般的には、tf-idf表現というtf-idfという手法で計算された、文章中の各単語の重み情報を扱う方法を使います。今回もこれを使います。

tf-idfは単語の出現頻度である tftf(Term frequency) と、その単語がどれだけ珍しいか(希少性)をしめす逆文書頻度 idfidf(Inverse Document Frequency) の 積で表されます。

tf-idfを使うと、多くの文書に出現する語(一般的な語)の重要度を下げ、特定の文書にしか出現しない単語の重要度を上げる役割を果たします。これにより、「です」や「ます」などの値が小さくなり、正しく重要度を設定することができます。

参考サイト基礎的な文章のベクトル化を考える

from sklearn.feature_extraction.text import TfidfVectorizer

# tf-idfベクトル表現化を行う変換器を生成
vectorizer = TfidfVectorizer()

# 文書をベクトルに変換
train_matrix = vectorizer.fit_transform(train_data)
test_matrix = vectorizer.transform(test_data)

train_matrixやtest_matrixは、scipy.sparse.csr.csr_matrix型というSciPy特有の型です。参考サイトが詳しいです。

参考サイトTf-idfベクトルってなんだ?

ランダムフォレストで学習

今回は、ランダムフォレストという手法を使います。

from sklearn.ensemble import RandomForestClassifier

# ランダムフォレストで学習
clf = RandomForestClassifier(n_estimators=10)
clf.fit(train_matrix, train_labels)

# 精度の出力
print(clf.score(train_matrix, train_labels))
print(clf.score(test_matrix, test_labels))

トレインデータの精度:0.998、テストデータの精度:0.862となりました。

精度の改善

上記では、文書のすべての単語を使用しました。しかし、最も文書の分類において重要となるのは名詞とかではないか?ということで、名詞、動詞、形容詞、形容動詞のみを取り出して、学習させて精度を求めてみます。

TfidfVectorizer() のパラメーターに tokenizer=関数 を設定すると、その関数でテキストを分割できます。

from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer

# 単語の抽出
def tokenize(text):
    t = Tokenizer()
    tokens = t.tokenize(','.join(text))
    noun = []
    for token in tokens:
        # 品詞を取り出し
        partOfSpeech = token.part_of_speech.split(",")[0]
        
        if partOfSpeech in ["名詞", "動詞", "形容詞", "形容動詞"]:
            noun.append(token.surface) 
    return noun

# tf-idfでトレイニングデータとテストデータをベクトル化
vectorizer = TfidfVectorizer(tokenizer=tokenize)
train_matrix = vectorizer.fit_transform(train_data)
test_matrix = vectorizer.transform(test_data)

そして、ランダムフォレストで学習して精度を求めると、結局あまり変わりませんでした。。。

と〜げ

他の特徴量ベクトルを使うなどして、精度はもう少し改善できそうです。今回は、文書分類を機械学習でする流れを知るのが目的だったので、精度の改善はこんなところにしておきます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です