1교시에는 벡터를 평면 위의 점으로 그렸습니다. 이번 교시에는 같은 벡터를 화살표로 그립니다. 점은 "어디에 있는가"를 보여 주고, 화살표는 "어느 쪽으로 얼마나 움직이는가"를 보여 줍니다. 숫자 묶음은 그대로인데 보는 눈만 바뀝니다.
화살표로 보면 벡터끼리 더하고 늘이는 일이 아주 자연스러워집니다. 게임을 만들어 봤다면 이미 해 본 계산입니다. 캐릭터의 위치에 속도를 더해 다음 위치를 구하고, 달리기 버튼을 누르면 속도를 두 배로 늘리고, 뒤로 물러날 때는 방향을 뒤집습니다. 이것이 이번 교시의 전부, 벡터의 덧셈과 스칼라배입니다.
마지막에는 같은 계산을 NumPy로 합니다. 이때 파이썬 리스트의 +는 전혀 다른 일을 한다는 점을 꼭 확인합니다. 선형대수를 코드로 옮길 때 가장 먼저 걸려 넘어지는 곳입니다.
벡터 (3, 2)를 화살표로 그리려면 원점 (0, 0)에서 출발해 점 (3, 2)에서 끝나는 화살표를 그립니다. 화살표의 뜻은 "오른쪽으로 3칸, 위로 2칸 움직여라"입니다.
여기서 중요한 생각이 하나 있습니다. 화살표는 어디서 출발해도 같은 벡터입니다. (1, 1)에서 출발해 (4, 3)에 도착하는 화살표도 오른쪽으로 3칸, 위로 2칸 움직이니 같은 벡터 (3, 2)입니다. 화살표를 방향과 길이를 바꾸지 않고 옮기는 것을 평행이동이라고 하는데, 평행이동한 화살표는 모두 같은 벡터로 칩니다.
| 출발점 | 도착점 | 움직인 양 (도착 − 출발) |
|---|---|---|
| (0, 0) | (3, 2) | (3, 2) |
| (1, 1) | (4, 3) | (3, 2) |
| (−2, 0) | (1, 2) | (3, 2) |
벡터가 담고 있는 것은 "얼마나 움직였는가"뿐이고, "어디서 출발했는가"는 담고 있지 않습니다. 그래서 같은 벡터를 필요한 곳 아무 데나 옮겨 그려도 됩니다. 바로 다음 절의 덧셈이 이 성질을 씁니다.
점으로 볼지 화살표로 볼지는 상황에 따라 고릅니다. 캐릭터의 위치는 점으로, 속도나 이동량은 화살표로 보는 것이 자연스럽습니다. 둘 다 숫자 두 개짜리 벡터라는 점은 같습니다.
두 벡터를 더하는 그림은 간단합니다. 첫째 화살표의 끝에 둘째 화살표의 꼬리를 이어 붙이고, 원점에서 마지막 끝까지 새 화살표를 긋습니다. 그 새 화살표가 합입니다.
아래 실습대에서 청록 화살표가 u, 진홍 화살표가 w입니다. 'u + w 보기'가 켜져 있으면 진홍 점선이 w를 u의 끝으로 옮겨 이어 붙인 모습이고, 검정 화살표가 u + w입니다.
w를 u의 끝에 이어 붙이면 u + w에 도착합니다
NumPy로 확인하기
실습대에서 값을 정할 때마다, 서버의 NumPy가 같은 숫자로 계산한 결과입니다.
>>> u = np.array([2, 1])
>>> w = np.array([1, 2])
>>> u + w
array([3, 3])
>>> 2 * u
array([4, 2])
>>> u - w
array([ 1, -1])
>>> [2, 1] + [1, 2]
[2, 1, 1, 2]기본값 u = (2, 1), w = (1, 2)에서 화살표를 따라가 보면 이렇습니다. u를 따라 오른쪽 2, 위 1, 이어서 w를 따라 오른쪽 1, 위 2. 모두 합쳐 오른쪽 3, 위 3이니 u + w = (3, 3)입니다. 그림에서 한 일을 숫자로 적으면 같은 자리의 성분끼리 더하기가 됩니다.
실습대에 옅은 청록 점선도 하나 보입니다. 이번에는 u를 w의 끝에 이어 붙인 길입니다. 이쪽으로 가도 같은 점 u + w에 도착합니다. 두 길이 평행사변형을 이루기 때문에, 더하는 순서를 바꿔도 결과가 같습니다(u + w = w + u).
게임으로 옮기면 이렇습니다. 캐릭터가 지금 (2, 1)에 있고, 한 프레임 동안의 속도가 (1, 2)라면 다음 프레임의 위치는 (2, 1) + (1, 2) = (3, 3)입니다. 위치(점)에 이동량(화살표)을 더하면 새 위치(점)가 나옵니다.
책에서는 덧셈을 세로 벡터로 이렇게 씁니다.
성분끼리 더하므로 차원이 같은 벡터끼리만 더할 수 있습니다. 2차원 벡터와 3차원 벡터는 "같은 자리"가 맞지 않아 더할 수 없습니다. 이 규칙은 784차원이든 768차원이든 똑같습니다.
벡터에 숫자 하나를 곱하는 것을 스칼라배라고 합니다. 여기서 숫자 하나를 스칼라(scalar) 라고 부르는데, 벡터처럼 묶음이 아닌 그냥 숫자라는 뜻입니다. 곱하는 방법은 모든 성분에 그 숫자를 곱하는 것입니다.
그림으로는 화살표의 방향은 그대로 두고 길이만 바꿉니다. 곱하는 수에 따라 이렇게 달라집니다.
| c | 그림에서 일어나는 일 | u = (2, 1)일 때 |
|---|---|---|
| 2 | 같은 방향으로 두 배 길게 | (4, 2) |
| 0.5 | 같은 방향으로 절반 길이 | (1, 0.5) |
| 1 | 그대로 | (2, 1) |
| 0 | 길이 0: 원점에 붙은 점 | (0, 0) |
| −1 | 길이는 그대로, 방향만 정반대 | (−2, −1) |
| −2 | 정반대 방향으로 두 배 | (−4, −2) |
실습대에서 'c·u 보기'를 켜고 슬라이더 c를 움직여 보세요. 굵고 옅은 청록 화살표가 c·u입니다. c를 아무리 바꿔도 c·u는 u를 지나는 옅은 청록 직선 위에서만 움직입니다. 스칼라배로는 방향을 "뒤집을" 수는 있어도 "돌릴" 수는 없다는 뜻입니다. 이 사실은 4교시에서 아주 중요해집니다.
c = 0일 때 나오는 을 영벡터라고 하고 굵은 으로 씁니다. 화살표로는 길이가 0이라 방향이 없는, 원점의 점 하나입니다. 어떤 벡터에 영벡터를 더해도 그대로입니다: . 숫자의 0과 같은 역할입니다.
게임에서는 달리기 버튼을 누를 때 속도에 2를 곱하고, 뒷걸음질할 때 −1을 곱하고, 멈출 때 0을 곱합니다.
뺄셈은 "뒤집어서 더하기"입니다. 는 와 같습니다. 숫자로는 역시 성분끼리 빼면 됩니다.
그림으로는 더 쓸모 있는 해석이 있습니다. u − w는 w의 끝에서 u의 끝으로 가는 화살표입니다. w에서 출발해 만큼 더 가면 u에 도착하기 때문입니다: . 2-1에서 본 "도착 − 출발 = 움직인 양"과 같은 이야기입니다.
실습대에서 'u − w 보기'를 켜면 노랑 화살표가 진홍 w의 끝에서 청록 u의 끝을 향합니다. 원점에서 출발하지 않았지만, 2-1에서 말했듯 평행이동한 화살표는 같은 벡터이니 문제없습니다. u와 w를 같은 점에 겹쳐 놓으면 노랑 화살표가 사라지는 것도 확인해 보세요. 입니다.
게임에서 적이 플레이어를 쫓아가려면 "플레이어 위치 − 적 위치"를 계산합니다. 그것이 적에서 플레이어로 향하는 화살표입니다. 순서를 거꾸로 빼면 화살표 방향이 반대가 되어 적이 도망칩니다.
새로 나온 기호를 정리합니다.
| 책에 적힌 모양 | 읽는 법 | 뜻 | 파이썬에서는 |
|---|---|---|---|
| u 더하기 w | 성분끼리 더하기, 화살표 이어 붙이기 | u + w | |
| 씨 u | 모든 성분에 c 곱하기, 늘이기·줄이기 | c * u | |
| 마이너스 u | , 방향 뒤집기 | -u | |
| u 빼기 w | w의 끝에서 u의 끝으로 가는 화살표 | u - w | |
| (굵은 0) | 영벡터 | 모든 성분이 0인 벡터 | np.zeros(2) |
| 씨는 실수 | c는 숫자 하나(스칼라)다 | float |
실습대 아래 "NumPy로 확인하기" 칸에는 손을 뗄 때마다 이런 대화형 셸이 나옵니다. 기본값 그대로 한 줄씩 따라 해 봅시다.
>>> import numpy as np
>>> u = np.array([2, 1])
>>> w = np.array([1, 2])
>>> u + w
array([3, 3])
>>> 2 * u
array([4, 2])
>>> u - w
array([ 1, -1])NumPy 배열의 +, -는 그림에서 한 일 그대로 같은 자리의 성분끼리 계산합니다. 이런 계산을 원소별(element-wise) 연산이라고 부릅니다. 2 * u는 스칼라배로, 모든 성분에 2를 곱합니다. array([ 1, -1])에서 1 앞에 빈칸이 있는 것은 자릿수를 맞추려고 NumPy가 띄운 것일 뿐, 값과는 상관없습니다.
실습대의 'c·u 보기'를 켜지 않아도 NumPy 칸에는 언제나 c·u 줄이 나옵니다(처음 c는 2). 슬라이더를 0.5로 옮기면 그 줄이 이렇게 바뀝니다.
>>> 0.5 * u
array([1. , 0.5])
>>> -1 * u
array([-2, -1])
>>> -u
array([-2, -1])
>>> 0 * u
array([0, 0])정수 배열에 0.5를 곱하면 결과가 소수가 되므로 dtype이 float64로 바뀝니다(1교시에서 본 그 규칙). 1.은 1.0입니다. -u는 -1 * u를 줄여 쓴 것입니다.
NumPy 칸의 마지막 줄은 일부러 넣어 둔 함정입니다. 같은 숫자를 파이썬 리스트로 더하면 이렇게 됩니다.
>>> [2, 1] + [1, 2]
[2, 1, 1, 2]리스트의 +는 성분끼리 더하지 않고 두 리스트를 이어 붙입니다. 숫자 두 개짜리가 숫자 네 개짜리가 되었으니, 벡터의 덧셈과는 완전히 다른 일입니다. 곱셈도 마찬가지입니다.
>>> [2, 1] * 3
[2, 1, 2, 1, 2, 1]
>>> np.array([2, 1]) * 3
array([6, 3])리스트에 정수를 곱하면 그만큼 반복하고, 배열에 곱하면 성분마다 곱합니다. 리스트끼리는 아예 뺄 수도 없습니다.
>>> [2, 1] - [1, 2]
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for -: 'list' and 'list'| 계산 | 파이썬 리스트 | NumPy 배열 |
|---|---|---|
a + b | 이어 붙이기 | 성분끼리 더하기 |
a * 3 | 3번 반복 | 성분마다 3 곱하기 |
a * 0.5 | 오류 | 성분마다 0.5 곱하기 |
a - b | 오류 | 성분끼리 빼기 |
그래서 벡터 계산을 할 때는 처음에 np.array로 바꿔 두는 습관을 들이세요. 배열과 리스트를 섞으면 NumPy가 리스트를 배열로 바꿔서 계산해 주기는 합니다. 그래도 둘 다 리스트이면 이어 붙이기가 되어 버리니, 처음부터 배열로 만들어 두는 편이 안전합니다.
>>> u + [1, 2]
array([3, 3])배열에 숫자 하나를 더하면 모든 성분에 그 숫자가 더해집니다. NumPy가 숫자 하나를 배열 모양에 맞게 "넓게 펼쳐서" 계산하기 때문인데, 이것을 브로드캐스팅(broadcasting) 이라고 부릅니다. 스칼라배 2 * u도 같은 원리로 동작합니다.
>>> u + 10
array([12, 11])
>>> u / 2
array([1. , 0.5])u + 10은 선형대수 책에 나오는 연산은 아니지만, 데이터를 다룰 때 아주 자주 씁니다(예: 모든 점수에 10점 더하기). /로 나누면 결과는 늘 실수가 됩니다.
브로드캐스팅은 숫자 하나를 펼치는 것이지, 모양이 다른 벡터끼리 억지로 맞춰 주지는 않습니다. 2차원 벡터에 3차원 벡터를 더하려고 하면 오류가 납니다.
>>> np.array([1, 2, 3]) + u
Traceback (most recent call last):
...
ValueError: operands could not be broadcast together with shapes (3,) (2,)오류 메시지의 shapes (3,) (2,)가 원인을 그대로 알려 줍니다. 이런 오류를 만나면 먼저 양쪽의 shape부터 확인하세요.
위치에 속도를 프레임마다 더하는 게임 루프를 NumPy로 적어 봅시다.
>>> pos = np.array([0.0, 0.0])
>>> vel = np.array([1.5, 0.5])
>>> for frame in range(3):
... pos = pos + vel
... print(pos)
[1.5 0.5]
[3. 1.]
[4.5 1.5]
>>> pos + 2 * vel
array([7.5, 2.5])print로 찍은 배열은 array(...)라는 겉옷 없이 숫자만 빈칸으로 구분해 보여 줍니다. 마지막 줄은 "속도를 두 배로 올려 한 프레임 더 가면 어디에 있을까"를 계산한 것으로, 덧셈과 스칼라배를 섞어 쓴 것입니다.
정수 배열에 소수를 더해 그 자리에 담기. u += 0.5는 "u에 0.5를 더한 결과를 u 자리에 다시 담아라"라는 뜻입니다. 그런데 u는 정수(int64) 배열이라 소수 결과를 담을 수 없어 오류가 납니다.
>>> u = np.array([2, 1])
>>> u += 0.5
Traceback (most recent call last):
...
numpy._core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'메시지는 길지만 뜻은 "float64 결과를 int64 자리에 넣을 수 없다"입니다. u = u + 0.5처럼 새 배열을 만들거나, 처음부터 np.array([2.0, 1.0])처럼 실수로 만들어 두면 됩니다. 위의 게임 예시에서 위치를 0.0으로 시작한 이유가 이것입니다.
>>> u = u + 0.5
>>> u
array([2.5, 1.5])리스트끼리 더하기. 2-5에서 본 것처럼 [2, 1] + [1, 2]는 오류 없이 [2, 1, 1, 2]를 돌려줍니다. 오류가 나지 않아서 더 위험합니다. 결과의 길이가 갑자기 두 배가 되었다면 이 실수를 의심하세요.
(1, 4)에 있고 적이 (4, 0)에 있습니다. 적이 플레이어 쪽으로 움직이려면 어느 방향 벡터를 써야 할까요? NumPy로 계산해 보세요.a = [1, 2, 3]일 때 a * 2와 np.array(a) * 2의 결과를 예상한 뒤 확인해 보세요.>>> u = np.array([3, 1])
>>> w = np.array([-1, 2])
>>> u + w
array([2, 3])
>>> u - w
array([ 4, -1])
>>> player = np.array([1, 4])
>>> enemy = np.array([4, 0])
>>> player - enemy
array([-3, 4])
>>> a = [1, 2, 3]
>>> a * 2
[1, 2, 3, 1, 2, 3]
>>> np.array(a) * 2
array([2, 4, 6])
>>> -1.5 * np.array([2, 1])
array([-3. , -1.5])2번은 "도착(플레이어) − 출발(적)"이므로 player - enemy입니다. 왼쪽으로 3, 위로 4 가는 화살표입니다. 4번은 두 성분 모두 원래의 −1.5배이므로 c = −1.5입니다. 방향이 뒤집혔으니 c는 음수여야 합니다.
이번 교시 정리
벡터는 화살표로도 그린다: '어느 쪽으로 얼마나'. 평행이동한 화살표는 모두 같은 벡터.
덧셈 u + w = 화살표 이어 붙이기 = 성분끼리 더하기. 차원이 같아야 더할 수 있다.
스칼라배 c·u = 성분마다 c 곱하기: 늘이기·줄이기, 음수면 뒤집기, 0이면 영벡터.
뺄셈 u − w = w의 끝에서 u의 끝으로 가는 화살표 (도착 − 출발).
NumPy 배열의 + − *는 원소별 연산. 파이썬 리스트의 +는 이어 붙이기, *3은 반복이다.
배열과 숫자 하나는 브로드캐스팅으로 계산되지만, shape이 다른 벡터끼리는 ValueError.
다음 교시에는 화살표의 길이와 두 화살표 사이의 각도를 숫자로 구합니다. 그 중심에 있는 계산이 내적이고, 추천 시스템이 "취향이 비슷한 사람"을 찾을 때 쓰는 바로 그 계산입니다.
새 교시·새 강의 소식 받기
이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.
R
ReflexKR
눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.
ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.
© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
질문과 답변
이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.