2교시에 벡터를 늘이고(스칼라배) 더하는(덧셈) 법을 배웠습니다. 이번 교시에는 이 두 가지를 섞어서 씁니다. 벡터 두 개를 각각 원하는 만큼 늘인 뒤 더하면, 어디까지 갈 수 있을까요?
버튼 두 개로만 움직이는 로봇을 떠올려 보세요. 버튼 A를 한 번 누르면 u만큼, 버튼 B를 한 번 누르면 w만큼 움직이고, 버튼을 반 번이나 −2번 누르는 것도 허용됩니다. 이 로봇이 평면의 모든 곳에 갈 수 있을지, 아니면 한 직선 위에 갇히게 될지는 u와 w가 어떤 방향을 보느냐에 달려 있습니다.
이 질문은 선형대수 전체를 꿰는 질문입니다. 8교시의 연립방정식은 "이 점에 갈 수 있는가, 간다면 버튼을 몇 번씩 눌러야 하나"이고, 9교시의 계수(rank)는 "갈 수 있는 곳이 몇 차원인가"입니다. 이번 교시에는 그 바탕이 되는 말들을 익히고, NumPy로 처음 행렬을 만들어 봅니다.
벡터 u와 w에 숫자 a, b를 곱해 더한 것을 u와 w의 일차결합(linear combination) 이라고 합니다.
a와 b는 아무 실수나 됩니다. 이름은 거창하지만 2교시의 스칼라배와 덧셈을 한 번씩 한 것뿐입니다. 예를 들어 , 이고 , 이면
입니다. a와 b를 바꾸면 도착하는 점이 바뀝니다. , 이면 입니다.
아래 실습대에서 슬라이더 a, b를 움직여 보세요. 청록 점선이 이고, 그 끝에 이어 붙인 진홍 점선이 , 그리고 검정 점이 도착한 곳 입니다. 2교시에서 본 "이어 붙이기"가 그대로 쓰였습니다.
u와 w가 서로 다른 방향이라 평면 전체에 갈 수 있습니다 (일차독립)
NumPy로 확인하기
실습대에서 값을 정할 때마다, 서버의 NumPy가 같은 숫자로 계산한 결과입니다.
>>> u = np.array([2, 1])
>>> w = np.array([-1, 1])
>>> M = np.column_stack([u, w])
>>> M
array([[ 2, -1],
[ 1, 1]])
>>> M @ np.array([1, 1])
array([1, 2])
>>> 1 * u + 1 * w
array([1, 2])
>>> np.linalg.matrix_rank(M)
np.int64(2)'목표 점 T에 도착해 보기'를 켜면 노랑 원 T = (−2, 3)이 나타납니다. 슬라이더로 검정 점을 T에 맞춰 보세요. a를 0.3, b를 2.7 근처로 맞추면 "도착했습니다!"가 뜹니다.
사실 정확한 답은 a = 1/3, b = 8/3입니다. 슬라이더가 0.1 간격이라 1/3 = 0.333…에 딱 맞출 수는 없어서, 실습대는 T에 충분히 가까우면 도착한 것으로 칩니다. 이런 a, b를 손으로 정확히 구하는 법은 8교시에서 연립방정식으로 배웁니다. 지금 중요한 것은 "a, b를 잘 고르면 T에 갈 수 있다"는 사실입니다.
a와 b를 모든 실수에 걸쳐 바꿀 때 가 닿는 점을 모두 모은 것을 u와 w의 생성(span) 이라고 합니다. "u와 w가 만들어 내는 곳 전부"라는 뜻입니다.
처음 보면 기호가 많지만 하나씩 읽으면 어렵지 않습니다. 중괄호 는 "이런 것들의 모임(집합)", 세로줄 는 "단,", 그 뒤는 조건입니다. 통째로 읽으면 "a와 b가 아무 실수일 때 모양으로 쓸 수 있는 벡터들의 모임"입니다.
실습대의 '지나간 곳 남기기'(기본으로 켜져 있음)는 검정 점이 지나간 자리에 남색 점을 찍습니다. 슬라이더를 오래 움직일수록 흔적이 퍼져 나가는데, 기본값 u = (2, 1), w = (−1, 1)에서는 평면 전체가 옅게 칠해져 있고 상태 문구도 "평면 전체에 갈 수 있습니다"입니다. 슬라이더 범위(−3에서 3까지)가 한정되어 있어 화면 끝까지 다 칠하지는 못하지만, a와 b를 더 크게 키울 수만 있다면 어떤 점이든 닿을 수 있습니다.
이번에는 진홍 w의 끝점을 (4, 2)로 끌어 보세요. w가 u = (2, 1)의 정확히 두 배가 되면서 두 벡터가 한 직선 위에 놓입니다. 평면의 옅은 색이 사라지고 노랑 직선이 나타나며, "그 직선 위만 갈 수 있습니다"라는 경고가 뜹니다. w = 2u이니
가 되어, 결국 u 하나의 스칼라배밖에 만들 수 없습니다. 2교시에서 "스칼라배로는 방향을 돌릴 수 없다"고 한 것이 여기서 쓰입니다. 슬라이더를 아무리 움직여도 검정 점은 노랑 직선을 벗어나지 못합니다. 이때 T = (−2, 3)은 직선 위에 있지 않아서 어떤 a, b로도 도착할 수 없습니다.
정리하면 2차원에서 두 벡터의 생성은 세 가지 중 하나입니다.
| u와 w의 관계 | 생성(span) | 실습대 화면 |
|---|---|---|
| 서로 다른 방향 | 평면 전체 | 평면이 옅게 칠해짐 |
| 한 직선 위 (한쪽이 다른 쪽의 스칼라배) | 원점을 지나는 직선 하나 | 노랑 직선 + 경고 |
| 둘 다 영벡터 | 원점 하나 | "원점에서 움직일 수 없습니다" |
어떤 경우든 생성에는 원점이 꼭 들어 있습니다. a = b = 0으로 두면 언제나 영벡터가 나오기 때문입니다.
4-2의 첫째 경우와 둘째 경우를 가르는 말이 있습니다.
종속인 벡터는 생성에 아무것도 보태지 못합니다. w = 2u라면 w는 u가 이미 갈 수 있는 곳을 다시 가리킬 뿐이라, 새 방향을 열어 주지 못합니다. 버튼 두 개짜리 로봇의 두 번째 버튼이 사실 첫 번째 버튼을 두 번 누르는 것과 같다면, 버튼이 하나뿐인 로봇과 다를 바 없습니다.
책에서는 같은 뜻을 이렇게 적기도 합니다. u와 w가 일차독립이라는 것은
라는 뜻입니다. 종속이면 0이 아닌 a, b로도 원점에 돌아올 수 있습니다. w = 2u라면 입니다. 실습대에서 w를 (4, 2)로 두고 a = 2, b = −1로 맞추면 검정 점이 원점에 앉는 것을 볼 수 있습니다.
u = (2, 1), w = (−1, 1)은 이미 평면 전체를 생성합니다. 여기에 셋째 벡터 v = (4, 5)를 보태면 어떻게 될까요? v도 평면 위의 점이니 u와 w로 이미 갈 수 있는 곳입니다. 실제로
이므로 v = 3u + 2w입니다. 셋째 벡터는 새 방향을 열어 주지 못하니 세 벡터는 일차종속입니다. 이것은 v를 무엇으로 고르든 마찬가지입니다. 2차원에서는 벡터가 셋 이상이면 언제나 일차종속입니다. 평면에는 서로 독립인 방향이 둘밖에 없기 때문입니다. 3차원이라면 독립인 방향이 셋까지 있을 수 있고, 넷째부터는 반드시 남습니다.
평면 전체를 생성하면서 일차독립인 벡터 묶음을 기저(basis) 라고 합니다. "평면 전체에 갈 수 있으면서, 쓸데없이 겹치는 벡터가 없다"는 뜻입니다. 2차원에서 기저는 언제나 벡터 두 개입니다. 위의 u = (2, 1), w = (−1, 1)도 기저입니다.
가장 흔히 쓰는 기저는 x축 방향으로 한 칸, y축 방향으로 한 칸인 두 벡터입니다.
이 둘을 표준기저라고 하고 "아이 햇", "제이 햇"이라고 읽습니다. 책에 따라 로 쓰기도 합니다. 1교시부터 써 온 좌표가 사실 표준기저의 일차결합이었습니다.
"오른쪽으로 3칸, 위로 2칸"이 곧 "î를 3번, ĵ를 2번"입니다. 그렇다면 다른 기저를 자로 삼아 좌표를 잴 수도 있겠지요. 이 이야기는 10교시 '기저 바꾸기'에서 이어 갑니다. 그리고 5교시에는 î와 ĵ를 다른 곳으로 옮기는 것만으로 평면 전체가 움직이는 모습을 보게 됩니다.
새 기호를 정리합니다.
| 책에 적힌 모양 | 읽는 법 | 뜻 | 파이썬에서는 |
|---|---|---|---|
| 에이 u 더하기 비 w | 일차결합 | a * u + b * w | |
| …인 것들의 모임 | 집합. 세로줄 뒤는 조건 | (해당 없음) | |
| u, w의 스팬 | u와 w로 갈 수 있는 곳 전부 | 계수로 크기를 잼: np.linalg.matrix_rank(M) | |
| 또는 | 아이 햇, 제이 햇 | 표준기저 (1, 0), (0, 1) | np.array([1, 0]), np.array([0, 1]) |
| u, w를 열로 하는 행렬 | 벡터를 세로로 나란히 세운 표 | np.column_stack([u, w]) | |
| 엠 일이 | M의 1행 2열 성분 | M[0, 1] |
실습대의 "NumPy로 확인하기" 칸을 처음부터 따라가 봅시다. 새 함수가 하나 등장합니다.
>>> import numpy as np
>>> u = np.array([2, 1])
>>> w = np.array([-1, 1])
>>> M = np.column_stack([u, w])
>>> M
array([[ 2, -1],
[ 1, 1]])np.column_stack은 벡터들을 세로 기둥(열, column)으로 세워 나란히 붙입니다. 1열은 u = (2, 1), 2열은 w = (−1, 1)입니다. 이렇게 숫자를 가로세로 표 모양으로 늘어놓은 것을 행렬(matrix) 이라고 합니다. 가로줄을 행(row), 세로줄을 열(column) 이라고 부릅니다.
화면에 찍힌 모양은 대괄호가 두 겹입니다. 바깥 대괄호 안에 행 두 개 [ 2, -1]과 [ 1, 1]이 들어 있다고 읽으면 됩니다. 행으로 적혀 있으니 열(u, w)은 세로로 읽어야 합니다: 첫 열은 2와 1, 둘째 열은 −1과 1.
1교시의 벡터는 숫자가 한 줄로 늘어선 1차원 배열이었습니다. 행렬은 축이 두 개인 2차원 배열입니다.
>>> M.shape
(2, 2)
>>> M.ndim
2(2, 2)는 "2행 2열"입니다. 언제나 (행 수, 열 수) 순서입니다. 성분 하나를 꺼낼 때도 M[행, 열] 순서로 적고, 1교시처럼 0부터 셉니다.
>>> M[0, 1]
np.int64(-1)
>>> M[1, 0]
np.int64(1)M[0, 1]은 0번 행(첫째 행), 1번 열(둘째 열)의 성분, 책의 표기로는 $M_{12}$입니다. 행이나 열을 통째로 꺼낼 수도 있습니다. 콜론 :는 "그 축은 전부"라는 뜻입니다.
>>> M[:, 0]
array([2, 1])
>>> M[:, 1]
array([-1, 1])
>>> M[1]
array([1, 1])M[:, 0]은 "모든 행의 0번 열", 즉 1열을 꺼내는 것이라 u가 그대로 돌아옵니다. M[1]처럼 숫자 하나만 적으면 그 행을 꺼냅니다. 열을 꺼낼 때는 늘 M[:, j]를 쓴다고 기억해 두세요.
실습대 NumPy 칸의 다음 두 줄은 같은 결과를 서로 다른 방법으로 계산합니다.
>>> M @ np.array([1, 1])
array([1, 2])
>>> 1 * u + 1 * w
array([1, 2])3교시에서 @는 1차원 배열 두 개 사이에서 내적이었습니다. 왼쪽이 행렬이고 오른쪽이 벡터이면 @는 행렬-벡터 곱이 되는데, 그 결과는 행렬의 열들을 벡터의 성분만큼 늘여서 더한 것, 즉 일차결합입니다.
| 계산 | 뜻 |
|---|---|
M @ np.array([a, b]) | a × (M의 1열) + b × (M의 2열) |
a * u + b * w | a × u + b × w |
M의 열이 u와 w이니 두 줄은 같은 계산입니다. 이것이 행렬을 읽는 가장 중요한 방법인 열 결합 해석이고, 5교시에서 이 해석으로 행렬이 공간을 움직이는 모습을 보게 됩니다. 실습대의 슬라이더 a, b는 결국 M @ [a, b]의 [a, b]를 고르는 손잡이였던 셈입니다.
4-1의 목표 점 T = (−2, 3)도 확인해 봅시다. 정확한 답 a = 1/3, b = 8/3과 슬라이더로 맞춘 a = 0.3, b = 2.7을 비교합니다.
>>> M @ np.array([1/3, 8/3])
array([-2., 3.])
>>> np.allclose(M @ np.array([1/3, 8/3]), [-2, 3])
True
>>> M @ np.array([0.3, 2.7])
array([-2.1, 3. ])1/3은 소수로 끝없이 이어지는 수라 계산 결과에 아주 작은 부동소수점 오차가 숨어 있을 수 있으니, 3교시에서 배운 np.allclose로 확인했습니다.
NumPy 칸의 마지막 줄은 np.linalg.matrix_rank입니다.
>>> np.linalg.matrix_rank(M)
np.int64(2)이 숫자를 행렬의 계수(rank) 라고 합니다. 지금은 "열들이 생성하는 곳이 몇 차원인가"로 이해하면 충분합니다. 2이면 평면 전체(일차독립), 1이면 직선 하나(일차종속), 0이면 원점 하나입니다. 실습대에서 w를 (4, 2)로 옮겼을 때와 같은 행렬을 만들어 보면 계수가 1로 떨어집니다.
>>> M2 = np.column_stack([u, np.array([4, 2])])
>>> M2
array([[2, 4],
[1, 2]])
>>> np.linalg.matrix_rank(M2)
np.int64(1)
>>> np.linalg.matrix_rank(np.zeros((2, 2)))
np.int64(0)np.zeros((2, 2))는 모든 성분이 0인 2×2 행렬입니다. 모양을 튜플 (2, 2)로 넘긴다는 점에 주의하세요. 계수는 9교시에서 다시 자세히 다룹니다.
4-3의 세 벡터를 열로 쌓으면 2행 3열 행렬이 됩니다.
>>> v = np.array([4, 5])
>>> M3 = np.column_stack([u, w, v])
>>> M3
array([[ 2, -1, 4],
[ 1, 1, 5]])
>>> M3.shape
(2, 3)
>>> np.linalg.matrix_rank(M3)
np.int64(2)
>>> M3 @ np.array([3, 2, -1])
array([0, 0])열이 셋이어도 계수는 2입니다. 2차원 평면에서는 2보다 커질 수 없습니다. 마지막 줄은 3u + 2w − v를 계산한 것으로, 0이 아닌 계수들로 영벡터가 만들어졌으니 세 벡터가 일차종속이라는 증거입니다(4-3의 정의).
î와 ĵ를 열로 쌓으면 대각선에만 1이 있는 행렬이 나옵니다. 이 행렬에 어떤 벡터를 곱해도 그대로 돌아옵니다. "3 × î + 2 × ĵ = (3, 2)"이기 때문입니다.
>>> I = np.column_stack([np.array([1, 0]), np.array([0, 1])])
>>> I
array([[1, 0],
[0, 1]])
>>> I @ np.array([3, 2])
array([3, 2])이 행렬은 6교시에서 항등행렬이라는 이름으로 다시 만납니다.
np.array([u, w])로 열을 쌓았다고 착각하기. 벡터 목록을 np.array에 넘기면 각 벡터가 행이 됩니다. 열로 세우는 np.column_stack과 결과가 다릅니다.
>>> R = np.array([u, w])
>>> R
array([[ 2, 1],
[-1, 1]])
>>> R @ np.array([1, 1])
array([3, 0])u + w = (1, 2)를 기대했는데 (3, 0)이 나왔습니다. R의 열은 (2, −1)과 (1, 1)이라 전혀 다른 일차결합을 계산한 것입니다. 행렬을 만들고 나면 화면에 찍어 열을 세로로 읽어 확인하는 습관을 들이세요.
행과 열 순서 헷갈리기. M[0, 1]은 "0번 행, 1번 열"입니다. 첫 숫자가 행(가로줄), 둘째 숫자가 열(세로줄)입니다. 열을 꺼낸다고 M[0]을 쓰면 첫 열이 아니라 첫 행이 나옵니다.
>>> M[0]
array([ 2, -1])
>>> M[:, 0]
array([2, 1])M @ np.array([a, b])로 확인하세요.K = np.column_stack([np.array([1, 4]), np.array([2, 5]), np.array([3, 6])])에서 2열 전체와, 1행 3열 성분(책의 표기로 K₁₃)을 꺼내 보세요.>>> M @ np.array([1, 2])
array([0, 3])
>>> np.linalg.matrix_rank(np.column_stack([np.array([1, 2]), np.array([-2, -4])]))
np.int64(1)
>>> K = np.column_stack([np.array([1, 4]), np.array([2, 5]), np.array([3, 6])])
>>> K
array([[1, 2, 3],
[4, 5, 6]])
>>> K[:, 1]
array([2, 5])
>>> K[0, 2]
np.int64(3)1번은 a = 1, b = 2입니다. 2·1 − 2 = 0, 1 + 2 = 3이 되어 (0, 3)에 도착합니다. 2번은 (−2, −4) = −2 × (1, 2)라서 한 직선 위에 있으니 일차종속이고, 계수도 1입니다. 3번의 책 표기 K₁₃은 1부터 세니 파이썬에서는 K[0, 2]입니다. 4번은 "평면에서는 서로 다른 두 방향만으로 이미 모든 점에 갈 수 있으니, 셋째 벡터는 늘 앞의 둘의 일차결합으로 만들어진다"입니다(앞의 둘이 한 직선 위에 있는 경우에는 그 둘부터가 이미 종속입니다).
이번 교시 정리
일차결합 a·u + b·w = 늘이고 더하기. a, b는 아무 실수나 된다.
생성(span) = 일차결합으로 갈 수 있는 곳 전부. 2차원에서는 평면 전체, 직선 하나, 원점 하나 중 하나이고 원점은 늘 들어 있다.
일차독립 = 어느 것도 나머지의 일차결합이 아니다. 두 벡터라면 '한 직선 위에 있지 않다'.
2차원에서 벡터가 셋 이상이면 늘 일차종속. 평면 전체를 생성하는 독립인 두 벡터가 기저이고, î = (1, 0), ĵ = (0, 1)이 표준기저.
np.column_stack([u, w])는 벡터를 열로 세운 행렬. shape은 (행, 열), 성분은 M[행, 열], 열은 M[:, j].
M @ np.array([a, b]) = a·(1열) + b·(2열) = a·u + b·w. matrix_rank는 열들이 생성하는 곳의 차원.
다음 교시에는 행렬을 "벡터를 쌓아 둔 표"가 아니라 공간 전체를 옮기는 규칙으로 봅니다. 오늘 본 M @ [a, b]의 열 결합 해석이 그 출발점입니다.
새 교시·새 강의 소식 받기
이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.
R
ReflexKR
눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.
ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.
© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
질문과 답변
이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.