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)
(→このデータセットをどこから持ってきたか忘れてしまった)

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
...
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
...
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
...

入力層と出力層のみの2層。
→隠れ層を入れると判定の精度が下がる(現時点で原因不明)。
入力層は4つのノード(4つの特徴量を入力)

  • 4つの特徴量
    Sepal Length (がく片の長さ)
    Sepal Width (がく片の幅)
    Petal Length (花びらの長さ)
    Petal Width (花びらの幅)

出力層のノード数は2つ。
出力値は以下のように対応させる。

  • setosa : (1.0, 1.0)

  • versicolor : (1.0, 0.0)

  • virginica : (0.0, 0.0)

    neural.png

下記で、* と表記しているものはその都度変更する。ただ学習に用いるデータ数は、アヤメが3種類であるため、3の倍数にする。
学習回数は10^3~10^4オーダー、学習率は10^(-1)オーダーにしている。

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

// 入力層のノード数
#define INPUTNUM 4
// 出力層のノード数
#define OUTPUTNUM 2
// irisの種類
#define IRIS_TYPE 3
// データ数
#define DATANUM 150
// 学習に使うデータ数
#define LEARNING_DATANUM *
// テスト用に使うデータ数
#define TESTING_DATANUM (DATANUM - LEARNING_DATANUM)
// iris 1種類あたりのデータ数
#define DATANUM_PER_IRIS (DATANUM / IRIS_TYPE)
// iris 1種類あたりの学習データ数
#define LEARNING_DATANUM_PER_IRIS (LEARNING_DATANUM / IRIS_TYPE)
// iris 1種類あたりのテストデータ数
#define TESTING_DATANUM_PER_IRIS (TESTING_DATANUM / IRIS_TYPE)
// 学習回数
#define REPEAT *
// 学習率
#define LEARNING_RATE *

データ一つを構造体で表す。つまり今回は要素数150個の構造体の配列が作られる。
outputValue が出力の文字列表記。
numericOutputValue が出力(文字列)を数値に対応させたもの。

typedef struct{
    // index
    int n;
    // 入力値(説明変数)
    float inputValue[INPUTNUM];
    // 出力値
    char outputValue[100];
    // 数値で表した出力値 (目的変数)
    float numericOutputValue[OUTPUTNUM];
} DATASET;

3つの配列をcallocで動的にメモリ確保する。

    // 全データを格納する構造体配列(の先頭ポインタ)
    DATASET *data;
    // 学習用データを格納する配列
    DATASET *trainingData;
    // テスト用データを格納する配列
    DATASET *testingData;

重み係数を格納する配列weight[][]の宣言やその他の変数の設定。

    // 重み係数 (weight coefficient)
    float weight[INPUTNUM+1][OUTPUTNUM];
    // バイアス
    float bias = 1.0;
    // 学習回数 (leaning times)
    int repeat = REPEAT;

重み係数は乱数で0.0~1.0に設定する。

    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;
        }
    }

本プログラムの要↓
入力値からランダムに初期設定した重み係数により出力値を算出する。この算出された出力値と正しい出力値の誤差から重み係数を修正する。

    // 入力層
    float x[INPUTNUM+1] = {}, out1[INPUTNUM+1] = {};
    // 出力層
    float net2[OUTPUTNUM] = {}, out2[OUTPUTNUM] = {};
    // 誤差
    float E2[OUTPUTNUM] = {}, delta2[OUTPUTNUM] = {};
    for(count = 0; count < REPEAT; count++){
        // forward propagation
        for(i = 0; i < LEARNING_DATANUM; i++){
            // 入力層での計算
            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];
            }
            // 出力層での計算
            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
            // 出力層から入力層へ
            for(j = 0; j < OUTPUTNUM; j++){
                E2[j] = trainingData[i].numericOutputValue[j] - 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];
                }
            }
        }
    }

学習により計算した重み係数を用いて、テスト用データの入力値から出力値を計算する。

    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];
        }
    }

動的確保した配列のメモリ解放を忘れずに。

    free(data);
    free(trainingData);
    free(testingData);

学習用データ数を105(=テスト用データを45個)、つまり1種類あたり35個とし、学習回数を1000、学習率を0.1に設定してプログラムを動かしてみる。出力値がどれだけ正解に近づいているかを確認する。

// 学習に使うデータ数
#define LEARNING_DATANUM 105
// 学習回数
#define REPEAT 1000
// 学習率
#define LEARNING_RATE 0.1

コンソールでの出力↓

<No. データ番号> : 入力1, 入力1, 入力1, 入力1, <種類名>, 出力1, 出力2
No. 36 : 5.0, 3.2, 1.2, 0.2,     Iris-setosa,  0.0000000,  0.0031175
No. 37 : 5.5, 3.5, 1.3, 0.2,     Iris-setosa,  0.0000000,  0.0017389
No. 38 : 4.9, 3.1, 1.5, 0.1,     Iris-setosa,  0.0000000,  0.0089168
No. 39 : 4.4, 3.0, 1.3, 0.2,     Iris-setosa,  0.0000000,  0.0092934
No. 40 : 5.1, 3.4, 1.5, 0.2,     Iris-setosa,  0.0000000,  0.0051745
...
No. 50 : 5.0, 3.3, 1.4, 0.2,     Iris-setosa,  0.0000000,  0.0048661
No. 86 : 6.0, 3.4, 4.5, 1.6, Iris-versicolor,  0.0376224,  0.9973061
No. 87 : 6.7, 3.1, 4.7, 1.5, Iris-versicolor,  0.0122704,  0.9985036
No. 88 : 6.3, 2.3, 4.4, 1.3, Iris-versicolor,  0.0643542,  0.9991106
No. 89 : 5.6, 3.0, 4.1, 1.3, Iris-versicolor,  0.0065587,  0.9944878
No. 90 : 5.5, 2.5, 4.0, 1.3, Iris-versicolor,  0.0542432,  0.9972597
...
No.100 : 5.7, 2.8, 4.1, 1.3, Iris-versicolor,  0.0111303,  0.9959730
No.136 : 7.7, 3.0, 6.1, 2.3,  Iris-virginica,  0.9997730,  0.9999921
No.137 : 6.3, 3.4, 5.6, 2.4,  Iris-virginica,  0.9999573,  0.9999719
No.138 : 6.4, 3.1, 5.5, 1.8,  Iris-virginica,  0.9924076,  0.9999403
No.139 : 6.0, 3.0, 4.8, 1.8,  Iris-virginica,  0.9225439,  0.9996471
No.140 : 6.9, 3.1, 5.4, 2.1,  Iris-virginica,  0.9924627,  0.9999285
...
No.150 : 5.9, 3.0, 5.1, 1.8,  Iris-virginica,  0.9922869,  0.9998731

重み係数はランダムに決まるため複数回実行したが、すべて正解である。

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?