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

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

![neural.png](https://qiita-image-store.s3.amazonaws.com/0/294396/fc765b28-3f1e-5bb3-5174-d1976144c536.png)


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

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

``` c++: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の値に設定。

```c++: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個)をそれぞれ格納。

```c++: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;

}
```
重み係数を表示する関数

```c++: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つずつ代入し、結果を確認

```c++: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);

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

```c++: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;

}
```