가장 쉽게 생각할 수 있는 방법은 이동평균(moving average)로, 주어진 데이터 전/후의 일정 개수의 데이터의 평균을 그 데이터의 값으로 추정하는 방법이다. 만약 주어진 데이터에서 멀어지는 점일수록 중요도가 떨어진다면 중요도를 낮추어 주면 된다. 즉, 이동평균은 주어진 데이터와의 거리에 상관없이 모두 동일한 가중치(1)를 두는 것으로 생각할 수 있는 것이다. 이와 같은 개념을 좀 더 일반화시킨 다음 다항식으로까지 전개시켜서 smoothing 하는 알고리즘이 Saivtzky-Golay 알고리즘이다.
이 글은 Smoothing and Differentiation of Data by Simplified Least Squares Procedures, Abraham Savitzky and Marcel J. E. Golay, 1994, Analytical Chemistry 논문에 근거하여 작성한다.
이동평균은 y=1 인 함수와 원 함수와의 convolution 이라고 할 수 있다. 그 이외에도 여러 convolution 함수를 사용할 수 있다. 위 그림 참조. Savitzky-Golay 알고리즘은 데이터와 양옆의 데이터들을 다항식으로 회귀시켰을 때 만들어지는 다항식으로 현재의 데이터 값을 설정하자는 것이다. 핵심은 각각의 데이터에 대해 매번 회귀시킨 다항식을 계산할 필요가 없이 특정 convolution 을 사용하면 다항식을 회귀시킨 것을 '정확히' 찾아낼 수 있다는 것이다. 이와 같은 것을 논문에서는 다음과 같이 표현하고 있다. 1
즉, xi의 새로운 값은 양쪽 근방 2n+1 개의 점으로 다항식 회귀한 식으로부터 다시 추정해 내는 것이다. 즉, xi 를 포함하고 있는 양쪽 근방 2n+1 개의 점으로 다항식 회귀한 식 Si을 찾고, Si(xi)로 xi 를 대체하는 것이다. 문제는 Si를 찾아 내는 것인데, 각 xi에 대해서 Si를 매번 찾아내야 한다면 계산횟수가 많아질텐데, Si를 3차 다항식으로 식을 만든 후, 각 점에 대해서 다항연립방정식을 만들면, 놀랍게도, 각 계수가 정확히 계산이 된다! 이렇게 계산된 계수로부터 추정된 값, 즉 Si(xi)을 계산하는 것은 결국 convolution 형태로 나타나게 된다. 쉽게 말해, 이동평균을 구할 때는 앞/뒤 구간에 있는 값들에 1씩 곱해서 더한 후 평균을 구하는 것과 비슷하게, Savitzky-Golay smoothing 은 정해진 weight로 이동평균을 구하는 것과 비슷하게 하는 것이다. 더욱 놀라운 것은, 원래의 데이터를 smoothing 하는 것 뿐만이 아니라, 그 데이터를 미분한 그래프가 어떻게 생겼는지까지 이렇한 convolution 형태로 쉽게 계산할 수 있다는 것이다. 데이터가 급격하게 변하는 곳은 고차 다항식으로, 그렇지 않은 곳은 저차 다항식으로 smoothing 할 수도 있는 것이다. 이와 같은 테이블의 예는 다음과 같다.
이와 같은 알고리즘을 C++ 코드로 나타내면 다음과 같다.
int A[] = { -2, 3, 6, 7, 6, 3, -2 };
int k = 0;
int point_number = (int)x_series->size();
for(k = 0; k<n; k++){
if(destX != NULL) destX->push_back(x_series->operator [](k));
if(destY != NULL) destY->push_back(y_series->operator [](k));
}
for(k = n; k<point_number-n; k++){
double x = x_series->operator[](k);
int i = 0;
double nominator = 0;
double denominator = 0;
for(i = -n; i <= n; i++){
nominator += (A[n+i]*y_series->operator[](k+i));
denominator += A[n+i];
}
double y = nominator / denominator;
if(destX != NULL) destX->push_back(x);
if(destY != NULL) destY->push_back(y);
}
for(k = point_number-n; k<point_number; k++){
if(destX != NULL) destX->push_back(x_series->operator [](k));
if(destY != NULL) destY->push_back(y_series->operator [](k));
}
return true;
}
위 코드는 3차 다항식으로 regression 하는 것을 사용하고 있으며, 위 테이블에 NORM으로 되어 있는 normalizing factor 로 나누어 주고 있지 않다. 내가 사용한 코드에서는 특성상 NORM으로 나눌 필요가 없었기 때문이며, 원래는 나누어 주어야지 xi와 Si(xi)의 scale이 같아진다. 또한 이런 식으로 window를 사용하는 알고리즘들은 가장 앞/뒤의 window size / 2 크기만큼의 데이터가 문제가 되는데, 위 코드에서는 이 값을 그냥 원래의 값을 사용하고 있다. 이것도 나는 문제의 특성상 가장 앞/뒤 몇 개의 데이터는 필요 없어서 그냥 저렇게 한 것이므로 그렇지 않은 경우엔 적당한 수정이 필요하다.
- 원 논문을 읽어 보면, 'It is not approximate.' 란 구절이 나온다. 감동!! [본문으로]
'컴퓨터 > 수학이랑' 카테고리의 다른 글
constraint adaptive differential evolution (2) | 2010.02.16 |
---|---|
다익스트라(Dijkstra) 알고리즘의 재발견 (22) | 2010.02.07 |
monotone cubic Hermite interpolation (15) | 2010.01.12 |
cubic spline interpolation (18) | 2010.01.11 |
2차 다항식 회귀 (polynomial regression of order 2) (18) | 2009.12.27 |