2016. 9.27.
石立 喬

OpenCV3.1.0とVisual C++ 2015による画像処理と認識(14)

----- ニューラルネットワークで数字を判別する -----

 最近、深層学習(deep learning)が話題になっている。ニューラルネットワークは、その基礎となるもので、すでに、「OpenCVとVisual C++による画像処理と認識(14)」で説明したものを、OpenCV3.1.0に書き換えて紹介する。前資料では、BPROP(Backpropagation)とRPROP(Resillient Backpropagation)の双方を試みて比較したが、ここでは、性能が優れ、デフォルトになっているRPROPに限定した。
 ニューラルネットワークに関する一般的な説明は簡潔にし、OpenCV3.1.0によって変わった部分を重点的に紹介する。

ニューラルネットワーク
 ニューラルネットワーク(Neural Network)は、ニューロン(Neuron、脳神経細胞)が結合してできたネットワーク(Network、回路網)のことで、OpenCVでは、特にANN(Artificial Neural Network、人工的脳神経回路網)として、コンピュータ上のプログラムであることを明確にしている。ニューロンは、複数個の入力端子を持ち、信号が来ると、それぞれの端子に設定された重みを掛けて加算し、一定のしきい値を超えた分を活性化関数(activation function)に入れ、その結果を出力端子から出す。
 ニューラルネットワークは、これらのニューロンを入力層(判定すべき入力群を与える)、隠れ層(ネットワークの外部とは接続の無い、機能向上のためのもの)、出力層(判定結果として一部ののニューロンのみが信号を発する)に割り当てて構成する。
 ニューラルネットワークは、そのままでは判定能力がない。あらかじめ既知の訓練用データを入力層に与え、出力層に現れた結果を教師データと比較し、誤差をフィードバックしてニューロンの重みを修正し、これを正解を出すまで繰り返す。これを、バックプロパゲーション(Backpropagation、誤差逆伝播)アルゴリズムと言う。

OpenCV3.0からOpenCV3.1.0への大きな変化
 ニューラルネットワークに関しては、OpenCV2.4からOpenCV3.0への変化は少なく、OpenCV3.1.0で大きく変わった。ニューラルネットワークのオブジェクトの生成は、cv::ml::ANN_MLPクラスのcreateメソッドを用いるが、訓練と判別は、cv::ml::StatModel基底クラスのtrainとpredictのメソッドを使用するように変わった。詳細条件設定は、cv::ml::ANN_MLPクラスに多数用意されているsetLayerSizesなどのメソッドを使用する。
◎モジュール構成が変わった
 ニューラルネットワークのクラスは、従来、CvANN_MPL(Artificial Neural Network - Multi-Layer Perceptronの意)と呼ばれ、ml( Machine Learning)モジュールのNewral Networkサブモジュールにあった。しかし、OpenCV3.1.0では、名称がcv::ml::ANN_MLPに変わり、ml(Machine Learning)モジュールの下に、直接配置された(Neural Networkのサブモジュールは無くなった)。
◎ニューラルネットワーククラスのオブジェクト生成と各種設定が分離され、分かり易くなった。
 従来は、CvANN_MPLクラスの生成を、
    create(各層のニューロン数、活性化関数のタイプ、訓練方法)
で行い、オブジェクト生成と同時に、各種設定ができた。しかし、OpenCV3.1.0では、createメソッドは空のオブジェクトを生成するのみで、各種パラメータは、
    setLayerSizes ----------- 各層のニューロン数
    setActivationFunction ----- 活性化関数のタイプ
    setTrainMethod ---------- 訓練方法
などのパラメータ設定メソッドを用いて、別途設定する。
◎訓練メソッドtrainで、TrainParamsクラスのパラメータを渡す必要が無くなり、簡単になった
 従来は、訓練実施に当たって、CvANN_MLPクラスのtrainメソッドを用いて、
    train(訓練データ、データ並びの方向、教師データ、パラメータ)
で指定する必要があり、あらかじめ、CvANN_MLP_TrainParamsクラスのパラメータを用意しておく必要があった。
 しかし、OpenCV3.1.0では、cv::ml::StatModel基底クラス(cv::ml::ANN_MLPクラスの親クラス)のtrainメソッドで、
    train(訓練データ、データ並びの方向、教師データ)
のみで良く、パラメータは、
    setRpropDW0
などの各種設定で、済ますことができる。
◎訓練の打ち切り条件TermCriteriaの設定が簡単になった
 従来は、CvANN_MLP_TrainParamsクラスのパラメータに入れてからtrainメソッドに渡していたが、OpenCV3.1.0では、
    setTermCriteria(TermCriteria)
で設定するようになり、単純になった。

ニューラルネットワークを用いて、「0」〜「9」の数字パターンを判定するプログラム
 訓練用データとして、数字パターンを教え込み、その後、正しく訓練されているかどうかを調べる簡単な数字判定プログラムで、下記から成っている。
 1)ニューラルネットワークを構成する各層の数layer_sizesを作成する。
    入力層 --- 5x7=35(ドット)、隠れ層 --- 10、出力層 --- 10(種類)とする。隠れ層の数10は経験による。
            のちに、隠れ層を8や6にも設定して、結果を比較する。
 2)ニューラルネットワークneuronを生成し、set*****メソッドで、訓練方法、各層の数、活性化関数を設定する。
    活性化関数は、SIGMOID_SYMをパラメータ設定で使用するが、のちに、設定なし、GAUSSIANなども試みる。
 3)訓練打ち切りの条件を設定する(デフォルト条件使用)。
 以上を図1に示す。
 4)訓練用データnumber_dataを用意する。「0」〜「9」の数字を5x7ピクセルで作成する
 5)訓練用データを、training_imageとして表示する(結果を図3に示す)。
 以上を図2に示す。
 6)「入力した数字が「0」の場合は、出力「0」が1で、他の出力は0でなくてはならない」などの教師データを作成する。
 7)trainメソッドで訓練を実施する。
 8)predictメソッドで判定させ、訓練結果をテストし、結果をアナログ的に表示する。
 以上を図4に示す。
 9)minMaxLoc関数で結果resultsの中から最大の値を持つ位置を求め、判定結果を「0」〜「9」で表示する(図5)。

使用したOpenCV関数の説明
◎ml::StatModel基底クラス
 ニューラルネットワークを含む多くの統計的モデル(statistical models)の基底クラスである。これ自身のオブジェクトは存在しない。ニューラルネットワークを訓練し、判定させるには、このクラスのメンバー関数を使用する。
 ニューロンを訓練するには、train関数を用いる。
  train(
    samples ------- InputArray、訓練用データの入力配列
    layout -------- int、cv::ml::ROW_SAMPLE( = 0)またはcv::ml::COL_SAMPLE( = 1)、データの並び方を指定
    responses ----- InputArray、教師データ、判定結果として期待すべき反応(response)の入力配列
  )
 ニューロンに判定させるには、predictメソッドを用いる。
  predict(
    samples ------------ InputArray、float型の被判定入力配列
    results = noArray() --- OutputArray、判定結果の出力配列
    flags = 0 ----------- int、モデルタイプにより使用、デフォルトのままで良い
  )
判定結果は、このメソッドの戻り値としても得られる。
◎ml::ANN_MLPクラス
 これがニューラルネットワークの主体となるクラスで、オブジェクトを生成し、条件を設定するには、
  Ptr<ANN_MLP> neuron = ANN_MLP::create();
で、まず空のneuronを生成し、下記のメソッドでメンバーパラメータを設定する。LayerSizesをActivationFunctionよりも先に設定するしないと、エラーになる。TrainingMethodsとして、RPROPがデフォルトになっているので、それに従う。デフォルト設定で十分なパラメータのset関連関数は使用しないので、省略してある。
 入力層、隠れ層、出力層のニューロン数を設定する。
  setLayerSizes(
    layer_sizes ----- InputArray、各層のニューロンの数を入れたint型のベクトル(最初が入力層、最後が出力層)
  )
 活性化関数の種類をパラメータを設定する。を
  setActivationFunction(
    type ---------- int、ANN_MLP::SIGMOID_SYM( = 1)を使う
    param1 = 0 -- double、デフォルトでも使えるが、0.66が良かった
    param2 = 0 -- double、デフォルトでも使えるが、、1が良かった
  )
  ActivationFunctuonのタイプには下記がある。
    IDENTITY( = 0) ------- 関数は f(x) = x であり、何もしないのと同じで、試みたが、全く使えなかった。
    SIGMOID_SYM( = 1) ---- 関数は f(x) = β:(1 - e-αx) / (1 + e-αx) であり、sigmoidと言うよりtanhである。
                     α = param1、β = param2である。調べたところ、実際のデフォルト値は、
                     param1 = 2 / 3、param2 = 1.7159 との説があるが、調査の結果、これは疑わしい。
    GAUSSIAN( = 2) ------ 関数は f(x) = β e-αx2であり、デフォルト値は確認していない。
 訓練の打ち切り条件を設定する。
  setTermCriteria(
    val ------ TermCriteria、後述のTermCriteriaクラスのコンストラクタで作成する
  )
    setTermCriteria(TermCriteria());
  のデフォルト設定で、十分な性能が得られる。
 訓練方法を設定する。
  setTrainMethod(
    method ----- int、デフォルトはANN_MLP::RPROP( = 1)
    param1 = 0 -- double、デフォルトのままで良い
    param2 = 0 -- double、デフォルトのままで良い
  )
◎TermCriteriaクラス
   繰り返し処理を終了させる(terminate)条件を設定するクラスで、core(Core Functionality)モジュールのBasicStructuresサブモジュールに含まれ、パラメータには次のものがある。資料にデフォルト値は示されていないが、推測値を示しておく。
  type ------- int、 maxCountのみ使用は、TermCriteria::COUNT = TermCriteria::MAX_ITER ( = 1)、
               epsilonのみ使用は、TermCriteria::EPS( = 2)
               双方を使う場合には、「|」(論理和記号)または「+」を使って併記
              デフォルトは双方使用らしい
  maxCount -- int、訓練の最大繰り返し回数、デフォルトは1000らしい
  epsilon ---- double、繰り返しごとの誤差の変化がこれ以下になれば繰り返しを打切る、デフォルトはFLT_EPSILONらしい


図1 ニューラルネットワークの構成を決め、訓練条件を設定する




図2 訓練用データを用意し、データの「1」、「0」を0,255の濃度に変換して表示する



図3 訓練用データを表示した結果



図4 対角線が1で、他は0の訓練用教師テータを作成し、訓練し、判定結果を表示する



図5 判定結果をアナログ値ではなく、「0」〜「9」で表示する


得られた結果
 図6に結果を示す。上の表の斜め対角線が、一致した場合の出力値で、1.000に近い値となっており、良い精度である。下の表は、それを「0」〜「9」の判定結果として表示したもので、参考までに、その時のアナログ出力値も表示している。


図6 入力と判定結果は良く一致している


隠れ層のニューロン数を変えた場合の判定結果
 図7は、図1のプログラムで、隠れ層の数のみを変えた結果を示す(左のN_HIDDEN = 10は良好な参考データ)。計算速度を上げるために隠れ層を減らしても、意外に悪くならなかった。実行時間と精度のトレードオフで選択すべきである。


図7 隠れ層のニューロン数を変えてみた結果


活性化関数を変えた場合の判定結果
 図8の左は、setActivationFunctionメソッドでの設定に当たり、活性化関数をSIGMOID_SYMのまま、パラメータ設定をデフォルトにした場合で、精度は劣るものの、一応使用できる。右は、活性化関数にGAUSSIANを指定した場合で(パラメータはデフォルト)、「7」を「1」と誤判定している。IDENTITYは全くダメであったので、結果は紹介しない。


図8 SIGMOID_SYMをデフォルトで使用した場合と、GAUSSIAN使用の場合の結果


結 論
 ニューラルネットワークに関しては、OpenCV3.0はOpenCV2.4とほぼ同じで、OpenCV3.1.0になって大きく変わった。ニューラルネットワークの生成に用いるcreateメソッドは、引数で各種パラメータの設定ができなくなり、代わりに、setLayerSizesなど多くのメソッドで個別に設定するようになった。これは、面倒のようにも見えるが、統一されて単純になったともいえる。
 従来必要だった、TrainParamsクラス、TermCriteriaクラスも、set****メソッドで、直接設定できるようになり、簡単になった。
 従来、ニューラルネットワークのメンバーメソッドであったtrainやpredictが、OpenCV3.1.0では、親クラスに当たるStatModel基底クラスのものを共通的に使うことになった。
 「OpenCVとVisual C++による画像処理と認識(14)」で紹介したのと同じ内容をOpenCV3.1.0に書き換えて実施したところ、同様の結果が得られた。
 隠れ層のニューロンの数を減らしてみたが、精度への影響は比較的少なかった。活性化関数のタイプをSIGMOID_SYMにしてパラメータをデフォルトのままにしても、精度はやや劣るものの、一応使えることが分かった。活性化関数のGAUSSIANは「7」を「1」と誤判定し、IDENTITYは全く使えなかった。

参考文献
 M.Riedmiller他、"A Direct Adaptive Method for Faster Backpropagation Learning: The RPROP Algorithm"(1993)
 http://deeplearning.cs.cmu.edu/pdfs/Rprop.pdf




「Visual C++の勉強部屋」(目次)へ