LoginSignup
8
10

More than 5 years have passed since last update.

c++を用いたニューラルネットワーク(アヤメ)

Last updated at Posted at 2018-12-06

アヤメの種類(setosa, versicolor, virginica)を判定する誤差逆伝搬法を用いたニューラルネットワークを書いてみた。
データセットは、各種類50個ずつ
それぞれ40個が学習用、残り10個がテスト用
つまり合計150のデータがあり、120が教師用、30がテスト用
隠れ層は用いず、入力層と出力層のみの2層構
入力層にはアヤメの4つの特徴量を入力
ここで4つの特徴量とは、以下に示す通り
Sepal Length (がく片の長さ)
Sepal Width (がく片の幅)
Petal Length (花びらの長さ)
Petal Width (花びらの幅)

neural.png

上記より、入力層のノード数は4つとした。
また、出力層のノード数は2つとし、出力値はそれぞれ、
setosa : (1.0, 1.0)
versicolor : (1.0, 0.0)
virginica : (0.0, 0.0)
に対応させる

まずはクラス(Multi_layer_perceptron)の宣言。
void predict(int) は教師データにより学習させたのち、新たなテスト用のデータを入力してテストさせるための関数

neural8.cpp
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <ctime>

#include <stdio.h>

using namespace std;

class Multi_layer_perceptron{

private:
    /* the number of readed data fron file 読み込んだデータ数 */
    int sample;
    /* the number of train data 訓練データ数 */
    int train_sample;
    /* the number of test data テスト用データ数 */
    int test_sample;
    /* the number of node of each layer 各層のノード数 */
    int in, hide, out;
    /* the number of explanatory variables 説明変数 */
    int variable = 0;
    /* input layer 入力層 */
    double *input1, *input2;
    /* output layer 出力層 */
    double *output1, *output2;
    /* error layer 誤差関数 */
    double *error2;
    /* weight 重み係数 */
    double **weight1;
    /* delta 修正量 */
    double *delta2;
    /* bias バイアス */
    double bias = 1.0;
    /* output value (教師データ)出力値 */
    double *answer;

    /* 教師データ・訓練用データ */
    double **test_data, **train_data;

    /* sigmoid function */
    double sigmoid(double x){
        return 1.0 / (1.0 + exp(-x));
    }

    /* derivative of sigmoid function シグモイド関数の微分 */
    double diff_sigmoid(double x){
        return (1.0 - sigmoid(x)) * sigmoid(x);
    }

public:
    /* コンストラクタ */
    Multi_layer_perceptron(int, int, int, int, int);
    /* デストラクタ */
    ~Multi_layer_perceptron();
    /* ファイル読み込み関数 */
    void read_file(char*, int, int);

    void forward_propagation(int);
    /* 誤差逆伝搬法による重み係数の修正量 */
    void back_propagation(int, double);
    /* テスト */
    void predict(int);
    /* 重み係数表示 */
    void print_weight();

};

以下のコンストラクタで、入力層や出力層のノード数に応じて重み係数などの配列の要素数を動的に確保する。重み係数の初期値は乱数で0から1の値に設定。

neural8.cpp
/* Constructor */
Multi_layer_perceptron::Multi_layer_perceptron(int n_in, int n_hide, int n_out, int n_test_sample, int n_train_sample){

    int i, j;

    /* the number of test data テスト用データの数 */
    test_sample = n_test_sample;
    /* the number of training data 教師データの数 */
    train_sample = n_train_sample;
    /* the number of reade data from file 読み込んだデータ数 */
    sample = test_sample + train_sample;

    /* unit number 各層のノード数 */
    in   = n_in;
    hide = n_hide;
    out  = n_out;

    /* allocate inputs */
    input1 = new double[in+1];
    input2 = new double[out];

    /* allocate outputs */
    output1 = new double[in+1];
    output2 = new double[out];

    /* allocate delta */
    delta2 = new double[out];

    /* allocate error */
    error2 = new double[out];

    /* allocate weight */
    weight1 = new double*[in+1];
    for(i = 0; i < in+1; i++) weight1[i] = new double[out];

    /* initialize weight 重み係数を乱数により初期化 */
    srand((unsigned)(time(0)));

    for(i = 0; i < in+1; i++){
        for(j = 0; j < out; j++){
            //weight1[i][j] = 0.1;
            weight1[i][j] = (double)rand() / (double)RAND_MAX;
        }
    }

    /* allocate output value */
    answer = new double[out];

    cout << "Memory allocated ." << endl;

}

コメントが少ないが、アヤメのcsvファイルからデータを読み取り配列に格納する関数
2次元配列train_dataに教師データ(40*3 = 120個)、test_dataにテスト用データ(10*3 = 30個)をそれぞれ格納。

neural8.cpp
/* reading file ファイル読み込み */
void Multi_layer_perceptron::read_file(char* filename, int n_variable, int kind){

    int i, j, k;
    int flag = 0;
    int train, test;

    /* explanatory variables 説明変数 */
    variable = n_variable;

    /* the number of train or test data of each kind  */
    /* 各アヤメの種類ごとの教師or訓練用データ */
    train = train_sample / kind;
    test = test_sample / kind;

    /* allocate training data */
    train_data = new double*[train_sample];
    for(i = 0; i < train_sample; i++) train_data[i] = new double[variable+out];

    /* allocate test data */
    test_data = new double*[test_sample];
    for(i = 0; i < test_sample; i++) test_data[i] = new double[variable+out];

    cout << "Star reading file ." << endl;

    /* Start reading file */
    FILE *fp;

    fp = fopen(filename, "r");
    if(fp == NULL){
        cout << "can't open file ." << endl;
        exit(1);
    }

    /* 各グループごとに読み取る */
    for(k = 0; k < kind; k++){

        /* 教師データの読み取り */
        for(i = 0; i < train; i++){

            for(j = 0; j < variable+out; j++){

                if(j < variable+out-1){

                    if(fscanf(fp, "%lf, ", &train_data[i+train*k][j]) == EOF){
                        flag = 1;
                        break;
                    }

                }else{
                    if(fscanf(fp, "%lf\n", &train_data[i+train*k][j]) == EOF){
                        flag = 1;
                        break;
                    }

                }

            }

            /* 読み取るデータがなければ終了 */
            if(flag == 1){
                cout << "can't read file ." << endl;
                exit(1);
            }

        }

        /* テスト用データの読み取り */
        for(i = 0; i < test; i++){

            for(j = 0; j < variable+out; j++){

                if(j < variable+out-1){

                    if(fscanf(fp, "%lf, ", &test_data[i+test*k][j]) == EOF){
                        flag = 1;
                        break;
                    }

                }else{

                    if(fscanf(fp, "%lf\n", &test_data[i+test*k][j]) == EOF){
                        flag = 1;
                        break;
                    }

                }

            }

            /* 読み取るデータがなければ終了 */
            if(flag == 1){
                cout << "can't read file ." << endl;
                exit(1);
            }

        }

    }

    fclose(fp);
    /* Finish reading file */

    cout << "Finish reading file ." << endl;

}

重み係数を表示する関数

neural8.cpp
/* 重み係数の表示 */
void Multi_layer_perceptron::print_weight(void){

    cout << "\n--Weight1--" << endl;

    for(int i = 0; i < in+1; i++){
        for(int j = 0; j < out; j++) cout << weight1[i][j] << ", ";
        cout << endl;
    }

}

(main関数内)
train_sample(教師データの個数) = 120, test_sample(テスト用データの個数) = 30
in(入力層のノード数) = 4, out(出力層のノード数) = 2
*今回は隠れ層がないため、hide(隠れ層のノード数)は考えない
explanatory_variable(説明変数) = 4 (特徴量の数であり、入力層ノード数と同じ)
の宣言
繰り返し回数repeat = 500回、学習率eta = 0.1に設定

netというオブジェクトを生成
net.read_file() でアヤメのデータを読み取る
2重のforループの中で、それぞれデータごとに
出力値と教師データの誤差関数を計算 net.forward_propagation
その誤差関数をもとに誤差逆伝搬法により重み係数を修正 net.back_propagation
これを500回繰り返す

void forward_propagation(int i) はi番目の教師データ(train_data[i][])を代入して出力値を計算
void _propagation(int i) はi番目のデータの誤差関数から重み係数を修正

計算した重み係数をもとに
predict関数によりテスト用データを1つずつ代入し、結果を確認

neural8.cpp
/* calculation of forward propagation of 'i'th data */
/* i番目の入力データから出力値を計算 */
void Multi_layer_perceptron::forward_propagation(int i){

    int j, k;

    /* calculation of input layer 入力層の計算 */
    for(j = 0; j < in; j++){

        input1[j] = train_data[i][j];
        //output1[j] = sigmoid(input1[j]);
        output1[j] = input1[j];

    }

    input1[in] = bias;
    output1[in] = bias;

    /* calculation of output layer 出力層の計算 */
    for(j = 0; j < out; j++){

        input2[j] = 0.0;

        for(k = 0; k < in+1; k++) input2[j] += weight1[k][j] * output1[k];

        output2[j] = sigmoid(input2[j]);
        //output2[j] = input2[j];

    }

}

/* back propagation 誤差逆伝搬法 */
void Multi_layer_perceptron::back_propagation(int i, double eta){

    int j, k;

    for(k = 0; k < out; k++) answer[k] = train_data[i][variable+k];

    for(j = 0; j < in; j++){

        for(k = 0; k < out; k++){

            error2[k] = answer[k] - output2[k];
            delta2[k] = -error2[k] * output2[k] * (1.0 - output2[k]);
            weight1[j][k] += -eta * delta2[k] * output1[j];

        }

    }

}

/* テスト用データを計算する関数 */
void Multi_layer_perceptron::predict(int i){

    int j, k;

    /* calculation of input layer 入力層の計算 */
    for(j = 0; j < in; j++){

        input1[j] = test_data[i][j];
        //output1[j] = sigmoid(input1[j]);
        output1[j] = input1[j];

    }

    input1[in] = bias;
    output1[in] = bias;

    /* calculation of output layer 出力層の計算 */
    for(j = 0; j < out; j++){

        input2[j] = 0.0;

        for(k = 0; k < in+1; k++) input2[j] += weight1[k][j] * output1[k];

        output2[j] = sigmoid(input2[j]);
        //output2[j] = input2[j];

    }

    /* テスト用データの計算結果の表示 */
    printf("\nNo.%3d \n", i+1);

    for(j = 0; j < out; j++)
        printf("Output : %12.10lf (Ideal : %3.1lf )\n", output2[j], test_data[i][variable+j]);  

}

/* Main function */
int main(void){

    int i;
    /* 教師データの数・テスト用データの数 */
    int test_sample = 30, train_sample = 120;
    /* 読み込むファイル */
    char filename[30] = "iris3.csv";
    /* 各層のノード数 */
    int in = 4, hide = 2, out = 2;
    /* 説明変数 */
    int variable = 4;
    /* 種類 */
    int kind = 3;

    /* learning times 学習回数 */
    const int repeat = 500;
    /* learning rate 学習率 */
    double eta = 0.1;

    /* Making object named "net" for neural network */
    /* "net"というオブジェクト生成
     * このオブジェクト内でデータの読み込み・学習を行う */
    Multi_layer_perceptron net(in, hide, out, test_sample, train_sample);

    net.read_file(filename, variable, kind);

    cout << "Start neural network ." << endl;

    /* training 学習 */
    for(int time = 0; time < repeat; time++){

        /* i番目の教師データから重み係数を修正する */
        for(i = 0; i < train_sample; i++){

            /* forward propagation */
            net.forward_propagation(i);

            /* back propagation 誤差逆伝搬法 */
            net.back_propagation(i, eta);

        }

    }
    /* -- finish neural network -- */

    cout << "\nTest result .\n" << endl;

    /* display the result of the test テスト結果表示 */
    for(i = 0; i < test_sample; i++) net.predict(i);

    cout << "\n(Reference)" << endl;
    cout << "(1.0, 1.0) -- setosa" << endl;
    cout << "(1.0, 0.0) --versicolor" << endl;
    cout << "(0.0, 0.0) --virginica" << endl;

    net.print_weight();

    return(0);

}

最後にデストラクター
動的確保した配列のメモリをすべて開放

neural8.cpp
/* Destructor */
Multi_layer_perceptron::~Multi_layer_perceptron(){

    /* delete memories */
    delete[] input1;
    delete[] input2;
    delete[] output1;
    delete[] output2;
    delete[] error2;
    delete[] delta2;
    delete[] answer;

    for(int i = 0; i < in+1; i++) delete[] weight1[i];
    delete[] weight1;

    for(int i = 0; i < train_sample; i++) delete[] train_data[i];
    delete[] train_data;

    for(int i = 0; i < test_sample; i++) delete[] test_data[i];
    delete[] test_data;

}
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