Droolsブログ : 02 デシジョンテーブル

02 デシジョンテーブル

前回は HelloWorld ということで基本の DRL でルールを書きましたが、次はよく使われる「デシジョンテーブル」を説明します。

デシジョンテーブル(意思決定表)は以下のように表形式で、条件と結果を表すものです。DRL よりずっと分かりやすいですね。

f:id:tokobayashi:20190501110330p:plain

MS ExcelLibreOffice で書く XLS/XLSX 形式と、Drools のワークベンチ GUI (Decision Central)で書く GDST 形式がありますが、ここでは前者を説明します。

デシジョンテーブルのメリットは、Drools に詳しいメンバーが下準備さえ済ませれば、あとはビジネスユーザーがセルの値を埋めて行くだけ、という分業が簡単に行えることです。もちろん表にするまでもないルールは直接 DRL を書いて組み合わせることもあります。

実際のところ、デシジョンテーブルは DRL 生成テンプレートです。1行あたり、1ルールが内部的に生成されます。例えば上記の「ノーマルカード」の行は以下のような DRL になります。

rule "PointCalc_11"
    when
        $o : Order(consumer.card == MembershipCard.NORMAL)
    then
        $o.setPointRate($o.getPointRate().add(new BigDecimal("1.0")));
end
...

イメージついたでしょうか?

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 02_decisiontable です。お題は買い物した時のポイント付与サービスです。今回は4つのルールだけですが、実際にはものすごい量のルールだったりしますよね。

まずプロジェクト内のデシジョンテーブル

https://github.com/tkobayas/drools-blog/blob/master/02_decisiontable/src/main/resources/org/example/point-calc.xls

を見てみましょう。上記画像と同じものです。中身を順番に説明していきます。

まず前半は表共通の設定事項です。

  • 2行目: RuleSet の次のセルはパッケージ名です
  • 3行目: Import の次のセルは import するクラス名をカンマ区切りで並べます
  • 4行目: Notes の次のセルはただの説明です。ルールには反映されません
  • 6行目: RuleTable は半角スペース空けて、同じセルに表の名前を書きます。この名前が生成される各ルールのプリフィックスになります
  • 7行目: 条件部は「CONDITION」、結果部は「ACTION」と記入します。DRLにおける 「when」「then」に相当します。必要に応じて何列でも追加できます。
  • 8行目: 「CONDITION」の場合のみ、条件ファクトを指定します
  • 9行目: 「CONDITION」の場合は、上記ファクトの制約条件を記入します。テンプレートの挿入変数がひとつの場合は「$param」、複数の場合は「$1」「$2」...と書きます。「ACTION」の場合は「then」で実行したい Java コードを書きます。同様にテンプレートの挿入変数が使えます。

後半が実際にテンプレートに挿入する値となります。

  • 10行目: コメント用。以下のセルに何を入れればよいのかわかりやすく書きましょう。
  • 11行目: ここ以降が1行あたり1ルールに相当します。セルの値が上記9行目のテンプレートに適用されます。複数変数の場合はカンマ区切りです。空欄の場合はその列は丸ごと使用されません。B列も単なるコメントです。
  • 12行目: 以下同様

次に Java コードを見てみましょう。

https://github.com/tkobayas/drools-blog/blob/master/02_decisiontable/src/test/java/org/example/DroolsTest.java

        // デフォルトの dateformat は "dd-MMM-yyyy" (例: "01-Apr-2019") なので変更する
       System.setProperty("drools.dateformat", "yyyy-MM-dd");

まず日付フォーマットは日本向け ("yyyy-MM-dd") に変更しておきます。ソースコメントのとおりです。

        SpreadsheetCompiler compiler = new SpreadsheetCompiler();
        String drl = compiler.compile(ks.getResources().newClassPathResource("org/example/point-calc.xls").getInputStream(), InputType.XLS);
        System.out.println(drl);

これはデバッグ用コードで、本当は省いても構いません。このコードにより、デシジョンテーブルから変換された DRL を見る事ができます。ルールのコンパイルエラーなどがあったときには重宝します。

        KieContainer kcontainer = ks.getKieClasspathContainer();
        KieSession ksession = kcontainer.newKieSession();
        ...
        ksession.insert(john);
        ...
        ksession.insert(order);
        ...
        int fired = ksession.fireAllRules();

あとは HelloWorld と同じですね。

さて、実際に動かしてみましょう。

ジョンが20万円のギターを買いました。メンバーズカードはシルバーなので 2% 還元。また、この4月に入会したので「春の特別キャンペーン」が適用され、還元率 0.5 アップの 2.5%。更に特別ポイントが 1000 ポイント加算されます。

$ cd 02_decisiontable
$ mvn clean test

...

+++ ルール実行開始 +++
insert : Person [name=ジョン, memberCreatedAt=2019-04-11, card=SILVER]
insert : Order [consumer=ジョン, itemName=ギター, itemPrice=200000]
======================================
お買い上げにより、 6000 ポイントが付与されます

200000*0.025+1000=6000ですね。

Droolsブログ : 01 Drools HelloWorld

01 Drools HelloWorld

こんにちは、これから Drools についていろいろと書いていこうと思います。サンプルと共に、分かりやすい入門的なエントリを中心にしていく予定です。

Drools というのはオープンソースのルールエンジンで、Java で書かれています。

https://www.drools.org/

ルールエンジンとは、簡単に言うとインプットをルールに基づいて処理してアウトプットする、というエンジンです。あ、普通のプログラミングと同じですね?そうです、では普通の手続き型プログラミング言語(例えば Java)とどう違うかというと、ルールは宣言的(Declarative)に書かれる、ということです。例えばこのルールを見てください。

rule "Hello Child"
    when
        $p : Person( age < 12 )
    then
        // 何かする
end

rule "Hello Adult"
    when
        $p : Person( age >= 20 )
    then
        // 何かする
end

1つ目のルールは「12歳未満なら XXX する」

2つ目のルールは「20歳以上なら YYY する」

というものです。Java であれば

if (person.getAge() < 12) {
    // 何かする
}
if (person.getAge() >= 20) {
    // 何かする
}

のようになるでしょう。一見たいして変わらないようですが、大きな違いがあります。Java の場合は「書かれた順番通りに処理される」のです。数千、数万の複雑なルールの場合、全てを上から順番に処理するのは現実的ではないでしょう。プログラマは最適化のために、様々な手を打てます(データによって不要な部分を評価しない、共通部分をまとめて評価、キャッシュ/インデックス化など)。しかしそれをするとおそらくルールは最適化ロジックと混じり合い、ビジネスユーザが解読できないものになるでしょう。また、その後のルール追加、変更も困難になります。

ルールエンジンの場合、最適化はエンジンがやってくれます。ユーザは単純にルールを並列に並べるだけです。(もちろん「順番」がルール上必要な場合はいくつかの手段、文法があります。それはまた後日)

さて、概要はこのくらいにして早速 HelloWorld しましょう。

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 01_helloworld です。

ではソースコードを見てみましょう。

まずルールです。

https://github.com/tkobayas/drools-blog/blob/master/01_helloworld/src/main/resources/org/example/Sample.drl

package org.example
 
import org.example.Person;

rule "Hello Child"
    when
        $p : Person( age < 12 )
    then
        System.out.println( "Hello Child, " + $p.getName());
end

rule "Hello Adult"
    when
        $p : Person( age >= 20 )
    then
        System.out.println( "Hello Adult, " + $p.getName());
end

これは DRL (Drools Rule Language) という言語で書かれています。新しい言語を覚えるというと若干ハードルが高いですが、結構簡単です。見た通り、"when"に条件を書き、"then"に結果を書くだけです。"then"のところは普通の Java で書けます。"$p" はバインド変数です。マッチした Person を "then" で参照できます。

ではこのルールをどう実行するかというと、こちらの Java コードを見てください。

https://github.com/tkobayas/drools-blog/blob/master/01_helloworld/src/test/java/org/example/DroolsTest.java

        KieServices ks = KieServices.Factory.get();
        KieContainer kcontainer = ks.getKieClasspathContainer();
        KieSession ksession = kcontainer.newKieSession();

この3行は定型処理と思ってください。クラスパスにルールを置いた場合はこうなりますが、別の方法も後日紹介します。KieSession が、ユーザが使うエンジンインターフェースです。ksession と略されることもあります。

        Person john = new Person("ジョン", 25);
        ksession.insert(john);
        Person paul = new Person("ポール", 10);
        ksession.insert(paul);

2つの Person オブジェクトを ksession に insert します。Person はただの POJO です。ファクト(Fact) と呼ばれることもあります。

        int fired = ksession.fireAllRules();
        assertEquals(2, fired);

fireAllRules() でマッチしたルールが実行(fire)されます。戻り値は実行されたルールの数です。

        ksession.dispose();

最後、dispose() は忘れないように呼んでください。ksession に紐づいたリソースを解放します。

では maven で実行してみましょう。

$ cd 01_helloworld
$ mvn clean test

アウトプットはこんな感じになるでしょう

...
Running org.example.DroolsTest
...
Hello Child, ポール
Hello Adult, ジョン
...

ルール通りに出力されましたね。

まずはここまで!

Introduction to ML with Python

Pyhton3

export PATH="/home/tkobayas/anaconda3/bin:$PATH"

Jupyter Notebook

cd /home/tkobayas/usr/git/amueller/introduction_to_ml_with_python

jupyter notebook

 

Supervised Learning

Classifier と Regressor

  • k-NN (k-Nearest Neighbors)
    k個の近いやつを拾う。訓練はデータを格納するだけ。予測に時間がかかる
  • Linear Regression (ordinary least squares : OLS)
    二乗誤差(mean squared error) が最小になるように w (係数: coefficient) と b (切片: intercept) を求める。
  • Ridge
    L2正則化 (regularization) で w を小くし、汎化性能を上げる
  • Lasso
    L1正則化
  • Logistic Regression
    (TODO)
  • Linear Support Vector Machines: SVM
    (TODO)
  • Naive Bayes Classifiers
    (TODO)
  • Decision Trees
    2分木の繰り返し。depthを調整して汎化。外挿(extrapolate)ができない、つまり訓練データのレンジの外側に対しては予測できない
  • Random Forests
    featureやデータを減らした条件で複数のDecision Treeを生成し、組み合わせる(それぞれの確率予測を平均し、最も高いものに決定)
  • Gradient Boosted Regression Trees (勾配ブースティング回帰木)
    1つ前のDecision Treeの誤りを次のDecision Treeが修正するようにして、Decision Treeを順番に作っていく
  • Kernelized Support Vector Machines
    非線形特徴量を追加する(例:ある特徴量の2乗)。決定境界に重要な一部のデータポイントを Support Vector と呼ぶ。前処理:全ての特徴量が0から1の間になるようにスケール変換する。
  • Neural Network
    多層パーセプトロン(multilayer perceptron: MLP)。隠れユニット(Hidden Unit)による重み付け。活性化関数 RELU、TANH

不確実性の推定

  • Decision Function
    正負で予測クラス、絶対値でそのモデルが信じている度合いが表される
  • Predicting Probabilities
    0から1で確率(確信度)を表す

Unsupervised Learning and PreProcessing

前処理、スケール変換

  •  StandardScaler など

次元削減、特徴量抽出、多様体学習

  • 主成分分析 Principal Component Analysis : PCA
    まず分散が大きい方向を第1成分とする。次に第1成分と直交する方向から、最も情報を持っている方向を探し、第2成分とする。以下繰り返す。
    主成分のいくつかだけを残すことで次元削減にも使える。
    例えば顔認識に使う時、画像を各ピクセル(特徴量)ではなく、PCA変換後の各主成分の重み付き和で表現する、つまり別の特徴量で表現する。
  • 非負値行列因子分解 Non-negetive Matric Factorization : NMF
    データを非負の重み付き和に分解する。複数のデータが重ね合わせられた場合(複数楽器の音声データなど)に有効
  • t-SNE
    多様体学習。主に2次元表現にして可視化に用いる。

クラスタリング

  • k-means クラスタリング
    まずランダムにクラスタ重心を決める。個々のデータポイントを最寄りの重心に割り当て、重心位置を再計算する、を繰り返す。クラスタが丸い塊でないと上手く機能しない。
  • 凝集型クラスタリング agglomerative clustering
    個々のデータポイントを個別のクラスタとして開始する。類似したクラスタをマージしていく。「類似」のデフォルトは ward。マージした際にクラスタないの分散の増分が最小になるように2つのクラスタを選択する。これは階層型クラスタリング hierarchical clustering でもある。デンドログラムで階層を見る。
  • DBSCAN (Density-Based Spatial Clustering of Applications with Noise)
    クラスタはデータ中で高密度領域を構成していて、比較的空虚な領域で区切られていると考える。空虚な領域のデータを noise とみなす。クラスタ数を先験的に与える必要がない。
クラスタリングアルゴリズムの比較と評価
  • 調整ランド指数(adjusted rand index : ARI)
  • 正規化相互情報量(normalized mutual information : NMI)

データの表現と特徴量エンジニアリング

Categorical Variables

  • One-Hot-Encoding (Dummy variables)
    カテゴリごとに 0/1 の特徴量に置き換える。連続性のない数値データもワンホットエンコードする

 Binning, Discretization, Linear Models, and Trees

  • Binning/Discretization
    連続量を等間隔(ビン)に区切り、複数の離散値にし、ワンホットエンコーディングする。線形モデルに適用すると表現力が上がる場合がある。
  • 交互作用特徴量(interaction feature)
    2つの特徴量の積を特徴量として加える
  • 多項式特徴量(polynominal deature)
    特徴量の2乗、3乗 ... を特徴量として加える。Linear Regression と組み合わせると、Polynominal Linear Regression となる。スムーズに適合するようになる。

単変量非線形変換 Univariate Nonlinear Transformation

  •  log, exp はスケール変換で有用。sin,cos は周期的なパターンに有用。

自動特徴量選択

特徴量が多すぎると過剰適合の可能性が高くなる。どのように良い特徴量を選択すればよいか?

  • 単変量統計 univariate statistics
    個々の特徴量とターゲットとの間の統計的関係を計算。高い確信度で関連している特徴量を選択する。モデルの構築が不要
  • モデルベース選択 model-based selection
    教師あり学習モデルを用いて個々の特徴量の重要性を判断。決定木ベースモデルなどを使う。
  • 反復選択 iteratice selection
    再帰的特徴量削減 recursive feature elimination : RFE まず全ての特徴量を使ってモデルを作り、最も重要度が低い特徴量を削減する。そしてまたモデルを作り。。。を繰り返す。

専門家知識の利用

専門家によるレビューで、適切な特徴量を追加する。単なるPOSIX時刻ではなく、時刻や曜日など。

モデルの評価と改良

 交差検証 cross-validation

k-fold cross-validation データをk個に分割して1つをテストデータとし、残りを訓練データにする。kパターンの組合わせで精度を検証できる。

層化k分割交差検証 stratified k-fold cross-validation 各分割内でのクラスの比率が全体の比率と同じになるように分割する。

1つ抜き交差検証 leave-one-out データ1つが1分割に相当する。大量に検証出来る。

グリッドサーチ

各パラメータ(SVMにおける gamma [カーネルのバント幅]、C[正則化パラメータ])の各値でグリッド表にして、比較する。

パラメータ選択用にもう一つ「検証セット」を分ける。つまりデータを training set, validation set, test set に3分割する

評価基準とスコア

最終的な目標を見失わないこと。アプリケーションの高レベルでの目的を考える。

精度が予測性能の尺度として良くない場合がある。結果を true positive (TP), false positive (FP), true negative (TN), false negative (FN)  に分ける。

精度 = (TP+TN) / (TP + TN + FP + FN)

適合率 = TP / (TP + FP) ... 偽陽性を減らしたいときに用いる

再現率 = TP / (TP + FN) ... 偽陰性を避けたいときに用いる(ガン検査など)

f-measure (f-値) = 2 * ( (適合率 * 再現率) / (適合率 + 再現率) ) ... 総合的にまとめて評価。偏った2クラス分類データセットに対しては、精度よりも良い基準となる。

ROCカーブ receiver operating characterristics curve 偽陽性率と真陽性率を軸にしてプロットする。このカーブの下の領域面積 AUC : area under the curve のスコアで評価。偏ったクラス分類問題を評価する基準としては、精度よりもAUCの方がはるかに良い。

回帰の場合、R^2スコア(coefficient of determination)で良い

アルゴリズムチェーンとパイプライン

複数の Estimator をひとつの Pipeline にまとめる(例: MinMaxScaler と SVC)。この pipeline をグリッドサーチに渡せばよい。

(fit -> transform) -> (fit -> transform) -> (fit) とか

(transform) -> (transform) -> (predict) のように呼ばれる

グリッドサーチとパイプラインの組合わせで、例えば RandomForestClassifier と SVC の比較もできる。

テキストデータの処理

Bag of Words (BoW)

単語に分割し、出現回数をカウント。そのまま特徴量として Classifier を訓練できる。

不要な単語などを検出し、特徴量を抽出/削減する。あるいはスケールを調整する。

  • min_df : 最小出現文書数。例えば、1つの文書にしか出現しない単語は評価する意味が無いだろう。
  • ストップワード : 辞書ベースで固有の不要リストを作る。英語だと the, me, など
  • tf-idf (term frequency-inverse document frequency) : 特定の文書に頻出する単語に重みを与える(満遍なく出るような単語は重要では無い)。

n-gram

2つや3つのトークンをまとめる。例えば "not worth" は否定的、"definitely worth", "well worth" は肯定的。

正規化 normalization

  • 語幹処理 stemming : 単語の末尾を取り除き、単語の変化形を共通化する。機械的なので、うまくいかない場合も多い。
  • 見出し語化 lemmatization : 語幹処理よりも複雑な処理で、一般に良い結果が得られる
  • スペルミスの修正など

トピックモデリング、文書クラスタリング

  • LDA (Latent Dirichlet Allocation) : 同時に現れる頻度の高い単語の集合(= トピック)を探す。

その他

  • word2vec : ワードベクタと呼ばれる連続値ベクタ表現を利用
  • RNN (Recurrent Neural Networks) : テキストからテキストを生成。自動翻訳や要約に適している