C# - 최소 자승법의 1차 함수에 대한 매개변수를 단순 for 문으로 구하는 방법
일단 행렬식을 이용하면,
C# - 행렬식을 이용한 최소 자승법(LSM: Least Square Method)
; https://www.sysnet.pe.kr/2/0/11918
범용적으로 다항식에 대한 근사를 최소 자승법(최소 제곱법)으로 구할 수 있습니다. 하지만, 만약 대상을 "1차 함수"로 직선에 대한 근사만을 구한다면 복잡한 행렬 연산 없이 for 문만으로 매개 변수를 구하는 것이 가능합니다.
가령, 지난번 예제의 행렬식을 보겠습니다.
θ0 + θ1x1 = y1
θ0 + θ1x2 = y2
...
θ0 + θ1xn = yn
AX=B
A-1AX=A-1B
X=A-1B (A-1 == 의사역행렬)
결국 중요한 것은, 위의 식에서 A
-1B 연산 결과를 구하는 것인데요, 헷갈리니까 일단 의사역행렬을 A
+라고 정의하고, 이것을 행렬 라이브러리를 이용하면 단순히 Matrix 타입의 PseudoInverse를 호출하는 것으로 쉽게 해결했지만 만약 직접 구하고 싶다면 다음과 같은 과정을 거쳐야 합니다.
A+ = (ATA)-1AT
따라서, 매개변수를 나타내는 행렬 X는 B 행렬까지 곱해주면서 다음과 같이 계산할 수 있습니다.
X = A+ * B
= (ATA)-1AT * B
이제 남은 작업은 위의 식을 간략하게 바꿔주면 됩니다. ^^
이 상태에서 A 행렬을 보면 "n x 2" 행렬이고 이것의 전치 행렬(A
T)은 "2 x n" 행렬이 됩니다. 또한 B 행렬도 "n x 1" 행렬임을 감안하면 연산 결과가 다음과 같이 정리될 수 있습니다.
X = (ATA)-1AT * B
= ((2 x n) * (n x 2))-1 * (2 x n) * (n x 1)
= (2 x 2)-1 * (2 x 1)
= (2 x 2) * (2 x 1)
= (2 x 1)
즉, X 행렬은 (당연히 1차 함수의 매개변수 2개를 구하는 것이므로) 언제나 "2 x 1" 행렬이 나오므로 X 행렬의 인덱스에 해당하는 값을 정리해 볼 수도 있습니다. 이 과정을 단계별로 천천히 ^^ 접근해 볼까요?
2 x 2 행렬의 역행렬은 다음과 같이 간략화할 수 있으므로,
A
TA 결과의 역행렬을 구할 수 있습니다.
위의 결과를 2 x n 행렬의 A
T와 연산을 하면 과정이 좀 복잡하니 어차피 행렬곱은 결합법칙이 성립하므로 뒤의 A
T B 연산을 먼저 다음과 같이 정리할 수 있습니다.
마지막으로 (A
TA)
-1(2 x 2 행렬)에 A
T * B(2 x 1 행렬)을 곱하는 것이므로 다음과 같이 최종 정리가 됩니다.
따라서 1차 방정식의 매개변수는 이렇게 단일 식으로 각각 구할 수 있습니다.
구하는 과정에 정리할 식이 좀 끼어들어서 그렇지, 사실 C# 코드로 위의 계산을 나타내면 별거 아닙니다. ^^
// 단순 for 루프를 이용한 계산
private static (double theta1, double theta0) GetEquation2(double[] xData, double[] yData)
{
double sumAnBn = 0.0;
double sumAn = 0.0;
double sumBn = 0.0;
double sumAnAn = 0.0;
for (int i = 0; i < xData.Length; i ++)
{
sumAnBn += xData[i] * yData[i];
sumAn += xData[i];
sumBn += yData[i];
sumAnAn += xData[i] * xData[i];
}
int n = xData.Length;
double Q = sumAnAn * n - sumAn * sumAn;
double theta1 = (n * sumAnBn - sumAn * sumBn) / Q;
double theta0 = (-sumAn * sumAnBn + sumAnAn * sumBn) / Q;
return (theta1, theta0);
}
// 행렬을 이용한 계산
private static (double theta1, double theta0) GetEquation(double[] xData, double[] yData)
{
Matrix matA = CreateMatrix.DenseOfColumnMajor(xData.Count(), 1, xData);
Vector add1 = Vector.Build.DenseOfArray(Enumerable.Repeat(1.0, xData.Count()).ToArray());
Matrix matAwith1 = matA.InsertColumn(1, add1);
Console.WriteLine(matAwith1);
Matrix matB = CreateMatrix.DenseOfColumnMajor(yData.Count(), 1, yData);
Matrix pinvMatA = matAwith1.PseudoInverse();
Console.WriteLine(pinvMatA);
Matrix matX = pinvMatA * matB;
return (matX[0, 0], matX[1, 0]);
}
당연하겠지만 행렬식을 이용했던 GetEquation 메서드와 비교해 보면,
{
// y = theta0 + (theta1 * x)
(double theta1, double theta0) = GetEquation(xData, yData);
Console.WriteLine($"[method1] y = {theta0} + {theta1} * x");
/* 출력 결과
[method1] y = 231.545758451005 + 1.39551018043075 * x
*/
}
{
(double theta1, double theta0) = GetEquation2(xData, yData);
Console.WriteLine($"[method2] y = {theta0} + {theta1} * x");
/* 출력 결과
[method2] y = 231.545758451006 + 1.39551018043075 * x
*/
}
부동소수점 계산임을 감안해 값이 거의 동일하다는 것을 알 수 있습니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]