与えられたデータセットと評価値に近づくように重みとしきい値を
バックプロパゲーションで調整する3層パーセプトロン

/**
 * バックプロパゲーションによるニューラルネットの学習
 * 使い方
 *  $./backPropagation (変数の数) (中間層のセル数) (誤差の上限値) < (学習データセットのファイル名)
 *誤差の推移や、学習結果となる結合係数などを出力します
 **/

#include <iostream>
#include <vector>
#include <cstdlib>
#include <float.h>
#include <ctime>
#include <cmath>
using namespace std;

inline void initRand()
{
    srand((unsigned int)time(NULL));
}

/**シグモイド関数**/
inline double sigmoid(double u)
{
    return 1.0 / (1.0+exp(-u));
}

/**-1.0 - 1.0の実数乱数の生成**/
inline double randDouble()
{
    return (2.0 / RAND_MAX * rand())-1.0;
}

/**中間層の重みと出力層の重みを初期化(ランダム)**/
void initWeight(const int &n, const int &numAlliance,
                double **weightAlliance,  double *weightReaction)
{
    initRand();
    for(int i=0; i<numAlliance; ++i){
        for(int j=0; j<n+1; ++j){
            weightAlliance[i][j] = randDouble();
        }
        weightReaction[i] = randDouble();
    }
    weightReaction[numAlliance] = randDouble();
}

/**学習データをdataに読み込む**/
void readData(vector< vector<double> > &data, const int &n)
{
    // [n]は評価値
    double *buffer = new double [n+1];
    while(true){
        for(int i=0; i<n+1; ++i) cin >> buffer[i];
        if(cin.eof()) break;

        vector<double> bufferVector;
        for(int i=0; i<n+1; ++i) bufferVector.push_back(buffer[i]);

        data.push_back(bufferVector);
    }
}

/**順方向の出力の計算**/
double forward(double **weightAlliance, const double *weightReaction,
                double *outAlliance, const vector<double> &dataField,
                const int &n, const int &numAlliance)
{
    double out = 0.0;
    // outAllianceの計算
    for(int i=0; i<numAlliance; ++i){
        double u = 0; // 重み付き和
        for(int j=0; j<n; ++j){
            u += dataField[j] * weightAlliance[i][j];
        }
        u -= weightAlliance[i][n]; // しきい値の処理
        outAlliance[i] = sigmoid(u);
    }
    // outの計算
    for(int i=0; i<numAlliance; ++i)
        out += outAlliance[i] * weightReaction[i];
    out -= weightReaction[numAlliance]; // しきい値の処理

    return sigmoid(out);
}

/**出力層の重みの調整**/
void learnReaction(double *weightReaction, double *outAlliance,
                    const vector<double> &dataField, double outReaction,
                    const int &n, const int &numAlliance, const int &ALPHA)
{
    double diff; // 誤差
    diff = (dataField[n] - outReaction) * outReaction * (1 - outReaction);
    // 結合荷重の調整
    for(int i=0; i<numAlliance; ++i)
        weightReaction[i] += ALPHA * outAlliance[i] * diff;
    // しきい値の調整
    weightReaction[numAlliance] += ALPHA * (-1.0) * diff;
}

/**中間層の重みの調整**/
void learnAlliance(double **weightAlliance, double *weightReaction,
                    double *outAlliance, const vector<double> &dataField, double outReaction,
                    const int &n, const int &numAlliance, const int &ALPHA)
{
    double diff; // 重みが考慮された誤差
    // i番目の中間層セルについて
    for(int i=0; i<numAlliance; ++i){
        diff = outAlliance[i] * (1 - outAlliance[i]) * weightReaction[i] *
                (dataField[n] - outReaction) * outReaction * (1 - outReaction);
        // j番目の重みを調整
        for(int j=0; j<n; ++j)
            weightAlliance[i][j] += ALPHA * dataField[j] * diff;
        weightAlliance[i][n] += ALPHA * (-1.0) * diff; // しきい値の調整
    }
}

/**結合荷重の出力**/
void printWeight(double **weightAlliance, double *weightReaction,
                    const int &n,  const int &numAlliance)
{
    for(int i=0; i<numAlliance; ++i){
        for(int j=0; j<n+1; ++j)
            cout << weightAlliance[i][j] << " ";
    }
    cout << endl;
    for(int i=0; i<numAlliance+1; ++i)
        cout << weightReaction[i];
    cout << endl;
}

int main(int argc, char *argv[])
{
    // 定数
    const int                ALPHA = 10;           // 学習係数

    // 変数
    int                      n;                    // 変数の数=入力層の数
    int                      numAlliance;          // 中間層のセル数
    double                   errorLimit;           // 誤差の上限値
    vector< vector<double> > data;                 // 学習データセット
    double                   **weightAlliance = 0; // 中間層層の重み
    double                   *weightReaction = 0;  // 出力層の重み
    double                   *outAlliance = 0;     // 中間層の出力
    double                   outReaction = 0;      // 出力層の出力
    double                   error = DBL_MAX;      // データとの誤差
    int                      count = 0;            // 繰り返し回数のカウンタ

    // 引数のチェック
    if(argc != 4){
        cerr << "使い方 $./backPropagation "
             << "(変数の数) (中間層のセル数) (誤差の上限値) "
             << "< (入力ファイル)" << endl;
        return -1;
    }
    if((n=atoi(argv[1])) < 1){
        cerr << "変数の数の値が不適切です" << endl;
        return -1;
    }
    if((numAlliance=atoi(argv[2])) < 1){
        cerr << "中間層のセル数が不適切です" << endl;
        return -1;
    }
    if((errorLimit=atof(argv[3])) == 0.0){
        cerr << "収束しない可能性があります" << endl;
    }

    // get memory
    weightAlliance = new double * [numAlliance];
    weightReaction = new double [numAlliance+1]; // [numAlliance]はしきい値
    for(int i=0; i<numAlliance; ++i) weightAlliance[i] = new double [n+1];
    outAlliance = new double [numAlliance];

    initWeight(n, numAlliance, weightAlliance, weightReaction);
    readData(data, n);

    // 学習
    while(error > errorLimit){
        error = 0.0;
        for(int i=0; i<data.size(); ++i){
            // 順方向の計算
            outReaction = forward(weightAlliance, weightReaction, outAlliance,
                                    data[i], n, numAlliance);
            // 出力層の重みの調整
            learnReaction(weightReaction, outAlliance,
                            data[i], outReaction, n, numAlliance, ALPHA);
            // 中間層の重みの調整
            learnAlliance(weightAlliance, weightReaction, outAlliance,
                            data[i], outReaction, n, numAlliance, ALPHA);
            // 誤差の積算
            error += (outReaction - data[i][n]) *
                        (outReaction - data[i][n]);
        }
        ++count;
        // 誤差の出力
        cout << count << "t" << error << endl;
    } // 学習終了

    // 結果の出力
    // 1.結合荷重の出力
    printWeight(weightAlliance, weightReaction, n, numAlliance);
    // 2.学習データに対する結果の出力
    for(int i=0; i<data.size(); ++i){
        cout << i << " ";
        for(int j=0; j<n+1; ++j)
            cout << data[i][j] << " ";
        outReaction = forward(weightAlliance, weightReaction, outAlliance,
                                data[i], n, numAlliance);
        cout << outReaction << endl;
    }

    // memory release
    for(int i=0; i<numAlliance; ++i) delete [] weightAlliance[i];
    delete [] weightAlliance;
    delete [] weightReaction;
    delete [] outAlliance;

    return 0;
}

5章はちょっと習作を思いついたのでまた今度になるかも

関連記事

fitbitの睡眠スコアを90弱で安定させる良い睡眠を続ける簡単な方法

m1 ipad pro 12.9 2021のusb-cハブはコレがベスト

Time Machine不要!Macを11.2.3にダウングレードして原神をm1 macbook airでプレイする

MH-Z19CとM5StickCで二酸化炭素濃度モニタリング

【神軽量HMD】Avegant Glyph 改造: 瓶詰堂さんのaltglyphを作った

PC、iPad、Android、switchもドックいらず!あまりに万能なusb-cハブが最強だった

コメント

コメントを返信する

メールアドレスが公開されることはありません。 が付いている欄は必須項目です