<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>김데굴의 스터디룸</title>
    <link>https://degul-kim.tistory.com/</link>
    <description>궁금하면 다해보는 잡식 개발자</description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 07:19:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>김데굴</managingEditor>
    <image>
      <title>김데굴의 스터디룸</title>
      <url>https://tistory1.daumcdn.net/tistory/5349361/attach/8eb73c783cc845a6966074455fcbc3a2</url>
      <link>https://degul-kim.tistory.com</link>
    </image>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 12.1 고윳값과 고유벡터의 해석, 12.2 고윳값 구하기</title>
      <link>https://degul-kim.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter12] 고윳값 분해 : 선형대수학의 진주&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 고윳값&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기하학적 해석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬과 벡터를 특수하게 결합하면 행렬이 벡터를 늘리기는 하지만 회전시키지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 벡터가 행렬의 고유벡터이며, 늘어나는 양이 고윳값이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-09 오후 6.22.52.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJR5Xn/dJMcajgMAWV/TEPRTysM0tTyJAwpdNcBWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJR5Xn/dJMcajgMAWV/TEPRTysM0tTyJAwpdNcBWk/img.png&quot; data-alt=&quot;책 254p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJR5Xn/dJMcajgMAWV/TEPRTysM0tTyJAwpdNcBWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJR5Xn%2FdJMcajgMAWV%2FTEPRTysM0tTyJAwpdNcBWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;253&quot; data-filename=&quot;스크린샷 2026-02-09 오후 6.22.52.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 254p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림은 2x2 행렬로 곱하기 전과 후의 벡터를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 그림의 v1, v2를 보면, 실선은 원래 벡터를 말하고 점선은 행렬 M을 곱한 결과를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 실선과 점선이 같은 직선 위에 있기 때문에 v1, v2는 고유벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 오른쪽 그림에서 w1, w2와 Mw1, Mw2는 행렬을 곱한 후 벡터가 다른 방향으로 꺾였기 때문에 고유벡터가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 고유벡터는 &lt;b&gt;행렬 벡터 곱셈이 스칼라-벡터 곱셈처럼 작동한다&lt;/b&gt;는 것을 의미한다. 이를 방정식으로 나타내면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ Av = \lambda v $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 행렬이 스칼라와 같다는 의미는 아니고, 동일한 벡터에 대해 행렬이 미치는 효과가 스칼라가 미치는 효과와 동일하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 '&lt;b&gt;고윳값 방정식'&lt;/b&gt;이라고 하며 꼭 외워두어야 할 선형대수학의 핵심 공식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;잡음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 데이터 집합에는 잡음(noise)이 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡음은 설명할 수 없거나(예: 무작위 변동) 원치 않는 데이터 집합의 분산을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작위 잡음을 줄이는 한 가지 방법은 시스템의 고윳값과 고유벡터를 식별한 다음, 작은 고윳값과 관련된 데이터 공간에서 방향을 투영하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 무작위 잡음이 전체 분산에 기여하는 바가 상대적으로 작다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 차원을 '투영'한다는 것은 임곗값보다 낮은 일부 고윳값을 0으로 설정한 후 데이터 집합을 재구성하는 것을 의미한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ChatGPT랑 다시 이해하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 = 의미있는 구조 + 무작위 잡음 으로 정의했을 때, 잡음은 전체 분산에 조금만 기여한다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 행렬에서 고유벡터는 '데이터가 퍼져있는 방향'을 의미하고, 고윳값은 '그 방향으로 분산 크기'를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 큰 고윳값은 데이터가 많이 퍼진 방향(신호)이고, 작은 고윳값은 거의 안퍼진 방향(잡음일 가능성이 큼)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 고윳값에 해당하는 방향은 버려도 정보 손실이 크지 않은 잡음이라고 가정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;중요하지 않은(작은 고윳값) 방향을 제거하고 중요한 방향으로만 데이터를 다시 만들어 차원을 줄이고, 잡음을 줄이는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 고윳값 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정방 행렬을 고윳값 분해하려면 먼저 고윳값을 찾은 다음 각 고윳값을 사용하여 해당 고유벡터를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬을 이용하면 행렬의 고윳값을 매우 쉽게 찾을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1770629815003&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;matrix = np.array([
    [1,2],
    [3,4]
])

# 고윳값 구하기
evals = np.linalg.eig(matrix)[0]
print(evals)
# [-0.37228132  5.37228132]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬의 고윳값을 구하려면 위에서 본 고윳값 방정식으로 시작해 간단한 대수 연산을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{align} Av = \lambda v \\ Av - \lambda v = 0 \\ (A - \lambda I) = 0 \end {align} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 식의 왼쪽에는 두 개의 벡터 항이 있고, 둘다 v를 포함하기 때문에 공통인수를 추출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 A에서 스칼라 $\lambda$를 빼야하기 때문에 대신 행렬을 $\lambda$만큼 이동하여 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 방정식은 &lt;b&gt;고유벡터가 고윳값에 의해 이동된 행렬의 영공간에 존재한다&lt;/b&gt;는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{align}\widetilde{A} = A - \lambda I \\ \widetilde{A}v = 0 \end {align} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형대수학에서는 단순한 해를 무시하므로 v = 0을 고유벡터로 보지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, v는 0이 아니므로 Mv = 0이 되게 하는 행렬 M은 특이행렬이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;고윳값에 의해 이동된 행렬이 특이 행렬이라는 것을 의미&lt;/b&gt;하는데 특이 행렬만이 자명하지 않은 영공간을 갖기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 특이 행렬의 행렬식은 0이므로 아래와 같은 식을 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \left | A - \lambda I\right | = 0 $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래 그림처럼 식을 얻어 람다값을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-09 오후 6.52.54.png&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otvGN/dJMcafettzi/BXrk0zkw5xdmiY7GJXoh4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otvGN/dJMcafettzi/BXrk0zkw5xdmiY7GJXoh4K/img.png&quot; data-alt=&quot;책 258p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otvGN/dJMcafettzi/BXrk0zkw5xdmiY7GJXoh4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotvGN%2FdJMcafettzi%2FBXrk0zkw5xdmiY7GJXoh4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;154&quot; data-filename=&quot;스크린샷 2026-02-09 오후 6.52.54.png&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 258p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행렬-벡터 곱셈은 스칼라-벡터 곱셈(고윳값 방정식)처럼 동작한다.&lt;/li&gt;
&lt;li&gt;고윳값 방정식을 영벡터로 설정하고 공통항을 추출한다.&lt;/li&gt;
&lt;li&gt;이렇게 하면 고유벡터가 고윳값에 의해 이동된 행렬의 영공간에 있음을 알 수 있다. 영벡터를 고유벡터로 간주하지 않으므로 이동된 행렬은 특이행렬이다.&lt;/li&gt;
&lt;li&gt;따라서 이동된 행렬의 행렬식을 0으로 설정하고 미지의 고윳값을 구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고윳값으로 이동된 행렬의 행렬식을 0으로 둔 것을 행렬의 특성 다항식이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방정식을 만족시킬 수 있는 람다값은 두 가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 MxM 행렬의 특성 다항식은 $\lambda^M$항을 가진다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 고유벡터 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 아래와 같이 고유벡터를 찾을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1770715155501&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;evals, evecs = np.linalg.eig(matrix)
print(evals), print(evecs)
#[-0.37228132  5.37228132]

#[[-0.82456484 -0.41597356]
# [ 0.56576746 -0.90937671]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고유벡터는 행렬 evecs의 열에 존재하고, 고윳값과 같은 순서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 행렬 evecs의 첫 번째 열에 있는 고유벡터는 벡터 evals의 첫 번째 고윳값과 짝을 이룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고유벡터는 $\lambda$만큼 이동한 행렬의 영공간에 있는 벡터 v를 구해 찾을 수 있다. 식은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ v_i \in N(A - \lambda_iI) $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 다음은 행렬과 행렬의 고윳값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1 &amp;amp; 2 \\2 &amp;amp; 1 \\\end{bmatrix} \Rightarrow \lambda_1 = 3, \lambda_2 = -1 $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬의 고유벡터를 구하기 위해 행렬을 3만큼 이동하고 그 영공간에서 벡터를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1-3 &amp;amp; 2 \\2 &amp;amp; 1-3 \\\end{bmatrix} = \begin{bmatrix}-2 &amp;amp; 2 \\2 &amp;amp; -2\\\end{bmatrix}\begin{bmatrix}1 \\1\end{bmatrix} = \begin{bmatrix}0 \\0\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 [1 1]은 고윳값 3과 관련된 행렬의 고유벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영공간 벡터는 가우스-조던을 사용하여 연립방정식을 풀면 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 계수 행렬은 $\lambda$만큼 이동된 행렬이고, 상수벡터는 영벡터이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고유벡터의 부호와 크기 불확정성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 [1 1]이 영공간에 대한 유일한 기저벡터는 아니다. [4 4] 또는 [-5.4 -5.4] 등 무수한 벡터가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 벡터 [1 1]의 크기를 조정한 모든 벡터는 영공간의 기저가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v가 행렬의 고유벡터라면 0을 제외한 모든 실숫값 $\alpha$에 대해 $\alpha v$도 고유벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 고유벡터는 크기가 아닌 방향 때문에 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고유벡터는 무수히 많으나, 단위 정규화된 고유벡터(유클리드 노름 1)는 하나만 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 고유벡터는 방향만 중요하고 부호는 중요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고유벡터는 하나의 벡터가 아니라 원점을 지나는 직선이고, 그 직선위에 있는 모든 벡터는 고유벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 v와 -v는 같은 고유방향이기 때문에 부호가 정해지지 않는다. 이걸 부호 불확정성이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/52</guid>
      <comments>https://degul-kim.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 10 Feb 2026 18:30:15 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 11.2 다항식 회귀</title>
      <link>https://degul-kim.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter11] 최소제곱법 응용 : 실제 데이터를 활용한 최소제곱법&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 다항식 회귀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다항식 회귀(polynomial regression)는 일반 회귀와 비슷하지만 독립변수인 x축 값을 더 높은 차수로 끌어올린 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 설계 행렬의 각 열 i는 $x^i$로 정의되며, 여기서 x는 일반적으로 시간 또는 공간이지만 인구와 같은 다른 변수일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적 모델은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ y = \beta_0x^0 + \beta_1x^1 + \cdots + \beta_nx^n $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여기서 $x^0 = 1$은 모델의 절편이 되는데, 이러한 경우를 제외하고는 예측 데이터와 관측 데이터 간의 제곱 차이를 최소화 하는 $\beta$ 값을 찾는 것이 목표인 일반 회귀 분석이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;n차 다항식에는 절편을 포함해 n+1개의 회귀변수가 있으며, 다항식 함수는 관측 데이터를 모델링하기 위한 기저벡터이다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;특수한 설계 행렬을 제외하고 다항식 회귀는 다른 회귀와 완전히 동일하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;왼쪽 역을 사용하여 회귀변수의 가중치 조합이 관측 데이터와 가장 잘 일치하도록 하는 계수 집합을 구한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ChatGPT와 다시 한 번 이해해보기  &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다항식 회귀는 선형 회귀와 같은데, 입력값을 제곱, 세제곱 해서 사용하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞에서 봤던 선형회귀는 아래와 같은 모습이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$ y = \beta_0 + \beta_1x $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래프로 그리면 직선 형태이고, x가 커질수록 일정한 비율로 y가 변한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 때 데이터가 곡선의 형태일때 다항식 회귀를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;y = \beta_0x^0 + \beta_1x^1 + \cdots + \beta_nx^n $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요건 비선형 회귀라고 생각할 수 있는데, x에 대해선 곡선이나 $\beta$에 대해서는 선형이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계행렬 관점으로 보면 X는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ X&amp;nbsp;=&amp;nbsp;\begin{bmatrix}&lt;br /&gt;1&amp;nbsp;&amp;amp;&amp;nbsp;x_1&amp;nbsp;&amp;amp;&amp;nbsp;x^2_1&amp;nbsp;\\&lt;br /&gt;1&amp;nbsp;&amp;amp;&amp;nbsp;x_2&amp;nbsp;&amp;amp;&amp;nbsp;x^2_2&amp;nbsp;\\&lt;br /&gt;\vdots&amp;nbsp;&amp;nbsp;&amp;amp;&amp;nbsp;\vdots&amp;nbsp;&amp;nbsp;&amp;amp;&amp;nbsp;\vdots&amp;nbsp;&amp;nbsp;\\&lt;br /&gt;\end{bmatrix}&amp;nbsp;,&amp;nbsp;\beta&amp;nbsp;=&amp;nbsp;\begin{bmatrix}&lt;br /&gt;\beta_0&amp;nbsp;\\&lt;br /&gt;\beta_1&amp;nbsp;\\&lt;br /&gt;\beta_2&lt;br /&gt;\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결국 $ y = X\beta + \epsilon$의 형태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다항식 회귀는 $x, x^2, x^3$을 새로운 변수처럼 추가해 사용하는 형태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터를 적용해보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1770197841269&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 설계 행렬
X = np.zeros((N,4))
# 열 4개(다항식 차수 3까지)
for i in range(4): 
  X[:,i] = np.array(year)**i
  
# np.linalg.lstsq = 최소제곱법
# doubleTime = 실제관측값
beta = np.linalg.lstsq(X,doubleTime, rcond=None)
# beta[0]이 계수임
yHat = X@beta[0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하면 아래와 같이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 y1은 실제 데이터고, y2는 예측값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-04 오후 6.38.09.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FBhlU/dJMcaaYsB5X/LUSDIWyEzIRJP1Vzbzy1vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FBhlU/dJMcaaYsB5X/LUSDIWyEzIRJP1Vzbzy1vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FBhlU/dJMcaaYsB5X/LUSDIWyEzIRJP1Vzbzy1vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFBhlU%2FdJMcaaYsB5X%2FLUSDIWyEzIRJP1Vzbzy1vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;376&quot; data-filename=&quot;스크린샷 2026-02-04 오후 6.38.09.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곡선을 적합시키는데에 다항식 회귀는 일반적으로 많이 사용되며, Numpy에는 이러한 모델을 만들고 적합시키는 전용 함수가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1770197995576&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;beta = np.polyfit(year, doubleTime, 3) # 3차다항식
yHat = np.polyval(beta, year)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 그리드 서치로 모델 매개변수 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소제곱법은 정확하고 빠르지만, 선형 모델 적합에만 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 매개변수 식별에는 또 다른 최적화 방법인 그리드 서치(격자 검색)가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드 서치는 매개변수 공간을 샘플링하고 각 매개변숫값으로 데이터에 대한 모델 적합도를 계산한 다음 최고의 모델 적합도를 가지는 매개변숫값을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 $y = x^2$ 함수의 최솟값을 찾는다고 하면, 그리드 서치 기법에서는 시험에 사용할 미리 정의된 x값 집합으로 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(-2, -1, 0, 1, 2) 집합을 정해두고, 이를 그리드(격자)라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 각 격잣값에 대한 함수를 계산하여 y = (4,1,0,1,4)를 얻는다. 그러면 x=0일 때 y가 최소가 된다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 그리드 서치가 항상 최적의 해를 구한다고 보장할 수는 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 범위를 잘못 선택해 그리드를 (-1000, -990, -980, -970)이라고 가정한다면 x = -970일 때 최소화된다는 결론을 내릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 그리드 서치에서는 범위와 해상도(격자 점 사이의 간격)가 모두 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드 서치를 시각적으로 나타내면 아래 그림과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-04 오후 6.45.52.png&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BZFBC/dJMcafMgiVu/UIaNTj8DhURcnU3Iquf3lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BZFBC/dJMcafMgiVu/UIaNTj8DhURcnU3Iquf3lk/img.png&quot; data-alt=&quot;책 244p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BZFBC/dJMcafMgiVu/UIaNTj8DhURcnU3Iquf3lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBZFBC%2FdJMcafMgiVu%2FUIaNTj8DhURcnU3Iquf3lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;239&quot; data-filename=&quot;스크린샷 2026-02-04 오후 6.45.52.png&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 244p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소제곱법으로 해결된다면 굳이 그리드 서치를 사용할 필요가 없지만, 그리드 서치는 비선형 모델에서 매개변수를 찾는 데 유용한 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드 서치는 딥러닝의 대규모 모델의 경우 시간이 많이 소요될 수 있지만 병렬화를 통해 더 효율적으로 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 그리드 서치는 선형 방법을 적용할 수 없을 때 데이터에 모델을 맞추기 위한 비선형 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/51</guid>
      <comments>https://degul-kim.tistory.com/51#entry51comment</comments>
      <pubDate>Wed, 4 Feb 2026 18:48:12 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 11.1 날씨에 따른 자전거 대여량 예측</title>
      <link>https://degul-kim.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter11] 최소제곱법 응용 : 실제 데이터를 활용한 최소제곱법&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 실제 데이터를 이용한 최소제곱 모델 응용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 집합에는 자전거 대여량과 온도, 습도, 강우량, 풍속 등 날씨 관련 변수를 가진 약 9천 건의 관측 데이터가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터를 기반으로 자전거 대여량을 예측하는 비교적 간단한 회귀 모델을 구축해본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.12.06.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx6xqI/dJMcaf6xdfW/ha8ApPeG6WuEomYdWw1MaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx6xqI/dJMcaf6xdfW/ha8ApPeG6WuEomYdWw1MaK/img.png&quot; data-alt=&quot;책 232p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx6xqI/dJMcaf6xdfW/ha8ApPeG6WuEomYdWw1MaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx6xqI%2FdJMcaf6xdfW%2Fha8ApPeG6WuEomYdWw1MaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;223&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.12.06.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 232p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;선정한 네 가지 변수의 상관관계 행렬은 위 이미지와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에서는 자전거 대여량이 시간 및 기온과 양의 상관관계가 있고, 강우량과 음의 상관관계가 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 강우량과 계절에 따라 자전거 대여량을 예측하는데, 계절은 순환형이고 회귀는 선형인 차이점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 딥러닝 모델에 사용되는 원핫 인코딩(one-hot-encoding)을 사용하거나 계절을 이진화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 가을과 겨울을 0, 봄과 여름을 1로 표시한다. 베타 계수가 양수일수록 가을/겨울에 비해 봄/여름에 자전거 대여가 많다는 의미로 해석할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.22.20.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KAmCu/dJMcabpx77e/U6ifqxEML4Fu6zgIKkcaw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KAmCu/dJMcabpx77e/U6ifqxEML4Fu6zgIKkcaw0/img.png&quot; data-alt=&quot;책 233p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KAmCu/dJMcabpx77e/U6ifqxEML4Fu6zgIKkcaw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKAmCu%2FdJMcabpx77e%2FU6ifqxEML4Fu6zgIKkcaw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;305&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.22.20.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 233p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 왼쪽은 이미지로 시각화된 설계 행렬을 보여준다. 열은 회귀변수이고 행은 관측치이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ChatGPT랑 이해해보기  &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터는 강우량 + 계절 효과를 동시에 설명해야 하는 회귀 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 이미지를 보면 강우량이 많을수록 대여량이 급감하고, 같은 강우량이라도 &lt;b&gt;계절에 따라 대여량 수준이 다르다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여름엔 전체적으로 높고, 겨울엔 전체적으로 낮다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 이미지는 설계 행렬이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x축에는 강우량, 계절, 절편(상수항)이 있다. 즉 모델은 아래와 같은 형태이다.(beta1은 강우량, beta2는 계절)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ y = \beta_0 + \beta_1 + \beta_2 $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y축은 하루의 관측치 (데이터 포인트 하나)를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강우량은 대부분 0으로 희소하고 데이터 집합이 두 개의 가을/겨울 기간과 한 개의 봄/여름 기간으로 이루어졌음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 관측 값에 대해 동일한 값을 사용하기 때문에 절편은 흰색 단색이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강우량 및 계절에 대한 베타값은 각각 -80과 369이다. 이 수치는 비가 올 때 자전거 대여량이 적고 가을/겨울에 비해 봄/여름에 자전거 대여량이 많다는 것을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.41.53.png&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brGK5B/dJMcac20gd1/KqLqdxP2o9Ekejo9J3cbn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brGK5B/dJMcac20gd1/KqLqdxP2o9Ekejo9J3cbn0/img.png&quot; data-alt=&quot;책 235p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brGK5B/dJMcac20gd1/KqLqdxP2o9Ekejo9J3cbn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrGK5B%2FdJMcac20gd1%2FKqLqdxP2o9Ekejo9J3cbn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;269&quot; data-filename=&quot;스크린샷 2026-02-03 오후 6.41.53.png&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 235p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림으로 보아, 모델이 데이터에 완벽하게 맞는다면 점들은 기울기가 1인 대각선 위에 놓여있을 것이지만, 여기서는 그렇지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 모델이 데이터에 잘 맞지 않는다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 $R^2$은 0.097에 불과하다.(통계 모델이 데이터 분산 중 약 1%를 설명한다는 의미)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 모델에서는 자전거 대여량이 음수로 예측되는 것을 볼 수 있는데, 이는 잘못된 수치이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;statsmodels을 사용한 회귀 분석 표&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;statsmodels 라이브러리를 사용하면 회귀 분석 표를 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770112043058&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import statsmodels.api as sm

# 데이터 추출(판다스 데이터프레임으로 유지)
desmat_df = data[['Rainfall(mm)','Seasons']]
obsdata_df = data['Rented Bike Count']

# 모델 생성 및 적합(절편을 명시적으로 추가해야 함)
desmat_dt = sm.add_constant(desmat_df)
model = dm.OLS(obsdata_df, desmat_df).fit()
print(model.summary())&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다중공선성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중공선성이란 다중 회귀 모델에서 하나의 독립변수가 다른 독립변수로부터 상당한 정확도로 선형적으로 예측될 수 있는 것이라 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 설계 행렬 내부에 선형 종속성이 있다는 것을 의미하며, 이는 설계 행렬이 축소 계수 또는 특이 행렬이라는 말과 같은 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;축소계수 설계 행렬은 왼쪽 역행렬이 존재하지 않으므로 최소제곱 문제를 분석적으로 해결할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 축소계수 설계 행렬의 경우 고유한 해는 없지만 최소 오차를 갖는 해를 선택할 수 있다. 이를 '최소 노름 해' 또는 '최소 노름'이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형종속성이 있는 설계 행렬은 일반적으로 통계 모델에 문제가 있음을 나타내므로 반드시 조사해야 한다.(주로 데이터 입력 실수 및 코딩 버그)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정규화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화는 &lt;b&gt;통계 모델을 수정하는 다양한 방법을 총칭하는 포괄적인 용어&lt;/b&gt;로, 수치 안정성을 개선하거나, 특이 행렬 또는 나쁜 상태의 행렬을 최대 계수로 변환하거나, 과적합을 줄여 일반화 가능성을 개선하는 것을 목표로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화에는 릿지(L2), 라쏘(L1), 티호노프 및 수축 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화 기법마다 작동 방식이 다르지만 대부분의 정규화 기법은 설계 행렬을 일정량 이동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 행렬을 프로베니우스 노름의 일부 크기만큼 이동하여 정규화하면 식은 아래와 같이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \beta = \left ( X^TX + \gamma \left\| X\right\|_F^2 I \right)^{-1}X^Ty $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심 매개변수는 정규화의 양을 결정하는 $\gamma$이다. 적절한 $\gamma$ 매개변수를 선택하는 것은 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화의 가장 분명한 효과는 설계 행렬이 축소계수 행렬일 때 정규화된 정방 설계 행렬이 최대계수 행렬이 된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화는 또한 행렬에서 정보의 '퍼짐'을 측정하는 조건수를 감소시킨다. 이렇게 하면 행렬의 수치 안정성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화의 통계적 의미는 이상치이거나 대표성이 없는, 따라서 새로운 &lt;b&gt;데이터 집합에서 관찰될 가능성이 낮은 개별 데이터 점에 대한 모델의 민감도를 낮추어 '평활화'하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\gamma = 0.1$과 같이 정해진 $\gamma$값은 행렬의 수치 범위에 따라 설계 행렬에 큰 영향을 미칠 수도 있고 무시될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 $\gamma$ 매개변수를 정규화의 비율로 해석하여 행렬의 수치 범위에 따라 크기를 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로베니우스 노름을 제곱하는 이유는 설계 행렬의 제곱 노름은 설계 행렬의 노름에 자신의 전치를 곱한 값과 같기 떄문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 프로베니우스 노름 대신 설계 행렬의 고윳값의 평균을 사용하는 것이 더 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/50</guid>
      <comments>https://degul-kim.tistory.com/50#entry50comment</comments>
      <pubDate>Wed, 4 Feb 2026 18:16:44 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] Chapter 10 연습문제</title>
      <link>https://degul-kim.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter10] 일반 선형 모델 및 최소제곱법 : 우주를 이해하기 위한 방법&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10-1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잔차는 예측 데이터와 직교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 예로 든 데이터 집합으로 오차에 따른 예측 데이터의 산점도를 만든다. 그 다음 잔차와 모델 예측 데이터 사이의 내적과 상관계수를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769765205360&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;numcourses = [13,4,12,3,14,13,12,9,11,7,13,11,9,2,5,7,10,0,9,7]
happiness  = [70,25,54,21,80,68,84,62,57,40,60,64,45,38,51,52,58,21,75,70]

# X는 Nx1 열벡터
X = np.array(numcourses,ndmin=2).T
X_leftinv = np.linalg.inv(X.T@X) @ X.T
# 최소제곱 해
beta = X_leftinv @ happiness
# 예측값
pred_happiness = X@beta
# 잔차
res = happiness-pred_happiness

# 내적
print('Dot product: ' + str(np.dot(pred_happiness,res)) )
# Dot product: 5.4569682106375694e-12
# 0과 가깝다. 즉 직교한다.

# 상관계수
print('Correlation: ' + str(np.corrcoef(pred_happiness,res)[0,1]))
# Correlation: -0.6444852258446189
# 0과 가깝다.

print(' ')


# show in a plot
plt.figure(figsize=(6,6))
plt.plot(res,pred_happiness,'ko',markersize=12)
plt.xlabel('Residual error')
plt.ylabel('Model-predicted values')
plt.title(f'r = {np.corrcoef(pred_happiness,res)[0,1]:.20f}')
plt.savefig('Figure_10_06.png',dpi=300)
plt.show()

# 그래프를 보면 점이 산개해있다.
# 즉 예측값이 커져도 잔차는 커지지도 작아지지도 않는다.
# = 선형 관계 없다 = 직교&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력하면 아래처럼 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.27.00.png&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmzdKN/dJMcahcaM02/VnJB9Pg13GVbxBAWb5rvi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmzdKN/dJMcahcaM02/VnJB9Pg13GVbxBAWb5rvi1/img.png&quot; data-alt=&quot;x축은 잔차 오류, y축은 모델 예측값이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmzdKN/dJMcahcaM02/VnJB9Pg13GVbxBAWb5rvi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmzdKN%2FdJMcahcaM02%2FVnJB9Pg13GVbxBAWb5rvi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;459&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.27.00.png&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;x축은 잔차 오류, y축은 모델 예측값이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10-2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잔차벡터는 그 하나의 선형 가중 결합에만 직교하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 잔차벡터는 설계 행렬을 생성하는 전체 부분공간과 직교한다. 파이썬에서 이를 구현하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= 잔차는 X의 열공간과 직교한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= X의 모든 열벡터와 내적이 0이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 X의 모든 열벡터와 직교인 벡터들의 집합을 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769765881409&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# X의 열공간 외 나머지 공간
# X의 열공간에 직교한 모든 방향들의 집합이다.
nullspace = null_space(X.T)


# 잔차 res가 left-null space 안에 있는지 검사
# 어떤 벡터 r이 어떤 공간의 span 안에 있으면 기저에 r을 추가해도 랭크가 안 늘어난다
# = 기존 기저 벡터들 뒤에 벡터 r을 하나 더 붙여서 새 행렬을 만든다
nullspaceAugment = np.hstack( (nullspace,res.reshape(-1,1)) )


# 랭크가 같은지 검사
print(f'dim(  N(X)    ) = {np.linalg.matrix_rank(nullspace)}')
print(f'dim( [N(X)|r] ) = {np.linalg.matrix_rank(nullspaceAugment)}')

# dim(  N(X)    ) = 19
# dim( [N(X)|r] ) = 19&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10-3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR 분래를 통해 최소제곱법 계산하기. 아래 식들을 계산하고 서로 비교한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 역인 $(X^TX)^{-1}X^Ty$&lt;/li&gt;
&lt;li&gt;역을 $R^{-1}Q^Ty$로 하는 QR&lt;/li&gt;
&lt;li&gt;$Q^Ty$로 증강된 행렬에서 가우스-조던 소거법 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 방법의 베타 변수를 출력하고, 마지막으로 QR 분해의 결과 행렬을 출력하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770106710235&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;numcourses = [13,4,12,3,14,13,12,9,11,7,13,11,9,2,5,7,10,0,9,7]
happiness  = [70,25,54,21,80,68,84,62,57,40,60,64,45,38,51,52,58,21,75,70]

# np.array(numcourses,ndmin=2).T = numcourses를 일반 파이썬 리스트에서 열벡터로 만듦
# np.ones = 행 20개
# 둘다 행이 20개니까 np.hstack하면 (20,2)모양(열 기준으로 붙임)
# 예) X =
#    [[ 1, 13],
#     [ 1,  4],
#     [ 1, 12], ...
X = np.hstack((np.ones((20,1)),np.array(numcourses,ndmin=2).T))

# 왼쪽 역
beta1 = np.linalg.inv(X.T @ X) @ X.T @ happiness

# QR 분해
Q, R = np.linalg.qr(X)
beta2 = np.linalg.inv(R) @ (Q.T @ happiness)

# 가우스-조던
tmp = (Q.T@happiness).reshape(-1,1)
Raug = np.hstack( (R,tmp) ) # augment the matrix
Raug_r = sym.Matrix(Raug).rref()[0] # this gets the matrix
# Raug_r은 아래 형태가 됨
#[1  0 | &amp;beta;₁]
#[0  1 | &amp;beta;₂]

# 마지막 열, 즉 beta를 가져오기
beta3 = np.array(Raug_r[:,-1]) # convert back to numpy

print('Betas from left-inverse: ')
print(np.round(beta1,3)), print(' ')

print('Betas from QR with inv(R): ')
print(np.round(beta2,3)), print(' ')

print('Betas from QR with back-substitution: ')
print(np.round(np.array(beta3.T).astype(float),3))

# Betas from left-inverse: 
# [23.13   3.698]
 
# Betas from QR with inv(R): 
# [23.13   3.698]
 
# Betas from QR with back-substitution: 
# [[23.13   3.698]]

# 같은 최소제곱(OLS) 회귀 문제를 세 가지 서로 다른 선형대수 방법으로 풀면 결과가 같아야 한다는 걸 확인하는 문제

# show the matrices
print('Matrix R:')
print(np.round(R,3)) # note that it's upper-triangular (as you know!)

print(' ')
print(&quot;Matrix R|Q'y:&quot;)
print(np.round(Raug,3))

print(' ')
print(&quot;Matrix RREF(R|Q'y):&quot;)
print(np.round(np.array(Raug_r).astype(float),3)) # convert to numpy floats

# Matrix R:
# [[ -4.472 -38.237]
#  [  0.     17.747]]
 
# Matrix R|Q'y:
# [[  -4.472  -38.237 -244.849]
#  [   0.      17.747   65.631]]
 
# Matrix RREF(R|Q'y):
# [[ 1.     0.    23.13 ]
# [ 0.     1.     3.698]]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10-4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상치는 드물거나 대표적이지 않은 데이터값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서는 행복도 데이터에 이상치를 생성해 최소제곱법에 미치는 영향을 관찰한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 벡터에서 첫 번째 관측된 데이터 점을 70에서 170으로 변경한다.(데이터 입력 오타 시뮬레이션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 최소제곱 적합도를 다시 계산하고 데이터로 그래프를 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 마지막 점을 70에서 170으로 변경하고 똑같이 적합도 계산 및 그래프를 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시각화 자료를 만들어서 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상치는 결과 변수에서 동일하지만 해당 x축 값으로 인해 데이터에 대한 모델의 적합도에 미치는 영향은 상당히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상치의 이러한 차별적 영향을 지렛대라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770107928318&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;happiness_oops1 = [170,25,54,21,80,68,84,62,57,40,60,64,45,38,51,52,58,21,75,70]
happiness_oops2 = [70,25,54,21,80,68,84,62,57,40,60,64,45,38,51,52,58,21,75,170]

# X는 고정, y만 바뀐다.
# 즉 투영 공간 C(X)는 동일하나, y의 위치가 달라진다.
# 즉, y를 투영한 결과(beta)가 달라진다.

X = np.hstack((np.ones((20,1)),np.array(numcourses,ndmin=2).T))
X_leftinv = np.linalg.inv(X.T@X) @ X.T

_,axs = plt.subplots(1,3,figsize=(16,5))

# 정상 데이터 : 직선들이 대충 중앙을 통과함
# oops1 : x가 큰쪽(오른쪽)에서 y가 튐 &amp;gt; 회귀선 전체가 끌려올라감
# oops2 : x가 작은쪽(왼쪽)에서 y가 튐 &amp;gt; 회귀선은 크게 흔들리지 않고 잔차만 엄청 큼.
# 잔차가 아주 크면 SSE를 압도함(SSE : 오차제곱합)
for axi,y in zip(axs,[happiness,happiness_oops1,happiness_oops2]):

  # compute the best-fit parameters
  beta = X_leftinv @ y

  # predicted data
  pred_happiness = X@beta


  # plot the data and predicted values
  axi.plot(numcourses,y,'ks',markersize=15)
  axi.plot(numcourses,pred_happiness,'o-',color=[.6,.6,.6],linewidth=3,markersize=8)

  # plot the residuals (errors)
  for n,y,yHat in zip(numcourses,y,pred_happiness):
    axi.plot([n,n],[y,yHat],'--',color=[.8,.8,.8],zorder=-10)

  # make the plot look nicer
  axi.set(xlabel='Number of courses taken',ylabel='General life happiness',
          xlim=[-1,15],ylim=[0,100],xticks=range(0,15,2))
  axi.legend(['Real data','Predicted data','Residual'])
  axi.set_title(f'SSE = {np.sum((pred_happiness-y)**2):.2f}')
  


plt.tight_layout()
plt.savefig('Figure_10_07.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력하면 아래와 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-03 오후 5.39.11.png&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Ykbs/dJMcabC4Q7R/zKhUfPjT98AeBh9N5YaVLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Ykbs/dJMcabC4Q7R/zKhUfPjT98AeBh9N5YaVLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Ykbs/dJMcabC4Q7R/zKhUfPjT98AeBh9N5YaVLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Ykbs%2FdJMcabC4Q7R%2FzKhUfPjT98AeBh9N5YaVLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;181&quot; data-filename=&quot;스크린샷 2026-02-03 오후 5.39.11.png&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10-5&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소제곱법을 사용하여 역행렬을 계산하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방정식 $XB = Y$에서 X는 역행렬을 구할 정방 최대계수 행렬, B는 미지의 계수 행렬(역행렬이 됨), Y는 관측 데이터(단위행렬)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 방법으로 B를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 왼쪽 역 최소제곱법을 사용해서 한 번에 한 열씩 행렬을 계산한다. 이것은 for문에서 행렬 X와 Y의 각 열 사이에 최소제곱 적합도를 계산해 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 왼쪽 역행렬을 방법을 사용해 한 줄의 코드로 전체 B 행렬을 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 np.linalg.inv() 함수를 사용해 $X^{-1}$을 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 B 행렬에 X를 곱해 그림으로 보여준다. 마지막으로 역을 계산하는 이 세 가지 다른 방법이 동등한지 시험한다.(역행렬은 고유하므로 동등해야 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770108719060&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 6
X = np.random.randn(n,n)

Y = np.eye(n)

# np.zeros_like = X랑 똑같은 모양의 0으로만 된 배열을 만들기
Xinv1 = np.zeros_like(X)

for coli in range(n):
  # &amp;beta;=(X^TX)^{&amp;minus;1}X^Ty
  # OLS 해 공식
  # X가 정사각 &amp;amp; full rank이면 X^{-1} @ e_coli 가 X 역행렬의 coli번째 열
  # 즉, 각 열마다 Xbeta = e_i를 최소제곱으로 푸는 beta를 구해서 Xinv1의 i번째 열로 넣음
  Xinv1[:,coli] = np.linalg.inv(X.T @ X) @ X.T @ Y[:,coli]

Xinv2 = np.linalg.inv(X.T@X) @ X.T @ Y

Xinv3 = np.linalg.inv(X)

# visualize
_,axs = plt.subplots(1,3,figsize=(10,6))

# column-wise least-squares
axs[0].imshow( Xinv1@X ,cmap='gray')
axs[0].set_title('Via column-wise LS')

# matrix-wise least-squares
axs[1].imshow( Xinv2@X ,cmap='gray' )
axs[1].set_title('Via matrix-wise LS')

# inv()
axs[2].imshow( Xinv3@X ,cmap='gray' )
axs[2].set_title('Via inv()')


# don't need the tick marks
for a in axs: a.set(xticks=[],yticks=[])

plt.tight_layout()
plt.savefig('Figure_10_08.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-03 오후 5.52.15.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FR51t/dJMcabpx6U0/lsNmFxIqmu67BUmCxGot3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FR51t/dJMcabpx6U0/lsNmFxIqmu67BUmCxGot3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FR51t/dJMcabpx6U0/lsNmFxIqmu67BUmCxGot3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFR51t%2FdJMcabpx6U0%2FlsNmFxIqmu67BUmCxGot3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;214&quot; data-filename=&quot;스크린샷 2026-02-03 오후 5.52.15.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 그림이 거의 동일 &amp;rarr; &lt;b&gt;세 방법이 같은 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 역행렬은 &lt;b&gt;표준기저들을 타깃으로 한 최소제곱 문제들의 해를 모아놓은 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 X가 직사각 모양이면 최소제곱(OLS)만 살아남고, 역행렬은 죽는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X가 직사각이어도 각 열마다 &lt;b&gt;OLS 문제&lt;/b&gt;는 정의된다. 즉 유사역행렬은 OLS(최소제곱) 문제를 풀어서 얻은 연산자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/49</guid>
      <comments>https://degul-kim.tistory.com/49#entry49comment</comments>
      <pubDate>Tue, 3 Feb 2026 18:08:34 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 10.1 일반 선형 모델, 10.2 GLM 풀이, 10.3 GLM의 간단한 예, 10.4 QR 분해를 통한 최소제곱법</title>
      <link>https://degul-kim.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter10] 일반 선형 모델 및 최소제곱법 : 우주를 이해하기 위한 방법&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 일반 선형 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통계 모델은 예측변수(독립변수, independent variable)를 관측값(종속변수, dependent variable)과 연관시키는 방정식의 집합이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 선형 모델은 General Linear Model로, GLM이라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어는 아래처럼 정리할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.4728%;&quot;&gt;선형대수학&lt;/td&gt;
&lt;td style=&quot;width: 21.124%;&quot;&gt;통계&lt;/td&gt;
&lt;td style=&quot;width: 57.4031%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.4728%;&quot;&gt;$Ax = b$&lt;/td&gt;
&lt;td style=&quot;width: 21.124%;&quot;&gt;$X\beta =y$&lt;/td&gt;
&lt;td style=&quot;width: 57.4031%;&quot;&gt;일반 선형 모델(GLM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.4728%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 21.124%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 57.4031%;&quot;&gt;설계 행렬(열 = 독립변수, 예측변수, 희귀변수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.4728%;&quot;&gt;x&lt;/td&gt;
&lt;td style=&quot;width: 21.124%;&quot;&gt;$&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;\beta&lt;span&gt; $&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 57.4031%;&quot;&gt;희귀 계수 또는 베타 매개변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.4728%;&quot;&gt;b&lt;/td&gt;
&lt;td style=&quot;width: 21.124%;&quot;&gt;y&lt;/td&gt;
&lt;td style=&quot;width: 57.4031%;&quot;&gt;종속변수, 결과 측정값, 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구축&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLM을 구축하는 과정은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;독립변수와 종속변수를 연관시킨 방정식을 정의한다&lt;/li&gt;
&lt;li&gt;관찰된 데이터를 방정식에 대입한다&lt;/li&gt;
&lt;li&gt;일련의 방정식을 행렬 방정식으로 변환한다.&lt;/li&gt;
&lt;li&gt;해당 방정식을 푼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 개인의 체중과 부모의 키를 기반으로 성인 키를 예측하는 모델이 있다. 방정식은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ y = \beta_0 + \beta_1w + \beta_2h + \epsilon&amp;nbsp; $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y는 개인의 키, w는 개인의 체중, h는 부모의 키(어머니, 아버지의 평균)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\epsilon$은 오차 항(잔차라고도 한다)으로, 체중과 부모의 키가 개인의 키를 &lt;b&gt;완전히 결정한다고 할 수 없기 때문에&lt;/b&gt; 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 설명하지 못하는 무수한 요인이 있으며, 체중과 부모의 키에 기인하지 않는 분산이 이 오차 항에 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 각 변수가 얼마나 중요한지 알 수 없으므로 $\beta$항을 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\beta$ 항은 체중과 부모의 키를 어떻게 결합해서 개인의 키를 예측할지를 정하는 계수 또는 가중치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, $\beta$가 가중치인 선형 가중 결합이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\beta_0$을 절편(상수)이라고 한다. 이 절편 항은 모두 1로 이루어진 벡터이다. 절편항이 없으면 최적의 선은 강제로 원점을 통과하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 관측된 데이터를 방정식에 대입한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.26.46.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/syVTB/dJMcah4jHVF/MrKGRSwj2jodB2ULCev5Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/syVTB/dJMcah4jHVF/MrKGRSwj2jodB2ULCev5Dk/img.png&quot; data-alt=&quot;책 213p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/syVTB/dJMcah4jHVF/MrKGRSwj2jodB2ULCev5Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsyVTB%2FdJMcah4jHVF%2FMrKGRSwj2jodB2ULCev5Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;207&quot; height=&quot;142&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.26.46.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 213p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터를 통계 모델에 대입하려면 방정식을 4번 복제하고, 매번 변수 y,w,h를 측정값으로 대체해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 $\epsilon$항은 생략한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.27.21.png&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z5tzl/dJMcafyFqT5/rKvbHv9wEW5Q2qei3d7FYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z5tzl/dJMcafyFqT5/rKvbHv9wEW5Q2qei3d7FYk/img.png&quot; data-alt=&quot;책 213p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z5tzl/dJMcafyFqT5/rKvbHv9wEW5Q2qei3d7FYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz5tzl%2FdJMcafyFqT5%2FrKvbHv9wEW5Q2qei3d7FYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;178&quot; height=&quot;98&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.27.21.png&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 213p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 방정식을 행렬 방정식으로 변환한다. 이 방정식은 $X\beta = y $로 간결하게 표현될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.28.07.png&quot; data-origin-width=&quot;174&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wn6xi/dJMcagj11Yf/FiUmnHIrdpoXkwbkGbIYG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wn6xi/dJMcagj11Yf/FiUmnHIrdpoXkwbkGbIYG1/img.png&quot; data-alt=&quot;책 214p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wn6xi/dJMcagj11Yf/FiUmnHIrdpoXkwbkGbIYG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwn6xi%2FdJMcagj11Yf%2FFiUmnHIrdpoXkwbkGbIYG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;174&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.28.07.png&quot; data-origin-width=&quot;174&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 214p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;풀이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 미지 계수 $\beta$를 구하려면 방정식의 양변에 설계 행렬인 X의 왼쪽 역행렬을 곱하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.29.50.png&quot; data-origin-width=&quot;218&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1q9AY/dJMcahDdq15/xHNu0Kr7GK8SOYnuL2AfH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1q9AY/dJMcahDdq15/xHNu0Kr7GK8SOYnuL2AfH1/img.png&quot; data-alt=&quot;책 214p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1q9AY/dJMcahDdq15/xHNu0Kr7GK8SOYnuL2AfH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1q9AY%2FdJMcahDdq15%2FxHNu0Kr7GK8SOYnuL2AfH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;218&quot; height=&quot;79&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.29.50.png&quot; data-origin-width=&quot;218&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 214p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 그냥 $X^{-1}$을 안쓰는 이유는, 회귀 문제에서 X는 보통 정사각 + 가역이 아니기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정사각 + 가역이 아닌 행렬이므로 역행렬이 존재하지 않고, 따라서 가장 그럴듯한 역행렬 역할을 하는 것을 곱하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 마지막 방정식은 암기가 될 정도로 익혀야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \beta = (X^TX)^{-1}X^Ty $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방정식은 최소제곱법(least squares solution)이라고 불리며 응용 선형대수학에서 가장 중요한 수학 공식 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 다른 문자가 표시되거나 추가되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ b = (H^TWH + \lambda L^TL)^{-1}H^Tx $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 역을 이용한 최소제곱법은 파이썬으로 쉽게 변환할 수 있다.(아래 방법이 수치적으로 가장 안정된 방법은 아니다)&lt;/p&gt;
&lt;pre id=&quot;code_1769592973619&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;X_leftinv = np.linalg.inv(X.T @ X) @ X.T
beta = X_leftinv @ y&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해법의 정확성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$X\beta = y$ 방정식은 y가 설계 행렬 X의 열공간에 있을 때 정확히 풀 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터 벡터가 통계 모델의 열공간에 있다는 것은 대부분 보장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터에는 노이즈와 샘플링 변동성이 포함되어 있으며, 모델은 모든 변동성을 설명하지 못하는 단순화된 모델이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 난제를 해결하기 위해서는 모델 예측 데이터와 관측 데이터 간의 불일치를 허용하도록 GLM 방정식을 수정해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.40.16.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ugaap/dJMcadOiWuJ/wZNq5riZkpp5HXwcYDXgpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ugaap/dJMcadOiWuJ/wZNq5riZkpp5HXwcYDXgpk/img.png&quot; data-alt=&quot;책 216p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ugaap/dJMcadOiWuJ/wZNq5riZkpp5HXwcYDXgpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUgaap%2FdJMcadOiWuJ%2FwZNq5riZkpp5HXwcYDXgpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;150&quot; height=&quot;74&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.40.16.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 216p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방정식은 $\epsilon$ 가 설계 행렬의 열공간에 적합하도록 데이터 벡터에 추가하는 잔차, 즉 오차 항이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 방정식은 잔차 항이 데이터에 완벽하게 적합하도록 설계 행렬을 조정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 방정식은 잔차를 모델 예측 데이터와 관측 데이터의 차이로 정의한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요점은 관측된 데이터가 회귀변수가 생성하는 부분공간 내에 있는 경우가 거의 없다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 GLM의 목표는 관찰된 데이터에 최대한 근접하는 회귀변수의 선형 결합을 찾는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기하학적 관점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 행렬의 열공간인 C(X)이 $R^M$의 부분공간이라고 가정하면, 통계 모델에는 독립변수(열)보다 관측값(행)이 더 많은 경향이 있기 때문에 일반적으로 매우 저차원의 부분공간이다.(N &amp;lt; M)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제는 벡터 y가 설계 행렬의 열공간에 있는지 여부와, 그렇지 않다면 설계 행렬의 열공간 내부에서 데이터 벡터에 최대한 가까운 좌표가 무엇인가?의 두 가지 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 문제의 답은 '아니오'이나, 두 번째 질문은 애매하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.44.47.png&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsXPJA/dJMcaaxmg4x/KtTrvdmTsrQ46qKVNn4Ko0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsXPJA/dJMcaaxmg4x/KtTrvdmTsrQ46qKVNn4Ko0/img.png&quot; data-alt=&quot;책 217p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsXPJA/dJMcaaxmg4x/KtTrvdmTsrQ46qKVNn4Ko0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsXPJA%2FdJMcaaxmg4x%2FKtTrvdmTsrQ46qKVNn4Ko0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;184&quot; height=&quot;170&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.44.47.png&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 217p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 $X\beta$가 $\epsilon$만큼의 오차를 가지고 y와 떨어져있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리의 목적은 데이터 벡터 y와의 거리를 최소화하는 X의 열에 가중치를 부여하는 계수 $\beta$의 집합을 찾는 것이다.&amp;nbsp;투영 벡터는 $\epsilon$이라 부른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 $\epsilon$과 계수 $\beta$를 구하기 위해서는 직교벡터 투영을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 y와 X 사이의 최단 거리는 X와 직각으로 만나는 투영 벡터 $y - X\beta$가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 열공간 C(X)와 y 사이의 최단 거리 = 직교 이므로, 그 투영벡터가 $\epsilon$이 되고, 따라서 $\epsilon$은 C(X)와 직교한다. 즉 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \epsilon = X\beta - y $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방정식을 풀면 아까 그 방정식이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.58.02.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lvvAu/dJMcagj12EL/bMcrtfeKmZMHhtb44kPEPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lvvAu/dJMcagj12EL/bMcrtfeKmZMHhtb44kPEPk/img.png&quot; data-alt=&quot;책 218p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lvvAu/dJMcagj12EL/bMcrtfeKmZMHhtb44kPEPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlvvAu%2FdJMcagj12EL%2FbMcrtfeKmZMHhtb44kPEPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;126&quot; data-filename=&quot;스크린샷 2026-01-28 오후 6.58.02.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 218p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최소제곱법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소제곱법에서 '제곱'은 예측된 데이터와 관측된 데이터 간의 제곱 오차를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측된 i번째 데이터 점마다 오차 항이 있고, 이 오차 항은 $\epsilon_i = X_i\beta - y_i$로 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 각 데이터 점은 동일한 계수 집합, 즉 설계 행렬의 독립변수를 결합할 때 동일한 가중치를 사용하여 예측된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래 식처럼 모든 오차를 하나의 벡터에 담을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \epsilon = X\beta - y $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 데이터에 잘 적합하면 오차가 작아지기 때문에, 모델 적합의 목적은 $\epsilon$의 원소를 최소화하는 $\beta$의 원소를 선택하는 것이라 말할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 오차를 최소화하는 방식만 사용하면 모델이 음의 무한대를 향한 값을 예측하게 되므로, 예측 오차 자체의 양/음에 관계없이 데이터 y에 대한 기하학적 제곱 거리에 해당하는 제곱 오차를 최소화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 오차의 제곱 노름을 최소화하는 것과 같으므로 아래와 같은 식이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &amp;nbsp;\left\| e\right\|^2 = \left\| X\beta - y\right\|^2 $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식을 최적화하기 위해 미분을 0으로 설정하고 미적분과 대수를 적용하면 아래와 같은 과정의 식이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.03.12.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btvLDR/dJMcahpJu8X/Atap4YSTOR0KCBTHQftWUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btvLDR/dJMcahpJu8X/Atap4YSTOR0KCBTHQftWUK/img.png&quot; data-alt=&quot;책 219p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btvLDR/dJMcahpJu8X/Atap4YSTOR0KCBTHQftWUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtvLDR%2FdJMcahpJu8X%2FAtap4YSTOR0KCBTHQftWUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;99&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.03.12.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 219p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. QR 분해를 통한 최소제곱법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 역 방식은 일부에 대해 행렬 역행렬을 계산해야 하기 때문에 수치적으로 불안정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 $X^TX$, 행렬에 전치를 곱하면 노름 및 조건수와 같은 속성에 영향을 미친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 QR 분해는 최소제곱 문제를 더 안정적으로 풀 수 있는 방법을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.09.59.png&quot; data-origin-width=&quot;163&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntUfo/dJMcaihRr4Y/ZH2HzfJpaOTdC2k1Ll3N01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntUfo/dJMcaihRr4Y/ZH2HzfJpaOTdC2k1Ll3N01/img.png&quot; data-alt=&quot;책 224p&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntUfo/dJMcaihRr4Y/ZH2HzfJpaOTdC2k1Ll3N01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntUfo%2FdJMcaihRr4Y%2FZH2HzfJpaOTdC2k1Ll3N01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;163&quot; height=&quot;98&quot; data-filename=&quot;스크린샷 2026-01-30 오후 6.09.59.png&quot; data-origin-width=&quot;163&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;책 224p&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 R은 상삼각 행렬이므로 N행만 0이 아니고 행 N+1 부터 M까지는 결과에 아무런 영향을 주지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이러한 행은 R과 $Q^Ty$에서 제거할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 행 교환을 사용해 수치 안정성이 올라갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방정식의 가장 큰 장점은 행렬이 상삼각 행렬이므로 역치환(마지막행부터 대입해서 해 구하기)을 통해 해를 구할 수 있으므로 R의 역을 구할 필요가 없다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계수 행렬을 상수로 증상하고 행을 축소해 RREF를 구한 다음 증강된 행렬의 마지막 열에서 해를 추출하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 QR 분해는 $X^TX$를 제곱하지 않고 또 명시적으로 행렬의 역을 계산하지 않고도 최소제곱 문제를 해결한다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치 불안정성의 주된 위험은 Q를 계산할 때 발생하지만, 하우스홀더 변환을 통해 구현할 때는 안정적이다.&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/48</guid>
      <comments>https://degul-kim.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 30 Jan 2026 18:16:47 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] Chapter 9 연습문제</title>
      <link>https://degul-kim.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter9] 행 축소와 LU 분해: 선형대수학의 핵심 분해법 2&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9-1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해 실행하는 데 걸리는 시간 측정해보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768987890672&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import time

tic = time.time()

for i in range(1000):
  A = np.random.randn(100,100)
  P,L,U = scipy.linalg.lu(A)

toc = time.time() - tic
toc # 0.6329073905944824&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;LU 분해와 QR 분해는 각각 어떨때 쓸까? with ChatGPT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해가 QR 분해보다 더 빠르다면, QR 분해는 안쓰는걸까? 가 궁금해져서 ChatGPT에게 물어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU분해는 &lt;span&gt;&lt;span&gt;L&lt;/span&gt;&lt;/span&gt;: 아래 삼각 / &lt;span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;U&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;: 위 삼각으로 분해하는 방법으로, 계산량이 적어 빠르다. 하지만 수치적으로 불안정할 수 있고, A가 특이하면 결과가 망가질 수 있다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 LU 분해는 속도가 중요한 선형방정식 풀이용으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR분해는 Q: 직교 행렬 / R : 위 삼각행렬로 분해하는 방법으로, LU보다 계산량이 많아 느리다. 하지만 수치적으로 매우 안정적이고, 열 벡터의 독립성, 투영 구조가 깔끔하게 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 QR 분해는 정확성과 안정성이 중요한 문제용으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR분해는 최소제곱 문제, 데이터가 많고 잡음이 있을 때, 그람-슈미트, 직교기저, 차원 분석 등에서 주로 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9-2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬 곱셈을 사용해 계수-3의 6x8 행렬을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해 결과의 세 행렬을 행렬의 계수와 함께 제목에 표기한다. L의 대각선이 모두 1에 존재함에 주목한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768988445406&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;M = 5
N = 7
r = 5

A = np.random.randn(M,r) @ np.random.randn(r,N)

P,L,U = scipy.linalg.lu(A)

_,axs = plt.subplots(1,3,figsize=(12,7))

axs[0].imshow(A,vmin=-1,vmax=1,cmap='gray')
axs[0].set_title(f'A, rank={np.linalg.matrix_rank(A)}')

axs[1].imshow(L,vmin=-1,vmax=1,cmap='gray')
axs[1].set_title(f'L, rank={np.linalg.matrix_rank(L)}')

axs[2].imshow(U,vmin=-1,vmax=1,cmap='gray')
axs[2].set_title(f'U, rank={np.linalg.matrix_rank(U)}')

plt.tight_layout()
plt.savefig('Figure_09_02.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-21 오후 6.45.09.png&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMbiom/dJMcagjYY9q/RAJkXtwoPmC1IJWHKzNWik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMbiom/dJMcagjYY9q/RAJkXtwoPmC1IJWHKzNWik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMbiom/dJMcagjYY9q/RAJkXtwoPmC1IJWHKzNWik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMbiom%2FdJMcagjYY9q%2FRAJkXtwoPmC1IJWHKzNWik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1129&quot; height=&quot;403&quot; data-filename=&quot;스크린샷 2026-01-21 오후 6.45.09.png&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 L의 대각선에 모두 1인 것이 왜인지 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해는 유일하지 않기 때문에, 대각 성분을 L과 U 사이에서 마음대로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 관례적으로 L의 대각선 = 1, U = 실제 피벗 크기를 모두 가지도록 설정해 자유도를 하나 제거해서 분해를 유일하게 만드는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9-3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해를 활용하면 행렬식을 계산할 수 있다. 아래는 행렬식의 두 가지 속성이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삼각 행렬의 행렬식은 대각선의 곱이다&lt;/li&gt;
&lt;li&gt;행렬 곱셈의 행렬식은 각 행렬식의 곱과 같다($det(AB) = det(A)det(B)$)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지를 종합하면 행렬식은 L의 대각선 곱에 U의 대각선 곱을 곱한 값으로 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, L의 대각선의 곱은 모두 1이기 때문에 행렬 A의 행렬식은 단순히 U의 대각선의 곱이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768989099849&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;M = 6
A = np.random.randn(M,M)

P,L,U = scipy.linalg.lu(A) 
# scipy에서는 PA = LU
# P = 치환행렬(행을 바꾼 기록을 모아둔 행렬)
# 따라서 계산 안정성을 위해 P를 곱한다.
# P의 행렬식 : 행을 한 번 교환(-1), 두 번 교환(+1) ...

detLU = np.prod(np.diag(U)) * np.linalg.det(P)
detNp = np.linalg.det(A)

print(detLU, detNp) # -14.420330528637672 -14.420330528637665&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9-4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4x4 난수 행렬에 대해 scipy.linalg.lu를 호출한 결과를 사용해 아래 방정식을 직접 구현하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ A^{-1} = U^{-1}L^{-1}P $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$AA^{-1}$은 단위행렬일 수도 있고 아닐수도 있는데, P에 따라 달라진다. 이러한 불일치는 scipy.linalg.lu의 출력으로 인해 발생하는데, 수학 규칙이 아닌 Scipy의 규칙을 따르도록 수정하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 코드&lt;/p&gt;
&lt;pre id=&quot;code_1768989409037&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;m = 4
A = np.random.randn(m,m)

P,L,U = scipy.linalg.lu(A)

right = np.linalg.inv(U) @ np.linalg.inv(L) @ P.T
left = np.linalg.inv(A)

np.round(A@left, 10)
#array([[ 1.,  0.,  0.,  0.],
#       [-0.,  1., -0.,  0.],
#       [-0., -0.,  1., -0.],
#       [-0., -0.,  0.,  1.]])&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9-5&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬 A = PLU에서, 치환 행렬을 사용하지 않고도 $A^TA$를 $U^TL^TLU$로 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;치환 행렬을 삭제할 수 있는 이유가 무엇인지 살펴보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 파이썬에서 무작위 행렬을 사용해 $P\neq I$일 때에도 $A^TA = U^TL^TLU$임을 확인하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이유 살펴보기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해는 보통 아래처럼 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ PA = LU $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 치환행렬 P는 직교행렬이므로 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ P^T = P^{-1}, P^TP = I $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식으로 바꿔보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ A = P^TLU $ 이고, 전치하면 $A^T = U^TL^TP$ 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개를 곱하면 $A^TA = (U^TL^TP)(P^TLU)$가 되는데, $PP^T= I$이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래처럼 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ A^TA = U^TL^TLU$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬으로 확인하기&lt;/p&gt;
&lt;pre id=&quot;code_1769591693443&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = np.random.randn(4,4)

P,L,U = scipy.linalg.lu(A)

AtA_lu = U.T @ L.T @ L @ U
AtA_direct = A.T @ A

np.round(AtA_lu - AtA_direct, 10)
#array([[ 0.,  0.,  0., -0.],
#       [ 0., -0.,  0., -0.],
#       [-0.,  0.,  0., -0.],
#       [-0., -0.,  0., -0.]]) 같다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/47</guid>
      <comments>https://degul-kim.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 28 Jan 2026 18:15:11 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 9.1 연립방정식, 9.2 행 축소, 9.3 LU 분해</title>
      <link>https://degul-kim.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter9] 행 축소와 LU 분해: 선형대수학의 핵심 분해법 2&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 연립방정식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연립방정식의 중요한 특징은 개별 방정식을 서로 더하거나 뺄 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방정식에 스칼라를 곱하고 이를 다른 방정식에 더하면 연립방정식의 해를 더 쉽게 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 변형된 연립방정식과 원래 연립방정식이 동일하지는 않지만, 두 연립방정식이 일련의 선형 연산으로 연결되어 있기 때문에 해는 동일하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;연립방정식을 행렬로 변환하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연립방정식을 행렬-벡터 방정식으로 변환해 연립방정식을 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 상수가 방정식의 오른쪽에 오도록 방정식을 정리한다. 이 때 상수란, 변수에 결합되지 않은 숫자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{align} x + y = 4 \\ -x/2 + y = 2 \end{align}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 계수를 행렬로 분리한다. 각 방정식의 계수는 행이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수는 방정식의 우변에 열벡터로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1 &amp;amp; 1 \\-1/2 &amp;amp; 1 \\\end{bmatrix}\begin{bmatrix}x \\ y\end{bmatrix} =\begin{bmatrix}4 \\ 2\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬 방정식은 곱하기, 더하기, 바꾸기 등 여러 가지 조작을 할 수 있으나 스칼라 방정식과 주요 차이점은 곱하는 방향에 따라 달라지기 때문에 &lt;b&gt;행렬을 곱할 때 양쪽에서 같은 방향으로 행렬을 곱해야한다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 행 축소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 축소란 행렬의 행에 스칼라 곱셈과 덧셈이라는 두 가지 연산을 반복적으로 적용하는 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 축소의 목표는 &lt;b&gt;밀집 행렬을 상삼각 행렬로 변환하는 것&lt;/b&gt;이다. 행 축소의 결과인 상삼각 행렬을 &lt;b&gt;사다리꼴 형태&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식적으로 아래의 두 가지 조건을 만족하면 사다리꼴 형태라고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 행에서 가장 왼쪽에 있는 0이 아닌 숫자(기준 원소, pivot)가 위 행의 기준 원소 오른쪽에 있다.&lt;/li&gt;
&lt;li&gt;모든 원소가 0인 행은 0이 아닌 원소를 포함한 행 아래에 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 축소 후의 행렬의 모습은 행 축소 전의 행렬과 다르지만, 두 행렬은 선형 변환으로 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 선형 변환은 행렬로 표현할 수 있기 때문에, 행렬 곱셈을 사용해 행 축소를 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &amp;nbsp;\begin{bmatrix}1 &amp;amp; 0 \\1 &amp;amp; 1 \\\end{bmatrix}\begin{bmatrix}2 &amp;amp; 3 \\-2 &amp;amp; 2 \\\end{bmatrix} = \begin{bmatrix}2 &amp;amp; 3 \\0 &amp;amp; 5 \\\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 행렬을 $L^{-1}$ 이라 부르는데, $L^{-1}$은 행 축소 과정의 연산을 기록하고 있는 선형 변환이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;&quot;행 축소는 행 조작을 통해 행렬을 상삼각 행렬로 변환하는 작업이며, 이는 변환 행렬을 좌측에서 곱해 구할 수 있다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬의 사다리꼴 형태는 고유하지 않으며, 해당 행렬과 관련된 사다리꼴 행렬은 무한하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 무한히 가능한 사다리꼴 형태보다 두 가지 형태의 사다리꼴 행렬이 선호된다. 이 두 가지 형태는 몇 가지 제약 조건을 추가해서 고유하며, 기약 행 사다리꼴 형태와 LU 분해의 U라고 한다. (나중에 나온다고 함)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가우스 소거법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가우스 소거법은 계수 행렬을 상수벡터로 증강하고, 행을 사다리꼴 형태로 축소한 다음, 역치환을 사용해 각 변수를 차례로 푸는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1 &amp;amp; 1 \\-1/2 &amp;amp; 1 \\\end{bmatrix}\begin{bmatrix}x \\ y\end{bmatrix} =\begin{bmatrix}4 \\ 2\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 행렬에서 계수 행렬을 상수벡터로 증강한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1 &amp;amp; 1 &amp;amp; 4 \\-1/2 &amp;amp; 1 &amp;amp; 2 \\\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 증강된 행렬의 행을 축소한다. 상수의 열 벡터는 행 축소 중에 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1 &amp;amp; 1 &amp;amp; 4 \\-1/2 &amp;amp; 1 &amp;amp; 2 \\\end{bmatrix}\xrightarrow[]{1/2R_1 + R_2}\begin{bmatrix}1 &amp;amp; 1 &amp;amp; 4 \\0 &amp;amp; 3/2 &amp;amp; 4 \\\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬을 사다리꼴 형태로 만든뒤, 증강 행렬을 다시 연립방정식으로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{align} x + y = 4 \\ 3/2y = 4 \end{align}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 방정식에서 x항이 제거되었으므로 y를 구한 뒤, 첫 번째 방정식의 y에 대입하고 x를 구하면 된다. 이 과정을 역치환이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기약행 사다리꼴 형태는 유일&lt;/b&gt;하며, RREF로 축약해서 부른다. 파이썬으로도 구할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가우스-조던 소거법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가우스 소거법에서 피벗 아래뿐 아니라 &lt;b&gt;위의 원소도 모두 0으로 만드는 소거법&lt;/b&gt;을 가우스-조던 소거법이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 피벗을 1로 정규화해서 결과적으로 항등행렬처럼 만드는 것을 말한다. 결과는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}1&amp;nbsp;&amp;amp;&amp;nbsp;0&amp;nbsp;&amp;amp;&amp;nbsp;4/3&amp;nbsp;\\0&amp;nbsp;&amp;amp;&amp;nbsp;1&amp;amp;&amp;nbsp;8/3&amp;nbsp;\\\end{bmatrix}&amp;nbsp;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RREF는 고유하므로 행렬마다 정확히 하나의 RREF만 존재한다. sympy 라이브러리를 사용하면 구할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1768814098279&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sympy as sym
import numpy as np

M = np.array([[1,1,4],[-1/2,1,2]])
symMat = sym.Matrix(M)

symMat.rref()[0]
#[
#    1 0 1.3333333333
#    0 1 2.6666666667
#]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가우스-조던 소거법을 통한 역행렬 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{align} ax_1 + by_1 = 1 \\ cx_1 + dy_1 = 0 \end{align} $$&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같은 연립방정식으로 행렬 방정식으로 변환하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}a &amp;amp; b \\c &amp;amp; d \\\end{bmatrix}\begin{bmatrix}x_1 \\ y_1\end{bmatrix} = \begin{bmatrix}1 \\ 0\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RREF를 단위 행렬의 첫 번째 열로 증강된 정방 최대계수 행렬에 적용하면 행렬을 단위 행렬의 첫 번째 열로 변환하는 선형 변환을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 벡터 $\begin{bmatrix}x_1 &amp;amp; y_1&amp;nbsp;&amp;nbsp;\\\end{bmatrix}^T$는 역행렬의 첫 번째 열이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 잘 이해가 안가서 ChatGPT랑 공부했다.   (아래는 그 내용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 행렬(a,b,c,d가 있는 행렬)을 A라고 하면, 식을 아래와 같이 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ A\begin{bmatrix}x_1 \\ y_1\end{bmatrix} = \begin{bmatrix}1 \\ 0\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 역행렬의 정의는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ AA^{-1} = I$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 역행렬을 열 벡터 두 개로 적을 수 있다. 여기서 v1, v2는 모두 2x1 열벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ A^{-1} = \begin{bmatrix}v_1 &amp;amp; v_2 \\\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 아래처럼 역행렬 정의에 대입해 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ AA^{-1} = A\begin{bmatrix}v_1 &amp;amp; v_2 \\\end{bmatrix} = \begin{bmatrix}Av_1 &amp;amp; Av_2 \\\end{bmatrix}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값이 I여야 한다. 따라서 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}Av_1 &amp;amp; Av_2 \\\end{bmatrix} = \begin{bmatrix}e_1 &amp;amp; e_2 \\\end{bmatrix} = I$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ Av_1 = e_1 = \begin{bmatrix}1 \\ 0\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 $\begin{bmatrix}x_1 &amp;amp; y_1&amp;nbsp;&amp;nbsp;\\\end{bmatrix}^T$는 역행렬의 첫 번째 열이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 되풀이하여 역행렬의 두 번째 열을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 가우스-조던 소거법을 통해 역행렬을 구하는 과정을 아래 식으로 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ rref([A | I]) \Rightarrow [I | A^{-1}] $$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. LU 분해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해에서 LU는 하삼각, 상삼각에서와 같이 'lower upper'을 의미한다. 즉, 행렬을 두 개의 삼각 행렬의 곱으로 분해하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ A = LU $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \begin{bmatrix}2 &amp;amp; 2 &amp;amp; 4 \\1 &amp;amp; 0 &amp;amp; 3 \\2 &amp;amp; 1 &amp;amp; 2 \\\end{bmatrix} = \begin{bmatrix}1 &amp;amp; 0 &amp;amp; 0 \\1/2 &amp;amp; 1 &amp;amp; 0 \\1 &amp;amp; 1 &amp;amp; 1 \\\end{bmatrix}\begin{bmatrix}2 &amp;amp; 2 &amp;amp; 4 \\0 &amp;amp; -1 &amp;amp; 1 \\0 &amp;amp; 0 &amp;amp; -3 \\\end{bmatrix} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LU 분해는 파이썬으로 아래처럼 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1768815575747&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import scipy.linalg 
import numpy as np

A = np.array([
    [2,2,4],
    [1,0,3],
    [2,1,2]
])
_,L,U = scipy.linalg.lu(A)
print('L: '), print(L)
print('U: '), print(U)
#L: 
#[[1.  0.  0. ]
# [0.5 1.  0. ]
# [1.  1.  1. ]]
#U: 
#[[ 2.  2.  4.]
# [ 0. -1.  1.]
# [ 0.  0. -3.]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 축소는 $L^{-1}A = U$로 표현할 수 있으며, 여기서 L^{-1}에는 밀집 행렬 A를 상삼각행렬 U로 변환하는 행 변환 집합을 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리꼴 형태가 고유하지 않기 때문에, LU 분해도 고유하지 않다. 하지만 L의 대각선이 1이라는 제약 조건을 추가하면 최대 계수 정방 행렬 A에 대해 LU 분해가 고유하다는 것을 보장할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;치환 행렬을 통한 행 교환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행을 서로 바꿔 상삼각 형태로 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 교환은 행 축소 기법 중 하나로, 다음과 같이 치환 행렬을 통해 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &amp;nbsp;\begin{align} PA = LU \\ A = P^TLU \end{align} $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 치환 행렬은 직교 행렬이므로 $P^{-1} = P^T$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;치환 행렬의 모든 원소가 0 또는 1이고, 행은 한 번만 교환되므로 각 열에는 정확히 하나의 0이 아닌 원소만 존재하기 때문이다.&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/46</guid>
      <comments>https://degul-kim.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 19 Jan 2026 18:52:52 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] Chapter 8 연습문제</title>
      <link>https://degul-kim.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter8] 직교 행렬과 QR 분해: 선형대수학의 핵심 분해법 1&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난수 행렬 Q를 생성하고 $Q^T$와 $Q^{-1}$을 계산하는 코드를 작성해서 아래 수식 구현하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ Q^TQ = QQ^T = Q^{-1}Q = QQ^{-1} = I $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767872588595&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = np.random.randn(5,5)
Q = np.linalg.qr(A)[0]
Qt = Q.T
Qin = np.linalg.inv(Q)

QtQ = Qt @ Q
print(np.round(QtQ, 8)), print(' ')
QQt = Q @ Qt
print(np.round(QQt, 8)), print(' ')
QinQ = Qin @ Q
print(np.round(QinQ,8)), print(' ')
QQin = Q @ Qin
print(np.round(QQin, 8)), print(' ')

# 모두 단위행렬이 나온다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그람-슈미트 과정 구현하기. 4x4 난수 행렬을 사용하고, 결과를 np.linalg.qr의 Q와 대조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하우스홀더 변환과 같은 변환에는 근본적으로 부호 불확실성이 존재해서, 사소산 차이에 따라 벡터가 뒤집힐 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 Q에서 만든 Q를 빼고 만든 Q와 파이썬의 Q를 더하면 한 쪽의 0이아닌 열이 다른 쪽에서는 0이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 코드&lt;/p&gt;
&lt;pre id=&quot;code_1767873341542&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 4
A = np.random.randn(n,n)

Q = np.zeros((n,n))

for i in range(n):
    
    # 원본 벡터 복사
    Q[:,i] = A[:,i]
    
    a = A[:,i] 
    for j in range(i):
        q = Q[:,j] 
        Q[:,i]=Q[:,i]-np.dot(a,q)/np.dot(q,q)*q # q방향 성분을 계산한 뒤에 뺀다.
        # np.dot(a,q)/np.dot(q,q) = a가 q방향으로 얼마나 기울어있는지 구함(스칼라)
        # *q = 벡터를 빼야하기 때문에 q를 곱해 q방향의 벡터를 만듦
    
    # 정규화
    Q[:,i] = Q[:,i] / np.linalg.norm(Q[:,i])

Q2,R = np.linalg.qr(A)

print( np.round( Q-Q2 ,10) ), print(' ')
print( np.round( Q+Q2 ,10) )
#[[ 1.82397971 -0.          0.          0.30413865]
# [-0.20295416  0.          0.          0.79317595]
# [-0.7945823  -0.         -0.          0.44391235]
# [ 0.02337943 -0.         -0.         -1.75536703]]
 
#[[ 0.          0.6975557  -0.30661661  0.        ]
# [-0.         -0.57618663 -1.73138398  0.        ]
# [ 0.          1.75739636 -0.28833806  0.        ]
# [ 0.          0.30493122 -0.90838149  0.        ]]

# 둘 중 하나는 항상 0열이 나오는 열이 생김
# &amp;rarr; 결국 Q와 Q2는 같은 직교공간을 표현한다는 뜻&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 직교에 가깝지만 직교는 아닌 행렬에 QR 분해를 적용하면 어떤 일이 발생하는지 알아보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6x6 난수 행렬의 QR 분해로부터 U라는 직교행렬을 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. U의 QR분해를 계산하고 R = I임을 확인하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. U의 각 열의 노름을 수정하기. 1~6열의 노름을 10~15값의 노름으로 설정한다.(U의 첫 번째 열의 노름은 10, 두번째 열의 노름은 11)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변조된 U 행렬을 QR 분해하여 그 R의 대각선 원소가 10에서 15인 대각 행렬인지 살펴보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 행렬의 $Q^TQ$는 무엇인지 보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 원소 $u_{1.4} = 0$으로 설정하여 U의 직교성을 깨뜨리기. R은 어떻게 되며 그 이유는 무엇인지 살펴보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767874292908&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 6
A = np.random.randn(n,n)

U = np.linalg.qr(A)[0]

#1
Q,R = np.linalg.qr(U)
print(np.round(R,8))
#[[ 1. -0.  0.  0.  0.  0.]
# [ 0.  1.  0. -0.  0. -0.]
# [ 0.  0.  1.  0.  0.  0.]
# [ 0.  0.  0.  1.  0. -0.]
# [ 0.  0.  0.  0.  1.  0.]
# [ 0.  0.  0.  0.  0.  1.]]

# U는 이미 직교행렬이기 때문에 U를 QR분해하면 Q는 U 자기자신이 되고, R는 단위행렬이 된다.

#2
for i in range(U.shape[0]):
  U[:,i] = U[:,i]*(10+i)
Q2, R2 = np.linalg.qr(U)
print(np.round(R2,8))
#[[10. -0.  0.  0.  0. -0.]
# [ 0. 11. -0.  0.  0. -0.]
# [ 0.  0. 12.  0.  0. -0.]
# [ 0.  0.  0. 13.  0.  0.]
# [ 0.  0.  0.  0. 14.  0.]
# [ 0.  0.  0.  0.  0. 15.]]

print(np.round(Q2.T @ Q,8))
#[[ 1. -0.  0. -0. -0.  0.]
# [-0.  1. -0. -0. -0. -0.]
# [-0. -0.  1. -0. -0. -0.]
# [ 0. -0.  0.  1.  0. -0.]
# [ 0. -0. -0.  0.  1. -0.]
# [-0.  0. -0.  0.  0.  1.]]

# QR 분해 = 벡터의 길이를 R의 대각 성분으로 뽑아내고 방향은 Q에 집어넣음.

#3
U[0,3] = 0
print(np.round(np.linalg.qr(U)[1]))
#[[10.  0. -0.  0.  0. -0.]
# [ 0. 11.  0.  1. -0.  0.]
# [ 0.  0. 12.  0.  0.  0.]
# [ 0.  0.  0. 13.  0. -1.] 
# [ 0.  0.  0.  0. 14.  0.]
# [ 0.  0.  0.  0.  0. 15.]]

# 직교가 깨지면, QR 분해는 이를 복원하기 위해 앞 열의 방향을 제거하는 과정(투영)을 수행하고, 
# 그 투영 크기가 R의 비대각 성분에 나타난다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 역행렬 계산 방식의 수치 오차와 QR 기반의 오차를 비교하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연습문제 7-2의 코드를 복사한 다음, 행렬을 입력으로 받고 그 역행렬을 출력하는 파이썬 함수에 붙여넣는다.(oldSchoolInv)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 5x5 난수 행렬을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 방법과 QR 분해 방법을 사용해 그 역행렬을 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역 추정 오차는 행렬과 계산된 역행렬의 곱으로부터 np.eye로 구한 단위행렬의 유클리드 거리로 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 막대 그래프로 만들어 두 가지 방법을 x축에, 오차에 대한 유클리드 거리를 y축에 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 여러 번 실행하고 그래프를 살펴보고, 30x30 행렬을 사용해 다시 시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767875262616&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def oldSchoolInv(A):
  n = A.shape[0]

  if not np.diff(A.shape)[0]==0:
    raise Exception('Matrix must be square.')

  # 소행렬
  M = np.zeros((n,n))
  # 격자행렬
  G = np.zeros((n,n))

  # 여인수행렬 구현
  for i in range(n):
    for j in range(n):
      rows = [True]*n
      rows[i] = False # i행 제거

      cols = [True]*n
      cols[j] = False # j열 제거

      M[i,j] = np.linalg.det(A[rows,:][:,cols]) # i행,j열 제거 후 행렬식
      G[i,j] = (-1)**(i+j) # 격자행렬 완성

  # 여인수행렬
  C = M * G

  # 역행렬 = 여인수행렬의 전치에서 행렬식을 나눈다
  Ainv = C.T / np.linalg.det(A)
  return Ainv

m = 30
B = np.random.randn(m,m)

Q,R = np.linalg.qr(B)
Binv_qr = oldSchoolInv(R) @ Q.T
Binv_old = oldSchoolInv(B)

BBi_qr = B @ Binv_qr
BBi_old = B @ Binv_old

trueI = np.eye(m)
sse = [0,0]
# 각각 유클리드 거리를 구함
sse[0] = np.sqrt(np.sum((BBi_old-trueI)**2))
sse[1] = np.sqrt(np.sum((BBi_qr-trueI )**2))

plt.figure(figsize=(6,6))

plt.bar(range(2),sse,color=[.7,.7,.7])
plt.xticks(range(2),labels=['OldSchool','QR'])
plt.ylim([0,np.max(sse)*1.1])
plt.ylabel('Eucl. distance to identity')
plt.title(f'Inverse error ({m}x{m} matrix)',ha='center')
plt.savefig('Figure_08_03.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력하면 아래와 같이 나온다(30x30 일 때)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;oldschool.png&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bva7LG/dJMcahQExOo/8bhiCUL5v0VocpNOc7iRJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bva7LG/dJMcahQExOo/8bhiCUL5v0VocpNOc7iRJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bva7LG/dJMcahQExOo/8bhiCUL5v0VocpNOc7iRJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbva7LG%2FdJMcahQExOo%2F8bhiCUL5v0VocpNOc7iRJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;381&quot; data-filename=&quot;oldschool.png&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5x5 행렬일 때는 oldSchool이 더 작을 때도 있었지만, 30x30 행렬이 되니 QR이 OldSchool 보다 작을 때가 훨씬 많아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 문제는&lt;b&gt;&amp;nbsp;QR 기반 역행렬이 고전 adjoint 방식보다 훨씬 정확하고 안정적&lt;/b&gt;이라는 것을 보여준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-5&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 연습문제의 코드를 매번 다른 난수행렬을 사용하여 실험을 100회 이상 반복하는 for문에 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 반복마다 오차(유클리드 거리)를 저장하고 모든 실험 결과의 평균과 모든 개별 오차를 보여주는 그래프를 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5x5 행렬과 30x30 행렬에 대해 실험을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 방법 대신 np.linalg.inv를 사용해 R을 반전시켜서 효과가 있는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767875717260&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 30

numExprs = 100

sse = np.zeros((numExprs,2))

for expi in range(numExprs):

  A = np.random.randn(n,n)

  # old school
  Ainv_old = oldSchoolInv(A)
  AAi_old  = Ainv_old@A

  # QR
  Q,R = np.linalg.qr(A)
  #Ainv_qr = oldSchoolInv(R)@Q.T # using the old-school method
  Ainv_qr = np.linalg.inv(R)@Q.T # using numpy's inv
  AAi_qr  = Ainv_qr@A

  # differences
  trueI = np.eye(n)
  sse[expi,0] = np.sqrt(np.sum((AAi_old-trueI)**2))
  sse[expi,1] = np.sqrt(np.sum((AAi_qr-trueI )**2))


# and plot
plt.figure(figsize=(6,6))

plt.plot(np.zeros(numExprs),sse[:,0],'ko')
plt.plot(np.ones(numExprs),sse[:,1],'ko')
plt.bar(range(2),np.mean(sse,axis=0),color=[.7,.7,.7])

plt.xticks(range(2),labels=['OldSchool','QR'])
plt.ylim([0,np.max(sse)*1.1])
plt.ylabel('Eucl. distance to identity')
plt.title(f'Inverse error ({n}x{n} matrix)',ha='center')
plt.savefig('Figure_08_04a.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30x30 행렬일 때 출력하면 아래와 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;inverse1.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3lQl/dJMcacogIQj/ZoSoIQicbRwvmkEauOEffk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3lQl/dJMcacogIQj/ZoSoIQicbRwvmkEauOEffk/img.png&quot; data-alt=&quot;QR에서 oldSchool로 R의 inverse를 구했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3lQl/dJMcacogIQj/ZoSoIQicbRwvmkEauOEffk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3lQl%2FdJMcacogIQj%2FZoSoIQicbRwvmkEauOEffk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;403&quot; data-filename=&quot;inverse1.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;QR에서 oldSchool로 R의 inverse를 구했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;inverse2.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db57fD/dJMcaiPvNG7/oPyav8TA1WkuhmGTHuqJY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db57fD/dJMcaiPvNG7/oPyav8TA1WkuhmGTHuqJY0/img.png&quot; data-alt=&quot;QR에서 np.linalg.inv로 R의 inverse를 구했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db57fD/dJMcaiPvNG7/oPyav8TA1WkuhmGTHuqJY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb57fD%2FdJMcaiPvNG7%2FoPyav8TA1WkuhmGTHuqJY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;405&quot; data-filename=&quot;inverse2.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;QR에서 np.linalg.inv로 R의 inverse를 구했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제도 8-4처럼 QR 분해로 역행렬을 구하는 것이 훨씬 더 안정적이라는 것을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ np.linalg.inv를 사용하면 더 안정적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-6&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정방 직교 행렬은 모든 특잇값이 1이다. 즉, 유도된 2-노름(유도된 노름은 가장 큰 특잇값)이 1이고, 프로베니우스 노름이 M이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로베니우스 노름이 M인 이유는 제곱된 특잇값을 더한 값의 제곱근이기 때문이다. 이러한 특성을 확인하는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난수 행렬의 QR 분해를 통해 MxN 직교 행렬을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;np.linalg.norm을 사용해 유도된 2-노름을 계산하고 5장에서 배운 방정식을 사용해 M의 제곱근으로 나누어 프로베니우스 노름을 계산한다. 두 값이 모두 1인지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 행렬-벡터 곱셈을 사용해 유도된 노름의 의미를 알아본다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작위로 M개의 원소를 가진 열벡터 v를 생성한 다음 v와 Qv의 노름을 계산한다. 이 노름은 서로 같아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767877183332&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 13
Q,R = np.linalg.qr(np.random.randn(n,n))

print( np.round(np.linalg.norm(Q,2),8),               # induced 2-norm
       np.sqrt( np.sum(Q**2) )/np.sqrt(n) # 프로베니우스 노름을 n 제곱근으로 나눔
)
# 둘 다 1

# 직교행렬 Q는  길이를 절대 안 바꾸는 변환(회전/반사)이라서
# 공간에서 가장 많이 늘어난 벡터의 길이 증가량이 1이다.
# 따라서 유도된 2-노름 = 가장 큰 특잇값 = 1

# 직교행렬 Q의 각 열벡터의 길이는 1
# 프로베니우스 노름 = 각 열의 노름의 제곱을 모두 더해 루트 씌운 것
# 각 열의 노름이 모두 1이니까 프로베니우스 노름 = n의 제곱근
# 따라서 n의 제곱근으로 나누면 1

v = np.random.randn(n,1) # 랜덤한 벡터 생성
norm_v  = np.linalg.norm(v) # 벡터의 노름을 구함
norm_Qv = np.linalg.norm(Q@v) # 직교행렬 Q로 회전/반사시킨 뒤 노름을 구함

print(norm_v)
print(norm_Qv)
# 두 값이 같다
# 즉, 직교행렬 Q는 벡터의 길이를 절대 바꾸지 않는다

# 유도된 2-노름은 어떤 벡터를 곱했을 때 가장 많이 늘어나는 비율을 의미함
# 직교행렬 Q의 2-노름은 1
# 즉, 직교행렬 Q는 직교행렬은 벡터를 절대 늘리지 않고 회전만 시킨다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식으로도 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 노름 $ \left\| v\right\| $는 $v^Tv$로 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 $ \left\|Qv\right\| = \left ( Qv \right )^TQv = v^TQ^TQv = v^Tv = \left\| v\right\|$ 이렇게 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;직교 행렬을 통해 벡터를 회전시킬 수는 있지만 벡터의 크기는 조절할 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8-7&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 높고 최대열계수일 때, R 의 처음 N 행은 상삼각이고, 반면에 N+1부터 M까지의 행은 0이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 10x4 난수 행렬을 사용해 이를 확인한다. 경제형 분해가 아닌 완전형 QR 분해를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R은 정방이 아니므로 비가역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 처음 N개의 행을 구성하는 행렬은 정방이고 최대계수이므로 완전 역행렬을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 높은 행렬은 의사역행렬을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 역행렬을 모두 계산하고, R의 처음 N행의 완전 역행렬이 R의 의사역행렬의 처음 N열과 같다는 것을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767877764427&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = np.random.randn(10,4)

# get R
_,R = np.linalg.qr(A,'complete')

# examine R
np.round(R,3)
# array([[ 3.461, -0.716, -0.817, -0.628],
#       [ 0.   , -2.678, -0.591,  0.165],
#       [ 0.   ,  0.   ,  2.549, -0.259],
#       [ 0.   ,  0.   ,  0.   ,  3.317], &amp;lt; 4행까지 상삼각이다
#       [ 0.   ,  0.   ,  0.   ,  0.   ],
#       [ 0.   ,  0.   ,  0.   ,  0.   ],
#       [ 0.   ,  0.   ,  0.   ,  0.   ],
#       [ 0.   ,  0.   ,  0.   ,  0.   ],
#       [ 0.   ,  0.   ,  0.   ,  0.   ],
#       [ 0.   ,  0.   ,  0.   ,  0.   ]])

# 처음 4행까지만 자름
Rsub = R[:4,:]

# inverses
Rsub_inv = np.linalg.inv(Rsub) # 4행까지 자른 행렬의 역행렬
Rleftinv = np.linalg.pinv(R) # R 자체의 의사역행렬

# print out both
print('Full inverse of R submatrix:')
print(np.round(Rsub_inv,3)), print(f'\n\n')

# Full inverse of R submatrix:
# [[ 0.369 -0.134  0.242 -0.13 ]
# [-0.    -0.266  0.11  -0.045]
# [-0.    -0.    -0.33   0.301]
# [ 0.     0.     0.     0.405]]

print('Left inverse of R:')
print(np.round(Rleftinv,3))

# Left inverse of R:
# [[ 0.369 -0.134  0.242 -0.13   0.     0.     0.     0.     0.     0.   ]
# [-0.    -0.266  0.11  -0.045  0.     0.     0.     0.     0.     0.   ]
# [-0.     0.    -0.33   0.301  0.     0.     0.     0.     0.     0.   ]
# [-0.    -0.     0.     0.405  0.     0.     0.     0.     0.     0.   ]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 긴 R(10&amp;times;4)의 의사역행렬 pinv(R)은, 위쪽 4&amp;times;4 상삼각 부분 Rsub의 역행렬을 뒤에 0을 더해서 길게 만든 것과 같다.&lt;/p&gt;
&lt;p data-end=&quot;1361&quot; data-start=&quot;1328&quot; data-ke-size=&quot;size16&quot;&gt;A는 10&amp;times;4이고 rank=4이면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1458&quot; data-start=&quot;1363&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1393&quot; data-start=&quot;1363&quot;&gt;Q는 10차원 공간에서 4차원 부분공간을 만들어내고&lt;/li&gt;
&lt;li data-end=&quot;1427&quot; data-start=&quot;1394&quot;&gt;Rsub는 그 공간에서 좌표를 변환하는 진짜 역할을 하고&lt;/li&gt;
&lt;li data-end=&quot;1458&quot; data-start=&quot;1428&quot;&gt;나머지 6개 행의 0은 그냥 쓸데없는 padding&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/45</guid>
      <comments>https://degul-kim.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 8 Jan 2026 22:13:58 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] 8.1 직교 행렬, 8.2 그람-슈미트 과정</title>
      <link>https://degul-kim.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter8] 직교 행렬과 QR 분해: 선형대수학의 핵심 분해법 1&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 직교 행렬(orthogonal matrix)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직교 행렬은 아래 두 가지 속성을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행렬의 모든 열은 직교한다.&lt;/li&gt;
&lt;li&gt;각 열의 노름은 정확히 1이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 속성을 아래 수식으로 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &amp;nbsp;\left&amp;lt;&amp;nbsp;q_i,&amp;nbsp;q_j\right&amp;gt;&amp;nbsp;=&amp;nbsp;\left\{\begin{matrix} &lt;br /&gt;0,&amp;nbsp;\textit{if}\;&amp;nbsp;&amp;nbsp;i\neq&amp;nbsp;j&amp;nbsp;\\&amp;nbsp;1,&amp;nbsp;\textit{if}\;&amp;nbsp;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;j &lt;br /&gt;\end{matrix}\right. $$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식은 모든 열은 자기자신과의 내적은 1이지만 다른 열과의 내적은 0이라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬의 왼쪽으로 그 행렬의 전치를 곱하면 열들 사이의 모든 내적을 구할 수 있다. 아래 수식이 성립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ Q^TQ = I$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 직교 행렬의 역행렬은 그 행렬의 전치이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 그람-슈미트 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그람-슈미트 과정(GS, G-S)은 비직교 행렬을 직교 행렬로 변환하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 작은 숫자로 나눗셈과 곱셈을 많이 수행하면 수치적으로 불안정해지기 때문에 많이 사용되지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 알고리즘으로 구현된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행렬 V가 열 $v_1$부터 $v_n$까지로 구성되어 있을 때, 열 $q_k$를 갖는 직교 행렬 Q로 변환한다.&lt;/li&gt;
&lt;li&gt;V의 모든 열벡터는 첫 번째(가장 왼쪽)부터 시작해 마지막(가장 오른쪽)까지 차례로 이동한다.&lt;/li&gt;
&lt;li&gt;알고리즘
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) 직교벡터 분해를 사용해 $v_k$를 행렬 Q의 모든 이전 열과 직교화한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$q_{k-1}, q_{k-2}$에서 $q_1$까지의 모든 열과 수직인 $v_k$의 원소를 계산한다.&lt;/li&gt;
&lt;li&gt;이렇게 직교화된 벡터를 $v_k^*$라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(2) $v_k^*$를 단위 길이로 정규화한다. 이것은 Q 행렬의 k번째 열인 $q_k$가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;잘 이해가 안가서 다시 정리  &lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직교 행렬 Q를 만드는데, 먼저 V를 변형한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$v_1^*$는 $v_1$을 그대로 쓰고, $v_2^*$는 $ v_1^*$과 직교하게 만든다. 그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;$v_3^*$는 $&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;v_2^*$, $&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;v_1^*$&lt;/span&gt; 과 직교하게 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이제 이렇게 만들어진 $v_k^*$를 단위길이로 정규화하면 $q_k$가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3. QR 분해&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;GS는 행렬을 직교 행렬 Q로 변환한다. 이때 Q는 원래 행렬과 당연히 달라지므로, 원래 행렬에 대한 정보가 손실되었다고 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 손실된 정보는 Q에 곱하는 다른 행렬 R에 쉽게 복구해서 저장할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 R을 생성하는 것은 QR분해의 정의에서 바로 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$ A = QR $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$ Q^TA = Q^TQR $$&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$ Q^TA = R $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정방 행렬의 QR분해는파이썬에서 np.linalg.qr() 함수로 구현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;R 행렬은 항상 상삼각 행렬이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q와 R의 크기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q와 R의 크기는 분해될 행렬 A의 크기와 QR 분해가 '경제형(축소)'인지 '완전형(전체)'인지에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경제형인지 아니면 완성형인지는 높은 행렬의 QR 분해에서만 적용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경제형(축소) : 높은 행렬(M&amp;gt;N)에서 열이 N개인 Q 행렬을 만든다. 높은 Q가 만들어진다.&lt;/li&gt;
&lt;li&gt;완전형(전체) : 높은 행렬(M&amp;gt;N)에서 열이 M개인 Q 행렬을 만든다. 정방 Q가 만들어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rank에 따른 Q, R의 형태 with ChatGPT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 보던 도중에 그림 8-2가 이해가 안되어서 GPT랑 같이 공부했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(확실하진 않지만) 책에 오류가 있는 것 같았다. 책에서는 높은 경제형일 때 원래 행렬 A가 MxN(M&amp;gt;N)형태이고 r=k일 때, 직교행렬 Q는 MxN, r=N이고, R은 MxN r=k라고 적혀있다. 그런데 Q와 R을 행렬곱하면 A가 되어야 하는데 MxN과 MxN 행렬은 곱할 수 없지 않나.. 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 GPT와 공부한 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 경제형 QR&lt;/b&gt;에서 &lt;b&gt;Q는 독립적인 열벡터 수만큼 만든다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q는 A의 열 공간(column space)을 정규직교 기저로 바꾼 행렬로, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;rank가 k이면&lt;span&gt; Mxk 의 형태가 되는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 완전형 QR은 Q를 MxM의 전체 직교기저로 확장하고, R도 &lt;b&gt;M&amp;times;N으로 확장&lt;/b&gt;한 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 k개의 열은 선형 독립이며, 나머지 N&amp;minus;k개는 &lt;b&gt;독립열들의 조합으로 표현되는 종속열&lt;/b&gt;이다&lt;b&gt;.&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 아래와 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;형태&lt;/td&gt;
&lt;td&gt;Q 크기&lt;/td&gt;
&lt;td&gt;R 크기&amp;nbsp;&lt;/td&gt;
&lt;td&gt;만든 직교벡터&amp;nbsp;&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;경제형 QR&lt;/td&gt;
&lt;td&gt;MxK&lt;/td&gt;
&lt;td&gt;kxN&lt;/td&gt;
&lt;td&gt;k&lt;/td&gt;
&lt;td&gt;&lt;b&gt;필요한 기저만&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;완전형 QR&lt;/td&gt;
&lt;td&gt;MxM&lt;/td&gt;
&lt;td&gt;MxN&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;전체 공간 기저까지 확장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 np.linalg.qr()로 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 인수에 'complete'를 넣으면 전체 QR 분해가 되고, 'reduced'를 넣으면 경제형 QR 분해가 된다.(기본값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아가서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직교화로 인한 Q와 A의 계수 차이는 A의 열공간이 $R^M$의 저차원 하위 공간일지라도 Q는 $R^M$ 전체를 생성한다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR 분해는 모든 행렬의 크기와 계수에 대해 고유하지 않다. 즉, 같은 A에 대해 서로 다른 Q,R 조합이 얼마든지 나올 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;R이 상삼각 행렬인 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 세 가지 사실에서 도출된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R은 $Q^TA = R$ 수식으로 부터 도출된다.&lt;/li&gt;
&lt;li&gt;곱 행렬의 하삼각은 왼쪽 행렬의 아래쪽 행과 오른쪽 행렬의 위쪽 열 사이의 내적으로 이루어진다.&lt;/li&gt;
&lt;li&gt;$Q^T$의 행은 $Q$의 열이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합하면 직교화는 왼쪽에서 오른쪽으로 열 단위로 처리되기 때문에 Q의 아래쪽 열은 A의 위쪽 열과 직교한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 R의 하삼각은 직교화된 벡터 쌍으로 이루어져 있다. 반대로 Q의 위쪽 열은 A의 아래쪽 열과 직교하지 않으므로 내적이 0이되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A의 열 i와 j가 이미 직교한 경우 R의 해당 (i,j)번째 원소는 0이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 직교 행렬의 QR 분해를 계산하면 R은 대각선 원소가 A의 각 열의 노름인 대각 행렬이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, A = Q이면 R = I이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QR 분해와 역&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR 분해를 사용하면 역행렬을 수직적으로 더 안정적으로 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$A = QR$ 수식을 $A^{-1} = R^{-1}Q^{-1}$로 반전시키면, A의 역행렬은 R의 역행렬에 Q의 역행렬, 즉 Q의 전치를 곱해서 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Q는 하우스홀더 변환 알고리즘 덕분에 수학적으로 안정적이고, R은 단순히 행렬 곱셈의 결과이기 때문에 역시 수치적으로 안정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR 분해의 핵심은 그람-슈미트 과정의 알고리즘보다 수치적으로 더 안정적인 방법을 제공하는 것이다.&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/44</guid>
      <comments>https://degul-kim.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 7 Jan 2026 22:14:51 +0900</pubDate>
    </item>
    <item>
      <title>[개발자를 위한 실전 선형대수학] Chapter 7 연습문제</title>
      <link>https://degul-kim.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;[chapter7] 역행렬 : 행렬 방정식의 만능 키&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역행렬의 역행렬은 원래 행렬이 된다. 파이썬을 사용해 이를 증명하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;내 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767428167374&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = np.random.randn(3,3)
Ainv = np.linalg.inv(A)
Ainvinv = np.linalg.inv(Ainv)
print(Ainvinv)
print(A) # 두 개가 같다&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-3-3 절에서 설명한 전체 알고리즘을 구현하고 그림 7-3을 재현하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767428769562&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = 4
A = np.random.randn(n,n)

# 소행렬
M = np.zeros((n,n))
# 격자행렬
G = np.zeros((n,n))

# 여인수행렬 구현
for i in range(n):
  for j in range(n):
    rows = [True]*n
    rows[i] = False # i행 제거

    cols = [True]*n
    cols[j] = False # j열 제거

    M[i,j] = np.linalg.det(A[rows,:][:,cols]) # i행,j열 제거 후 행렬식
    G[i,j] = (-1)**(i+j) # 격자행렬 완성

# 여인수행렬
C = M * G

# 역행렬 = 여인수행렬의 전치에서 행렬식을 나눈다
Ainv = C.T / np.linalg.det(A)

# 역행렬2
Ainv2 = np.linalg.inv(A)

# 두 개가 같은지 확인
np.round(Ainv2 - Ainv, 8)

# array([[ 0., -0., -0., -0.],
#       [ 0., -0.,  0.,  0.],
#       [-0.,  0.,  0., -0.],
#       [ 0.,  0.,  0., -0.]])&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넓은 행렬에 대한 오른쪽 역행렬을 유도하기. 넓은 행렬에 대해 그림 7-4 재현하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767429409922&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;W = np.random.randint(-10,11,size=(4,40))

print( f'This matrix has rank={np.linalg.matrix_rank(W)}\n\n' )

WWt = W@W.T
WWt_inv = np.linalg.inv(WWt)
print( np.round(WWt_inv@WWt,4) )

R = W.T @ WWt_inv&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-5&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 의사역행렬(np.linalg.pinv)이 가역&amp;nbsp; 행렬의 완전 역행렬(np.linalg.inv)과 같다는 것을 구현하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 의사역행렬이 높은 최대열계수 행렬의 경우 왼쪽 역행렬과 같고, 넓은 최대계수 행렬의 경우 오른쪽 역행렬과 같다는 것을 나타내기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;내 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767430199735&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = np.random.randint(-10,11,size=(4,4))

Ainv = np.linalg.inv(A)
Apinv = np.linalg.pinv(A)

np.round(Apinv - Ainv, 8)
# array([[-0., -0., -0., -0.],
#       [-0.,  0.,  0.,  0.],
#       [ 0.,  0.,  0.,  0.],
#       [ 0., -0.,  0., -0.]])

m,n = 14,4
# 높은 행렬
B = np.random.randint(-10,11,size=(m,n))
Bleft = np.linalg.inv(B.T @ B) @ B.T
Bpinv = np.linalg.pinv(B)
np.round(Bpinv - Bleft, 8)
#array([[ 0., -0., -0., -0., -0., -0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
#         0.],
#       [ 0.,  0.,  0., -0., -0., -0.,  0., -0.,  0.,  0., -0.,  0.,  0.,
#         0.],
#       [ 0., -0., -0., -0., -0., -0.,  0.,  0.,  0.,  0., -0.,  0.,  0.,
#         0.],
#       [ 0., -0.,  0., -0.,  0., -0.,  0.,  0.,  0., -0., -0.,  0., -0.,
#        -0.]])

# 넓은 행렬
C = np.random.randint(-10, 11, size=(n,m))
Cright = C.T @ np.linalg.inv(C@C.T)
Cpinv = np.linalg.pinv(C)

np.round(Cpinv - Cright, 8)
# array([[ 0.,  0.,  0., -0.],
#       [ 0., -0.,  0., -0.],
#       [ 0., -0.,  0., -0.],
#       [-0., -0., -0.,  0.],
#       [ 0.,  0.,  0.,  0.],
#       [ 0., -0.,  0.,  0.],
#       [ 0.,  0.,  0., -0.],
#       [ 0.,  0.,  0., -0.],
#       [-0., -0., -0., -0.],
#       [-0., -0., -0.,  0.],
#       [ 0.,  0.,  0., -0.],
#       [-0., -0.,  0., -0.],
#       [-0., -0., -0., -0.],
#       [ 0.,  0.,  0., -0.]])&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-6&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIVE EVIL 규칙은 곱 행렬의 역행렬에 적용된다. 코드에서 이를 테스트해보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 정방 최대계수 행렬 A와 B를 생성한 다음, 유클리드 거리를 사용해 $(AB)^{-1}$, $A^{-1}B^{-1}$, $B^{-1}A^{-1}$을 비교하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767430630996&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;N = 4
A = np.random.randn(N,N)
B = np.random.randn(N,N)

op1 = np.linalg.inv(A@B)
op2 = np.linalg.inv(A) @ np.linalg.inv(B)
op3 = np.linalg.inv(B) @ np.linalg.inv(A)

# 유클리드 거리
dist12 = np.sqrt(np.sum( (op1-op2)**2 ))
dist13 = np.sqrt(np.sum((op1-op3) ** 2))

print(f'Distance between (AB)^-1 and (A^-1)(B^-1) is {dist12:.8f}')
print(f'Distance between (AB)^-1 and (B^-1)(A^-1) is {dist13:.8f}')

# Distance between (AB)^-1 and (A^-1)(B^-1) is 2.08813520
# Distance between (AB)^-1 and (B^-1)(A^-1) is 0.00000000
# 즉 (AB)^-1은 (B^-1)(A^-1)과 같다.(LIVE EVIL) 법칙&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-7&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIVE EVIL 규칙이 단방향 역행렬에도 적용되는지 테스트하기.&lt;/p&gt;
&lt;pre id=&quot;code_1767430820904&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;M,N = 14,4
A = np.random.randn(M,N)

op1 = np.linalg.inv(A@A.T)
op2 = np.linalg.inv(A) @ np.linalg.inv(A.T)
# 정방이 아니기 때문에 역행렬 정의 불가.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-8&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림 7-6을 재현하는 코드 작성하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 연습문제 6-3의 코드를 복사하고, 그림을 재현한 후 왼쪽 아래 원소를 1로 설정해 변환행렬을 비가역으로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;정답 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767430987133&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;T = np.array([
    [1, .5],
    [0, .5]
])

Tinv = np.linalg.inv(T)

# np.linspace(start, end, num) = start부터 end까지 포함해서 num개의 숫자를 균등 간격으로 생성
# 원을 20등분한 각도를 하나씩 만들되, 시작점(0)과 끝점(2pi)을 중복하지 않게 함
theta = np.linspace(0, 2*np.pi - 2*np.pi/20, 20)
origPoints = np.vstack((np.cos(theta), np.sin(theta))) # 단위 원 위의 좌표들

# 변형
transformedPoints = T @ origPoints

# 다시 되돌리기
backTransformed = Tinv @ transformedPoints

# 이미지화
plt.figure(figsize=(6,6))
plt.plot(origPoints[0,:],origPoints[1,:],'ko',label='Original')
plt.plot(transformedPoints[0,:],transformedPoints[1,:],'s',
         color=[.7,.7,.7],label='Transformed')
plt.plot(backTransformed[0,:],backTransformed[1,:],'rx',markersize=15,
         color=[.7,.7,.7],label='Inverse-transformed')

plt.axis('square')
plt.xlim([-2,2])
plt.ylim([-2,2])
plt.legend()
plt.savefig('Figure_07_06.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하면 아래와 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;inverse.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDK932/dJMcajt4DHS/iTgAhfY9KCqhFBsvrlPWk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDK932/dJMcajt4DHS/iTgAhfY9KCqhFBsvrlPWk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDK932/dJMcajt4DHS/iTgAhfY9KCqhFBsvrlPWk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDK932%2FdJMcajt4DHS%2FiTgAhfY9KCqhFBsvrlPWk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;384&quot; data-filename=&quot;inverse.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-9&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힐버트 행렬을 생성하고, 식 7-1에 맞춰 정수를 입력으로 받아 힐버트 행렬을 생성하는 파이썬 함수 작성하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그림 7-5를 재현하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 for문 사용한 버전, 사용하지 않은 버전(외적) 두 가지의 정확성 살펴보기.&lt;/p&gt;
&lt;pre id=&quot;code_1767431733017&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def hilbmat(k):
  H = np.zeros((k,k))
  for i in range(k):
    for j in range(k):
      H[i,j] = 1/((i+1)+(j+1)-1) # 인덱스 0부터니까 +1 해준다.
  return H

def hilbmat2(k):
  # np.arange로 1부터 k까지 일정 간격으로 숫자를 만든다.
  # reshape로 열벡터로 바꿔준다.(-1 = 알아서 개수만큼 = 40)
  k = np.arange(1,k+1).reshape(1,-1)

  # 브로드캐스팅을 활용한다.
  # k가 4라면 k.T는 4x1 행렬, k는 1x4 행렬
  # k.T + k의 결과는 4x4
  # 따라서 i+j-1이 된다.
  return 1/(k.T+k-1)

print(hilbmat(5)), print(' ')
print(hilbmat2(5)), print(' ')
from scipy.linalg import hilbert
print( hilbert(5) )
# 3개가 모두 같다.

#[[1.         0.5        0.33333333 0.25       0.2       ]
# [0.5        0.33333333 0.25       0.2        0.16666667]
# [0.33333333 0.25       0.2        0.16666667 0.14285714]
# [0.25       0.2        0.16666667 0.14285714 0.125     ]
# [0.2        0.16666667 0.14285714 0.125      0.11111111]]
 
#[[1.         0.5        0.33333333 0.25       0.2       ]
# [0.5        0.33333333 0.25       0.2        0.16666667]
# [0.33333333 0.25       0.2        0.16666667 0.14285714]
# [0.25       0.2        0.16666667 0.14285714 0.125     ]
# [0.2        0.16666667 0.14285714 0.125      0.11111111]]
 
#[[1.         0.5        0.33333333 0.25       0.2       ]
# [0.5        0.33333333 0.25       0.2        0.16666667]
# [0.33333333 0.25       0.2        0.16666667 0.14285714]
# [0.25       0.2        0.16666667 0.14285714 0.125     ]
# [0.2        0.16666667 0.14285714 0.125      0.11111111]]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7-10&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힐버트 행렬 함수를 사용해서 힐버트 행렬을 만들고, np.linalg.inv를 사용해서 역을 계산하고 두 행렬의 곱을 계산하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 곱은 단위행렬과 일치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 곱과 np.eye가 생성한 실제 단위 행렬 사이의 유클리드 거리는 0이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 3x3에서 12x12에 이르는 다양한 행렬 크기 범위에 대해 수행되도록 for문제 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 행렬 크기에 대해 유클리드 거리와 힐버트 행렬의 조건수를 저장한다.(np.linalg.cond 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이전 코드를 되풀이하지만 힐버트 행렬 대신 가우스 난수 행렬 사용하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 결과를 그래프로 나타내기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;내 코드&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767494989371&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from scipy.linalg import hilbert

sizes = np.arange(3, 13)

distances = np.zeros((len(sizes),2))
condNumbers = np.zeros((len(sizes),2))

for idx, val in enumerate(sizes):
  # 힐버트 행렬
  A = hilbert(val)
  Ai = np.linalg.inv(A)
  AAi = A @ Ai
  gap = AAi - np.eye(val)
  gap_dist = np.sqrt(np.sum(gap**2))
  distances[idx,0] = gap_dist
  condNumbers[idx,0] = np.linalg.cond(A)

  # 랜덤
  B = np.random.randn(val, val)
  Bi = np.linalg.inv(B)
  BBi = B @ Bi
  Bgap = BBi - np.eye(val)
  Bgap_dist = np.sqrt(np.sum(Bgap **2))
  distances[idx,1] = Bgap_dist
  condNumbers[idx,1] = np.linalg.cond(B)

# 그래프 그리기
fig,axs = plt.subplots(1,2,figsize=(14,5))

h = axs[0].plot(sizes, np.log(distances), 's-', markersize=12)
h[0].set_color('k')
h[0].set_marker('o')
h[1].set_color('gray')

axs[0].legend(['Hilbert','Random'])
axs[0].set_xlabel('Matrix size')
axs[0].set_ylabel('Log Euclidan distance')
axs[0].set_title('Distance to identity matrix')

h = axs[1].plot(sizes,np.log(condNumbers),'s-',markersize=12)
h[0].set_color('k') # adjust the individual line colors and shapes
h[0].set_marker('o')
h[1].set_color('gray')

axs[1].legend(['Hilbert','Random'])
axs[1].set_xlabel('Matrix size')
axs[1].set_ylabel('Log Kappa')
axs[1].set_title('Matrix condition number')

plt.savefig('Figure_07_07.png',dpi=300)
plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력하면 아래와 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;hilbert.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MhlcI/dJMcabpkg4U/ZyQwTuRsXPXylChmwBLDI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MhlcI/dJMcabpkg4U/ZyQwTuRsXPXylChmwBLDI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MhlcI/dJMcabpkg4U/ZyQwTuRsXPXylChmwBLDI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMhlcI%2FdJMcabpkg4U%2FZyQwTuRsXPXylChmwBLDI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;916&quot; height=&quot;372&quot; data-filename=&quot;hilbert.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힐버트 행렬은 수치적으로 매우 불안정한 행렬이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 크기가 조금만 커져도 조건수가 폭발적으로 커지기 때문에, 역행렬 계산 오차도 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프에서는 오차가 직선모양으로 상승하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤 정규분포 행렬은 일반적으로 크기가 커져도 조건수가 비교적 안정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프에서도 오차가 불규칙하게 변하는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;같은 크기의 행렬이라도 구조적인 특성에 따라 수치 계산 안정성이 달라진다&lt;/b&gt;는 것을 보여주는 문제였다.&lt;/p&gt;</description>
      <category>수학/선형대수</category>
      <category>개발</category>
      <category>개발자를 위한 실전 선형대수학</category>
      <category>선형대수</category>
      <category>수학</category>
      <author>김데굴</author>
      <guid isPermaLink="true">https://degul-kim.tistory.com/43</guid>
      <comments>https://degul-kim.tistory.com/43#entry43comment</comments>
      <pubDate>Sun, 4 Jan 2026 11:52:09 +0900</pubDate>
    </item>
  </channel>
</rss>