Linear Regression - 3

첫 번째 글과 두 번째 글에서 다룬 데이터들은 변수와 가중치가 각각 1개씩 있었습니다. 이번 글에서는 변수와 가중치가 각각 n개씩 있는 데이터를 학습시켜봅니다.

먼저, 변수와 가중치가 각각 2개씩 있는 데이터를 학습시켜봅시다.

변수와 가중치가 2개씩 있을 때, 가설 함수는 다음과 같이 생겼습니다.

1
2
3
float f(float w1, float w2, float x1, float x2){
	return w1*x1 + w2*x2;
}

비용 함수는 다음과 같습니다.

1
2
3
4
float cost(float w1, float w2, float x1, float x2, float y){
	float tmp = f(w1, w2, x1, x2) - y;
	return tmp*tmp;
}

학습을 위해 사용되는 미분 값은 다음과 같이 구할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
float descent1(float w1, float w2, float x1, float x2, float y){ // w1에 대해 미분
	float c1 = cost(w1-alpha, w2, x1, x2, y);
	float c2 = cost(w1+alpha, w2, x1, x2, y);
	return (c2-c1) / (alpha*2);
}

float descent2(float w1, float w2, float x1, float x2, float y){ // w1에 대해 미분
	float c1 = cost(w1, w2-alpha, x1, x2, y);
	float c2 = cost(w1, w2+alpha, x1, x2, y);
	return (c2-c1) / (alpha*2);
}

descent1 함수는 비용 함수를 w1에 대해 편미분한 것이고, 반대로 descent2 함수는 비용 함수를 w2에 대해 편미분한 것입니다.
편미분이란, 다변수 함수의 특정 변수를 제외한 나머지 변수를 상수로 취급하여 미분을 하는 것입니다.

본격적으로 학습을 시켜봅시다.
먼저, 데이터들은 다음과 같이 정의된 구조체로 이루어졍 있습니다.

1
2
3
4
struct Data{
	float x1, x2, y;
	Data(float a, float b, float c) : x1(a), x2(b), y(c) {}
};

이전 글에서 사용했던 학습 함수를 약간만 수정하면 변수와 가중치가 두 개씩 있는 데이터를 학습 시키는 함수도 쉽게 만들 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pair<float, float> linear_double_var_learning(vector<Data> &data, float w1, float w2, float cnt){
	float d1 = 0.0, d2 = 0.0, c = 0.0;
	for(int step=1; step<=cnt; step++){
		d1 = 0.0, d2 = 0.0, c = 0.0;
		for(int i=0; i<data.size(); i++){
			c += cost(w1, w2, data[i].x1, data[i].x2, data[i].y);

			d1 += descent1(w1, w2, data[i].x1, data[i].x2, data[i].y);
			d2 += descent2(w1, w2, data[i].x1, data[i].x2, data[i].y);
		}
		c /= data.size();
		w1 -= alpha * d1 / data.size();
		w2 -= alpha * d2 / data.size();
		printf("step : %d\tcost : %f\tweight : %f, %f\n", step, c, w1, w2);
	}
	return {w1, w2};
}

이 함수는 가중치 두 개를 pair로 묶어서 반환을 합니다.

아래 코드는 {x1, x2, y} 꼴로 구성되어 있는 {1, 0, 1}, {0, 2, 2}, {3, 0, 3}, {0, 4, 4}, {5, 0, 5} 총 5개의 데이터를 학습시키는 전체 코드입니다.
참고로, 이 데이터는 y = x1 + x2 함수에서 뽑아낸 데이터입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <vector>
#include <utility>
using namespace std;

const float alpha = 0.01;
const float target = 0.0001;

struct Data{
	float x1, x2, y;
	Data(float a, float b, float c) : x1(a), x2(b), y(c) {}
};

inline float f(float w1, float w2, float x1, float x2){ // y = w1x1 + w2x2
	return w1*x1 + w2*x2;
}

inline float cost(float w1, float w2, float x1, float x2, float y){
	float tmp = f(w1, w2, x1, x2) - y;
	return tmp*tmp;
}

inline float descent1(float w1, float w2, float x1, float x2, float y){ // w1에 대해 미분
	float c1 = cost(w1-alpha, w2, x1, x2, y);
	float c2 = cost(w1+alpha, w2, x1, x2, y);
	return (c2-c1) / (alpha*2);
}

inline float descent2(float w1, float w2, float x1, float x2, float y){ // w1에 대해 미분
	float c1 = cost(w1, w2-alpha, x1, x2, y);
	float c2 = cost(w1, w2+alpha, x1, x2, y);
	return (c2-c1) / (alpha*2);
}

pair<float, float> linear_double_var_learning(vector<Data> &data, float w1, float w2, float cnt){
	register float d1 = 0.0, d2 = 0.0, c = 0.0;
	for(int step=1; step<=cnt; step++){
		d1 = 0.0, d2 = 0.0, c = 0.0;
		for(int i=0; i<data.size(); i++){
			c += cost(w1, w2, data[i].x1, data[i].x2, data[i].y);

			d1 += descent1(w1, w2, data[i].x1, data[i].x2, data[i].y);
			d2 += descent2(w1, w2, data[i].x1, data[i].x2, data[i].y);
		}
		c /= data.size();
		w1 -= alpha * d1 / data.size();
		w2 -= alpha * d2 / data.size();
		printf("step : %d\tcost : %f\tweight : %f, %f\n", step, c, w1, w2);
	}
	return {w1, w2};
}

int main(){
	vector<Data> data;
	data.push_back( {1.0, 0.0, 1.0} );
	data.push_back( {0.0, 2.0, 2.0} );
	data.push_back( {3.0, 0.0, 3.0} );
	data.push_back( {0.0, 4.0, 4.0} );
	data.push_back( {5.0, 0.0, 5.0} );

  float prv_w = 100.0f;
	pair<float, float> now_w = linear_double_var_learning(data, prv_w, prv_w, 1500);
	printf("ans : %f, %f\n", now_w.first, now_w.second);
}

w1, w2 모두 초기값은 100으로 주고 학습을 시작합니다. 보다 빠른 실행 속도를 위해 자주 쓰이는 변수는 stack 영역이 아닌 register에 직접 저장을 했습니다. 또한, 짧은 함수들은 inline으로 선언을 해, 실행 속도를 더욱 줄었습니다.


이제 변수와 가중치 모두 n개씩 있는 데이터를 학습시켜봅시다.

위에서 설명한 것과 거의 다른 것이 없습니다.
먼저 데이터들을 담을 구조체를 새로 만들어봅시다.

1
2
3
4
struct Data{
	vector<float> x;
	float y;
};

가설 함수와 비용 함수는 반복문을 n번 돌리면 쉽게 구현이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
float f(vector<float> &w, vector<float> &x){ // f = w1x1 + w2x2 + ... + wnxn
	register float sum = 0.0f;
	for(register int i=0; i<w.size(); i++){
		sum += w[i] * x[i];
	}
	return sum;
}

float cost(vector<float> &w, vector<float> &x, float y){
	float tmp = f(w, x) - y;
	return tmp * tmp;
}

미분 함수는 변수와 가중치가 2개씩 있을 때처럼 wi에 대해 편미분을 할 것입니다.

1
2
3
4
5
6
7
8
9
10
float descent(int i, vector<float> &w, vector<float> &x, float y){ //wi에 대해 미분
	float d = w[i];
	w[i] = d - alpha;
	float c1 = cost(w, x, y);
	w[i] = d + alpha;
	float c2 = cost(w, x, y);
	w[i] = d;
	d = (c2-c1) / (alpha*2);
	return d;
}

학습 함수도 변수, 가중치가 2개인 경우와 다른 점은 없습니다. 대신, 값을 반환하지 않고 call by reference로 전달되는 가중치 배열의 값을 직접 수정할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void linear_multi_var_learning(vector<Data> &data, vector<float> &w, float cnt){
	float c;

	vector<float> d(w.size());
	for(int step=1; step<=cnt; step++){
		c = 0.0f; for(int i=0; i<d.size(); i++) d[i] = 0.0f;

		for(int i=0; i<data.size(); i++){
			c += cost(w, data[i].x, data[i].y);
			for(int j=0; j<d.size(); j++){
				d[j] += descent(j, w, data[i].x, data[i].y);
			}
		}
		c /= d.size();
		for(int i=0; i<w.size(); i++) w[i] -= alpha * d[i] / d.size();
		printf("step : %d\tcost : %f\n", step, c);
		printf("weight : "); for(int i=0; i<w.size(); i++) printf("%f ", w[i]); printf("\n");
	}
}

아래 코드는 전체 실행 코드입니다. 학습 데이터는 변수와 가중치가 각각 3개인 데이터를 학습하며, y = 2*x1 + 2*x2 - 2*x3 에서 뽑아낸 데이터들입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <vector>
#include <utility>
using namespace std;

const float alpha = 0.01;
const float target = 0.0001;

struct Data{
	vector<float> x;
	float y;
};

inline float f(vector<float> &w, vector<float> &x){ // f = w1x1 + w2x2 + ... + wnxn
	register float sum = 0.0f;
	for(register int i=0; i<w.size(); i++){
		sum += w[i] * x[i];
	}
	return sum;
}

inline float cost(vector<float> &w, vector<float> &x, float y){
	float tmp = f(w, x) - y;
	return tmp * tmp;
}

inline float descent(int i, vector<float> &w, vector<float> &x, float y){ //wi에 대해 미분
	float d = w[i];
	w[i] = d - alpha;
	float c1 = cost(w, x, y);
	w[i] = d + alpha;
	float c2 = cost(w, x, y);
	w[i] = d;
	d = (c2-c1) / (alpha*2);
	return d;
}

void linear_multi_var_learning(vector<Data> &data, vector<float> &w, float cnt){
	register float c;
	vector<float> d(w.size());
	for(int step=1; step<=cnt; step++){
		c = 0.0f; for(int i=0; i<d.size(); i++) d[i] = 0.0f;

		for(int i=0; i<data.size(); i++){
			c += cost(w, data[i].x, data[i].y);
			for(int j=0; j<d.size(); j++){
				d[j] += descent(j, w, data[i].x, data[i].y);
			}
		}
		c /= d.size();
		for(int i=0; i<w.size(); i++) w[i] -= alpha * d[i] / d.size();
		//printf("step : %d\tcost : %f\n", step, c);
		//printf("weight : "); for(int i=0; i<w.size(); i++) printf("%f ", w[i]); printf("\n");
	}
}



int main(){
	vector<Data> data(5);
	data[0].x = {1.0, 1.0, 1.0};
	data[0].y = 2.0;
	data[1].x = {1.0, 2.0, 1.0};
	data[1].y = 4.0;
	data[2].x = {3.0, 1.0, 1.0};
	data[2].y = 6.0;
	data[3].x = {1.0, 4.0, 1.0};
	data[3].y = 8.0;
	data[4].x = {5.0, 1.0, 1.0};
	data[4].y = 10.0;

	vector<float> w(3, 20);
	linear_multi_var_learning(data, w, 15000);
	for(int i=0; i<w.size(); i++) printf("%f ", w[i]);
}

이 코드 또한 register 변수와 inline 함수를 이용해 실행 속도를 향상시켰습니다.


다음 글에서 행렬을 이용한 Linear Regression를 마지막으로 Linear Regression 파트를 마치도록 하겠습니다.