From 10500e3a5d7a18fd054eda9fe8e3275be6395715 Mon Sep 17 00:00:00 2001 From: CrystSw Date: Fri, 7 Jun 2019 19:20:04 +0900 Subject: [PATCH 1/5] Files were separated. --- src/header/mnist.h | 10 + src/{snnet.h => header/nnet.h} | 10 +- src/{snnet-types.h => header/nnet_types.h} | 5 + src/header/util.h | 16 ++ src/main.c | 137 +++-------- src/mnist.c | 127 ++++++++++ src/nnet.c | 130 ++++++++++ src/result/.gitkeep | 0 src/snnet.c | 270 --------------------- src/util.c | 216 +++++++++++++++++ 10 files changed, 546 insertions(+), 375 deletions(-) create mode 100644 src/header/mnist.h rename src/{snnet.h => header/nnet.h} (67%) rename src/{snnet-types.h => header/nnet_types.h} (88%) create mode 100644 src/header/util.h create mode 100644 src/mnist.c create mode 100644 src/nnet.c create mode 100644 src/result/.gitkeep delete mode 100644 src/snnet.c create mode 100644 src/util.c diff --git a/src/header/mnist.h b/src/header/mnist.h new file mode 100644 index 0000000..79b5d92 --- /dev/null +++ b/src/header/mnist.h @@ -0,0 +1,10 @@ +#ifndef __SNN_MNIST_H_INCLUDED__ +#define __SNN_MNIST_H_INCLUDED__ + +#include "nnet_types.h" + +extern FVALUE read_mnistimg(char *filename); +extern LABELOHV read_mnistlbl_ohv(char *filename); +extern LABEL read_mnistlbl(char *filename); + +#endif \ No newline at end of file diff --git a/src/snnet.h b/src/header/nnet.h similarity index 67% rename from src/snnet.h rename to src/header/nnet.h index 2c1e05a..7aabf63 100644 --- a/src/snnet.h +++ b/src/header/nnet.h @@ -1,17 +1,13 @@ -#ifndef __SIMPLE_NNET_H__ -#define __SIMPLE_NNET_H__ +#ifndef __SNN_NNET_H_INCLUDED__ +#define __SNN_NNET_H_INCLUDED__ -#include "snnet-types.h" +#include "nnet_types.h" -extern FVALUE read_mnistimg(char *filename); -extern LABEL read_mnistlbl(char *filename); -extern LABELOHV read_mnistlbl_ohv(char *filename); extern double softmax(const double *output, const int id, const int size); extern double cross_entropy(const double *rpp, const double *wpp, const int size); extern void nncout_train(const TRAINDATA td, double **weight, const double *bias, const int id, double *catout); extern void nncout_test(const TESTDATA td, double **weight, const double *bias, const int id, double *catout); extern void nncpp(const double *catout, const int size, double *catpp); extern double xentrloss(const TRAINDATA td, double **weight, const double *bias); -extern int getmax(const double *p, int size); #endif \ No newline at end of file diff --git a/src/snnet-types.h b/src/header/nnet_types.h similarity index 88% rename from src/snnet-types.h rename to src/header/nnet_types.h index 9274368..0305cf8 100644 --- a/src/snnet-types.h +++ b/src/header/nnet_types.h @@ -1,3 +1,6 @@ +#ifndef __SNN_NNET_TYPES_H_INCLUDED__ +#define __SNN_NNET_TYPES_H_INCLUDED__ + typedef unsigned char u_char; typedef struct { @@ -29,3 +32,5 @@ typedef struct{ FVALUE fval; LABEL label; } TESTDATA; + +#endif \ No newline at end of file diff --git a/src/header/util.h b/src/header/util.h new file mode 100644 index 0000000..e76a4fd --- /dev/null +++ b/src/header/util.h @@ -0,0 +1,16 @@ +#ifndef __SNN_UTIL_H_INCLUDED__ +#define __SNN_UTIL_H_INCLUDED__ + +extern double *malloc_d1(const int size1); +extern double *calloc_d1(const int size1); +extern double **malloc_d2(const int size1, const int size2); +extern double **calloc_d2(const int size1, const int size2); +extern void free_d1(double *array); +extern void free_d2(double **array, const int size1); +extern int getmax(const double *array, int size1); +extern void writefile_d1(const char *filename, double *array_d1, const int size1); +extern void readfile_d1(const char *filename, double *array_d1, const int size1); +extern void writefile_d2(const char *filename, double **array_d2, const int size1, const int size2); +extern void readfile_d2(const char *filename, double **array_d2, const int size1, const int size2); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 2bcf775..ac6520c 100644 --- a/src/main.c +++ b/src/main.c @@ -2,19 +2,20 @@ #include #include -#include "snnet.h" +#include "header/nnet.h" +#include "header/util.h" +#include "header/mnist.h" #define LOSS_CALC_RATE 5000 -//#define OUTPUT_IMAGE void learn(void); void test(void); int main(int argc, char *argv[]){ if(argc > 1){ - if(!strcmp(argv[1],"learn")){ + if(!strcmp(argv[1],"-l")){ learn(); - }else if(!strcmp(argv[1],"test")){ + }else if(!strcmp(argv[1],"-t")){ test(); } } @@ -39,20 +40,15 @@ void learn(void){ double alpha = 0.0000001; //重みとバイアスの宣言(callocで確保します) - double **weight, *bias; - weight = (double**)calloc(td.label.size, sizeof(double*)); - for(j = 0; j < td.label.size; ++j){ - weight[j] = (double*)calloc(td.fval.size, sizeof(double)); - } - bias = (double*)calloc(td.label.size, sizeof(double)); + double **weight = calloc_d2(td.label.size, td.fval.size); + double *bias = calloc_d1(td.label.size); //学習前のクロスエントロピーの総和 printf("Data: %6d, Loss: %.8f\n", 0, xentrloss(td, weight, bias)); //カテゴリ出力とカテゴリ事後確率の宣言 - double *catout, *catpp; - catout = (double*)malloc(sizeof(double)*td.label.size); - catpp = (double*)malloc(sizeof(double)*td.label.size); + double *catout = malloc_d1(td.label.size); + double *catpp = malloc_d1(td.label.size); //学習 for(i = 0; i < td.fval.num; ++i){ @@ -69,41 +65,19 @@ void learn(void){ //バイアスの更新 bias[j] += alpha*(td.label.data[i][j]-catpp[j]); } - //クロスエントロピーの総和 + //クロスエントロピー誤差(計算負荷の高い処理なので,実行回数は少なめ) if((i+1) % LOSS_CALC_RATE == 0) printf("Data: %6d, Loss: %.8f\n", i+1, xentrloss(td, weight, bias)); } - free(catout); - free(catpp); + free_d1(catout); + free_d1(catpp); //重みの書き出し - FILE *wwp; - if((wwp = fopen("weight-value","w")) != NULL){ - for(i = 0; i < td.label.size; ++i){ - for(j = 0; j < td.fval.size; ++j){ - fprintf(wwp, "%.20f\n", weight[i][j]); - } - } - }else{ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - fclose(wwp); - + writefile_d2("./result/weight-value", weight, td.label.size, td.fval.size); //バイアスの書き出し - FILE *wbp; - if((wbp = fopen("bias-value","w")) != NULL){ - for(i = 0; i < td.label.size; ++i){ - fprintf(wbp, "%.20f\n", bias[i]); - } - }else{ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - fclose(wbp); + writefile_d1("./result/bias-value", bias, td.label.size); - for(i = 0; i < td.label.size; ++i) free(weight[i]); - free(weight); - free(bias); + free_d2(weight, td.label.size); + free_d1(bias); } void test(void){ @@ -117,50 +91,20 @@ void test(void){ puts("Label data has loaded."); //重みとバイアスの宣言(callocで確保します) - double **weight, *bias; - weight = (double**)calloc(td.label.size, sizeof(double*)); - for(j = 0; j < td.label.size; ++j){ - weight[j] = (double*)calloc(td.fval.size, sizeof(double)); - } - bias = (double*)calloc(td.label.size, sizeof(double)); + double **weight = calloc_d2(td.label.size, td.fval.size); + double *bias = calloc_d1(td.label.size); //重みの読み込み - FILE *rwp; - if((rwp = fopen("weight-value","r")) != NULL){ - for(i = 0; i < td.label.size; ++i){ - for(j = 0; j < td.fval.size; ++j){ - fscanf(rwp, "%lf", &weight[i][j]); - } - } - }else{ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - fclose(rwp); - + readfile_d2("./result/weight-value", weight, td.label.size, td.fval.size); //バイアスの読み込み - FILE *rbp; - if((rbp = fopen("bias-value","r")) != NULL){ - for(i = 0; i < td.label.size; ++i){ - fscanf(rbp, "%lf", &bias[i]); - } - }else{ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - fclose(rbp); + readfile_d1("./result/bias-value", bias, td.label.size); //混同行列 - double **confmat; - confmat = (double**)calloc(td.label.size, sizeof(double*)); - for(j = 0; j < td.label.size; ++j){ - confmat[j] = (double*)calloc(td.fval.size, sizeof(double)); - } + double **confmat = calloc_d2(td.label.size, td.fval.size); //カテゴリ出力とカテゴリ事後確率の宣言 - double *catout, *catpp; - catout = (double*)malloc(sizeof(double)*td.label.size); - catpp = (double*)malloc(sizeof(double)*td.label.size); + double *catout = malloc_d1(td.label.size); + double *catpp = malloc_d1(td.label.size); //カテゴリ分類 int m_category; @@ -171,21 +115,13 @@ void test(void){ nncpp(catout, td.label.size, catpp); //事後確率が最大であるカテゴリを導出 m_category = getmax(catpp, td.label.size); + //分類結果を格納(混同行列の計算に利用) confmat[td.label.data[i]][m_category] += 1; - printf("[Data:%d]right:%d predict:%d(pp:%.1f)\n", i+1, td.label.data[i], m_category, catpp[m_category]*100); -#ifdef OUTPUT_IMAGE - //誤った画像の表示 - if(td.label.data[i] != m_category){ - puts("-----Image-----"); - for(j = 0; j < td.fval.size; ++j){ - putchar(td.fval.data[i][j] < 128 ? '-' : '#'); - if((j+1) % td.fval.c_size == 0) putchar('\n'); - } - } -#endif + //分類結果を出力 + printf("[Data:%d]truth:%d predict:%d(pp:%.1f)\n", i+1, td.label.data[i], m_category, catpp[m_category]*100); } - free(catout); - free(catpp); + free_d1(catout); + free_d1(catpp); //混同行列の計算 int cat_num; @@ -194,7 +130,6 @@ void test(void){ for(j = 0; j < td.label.size; ++j) cat_num += confmat[i][j]; for(j = 0; j < td.label.size; ++j) confmat[i][j] /= cat_num; } - //混同行列の出力 puts("-----Confusion Matrix-----"); printf(" "); @@ -208,10 +143,16 @@ void test(void){ putchar('\n'); } - for(i = 0; i < td.label.size; ++i) free(weight[i]); - free(weight); - free(bias); + //正解率の出力 + puts("-----Accuracy Rate-----"); + double arate = 0; + for(i = 0; i < td.label.size; ++i){ + arate += confmat[i][i]; + } + arate /= td.label.size; + printf("%3.1f\n", arate*100); - for(i = 0; i < td.label.size; ++i) free(confmat[i]); - free(confmat); + free_d2(weight, td.label.size); + free_d1(bias); + free_d2(confmat, td.label.size); } \ No newline at end of file diff --git a/src/mnist.c b/src/mnist.c new file mode 100644 index 0000000..8a40875 --- /dev/null +++ b/src/mnist.c @@ -0,0 +1,127 @@ +#include +#include + +#include "header/nnet_types.h" + +/** + * mnistの画像データをロードする. + * + * @param filename - ファイル名 + * @return - 特徴量データ + */ +FVALUE read_mnistimg(char *filename) { + int i, j; + + FVALUE fval; + FILE *mnistimg; + //画像データのロード + if((mnistimg = fopen(filename, "rb")) == NULL){ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + //ヘッダ部の読み込み + u_char header[16]; + if(fread(header, sizeof(u_char), 16, mnistimg) != 16){ + fprintf(stderr, "Error: Invalid file format.\n"); + exit(1); + } + fval.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; + fval.r_size = ((header[8] << 24) + (header[9] << 16) + (header[10] << 8) + header[11]); + fval.c_size = ((header[12] << 24) + (header[13] << 16) + (header[14] << 8) + header[15]); + fval.size = fval.r_size*fval.c_size; + //画像データの読み込み + fval.data = (u_char**)malloc(sizeof(u_char*)*fval.num); + for(i = 0; i < fval.num; ++i){ + fval.data[i] = (u_char*)malloc(sizeof(u_char)*fval.size); + for(j = 0; j < fval.size; ++j){ + if(fread(&fval.data[i][j], sizeof(u_char), 1, mnistimg) != 1){ + fprintf(stderr, "Error: Invalid file format."); + exit(1); + } + } + } + fclose(mnistimg); + + return fval; +} + +/** + * mnistのラベルデータをロードする. + * この関数はラベルデータをone-hot-vector形式で読み込む. + * + * @param filename - ファイル名 + * @return - ラベルデータ(one-hot-vector形式) + */ +LABELOHV read_mnistlbl_ohv(char *filename) { + int i, j; + + LABELOHV label; + FILE *mnistlbl; + //ラベルデータのロード + if((mnistlbl = fopen(filename, "rb")) == NULL){ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + //ヘッダ部の読み込み + u_char header[8]; + if(fread(header, sizeof(u_char), 8, mnistlbl) != 8){ + fprintf(stderr, "Error: Invalid file format.\n"); + exit(1); + } + label.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; + label.size = 10; //カテゴリ数 + //ラベルデータの読み込み + label.data = (double**)malloc(sizeof(double*)*label.num); + //one-hot-vector形式へ変換 + u_char category; + for(i = 0; i < label.num; ++i){ + label.data[i] = (double*)malloc(sizeof(double)*label.size); + if(fread(&category, sizeof(u_char), 1, mnistlbl) != 1){ + fprintf(stderr, "Error: Invalid file format."); + exit(1); + } + for(j = 0; j < label.size; ++j){ + label.data[i][j] = (double)(j == category ? 1 : 0); + } + } + fclose(mnistlbl); + + return label; +} + +/** + * mnistのラベルデータをロードする. + * + * @param filename - ファイル名 + * @return - ラベルデータ + */ +LABEL read_mnistlbl(char *filename) { + int i; + + LABEL label; + FILE *mnistlbl; + //ラベルデータのロード + if((mnistlbl = fopen(filename, "rb")) == NULL){ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + //ヘッダ部の読み込み + u_char header[8]; + if(fread(header, sizeof(u_char), 8, mnistlbl) != 8){ + fprintf(stderr, "Error: Invalid file format.\n"); + exit(1); + } + label.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; + label.size = 10; + //ラベルデータの読み込み + label.data = (u_char*)malloc(sizeof(u_char)*label.num); + for(i = 0; i < label.num; ++i){ + if(fread(&label.data[i], sizeof(u_char), 1, mnistlbl) != 1){ + fprintf(stderr, "Error: Invalid file format."); + exit(1); + } + } + fclose(mnistlbl); + + return label; +} \ No newline at end of file diff --git a/src/nnet.c b/src/nnet.c new file mode 100644 index 0000000..27f4a1d --- /dev/null +++ b/src/nnet.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#include "header/util.h" +#include "header/nnet_types.h" + +/** + * ソフトマックス値を計算する. + * + * @param catout - ソフトマックス値の計算に用いる数値が格納された配列 + * @param id - ソフトマックス値を計算したい数値のインデックス + * @param size - 要素数 + * @return - ソフトマックス値 + */ +double softmax(const double *output, const int id, const int size){ + int i; + + double denom = 0.0; + for(i = 0; i < size; ++i){ + denom += exp(output[i]); + } + return exp(output[id]) / denom; +} + +/** + * クロスエントロピー値を計算する. + * + * @param rpp - 真の確率分布 + * @param wpp - 誤った確率分布 + * @param size - 要素数 + * @return - クロスエントロピー値 + */ +double cross_entropy(const double *rpp, const double *wpp, const int size){ + int i; + + double cross_emt = 0.0; + for(i = 0; i < size; ++i){ + //カテゴリ事後確率が0の場合,log0が計算できないので,double型の最小値dbl_min(>0)で計算 + cross_emt += -rpp[i]*log((wpp[i] != 0 ? wpp[i] : DBL_MIN)); + } + return cross_emt; +} + +/** + * ある特徴量を入力した際のニューラルネットワークの出力を計算する.(教師データ用) + * + * @param td - 教師データ + * @param weight - 重みデータ + * @param bias - バイアスデータ + * @param id - 計算に用いる教師データのインデックス + * @param catout - 各ニューロンの出力が格納された配列(出力) + */ +void nncout_train(const TRAINDATA td, double **weight, const double *bias, const int id, double *catout){ + int i, j; + + for(i = 0; i < td.label.size; ++i){ + catout[i] = 0; + for(j = 0; j < td.fval.size; ++j){ + catout[i] += weight[i][j]*td.fval.data[id][j]; + } + catout[i] += bias[i]; + } +} + +/** + * ある特徴量を入力した際のニューラルネットワークの出力を計算する.(テストデータ用) + * + * @param td - テストデータ + * @param weight - 重みデータ + * @param bias - バイアスデータ + * @param id - 計算に用いる教師データのインデックス + * @param catout - 各ニューロンの出力が格納された配列(出力) + */ +void nncout_test(const TESTDATA td, double **weight, const double *bias, const int id, double *catout){ + int i, j; + + for(i = 0; i < td.label.size; ++i){ + catout[i] = 0; + for(j = 0; j < td.fval.size; ++j){ + catout[i] += weight[i][j]*td.fval.data[id][j]; + } + catout[i] += bias[i]; + } +} + +/** + * ニューラルネットワークの出力を基にカテゴリ事後確率の計算を行う. + * この関数の処理結果として得られたポインタは,必ずfree関数を用いて解放してください. + * + * @param catout - 各ニューロンの出力が格納された配列 + * @param size - カテゴリ数 + * @param catcpp - カテゴリ事後確率各が格納された配列(出力) + */ +void nncpp(const double *catout, const int size, double *catpp){ + int i; + + for(i = 0; i < size; ++i){ + catpp[i] = softmax(catout, i, size); + } +} + +/** + * クロスエントロピーの総和を計算する. + * + * @param td - 教師データ + * @param weight - 重みデータ + * @param bias - バイアスデータ + * @return - クロスエントロピーの総和 + */ +double xentrloss(const TRAINDATA td, double **weight, const double *bias){ + int i, j, k; + + double loss = 0; + double *catout, *catpp; + catout = malloc_d1(td.label.size); + catpp = malloc_d1(td.label.size); + + for(i = 0; i < td.fval.num; ++i){ + //ニューラルネットワークの出力を計算 + nncout_train(td, weight, bias, i, catout); + //カテゴリ事後確率の導出 + nncpp(catout, td.label.size, catpp); + //クロスエントロピーの計算 + loss += cross_entropy(td.label.data[i], catpp, td.label.size); + } + free_d1(catout); + free_d1(catpp); + return loss; +} diff --git a/src/result/.gitkeep b/src/result/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/snnet.c b/src/snnet.c deleted file mode 100644 index a37a35e..0000000 --- a/src/snnet.c +++ /dev/null @@ -1,270 +0,0 @@ -#include -#include -#include -#include - -#include "snnet-types.h" - -/** - * mnistの画像データをロードする. - * - * @param filename - ファイル名 - * @return - 特徴量データ - */ -FVALUE read_mnistimg(char *filename) { - int i, j; - - FVALUE fval; - FILE *mnistimg; - //画像データのロード - if((mnistimg = fopen(filename, "rb")) == NULL){ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - //ヘッダ部の読み込み - u_char header[16]; - if(fread(header, sizeof(u_char), 16, mnistimg) != 16){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - fval.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; - fval.r_size = ((header[8] << 24) + (header[9] << 16) + (header[10] << 8) + header[11]); - fval.c_size = ((header[12] << 24) + (header[13] << 16) + (header[14] << 8) + header[15]); - fval.size = fval.r_size*fval.c_size; - //画像データの読み込み - fval.data = (u_char**)malloc(sizeof(u_char*)*fval.num); - for(i = 0; i < fval.num; ++i){ - fval.data[i] = (u_char*)malloc(sizeof(u_char)*fval.size); - for(j = 0; j < fval.size; ++j){ - if(fread(&fval.data[i][j], sizeof(u_char), 1, mnistimg) != 1){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - } - } - fclose(mnistimg); - - return fval; -} - -/** - * mnistのラベルデータをロードする. - * この関数はラベルデータをone-hot-vector形式で読み込む. - * - * @param filename - ファイル名 - * @return - ラベルデータ(one-hot-vector形式) - */ -LABELOHV read_mnistlbl_ohv(char *filename) { - int i, j; - - LABELOHV label; - FILE *mnistlbl; - //ラベルデータのロード - if((mnistlbl = fopen(filename, "rb")) == NULL){ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - //ヘッダ部の読み込み - u_char header[8]; - if(fread(header, sizeof(u_char), 8, mnistlbl) != 8){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - label.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; - label.size = 10; //カテゴリ数 - //ラベルデータの読み込み - label.data = (double**)malloc(sizeof(double*)*label.num); - //one-hot-vector形式へ変換 - u_char category; - for(i = 0; i < label.num; ++i){ - label.data[i] = (double*)malloc(sizeof(double)*label.size); - if(fread(&category, sizeof(u_char), 1, mnistlbl) != 1){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - for(j = 0; j < label.size; ++j){ - label.data[i][j] = (double)(j == category ? 1 : 0); - } - } - fclose(mnistlbl); - - return label; -} - -/** - * mnistのラベルデータをロードする. - * - * @param filename - ファイル名 - * @return - ラベルデータ - */ -LABEL read_mnistlbl(char *filename) { - int i; - - LABEL label; - FILE *mnistlbl; - //ラベルデータのロード - if((mnistlbl = fopen(filename, "rb")) == NULL){ - fprintf(stderr, "Error: Cannot open file."); - exit(1); - } - //ヘッダ部の読み込み - u_char header[8]; - if(fread(header, sizeof(u_char), 8, mnistlbl) != 8){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - label.num = (header[4] << 24) + (header[5] << 16) + (header[6] << 8) + header[7]; - label.size = 10; - //ラベルデータの読み込み - label.data = (u_char*)malloc(sizeof(u_char)*label.num); - for(i = 0; i < label.num; ++i){ - if(fread(&label.data[i], sizeof(u_char), 1, mnistlbl) != 1){ - fprintf(stderr, "Error: Invalid file format."); - exit(1); - } - } - fclose(mnistlbl); - - return label; -} - -/** - * ソフトマックス値を計算する. - * - * @param catout - ソフトマックス値の計算に用いる数値が格納された配列 - * @param id - ソフトマックス値を計算したい数値のインデックス - * @param size - 要素数 - * @return - ソフトマックス値 - */ -double softmax(const double *output, const int id, const int size){ - int i; - - double denom = 0.0; - for(i = 0; i < size; ++i){ - denom += exp(output[i]); - } - return exp(output[id]) / denom; -} - -/** - * クロスエントロピー値を計算する. - * - * @param rpp - 真の確率分布 - * @param wpp - 誤った確率分布 - * @param size - 要素数 - * @return - クロスエントロピー値 - */ -double cross_entropy(const double *rpp, const double *wpp, const int size){ - int i; - - double cross_emt = 0.0; - for(i = 0; i < size; ++i){ - //カテゴリ事後確率が0の場合,log0が計算できないので,double型の最小値dbl_min(>0)で計算 - cross_emt += -rpp[i]*log((wpp[i] != 0 ? wpp[i] : DBL_MIN)); - } - return cross_emt; -} - -/** - * 配列内で最も値の大きい要素のインデックスを取得する. - * - * @param p - 配列 - * @param size - 要素数 - * @return - インデックス - */ -int getmax(const double *p, int size){ - int i; - - int max = 0; - for(i = 1; i < size; ++i){ - if(p[i] > p[max]) max = i; - } - return max; -} - -/** - * ある特徴量を入力した際のニューラルネットワークの出力を計算する.(教師データ用) - * - * @param td - 教師データ - * @param weight - 重みデータ - * @param bias - バイアスデータ - * @param id - 計算に用いる教師データのインデックス - * @param catout - 各ニューロンの出力が格納された配列(出力) - */ -void nncout_train(const TRAINDATA td, double **weight, const double *bias, const int id, double *catout){ - int i, j; - - for(i = 0; i < td.label.size; ++i){ - catout[i] = 0; - for(j = 0; j < td.fval.size; ++j){ - catout[i] += weight[i][j]*td.fval.data[id][j]; - } - catout[i] += bias[i]; - } -} - -/** - * ある特徴量を入力した際のニューラルネットワークの出力を計算する.(テストデータ用) - * - * @param td - テストデータ - * @param weight - 重みデータ - * @param bias - バイアスデータ - * @param id - 計算に用いる教師データのインデックス - * @param catout - 各ニューロンの出力が格納された配列(出力) - */ -void nncout_test(const TESTDATA td, double **weight, const double *bias, const int id, double *catout){ - int i, j; - - for(i = 0; i < td.label.size; ++i){ - catout[i] = 0; - for(j = 0; j < td.fval.size; ++j){ - catout[i] += weight[i][j]*td.fval.data[id][j]; - } - catout[i] += bias[i]; - } -} - -/** - * ニューラルネットワークの出力を基にカテゴリ事後確率の計算を行う. - * この関数の処理結果として得られたポインタは,必ずfree関数を用いて解放してください. - * - * @param catout - 各ニューロンの出力が格納された配列 - * @param size - カテゴリ数 - * @param catcpp - カテゴリ事後確率各が格納された配列(出力) - */ -void nncpp(const double *catout, const int size, double *catpp){ - int i; - - for(i = 0; i < size; ++i){ - catpp[i] = softmax(catout, i, size); - } -} - -/** - * クロスエントロピーの総和を計算する. - * - * @param td - 教師データ - * @param weight - 重みデータ - * @param bias - バイアスデータ - * @return - クロスエントロピーの総和 - */ -double xentrloss(const TRAINDATA td, double **weight, const double *bias){ - int i, j, k; - - double loss = 0; - double *catout, *catpp; - catout = (double*)malloc(sizeof(double)*td.label.size); - catpp = (double*)malloc(sizeof(double)*td.label.size); - - for(i = 0; i < td.fval.num; ++i){ - //ニューラルネットワークの出力を計算 - nncout_train(td, weight, bias, i, catout); - //カテゴリ事後確率の導出 - nncpp(catout, td.label.size, catpp); - //クロスエントロピーの計算 - loss += cross_entropy(td.label.data[i], catpp, td.label.size); - } - free(catout); - free(catpp); - return loss; -} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..3137f15 --- /dev/null +++ b/src/util.c @@ -0,0 +1,216 @@ +#include +#include + +/** + * 不定長1次元配列の確保(double) + * malloc版 + * + * @param size1 - 1次元目の要素数 + * @return - 確保した領域へのポインタ + */ +double *malloc_d1(const int size1){ + int i; + + double *pointer; + pointer = (double*)malloc(size1*sizeof(double)); + return pointer; +} + + +/** + * 不定長1次元配列の確保(double) + * calloc版 + * + * @param size1 - 1次元目の要素数 + * @return - 確保した領域へのポインタ + */ +double *calloc_d1(const int size1){ + int i; + + double *pointer; + pointer = (double*)calloc(size1, sizeof(double)); + return pointer; +} + + +/** + * 不定長2次元配列の確保(double) + * malloc版 + * + * @param size1 - 1次元目の要素数 + * @param size2 - 2次元目の要素数 + * @return - 確保した領域へのポインタ + */ +double **malloc_d2(const int size1, const int size2){ + int i; + + double **pointer; + pointer = (double**)malloc(size1*sizeof(double*)); + for(i = 0; i < size1; ++i){ + pointer[i] = (double*)malloc(size2*sizeof(double)); + } + return pointer; +} + + +/** + * 不定長2次元配列の確保(double) + * calloc版 + * + * @param size1 - 1次元目の要素数 + * @param size2 - 2次元目の要素数 + * @return - 確保した領域へのポインタ + */ +double **calloc_d2(const int size1, const int size2){ + int i; + + double **pointer; + pointer = (double**)calloc(size1, sizeof(double*)); + for(i = 0; i < size1; ++i){ + pointer[i] = (double*)calloc(size2, sizeof(double)); + } + return pointer; +} + + +/** + * 不定長1次元配列の解放(double) + * プログラム中の関数名の統一のために用意しています. + * 内容はfree()を利用した場合と同じです. + * + * @param array - 1次元配列のポインタ + */ +void free_d1(double *array){ + free(array); +} + + +/** + * 不定長2次元配列の解放(double) + * + * @param array - 2次元配列のポインタ + * @param size1 - 1次元目の要素数 + */ +void free_d2(double **array, const int size1){ + int i; + + for(i = 0; i < size1; ++i){ + free(array[i]); + } + free(array); +} + + +/** + * 配列内で最も値の大きい要素のインデックスを取得する(double) + * + * @param array - 1次元配列のポインタ + * @param size1 - 1次元目の要素数 + * @return - インデックス + */ +int getmax(const double *array, int size1){ + int i; + + int max = 0; + for(i = 1; i < size1; ++i){ + if(array[i] > array[max]) max = i; + } + return max; +} + + +/** + * 1次元配列をファイルへ出力する(double) + * + * @param filename - ファイル名 + * @param array_d1 - 1次元配列のポインタ + * @param size1 - 1次元目の要素数 + */ +void writefile_d1(const char *filename, const double *array_d1, const int size1){ + int i; + + FILE *wfp; + if((wfp = fopen(filename,"w")) != NULL){ + for(i = 1; i < size1; ++i){ + fprintf(wfp, "%.15f\n", array_d1[i]); + } + }else{ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + fclose(wfp); +} + + +/** + * ファイルから1次元配列をロードする(double) + * + * @param filename - ファイル名 + * @param array_d1 - 1次元配列のポインタ + * @param size1 - 1次元目の要素数 + */ +void readfile_d1(const char *filename, double *array_d1, const int size1){ + int i; + + FILE *rfp; + if((rfp = fopen(filename,"r")) != NULL){ + for(i = 0; i < size1; ++i){ + fscanf(rfp, "%lf", &array_d1[i]); + } + }else{ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + fclose(rfp); +} + + +/** + * 2次元配列をファイルへ出力する(double) + * + * @param filename - ファイル名 + * @param array_d2 - 2次元配列のポインタ + * @param size1 - 1次元目の要素数 + * @param size2 - 2次元目の要素数 + */ +void writefile_d2(const char *filename, const double **array_d2, const int size1, const int size2){ + int i, j; + + FILE *wfp; + if((wfp = fopen(filename,"w")) != NULL){ + for(i = 0; i < size1; ++i){ + for(j = 0; j < size2; ++j){ + fprintf(wfp, "%.15f\n", array_d2[i][j]); + } + } + }else{ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + fclose(wfp); +} + + +/** + * ファイルから2次元配列をロードする(double) + * + * @param filename - ファイル名 + * @param array_d2 - 2次元配列のポインタ + * @param size1 - 1次元目の要素数 + */ +void readfile_d2(const char *filename, double **array_d2, const int size1, const int size2){ + int i, j; + + FILE *rfp; + if((rfp = fopen(filename,"r")) != NULL){ + for(i = 0; i < size1; ++i){ + for(j = 0; j < size2; ++j){ + fscanf(rfp, "%lf", &array_d2[i][j]); + } + } + }else{ + fprintf(stderr, "Error: Cannot open file.\n"); + exit(1); + } + fclose(rfp); +} \ No newline at end of file From 45c42b0bc9f351271d5e71e4e36166055bf285ef Mon Sep 17 00:00:00 2001 From: CrystSw Date: Fri, 7 Jun 2019 19:29:23 +0900 Subject: [PATCH 2/5] Updated Readme.md. --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63f05a3..02b731d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Simple-Neural-Network +# Simple-Neural-Network ## 概要 Sample of 2-layer categorization-neural-network(CNN). This CNN consists of 2-layer(input-layer, output-layer). not exist hidden-layer. @@ -17,6 +17,7 @@ This CNN consists of 2-layer(input-layer, output-layer). not exist hidden-layer. の4つのファイルをダウンロードおよび解凍し,src/mnistの中へ入れてください. Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行っています. +###ver1.1以前(旧バージョン) コンパイルを行うには次のコマンドを実行します. ```gcc -lm snnet.c main.c -o snnet``` 最適化オプションを付けたほうが,動作は早くなります. @@ -26,6 +27,16 @@ Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行ってい テストデータを基に識別を行うには次のコマンドを実行します. ```./snnet test``` +###ver1.2以降(新バージョン) +コンパイルを行うには次のコマンドを実行します. +```gcc -lm nnet.c util.c mnist.c main.c -o snnet``` +最適化オプションを付けたほうが,動作は早くなります. + +教師データを基に学習を行うには次のコマンドを実行します. +```./snnet -l``` + +テストデータを基に識別を行うには次のコマンドを実行します. +```./snnet -t``` --- ## 注意事項 From fb2654e3062d220a905cb4cfa95017c98da89192 Mon Sep 17 00:00:00 2001 From: CrystSw Date: Fri, 7 Jun 2019 19:31:12 +0900 Subject: [PATCH 3/5] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02b731d..861d06e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This CNN consists of 2-layer(input-layer, output-layer). not exist hidden-layer. の4つのファイルをダウンロードおよび解凍し,src/mnistの中へ入れてください. Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行っています. -###ver1.1以前(旧バージョン) +**ver1.1以前(旧バージョン)** コンパイルを行うには次のコマンドを実行します. ```gcc -lm snnet.c main.c -o snnet``` 最適化オプションを付けたほうが,動作は早くなります. @@ -26,8 +26,8 @@ Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行ってい ```./snnet learn``` テストデータを基に識別を行うには次のコマンドを実行します. -```./snnet test``` -###ver1.2以降(新バージョン) +```./snnet test``` +**ver1.2以降(新バージョン)** コンパイルを行うには次のコマンドを実行します. ```gcc -lm nnet.c util.c mnist.c main.c -o snnet``` 最適化オプションを付けたほうが,動作は早くなります. From 7ba80d9a356c14c044b4857f7b95b812a36f35d7 Mon Sep 17 00:00:00 2001 From: CrystSw Date: Fri, 7 Jun 2019 19:32:12 +0900 Subject: [PATCH 4/5] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 861d06e..f976dd0 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,9 @@ This CNN consists of 2-layer(input-layer, output-layer). not exist hidden-layer. の4つのファイルをダウンロードおよび解凍し,src/mnistの中へ入れてください. -Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行っています. -**ver1.1以前(旧バージョン)** +Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行っています. + +**◎ver1.1以前(旧バージョン)** コンパイルを行うには次のコマンドを実行します. ```gcc -lm snnet.c main.c -o snnet``` 最適化オプションを付けたほうが,動作は早くなります. @@ -27,7 +28,8 @@ Windows10 64bit(gcc 6.3.0)とCentOS6.10(gcc 4.4.7)で動作確認を行ってい テストデータを基に識別を行うには次のコマンドを実行します. ```./snnet test``` -**ver1.2以降(新バージョン)** + +**◎ver1.2以降(新バージョン)** コンパイルを行うには次のコマンドを実行します. ```gcc -lm nnet.c util.c mnist.c main.c -o snnet``` 最適化オプションを付けたほうが,動作は早くなります. From 2cb4f6ef8169ca279f1040cc1f2e32486cdd6440 Mon Sep 17 00:00:00 2001 From: CrystSw Date: Sat, 8 Jun 2019 18:49:15 +0900 Subject: [PATCH 5/5] Update main.c --- src/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index ac6520c..381a342 100644 --- a/src/main.c +++ b/src/main.c @@ -80,6 +80,10 @@ void learn(void){ free_d1(bias); } +/** + * 学習したニューラルネットワークを用いてテストデータの分類を行う. + * 結果は混同行列として出力される. + */ void test(void){ int i, j, k; @@ -155,4 +159,4 @@ void test(void){ free_d2(weight, td.label.size); free_d1(bias); free_d2(confmat, td.label.size); -} \ No newline at end of file +}