HOME    BRMSブログ  No.20 『機械学習をルールベースAI:Corticonで実装する』

BRMS徹底活用ブログ

No.20 『機械学習をルールベースAI:Corticonで実装する』

2017.09.01 Progress Corticon

本エントリーは株式会社アシスト様が寄稿したエントリー(https://www.ashisuto.co.jp/product/category/brms/progress_corticon/column/detail/brmstech20.html)を転載したものとなります。

No.20  『機械学習をルールベースAI:Corticonで実装する』

2016年4月からProgress製品を担当しています棚橋浩志です。昨今『AI』はじめ『深層学習』や『Deep Learning』という言葉をよく見かけるようになりました。一方で『機械学習 』という言葉はめっぽう減りました。 この単語の違いは何でしょうか?興味ある方は米NVIDIA社の2016年7月29日の公式ブログでマイケル・コープランド氏が「What's the Difference Between Artificial Intelligence, Machine Learning, and Deep Learning?」を執筆しています。

さて話は変わりますが私たちにとっては長い長い子供達の夏休みがやっと終わりました。その夏休み中に愚息の水泳大会や剣道大会の引率をしましたが一日費やす割りに子供の出番は正味10分もありませんでした。そこで、その長い長い空き時間を利用して『深層学習』の本を数冊読んでみました。
4年前に、ある『機械学習』の製品を弊社取り扱いのQlikViewに取り込むConnectorを開発したときに『教師ありの学習』について学習しましたが、その時と比較して変わったことは
1) プログラミング言語が機械学習(深層学習)をサポートしはじめ身近に開発・利用できるようになった
2) CPUを使用しないでGPUで処理するようになった
3) 1次元で多要素の解析よりも多次元で要素の少ない解析が主流になってきた
ことでした。

教師あり学習

『機械学習』には『教師あり学習』と『教師なし学習』の2通りの方法があります。『深層学習』や『Deep Learning』は主に『教師あり学習』を指しています。
『教師あり学習』とは、教師となる模範データを多量に解析して傾向を見出して、新しい事象に適応する方法になります。 google社の『AlphaGo』は世界中の棋士の棋譜をCPUの処理速度を50倍程度上回るGPUで解析した結果と『さまざまな調整』を施して人間に勝利して有名になりました。 この『さまざまな調整』とは勝利するために多大な試行錯誤が行われた結果でありこの部分は確率統計学を極めないと手が出せない部分と思われます。 また解答が正しくない場合には、正しいデータを何度も取り込んで学習させる必要があります。 一方で模範データを多くすると『過学習』という負の面が現れて、事象の全体を見ることができなくなりますのでバランスの調整が必要になります。
このように『教師あり学習』とはある種『模範解答』が存在して、その解答にいかに近づけるかを『さまざまな調整』によって行うことで、この『さまざまな調整』は『教師あり学習』の中核を構成する部分になります。 そしてこの『さまざまな調整』をプログラミング言語が機械学習(深層学習)をサポートしはじめたとはいっても汎用化されたパラメータを使用するものであり、到底専用のアプリケーションとして精度不足になります。 そして精度不足の時に何をどのようにすればよいかという手法が見出せないところがまだまだ『機械学習』の試行錯誤の段階ではないでしょうか。  これは QlikView の Connector を開発していた時から実感していたことでしたが、今回読んだ書籍でもこの部分が簡単になったという記述はありませんでした。

教師なし学習

『教師なし学習』とは『教師あり学習』の逆で解答がないことを言います。 しかし解答がないといっても目標のようなものがないと学習できません。 『教師なし学習』の例としてGoogle DeepMindの「DQN」は、ブロック崩しゲームのルールをコンピュータが自分で学習し人間よりも高得点が得られるようになりました。
『教師なし学習』の場合には解答はないのですが、『高得点』という目標はあります。
『教師なし学習』のときに出てくる命題として『多腕バンディット』があります。この命題を考えてみましょう。

ある確率で"当たり"と"はずれ"の二者択一が抽選されるスロットマシーン(抽選機)があります。
今70%の確率で"当たり"が出るスロットマシーンAと40%の確率で"当たり"が出るスロットマシーンBがあります。
どのように選ぶと"当たり"の回数が最多になるでしょうか。

『教師あり学習』の場合には

1)スロットマシーンAを10,000回実行させると"当たり"が出る確率は70%位になります。
2)スロットマシーンBを10,000回実行させると"当たり"が出る確率は40%位になります。
3)スロットマシーンAとスロットマシーンBの結果を比較して、スロットマシーンAを初回から実行し続けると最大になる。
という結果になります。

しかし『教師なし学習』では利用できる情報は実行した結果しかありません。あなたならどうしますか?

教師なし学習 よくある手法

『確率のよいスロットマシーンを抽選する』が単純明快です。しかし『確率のよいスロットマシーン』に定義が不明確です。2回実行した結果や10回実行した結果という明確な定義がなければプログラムは記述できません。

例えば『累積結果を比較して確率のよいスロットマシーンを次回使用する。結果が同じ場合には別のスロットマシーンを次回使用する。』としましょう。ルールは明確です。
1)スロットマシーンAを実行する。
 残念ながら30%の"はずれ"を引いてしまいました。
 スロットマシーンAとスロットマシーンBの"当たり"を引いた累積結果を0%なので次回はスロットマシーンBを
 使用します。
2)スロットマシーンBを実行する。
 偶然にも40%の"あたり"を引いてしまいました。
 スロットマシーンAの累積結果(0/1)0%とスロットマシーンB(1/1)100%と比較して次回はスロットマシーンBも
 使用します。
3)スロットマシーンAの確率は0%のために以後スロットマシーンBを使用し続け最終的は40%位になります。

しかしこの方法は確率のよいスロットマシーンが初回抽選のときに"はずれ"を抽選した場合には誤った選択になる事を意味しています。

累積試行回数 スロットマシーンA (70%) スロットマシーンB (40%)
マシーン別
試行回数
結果 マシーン別
確率
マシーン別
試行回数
結果 マシーン別
確率
1 1 はずれ   0/1=0%
2 1 あたり 1/1=100%
3 2 はずれ 1/2=50%
4 3 はずれ 1/3=33%
5 4 あたり 2/4=50%
6 5 あたり 3/5=60%
7 6 はずれ 3/6=50%
8 7 はずれ 3/7=42%
9 8 はずれ 3/8=37%
10 9 はずれ 3/9=33%

人間は目標がわかっているから、何とか正しい方法で確率のよいスロットマシーンAを選ばせるように画策します。これが上記の『さまざまな調整』になります。これは『教師なし学習』も同じです。ある意味ずるいと感じる部分ですね。

教師なし学習 さまざまな調整

上記ルールに下記の条件を加えて見ましょう。

最初にスロットマシーンAを2回実行した結果とスロットマシーンBを2回実行した結果を比較して、
以後は確率のよいスロットマシーンを使用する

なかなかよい『調整』ですね。これは仮説検定のABテストになります。このように"スロットマシーンAを2回実行(検定)する"ことを『探索(EXPLORE)』と呼びます。 『探索(EXPLORE)』した後にその探索結果を基に特定のスロットマシーンを使用する事を『活用(EXPLOIT) 』と呼びます。

このコラムは確率・統計学ではないので私が計算したいところ 15%位でスロットマシーンBが選択されます。
『探索(EXPLORE)』は2回で十分でしょうか?10回にすれば十分でしょうか?根本的に確率だけではこの命題をとくことが難しいのです。何か違う策が必要になります。

教師なし学習 UCB1アルゴリズム

この命題をとく方法としては代表的な以下のアルゴリズムをはじめさまざまなアルゴリズムが生み出されています。

■Epsilon-Greedyアルゴリズム
■Softmaxアルゴリズム
■UCB1(Upper Confidence Bound 1 Algorithm)アルゴリズム

本稿ではアルゴリズムの解説ではないので各アルゴリズムの詳細は読者の皆様に調べてもらうことにしてUCB1アルゴリズムの計算式を評価してみましょう。

画像1

a "この"スロットマシーンで当たりがでた累積回数
n "この"スロットマシーンで抽選した累積試行回数
N すべてのスロットマシーンで抽選した累積試行回数
C 定数

このUCBアルゴリズムを『探索(EXPLORE)』に利用して以下のルールを机上で実践してみましょう。

初期値として全スロットマシーンを実行してUCB1値を出す
UCB1値が一番大きいスロットマシーンを選択する
UCB1値が一番大きいスロットマシーンが複数存在する場合は直前のスロットマシーンを選択する
定数Cを2とする

累積試行回数 スロットマシーンA (70%) スロットマシーンB (40%)
マシーン別
試行回数
結果 マシーン別
確率
マシーン別
UCB1値
マシーン別
試行回数
結果 マシーン別
確率
マシーン別
UCB1値
1(初期値取得) 1 はずれ   0/1=0% 0 -
2(初期値取得) 1.665 1 あたり 1/1=100% 2.665
3 2.096 2 はずれ 1/2=50% 1.982
4 2 はずれ 0/2=0% 1.665 1/3=33% 2.165
5 1.794 3 はずれ 1/3=34% 1.798
6 1.893 4 はずれ 1/4=25% 1.589
7 3 あたり 1/3=34% 1.944 1.645
8 4 あたり 2/4=50% 1.942 1.692
9 5 あたり 3/5=60% 1.926 1.732
10 6 あたり 4/6=67% 1.906 1.767

スロットマシーンAを選択する回数が多そうですね。偶然なのか必然なのかは試行回数を増やして検証する必要があります。

UCBアルゴリズムをCorticonで実装する

前節で使用したルールをCorticonで実装してみましょう。

まずはスロットマシーンを作成します。これは乱数発生器である下記演算子を使用します。

RandomGenerator.getRandomNumber

この演算子は0以上1以下の『デシマル』型の値を作成します。私はこの値の小数点以下第4桁の数字を取り出して、0から9迄の数字1文字を作成しました。

(((RandomGenerator.getRandomNumber * 10000).toInteger).mod(10)).toString

私の経験上乱数発生関数は結構値が偏る傾向が多いために『小数点以下第4桁の数字』を使用しています。 またルールテストで上記スクリプトを100万回実行を数回テストしましたが、生成される数字の出現率はほぼ1/10になる事を確認しました。

残りは抽選結果を累積保持するルールシートを加えます。表示結果としてスロットマシーンAおよびスロットマシーンBの実行回数および"あたり"の回数を表示させます。

bandit.ecore

画像2

語彙属性と設定値

エンティティ名 属性名 データ型 必須 モード 説明
bandit スロットマシーンになります
latestN 整数 いいえ 一過性 実行時の試行回数カウンター
next Boolean いいえ 一過性 次回使用するスロットマシーンはtrue
次回使用しないスロットマシンはfalseを保持する
number 文字列 はい ベース "あたり"と判定する数字の文字列
result Boolean いいえ ベース "あたり"の場合はtrue、はずれの場合はfalseを保持する
try 整数 いいえ ベース マシン別の累積試行回数
ucb1 デシマル いいえ 一過性 UCB1値を計算して保持する
win 整数 いいえ ベース マシン別の"あたり"を抽選した累積回数
wstring 文字列 いいえ 一過性 乱数発生器で抽選した数字1文字
test 検証環境になります
expectedNumberOfTrials 整数 はい ベース 予定試行回数
numberOfTrials 整数 いいえ 一過性 試行回数カウンター
ucb1_c デシマル はい ベース UCB1値計算時に使用する定数Cの値
bandit(bandit) banditと1:nの関連性を持たせる

Corticonで汎用的なルールを作成するために、latestN,nextという属性を作成しています。プログラミング言語で作成する場合には不要な変数です。

まずは属性を初期化するルールシートを2つ作成します。尚この2つの初期化ルールシートの実行順序は問いません。

initialize1.ers

画像3

testエンティティのnumberOfTrials属性をゼロクリアします。

initialize2.ers

画像4

banditエンティティのtry属性とwin属性をゼロクリアします。またnext属性をT(true)でクリアします。

いよいよ本処理部分のルールを作成します。まずは特定のスロットマシンを抽選するルールを作成します。

banditForLoop.ers

画像5s

画像をクリックで拡大

0行目で bandits.wstring に乱数を基に、任意の数字1文字を作成しています。
加えて マシン別の累積試行回数 である test.bandit.try をカウントアップしています。また試行回数カウンターである test.numberOfTrials をカウントアップしています。 次に条件項目a列で作成された文字列 bandits.wstring が事前に設定した文字列 test.bandits.numberに存在するかどうか検証して存在する場合には"あたり"と判定して test.bandits.result に T(true) を設定します。また"あたり"の場合にはマシン別の"あたり"を抽選した累積回数をカウントアップします。
このようにスロットマシンの確率は test.bandits.number に設定された文字で決定されます。数字が7文字設定されていれば70%の確率に、4個設定されていれば40%になります。

UCB1値を計算するルールを作成します。

ucb1.ers

画像6s

画像をクリックで拡大

抽選するスロットマシーンはフィルターを使用して bandits.next=T を設定します。初期処理の時には必ず全スロットマシーンを実行するので initialize2.ers で全スロットマシーンの bandits.next に T を設定しています。
初期処理以降は必ず1台 bandits.next に T を設定して、予定試行回数を超えた場合には null を設定しています。
予定試行回数以下の場合は全スロットマシーンのUCB1値を計算して test.bandit.ucb1 属性に格納し、次に実行するスロットマシーンを決定するために bandits.next を F クリアします。予定試行回数を超えた場合には null を設定します

次の実行するスロットマシーンを決定するルールを2段階で作成します。

next1.ers

画像7

UCB1値が最大のスロットマシーンを選択する為に、コレクション演算子を使用して bandits.ucb1->max が bandits.ucb1 と同じスロットマシーンの bandits.next 属性に T を設定します。

次にUCB1値が同値の場合には、直前のスロットマシーンを使用するというルールを実装します。

next2.ers

画像8

抽選の都度、使用されたスロットマシーンの bandits.latestN 属性に test.numberOfTrials を設定しているので、(直前のスロットマシーン) = (bandits.latestN 値が大きいスロットマシーン) になります。

上記ルールシートをルールフローで制御します。

test.erf

画像9

Loopというサブフローを作成してループ部分を格納します。最後にこのLoopサブフローにループ設定をしてます。

※ brms.propatiesのループ設定

ルールの設定は以上になります。いよいよテストを実施しますが試行回数を50,000回を想定してます。Corticonのデフォルト設定ではループの最高回数が100回に制限されているのでこの値を引き上げる必要があります。

com.corticon.reactor.rulebuilder.maxloops=1000000

上記設定は上限を1,000,000回にする設定です。プロパティファイルを上書きしてください。 詳しくは『Corticon Studio:クィックリファレンスガイド』 P13、または『Corticon Server:インテグレーション& デプロイメントガイド』『付録 C : Corticon のプロパティおよび設定の構成』"オーバーライド ファイル brms.properties の使用"を参照ください。

それではテストを設定します。
予定試行回数である text.expectedNumberOfTrials に 50000 を設定します。 スロットマシン2台を設定します。 test.bandit を2つ作成します。 抽選確率を2台のスロットマシーンに設定します。1台は70%なので test.bandit(bandit).number に文字列 "1246890" という数字7文字を設定しました。抽選確率が70%になります。 1台は40%なので test.bandit(bandit).number に文字列 "2468" という数字4文字を設定しました。抽選確率が40%になります。

それでは実行してみましょう。

test.ert

画像10

結果を表にまとめてみました。
項目 スロットマシーンA
bandit(bandit)[1]
スロットマシーンB
bandit(bandit)[2]
マシン別の累積試行回数 49806 194
マシン別の"あたり"を抽選した累積回数 34895 79
マシン別の"あたり"の確率 34895 / 49806 = 0.701 79 / 194 = 0.407
マシンを選択した割合 49806 / 50000 = 0.996 194 / 50000 = 0.004
実行する都度細かな数値は違ってきますがおおよそ上記のような結果になります。
スロットマシーンAの確率が70%で、スロットマシーンBの確率が40%になります。
そして驚くべき値としてスロットマシーンAを選択した割合が97%近くになりました。

信じられない数値が出たので100,000回で検証してみましょう。

test.ert

画像11

結果を表にまとめてみました。
項目 スロットマシーンA
bandit(bandit)[1]
スロットマシーンB
bandit(bandit)[2]
マシン別の累積試行回数 99785 215
マシン別の"あたり"を抽選した累積回数 69882 88
マシン別の"あたり"の確率 69882 / 99785 = 0.700 88 / 215 = 0.409
マシンを選択した割合 99785 / 100000 = 0.998 215 / 100000 = 0.002
50,000回の検証の時よりもスロットマシーンAを選択した割合はあがっています。

それでは回数を大幅に減らして100回で検証してみましょう。

test.ert

画像12

結果を表にまとめてみました。
項目 スロットマシーンA
bandit(bandit)[1]
スロットマシーンB
bandit(bandit)[2]
マシン別の累積試行回数 84 16
マシン別の"あたり"を抽選した累積回数 60 5
マシン別の"あたり"の確率 60 / 84 = 0.714 5 / 16 = 0.313
マシンを選択した割合 84 / 100 = 0.84 16 / 100 = 0.16
統計・確率学に基ずくために試行回数が少ないとマシンを選択した割合が10%-20%違ってきますが、数十回試行しましたが70%程度が下限になりました。

今度は確率20%のスロットマシーンCと確率10%のスロットマシーンDを増やして50,000回の再度検証してみましょう。

test.ert

画像13

結果を表にまとめてみました。
項目 スロットマシーンA
bandit(bandit)[1]
スロットマシーンB
bandit(bandit)[2]
スロットマシーンC
bandit(bandit)[3]
スロットマシーンD
bandit(bandit)[4]
マシン別の累積試行回数 49669 193 84 54
マシン別の"あたり"を抽選した累積回数 34827 79 20 7
マシン別の"あたり"の確率 34827 / 49669 = 0.701 79 / 194 = 0.407 20 / 83 = 0.241 7 / 54 = 0.123
マシンを選択した割合 49669 / 50000 = 0.9936 194 / 50000 = 0.004 83 / 50000 = 0.001 54 / 50000 = 0.001
スロットマシーンがあらたに2台増えても、もとの2台の確率に大きな影響を与えることはありませんでした。スロットマシーンCとスロットマシーンDは試行回数が少ない為に想定確率との誤差が大きいですが、選択した割合はスロットマシーンBよりも小さくなっています。

さいごに

教師なし学習のアルゴリズムは今後改良が加えられたり、あるいは別のアルゴリズムが出てくるかもしれませんが、UCB1(Upper Confidence Bound 1 Algorithm)アルゴリズムはとても優秀でビジネスでもいろいろ応用できそうですね。
一方でビジネスで使用する場合には、定数Cの値の決定方法や計算された値をどの様に利用するかという部分での試行錯誤が必要になってきます。
「Corticondで機械学習できますか?」というご質問をよく頂きます。解答は上記の様に『できます』というものの、試行錯誤部分はCorticonではできません。すなわち定数Cを決定することや、計算された確率をどのように使用するかも決定でません。
仮にCorticonが多数のアルゴリズムと定数Cを数パターン搭載したテンプレートを実装したとしても、最後には人間がどのアルゴリズムのどの定数Cを採用して、計算された値をどう使用するか意思決定する必要があり、この部分までの自動化は残念ながらできません。
また機械学習ではなぜその結果が導き出されたかと分析することはほぼ不可能といわれています。逆を言えばアルゴリズムAとアルゴリズムBのどちらを採用するか、定数Cが 2.0 と 1.0のどちらを採用するかという事は簡単に判別することができません。
この辺りまだまだ機械学習は遠い存在となっている部分でもあると思われます。