반응형

신경망 셈플 프로그램입니다.

 

#include <iostream>
#include <string>
#include <cmath>
#include <ctime>
#include <cstdlib>
#include <fstream>
using namespace std;


// 현재 네트워크의 학습률은 2의 제곱근
float learningRate = 1.414213562;

// 운동량
float momentum = 0.25;

// 바이어스(경향, 성향)
int bias = 1;


// 신경망을 통해 전달되는 학습 데이터
double arrTrainingData[4][2] = {
    { 1, 0 },   // target : 1
    { 1, 1 },   // target : 0
    { 0, 1 },   // target : 1
    { 0, 0 }    // target : 0
};

// 신경망 학습 데이터에 대한 값
int arrTargetData[4] = {
    1,
    0,
    1,
    0
};


// 신경망 가중치 값들
float arrWeight[9] = {};
// 신규 업데이트 가중치 값
float arrUpdateWeight[9];
// 이전 업데이트 가중치 값
float arrUpdatePrevWeight[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

// Hidden Layer
float sumHiddenLayer1;
float sumHiddenLayer2;


// 편미분(도함수) 값
float derivativeOutput;
float derivativeHiddenLayer1;
float derivativeHiddenLayer2;
float sumOutput;

// 기울기(미분 값)
float gradients[9];

// 최종 결과 값
float outputNeuron;

// 현재 학습중인 세대
int epoch = 0;

// 에러
float arrError[4];
float arrRMSE[20000];






////////Prototyping////////
float sigmoid(float x);
void calcHiddenLayers(double input1, double input2);
void calcOutputNeuron();
void calcError(int x);
void calcDerivatives();
void backwardPropagation(double input1, double input2, float error);
void calcUpdates();
void update_new_arrWeight();
float calcRMSE();
void generateWeight();
void trainingNeuralNetwork();
void testInput();
void saveData();
////////Prototyping////////


int main(int argc, const char * argv[]) {
    
    /*
     input neurons : 2
     hidden layers : 2
     output neuron : 1
     
     The learning algorithm i use is the backpropagation algorithm.
     The network has 2 input neurons, 2 hidden layers, and 1 output neuron.
     also the hidden layer and the output layer is supported by a additional bias neuron(a neuron with a constant value of 1)
     */

    
    // 가중치 값 생성
    generateWeight();
    
    // 신경망 학습
    trainingNeuralNetwork();
    
    // 저장파일 생성
    saveData();
    
    // 입력 시작
    testInput();
    
    system("pause");
    
    return 0;
}


////////////////////////////////////////////////////////////////////////////////////////////////
// 가중치 값 랜덤 생성
void generateWeight()
{
    srand((unsigned)time(NULL));
    
    for (int i = 0; i < 9; i++)
    {
        if (1 == rand() % 2) {
            // generate number between -1.0 and 0.0
            arrWeight[i] = -(double(rand()) / (double(RAND_MAX) + 1.0));
        } else {
            // generate number between 1.0 and 0.0
            arrWeight[i] = double(rand()) / (double(RAND_MAX) + 1.0);
        }
        
        cout << "weight " << i << " = " << arrWeight[i] << endl;
    }
    cout << "" << endl;
}

// 신경망 학습
void trainingNeuralNetwork()
{
    // 20000 세대만큼 반복
    while (epoch < 20000)
    {
        for (int i = 0; i < 4; i++)
        {
            double input1 = arrTrainingData[i][0];
            double input2 = arrTrainingData[i][1];
            calcHiddenLayers(input1, input2);   // input
            calcOutputNeuron();                 // output
            
            
            calcError(i); // input!!
            
            calcDerivatives(); // input!!
            
            float error = arrError[i];
            backwardPropagation(input1, input2, error); // input!!
            
            calcUpdates();
            update_new_arrWeight();
        }
        
        // 제곱근 평균 제곱 오차(RMSE;오차(잔차)의 제곱에 대해 평균을 취하고 이를 제곱근한 것)
        // 작을수록 추정의 정확성이 높다.
        // 추정값들이 중심으로부터 얼마나 멀리 떨어져 있는지를 나타낼때 많이 쓰인다.
        float rmse = calcRMSE();
        arrRMSE[epoch] = rmse;
        cout << "epoch: " << epoch << endl;
        
        // 세대수 증가
        epoch = epoch + 1;
        
        
        
        // Adding some motivation so if the neural network is not converging after 4000 epochs it will start over again until it converges
        // 신경망이 4000세대 이후까지 RMSE 에러율이 0.5이하가 안된다면 처음부터 다시 시도한다.(가중치 재설정)
        if (epoch > 4000 && rmse > 0.5)
        {
            for (int i = 0; i < 9; i++)
            {
                arrUpdatePrevWeight[i] = 0;
                arrUpdateWeight[i] = 0;
                gradients[i] = 0;
            }
            for (int i = 0; i < 4; i++) {
                arrError[i] = 0;
            }
            for (int i = 0; i < epoch; i++) {
                arrRMSE[i] = 0;
            }
            epoch = 0;
            generateWeight();
        }
    }
}


// 저장 파일 생성
void saveData()
{
    // 각 세대별 RMSE값 저장
    ofstream dataER;
    dataER.open("errorData1.txt");
    for (int i = 0; i < epoch; i++)
    {
        dataER << i << "   " << arrRMSE[i] << endl;
    }
    dataER.close();
    
    // 최종 가중치 값 저장
    ofstream dataER1;
    dataER1.open("weight_data1.txt");
    for (int i = 0; i < 9; i++)
    {
        dataER1 << i << "   " << arrWeight[i] << endl;
    }
    dataER1.close();
}

// 학습된 신경망 테스트
void testInput()
{
    char choise = 'Y';

    do
    {
        if (choise == 'Y' || choise == 'y')
        {
            float input1;
            float input2;
            cout << "user input 1: "; cin >> input1; cout << endl;
            cout << "user input 2: "; cin >> input2; cout << endl;
            calcHiddenLayers(input1, input2);   // input
            calcOutputNeuron();                 // output
            
            // 반올림 처리로 최종 값을 결정 함
            cout << "output = " << outputNeuron << " => " << floor(outputNeuron+0.5) << endl;
            
            cout << "Again(y/n)? "; cin >> choise;
        }
        else
        {
            break;
        }
    } while ((choise == 'Y' || 'y') && (choise != 'n' || 'N'));
}

////////////////////////////////////////////////////////////////////////////////////////////////

/*
           bias(1)       bias(1)
             | \         |
             |  w4       w8
             |   \       |
 input1 --w0 --- h1      |
        \    |  /  \     |
         \    \/    w6   |
          w1  /\     \   |
            \/  \      outputNeuron
            /\   \    /
          w2  \   w5 w7
          /    \  | /
         /      \ |/
 input2 --w3----- h2
 */

// Hidden Layer 계산
void calcHiddenLayers(double input1, double input2)
{
    sumHiddenLayer1 = (input1 * arrWeight[0]) + (input2 * arrWeight[2]) + (bias * arrWeight[4]);
    sumHiddenLayer2 = (input1 * arrWeight[1]) + (input2 * arrWeight[3]) + (bias * arrWeight[5]);
}

// Output Layer 계산
void calcOutputNeuron()
{
    sumOutput = (sigmoid(sumHiddenLayer1) * arrWeight[6]) + (sigmoid(sumHiddenLayer2) * arrWeight[7]) + (bias * arrWeight[8]);
    outputNeuron = sigmoid(sumOutput);
}


// sigmoid activation function
// 계단형식의 함수를 미분이 가능하도록 곡선화를 해주는 함수이다.
// 주어진 값(x)에 대하여 0 ~ 1사이의 값으로 변환하여 반환해 준다.
// 이 함수를 사용하는 이유는 미분이 용이하기 때문이다. 이 함수의 미분 공식은 sigmoid(x)(1-sigmoid(x)) 이다.
float sigmoid(float x)
{
    float sigmoid = 1.0 / (1.0 + exp(-x));
    return sigmoid;
}

// 타겟값과 얼마나 차이가 나는지 배열에 저장
void calcError(int x)
{
    arrError[x] = outputNeuron - arrTargetData[x];
}



// sigmoid 미분
void calcDerivatives()
{
    /*
    derivativeOutput       = (exp(sumOutput)       / pow((1 + exp(sumOutput)),       2))  * -arrError[x];
    derivativeHiddenLayer1 = (exp(sumHiddenLayer1) / pow((1 + exp(sumHiddenLayer1)), 2))  *  arrWeight[6] * derivativeOutput;
    derivativeHiddenLayer2 = (exp(sumHiddenLayer2) / pow((1 + exp(sumHiddenLayer2)), 2))  *  arrWeight[7] * derivativeOutput;
    */

    // 각 출력값에대한 미분값을 구한다.
    derivativeOutput       = sigmoid(sumOutput)       * (1 - sigmoid(sumOutput));
    derivativeHiddenLayer1 = sigmoid(sumHiddenLayer1) * (1 - sigmoid(sumHiddenLayer1));
    derivativeHiddenLayer2 = sigmoid(sumHiddenLayer2) * (1 - sigmoid(sumHiddenLayer2));
}

// 기울기 계산
// Backward Propagation (역전파)
void backwardPropagation(double input1, double input2, float error)
{
    /*
    gradients[0] = sigmoid(input1) * derivativeHiddenLayer1;
    gradients[1] = sigmoid(input1) * derivativeHiddenLayer2;
    
    gradients[2] = sigmoid(input2) * derivativeHiddenLayer1;
    gradients[3] = sigmoid(input2) * derivativeHiddenLayer2;
    
    gradients[4] = sigmoid(bias)   * derivativeHiddenLayer1;
    gradients[5] = sigmoid(bias)   * derivativeHiddenLayer2;

    gradients[6] = sigmoid(sumHiddenLayer1) * derivativeOutput;
    gradients[7] = sigmoid(sumHiddenLayer2) * derivativeOutput;
    gradients[8] = sigmoid(bias)            * derivativeOutput;
     */

    
    
    // Backward Propagation(역전파)란 Error(오차)가 본래 진행방향과 반대방향으로 전파 된다고 하여 붙여진 이름이다.
    // 역전파 알고리즘은 Supervised Learning(input과 output을 알고있는 상태)에서 신경망을 학습시키는 방법이다.

    double bpOutput = derivativeOutput * -error;
    double bpLayer2 = derivativeHiddenLayer2 * arrWeight[7] * bpOutput;
    double bpLayer1 = derivativeHiddenLayer1 * arrWeight[6] * bpOutput;
    
    // w8,w7,w6
    gradients[8] = sigmoid(bias)            * bpOutput;
    gradients[7] = sigmoid(sumHiddenLayer2) * bpOutput;
    gradients[6] = sigmoid(sumHiddenLayer1) * bpOutput;
    
    // w5, w4
    gradients[5] = sigmoid(bias) * bpLayer2;
    gradients[4] = sigmoid(bias) * bpLayer1;
    
    // w3, w2
    gradients[3] = sigmoid(input2) * bpLayer2;
    gradients[2] = sigmoid(input2) * bpLayer1;
    
    // w1, w0
    gradients[1] = sigmoid(input1) * bpLayer2;
    gradients[0] = sigmoid(input1) * bpLayer1;
}

void calcUpdates()
{
    for (int i = 0; i < 9; i++)
    {
        //arrUpdateWeight[i] = gradients[i];
        arrUpdateWeight[i] = (learningRate * gradients[i]) + (momentum * arrUpdatePrevWeight[i]);
        
        arrUpdatePrevWeight[i] = arrUpdateWeight[i];
    }
}

void update_new_arrWeight()
{
    for (int i = 0; i < 9; i++)
    {
        arrWeight[i] = arrWeight[i] + arrUpdateWeight[i];
    }
}

float calcRMSE()
{
    float rmse = sqrt((pow(arrError[0], 2) + pow(arrError[1], 2) + pow(arrError[2], 2) + pow(arrError[3], 2) / 4));
    cout << "RMSE : " << rmse << endl;
    cout << "" << endl;
    return rmse;
}
 

2020/05/19 - [AI/Algorithm] - minimax full search example

2020/05/19 - [AI/Algorithm] - minimax, alpha-beta pruning

2020/05/19 - [iOS/Tips] - Bitbucket Carthage 사용

2020/05/19 - [iOS/Jailbreak] - Fridump 사용법 (3/3) - 메모리 덤프

2020/05/19 - [iOS/Jailbreak] - Fridump 사용법 (2/3) - Mac OS X 환경 구축

2020/05/19 - [iOS/Jailbreak] - Fridump 사용법 (1/3) - iOS디바이스 환경 구축

2020/05/19 - [iOS/Jailbreak] - Fridump, Tcpdump, OpenSSL Quick Guide

2020/05/19 - [OS/Mac OS X] - gdb 사용

2020/05/19 - [iOS/Jailbreak] - Frida 설치 및 사용법

2020/05/19 - [OS/Mac OS X] - gdb 설치

2020/05/19 - [OS/Mac OS X] - Mac에서 Node.js 설치

2020/05/19 - [iOS/Jailbreak] - Tcpdump 사용법

2020/05/19 - [개발노트] - UUID의 구성 요소

2020/05/18 - [iOS/Tips] - APNs

 

 

반응형

'개발 > AI,ML,ALGORITHM' 카테고리의 다른 글

Neural Network (XOR)  (0) 2022.11.18
2D 충돌처리  (0) 2020.12.12
Generic algorithm  (0) 2020.05.19
minimax full search example  (0) 2020.05.19
minimax, alpha-beta pruning  (0) 2020.05.19
블로그 이미지

SKY STORY

,