wintertreey 님의 블로그

MLP 구현 3: 학습단계 본문

머신러닝과 딥러닝

MLP 구현 3: 학습단계

wintertreey 2025. 2. 2. 10:57

학습단계는 feedforward 순전파,
back propagation 역전파,
epoch를 이용해 반복학습을 통해 경사기울기를 줄여 가까워지는것이다.
 

feedforward

feedforward
double sigmoid(double z) {
	return 1.0 / (1.0 + exp(-z));
}

vector<double> feedforward(const vector<double>& input, vector<double>& hiddenLayer) {
	hiddenLayer.resize(w1[0].size());
	vector<double> outputLayer(w2[0].size());

	for (int j = 0; j < hiddenLayer.size(); j++) {
		double sum = 0.0;
		for (int i = 0; i < input.size(); i++) {
			sum += input[i] * w1[i][j];
		}
		sum += b1[j];
		hiddenLayer[j] = sigmoid(sum);
	}
	for (int k = 0; k < outputLayer.size(); k++) {
		double sum = 0.0;
		for (int j = 0; j < hiddenLayer.size(); j++) {
			sum += hiddenLayer[j] * w2[j][k];
		}
		sum += b2[k];
		outputLayer[k] = sigmoid(sum);
	}
	
	return outputLayer;
}

 
활성화함수로 시그모이드 함수를 사용하였다. 
feedforward 안에 정의하여 사용할까 하다가, 역전파때 미분하여 쓸것이기에 밖에 선언해주었다.
 
은닉층 노드값들을 저장할 hiddenLayer, 출력층 노드값들을 저장할 outputLayer를 선언해주었다.
 

sum을 루프 내부에서 초기화해줘야하는 이유

입력층 ---> 출력으로 진행하며 가중합을 계속 계산하면, sum이 누적이 되는게 맞지 않나 라는 생각을 하게 된다.
근데 그렇게 되면 이전 뉴런에서 계산된 sum값이 다음 뉴런의 계산에도 영향을 미치게된다. 각 뉴런은 독립적으로 존재해야하기에 루프안에서 초기화 해줘야한다.
 

hiddenLayer는 참조로, outputLLayer는 반환값으로 전달

역전파 작업에서 hiddenLayer를 불러 값을 수정할 필요가 있었다. feedforward 함수 밖에서도 사용하고자 참조로 설정.
outputLayer의 경우 계산되어 최종적으로 반환값으로 전달되므로 굳이 참조로 설정해줄 필요가 없었다.
 
 

backpropagation

각 층들의 오차를 계산하고 outputError, hiddenError
각 층들의 가중치를 업데이트한다. 
 

double sigmoid_derivate(double z) {
		return z * (1.0 - z);
}

void backpropagation(const vector<double>&input, const vector<double>& target, double learning_rate) {
	vector<double> hiddenLayer;
	vector<double> outputLayer = feedforward(input, hiddenLayer);

	vector<double> outputError(outputLayer.size());
	for (int k = 0; k < outputLayer.size(); k++) {
		outputError[k] = (target[k] - outputLayer[k]) * sigmoid_derivate(outputLayer[k]);
	}

	vector<double> hiddenError(w1[0].size(), 0.0);
	for (int j = 0; j < hiddenError.size(); j++) {
		for (int k = 0; k < outputLayer.size(); k++) {
			hiddenError[j] += outputError[k] * w2[j][k];
		}
		hiddenError[j] *= sigmoid_derivate(hiddenLayer[j]);
	}

	for (int k = 0; k < outputLayer.size(); k++) {
		for (int j = 0; j < w1[0].size(); j++) {
			w2[j][k] += learning_rate * outputError[k] * hiddenLayer[j];
		}
		b2[k] += learning_rate * outputError[k];
	}
	for (int j = 0; j < w1[0].size(); j++) {
		for (int i = 0; i < input.size(); i++) {
			w1[i][j] += learning_rate * hiddenError[j] * input[i];
		}
		b1[j] += learning_rate * hiddenError[j];
	}
	
}

 
순전파에서 활성화한 결과값들을 가지고온다.
 
출력층의 오차 outputError는 (실제 정답 데이터값 target - 신경망이 예측하여 계산한 값 outputLayer) 값과 시그모이드 미분함수를 이용하여 계산.  이는 출력층의 가중치를 업데이트하는데 사용된다.
 
이후 앞쪽으로 오차 전파를 계산하여 은닉층에서의오차를 가중치 업데이트에 활용한다.
 

은닉층 -> 입력층으로의 오차전파가 불필요한 이유

 

back propagation의 chain rule

 
back propagation의 경우
출력층 오차 -> 은닉층오차로 전파
은닉층 오차 로 은닉층 가중치 업데이트를 해준다. 
입력층의 가중치는 은닉층 오차를 통해 간접적으로 업데이트가 된다.
 
 
메인함수에서 학습을 시켜보자

#define _CRT_SECURE_NO_WARNINGS
#include "mlp.h"

int main() {
	string dataPath = "데이어파일경로";
	const char* NameofData = dataPath.c_str();
	vector<vector<double>> rawData;
	rawData = readFile(NameofData);

	vector<vector<double>> data;
	double ymin = 0;
	double ymax = 0;
	data = normalization(rawData, ymin, ymax);
	seperateData(data);
	
	int inputSize = x[0].size(); //53
	int hiddenSize = 10;
	int outputSize = y[0].size(); //1
	initializeWeights(inputSize, hiddenSize, outputSize);

	int epochs = 1000;
	double learning_rate = 0.01;

	for (int epoch = 0; epoch < epochs; epoch++) {
		double totalLoss = 0.0;

		for (int i = 0; i < x.size(); i++) {
			vector<double> input = x[i];
			vector<double> target = y[i];

			vector<double> hiddenLayer;
			vector<double> output = feedforward(input, hiddenLayer);

			double loss = 0.0;
			for (int k = 0; k < output.size(); k++) {
				loss += pow(target[k] - output[k], 2);
			}
			totalLoss += loss / output.size();

			backpropagation(input, target, learning_rate);
		}
		if (epoch % 100 == 0) {
			std::cout << "에포크" << epoch << "-손실" << totalLoss / x.size() << endl;
		}
	}

	return 0;
}

 
epoch는 1000, 학습률은 0.01로 설정해주었다.
 

epoch란

에포크 1000이라면 샘플데이터수(행의 수)가 54개인경우, 54샘플 * 1000번 학습함을 의미.
즉 데이터셋 전체 반복횟수를 의미한다.
 
epochs 값이 너무 작으면
모델이 충분히 학습되지 않아서 제대로 된 결과를 내지 못할 수 있음. (Underfitting)
epochs 값이 너무 크면
학습 시간이 오래 걸리고, 모델이 훈련 데이터에 과적합(Overfitting)될 가능성이 있음.
 
손실계산은 mse로 간단히 설정.
 

 
손실값이 줄어들고, 감소폭도 줄어드는것은 경사하강법이 수렴하고 있다는 증거.
즉 모델이 최적의 가중치를 찾아가는 과정이 정상적으로 진행되고 있다는것. 
 
 

경사하강법 Gradient Descent

 

'머신러닝과 딥러닝' 카테고리의 다른 글

MLP 구현 2: 신경망 초기화  (0) 2025.02.02
MLP 구현 1: 데이터 전처리  (0) 2025.02.01
MLP 개념  (0) 2025.02.01