성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[tree soap] 아차! f는 기억이 나는데, m은 ㅜㅜ 감사합니다!!! ^...
[정성태] 'm'은 decimal 타입의 숫자에 붙는 접미사입니다. ...
[정성태] https://lxr.sourceforge.io/ http...
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 최소 자승법의 1차 함수에 대한 매개변수를 단순 for 문으로 구하는 방법</h1> <p> 일단 행렬식을 이용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - 행렬식을 이용한 최소 자승법(LSM: Least Square Method) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11918'>http://www.sysnet.pe.kr/2/0/11918</a> </pre> <br /> 범용적으로 다항식에 대한 근사를 최소 자승법(최소 제곱법)으로 구할 수 있습니다. 하지만, 만약 대상을 "1차 함수"로 직선에 대한 근사만을 구한다면 복잡한 행렬 연산 없이 for 문만으로 매개 변수를 구하는 것이 가능합니다.<br /> <br /> 가령, 지난번 예제의 행렬식을 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > θ<sub>0</sub> + θ<sub>1</sub>x<sub>1</sub> = y<sub>1</sub> θ<sub>0</sub> + θ<sub>1</sub>x<sub>2</sub> = y<sub>2</sub> ... θ<sub>0</sub> + θ<sub>1</sub>x<sub>n</sub> = y<sub>n</sub> </pre> <br /> <script type="math/tex"> \begin{pmatrix}x_1 & 1\\ \vdots & \vdots \\ x_n & 1 \end{pmatrix} \begin{pmatrix} \theta_1 \\ \theta_0 \end{pmatrix} = \begin{pmatrix} y_1 \\ \vdots \\ y_n \end{pmatrix} </script><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AX=B A<sup>-1</sup>AX=A<sup>-1</sup>B X=A<sup>-1</sup>B (A<sup>-1</sup> == 의사역행렬) </pre> <br /> 결국 중요한 것은, 위의 식에서 A<sup>-1</sup>B 연산 결과를 구하는 것인데요, 헷갈리니까 일단 의사역행렬을 A<sup>+</sup>라고 정의하고, 이것을 행렬 라이브러리를 이용하면 단순히 Matrix 타입의 PseudoInverse를 호출하는 것으로 쉽게 해결했지만 만약 직접 구하고 싶다면 다음과 같은 과정을 거쳐야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > A<sup>+</sup> = (A<sup>T</sup>A)<sup>-1</sup>A<sup>T</sup> </pre> <br /> 따라서, 매개변수를 나타내는 행렬 X는 B 행렬까지 곱해주면서 다음과 같이 계산할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > X = A<sup>+</sup> * B = (A<sup>T</sup>A)<sup>-1</sup>A<sup>T</sup> * B </pre> <br /> 이제 남은 작업은 위의 식을 간략하게 바꿔주면 됩니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 상태에서 A 행렬을 보면 "n x 2" 행렬이고 이것의 전치 행렬(A<sup>T</sup>)은 "2 x n" 행렬이 됩니다. 또한 B 행렬도 "n x 1" 행렬임을 감안하면 연산 결과가 다음과 같이 정리될 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > X = (A<sup>T</sup>A)<sup>-1</sup>A<sup>T</sup> * B = ((2 x n) * (n x 2))<sup>-1</sup> * (2 x n) * (n x 1) = (2 x 2)<sup>-1</sup> * (2 x 1) = (2 x 2) * (2 x 1) = (2 x 1) </pre> <br /> 즉, X 행렬은 (당연히 1차 함수의 매개변수 2개를 구하는 것이므로) 언제나 "2 x 1" 행렬이 나오므로 X 행렬의 인덱스에 해당하는 값을 정리해 볼 수도 있습니다. 이 과정을 단계별로 천천히 ^^ 접근해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> A = \begin{pmatrix}a_1 & 1 \\ a_2 & 1 \\ \vdots & \vdots \\ a_n & 1 \end{pmatrix} \\ \\ A^T = \begin{pmatrix}a_1 & a_2 & \dots & a_n \\ 1 & 1 & \dots & 1 \end{pmatrix} \\ \\ B = \begin{pmatrix}b_1 \\ b_2 \\ \vdots \\ b_n \end{pmatrix} </script><br /> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> A^T A = \begin{pmatrix} (a_1 * a_1 + a_2 * a_2 + ... + a_n * a_n) & (a_1 + a_2 + ... + a_n) \\ (a_1 + a_2 + ... + a_n) & (1 * 1 + 1 * 1 + ... + 1 * 1) \end{pmatrix} \\ \qquad = \begin{pmatrix} \sum_{i=1}^n a_n * a_n & \sum_{i=1}^n a_n \\ \sum_{i=1}^n a_n & n \end{pmatrix} \\ </script><br /> </pre> <br /> 2 x 2 행렬의 역행렬은 다음과 같이 간략화할 수 있으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> A = \begin{pmatrix}a & b \\ c & d \end{pmatrix} \\ A^{-1} = \frac{1}{ad - bc} \begin{pmatrix} d & -b \\ -c & a \end{pmatrix} </script><br /> </pre> <br /> A<sup>T</sup>A 결과의 역행렬을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> Q = ad - bc \\ \quad = \sum_{i=1}^n (a_n * a_n) * n - \sum_{i=1}^n a_n * \sum_{i=1}^n a_n \\ \\ \\ (A^T A) ^{-1} = \frac{1}{Q} \begin{pmatrix} n & -\sum_{i=1}^n a_n \\ -\sum_{i=1}^n a_n & \sum_{i=1}^n a_n * a_n \end{pmatrix} \\ \qquad = \begin{pmatrix} \frac{n}{Q} & \frac{-\sum_{i=1}^n a_n}{Q} \\ \frac{-\sum_{i=1}^n a_n}{Q} & \frac{\sum_{i=1}^n a_n * a_n}{Q} \end{pmatrix} </script><br /> </pre> <br /> 위의 결과를 2 x n 행렬의 A<sup>T</sup>와 연산을 하면 과정이 좀 복잡하니 어차피 행렬곱은 결합법칙이 성립하므로 뒤의 A<sup>T</sup> B 연산을 먼저 다음과 같이 정리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> A^T B = \begin{pmatrix} a_1 * b_1 + a_2 * b_2 + ... + a_n * b_n \\ b_1 + b_2 + ... + b_n \end{pmatrix} \\ \qquad = \begin{pmatrix} \sum_{1=1}^n a_n * b_n \\ \sum_{1=1}^n b_n \end{pmatrix} </script><br /> </pre> <br /> 마지막으로 (A<sup>T</sup>A)<sup>-1</sup>(2 x 2 행렬)에 A<sup>T</sup> * B(2 x 1 행렬)을 곱하는 것이므로 다음과 같이 최종 정리가 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> (A^T A) ^{-1} A^T B = \begin{pmatrix} \frac{n}{Q} & \frac{-\sum_{i=1}^n a_n}{Q} \\ \frac{-\sum_{i=1}^n a_n}{Q} & \frac{\sum_{i=1}^n a_n * a_n}{Q} \end{pmatrix} \begin{pmatrix} \sum_{1=1}^n a_n * b_n \\ \sum_{1=1}^n b_n \end{pmatrix} \\ \qquad = \begin{pmatrix} \frac{n}{Q} * \sum_{1=1}^n a_n * b_n + \frac{-\sum_{i=1}^n a_n}{Q} * \sum_{1=1}^n b_n \\ \frac{-\sum_{i=1}^n a_n}{Q} * \sum_{1=1}^n a_n * b_n + \frac{\sum_{i=1}^n a_n * a_n}{Q} * \sum_{1=1}^n b_n \end{pmatrix} \\ \qquad = \begin{pmatrix} \frac{n * \sum_{1=1}^n a_n * b_n - \sum_{i=1}^n a_n * \sum_{1=1}^n b_n}{Q} \\ \frac{ -\sum_{i=1}^n a_n * \sum_{i=1}^n a_n * b_n + \sum_{i=1}^n (a_n * a_n) * \sum_{1=1}^n b_n}{Q} \end{pmatrix} </script><br /> </pre> <br /> 따라서 1차 방정식의 매개변수는 이렇게 단일 식으로 각각 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <script type="math/tex"> \theta_1 = \frac{n * \sum_{1=1}^n a_n * b_n - \sum_{i=1}^n a_n * \sum_{1=1}^n b_n}{Q} \\ \\ \theta_0 = \frac{ -\sum_{i=1}^n a_n * \sum_{i=1}^n a_n * b_n + \sum_{i=1}^n (a_n * a_n) * \sum_{1=1}^n b_n}{Q} </script><br /> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 구하는 과정에 정리할 식이 좀 끼어들어서 그렇지, 사실 C# 코드로 위의 계산을 나타내면 별거 아닙니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 단순 for 루프를 이용한 계산 private static (double theta1, double theta0) <span style='color: blue; font-weight: bold'>GetEquation2</span>(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) <span style='color: blue; font-weight: bold'>GetEquation</span>(double[] xData, double[] yData) { Matrix<double> matA = CreateMatrix.DenseOfColumnMajor(xData.Count(), 1, xData); Vector<double> add1 = Vector<double>.Build.DenseOfArray(Enumerable.Repeat(1.0, xData.Count()).ToArray()); Matrix<double> matAwith1 = matA.InsertColumn(1, add1); Console.WriteLine(matAwith1); Matrix<double> matB = CreateMatrix.DenseOfColumnMajor(yData.Count(), 1, yData); Matrix<double> pinvMatA = matAwith1.PseudoInverse(); Console.WriteLine(pinvMatA); Matrix<double> matX = pinvMatA * matB; return (matX[0, 0], matX[1, 0]); } </pre> <br /> 당연하겠지만 행렬식을 이용했던 GetEquation 메서드와 비교해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { // y = theta0 + (theta1 * x) (double theta1, double theta0) = <span style='color: blue; font-weight: bold'>GetEquation</span>(xData, yData); Console.WriteLine($"[method1] y = {theta0} + {theta1} * x"); /* 출력 결과 <span style='color: blue; font-weight: bold'>[method1] y = 231.545758451005 + 1.39551018043075 * x</span> */ } { (double theta1, double theta0) = <span style='color: blue; font-weight: bold'>GetEquation2</span>(xData, yData); Console.WriteLine($"[method2] y = {theta0} + {theta1} * x"); /* 출력 결과 <span style='color: blue; font-weight: bold'>[method2] y = 231.545758451006 + 1.39551018043075 * x</span> */ } </pre> <br /> 부동소수점 계산임을 감안해 값이 거의 동일하다는 것을 알 수 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1461&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3828
(왼쪽의 숫자를 입력해야 합니다.)