JavaとGridDBを用いたテキスト分類器

テキストデータは、電子メール、チャットでの会話、ソーシャルメディア、Webサイトなど、あらゆるところに存在します。テキストデータには多くの情報が含まれていますが、その非構造化データの性質上、情報を抽出することが困難な場合があります。非構造化データからインサイトを抽出するには、データの分類に多くの時間とコストがかかります。

自然言語処理（NLP）技術を用いたテキスト分類器は、高速でスケーラブル、かつコスト効率の高い方法でデータを構造化する代替方法を提供します。

テキストの分類は、テキストをグループに分けて整理することで、「テキストタグ付」または「テキスト分類」とも呼ばれます。自然言語処理では、テキストデータを分析し、その内容に応じてあらかじめ定義されたカテゴリーに分類することが可能です。

今回は、JavaとGridDBを使って、テキスト分類器を実装します。テキスト分類器の目的は、メールをspamとhamのどちらかに分類することです。

全ソースとデータはこちらでご覧いただけます。  
<https://github.com/griddbnet/Blogs/tree/text-classifier>

## データについて

使用するデータは、電子メールがスパムかハムかを示すものです。データは `mssspam.csv` という名前のカンマ区切り値 (CSV) ファイルに保存されています。データには `spamclass` と `text` という2つのフィールドがあります。最初のフィールドはメールがスパム (迷惑メール) かハム (迷惑メールでないもの) かを示しており、`spam` と `ham` の2つの値のみを取ります。2つ目のフィールドはメールの本文を表しています。

## GridDBにデータを格納する

CSVファイルから直接データを利用することも可能です。しかし、GridDBには、データの整理やクエリの性能向上など、多くの利点があります。そこで、CVSファイルからGridDBにデータを移行することにします。

まずは、GridDBにデータを格納するために、いくつかのライブラリをインポートしてみましょう。

```java
import java.io.File;
import java.util.Scanner;
import java.io.IOException;
import java.util.Collection;
import java.util.Properties;

import com.toshiba.mwcloud.gs.Query;
import com.toshiba.mwcloud.gs.RowKey;
import com.toshiba.mwcloud.gs.RowSet;
import com.toshiba.mwcloud.gs.GridStore;
import com.toshiba.mwcloud.gs.Collection;
import com.toshiba.mwcloud.gs.GSException;
import com.toshiba.mwcloud.gs.GridStoreFactory;
```

GridDBは、データをコンテナにグループ化します。データを格納するGridDBコンテナを表す静的なJavaクラスを作成しましょう。

```java
public static class EmailClassification{
    @RowKey String spamclass;
    String text;
}
```

上記の静的クラスは、2つの変数を持ちます。この 2 つの変数は、GridDB コンテナの 2 つのカラムに対応します。

GridDBにデータを移動するためには、JavaをGridDBに接続する必要があり、以下のようなコードで実現できます。

```java
Properties props = new Properties();
props.setProperty("notificationAddress", "239.0.0.1");
props.setProperty("notificationPort", "31999");
props.setProperty("clusterName", "defaultCluster");
props.setProperty("user", "admin");
props.setProperty("password", "admin");
GridStore store = GridStoreFactory.getInstance().getGridStore(props);
```

上記の内容を、お使いのGridDBの仕様に置き換えてください。

これで、データを格納するコンテナを選択できるようになりました。

```java
Collection<String, EmailClassification> coll = store.putCollection("col01", EmailClassification.class);
```

コンテナのインスタンスが作成されました。このインスタンスは、コンテナを参照するために使用することができます。

それでは、CSVファイルからデータを取り出し、GridDBに挿入してみましょう。

```java
File file1 = new File("smsspam.csv");
Scanner sc = new Scanner(file1);
String data = sc.next();

while (sc.hasNext()){
    String scData = sc.next();
    String dataList[] = scData.split(",");
    String spamclass = dataList[0];
    String text = dataList[1];

    EmailClassification ec = new EmailClassification();
    ec.spamclass = spamclass;
    ec.text = text;

    coll.append(ec);
}
```

上記のコードでは、CSV ファイルからデータを取り出し、GridDB コンテナに挿入しています。

## データを取得する

このデータをテキスト分類に利用したいので、GridDBコンテナからデータを取り出してみましょう。

```java
Query<emailclassification> query = coll.query("select *");
RowSet<emailclassification> rs = query.fetch(false);
RowSet res = query.fetch();
```

`select *`文は、GridDBコンテナに格納されているデータをすべて選択するのに役立ちました。

## テキスト分類の実装

データの準備ができたので、次はテキスト分類器を実装していきます。まず、テキスト分類器を実装するために使用するライブラリをインポートしましょう。

```java
import java.io.*;
import weka.core.*;
import java.util.List;
import java.util.Random;
import weka.filters.Filter;
import java.util.ArrayList;
import weka.core.Instances;
import weka.core.FastVector;
import weka.classifiers.Evaluation;
import weka.classifiers.bayes.NaiveBayes;
import weka.classifiers.meta.FilteredClassifier;
import weka.core.converters.ArffLoader.ArffReader;
import weka.filters.unsupervised.attribute.StringToWordVector;
```

データセットを読み込むためのバッファードリーダーを作成します。

```java
BufferedReader bufferedReader
    = new BufferedReader(
        new FileReader(res));

// Create dataset instances
Instances datasetInstances
    = new Instances(bufferedReader);
```

これでテキスト分類器を実装することができました。ここでは、`StringToWordVector` フィルタ (テキストを特徴ベクトルの形で表現するためのもの) と `NaiveBayes` 分類器 (学習を容易にするためのもの) を組み合わせて使用します。

```java
datasetInstances.setClassIndex(0);
StringToWordVector filter = new StringToWordVector();
filter.setAttributeIndices("last");
FilteredClassifier classifier = new FilteredClassifier();
classifier.setFilter(filter);
classifier.setClassifier(new NaiveBayes());
classifier.buildClassifier(datasetInstances);
System.out.println(classifier);
```

データセットのクラスは、最初の属性に設定されています。次にフィルタを作成し、テキストから特徴量に変換する属性を設定します。次に、`FilteredClassifier` クラスのオブジェクトを作成し、先ほどのフィルタと新しい `NaiveBayes` 分類器を追加します。データセットは、最初の属性としてクラス、2番目の属性としてテキストを持つ必要があります。

## モデルを評価する

これでモデルを評価し、そのパフォーマンス統計を見ることができます。次のコードはそのための助けになります。

```java
Evaluation eval = new Evaluation(datasetInstances);
eval.crossValidateModel(classifier, datasetInstances, 4, new Random(1));
System.out.println(eval.toSummaryString());
```

このコードでは、テキスト分類器の評価指標を返します。

## テキストの分類

さて、モデルの準備ができたので、それを使って予測をしてみましょう。予測を行う目的は、メールをスパムかハムに分類することです。

データセットの最後のインスタンスをスパムとハムのどちらかに分類してみましょう。次のコードはそれを示すものです。

```java
Instance pred = datasetInstances.lastInstance();
double answer = classifier.classifyInstance(pred);
System.out.println("Class predicted: " + pred.classAttribute().value((int) answer));
```

## モデルのコンパイルと実行

モデルをコンパイルして実行するには、Weka API が必要です。以下のURLからダウンロードしてください。

<http://www.java2s.com/Code/Jar/w/weka.htm>

次に、`gsadm` ユーザでログインします。作成した `.java` ファイルを GridDB の `bin` フォルダに移動します。移動先は以下の通りです。

/griddb_4.6.0-1_amd64/usr/griddb-4.6.0/bin

Linux端末で以下のコマンドを実行し、gridstore.jarファイルのパスを設定します。ファイルパスはお使いの環境に合わせて読み替えてください。

```sh
export CLASSPATH=$CLASSPATH:/home/osboxes/Downloads/griddb_4.6.0-1_amd64/usr/griddb-4.6.0/bin/gridstore.jar
```

次に、以下のコマンドを使用して `.java` ファイルをコンパイルします。

```sh
javac -cp weka-3-7-0/weka.jar TextClassification.java
```

最後に、以下のコマンドを実行することで生成される `.class` ファイルを実行します。

```sh
java -cp .:weka-3-7-0/weka.jar TextClassification
```

テキスト分類器は、メールを「ハム」と正しく分類します。