classifier-rebornを使ってRubyで手軽に文書分類


Rubyで機械学習とか自然言語処理を扱えるライブラリってホント少ないですよね。あるにはあるけど開発止まってるものが多いですし。Python使えって話ですが、やっぱりRubyで書きたいです。

というわけで、単純ベイズ分類器や潜在的意味解析をRubyで扱えるGemである[classifier-reborn](https://github.com/jekyll/classifier-reborn)を使ってみました。classifier-rebornはjekyllのチームが開発しているGemで、とても簡単に文章の分類が行えます。

分類器の説明に関しては割愛します。以下の投稿が詳しいです。

[ナイーブベイズ分類器を頑張って丁寧に解説してみる](http://qiita.com/aflc/items/13fe52243c35d3b678b0)

今回は、2chから引っ張ってきたSS(ショートストーリー:二次創作の短編小説のようなもの)をジャンルごとに分類する作業を通して、精度の検証を行いました。

# Introduction

## インストール

いつも通りgem installします。依存gemとしてfast-stemmerが必要です。

```
gem install fast-stemmer
gem install classifier-reborn
```

## 使用方法

以下公式のサンプルを簡単に説明したものです。単純ベイズ分類器を用いて、短い文章を 'Interresting' と 'Uninteresting'に分類するというものですね。

```rb
require 'classifier-reborn'

# オブジェクトを初期化
classifier = ClassifierReborn::Bayes.new 'Interesting', 'Uninteresting'

# 引数の文章を 'Interesting' として学習
classifier.train_interesting "here are some good words. I hope you love them"

# 引数の文章を 'Uninteresting' として学習
classifier.train_uninteresting "here are some bad words, I hate you"

# 引数の文章がどちらに分類されるかを返す
classifier.classify "I hate bad words and you" # returns 'Uninteresting'

# 学習させたオブジェクトをダンプ
classifier_snapshot = Marshal.dump classifier

# ダンプしたオブジェクトを 'classifier.dat' というファイル名で保存
File.open("classifier.dat", "w") {|f| f.write(classifier_snapshot) }

# 保存したファイルから学習済みオブジェクトを復元
data = File.read("classifier.dat")
trained_classifier = Marshal.load data

# 引数の文章がどちらに分類されるかを返す
trained_classifier.classify "I love" # returns 'Interesting'
```

とてもシンプルです。

## 日本語の取り扱い

classifier-rebornで日本語を扱うには、文章を分かち書きする必要があります。自分はmecabを用いました。ここでは詳しく説明しませんが、以下の投稿が詳しいです。

[mecabをRubyで使う](http://qiita.com/ysk_1031/items/2ebdfefbca7c01d19ac0)

# 検証

## 入力データ

検証には、2chからスクレイピングして得たSS(ショートストーリー:二次創作の短編小説のようなもの)を用いました。

目的は、魔法少女まどか☆マギカ 、艦これ、 モバマス、3ジャンルのSSを自動で分類することです。

魔法少女まどか☆マギカ 、艦これ、 モバマスのSSをそれぞれ1500本用意し、1000本を学習用、500本を検証用として使用しました。

|ジャンル|学習文書数|検証文書数|
|---|---|---|
|まどか☆マギカ|1000|500|
|艦これ|1000|500|
|モバマス|1000|500|

入力データの準備を簡略化したため、各ジャンルのSSは、__そのSSが本当に正しいジャンルに割り振られているかの確認をしていません。__

学習用データを、それぞれ一つのテキストファイルとして作成しました。一行に一本のSS文章が入力されています。

```text:train_madoka.txt

```

```text:train_kankore.txt

```

```text:train_mobamasu.txt

```

このままでは入力データとして使用できないので、形態素解析ツールmecabを使って分かち書きします。辞書にはmecab-ipadic-neologdを使っています。

```
cat train_madoka.txt | mecab -Owakati -b 1000000 > parsed_train_madoka.txt

cat train_kankore.txt | mecab -Owakati -b 1000000 > parsed_train_kankore.txt

cat train_mobamasu.txt | mecab -Owakati -b 1000000 > parsed_train_mobamasu.txt
```

これによって文章が品詞分解されます。以下のような感じです。

```
"じゃあ、午後もプロデュース活動、頑張りましょう！"

↓

"じゃあ　 、 　午後 　も 　プロデュース 　活動 　、　 頑張り 　ましょ　 う　 ！"
```

## 学習

では用意したデータを使って、分類器を学習させていきます。

```rb
# coding: utf-8

require 'classifier-reborn'

def open_each_ss(file)
  open(file) do |f|
    f.each do |row|
      begin
        yield row
      rescue
        p 'invalid string'
      end
    end
  end
end

# 分類器を定義
classifier = ClassifierReborn::Bayes.new 'kankore', 'madoka', 'mobamasu'

# 艦これのSS 1000件を学習
open_each_ss('parsed_train_kankore.txt') do |row|
  classifier.train_kankore row.to_s.strip
end

# まどか☆マギカのSS 1000件を学習
open_each_ss('parsed_train_madoka.txt') do |row|
  classifier.train_madoka row.to_s.strip
end

# モバマスのSS 1000件を学習
open_each_ss('parsed_train_mobamasu.txt') do |row|
  classifier.train_mobamasu row.to_s.strip
end

# 分類器をファイルとして保存
classifier_snapshot = Marshal.dump classifier
File.open("classifier.dat", "w") {|f| f.write(classifier_snapshot) }
```

## 分類

入力データの項目と同様の手順で、検証用のファイルを用意しました。各ジャンル500件のSSが分かち書きされた状態で格納されています。

```text:parsed_test_madoka.txt

```

```text:parsed_test_kankore.txt

```

```text:parsed_test_mobamasu.txt

```

下記の例では、モバマスのSS 500件が正しく分類された割合を出力しています。

```rb
# coding: utf-8

require 'classifier-reborn'

def open_each_ss(file)
  open(file) do |f|
    f.each do |row|
      begin
        yield row
      rescue
        p 'invalid string'
      end
    end
  end
end

data = File.read("classifier.dat")
trained_classifier = Marshal.load data
count = 0
open_each_ss('parsed_test_mobamasu.txt') do |row|
  count += 1 if trained_classifier.classify(row) == 'Madoka'
end
p count / 500
```

で、実際にどのような結果になったかというと・・・

### まどか☆マギカ：正答率  89.2%

### 艦これ：正答率 86.8%

### モバマス：正答率 80.0%

### 全体：正答率：85.3%

と、なりました。悪くはないですが、少し入力データを加工してみます。

## 学習・分類 Take 2

先ほどの学習・分類では入力データとして、__文章を単純に分かち書きしただけのもの__を使用しました。今回は、文章の特徴づけにおいて意味を持たない部分を取り除くことで、精度の向上を試みます。具体的には、各文章を分かち書きし、名詞及び形容詞以外を取り除いた上で、学習・分類します。以下のような感じです。

```
"じゃあ、午後もプロデュース活動、頑張りましょう！"

↓

"じゃあ　 、 　午後 　も 　プロデュース 　活動 　、　 頑張り 　ましょ　 う　 ！"

↓

"午後  プロデュース  活動"
```

このように入力データを加工したうえで、同じように正答率を求めたところ・・・

### まどか☆マギカ：正答率  88.0%

### 艦これ：正答率 95.8%

### モバマス：正答率 93.4%

### 全体：正答率：92.4%

なぜか まどか☆マギカの正答率が下がりましたが、全体で見ると大きく改善されました。

# まとめ

今回は、3つの項目いずれかに分類するという単純な内容を扱いましたが、最終的に92.4%と、それなりの精度を出すことができました。ただし今回用いた入力データは、__本当に正しいジャンルに割り振られていたのかを確認していない__ので、間違った学習が発生したり、分類の模範解答が間違っているものが一定数存在しています。なので、学習データ及びテストデータが正しいジャンルに割り振られていたとしたら、ほぼ100%に近い分類精度が出るのではないかと思われます。