LoginSignup
1
1

More than 3 years have passed since last update.

MarkLogic10で多層パーセプトロンを実装してみる

Last updated at Posted at 2019-07-03

はじめに

2019年6月にMarkLogic10がリリースされ、GPU対応およびCNTKが実装されました。今回はこの新機能を使って多層パーセプトロンによる訓練と推論を実装してみようと思います。

例題は定番のアイリス分類とします。言語はXQueryです。

ソースコードは以下に置きました。
https://github.com/t2hk/marklogic-mlp/

MarkLogicのCNTK

MarkLogicはCNTKを実装しています。APIはオリジナルのCNTKに類似しているものの、XQueryという言語やMarkLogic自体の特性の影響からかCNTKオリジナルとは異なる部分も多々あります。
それでも、XQueryで機械学習のアルゴリズムを実装してモデル構築でき、データベース上のデータを直接学習・推論に利用できるのは便利です。

学習データとテストデータをMarkLogicにロードする

アイリスのデータセットを学習用とテスト用にわけます。今回は学習用に120件、テスト用に30件としました。
データ形式はCSVで、以下のようなデータになります(一部抜粋)。

sepal_length sepal_width petal_length petal_width species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
7.0 3.2 4.7 1.4 versicolor
6.4 3.2 4.5 1.5 versicolor
6.9 3.1 4.9 1.5 versicolor

用意したCSVファイルをMarkLogic用のデータロードツール「MLCP」を使用してXML変換してロードします。
学習データは"/iris/train/"にロードし、テストデータは"/iris/test/"にロードしています。

MLCPのオプションは以下のような感じです。学習データのロード用の設定例です。

import \
-mode local \
-host [MarkLogicサーバ] \
-port [XDBCポート番号] \
-username [ユーザ名] -password [パスワード] \
-input_file_type delimited_text \
-input_file_path [学習データのフルパス] \
-delimited_root_name iris \
-document_type xml \
-output_uri_replace "[学習データのフルパス], '/iris/train/iris'" \
-generate_uri true \
-output_uri_suffix .xml

学習用のXQuery

ソースコードは以下になります。
https://github.com/t2hk/marklogic-mlp/blob/master/ML10_MLP_Train.xqy

MLPの入力層と隠れ層を関数化

隠れ層のレイヤー数を任意とするため関数化して再帰呼び出し可能としました。
今回はアイリス分類なので、入力層の入力数は4となります(がく片の長さと幅、花びらの長さと幅)。
隠れ層は全結合のDenseを使用しています。

(:
  マルチパーセプトロンの入力層と隠れ層を構築する。出力層は含まない。
  input_variable : 入力層の入力数
  num_hidden_layers : 隠れ層のレイヤー数
  dense_map : アクティベーションのアルゴリズムや隠れ層の出力数などのパラメータを保持するmap
:)
declare function local:mlp($input_variable as cntk:variable, $num_hidden_layers as xs:integer, $dense_map as map:map){
  if($num_hidden_layers = 0) then cntk:dense-layer($input_variable, $dense_map)
  else cntk:dense-layer(local:mlp($input_variable, ($num_hidden_layers - 1), $dense_map), $dense_map)
};

パラメータ

MLPの各種パラメータを変数定義しています。隠れ層の数や出力数は可変です。

let $input_dims := 4 (:入力数:)
let $num_classes := 3 (:分類数:)
let $num_hidden_layers := 2 (:隠れ層の数:)
let $hidden_layers_dim := 10 (:隠れ層の出力数:)
let $hidden_activation := "relu"
let $minibatch_size := 100 (:ミニバッチの実行回数:)

学習データの作成

ロードした学習データを1x4の行列として読み込みます。

let $input_variable := cntk:input-variable(cntk:shape(( 1, $input_dims)), "float")
let $train_data :=
  for $x in xdmp:directory("/iris/train/", "infinity")
    return ($x/iris/sepal_length/text(), $x/iris/sepal_width/text(), $x/iris/petal_length/text(),  $x/iris/petal_width/text())

let $input_value := cntk:batch(cntk:shape((1, $input_dims)), json:to-array($train_data))

正解ラベルの作成

同様に正解ラベルを作成します。正解ラベルは分類名そのものではなく、One-Hot表現としています。
Pythonなどなら楽なのですが、XQueryでOne-Hot表現を作り出すのは泥臭く力業になります。
私が良い方法を知らないだけだと思いますが・・・

let $label_variable := cntk:input-variable(cntk:shape(( 1, $num_classes)), "float")
let $labels :=
for $x in xdmp:directory("/iris/train/", "infinity")
  let $_label := $x/iris/species/text()
  return if ($_label = "setosa") then (1,0,0)
  else if ($_label = "virginica")  then (0,1,0)
  else (0,0,1)

let $label_value := cntk:batch(cntk:shape((1,$num_classes)), json:to-array(($labels)))

MLPを組み立てる

関数定義したMLP関数を使用し、出力層も含めて構築します。

アクティベーションや出力数などはmapにまとめて設定します。

(:入力層と隠れ層を組み立てる:)
let $_model_map := map:map()
let $_ := map:put($_model_map, "activation", $hidden_activation)
let $_ := map:put($_model_map, "output-shape", cntk:shape((1, $hidden_layers_dim)))
let $_model := local:mlp($input_variable, ($num_hidden_layers), $_model_map)

(:出力層を組み立てる:)
let $_output_map := map:map()
let $_ := map:put($_output_map, "activation", $hidden_activation)
let $_ := map:put($_output_map, "output-shape", cntk:shape((1, $num_classes)))
let $model := cntk:dense-layer($_model, $_output_map)

訓練器を組み立てる

評価関数などを設定し、訓練器を組み立てます。

(:予測結果と正解の交差エントロピー誤差を求める:)
let $loss := cntk:cross-entropy-with-softmax(
     $model, 
     $label_variable, 
     cntk:axis(-1))

(:予測結果の評価:)     
let $err := cntk:classification-error($model, $label_variable, 1, cntk:axis(-1))

(:訓練器の構築:)
let $parameter:=cntk:function-parameters($model)
let $learner := 
     cntk:sgd-learner(($parameter), 
     cntk:learning-rate-schedule-from-constant(0.1))
let $trainer := cntk:trainer($model, ($learner), $loss, $err)

ミニバッチの実行

学習データと正解ラベルを使ってミニバッチを実行します。

(:学習データと正解データからミニバッチ用データを組み立てる:)
let $input_pair := json:to-array(($input_variable, $input_value))
let $labels_pair := json:to-array(($label_variable, $label_value))
let $minibatch := json:to-array(($input_pair, $labels_pair))

(:ミニバッチ実行:)
let $train_result :=
    for $i in (1 to $minibatch_size)
      let $__ := cntk:train-minibatch($trainer, $minibatch, fn:false())      
      let $loss := cntk:previous-minibatch-loss-average($trainer)
      let $evaluation := (1.0 - cntk:previous-minibatch-evaluation-average($trainer))       
      return json:to-array(($loss, $evaluation))

モデルの保存

学習したモデルをMarkLogicに保存して学習を終了します。

let $model_doc := cntk:function-save($model)
let $__ := xdmp:document-insert("/model/iris_model", $model_doc)

損失と正解率

学習した結果の損失と正解率を出力すると、以下のようになります。

[1.04729131062826, 0.516666666666667]
[0.685380744934082, 0.666666666666667]
[0.591424878438314, 0.808333333333333]
[0.532787577311198, 0.95]
[0.495430056254069, 0.841666666666667]
[0.482377560933431, 0.883333333333333]
[0.554083633422852, 0.666666666666667]
[0.767242622375488, 0.658333333333333]
[0.967457008361816, 0.666666666666667]
[0.577702267964681, 0.666666666666667]
[0.488694985707601, 0.925]
[0.439605490366618, 0.958333333333333]
・・・

推論用のXQuery

続いて学習したモデルを使用した推論です。

モデルの読み込み

学習したモデルを読み込み、入出力層のパラメータを設定します。

let $input_dims := 4
let $num_classes := 3
let $model := cntk:function(fn:doc("/model/iris_model")/binary())
let $input_variable := cntk:function-arguments($model)
let $output_variable := cntk:function-output($model)

推論対象のデータの読み込み

推論するデータを読み込み、入力データと正解ラベルを組み立てます。正解ラベルはOneHot表現とします。

let $test_data_docs :=
  for $doc in xdmp:directory("/iris/test/", "infinity")
    return $doc

(:入力データの組み立て:)
let $test_data :=
  for $x in $test_data_docs
    let $_test_data := ($x/iris/sepal_length/text(), $x/iris/sepal_width/text(), $x/iris/petal_length/text(),  $x/iris/petal_width/text())
    return $_test_data

(:正解ラベルの組み立て:)
let $test_labels :=
  for $x in $test_data_docs
    let $_label := $x/iris/species/text()
    let $_one_hot_label := 
      if ($_label = "setosa") then ("setosa", 1,0,0)
      else if ($_label = "virginica")  then ("virginica", 0,1,0)
      else if ($_label = "versicolor") then ("versicolor", 0,0,1)
      else ("unknown", 0,0,0)

    return json:to-array($_one_hot_label)

推論実行

推論を実行します。

let $test_value := cntk:batch(cntk:shape((1, $input_dims)), json:to-array($test_data))
let $test_pair := json:to-array(($input_variable, $test_value))
let $output_value := cntk:evaluate(
     $model, 
     $test_pair, 
     ($output_variable))
let $result_array := cntk:value-to-array($output_variable, $output_value)

推論した結果は以下のようになります。OneHotで表した各分類の確率が出力されます。
確率が一番高い分類が予測結果となります。

[0.1775239, 0.4641904, 0.3582857],
[0.1355018, 0.4985289, 0.3659692],
[0.1276607, 0.5236636, 0.3486757],
[0.1344947, 0.5015103, 0.363995],
・・・

おしまい

XQueryで機械学習というもちょっと変化球な感がありますが、DB上でGPUを使用した機械学習を実現できるのはなかなか便利だと思います。
今回はXQueryを使用しましたが、MarkLogicはサーバサイドでJavascriptも実行でき、CNTKのJavascript実装も搭載されています。Javascriptに慣れている方は、こちらを利用した方が良いと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1