wintertreey 님의 블로그

MLP 구현 1: 데이터 전처리 본문

머신러닝과 딥러닝

MLP 구현 1: 데이터 전처리

wintertreey 2025. 2. 1. 16:32

데이터전처리

저장해둔 데이터 경로를 찾아 읽어오고, readFile
데이터 정규화작업을 해주고, normalization
데이터를 분리해주어야한다. separate
 
내가 가진 데이터파일의 형태는 다음과 같다. 
 
 
A2 A3 A4 A5 .... A54 Y
1.95173 115.151 27.2738 72.2082 ...
1.97996 115.486 27.0532 72.2082 ...
2.02079 115.316 27.0514 72.2082 ...
2.0012 115.338 27.3179 72.2082 ...
...
55*54의 형태.
 

readFile

읽어오는 작업에 대한 설명은 패스.
 

Normalization

데이터의 값이 제각각. 각 값들의 영향력을 제어하기 위해 0~1사이로 맞춰주는 작업.

vector<vector<double>> normalization(vector<vector<double>>& rowData, double& ymax, double& ymin)
{
	int numrows = rowData.size();
	int numcols = rowData[0].size();
	double min = 0;
	double max = 0;


	for (int i = 0; i < numcols; i++) {
		max = rowData[0][i];
		min = rowData[0][i];

		for (int j = 0; j < rowData.size(); j++) {
			if (rowData[j][i] > max) {
				max = rowData[j][i];
			}
			if (rowData[j][i] < min) {
				min = rowData[j][i];
			}
		}

		if (i == numcols - 1) {
			ymax = max;
			ymin = min;
		}
			
		for (int j = 0; j < numrows; j++) {
			rowData[j][i] = (rowData[j][i] - min) / (max - min);
		}
	}
	return rowData;
}

 

컬럼별로 최대값, 최소값을 초기화해줘야하는이유

각 컬럼은 독립적인 데이터이고, 각 컬럼에 대해 최댓값과 최솟값을 구하는것이기 때문.
예시로 이해해보자.

예시데이터
col1    col2    col3
  2       4       7
  3       5       8
  1       6       9

첫 번째 컬럼 (col1)의 경우
max = 3, min = 1

정규화 후:
(2-1)/(3-1) = 0.5
(3-1)/(3-1) = 1.0
(1-1)/(3-1) = 0.0

두 번째 컬럼 (col2)의 경우
max = 6, min = 4

정규화 후:
(4-4)/(6-4) = 0.0
(5-4)/(6-4) = 0.5
(6-4)/(6-4) = 1.0

세 번째 컬럼 (col3)의 경우
max = 9, min = 7

정규화 후:
(7-7)/(9-7) = 0.0
(8-7)/(9-7) = 0.5
(9-7)/(9-7) = 1.0

 
 
마지막 열(numcols - 1)의 경우, Y값이기에 따로 처리해주는것 잊지말자.

 

Separation

void seperateData(vector<vector<double>> data)
{
	int rowSize = data.size(); 
	int colSize = data[0].size();

	x.resize(rowSize, vector<double>(colSize - 1, 1)); 
	y.resize(rowSize, vector<double>(1, 0)); 

	for (int i = 0; i < rowSize; i++) { 
		for (int j = 0; j < colSize - 1; j++) {
				x[i][j] = data[i][j]; 
		}
		y[i][0] = data[i][colSize - 1];
	}
}

 

colSize가 필요한 이유

예제 데이터 (5개의 샘플, 4개의 입력 특징 + 1개의 정답값)

vector<vector<double>> data = {
    {1.2, 2.3, 3.1, 4.5, 0},  // 입력값: [1.2, 2.3, 3.1, 4.5], 정답: 0
    {2.1, 3.4, 1.8, 2.7, 1},  // 입력값: [2.1, 3.4, 1.8, 2.7], 정답: 1
    {0.5, 1.7, 2.9, 3.6, 0},  // 입력값: [0.5, 1.7, 2.9, 3.6], 정답: 0
    {3.3, 4.2, 2.1, 1.5, 1},  // 입력값: [3.3, 4.2, 2.1, 1.5], 정답: 1
    {1.1, 2.5, 3.7, 4.8, 0}   // 입력값: [1.1, 2.5, 3.7, 4.8], 정답: 0
};

이 데이터를 separateData를 통해 분리하면:

rowSize = 5;  // 데이터 샘플 개수
colSize = 5;  // 특징 4개 + 정답 1개 (총 열 개수)

x = [
    [1.2, 2.3, 3.1, 4.5],
    [2.1, 3.4, 1.8, 2.7],
    [0.5, 1.7, 2.9, 3.6],
    [3.3, 4.2, 2.1, 1.5],
    [1.1, 2.5, 3.7, 4.8]
];

y = [
    [0],
    [1],
    [0],
    [1],
    [0]
];

이제 x에는 입력 데이터가, y에는 정답이 정확하게 들어가게 됨! 
 
행: 샘플 데이터 수
열: 특징 수 + 1개 Y
 
 

바이어스 포함 여부

바이어스를 데이터전처리과정에 포함할지 말지 고민했다.
1. 바이어스를 포함하는경우
입력 행렬 x 자체에 바이어스(첫 번째 열 1)을 포함.
feedforward 계산 시 코드가 단순해짐. 
하지만 데이터 원본에는 없는 값을 추가하는 과정이므로, 전처리 과정에 데이터 변형이 포함됨.
 
2. 바이어스를 제외한 경우
데이터 전처리과정에서는 바이어스를 추가하지않고, feedforward()과정에서 따로 계산하는 방식
원본 데이터를 유지할 수 있다. 
다만 연산시 코드가 좀 더 복잡해짐. 
 
나의 경우 데이터 변형을 최소화하고, 연산이 복잡해지더라도 y = w*x + b의 형태를 명확히하고 싶어서
데이터 전처리 과정에 바이어스를 제외하였다.
 
 
 
메인함수를 빌드시켜서 확인해보자.

#define _CRT_SECURE_NO_WARNINGS
#include "mlp.h"

int main() {
	string dataPath = "pathofdatafile";
	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);


	std::cout << "x.size(): " << x.size() << ", x[0].size(): " << x[0].size() << std::endl;
	std::cout << "y.size(): " << y.size() << ", y[0].size(): " << y[0].size() << std::endl;

	std::cout << "First x row: ";
	for (double val : x[0]) cout << val << " ";
	std::cout << std::endl;

	std::cout << "First y value: " << y[0][0] << std::endl;

	return 0;
}

 
 
 
 
 



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

MLP 구현 3: 학습단계  (0) 2025.02.02
MLP 구현 2: 신경망 초기화  (0) 2025.02.02
MLP 개념  (0) 2025.02.01