機械学習、Java、GridDBを利用した不正取引検知

今日、金融の世界では、金融データが新しい通貨となっています。しかし、ますます拡大する金融データを従来の統計的手法で処理することは難しくなってきています。機械学習は、企業価値や株価、あるいは金融詐欺の検出といった形で金融データを予測するプログラムの作成を可能にする一連のテクニックとモジュールを提供します。

本稿では、クレジットカード決済を利用したお客様の不正取引を検知することができる決定木の作成について説明します。本レポートでは、Javaプログラミングを用いた決定木の実装を取り上げます。この記事ではまず、機械学習アルゴリズムの主な要件について説明します。次に、データセットとその主な構成要素について説明します。その後、決定木を使って決定木を実装を行います。最後に、GridDB の read、write、store メソッドを実装し、展開とテストのためにデータを保存しています。

[ フルソースコードはこちら ][1]

## 必要条件

Weka 3.9: weka.jar ファイルをダウンロードし、以下のパスに配置します。
/usr/share/java/.

GridDB 4.6:インストール後、必ずGridDBクラスタをアクティブにしてください。

Weka Jar ファイルをお使いの環境の CLASSPATH に配置することが重要です。GridStoreのJarファイルも同様に、GridDBデータベースが稼働していることを確認してください。

この作業を行うには、以下のコマンドラインを使用してください。

```sh
$ export CLASSPATH=${CLASSPATH}:/usr/share/java/weka.jar
$ export CLASSPATH=$CLASSPATH:/usr/share/java/gridstore.jar
```

## データセット

デモンストレーションのために、クレジットカード取引を含むサンプルデータセットを選択しました。このデータセットは、以下のリンクから入手可能です。このデータセットは9999個のインスタンスと31個の属性から構成されており、その詳細は以下の通りです。

1.  **Time**: すべてのクレジットカード決済の間に経過した秒数を測定します。
2.  **V1～V28**: プライバシー保護のため匿名化されたクレジットカードの利用者識別情報を測定します。
3.  **Amount**: 取引金額をドル建てで測定します。
4.  **Class**: 取引の種類を示し、この属性は2つの可能な値をとります。**0** は不正ではない、**1** は不正であることを示します。

以下は、そのデータセットの抜粋です。

```sh
"Time","V1","V2","V3","V4","V5","V6","V7","V8","V9","V10","V11","V12","V13","V14","V15","V16","V17","V18","V19","V20","V21","V22","V23","V24","V25","V26","V27","V28","Amount","Class"

0,-1.3598071336738,-0.0727811733098497,2.53634673796914,1.37815522427443,-0.338320769942518,0.462387777762292,0.239598554061257,0.0986979012610507,0.363786969611213,0.0907941719789316,-0.551599533260813,-0.617800855762348,-0.991389847235408,-0.311169353699879,1.46817697209427,-0.470400525259478,0.207971241929242,0.0257905801985591,0.403992960255733,0.251412098239705,-0.018306777944153,0.277837575558899,-0.110473910188767,0.0669280749146731,0.128539358273528,-0.189114843888824,0.133558376740387,-0.0210530534538215,149.62,"0"
```

今回使用したデータセットは、プロジェクトのリソースページに格納されているカンマ区切りの値ファイルです。Javaを使用すると、データ処理コードは、まずファイルパスを読み取り、スキャナメソッドを使用してデータを読み取り、一次メモリにデータを格納します。以下のJavaスニペットは、上記で説明したことを実行するものです。

```java
// Handling Dataset and storage to GridDB
File data = new File("/home/ubuntu/griddb/gsSample/creditcard.csv");
Scanner sc = new Scanner(data);  
sc.useDelimiter("\n");

while (sc.hasNext())  // Returns a boolean value  
{ 
    int i = 0;
    Row row = collection.createRow();

    String line = sc.next();
    String columns[] = line.split(",");

    int Time = Integer.parseInt(columns[0]);

    float V1 = Float.parseFloat(columns[1]);
    float V2 = Float.parseFloat(columns[2]);
    float V3 = Float.parseFloat(columns[3]);
    float V4 = Float.parseFloat(columns[4]);
    float V5 = Float.parseFloat(columns[5]);
    float V6 = Float.parseFloat(columns[6]);
    float V7 = Float.parseFloat(columns[7]);
    float V8 = Float.parseFloat(columns[8]);
    float V9 = Float.parseFloat(columns[9]);
    float V10 = Float.parseFloat(columns[10]);
    float V11 = Float.parseFloat(columns[11]);
    float V12 = Float.parseFloat(columns[12]);
    float V13 = Float.parseFloat(columns[13]);
    float V14 = Float.parseFloat(columns[14]);
    float V15 = Float.parseFloat(columns[15]);
    float V16 = Float.parseFloat(columns[16]);
    float V17 = Float.parseFloat(columns[17]);
    float V18 = Float.parseFloat(columns[18]);
    float V19 = Float.parseFloat(columns[19]);
    float V20 = Float.parseFloat(columns[20]);
    float V21 = Float.parseFloat(columns[21]);
    float V22 = Float.parseFloat(columns[22]);
    float V23 = Float.parseFloat(columns[23]);
    float V24 = Float.parseFloat(columns[24]);
    float V25 = Float.parseFloat(columns[25]);
    float V26 = Float.parseFloat(columns[26]);
    float V27 = Float.parseFloat(columns[27]);
    float V28 = Float.parseFloat(columns[28]);

    int Amount = Integer.parseInt(columns[29]);
    int Class = Integer.parseInt(columns[30]);

    row.setInteger(0,i);
    row.setInteger(1, Time);

    row.setFloat(2, V1);
    row.setFloat(3, V2);
    row.setFloat(4, V3);
    row.setFloat(5, V4);
    row.setFloat(6, V5);
    row.setFloat(7, V6);
    row.setFloat(8, V7);
    row.setFloat(9, V8);
    row.setFloat(10, V9);
    row.setFloat(11, V10);
    row.setFloat(12, V11);
    row.setFloat(13, V12);
    row.setFloat(14, V13);
    row.setFloat(15, V14);
    row.setFloat(16, V15);
    row.setFloat(17, V16);
    row.setFloat(18, V17);
    row.setFloat(19, V18);
    row.setFloat(20, V19);
    row.setFloat(21, V20);
    row.setFloat(22, V21);
    row.setFloat(23, V22);
    row.setFloat(24, V23);
    row.setFloat(25, V24);
    row.setFloat(26, V25);
    row.setFloat(27, V26);
    row.setFloat(28, V27);
    row.setFloat(29, V28);
    row.setFloat(30, V28);

    row.setInteger(31,Amount);
    row.setInteger(32,Class);

    rowList.add(row);

    i++;
}
```

データセット・ファイルからデータを取得したら、次のJavaステートメントを使用して、データ処理に使用したスキャナを閉じることが重要です。

```java
sc.close();
```

この決定木は、不正と思われるクレジットカード取引を予測するために実装されます。以下では、Javaで記述された決定木アルゴリズムの実装を行います。

## 決定木アルゴリズムのJavaによる実装

Wekaパッケージの **J48** アルゴリズムを使って、決定木を実装します。生成された決定木は **C4.5** 決定木と呼ばれ、刈り込みと刈り込み解除が可能です。実装作業を開始するために、まずは必要なライブラリをすべてインポートすることから始めます。

以下に、決定木の実装に使用したライブラリの一覧を示します。

```java
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
import weka.classifiers.Evaluation;
```

この決定木はoptions引数で設定することができます。以下は、決定木が多くのインスタンスを受け入れるようにするために実施可能な、一連のデフォルトの変更です。

これらの設定を変更するためのJavaコードは次のとおりです。

```java
String[] options = new String[4];
options[0] = "-C";
options[1] = "0.25";
options[2] = "-M";  
options[3] = "30";
```

## インポートパッケージ

Javaを使ってデータセットを確実に処理するためには、一連のライブラリが必要です。これらのライブラリは、私たちのプログラムにおける保管場所として機能する予定です。最初にインポートされるライブラリは、**ArrayList** と **List** モジュールです。

<div class="clipboard">
  <pre><code class="language-java">import java.util.ArrayList;
import java.util.List;</code></pre>
</div>

さらに、**GridDB** ストアを処理するために、**Properties** と **Random** という2つの追加ライブラリーをインポートする必要があります。

```java
import java.util.Properties;
import java.util.Random;
```

次に、**GridDB** インターフェイスを実装するために必要なライブラリです。

```java
import com.toshiba.mwcloud.gs.Collection;
import com.toshiba.mwcloud.gs.ColumnInfo;
import com.toshiba.mwcloud.gs.Container;
import com.toshiba.mwcloud.gs.ContainerInfo;
import com.toshiba.mwcloud.gs.GSType;
import com.toshiba.mwcloud.gs.GridStore;
import com.toshiba.mwcloud.gs.GridStoreFactory;
import com.toshiba.mwcloud.gs.Query;
import com.toshiba.mwcloud.gs.Row;
import com.toshiba.mwcloud.gs.RowSet;
```

最後に、**CreditCard** CSV ファイルを操作するためのパッケージをインポートします。

```java
import java.io.IOException;
import java.util.Scanner;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileReader;
```

## GridDBにデータを書き込む

クレジットカードのデータを **GridDB** データベースに格納するために、あらかじめ定義されたサーバーへの接続オブジェクトをインスタンス化することから始めます。また、**getInstance()** メソッドを用いて、**GridStore** クラスを初期化します。

```java
// Manage connection to GridDB
Properties prop = new Properties();
      
prop.setProperty("notificationAddress", "239.0.0.1");
prop.setProperty("notificationPort", "31999");
prop.setProperty("clusterName", "cluster");
prop.setProperty("database", "public");
prop.setProperty("user", "admin");
prop.setProperty("password", "admin");

// Get Store and Container
GridStore store = GridStoreFactory.getInstance().getGridStore(prop);
store.getContainer("newContainer");
String containerName = "mContainer";
```

オブジェクトを作成したら、まずはデータセットからカラムを抽出します。これは、add() メソッドで行うことができます。

```java
// Define container schema and columns
ContainerInfo containerInfo = new ContainerInfo();
List<columninfo> columnList = new ArrayList<columninfo>();

columnList.add(new ColumnInfo("key", GSType.INTEGER));
columnList.add(new ColumnInfo("Time", GSType.INTEGER));

columnList.add(new ColumnInfo("V1", GSType.FLOAT));
columnList.add(new ColumnInfo("V2", GSType.FLOAT));
columnList.add(new ColumnInfo("V3", GSType.FLOAT));
columnList.add(new ColumnInfo("V4", GSType.FLOAT));
columnList.add(new ColumnInfo("V5", GSType.FLOAT));
columnList.add(new ColumnInfo("V6", GSType.FLOAT));
columnList.add(new ColumnInfo("V7", GSType.FLOAT));
columnList.add(new ColumnInfo("V8", GSType.FLOAT));
columnList.add(new ColumnInfo("V9", GSType.FLOAT));
columnList.add(new ColumnInfo("V10", GSType.FLOAT));
columnList.add(new ColumnInfo("V11", GSType.FLOAT));
columnList.add(new ColumnInfo("V12", GSType.FLOAT));
columnList.add(new ColumnInfo("V13", GSType.FLOAT));
columnList.add(new ColumnInfo("V14", GSType.FLOAT));
columnList.add(new ColumnInfo("V15", GSType.FLOAT));
columnList.add(new ColumnInfo("V16", GSType.FLOAT));
columnList.add(new ColumnInfo("V17", GSType.FLOAT));
columnList.add(new ColumnInfo("V18", GSType.FLOAT));
columnList.add(new ColumnInfo("V19", GSType.FLOAT));
columnList.add(new ColumnInfo("V20", GSType.FLOAT));
columnList.add(new ColumnInfo("V21", GSType.FLOAT));
columnList.add(new ColumnInfo("V22", GSType.FLOAT));
columnList.add(new ColumnInfo("V23", GSType.FLOAT));
columnList.add(new ColumnInfo("V24", GSType.FLOAT));
columnList.add(new ColumnInfo("V25", GSType.FLOAT));
columnList.add(new ColumnInfo("V26", GSType.FLOAT));
columnList.add(new ColumnInfo("V27", GSType.FLOAT));
columnList.add(new ColumnInfo("V28", GSType.FLOAT));

columnList.add(new ColumnInfo("Amount", GSType.FLOAT));
columnList.add(new ColumnInfo("Class", GSType.INTEGER));

containerInfo.setColumnInfoList(columnList);
containerInfo.setRowKeyAssigned(true);
```

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

Javaプログラミングでデータセットを処理したら、次はデータセットをデータベースに長期保存する作業に移ります。ここで **GridDB** が非常に便利になります。各カラムをあらかじめ定義された行に保存しておいてください。

その作業を行うために、以下のJavaコードスニペットを使用します。

```java
row.setInteger(0,i);
row.setInteger(1, Time);

row.setFloat(2, V1);
row.setFloat(3, V2);
row.setFloat(4, V3);
row.setFloat(5, V4);
row.setFloat(6, V5);
row.setFloat(7, V6);
row.setFloat(8, V7);
row.setFloat(9, V8);
row.setFloat(10, V9);
row.setFloat(11, V10);
row.setFloat(12, V11);
row.setFloat(13, V12);
row.setFloat(14, V13);
row.setFloat(15, V14);
row.setFloat(16, V15);
row.setFloat(17, V16);
row.setFloat(18, V17);
row.setFloat(19, V18);
row.setFloat(20, V19);
row.setFloat(21, V20);
row.setFloat(22, V21);
row.setFloat(23, V22);
row.setFloat(24, V23);
row.setFloat(25, V24);
row.setFloat(26, V25);
row.setFloat(27, V26);
row.setFloat(28, V27);
row.setFloat(29, V28);
row.setFloat(30, V28);

row.setInteger(31,Amount);
row.setInteger(32,Class);

rowList.add(row);
```

GridDBデータベースへの変更をコミットするには、データベースの行を引数にとる **put()** メソッドを使用することができます。

この作業を行うために、以下のコードを使用します。

```java
collection.put(rowList);
```

## GridDBからデータを取得する。

データベースにある店舗データのインスタンスを取得するために、**SELECT** クエリを使用して、必要な行と列を **GridDB** データベースから取得する必要があります。

以下は、上記のタスクを実行するコードスニペットです。

```java
Query<row> query = container.query("SELECT * ");
RowSet<row> rs = query.fetch();
```

Javaにある **println()** メソッドを使用して、データを表示することができます。この作業は、次のように行うことができます。

```java
// Print GridDB data
while (rs.hasNext()) {
    Row row = rs.next();

    System.out.println(" Row=" + row);
}
```

## デシジョンツリーの構築

データが**GridDB**データベースに安全に保存されたことを確認した後、決定木アルゴリズムを実行し、Javaを使ってモデルの精度を決定する処理を行うことができます。 Javaプログラムでは、**Instances()**というメソッドを使って、データを適切な機械学習データモデルに変換し、それを使って決定木を作成することができます。

その作業を行うには、次のコードを使用します。

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

データをすぐに使えるインスタンスに変換して準備ができたら、buildClassifer()メソッドを使って機械学習モデルを作成します。次のモデルは、決定木の構築に使用されます。

```java
mytree.buildClassifier(datasetInstances);
```

機械学習モデルを実行した後、その精度を決定するために、モデルが正しく動作しているかどうかを確認することが重要です。 これらの作業は、クロスバリデーションモデル中に達成することができます。

```java
Evaluation eval = new Evaluation(datasetInstances);  
eval.crossValidateModel(mytree, datasetInstances, 10, new Random(1));
```

コードの最後の行で、データインスタンスに対してクロスバリデーションを実行していることがわかります。このプロセスは、特に限られたデータセットで計算するため、偏りのない結果を得るためにデータセットが異なる方法で分割されていることを確認します。

最後に、今回のモデルの概要を以下のように印刷します。

```java
System.out.println(eval.toSummaryString("\n === Summary === \n", true));
```

## コードのコンパイルと実行

コードをコンパイルして実行するには、まず、**gsSample/** にある **FraudulentTransactions.java** ファイルを探すことから始めます。フォルダが見つかったら、以下のコマンドを実行して、Javaコードを実行し、結果をコンパイルしてください。

```sh
~/griddb$ javac gsSample/FraudulentTransactions.java
~/griddb$ java gsSample/FraudulentTransactions.java
```

## 結論

Javaプログラムを用いて決定木アルゴリズムで作成した結果の概要は次のとおりです。

```sh
=== Summary ===

Correlation coefficient                  0.7445
Mean absolute error                      0.0009
Root mean squared error                  0.0296
Relative absolute error                 25.4503 %
Root relative squared error             71.3443 %
```

実証したように、決定木はクレジットカードの不正取引を分類する際に、**71.34%** の精度という結果を出しました。このモデルの精度を上げ、より良い精度を達成するためには、データセットのサイズを大きくして、モジュールが異なる不正取引を学習できるようにすることが望ましいです。

**クエリ**、**コンテナ**、および**GridDB**データベースを必ず終了してください。

```java
query.close();
container.close();
store.close();
```

[ フルソースコードはこちら ][1]

 [1]: https://github.com/SimoCs/Fraudulent-Transactions-Java