글목록

2021년 4월 21일

Module 4. 다항식으로 일괄 Fitting (Regression) 하기 - (3)선형연립방정식 풀기 (Gauss Jordan 소거법)

(x,y) 데이터로부터 fitting하기 위해서, 마지막 단계에 선형연립방정식을 풀어야하는 문제가 있었고,이를 위해서는 역행렬을 구해야합니다.

엑셀에 n x n 정방행렬을 입력한 후, 역행렬을 구하기 위해서는 MInverse 함수를 사용할 수 있습니다. 역행렬 구하는 알고리즘을 만들고 매크로를 직접 작성하지 않더라도, 엑셀에서 제공하는 역행렬 함수를 이용하여 작업하기 위해서 작업용 시트를 만들고, 이 함수를 이용해서 역행렬을 구한 후, 다시 값을 읽어오는 방식으로 쉽게 역행렬을 구할 수도 있을 것입니다. 그러나, 이렇게 되면, 불필요한 시트를 만들고, 필요하면 다시 삭제하기를 반복해야하는 번거로움이 있습니다. 또한 계산은 빠를지 모르겠으나, 이러한 작업이 반복되다보면 새로운 object 작업이 포함되다 보니 전체 작업 속도는 오히려 저하될 수 있습니다.

수학적인 정의에 의해 역행렬을 구하려고 하면, 행렬의 크기에 따라 계산 횟수가 팩토리얼에 비례해서 기하급수적으로 늘어나고, 100x100 행렬이라도 구하려고 하면, 100! 횟수만큼 시간이 소요되니 계산이 거의 불가능하게 됩니다.

따라서, 빠른 계산을 위해 Gauss-Jordan 소거법을 이용하는 것이 좋습니다.

Gauss-Jordan 소거법은 n개의 변수를 가진 선형연립방정식을 풀기 위해, 한 개의 식에서 다른 식을 빼더라도 동일한 결과를 갖는다는 점에서 시작합니다.

위와 같이 2개의 식을 생각할 때, (1)에는 a11로, (2)에는 a21으로 나눠주어 (1'), (2')을 만들고, 다시 (2')에서 (1')을 빼주면, (2'')식에서는 x1이 소거됩니다. 이러한 방법으로 n개의 식에 반복하다보면, 1번식에는 n개, 2~n번째 식에는 n-1개의 x가 남게 됩니다.

이번엔 2~n번째 식에 대해서 동일한 방법으로 x2를 소거해나가면, 3~n번째 식에는 n-2개의 x가 남게 됩니다. 이런 방법으로 반복하다보면, 1번식은 n개, 2번식은 n-1개, ... , n번식에는 1개의 x가 남습니다.

이번엔 (n-1)번째 식에서 n번째 식을 빼주고, (n-2)번째 식에서는 (n-1)번째 식을 빼주는 방식으로 소거해나가다보면, 결국 1번 식에는 x1, 2번 식에는 x2,..., n번 식에는 xn만 남게 되므로 연립방정식의 해를 구하게 되는 것이지요.

얼핏 보기에는 식이 복잡해보여서 구현하기가 어려워보일 수 있습니다만, 저 식들을 그대로 사용하는 것이 아니라, 연산을 수행하는 순서만 정확하게 지정하면 계산은 컴퓨터가 수행하게 되니 걱정하실 필요는 없습니다. 다만 주의하실 점은, 곱하는 것은 상관없지만, 나누는 연산에 있어서는 '0'으로 나눠지는 상황이 발생하지 않는지 유의해야합니다.


위의 Gauss-Jordan 소거법을 이용해 선형연립방정식의 해를 구하도록 하겠습니다. 이를 위해 아래와 같이 3개의 함수를 작성하였습니다.

첫번째 함수는 임의의 행렬이 있을 때, 두 열의 위치를 바꾸는 함수이고, 두번재 함수는 특정 행에 대하여 Gauss Jordan 소거법을 실행하는 함수이며, 마지막 함수는 Gauss Jordan 소거법으로 전체 해를 구하는 함수입니다. 한개의 함수로 통째로 만들어도 되지만, 반복되는 구간을 함수로 만들어 코드를 짧고 간결하게 하기 위함입니다. 불필요하다고 느껴진다면 그냥 1개의 함수로 통합해도 상관은 없습니다.

'-----------------------------------------------------
Function MatOpr_CommuteRow(iMat(), iRow1 As Long, iRow2 As Long)
  Dim i As Long, tVal As Double
  '배열과 위치를 교환할 행 번호 2개를 입력받으면, 두 행의 위치만 교환합니다.
  For i = 1 To UBound(iMat, 2)
    tVal = iMat(iRow1, i)
    iMat(iRow1, i) = iMat(iRow2, i)
    iMat(iRow2, i) = tVal
  Next
End Function
'-----------------------------------------------------
Function GaussJordanStep(iMat(), iVec(), iNum As Long) As Boolean
  Dim i As Long, j As Long, tCheck As Boolean, tVal As Double
  '2차원 배열을 입력받습니다. iMat은 aij가 대입된 n x n 행렬이고, iVec은 bi값이 대입된 n x 1 행렬입니다. 1차원 배열로 바꾸셔도 상관없습니다만, 엑셀에 있는 데이터를 그대로 입력받고, 출력하려면 2차원 배열 형식을 그대로 쓰는 것이 활용면에서는 유리하기 때문에 이렇게 정의하였습니다.
  'iNum은 몇 번째 행에 대해 Gauss Jordan 소거법을 실행할지 지정하는 값입니다.

  '만약 입력된 iMat 행렬에서 iNum번째 대각선 값이 0이면, 이 위치의 값이 0이 아닌 다른 행과 교환합니다. 이때, iVec 도 함께 교환해줍니다.
  '모든 행에 대해 이 값이 0이라면, Determinant가 0이 되며, 연립방정식의 해가 없음을 의미하므로 작업을 중단하고, "해가 없음(False)"을 반환합니다.
  If iMat(iNum, iNum) = 0 Then
    tCheck = False
    For i = iNum + 1 To UBound(iMat, 1)
      If iMat(i, iNum) <> 0 Then
        MatOpr_CommuteRow iMat, i, iNum
        MatOpr_CommuteRow iVec, i, iNum
        tCheck = True
      End If
    Next
    If Not tCheck Then
      GaussJordanStep = False
      Exit Function
    End If
  End If
  '만약, iNum번째 대각선값이 0이 아니라면, 이 값으로 해당 행의 값들을 나눠주어, 대각선 값을 1로 만듭니다.
  tVal = iMat(iNum, iNum)
  For i = 1 To UBound(iMat, 2)
    iMat(iNum, i) = iMat(iNum, i) / tVal
  Next
  'iNum번 행을 제외한 나머지행에서 iNum번째 행을 빼주면 iNum번째 대각선값은 1이 되고, 해당 열의 나머지 행의 값은 0이 됩니다.
  iVec(iNum, 1) = iVec(iNum, 1) / tVal
  For i = 1 To UBound(iMat, 1)
    If i <> iNum Then
      tVal = -iMat(i, iNum)
      For j = 1 To UBound(iMat, 2)
        iMat(i, j) = iMat(i, j) + tVal * iMat(iNum, j)
      Next
      iVec(i, 1) = iVec(i, 1) + tVal * iVec(iNum, 1)
    End If
  Next
  GaussJordanStep = True
End Function
'-----------------------------------------------------
Function Solve_GaussJordan(iMat(), iVec(), oVec()) As Boolean
  Dim i As Long, tMat(), tVec(), tIsOK As Boolean
  tIsOK = True

  '입력된 iMat 행렬이 n x n 정방행렬인지 확인합니다.
  '만약, 입력된 행렬을 보존할 필요가 없다면, 굳이 tMat, tVec을 정의할 필요는 없습니다.
  If UBound(iMat, 1) <> UBound(iMat, 2) Then tIsOK = False: GoTo ErrorHandler
  tMat = iMat
  tVec = iVec

  '1번째 행부터 Gauss Jordan 소거법을 적용하게 되면, 모든 계산이 완료된 후 tMat은 단위행렬(대각선만 1인 행렬)이 되고, tVec은 연립방정식의 해가 됩니다. 만약, tVec을 n x n 행렬로 입력하도록 GaussJordanStep 함수를 약간 수정하게 되면, 역행렬을 계산하는 함수로 쉽게 변경이 가능합니다.
  'GaussJordanStep 행에서 오류가 반환되면 연립방정식의 해가 존재하지 않으며, 계산을 종료하고 False를 반환합니다.
  For i = 1 To 
UBound(iMat, 1)
    If Not GaussJordanStep(tMat, tVec, i) Then
      tIsOK = False
      Exit For
    End If
  Next
  If tIsOK Then oVec = tVec
ErrorHandler:
  Solve_GaussJordan = tIsOK
  Erase tMat, tVec
End Function
'-----------------------------------------------------


위의 함수들을 조금만 수정하면 역행렬을 계산하는 함수로도 쉽게 변경이 가능합니다. 다만, 현재의 목적은 Data fitting이기 때문에, 역행렬 계산이 아니라, 연립방정식의 해찾기 함수로만 작성해두었습니다.


댓글 없음:

댓글 쓰기

의견이나 질문이 있으신 분은 언제든지 댓글을 달아주세요~

많이 본 글 :