L24 서술 파트의 결론은 조건 두 개가 결론 두 개를 낳는다는 것이었다. 성분이 음이 아니고 열의 합이 1이면, 고윳값 1이 반드시 있고 나머지는 단위원을 벗어나지 못한다.
이 노트북에서는 무작위 마코브 행렬을 잔뜩 만들어 그 주장이 정말 예외 없이 성립하는지 확인하고, 을 정상상태로 착각하는 함정을 코드로 확인하며, 세 상태의 확률분포 구름이 삼각형 위에서 한 점으로 수축하는 것을 본다. 마지막으로 작은 웹을 만들어 페이지랭크를 직접 계산한다.
| 서술 파트의 내용 | 여기서 확인하는 방법 |
|---|---|
| 확률벡터를 확률벡터로 | 성분과 합을 찍어 본다 |
| 고윳값 1이 반드시 있다 | 무작위 행렬 500개에서 예외를 찾아본다 |
| 와 의 고윳값이 같다 | 특성다항식 계수를 대조 |
| 고유벡터는 다르다 | |
| 최대 절댓값을 모아 본다 | |
| 다른 고유벡터는 성분 합 0 | 를 찍는다 |
| 초기 상태를 바꿔도 그대로 | |
| 어디서 출발하든 | 삼각형 위의 구름이 한 점으로 |
| 수렴하지 않는 경우 | 주기적 사슬과 |
| 페이지랭크 | 작은 웹, 감쇠 전후, |
| L23의 행렬에서 이번 행렬 만들기 |
0. 준비¶
import numpy as np
import plotly.graph_objects as go
from linalg_viz import COLORS, layout2d, show_matrix, slider_figure
np.set_printoptions(precision=4, suppress=True)
rng = np.random.default_rng(24)
print("numpy", np.__version__)numpy 2.5.2
1. 두 조건이 하는 일¶
마코브 행렬의 존재 이유는 확률벡터를 확률벡터로 보내는 것이다. 두 조건이 정확히 그것을 보장하는지 확인하자.
A = np.array([[0.9, 0.2],
[0.1, 0.8]])
def 마코브인가(M, 눈감아=1e-12):
"""성분이 음이 아니고 열의 합이 1인지 본다."""
M = np.asarray(M, dtype=float)
return bool((M >= -눈감아).all() and np.allclose(M.sum(axis=0), 1.0))
def 확률벡터인가(p, 눈감아=1e-12):
"""성분이 음이 아니고 합이 1인지 본다."""
p = np.asarray(p, dtype=float)
return bool((p >= -눈감아).all() and np.isclose(p.sum(), 1.0))print(show_matrix(A, "A"))
print("열의 합 :", A.sum(axis=0))
print("마코브 행렬인가 :", 마코브인가(A))
print()
p = np.array([0.3, 0.7])
for k in range(4):
print(f"k={k} : p = {p}, 합 = {p.sum():.12f}, 확률벡터 {확률벡터인가(p)}")
p = A @ pA
[ 0.9 0.2 ]
[ 0.1 0.8 ]
열의 합 : [1. 1.]
마코브 행렬인가 : True
k=0 : p = [0.3 0.7], 합 = 1.000000000000, 확률벡터 True
k=1 : p = [0.41 0.59], 합 = 1.000000000000, 확률벡터 True
k=2 : p = [0.487 0.513], 합 = 1.000000000000, 확률벡터 True
k=3 : p = [0.5409 0.4591], 합 = 1.000000000000, 확률벡터 True
하나 = np.ones(2)
print("1^T A =", 하나 @ A, " -> 1^T 인가 :", np.allclose(하나 @ A, 하나))
p = np.array([0.3, 0.7])
print("1^T (A p) =", 하나 @ (A @ p))
print("(1^T A) p =", (하나 @ A) @ p, " <- 같은 수")1^T A = [1. 1.] -> 1^T 인가 : True
1^T (A p) = 1.0
(1^T A) p = 1.0 <- 같은 수
2. 고윳값 1은 우연이 아니다¶
무작위로 마코브 행렬을 잔뜩 만들어 예외를 찾아보자. 하나라도 있으면 서술 파트가 틀린 것이다.
def 무작위마코브(n, rng):
"""성분을 아무렇게나 뽑고 열마다 합으로 나눈다."""
M = rng.random((n, n))
return M / M.sum(axis=0)최악 = 0.0
표본 = 500
for _ in range(표본):
n = int(rng.integers(2, 9))
M = 무작위마코브(n, rng)
값 = np.linalg.eigvals(M)
최악 = max(최악, float(np.abs(값 - 1.0).min())) # 1 에 가장 가까운 고윳값과의 거리
print(f"마코브 행렬 {표본}개에서 '1 에 가장 가까운 고윳값' 과 1 의 거리")
print(f" 그중 최댓값 : {최악:.3e} -> 전부 정확히 1 을 가지고 있다")마코브 행렬 500개에서 '1 에 가장 가까운 고윳값' 과 1 의 거리
그중 최댓값 : 2.442e-15 -> 전부 정확히 1 을 가지고 있다
왜 그런가 — 특성다항식이 같기 때문¶
이고 이므로 두 특성다항식이 글자 그대로 같다.
M = 무작위마코브(5, rng)
print("A 의 특성다항식 계수 :", np.round(np.poly(M), 10))
print("A^T 의 특성다항식 계수 :", np.round(np.poly(M.T), 10))
print("같은가 :", np.allclose(np.poly(M), np.poly(M.T)))
print()
print("A^T 1 =", M.T @ np.ones(5), " <- 행의 합이 전부 1")A 의 특성다항식 계수 : [ 1. -1.1946 0.1826 0.0142 -0.0022 0.0001]
A^T 의 특성다항식 계수 : [ 1. -1.1946 0.1826 0.0142 -0.0022 0.0001]
같은가 : True
A^T 1 = [1. 1. 1. 1. 1.] <- 행의 합이 전부 1
그러나 고유벡터는 다르다¶
하나 = np.ones(2)
print("A^T (1,1) =", A.T @ 하나, " -> (1,1) 그대로. A^T 의 고유벡터")
print("A (1,1) =", A @ 하나, " -> 달라졌다. A 의 고유벡터가 아니다")
print()
값, V = np.linalg.eig(A)
i = int(np.argmin(np.abs(값 - 1)))
정상 = V[:, i].real
print("np.linalg.eig 가 준 고유벡터 :", 정상, " <- 길이가 1 로 맞춰져 있다")
정상 = 정상 / 정상.sum()
print("성분의 합으로 나눈 것 :", 정상, " = (2/3, 1/3)")
print("확률벡터인가 :", 확률벡터인가(정상))A^T (1,1) = [1. 1.] -> (1,1) 그대로. A^T 의 고유벡터
A (1,1) = [1.1 0.9] -> 달라졌다. A 의 고유벡터가 아니다
np.linalg.eig 가 준 고유벡터 : [0.8944 0.4472] <- 길이가 1 로 맞춰져 있다
성분의 합으로 나눈 것 : [0.6667 0.3333] = (2/3, 1/3)
확률벡터인가 : True
3. 나머지 고윳값과 성분의 합¶
두 가지를 한꺼번에 확인하자. 모든 고윳값이 이고, 인 고유벡터는 성분의 합이 0이다.
최대크기 = 0.0
최대합 = 0.0
for _ in range(300):
n = int(rng.integers(2, 8))
M = 무작위마코브(n, rng)
값, V = np.linalg.eig(M)
최대크기 = max(최대크기, float(np.abs(값).max()))
for j in range(n):
if abs(값[j] - 1) > 1e-8: # lambda != 1 인 것만
최대합 = max(최대합, float(abs(V[:, j].sum())))
print(f"모든 고윳값의 절댓값 중 최댓값 : {최대크기:.12f} -> 1 을 넘지 않는다")
print(f"lambda != 1 인 고유벡터의 성분 합의 절댓값 중 최댓값 : {최대합:.3e} -> 0 이다")모든 고윳값의 절댓값 중 최댓값 : 1.000000000000 -> 1 을 넘지 않는다
lambda != 1 인 고유벡터의 성분 합의 절댓값 중 최댓값 : 2.571e-15 -> 0 이다
앵커에서 눈으로 확인된다. 의 성분 합이 0이다.
값, V = np.linalg.eig(A)
for j in range(2):
x = V[:, j].real
한마디 = "0 이 아니다" if abs(값[j].real - 1) < 1e-9 else "0 이다"
print(f"lambda = {값[j].real:.4f} : 고유벡터 {np.round(x/abs(x).max(), 4)},"
f" 성분의 합 {x.sum():>10.6f} <- {한마디}")lambda = 1.0000 : 고유벡터 [1. 0.5], 성분의 합 1.341641 <- 0 이 아니다
lambda = 0.7000 : 고유벡터 [-1. 1.], 성분의 합 0.000000 <- 0 이다
4. 정상상태 — 은 언제나 1이다¶
이 확률벡터이기만 하면, 정상상태 방향의 계수가 초기 상태와 무관하게 1이다.
S = np.array([[2/3, 1.0], # 열1 = 정규화한 정상상태
[1/3, -1.0]])
print(f"{'초기 u0':>18}{'c1':>10}{'c2':>10}")
for u0 in ([1.0, 0.0], [0.0, 1.0], [0.5, 0.5], [0.13, 0.87]):
c = np.linalg.solve(S, np.array(u0))
print(f"{str(u0):>18}{c[0]:>10.6f}{c[1]:>10.6f}")
print()
print("c1 은 사실 초기벡터의 성분 합이다. 확률벡터가 아닌 것을 넣어 보자.")
for u0 in ([2.0, 1.0], [5.0, 0.0], [0.4, 0.4]):
c1 = np.linalg.solve(S, np.array(u0))[0]
print(f" u0 = {str(u0):>14} : 성분 합 {sum(u0):>5.1f}, c1 = {c1:>7.4f}") 초기 u0 c1 c2
[1.0, 0.0] 1.000000 0.333333
[0.0, 1.0] 1.000000 -0.666667
[0.5, 0.5] 1.000000 -0.166667
[0.13, 0.87] 1.000000 -0.536667
c1 은 사실 초기벡터의 성분 합이다. 확률벡터가 아닌 것을 넣어 보자.
u0 = [2.0, 1.0] : 성분 합 3.0, c1 = 3.0000
u0 = [5.0, 0.0] : 성분 합 5.0, c1 = 5.0000
u0 = [0.4, 0.4] : 성분 합 0.8, c1 = 0.8000
정상 = np.array([2/3, 1/3])
u = np.array([1.0, 0.0])
print(f"{'k':>4}{'u_k':>24}{'공식':>24}{'합':>6}")
for k in range(6):
공식 = 정상 + (1/3) * (0.7 ** k) * np.array([1.0, -1.0])
print(f"{k:>4}{str(np.round(u, 4)):>24}{str(np.round(공식, 4)):>24}{u.sum():>6.1f}")
u = A @ u
print()
print("A^50 =")
print(show_matrix(np.linalg.matrix_power(A, 50), " 랭크 1, 두 열이 모두 정상상태")) k u_k 공식 합
0 [1. 0.] [1. 0.] 1.0
1 [0.9 0.1] [0.9 0.1] 1.0
2 [0.83 0.17] [0.83 0.17] 1.0
3 [0.781 0.219] [0.781 0.219] 1.0
4 [0.7467 0.2533] [0.7467 0.2533] 1.0
5 [0.7227 0.2773] [0.7227 0.2773] 1.0
A^50 =
랭크 1, 두 열이 모두 정상상태
[ 0.667 0.667 ]
[ 0.333 0.333 ]
5. 삼각형 위의 구름이 한 점으로¶
상태가 셋이면 확률벡터는 삼각형(심플렉스) 안의 점이다. 무작위로 뿌린 점 200개가 걸음마다 어떻게 움직이는지 슬라이더로 보자.
P = np.array([[0.8, 0.1, 0.1],
[0.1, 0.7, 0.3],
[0.1, 0.2, 0.6]])
print("열의 합 :", P.sum(axis=0), " 마코브인가 :", 마코브인가(P))
값 = np.linalg.eigvals(P)
print("고윳값 :", np.round(np.sort(값.real)[::-1], 6))
print("두 번째 크기 |lambda2| :", np.sort(np.abs(값))[::-1][1])
_, V = np.linalg.eig(P)
s = V[:, int(np.argmin(np.abs(값 - 1)))].real
s = s / s.sum()
print("정상상태 :", np.round(s, 6), " = (1/3, 7/18, 5/18) 인가 :",
np.allclose(s, [1/3, 7/18, 5/18]))열의 합 : [1. 1. 1.] 마코브인가 : True
고윳값 : [1. 0.7 0.4]
두 번째 크기 |lambda2| : 0.7000000000000002
정상상태 : [0.3333 0.3889 0.2778] = (1/3, 7/18, 5/18) 인가 : True
꼭짓 = np.array([[0.0, 0.0], [1.0, 0.0], [0.5, np.sqrt(3)/2]])
구름 = rng.dirichlet(np.ones(3), size=200)
테두리 = np.vstack([꼭짓, 꼭짓[:1]])
끝점 = s @ 꼭짓
프레임 = []
걸음들 = list(range(0, 16))
점 = 구름.copy()
for k in 걸음들:
좌표 = 점 @ 꼭짓
프레임.append([
go.Scatter(x=테두리[:, 0], y=테두리[:, 1], mode="lines",
line=dict(color="#bbbbbb", width=2), showlegend=False),
go.Scatter(x=좌표[:, 0], y=좌표[:, 1], mode="markers",
marker=dict(color=COLORS["input"], size=6, opacity=0.65),
name="분포 200개"),
go.Scatter(x=[끝점[0]], y=[끝점[1]], mode="markers",
marker=dict(color="#d62728", size=14), name="정상상태"),
])
점 = 점 @ P.T # 행마다 P p 를 한꺼번에
배치 = layout2d("걸음을 옮기면", extent=1.0)
배치["height"] = 560
배치["xaxis"] = dict(visible=False, range=[-0.1, 1.1], scaleanchor="y")
배치["yaxis"] = dict(visible=False, range=[-0.1, 0.98])
slider_figure(프레임, 걸음들, 배치, prefix="k = ")구름이 삼각형 밖으로 나가는 일은 없다. 1절에서 확인한 대로 마코브 행렬이 확률벡터를 확률벡터로 보내기 때문이다. 그리고 이라 걸음마다 지름이 0.7배씩 준다.
점 = 구름.copy()
print(f"{'k':>4}{'구름의 지름':>14}{'0.7^k':>10}")
초기지름 = None
for k in range(0, 16, 3):
좌표 = np.linalg.matrix_power(P, k) @ 점.T
지름 = float(np.abs(좌표 - (s[:, None])).max())
if 초기지름 is None:
초기지름 = 지름
print(f"{k:>4}{지름:>14.6f}{초기지름 * 0.7 ** k:>10.6f}") k 구름의 지름 0.7^k
0 0.666202 0.666202
3 0.213761 0.228507
6 0.073320 0.078378
9 0.025149 0.026884
12 0.008626 0.009221
15 0.002959 0.003163
6. 수렴하지 않는 경우¶
은 보장되지만 은 보장되지 않는다. 두 반례를 보자.
B = np.array([[0.0, 1.0], [1.0, 0.0]]) # 반드시 번갈아 오간다
print("B 는 마코브인가 :", 마코브인가(B), " 고윳값 :", np.linalg.eigvals(B).real)
u = np.array([1.0, 0.0])
궤적 = [np.linalg.matrix_power(B, k) @ u for k in range(7)]
print("궤적 :", " -> ".join(f"({v[0]:.0f},{v[1]:.0f})" for v in 궤적))
print("-> 정상상태 (0.5, 0.5) 는 있지만 (1,0) 에서 출발하면 닿지 못한다")
print(" B (0.5,0.5) =", B @ np.array([0.5, 0.5]), " <- 정상상태 자체는 맞다")
print()
I = np.eye(3)
print("I 는 마코브인가 :", 마코브인가(I), " 고윳값 :", np.linalg.eigvals(I).real)
print("-> 모든 확률벡터가 정상상태. 유일하지 않다")
print(" 아무 것이나 :", I @ np.array([0.2, 0.3, 0.5]))B 는 마코브인가 : True 고윳값 : [ 1. -1.]
궤적 : (1,0) -> (0,1) -> (1,0) -> (0,1) -> (1,0) -> (0,1) -> (1,0)
-> 정상상태 (0.5, 0.5) 는 있지만 (1,0) 에서 출발하면 닿지 못한다
B (0.5,0.5) = [0.5 0.5] <- 정상상태 자체는 맞다
I 는 마코브인가 : True 고윳값 : [1. 1. 1.]
-> 모든 확률벡터가 정상상태. 유일하지 않다
아무 것이나 : [0.2 0.3 0.5]
두 반례 모두 0인 성분이 있다. 성분이 전부 양수이면 페론-프로베니우스에 의해 이런 일이 없다. 무작위 행렬로 확인하자.
최대둘째 = 0.0
for _ in range(300):
n = int(rng.integers(2, 8))
M = 무작위마코브(n, rng) # 성분이 전부 양수
크기 = np.sort(np.abs(np.linalg.eigvals(M)))[::-1]
최대둘째 = max(최대둘째, float(크기[1]))
print(f"성분이 전부 양수인 마코브 행렬 300개의 |lambda2| 중 최댓값 : {최대둘째:.6f}")
print("전부 1 보다 작은가 :", 최대둘째 < 1.0)성분이 전부 양수인 마코브 행렬 300개의 |lambda2| 중 최댓값 : 0.777574
전부 1 보다 작은가 : True
7. 페이지랭크¶
작은 웹을 하나 만들자. 페이지 에 링크가 개 있으면 무작위 서퍼는 각 링크를 의 확률로 고른다.
링크 = {0: [1, 2], 1: [2], 2: [0], 3: [0, 1],
4: [], # 막다른 페이지
5: [4, 6], 6: [5], 7: [0, 5]}
def 링크행렬(링크):
"""A[i, j] = 1/d_j (j 가 i 로 링크했을 때). 막다른 열은 0 으로 둔다."""
n = len(링크)
A = np.zeros((n, n))
for j, 나가는곳 in 링크.items():
for i in 나가는곳:
A[i, j] = 1.0 / len(나가는곳)
return AA0 = 링크행렬(링크)
print("열의 합 :", A0.sum(axis=0))
print("마코브인가 :", 마코브인가(A0), " <- 4번 열이 0 이라 아니다")열의 합 : [1. 1. 1. 1. 0. 1. 1. 1.]
마코브인가 : False <- 4번 열이 0 이라 아니다
막다른 페이지를 먼저 고친다. 나가는 링크가 없으면 아무 데로나 간다고 두면 된다.
A1 = A0.copy()
막다른 = A1.sum(axis=0) == 0
A1[:, 막다른] = 1.0 / len(링크)
print("고친 뒤 열의 합 :", A1.sum(axis=0), " 마코브인가 :", 마코브인가(A1))
값 = np.linalg.eigvals(A1)
print("고윳값의 절댓값 :", np.round(np.sort(np.abs(값))[::-1], 4))
print("|lambda2| =", np.sort(np.abs(값))[::-1][1], " <- 1 에 가까우면 느리다")고친 뒤 열의 합 : [1. 1. 1. 1. 1. 1. 1. 1.] 마코브인가 : True
고윳값의 절댓값 : [1. 0.8394 0.7071 0.7071 0.6578 0.0566 0. 0. ]
|lambda2| = 0.8394385518227846 <- 1 에 가까우면 느리다
이제 감쇠를 넣는다. 확률 로 링크를 무시하고 아무 페이지로나 순간이동한다.
def 구글행렬(A, 알파=0.85):
"""G = alpha A + (1-alpha) J/n. 모든 성분이 양수가 된다."""
n = A.shape[0]
return 알파 * A + (1 - 알파) * np.ones((n, n)) / n알파 = 0.85
G = 구글행렬(A1, 알파)
print("마코브인가 :", 마코브인가(G), " 모든 성분이 양수인가 :", (G > 0).all())
크기 = np.sort(np.abs(np.linalg.eigvals(G)))[::-1]
print("고윳값의 절댓값 :", np.round(크기, 6))
print(f"|lambda2| = {크기[1]:.6f} <= alpha = {알파} 인가 :", 크기[1] <= 알파 + 1e-9)마코브인가 : True 모든 성분이 양수인가 : True
고윳값의 절댓값 : [1. 0.7135 0.601 0.601 0.5592 0.0481 0. 0. ]
|lambda2| = 0.713523 <= alpha = 0.85 인가 : True
값, V = np.linalg.eig(G)
순위 = V[:, int(np.argmin(np.abs(값 - 1)))].real
순위 = 순위 / 순위.sum()
# 고윳값 분해 대신 거듭 곱하기만 해도 같은 답이 나온다 (L22 의 지배 고윳값)
p = np.ones(len(링크)) / len(링크)
for _ in range(60):
p = G @ p
print("고유벡터로 구한 순위 :", np.round(순위, 5))
print("거듭 곱해 구한 순위 :", np.round(p, 5))
print("최대 차이 :", np.abs(순위 - p).max())
print()
for i in np.argsort(순위)[::-1]:
막대 = "#" * int(round(순위[i] * 200))
print(f" page {i} : {순위[i]:.4f} {막대}")고유벡터로 구한 순위 : [0.2877 0.1587 0.2827 0.0256 0.0643 0.0911 0.0643 0.0256]
거듭 곱해 구한 순위 : [0.2877 0.1587 0.2827 0.0256 0.0643 0.0911 0.0643 0.0256]
최대 차이 : 1.5103196471244473e-10
page 0 : 0.2877 ##########################################################
page 2 : 0.2827 #########################################################
page 1 : 0.1587 ################################
page 5 : 0.0911 ##################
page 6 : 0.0643 #############
page 4 : 0.0643 #############
page 7 : 0.0256 #####
page 3 : 0.0256 #####
감쇠 인자를 바꾸면 수렴 속도가 그대로 따라간다. 가 곧 의 상한이다.
print(f"{'alpha':>8}{'|lambda2|':>12}{'60걸음 오차':>14}")
for 알파 in (0.5, 0.7, 0.85, 0.95, 0.99):
G2 = 구글행렬(A1, 알파)
크기 = np.sort(np.abs(np.linalg.eigvals(G2)))[::-1]
값2, V2 = np.linalg.eig(G2)
참 = V2[:, int(np.argmin(np.abs(값2 - 1)))].real
참 = 참 / 참.sum()
p = np.ones(len(링크)) / len(링크)
for _ in range(60):
p = G2 @ p
print(f"{알파:>8}{크기[1]:>12.6f}{np.abs(p - 참).max():>14.2e}") alpha |lambda2| 60걸음 오차
0.5 0.419719 1.39e-16
0.7 0.587607 1.19e-15
0.85 0.713523 1.51e-10
0.95 0.797467 1.89e-07
0.99 0.831044 2.80e-06
8. 열의 합 0에서 열의 합 1로¶
L23의 앵커에 를 더하면 이번 강의의 앵커가 된다.
Ac = np.array([[-1.0, 2.0], [1.0, -2.0]]) # L23 의 앵커. 열의 합 0
print("Ac 의 열의 합 :", Ac.sum(axis=0))
print(f"{'h':>6}{'열의 합':>16}{'성분>=0':>10}{'고윳값':>24}{'기대 1+h*lambda':>20}")
for h in (0.05, 0.1, 0.25, 0.5, 0.6):
Ad = np.eye(2) + h * Ac
값 = np.sort(np.linalg.eigvals(Ad).real)[::-1]
print(f"{h:>6}{str(Ad.sum(axis=0)):>16}{str((Ad>=0).all()):>10}"
f"{str(np.round(값, 4)):>24}{str([1.0, round(1-3*h, 4)]):>20}")
print()
print("h = 0.1 이 이번 강의의 앵커인가 :", np.allclose(np.eye(2) + 0.1*Ac, A))Ac 의 열의 합 : [0. 0.]
h 열의 합 성분>=0 고윳값 기대 1+h*lambda
0.05 [1. 1.] True [1. 0.85] [1.0, 0.85]
0.1 [1. 1.] True [1. 0.7] [1.0, 0.7]
0.25 [1. 1.] True [1. 0.25] [1.0, 0.25]
0.5 [1. 1.] True [ 1. -0.5] [1.0, -0.5]
0.6 [1. 1.] False [ 1. -0.8] [1.0, -0.8]
h = 0.1 이 이번 강의의 앵커인가 : True
까지만 성분이 음이 아니다. 대각이 와 이기 때문이다. 그리고 고유벡터는 와 무관하게 그대로이다.
for h in (0.1, 0.3, 0.5):
Ad = np.eye(2) + h * Ac
for x, 이름 in ((np.array([2.0, 1.0]), "(2,1)"), (np.array([1.0, -1.0]), "(1,-1)")):
결과 = Ad @ x
배율 = 결과[0] / x[0]
print(f"h={h}: Ad {이름:>6} = {np.round(결과,4)} = {배율:>6.2f} x "
f" (1 + h*lambda = {1 + h*(0 if 이름=='(2,1)' else -3):.2f})")h=0.1: Ad (2,1) = [2. 1.] = 1.00 x (1 + h*lambda = 1.00)
h=0.1: Ad (1,-1) = [ 0.7 -0.7] = 0.70 x (1 + h*lambda = 0.70)
h=0.3: Ad (2,1) = [2. 1.] = 1.00 x (1 + h*lambda = 1.00)
h=0.3: Ad (1,-1) = [ 0.1 -0.1] = 0.10 x (1 + h*lambda = 0.10)
h=0.5: Ad (2,1) = [2. 1.] = 1.00 x (1 + h*lambda = 1.00)
h=0.5: Ad (1,-1) = [-0.5 0.5] = -0.50 x (1 + h*lambda = -0.50)
마치며...¶
| 서술 파트의 내용 | 이 노트북의 코드 |
|---|---|
| 확률벡터 보존 | 확률벡터인가 가 걸음마다 참 |
| 괄호 옮기기 | |
| 고윳값 1 | 무작위 500개에서 예외 0건 |
| 특성다항식이 같다 | np.poly(M) 과 np.poly(M.T) 가 일치 |
| 고유벡터는 다르다 | |
| 300개에서 최댓값이 정확히 1 | |
| 성분 합 0 | 인 고유벡터에서 10-16 수준 |
| 초기 상태 넷 모두 1.000000 | |
| 구름의 수축 | 슬라이더. 지름이 로 |
| 반례 | 주기적 사슬과 , 둘 다 0인 성분이 있다 |
| 페이지랭크 | 막다른 페이지 고치기, 감쇠, |
| 오일러 한 걸음 | , 고유벡터는 그대로 |
더 해 볼 것¶
5절의 를 바꿔 를 0.95쯤으로 만들어 보자. 구름이 눈에 띄게 느려지는가? 가 1에 가깝다는 것은 어떤 구조를 뜻하는가?
7절의 링크를 바꿔 페이지 하나만 서로 링크하는 작은 무리를 만들어 보자. 감쇠를 빼면 순위가 그 무리로 빨려 드는가? 감쇠를 넣으면 어떻게 되는가?
6절의 주기적 사슬 에 감쇠를 씌워 보자. 가 얼마가 되는가?
마코브 행렬 둘을 곱하면 다시 마코브 행렬인가? 코드로 확인하고 이유를 로 설명해 보자.
다음 강의부터는 가장 좋은 행렬을 다룬다. 인 대칭행렬이다. 고윳값은 전부 실수이고 고유벡터는 서로 직교한다. L21의 앵커 에서 이미 보았던 그 현상이다.