안정성(Stable), 불안정성(Unstable), 조건수(Condition Number)
수치적 안정성과 문제의 민감도
수치해석에서 특정 문제를 풀기 위한 알고리즘이 얼마나 안정적으로 오차를 제어하며 결과를 산출하는지는 매우 중요한 주제다. 여기서 안정성(Stable)이라는 것은 간단히 말하면, 알고리즘이 입력 데이터의 미세한 변화나 부동소수점 연산으로 인한 오차에 의해 결과가 크게 훼손되지 않고, 실제로 구하고자 하는 해와 가깝게 유지되는 성질을 의미한다. 반면 불안정성(Unstable)인 경우에는 입력이나 중간 연산에서 발생하는 작은 오차조차도 결과를 크게 왜곡시켜, 결국 큰 오차를 가진 해를 산출하게 된다.
문제 자체가 민감한가, 아니면 문제는 단단(Well-posed)하지만 이를 푸는 알고리즘이 민감한가를 구분해야 한다. 문제 자체의 민감도는 조건수(Condition Number)로 측정하며, 알고리즘의 민감도는 전방오차와 후방오차의 개념 등을 통해 구체적으로 기술된다. 이 장에서는 안정성과 불안정성, 그리고 문제의 민감도를 측정하는 지표인 조건수에 대해 체계적으로 다룬다.
전방오차와 후방오차
오차를 정의하는 방식에는 대표적으로 전방오차(Forward Error)와 후방오차(Backward Error)가 있다. 전방오차는 구하고자 하는 해의 실제 참값(정확해)과 수치해로부터 측정되는 차이를 직접 비교하는 방식이다. 예를 들어 어떤 문제의 해를 정확히 $\mathbf{x}$라 하고, 알고리즘으로부터의 근사해를 $\mathbf{\hat{x}}$라고 한다면, 전방오차는 일반적으로
와 같은 형태로 표현할 수 있다.
그러나 전방오차는 문제 자체가 얼마나 민감하게 입력 변화를 결과에 반영하는지에 따라 달라질 수도 있다. 실제 계산 과정에서 발생하는 절대 오차에만 집중하면, 문제의 스케일(scale)이나 구조에 따라 그 오차가 심각한지 아닌지를 제대로 판단하기 어려울 수 있다. 그래서 후방오차(Backward Error)라는 개념이 도입된다. 후방오차는 알고리즘이 구한 해 $\mathbf{\hat{x}}$가 정확해처럼 보이도록 입력을 얼마나 뒤로 물려서(조금 수정하여) 생각할 수 있는지를 의미한다. 즉, “구현된 알고리즘의 결과가 마치 정확한 해처럼 보이려면 실제 입력에서 얼마만큼 변동이 있어야 하는가”를 묻는다.
예를 들어 선형시스템 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 푸는 상황에서, 알고리즘의 결과가 $\mathbf{\hat{x}}$라 했을 때, 후방오차는 다음과 같이 생각할 수 있다. 실제로는 $\mathbf{A}\mathbf{x} = \mathbf{b}$이지만, 약간 변형된 $\mathbf{A} + \Delta \mathbf{A}$와 $\mathbf{b} + \Delta \mathbf{b}$에 대해
가 정확히 성립한다고 본다면, 이 때의 $|\Delta \mathbf{A}|$나 $|\Delta \mathbf{b}|$가 얼마만큼 작은지를 측정하는 것이 후방오차이다. 만일 $|\Delta \mathbf{A}|$와 $|\Delta \mathbf{b}|$가 작다면, “정확한 해”를 구하고자 했던 문제를 조금만 뒤틀어도(입력을 살짝 변경해도) 알고리즘이 내놓은 해가 실제 정확해가 되어버린다는 뜻이다. 이는 알고리즘이 “후방 안정적(Backward Stable)”이라는 강력한 성질을 가질 수 있음을 보여준다.
안정성(Stable)의 정의와 분류
수치적 안정성은 일반적으로 전방 안정성(Forward Stability)과 후방 안정성(Backward Stability)으로 구분한다. 전방 안정성이란 계산 결과와 참값이 입력 오차 대비 너무 크게 벗어나지 않음을 의미하며, 후방 안정성이란 수치적 해를 참값과 연결하기 위해 입력을 아주 조금만 바꿔주면 문제의 정확한 해가 될 수 있음을 의미한다.
전방 안정성의 정식 정의는 문제 및 해석의 맥락에 따라 달라질 수 있으나, 가장 기초적인 수준에서는 전방오차가 문제 스스로의 특성에 의해 허용되는 범위를 넘어 폭발적으로 증가하지 않는지 확인하는 과정이라고 볼 수 있다. 만일 전방오차가 매우 크게 나타난다면 알고리즘은 전방 불안정이라고 말할 수 있으며, 이는 궁극적으로 사용자가 원하는 해의 정확도 측면에서 문제를 일으킨다.
후방 안정성은 전방 안정성보다 더 엄격하고 이상적인 개념이다. 후방 안정성의 좋은 예시로는 고전적인 가우스 소거법(Gaussian Elimination)이 부동소수점 연산에서 피봇팅 전략 등을 적절히 적용했을 때 후방 안정성을 갖는다는 사실이 있다. 이는 부동소수점 연산에서 나타나는 작은 실수나 반올림 오차에도 불구하고, 결과적으로 보면 입력 행렬 $\mathbf{A}$와 벡터 $\mathbf{b}$를 아주 미세하게 변경한 경우의 정확해에 해당하는 해를 구했다는 의미가 된다.
불안정성(Unstable)
알고리즘이 불안정하다는 말은, 대부분의 합리적인 크기의 입력 혹은 미소한 오차에도 계산 결과가 크게 요동치거나, 전혀 다른 해를 산출하거나, 심지어 발산해버리는 경우를 말한다. 이는 문제 자체가 매우 민감한 경우(조건수가 큰 경우)도 있을 수 있고, 문제는 원래 양호하지만 알고리즘 구현이 잘못되었거나 부동소수점 환경에서 중간 연산의 오차가 크게 증폭되도록 설계된 경우도 있다.
특히 반복 알고리즘(iterative method)에서는 누적되는 부동소수점 연산 오차나 각 단계에서의 축적된 반올림 오차가 결과를 왜곡시킬 위험이 높다. 만일 이 알고리즘이 후방 안정성을 전혀 보장하지 못한다면, 작은 입력 변화나 연산 누적으로 인해 최종적인 해가 크게 달라질 수 있어 불안정하다고 평가할 수 있다.
조건수(Condition Number)의 개념
조건수는 문제 자체의 민감도를 수치로 나타낸 것이다. 문제의 입력에 아주 작은 변화가 가해졌을 때, 출력 결과가 얼마나 크게 변하는지 정량적으로 평가한다. 예를 들어 스칼라 함수 $f(x)$의 경우, 입력 $x$가 $\Delta x$만큼 변할 때 출력 $f(x)$가 얼마나 변하는지에 대한 상대 변화비를 통해 조건수를 정의할 수 있다.
선형시스템 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 예시로 들어, $\mathbf{x} = \mathbf{A}^{-1}\mathbf{b}$라 할 때, $\mathbf{A}$나 $\mathbf{b}$가 미세하게 바뀌었을 때 $\mathbf{x}$가 얼마나 민감하게 변하는지를 보고 문제의 조건수를 측정한다. 특히 $\mathbf{A}$만 변한다고 보고, $\mathbf{b}$는 고정한다고 하면, 문제의 조건수는 일반적으로
로 정의한다. 행렬 노름 $|\cdot|$을 무엇으로 잡느냐에 따라 조건수는 달라질 수 있으나, 보통은 2-노름을 많이 사용한다.
조건수가 크다는 것은 행렬 $\mathbf{A}$가 역행렬과 곱했을 때 매우 큰 노름을 가진다는 의미이며, 그만큼 해석적으로 입력의 작은 변화가 큰 결과 변화를 야기할 수 있음(민감함)을 뜻한다. 이는 문제 자체가 나쁘게 자리잡혀(ill-conditioned) 있음을 나타낸다. 반면 조건수가 작으면(특히 1에 가까우면) 문제 자체가 잘 자리잡혀(well-conditioned) 있으며, 입력 변경에 대해 해가 비교적 안정적으로 반응한다는 의미다.
문제의 구조와 조건수
문제 자체의 특성으로 인해 입력 변화에 대한 해의 민감도가 높다면, 그 문제는 ill-conditioned되었다고 말한다. 스칼라 입력과 출력을 갖는 단순한 함수 $f(x)$에 대해서도 조건수 개념을 적용할 수 있다. 예를 들어 $x$에서의 상대오차와 $f(x)$에서의 상대오차를 비교하여,
와 같은 방식으로 조건수를 정의할 수 있다. 만약 이 값이 크면 $f(x)$가 입력 $x$의 작은 변화에 대해 매우 크게 반응한다는 뜻이므로, $f(x)$를 계산하는 문제는 ill-conditioned되었다고 말할 수 있다.
선형 연산이 아닌 상황에서도, 예컨대 비선형 방정식을 해석하거나 최적화 문제를 다룰 때에도 조건수 개념은 동일하게 적용된다. 입력 공간에서의 미세한 변경이 출력(또는 해, 혹은 목표 함수값)에 큰 변화를 일으키는지 여부를 파악하고, 이를 적절히 계량화하는 다양한 방법이 존재한다. 이러한 민감도 측정은 단지 수치해석에 국한되지 않고, 모델링과 데이터 분석 전반에 걸쳐 통찰을 제공한다.
Ill-Posed와 Ill-Conditioned
일부 문헌에서는 ill-posed 문제와 ill-conditioned 문제를 구분한다. 고전적 정의에서 Hadamard는 수학적 문제가 다음 조건들을 만족해야 well-posed라고 하였다: 해가 존재한다(existence), 해가 유일하다(uniqueness), 그리고 입력의 작은 변화에 대해 해가 연속적으로 변한다(stability). 반면 이 세 가지 요건 중 하나라도 어긋나면 ill-posed 문제가 된다. 그런데 실제 응용에서는 문제 자체가 해의 존재와 유일성을 만족하더라도, 민감도가 지나치게 커서 수치적으로 취급하기 까다로운 상황이 흔히 생긴다. 이 때 문제는 well-posed이되, ill-conditioned되었다고 한다.
즉, 문제 자체가 존재성과 유일성을 제대로 갖추고 있고 부드러운 연산 특성을 지닌다고 하더라도, 그 입력 데이터가 실용적인 계산 환경에서 매우 크게 왜곡될 수 있다면, 결국 실제 수치 계산에서는 해를 정확히 구하기 어려울 수 있다. 이런 문제 구조에서는 아무리 좋은 알고리즘을 쓰더라도 불가피하게 큰 전방오차가 나타난다. 따라서 조건수를 계산해보면 매우 큰 값을 얻는다. 이는 문제 자체에 잠재된 민감도가 크다는 의미다.
다중근(Multiple Root)과 민감도
비선형 방정식에서 입력 매개변수에 따라 해가 변하거나, 해 자체가 다중근(multiple root)을 갖는 경우 해는 특별히 더 민감해질 수 있다. 예를 들어 단순 1차 근이 아닌 2차 이상의 근을 갖는다는 것은, 함숫값의 변화가 해 근방에서 완만하게 움직인다는 의미이며, 그만큼 근 위치가 입력이나 파라미터 변경에 대해 민감해질 수 있다. 이런 경우 문제의 조건수를 구하고 나면 일반적인 단순근(single root)에 비해 훨씬 크게 나타난다. 이는 민감도가 높은, 즉 ill-conditioned 상황임을 의미한다.
알고리즘 수준에서의 안정화 기법
문제가 ill-conditioned된 경우, 아무리 후방 안정적인 알고리즘을 적용해도 결과가 크게 달라질 수 있다. 이때 인위적으로 문제를 조건이 좋게끔 만들거나, 혹은 다른 계산 방식으로 접근하여 민감도를 줄이는 기법들이 사용된다. 예를 들어 선형시스템에서는 행렬 조건을 개선하기 위해 사전조건화(Preconditioning)를 적용한다. 사전조건화 매트릭스 $\mathbf{M}$를 곱하여 $\mathbf{M}\mathbf{A}\mathbf{x} = \mathbf{M}\mathbf{b}$와 같이 바꾼 뒤, $\mathbf{M}\mathbf{A}$가 $\mathbf{A}$에 비해 훨씬 잘-conditioned되도록 조정하면 수치해석적 안정성이 개선된다.
또 다른 예로, 다항식을 직접 전개한 형태로 평가하기보다는 호너(Horner) 방법을 사용함으로써 중간 계산에서 발생하는 오차를 크게 줄일 수 있다. 호너 방법은 후방 안정적이라고 알려져 있는데, 이는 각 단계의 연산 횟수가 적고, 연산 과정에서 오차가 증폭될 위험을 완화하기 때문이다.
예시: 호너(Horner) 방법
차수가 $n$인 다항식
를 평가한다고 하자. 만약 이를 정의 그대로 계산한다면, $x^k$ 항을 차례대로 제곱(곱셈)으로 구해가면서 $a_k x^k$를 모두 더해야 한다. 이 방법은 중간 단계에서 큰 수나 매우 작은 수가 빈번히 등장할 수 있고, 그 과정에서 상대오차가 크게 커질 위험이 있다.
반면 호너 방법은
와 같은 반복적 구조로 풀어내므로, 계산 과정에서 곱셈과 덧셈이 효율적으로 결합된다. 결과적으로 연산 횟수가 줄고, 부동소수점 연산에서 발생할 수 있는 소수점 반올림이나 오버플로/언더플로의 위험이 상당히 감소한다. 이 때문에 호너 방법은 후방 안정적이며, 수치적 견고함을 갖춘 방법으로 알려져 있다.
반복법(Iterative Method)의 안정성
선형시스템을 반복법으로 푸는 경우나, 비선형방정식을 뉴턴(Newton) 방법 등으로 푸는 경우에도 안정성과 민감도를 모두 고려해야 한다. 예컨대 뉴턴 방법은 수렴 속도가 매우 빠르다는 장점이 있으나, 초기값 선택이나 함수의 구조에 따라 수렴 반경 밖에서 발산하거나, 중간 계산에서 수치적 오차가 증폭될 가능성도 있다. 이 때문에 뉴턴 방법이 수치적으로 안정적으로 동작하게끔 하는 다양한 변형 기법들이 연구되었다.
스테핑(stepping) 전략을 조금 더 보수적으로 가져가거나, 적응형(Adaptive) 기법으로 점근해가면서 민감도 문제를 극복하려는 시도가 이루어진다. 반복법이 갖는 근본적인 누적 오차는, 각 스텝에서 발생하는 반올림 오차가 계속 쌓이기 때문에 쉽게 무시할 수 없다. 그러나 후방 안정적 성질을 부분적으로라도 만족하거나, 적절한 사전조건화 또는 재정규화(Renormalization)를 통해 오차 축적을 줄이는 방법을 모색할 수 있다.
반올림 오차(Rounding Error)와 유효숫자(Significant Figures)
실제 계산 환경에서는 컴퓨터 내부에 유한한 자릿수로 실수를 표현하기 때문에, 어떠한 산술 연산에서도 미세한 반올림 오차가 발생한다. 이 반올림 오차가 여러 단계의 연산을 거치며 누적되면, 알고리즘의 전반적 안정성에 영향을 미치게 된다. 반올림 오차가 매우 작은 경우라면 일반적으로 크기가 무시될 수도 있지만, 수많은 반복 연산 혹은 매우 큰 수와 매우 작은 수가 섞여 있는 연산을 수행할 때에는 이러한 작은 오차도 계속 축적되며 최종 결과를 현저하게 왜곡할 수 있다.
유효숫자(Significant Figures)는 실수 연산에서 정확하게 표현될 수 있는 숫자의 자릿수를 의미한다. 예컨대 가령 3자리의 유효숫자를 지원한다고 하면, 소수점 앞뒤를 모두 포함하여 3자리까지만 정확히 표현이 가능하다는 뜻이다. 이때 3자리 이상의 정보가 필요한 상황이라면, 반올림이나 절삭(truncation) 과정을 통해 오차가 발생한다. 문제의 민감도(Conditioning)가 크면, 이렇게 사소해 보이는 유효숫자 한두 자리의 차이가 결과에 치명적인 악영향을 줄 수 있다.
오차 전파(Error Propagation)와 누적(Cumulative) 오차
소수점 연산에서 생긴 미세한 오차가 하나의 연산 단계에 머무르는 것이 아니라, 다음 단계의 입력으로 계속 전달되면서 확대될 수도 있다. 이를 오차 전파(Propagation)라고 하며, 결과적으로 점진적으로 커지는 누적(Cumulative) 오차를 유발한다. 누적 오차가 심해지면 알고리즘 전반이 불안정하게 되어, 전방오차가 폭발적으로 증대되거나 계산 결과가 발산해버릴 수도 있다.
예컨대, 합을 구하는 과정에서 매우 큰 수에 매우 작은 수를 더하면, 작은 수의 정보가 부동소수점 표현 한계로 인해 소실되어버릴 수 있다(소위 Catastrophic Cancellation). 이때 그 손실된 정보가 이후 과정에서 중요한 역할을 해야 한다면, 최종 결과가 심각하게 왜곡될 수 있다.
전방오차와 후방오차의 경계: 혼합(Mixed) 오차 분석
수치해석에서는 전방오차와 후방오차를 명확히 구분하지만, 실제 상황에서는 이 둘이 밀접하게 연결되어 있는 경우가 많다. 예컨대 “어떠한 알고리즘이 후방 안정적”이라고 해도, 문제 자체가 ill-conditioned라면 결국 전방오차가 크게 나타나므로, 사용자 입장에서 보면 “해가 틀렸다”고 느낄 수 있다. 따라서 혼합(Mixed) 오차 분석에서는 전방오차와 후방오차를 모두 고려하는데, 실제 응용에서는 어떠한 정도의 입력 변동이 허용 범위 내인지, 혹은 알고리즘이 얼마만큼의 후방오차를 수반하는지 등을 종합적으로 살펴야 한다.
특히 대규모 과학·공학 계산에서, 사전조건화(Preconditioning)나 적응형 스케일링(Adaptive Scaling)과 같은 기법을 적용하여 문제 자체 혹은 중간 연산에서의 상태를 개선하면, 후방오차와 전방오차 간의 괴리가 줄어드는 사례가 많다. 이런 측면에서 안정성을 단순히 알고리즘만의 특성으로 여길 것이 아니라, 문제 설정 및 전처리(preprocessing)의 측면에서도 함께 고찰해야 한다.
선형대수 문제에서의 조건수
선형 시스템 $\mathbf{A}\mathbf{x} = \mathbf{b}$뿐만 아니라, 고유값 문제나 고유벡터 문제도 중요한 선형대수 문제다. 예를 들어 $\mathbf{A}\mathbf{v} = \lambda \mathbf{v}$에 대한 해 $\lambda$와 $\mathbf{v}$에 대해 민감도를 살펴보려면, 행렬 $\mathbf{A}$의 고유값 구조와 분해(Spectral Decomposition)를 분석해야 한다. 어떤 고유값이 다른 고유값과 매우 근접하게 존재하거나(근접한 고유값), 혹은 Jordan 블록 구조로 인해 고유벡터가 서로 독립적이지 못한 상황이면, 해당 고유값과 고유벡터는 미세한 입력 변화에도 크게 요동칠 수 있다. 이때 고유값 혹은 고유벡터에 대한 조건수를 크게 갖는 것으로 해석한다.
예를 들어 단순화를 위해, $\mathbf{A}$가 대각화 가능(diagonalizable)하다고 하자. $\mathbf{A} = \mathbf{V}\mathbf{D}\mathbf{V}^{-1}$ 형태에서 $\mathbf{D}$는 고유값들을 대각성분으로 가진 행렬, $\mathbf{V}$는 고유벡터들을 열벡터로 모은 행렬이다. 이때 $|\mathbf{V}^{-1}||\mathbf{V}|$ 등이 매우 큰 값이면, 고유벡터들이 서로 거의 평행해 있을 정도로 종속적인 상태이므로, 미세한 교란에도 고유값·고유벡터가 크게 바뀔 가능성이 높다. 이를 고유분해(Eigendecomposition)의 ill-conditioning이라 부르며, 고유값·고유벡터 문제에 대한 수치해석적 안정성에 직접적인 영향을 미친다.
Loss of Significance와 회피 전략
Loss of significance(유효숫자 손실)은 두 개의 거의 같은 값의 차이를 계산할 때 그 앞자리(고차항) 정보가 소실되어, 결과적으로 매우 작은 유효숫자만이 남아버리는 현상을 말한다. 예컨대
이라 하자. 실제로 $x - y$는 $0.0001$ 정도이지만, 부동소수점 표현에서 $x$와 $y$를 저장하는 과정에서 이미 마지막 자리 반올림이 존재할 수 있고, 이들의 차이가 기대했던 정밀도를 충분히 확보하지 못하는 상태로 계산될 수 있다.
이때 알고리즘 설계에서 Loss of significance를 줄이는 다양한 재배열(Rearrangement) 기법이나, 치환(Substitution) 기법을 적용할 수 있다. 예를 들어, 다항식의 근을 직접 구할 때는 근의 상대적 크기에 맞추어 방정식의 형태를 변형하거나, 루틴(함수 호출) 단에서 더 안전한 연산순서를 취하도록 설정할 수 있다.
일상적인 예: 로그함수와 지수함수
로그함수나 지수함수는 대체로 부동소수점 연산에 안정적인 편으로 여겨지지만, 극단적인 범위로 입력이 치우칠 경우(매우 큰 값 또는 매우 작은 값) 여전히 오버플로나 언더플로가 발생할 수 있다. 이를 회피하기 위해, 실제로 많은 계산 루틴에서는 $\log$나 $\exp$ 연산 시 입력 범위를 미리 점검하고 스케일링을 가해준다.
예를 들어, 지수함수 $e^x$를 계산해야 하는데 $x$가 음수로 매우 큰 값(예: $-1000$)이라면, 바로 부동소수점 연산을 수행할 때 언더플로로 인해 $0$에 수렴해버릴 위험이 크다. 그래서 흔히 $e^x$가 등장하는 식 전체를 변형하여, 불필요한 언더플로나 오버플로를 피하는 전략을 쓴다. 이런 점에서, 알고리즘적 안정성(후방 안정성)뿐 아니라 함수 자체가 갖는 민감도, 계산 환경에서의 표현 능력 등 모두가 수치안정성의 관점에서 종합적으로 고려되어야 한다.
가우스 소거법(Gaussian Elimination)과 피봇팅(Pivoting)
선형시스템 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 푸는 가장 대표적인 직접법(direct method)으로 가우스 소거법이 있다. 이 방법은 연립방정식을 단계적으로 소거(Elimination)하면서 삼각화된 형태를 얻고, 이후 후진대입(Back Substitution)을 통해 해 $\mathbf{x}$를 구한다. 가우스 소거법이 부동소수점 환경에서 안정적으로 동작하려면, 피봇팅(Pivoting)이 필수적이다. 간단히 말해, 소거 연산에서 사용되는 피봇이 너무 작으면 연산 과정에서 반올림 오차가 크게 증폭될 수 있으므로, 행이나 열을 재배열하여 더 큰 절댓값 피봇을 선택한다.
대표적으로 부분 피봇팅(Partial Pivoting)은 각 단계에서 현재 열을 기준으로 가장 큰 절댓값을 갖는 원소를 피봇으로 가져오고, 그 행을 위로 올린다. 반면 완전 피봇팅(Complete Pivoting)은 행뿐 아니라 열도 재배열하여 최댓값을 피봇으로 삼는다. 이 과정을 통해, $\mathbf{A}$의 행렬 요소 중 아주 작은 값으로 인해 발생할 수 있는 극단적 반올림 오차를 완화할 수 있다.
가우스 소거법은 적절히 피봇팅이 수행되면 후방 안정적(Backward Stable)으로 알려져 있다. 즉, 결과적으로 보면 문제의 입력(행렬 $\mathbf{A}$와 벡터 $\mathbf{b}$)을 아주 미세하게 변경한 시스템의 정확해를 구하는 셈이 된다. 하지만 문제의 조건수가 매우 크면(ill-conditioned) 전방오차가 여전히 크게 발생할 수 있으므로, 해석적으로는 오차 분석을 좀 더 정밀하게 수행할 필요가 있다.
QR 분해(QR Decomposition)와 최소제곱문제(Least Squares Problem)
최소제곱(Least Squares) 문제인
를 해결할 때, 고전적으로 정규방정식(Normal Equation) $\mathbf{A}^\top \mathbf{A}\mathbf{x} = \mathbf{A}^\top \mathbf{b}$을 세워서 해를 구하기도 한다. 그러나 $\mathbf{A}^\top \mathbf{A}$가 원래의 $\mathbf{A}$에 비해 조건수가 훨씬 더 커질 수 있으므로, 수치적으로 불안정해질 위험이 있다.
이런 이유로 $\mathbf{A}$를 직교행렬(Orthogonal) $\mathbf{Q}$와 상삼각행렬(Upper Triangular) $\mathbf{R}$로 분해하는 QR 분해 방법이 널리 쓰인다. 즉,
로 표현해두고, 최소제곱 문제를
로 바꾸면, 직교성(orthogonality)에 의해 오차 증폭이 일어나지 않으므로 훨씬 안정적으로 문제를 풀 수 있다. 실제 계산에서는 하우스홀더(Householder) 반사나 기븐스(Givens) 회전 같은 고전적 방법들이 QR 분해를 구현할 때 사용된다. 이들은 모두 후방 안정성을 목표로 설계되어, 정규방정식을 직접 푸는 것보다 훨씬 믿을 만한 결과를 제공한다.
다른 분해 기법과 안정성
가우스 소거법(또는 LU 분해)나 QR 분해 외에도, SPD(대칭 양의 정부호) 행렬에 대해서는 촐레스키(Cholesky) 분해를 사용할 수 있다. 촐레스키 분해란, SPD 행렬 $\mathbf{A}$를 하삼각행렬 $\mathbf{L}$과 그 전치 $\mathbf{L}^\top$의 곱
로 나타내는 것이다. 이 방법은 LU 분해보다 계산량이 절반가량 적고, 수치적 안정성도 우수하다는 장점이 있다. 다만 $\mathbf{A}$가 SPD가 아닐 경우에는 바로 사용할 수 없으므로, 사전조건화 과정을 통해 SPD 형태로 만드는 전략이 종종 병행된다.
특히 공분산 행렬(Covariance Matrix) 등의 구조를 가진 양의 정부호 행렬을 다룰 때 촐레스키 분해가 널리 활용되며, 부동소수점 환경에서도 상대적으로 안정적으로 동작하는 것이 알려져 있다. 하지만 문제의 조건수가 이미 매우 크다면, 촐레스키 분해 자체도 오차에 취약해질 수 있으므로, 사전에 행렬을 적절히 스케일링하거나, 레귤러라이제이션(Regularization) 기법을 도입해 문제를 완화해야 한다.
역행렬 explicit 계산의 위험성
수치해석에서 직관적으로 $\mathbf{x} = \mathbf{A}^{-1}\mathbf{b}$를 써서 해를 구한다는 생각을 떠올리기 쉽다. 그러나 실제로는 $\mathbf{A}^{-1}$를 직접 구하는 과정이 수치적으로 상당히 불안정해질 수 있다. 역행렬을 구하는 과정에서 반올림 오차가 집중적으로 발생할 수 있고, 구해진 $\mathbf{A}^{-1}$ 자체의 표현이 ill-conditioned 문제에 대해 극단적으로 민감한 형태를 띌 수 있기 때문이다.
따라서 실무에서는 “해를 구하기 위해 역행렬을 직접 계산하지 말라”는 원칙이 자주 제시된다. 만약 꼭 필요한 경우(특수한 해석 목적 등)라면, 그 수치적 위험성을 인지하고 충분한 대응책(예: 고정밀 연산, 사전조건화, 재검증)을 마련해야 한다.
사전조건화(Preconditioning)의 효과
반복법을 이용하여 $\mathbf{A}\mathbf{x} = \mathbf{b}$를 풀 때, 사전조건화(Preconditioning)를 적용하면 문제의 조건수가 급격히 감소하여 수렴 속도와 안정성이 크게 개선될 수 있다. 사전조건화 행렬 $\mathbf{M}$를 미리 계산한 뒤,
형태로 문제를 변형하는 것이다. 이때 $\mathbf{M}\mathbf{A}$가 훨씬 잘-conditioned되도록 $\mathbf{M}$를 선택하면, 반복 과정에서 오차가 과도하게 증폭되는 현상이 완화되고, 수렴 횟수도 줄어든다.
사전조건화 방식에는 단순 대각행렬(가벼운 형태)의 Jacobi 사전조건부터, Incomplete LU(ILU) 분해 등을 이용한 좀 더 무거운 사전조건 등이 다양하게 존재한다. 사전조건화를 설계하는 것은, 해당 문제의 구조(행렬의 희소성, 대각우세성 여부, 대칭성 여부 등)에 따라 최적 해법이 다르므로, 문제 도메인에 대한 이해가 필요하다.
수치적 랭크(Numerical Rank)와 계수행렬의 열 독립성
행렬 $\mathbf{A}$의 실제 랭크(Rank)는 이론적으로 특정 차원을 갖지만, 부동소수점 연산에서는 “수치적으로 유의미하지 않은” 아주 작은 특잇값(Singular Value)이나 고유값에 의해 결정되는 유효 랭크(Effective Rank)가 따로 존재한다. 이를 수치적 랭크(Numerical Rank)라고 부르며, 매우 작은 특잇값들을 0으로 간주하면 실제 랭크보다 낮은 차원의 랭크로 볼 수 있다.
이런 일이 벌어지는 원인은, 미세한 오차나 문제가 ill-conditioned되었을 때 열 또는 행 벡터들이 실제로는 독립적이지만, 부동소수점 표현 상 거의 선형종속처럼 보이기 때문이다. 이를 정확히 간주하지 않고, 실제 이론적 랭크를 전제로 계산을 진행하면 해가 크게 흔들릴 수 있다. 따라서 대규모 행렬 계산 시에는 SVD(특잇값 분해)나 다른 안정적 방법을 통해 수치적 랭크를 파악한 뒤, 실제로 다루어야 할 유효 차원을 정제(Truncation)하는 전략을 쓸 때가 많다.
다항식 근사와 멱평균(Lanczos, Krylov) 방법
고차 다항식이나 고차원 행렬에 대한 반복 알고리즘을 설계할 때, 직접 모든 고차 항을 계산하지 않고, Krylov 하위공간(Krylov Subspace)을 활용하는 방법이 있다. 예를 들어, 대규모 희소 행렬에 대한 고유값 계산을 할 때, 랜초스(Lanczos) 방법이나 Arnoldi 방법 같은 알고리즘을 적용하면 일부 극단적 고유값과 고유벡터를 상대적으로 적은 연산으로 근사할 수 있다.
이때에도 중요한 것은 수치 안정성이다. Krylov 하위공간을 반복 구축하는 과정에서 정규화나 재직교(Reorthogonalization)를 적절히 수행하지 않으면, 부동소수점 반올림 오차가 쌓여서 서로 다른 방향 벡터들이 수치적으로 거의 동일해지는 사태가 벌어진다. 이 경우 얻어진 결과가 허수에 가까운 잡음을 포함하거나, 진정한 고유값과 고유벡터가 아니라 단지 오차가 증폭된 가짜 모드를 찾는 상황이 될 수 있다.
고정밀(Fixed Precision) vs. 가변정밀(Variable Precision)
대부분의 프로그래밍 환경에서는 배정도(double precision)나 단정도(single precision)를 쓰지만, 때로는 이를 넘어서는 고정밀 연산(Extended Precision)이나 가변정밀(Arbitrary Precision) 라이브러리를 사용해야 할 때가 있다. 예컨대 매우 높은 정확도가 필요한 과학 계산, 다항식의 루트가 서로 극단적으로 가까운 다중근 구조, 특잇값이 극도로 작은 행렬, 혹은 금융·보안 계산 등에서는 표준 배정도로는 충분하지 않을 수 있다.
가변정밀 방식은 연산마다 요구되는 정밀도를 동적으로 조절할 수 있으나, 연산 비용이 커지므로 대규모 문제에선 신중한 접근이 필요하다. 그래도 문제의 민감도가 매우 큰 상황에서, 단순히 배정도 연산을 반복하는 것보다 한 번의 가변정밀 연산으로 정확한 값을 얻어내는 편이 계산 효율이나 안정성 면에서 더 유리할 수 있다. 이런 trade-off를 잘 고려하여 알고리즘과 정밀도를 결합할 때, 수치적 안정성 면에서 더욱 견고한 해법을 구할 수 있다.
반복적 개선(Iterative Refinement)과 잔차 분석(Residual Analysis)
선형시스템이나 기타 수치 문제를 풀 때, 일차적으로 얻어진 근사해에 대해 반복적으로 수정(Refinement)을 진행해 해를 개선하는 기법이 있다. 이를 반복적 개선(Iterative Refinement)이라 하며, 잔차(Residual) 즉 $\mathbf{r} = \mathbf{b} - \mathbf{A}\mathbf{\hat{x}}$를 통해 현재 해의 정확도를 가늠하고, 부동소수점 연산으로 인한 오차를 보정해나가는 방법이다.
반복적 개선 방식의 대표적 예는 선형시스템에서 다음 절차를 밟는 것이다. 우선 $\mathbf{A}\mathbf{x} = \mathbf{b}$ 문제를 풀어 근사해 $\mathbf{\hat{x}}$를 구한다. 그 뒤 잔차 $\mathbf{r} = \mathbf{b} - \mathbf{A}\mathbf{\hat{x}}$를 계산한다. 이 $\mathbf{r}$에 대해 다시 $\mathbf{A}\mathbf{d} = \mathbf{r}$를 푼 뒤, $\mathbf{\hat{x}} \leftarrow \mathbf{\hat{x}} + \mathbf{d}$ 형태로 보정한다. 이렇게 하면, 부동소수점 연산에서 손실된 낮은 자리수 정보가 잔차를 통해 다시 보완될 수 있으므로, 전반적인 정밀도가 향상될 수 있다. 물론 문제의 조건수가 지나치게 크면 잔차 계산 또한 정확하지 못해, 개선 효과가 충분히 나지 않을 수도 있다.
SVD(특잇값 분해)와 수치해석의 안정성
$\mathbf{A} \in \mathbb{R}^{m \times n}$에 대해 특잇값 분해(SVD)를 하면,
형태로 나타낼 수 있다. 여기서 $\mathbf{U} \in \mathbb{R}^{m \times m}$와 $\mathbf{V} \in \mathbb{R}^{n \times n}$는 직교행렬(Orthogonal Matrices)이고, $\mathbf{\Sigma}$는 대각 요소에 특잇값(singular values) $\sigma_1 \ge \sigma_2 \ge \dots \ge 0$을 나열한 사각 대각 행렬이다. 이 특잇값들은 행렬 $\mathbf{A}$의 다양한 수치적 성질을 집약적으로 보여주며, 특히 첫 번째 특잇값 $\sigma_1$와 마지막 특잇값 $\sigma_{\min}$(0이 아닌 최소 특잇값)을 이용해
와 같은 2-노름 조건수를 구할 수 있다.
SVD는 행렬의 수치적 랭크(Numerical Rank) 판단이나 최소제곱 문제, 압축(Compression), 노이즈 제거(Denoising) 등 다양한 응용에서 쓰이는데, 가장 큰 장점 중 하나가 수치적 안정성이다. SVD는 후방 안정적인 알고리즘으로 구현 가능하기 때문에, 적절한 라이브러리를 이용해 계산하면 ill-conditioned된 문제에서도 상대적으로 신뢰도 높은 해석 결과를 얻을 수 있다. 다만 연산량이 $O(mn \min(m,n))$ 정도로 매우 커서, 대규모 문제에는 직접 적용하기가 부담스러울 수 있다.
일반화 고유값 문제와 부분공간 분해
행렬 쌍 $(\mathbf{A}, \mathbf{B})$가 주어졌을 때, 일반화 고유값 문제 $\mathbf{A}\mathbf{x} = \lambda \mathbf{B}\mathbf{x}$를 풀어야 하는 상황이 있다. 이 문제 또한 입력(특히 $\mathbf{B}$가 거의 특이(singular)에 가까운 경우)에 따라 해가 매우 민감해질 수 있다. 이때에도 SVD나 QR, 혹은 다른 정교한 분해를 통해 $\mathbf{B}$를 적절히 사전조건화하거나, 순차적으로 우선순위가 높은 특잇값·고유값만을 뽑아내는 방법(예: Krylov 하위공간 기반)이 동원된다.
특히 대규모 Sparse 행렬을 다루는 영역에서는 부분공간(subspace) 기법으로 순환하면서 극단적 고유값을 찾는 Lanczos, Arnoldi, Jacobi-Davidson 계열 알고리즘이 널리 쓰인다. 이들 알고리즘도 수치 안정성을 확보하기 위해서, 매 스텝에서 정규화나 재직교(Reorthogonalization)를 주기적으로 수행함으로써, 벡터 간 선형독립성을 최대한 유지하려 한다. 그러나 문제의 조건수가 지나치게 크거나, 고유값이 뭉쳐(clustered) 있다면, 작은 반올림 오차도 결과를 크게 왜곡할 수 있으므로 경계해야 한다.
연립비선형시스템과 민감도
연립비선형시스템 $\mathbf{F}(\mathbf{x}) = \mathbf{0}$을 해석할 때, $\mathbf{F}$의 야코비(Jacobian) 행렬을 기반으로 한 뉴턴(Newton) 방법 또는 준-뉴턴(Quasi-Newton) 방법이 자주 활용된다. 이때 근방정식의 민감도는 야코비 $\mathbf{J}(\mathbf{x})$가 얼마나 조건수가 큰지에 달려 있다. 즉, $\mathbf{x}$ 부근에서 $\mathbf{J}(\mathbf{x})$가 거의 특이(singular)하거나 고윳값이 극단적으로 작다면, 한 번의 업데이트에 대한 작은 교란이 해 전체를 뒤엎을 수 있다.
뉴턴 방법은 국소적으로 2차 수렴을 가지지만, 초기값 선택이 나쁘거나 야코비이 ill-conditioned되면 수렴하지 않거나 수렴하더라도 중간 계산에서 반올림 오차가 증폭되어 해가 부정확해질 수 있다. 이를 방지하기 위해, 선탐색(Line Search) 또는 댐핑(Damping) 기법을 결합하여 스텝 크기를 조절하거나, 헤시오(Hessian) 근사를 보정하는 BFGS나 SR1 같은 준-뉴턴 방식이 적용된다. 이런 보조 장치들은 모두 반복 과정이 불안정해지는 현상을 완화함으로써, 후방오차를 어느 정도 통제하려는 목적을 가진다.
편미분방정식(PDE) 수치해석에서의 안정성
PDE를 해석할 때는 유한차분(Finite Difference), 유한요소(Finite Element), 유한체적(Finite Volume) 등 다양한 방법을 사용할 수 있다. 이때 시간이나 공간을 분할하여 이산화(Discretization)한 뒤, 결과적으로 매우 큰 차원의 선형·비선형시스템을 풀어야 한다. 이 전과정에서 수치 안정성 개념이 핵심적이다.
예컨대 시간종속 PDE에 대한 유한차분 스킴을 구성할 때, 적절한 시간 스텝 크기와 공간 격자 크기를 정하지 않으면 수치해가 발산(Divergence)하거나 의미 없는 진동(Oscillation)을 일으킬 수 있다. Von Neumann 안정성 분석이나 CFL(Courant–Friedrichs–Lewy) 조건 등을 통해, 주어진 스킴이 안정하게 동작하기 위한 제약을 도출한다. 이때 문제의 구조(유동 방정식, 파동 방정식, 반응-확산 시스템 등)에 따라 고유한 안정성 조건이 있고, 이는 곧 알고리즘이 미세한 교란이나 반올림 오차를 어떻게 증폭 혹은 소산시키는지와 직결된다.
유한요소법 같은 경우에도, 요소(mesh) 분할이 불량하거나(매우 늘어난 삼각형·사각형 등), 특정 부분에서 계수가 급격히 변하는 PDE(예: 고콘트라스트 매질)라면 전방오차가 크게 나타날 수 있다. 실질적으로는 사전조건화된 반복솔버, 적응형(Adaptive) 메싱, 그리고 충분한 정규화(Regularization) 등을 결합해야 안정적 해를 얻을 수 있다.
미분방정식 초기값문제(IVP)에서의 안정성
상미분방정식(ODE) 초기값문제를 푸는 수치적 기법(룬게-쿠타 계열 등)에도 마찬가지로 안정성이라는 개념이 적용된다. 단순화해서, $y' = f(t, y), ; y(t_0) = y_0$ 문제에서 전진 오일러(Forward Euler) 방법을 쓸 경우, 시간 스텝 $h$가 너무 크면 수치해가 이론적 해와 전혀 다른 방향으로 폭주할 수 있다. 반면 어떤 고급된 다단계 방법도, 문제가 강한 경직성(Stiffness)을 띠면 시간 스텝 선정이 굉장히 까다로워진다.
경직성 문제에서 유리근접(Rosenbrock) 방식이나 다항근사(BDF) 계열과 같은 암시적(Implicit) 방법이 필요해진다. 이는 사실상 매 스텝마다 대규모 선형·비선형시스템을 풀어야 한다는 뜻이므로, 이 시스템을 안정적으로 해결하는 알고리즘이 필수다. 결과적으로, ODE(또는 DAE) 솔버가 불안정성을 피하기 위해선, 단계별 안정 영역(Region of Absolute Stability)을 만족하는 스킴인지, 내부 선형시스템 또는 비선형시스템 해법이 후방 안정적인지 등을 점검해야 한다.
정규화(Regularization)와 조건수 완화
정규화(Regularization)는 ill-posed 문제나 심각하게 ill-conditioned된 문제에서, 작은 교란에 대해 해가 무의미하게 변하지 않도록 제약을 추가하거나 해공간(search space)을 축소하는 접근법이다. 대표적으로 역문제(Inverse Problem)나 머신러닝에서 리지(Ridge) 혹은 라쏘(Lasso) 정규화를 적용해 매개변수 크기를 통제하는데, 이를 통해 해가 폭발하지 않도록 제약을 걸어 안정성을 확보한다.
선형시스템 $\mathbf{A}\mathbf{x} \approx \mathbf{b}$가 심각하게 ill-conditioned되어 있으면, $\lambda > 0$를 주고
와 같은 형태로 정규화 문제를 정의한다. 이렇게 하면 유효하게 $\mathbf{A}^\top \mathbf{A} + \lambda \mathbf{I}$를 다루는 셈이 되어, 작은 특잇값들이 너무 작아지는 것을 막고 문제의 조건수가 크게 개선된다. 물론 이는 본래의 문제를 수정하는 행위이므로, 해석적 의미가 달라질 수 있다. 그러나 실제 수치 계산에서는 이렇게 해서라도 “의미 있는 근사해”를 얻는 편이 더 유리한 경우가 많다.
확률적(Probabilistic) 관점에서의 안정성
실제 응용에서는 입력 데이터를 완벽히 알 수 없고, 노이즈나 불확실성(Uncertainty)이 동반되는 경우가 많다. 이때 문제의 민감도를 확률적·통계적으로 해석하여, 평균 오차나 분산 등을 고려하는 접근이 제시되기도 한다. 예컨대 랜덤 행렬(Random Matrix)을 구성해 몬테카를로 시뮬레이션을 실행함으로써, 특정 알고리즘이 입력 교란에 대해 전반적으로 어느 정도의 전방오차를 일으키는지(평균적으로 뒤틀린 해를 얼마나 산출하는지)를 추정할 수 있다.
조건수가 큰 문제라 하더라도, 입력의 분포가 특정 방향으로 제한되어 있으면 실제로 기대되는 오차 증폭이 크지 않을 수도 있다. 반면 조건수는 작아도, 입력이 특이한 패턴으로 변동하는 경우 오차가 특정 방향에만 국소적으로 매우 커질 수 있다. 이러한 상황에서 안정성을 보다 실용적으로 판단하기 위해서는, 문제의 구조와 입력 노이즈의 통계적 특성까지 포함한 포괄적 분석이 필요하다.
컴퓨터 구조와 병렬 연산 환경
수치해석을 병렬 연산(Parallel Computing)이나 GPU 환경에서 실행할 때, 연산 순서가 바뀌거나 연산의 병렬화로 인해 반올림 오차가 달라질 수 있다. 같은 알고리즘이라도 직렬(Serial)로 실행했을 때와 병렬(Parallel)로 실행했을 때, 혹은 쓰레드 수가 다를 때 결과가 조금씩 다른 현상이 발생하기도 한다. 이는 부동소수점 연산이 결합법칙(Associativity)을 완벽히 만족하지 않기 때문이다.
따라서 대규모 분산 환경(HPC 클러스터 등)에서 매우 큰 문제를 푸는 경우, 알고리즘 설계 시 연산 재배열이 야기하는 수치 오차를 주시해야 한다. 수치적 안정성이 뛰어난 알고리즘이라면, 연산 순서가 조금 달라져도 후방오차가 크게 달라지지 않는다. 반면 불안정한 알고리즘은 병렬 연산 시 작은 순서 변경에 의해 결과가 현저히 달라질 수 있으므로, 구현 단계에서의 주의가 요구된다.
복합적 고려 사항
안정성(Stable)과 불안정성(Unstable), 그리고 조건수(Condition Number)는 수치해석에서 가장 중심적이고 기초적인 개념이면서도, 실제로 문제를 다룰 때에는 매우 다양한 측면이 얽혀있다. 알고리즘 선택, 입력 데이터의 특성, 하드웨어 환경, 정규화나 사전조건화 기법, 가변정밀도 사용 여부 등 종합적인 요소들이 상호작용하며 결과물의 오차와 민감도가 결정된다. 이 장에서 논의한 개념들은 이후 다양한 응용 예제에서 재차 강조될 것이고, 상황별로 다른 관점과 기법이 추가되어 더욱 정교한 해결책을 모색하게 될 것이다.
Last updated