8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ニューラルネットワーク(誤差逆伝搬法) in C

Last updated at Posted at 2018-12-06

目的

ニューラルネットワーク(誤差逆伝搬法)でアヤメの種類(setosa, versicolor, virginica)を自動判定する。

検証方法

  • データセット
    • 各種類50個ずつ (合計 50*3種類=150)
      • (このデータセットをどこから持ってきたか忘れてしまった)
    • 4つの特徴量
      • Sepal Length (がく片の長さ)
      • Sepal Width (がく片の幅)
      • Petal Length (花びらの長さ)
      • Petal Width (花びらの幅)
    • 左からSepal Length, Sepal Width, Petal Length, Petal Width, 種類名
iris.data
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
...
4.8,3.0,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
...
5.7,3.0,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3.0,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3.0,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3.0,5.8,2.2,Iris-virginica
...
6.7,3.0,5.2,2.3,Iris-virginica
6.3,2.5,5.0,1.9,Iris-virginica
6.5,3.0,5.2,2.0,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica
  • 学習方法
    1. このデータを学習用とテスト用に分ける
    2. 学習用データを用いて各ノード間の重み係数を適切な値に修正する。つまりパーセプトロンを学習させる
    3. 学習させたパーセプトロンにテスト用データを入力してその出力が予想(正解)と正しいかを確認する
  • 今回は入力層と出力層のみの2層
    • 隠れ層を入れると判定の精度が下がってしまった(現時点で原因不明)
    • 入力層のノード数は4つ(=特徴量の数)
    • 出力層のノード数は2つ。出力値は以下のように対応させる
      • setosa : (0.0, 0.0)
      • versicolor : (0.0, 1.0)
      • virginica : (1.0, 1.0)

neural.png

実装

パラメータ設定
  • 諸々のパラメータをマクロ定義で設定
/* Neural Network Backpropagation*/
/* ニューラルネットワーク 誤差逆伝搬法 */
/* このソースコードでは入力層と出力層のみを実装する */
/* アヤメの特徴量からアヤメの種類を判定する */
/* 出力層を2つのニューロンに設定する*/

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>

// 入力値の数 (The number of input value)
#define INPUTNUM 4
// 出力値の数 (The number of output value)
#define OUTPUTNUM 2
// irisの種類 (type of iris)
#define IRIS_TYPE 3
// データ数 (The number of all data)
#define DATANUM 150
// 学習に使うデータ数 (data used for learning)
#define LEARNING_DATANUM 120
// テスト用に使うデータ数 (data used for testing)
#define TESTING_DATANUM (DATANUM - LEARNING_DATANUM)
// iris 1種類あたりのデータ数 (data per iris species)
#define DATANUM_PER_IRIS (DATANUM / IRIS_TYPE)
// iris 1種類あたりの学習データ数 (trainig data per iris species)
#define LEARNING_DATANUM_PER_IRIS (LEARNING_DATANUM / IRIS_TYPE)
// iris 1種類あたりのテストデータ数 (testing data per iris species)
#define TESTING_DATANUM_PER_IRIS (TESTING_DATANUM / IRIS_TYPE)
// 学習回数 (The number of leaning times)
#define REPEAT 1000
// 学習率 (learning rate)
#define LEARNING_RATE 0.1
  • パラメータの説明(一部)
    • LEARNING_DATANUM...学習用データの個数。上記では120個としている。
      • よってTESTING_DATANUM(テスト用データ)は30個となる。
      • アヤメ1種類あたりは50個ある。つまり40個を学習用にして残りの10個をテスト用にする。
    • REPEAT...学習回数。実際の出力と予測値の誤差により何回各ノードの重み係数を更新するか。
    • LEARNING_RATE...学習率。この値にしたのは特に根拠がない。
学習を始める前の実装
  • 学習を始める前までのフローチャート
  • データを格納する構造体
    • n : データの番号No、dataファイルの上から順番に割り振る。つまりsetosaがNo.1~No.50、versicolorがNo.51~No.100、virginicaがNo.101~150となる。
    • inputValue...4つの特徴量を格納する
    • outputValue...アヤメの種類名を格納する。文字配列であるため、計算するときに直接使用するものではない。
    • numericOutputValue...アヤメの種類ごとに置き換えた数値を格納する配列
typedef struct{
    // index
    int n;
    // 入力値(説明変数 explanatory variables)
    float inputValue[INPUTNUM];
    // 出力値 (Output value)
    char outputValue[100];
    // Numeric output value (objective variables)
    // 数値で表した出力値 (目的変数)
    float numericOutputValue[OUTPUTNUM];
} DATASET;
  • 構造体配列の動的確保とデータの読み取り
    • 全データ用配列と学習データ用配列とテストデータ用配列を確保する
    // データの構造体配列のポインタ
    DATASET *data;
    DATASET *trainingData;
    DATASET *testingData;

    // データの構造体の配列を動的に確保
    data         = (DATASET*)calloc(DATANUM, sizeof(DATASET));
    trainingData = (DATASET*)calloc(LEARNING_DATANUM, sizeof(DATASET));
    testingData  = (DATASET*)calloc(TESTING_DATANUM, sizeof(DATASET));
  • 以下のソースコードでは
    • データを読み取って構造体配列に格納
    • 文字ベースの出力を数字に置き換える
    • 全データを学習(訓練)データとテストデータに置き換える
      • setosaはNo.1~No.40が学習用でNo.41~No.50がテスト用
      • versicolorはNo.51~No.90が学習用でNo.91~No.100がテスト用
      • virginicaはNo.101~No.140が学習用でNo.141~No.150がテスト用
    • 重み係数の初期化(0.0~1.0の範囲でランダムに値を定める)
    // Read data from the file
    readDataFile(filename, data);
    
    // 文字ベースの出力を数値に置き換える
    replaceOutputWithNumbers(data, irisName);

    // Display all data
    //printf("== All Data ==\n");
    //printData(data, DATANUM);

    // Split into traning data and testing data
    // 元データを訓練用データとテスト用データに振り分ける
    makeTraingOrTestingData(data, trainingData, testingData);

    // Display training data
    printf("==  Training Data ==\n");
    printData(trainingData, LEARNING_DATANUM);

    // Display testing data
    printf("==  Test Data ==\n");
    printData(testingData, TESTING_DATANUM);

    // Initialize the weighting coefficients
    // 重み係数の初期値を設定
    srand((unsigned int)(time(0)));
    for(i = 0; i < INPUTNUM+1; i++){
        for(j = 0; j < OUTPUTNUM; j++){
            weight[i][j] = (float)(rand())/(float)RAND_MAX;
            //weight[i][j] = (float)(rand()%100/100);
            //weight[i][j] = 5.0;
        }
    }
誤差逆伝搬法による学習をする部分の実装
  • フローチャート
  • ソースコード
    /* ===== Start training =====*/
    // 入力層 (input layer)
    float x[INPUTNUM+1] = {}, out1[INPUTNUM+1] = {};
    // 出力層 (output layer)
    float net2[OUTPUTNUM] = {}, out2[OUTPUTNUM] = {};
    // 誤差 (error)
    float E2[OUTPUTNUM] = {}, delta2[OUTPUTNUM] = {};
    for(count = 0; count < REPEAT; count++){
        // forward propagation
        for(i = 0; i < LEARNING_DATANUM; i++){
            // 入力層での計算 (calculation in the input layer)
            for(j = 0; j < INPUTNUM+1; j++){
                if(j == 0) x[j] = bias;
                else x[j] = trainingData[i].inputValue[j-1];
                out1[j] = x[j];
            }
            // 出力層での計算 (calculation in the output layer)
            for(j = 0; j < OUTPUTNUM; j++){
                net2[j] = 0.0;
                for(k = 0; k < INPUTNUM+1; k++){
                    net2[j] += weight[k][j] * out1[k];
                }
                out2[j] = sigmoid(net2[j]);
            }

            // back propagation
            // 出力層から入力層へ (from the output layer to the input layer)
            for(j = 0; j < OUTPUTNUM; j++){
                E2[j] = trainingData[i].numericOutputValue[j] - out2[j];
                delta2[j] = -E2[j] * out2[j] * (1.0 - out2[j]);
                //delta2[j] = E2[j] * out2[j] * (1.0 - out2[j]);
            }
            for(j = 0; j < INPUTNUM+1; j++){
                for(k = 0; k < OUTPUTNUM; k++){
                    weight[j][k] += -LEARNING_RATE * delta2[k] * out1[j];
                }
            }
        }
    }

    /* ===== Start testing =====*/
    for(i = 0; i < TESTING_DATANUM; i++){
        // 入力層での計算 (calculation in the input layer)
        for(j = 0; j < INPUTNUM+1; j++){
            if(j == 0) x[j] = bias;
            else x[j] = testingData[i].inputValue[j-1];
            out1[j] = x[j];
        }
        // 出力層での計算 (calculation in the output layer)
        for(j = 0; j < OUTPUTNUM; j++){
            net2[j] = 0.0;
            for(k = 0; k < INPUTNUM+1; k++){
                net2[j] += weight[k][j] * out1[k];
            }
            out2[j] = sigmoid(net2[j]);
            testingData[i].numericOutputValue[j] = out2[j];
        }
    }

結果

  • テストデータはすべて予想通りの出力となっている。学習は上手くいった。
    • 重み係数はランダムに決まるため複数回実行したが、毎回すべて正解する。
No. 41 : 5.0, 3.5, 1.3, 0.3,     Iris-setosa,  0.0000000,  0.0026845
No. 42 : 4.5, 2.3, 1.3, 0.3,     Iris-setosa,  0.0000000,  0.0355544
No. 43 : 4.4, 3.2, 1.3, 0.2,     Iris-setosa,  0.0000000,  0.0063959
No. 44 : 5.0, 3.5, 1.6, 0.6,     Iris-setosa,  0.0000000,  0.0120607
No. 45 : 5.1, 3.8, 1.9, 0.4,     Iris-setosa,  0.0000000,  0.0116454
No. 46 : 4.8, 3.0, 1.4, 0.3,     Iris-setosa,  0.0000000,  0.0108464
No. 47 : 5.1, 3.8, 1.6, 0.2,     Iris-setosa,  0.0000000,  0.0031094
No. 48 : 4.6, 3.2, 1.4, 0.2,     Iris-setosa,  0.0000000,  0.0074419
No. 49 : 5.3, 3.7, 1.5, 0.2,     Iris-setosa,  0.0000000,  0.0022898
No. 50 : 5.0, 3.3, 1.4, 0.2,     Iris-setosa,  0.0000000,  0.0044369
No. 91 : 5.5, 2.6, 4.4, 1.2, Iris-versicolor,  0.1674164,  0.9990015
No. 92 : 6.1, 3.0, 4.6, 1.4, Iris-versicolor,  0.0511815,  0.9987439
No. 93 : 5.8, 2.6, 4.0, 1.2, Iris-versicolor,  0.0040013,  0.9953710
No. 94 : 5.0, 2.3, 3.3, 1.0, Iris-versicolor,  0.0008143,  0.9816664
No. 95 : 5.6, 2.7, 4.2, 1.3, Iris-versicolor,  0.0521161,  0.9979370
No. 96 : 5.7, 3.0, 4.2, 1.2, Iris-versicolor,  0.0033728,  0.9953467
No. 97 : 5.7, 2.9, 4.2, 1.3, Iris-versicolor,  0.0135798,  0.9967676
No. 98 : 6.2, 2.9, 4.3, 1.3, Iris-versicolor,  0.0031222,  0.9964274
No. 99 : 5.1, 2.5, 3.0, 1.1, Iris-versicolor,  0.0000715,  0.9398974
No.100 : 5.7, 2.8, 4.1, 1.3, Iris-versicolor,  0.0113796,  0.9962904
No.141 : 6.7, 3.1, 5.6, 2.4,  Iris-virginica,  0.9999476,  0.9999838
No.142 : 6.9, 3.1, 5.1, 2.3,  Iris-virginica,  0.9917510,  0.9998852
No.143 : 5.8, 2.7, 5.1, 1.9,  Iris-virginica,  0.9995515,  0.9999546
No.144 : 6.8, 3.2, 5.9, 2.3,  Iris-virginica,  0.9999545,  0.9999903
No.145 : 6.7, 3.3, 5.7, 2.5,  Iris-virginica,  0.9999720,  0.9999859
No.146 : 6.7, 3.0, 5.2, 2.3,  Iris-virginica,  0.9988658,  0.9999414
No.147 : 6.3, 2.5, 5.0, 1.9,  Iris-virginica,  0.9971870,  0.9999338
No.148 : 6.5, 3.0, 5.2, 2.0,  Iris-virginica,  0.9923266,  0.9999143
No.149 : 6.2, 3.4, 5.4, 2.3,  Iris-virginica,  0.9997564,  0.9999580
No.150 : 5.9, 3.0, 5.1, 1.8,  Iris-virginica,  0.9927638,  0.9998974

== Weight Coefficient ==
-5.668628, -0.021706, 
-4.276303, -0.843092, 
-4.785562, -1.829622, 
6.575436, 3.211346, 
9.245656, 1.828374,

課題

  • 隠れ層を入れたときにうまくいかない原因調査とその対処法
  • 出力値を数値に置き換える方法を変えてみてトライする
8
10
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
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?