ROOTで機械学習してみる〜Multi Layer Perceptron（ANN）

# 概要
欧州原子核研究機構CERNが開発しているデータ分析ツールROOTを使って簡単に機械学習をしてみる。

ROOTで用意されているチュートリアルのサンプルコードを参考に、
Multi Layer Perceptron（MLP）で回帰曲線を出すところまでやってみる。

ROOTのインストール方法や基本的な使い方は過去に書いた記事を参考までに。
https://qiita.com/dyamaguc/items/2f723cbc304c4debd82e
https://qiita.com/dyamaguc/items/397121b303e26f8286cf

# 環境

- Mac Book Air
- Mac OS X 10.13.6
- ROOT 6.14/06

# サンプルコードと実行結果

## ROOTのサンプルコード（C++）

ROOTが提供しているサンプルコードはこちら
https://root.cern.ch/root/html608/mlpRegression_8C.html

ROOTをすでにインストールしている場合、サンプルコードは
`<path to root>/tutorials/mlp/mlpRegression.C`
ここにおいてある。
これを動かすには
`root <path to root>/tutorials/mlp/mlpRegression.C`
とターミナルでコマンドを打てば、いくつかプロットが出てきて、
うまく予測できていることがわかる。
C++のコードだが、コンパイルは不要。

## 自分のサンプルコード（Python）

ROOTが提供しているサンプルコードだとあまり面白くないので、
自分でサンプルコード少し変えて走らせてみた。
まず目的変数zに対し、説明変数x、yが次のような関係を持っているとする。

````
def theUnknownFunction(x, y):
    return TMath.Sin( (1.7+x)*(x-0.3) - 2.3*(y+0.7))
````

(x、y、z)の組を500個学習させて、得た回帰曲線の予想z_predと、正解z_trueを比較する。

````
from ROOT import TNtuple, TRandom, TMultiLayerPerceptron, TMLPAnalyzer, TMath, TCanvas, TGraph, TGraph2D, TTree, gROOT
from array import array
import numpy as np

def createData():
    N = 1000
    r = TRandom()
    r.SetSeed(0)

    data_tree = TTree("tree", "tree")
    x = np.empty((1), dtype="float32")
    y = np.empty((1), dtype="float32")
    z = np.empty((1), dtype="float32")
    data_tree.Branch("x", x, "x/f")
    data_tree.Branch("y", y, "y/f")
    data_tree.Branch("z", z, "z/f")

    # fill data
    for i in range(0, N):
        x[0] = r.Rndm()
        y[0] = r.Rndm()
        z[0] = theUnknownFunction(x, y)
        data_tree.Fill()

    del x,y,z

    return data_tree

if __name__ == '__main__':

    # fill data
    data_tree = createData()

    # create ANN
    mlp = TMultiLayerPerceptron("x,y:10:8:z", data_tree,  "Entry$%2","(Entry$%2)==0")
    mlp.Train(150, "graph update=10")

    mlpa = TMLPAnalyzer(mlp)
    mlpa.GatherInformations()
    mlpa.CheckNetwork()
    mlpa.DrawDInputs()
````

`TMultiLayerPerceptron`の最初の引数`"x,y:10:8:z"`はニューラルネットワークのレイヤーを記述している。
今回は、xとyが入力の層、その次の10と8はhidden layerのニューロンの数、zが出力。
今回はもとのサンプルと同じ設定にしている。
ROOTでは、`Draw()`で設定したニューラルネットワークの構造を可視化できる（下図）。
![structure.png](https://qiita-image-store.s3.amazonaws.com/0/329888/36437208-9cd4-2c3f-865e-bfc92419264d.png)

第２引数はデータ。ROOTのデータ構造である、TTreeで渡している。
第３引数は学習用データの条件。今回はEntry（データのID）が奇数の場合を指定している。
第４引数はテスト（検証用）データの条件。Entryが偶数の場合を指定している。

その次に`Train(150, "graph update=10")`で学習をしている。
第１引数はイテレーションの回数。
第２引数は学習のオプション。10イテレーション毎にグラフで学習曲線を描くように指定している。

`TMLPAnalyzer`で学習したMLPの結果を簡単に表示できる。
結果を可視化する。


````
#draw statistics shows the quality of the ANN's approximation
    canvas = TCanvas("TruthDeviation", "TruthDeviation")
    canvas.Divide(2,2)
    canvas.cd(1)
    mlp.Draw()

    canvas.cd(2)
    # draw the difference between the ANN's output for (x,y) and
    # the true value f(x,y), vs. f(x,y), as TProfiles
    mlpa.DrawTruthDeviations()

    canvas.cd(3)
    # draw the difference between the ANN's output for (x,y) and
    # the true value f(x,y), vs. x, and vs. y, as TProfiles
    mlpa.DrawTruthDeviationInsOut()

    canvas.cd(4)
    graph_truth_y05 = TGraph()
    graph_predi_y05 = TGraph()
    graph_truth_y05.SetMarkerStyle(20)
    graph_predi_y05.SetMarkerStyle(21)
    graph_predi_y05.SetMarkerColor(2)
    for ix in range(0, 15 ):
        v = array( 'd', [0,0])
        v[0] = ix / 10.0
        v[1] = 0.5
        graph_truth_y05.SetPoint( graph_truth_y05.GetN(), v[0], theUnknownFunction(v[0], v[1]))
        graph_predi_y05.SetPoint( graph_predi_y05.GetN(), v[0], mlp.Evaluate(0, v))
    graph_truth_y05.Draw("AP1")
    graph_predi_y05.Draw("P1 SAME")
    graph_truth_y05.SetTitle("y = 0.5;x;z")


    canvas.SaveAs("output.png")

    # To avoid error
    del mlp
````

実行結果は下図の通り。
x,y（右上）,z（左下）を横軸にとって、縦軸に正解と予測の差をとると、いい精度で予測できていることがわかる。
右下は、y=0.5のときの正解（黒）と予想（赤）のプロット。横軸がx、縦軸がz。
xが1以上で予想と正解がずれているが、学習データの範囲外なので、まあそんなところかなというところ。
0 <= x <= 1では正解（黒）と予測（赤）がよく一致している。

![output.png](https://qiita-image-store.s3.amazonaws.com/0/329888/d5002f73-6822-7389-adf4-c8aaf4ea52bc.png)

# まとめ

ROOTのフレームワークを使うことで簡単に、ニューラルネットワークの機械学習をしてみた。
チュートリアルのコードを参考にしたので、簡単に（数行で）回帰曲線を求められた。

# 参考
TMultiLayerPerceptron
https://root.cern.ch/doc/master/classTMultiLayerPerceptron.html#a9262eee03feb52487900c31f279691c2

TMLPAnalyzer
https://root.cern.ch/doc/master/classTMLPAnalyzer.html

実行後に出力されるエラーを消す方法
https://root-forum.cern.ch/t/output-could-be-produced-but-here-is-a-long-list-of-errors/10620/4

その他
https://www-he.scphys.kyoto-u.ac.jp/member/n.kamo/wiki/doku.php?id=study:software:root:pyroot

http://cp3.irmp.ucl.ac.be/~delaere/MLP/