目的
ニューラルネットワーク(誤差逆伝搬法)でアヤメの種類(setosa, versicolor, virginica)を自動判定する。
検証方法
- データセット
- 各種類50個ずつ (合計 50*3種類=150)
- (このデータセットをどこから持ってきたか忘れてしまった)
- 4つの特徴量
- Sepal Length (がく片の長さ)
- Sepal Width (がく片の幅)
- Petal Length (花びらの長さ)
- Petal Width (花びらの幅)
- 左からSepal Length, Sepal Width, Petal Length, Petal Width, 種類名
- 各種類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
...
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
- 学習方法
- このデータを学習用とテスト用に分ける
- 学習用データを用いて各ノード間の重み係数を適切な値に修正する。つまりパーセプトロンを学習させる
- 学習させたパーセプトロンにテスト用データを入力してその出力が予想(正解)と正しいかを確認する
- 今回は入力層と出力層のみの2層
- 隠れ層を入れると判定の精度が下がってしまった(現時点で原因不明)
- 入力層のノード数は4つ(=特徴量の数)
- 出力層のノード数は2つ。出力値は以下のように対応させる
- setosa : (0.0, 0.0)
- versicolor : (0.0, 1.0)
- virginica : (1.0, 1.0)
実装
パラメータ設定
- 諸々のパラメータをマクロ定義で設定
/* 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...学習率。この値にしたのは特に根拠がない。
- LEARNING_DATANUM...学習用データの個数。上記では120個としている。
学習を始める前の実装
- 学習を始める前までのフローチャート
- データを格納する構造体
- 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,
課題
- 隠れ層を入れたときにうまくいかない原因調査とその対処法
- 出力値を数値に置き換える方法を変えてみてトライする
