개념
복소수(Complex Number)란 무엇인가?
복소수는 두 부분을 가집니다. 실수부(real part)와 허수부(imaginary part)입니다.
z = a + bi
where:
a는 실수부(real part)입니다.
b는 허수부(imaginary part)입니다.
i는 허수 단위(imaginary unit)이며, i^2 = -1로 정의됩니다.
이것이 전부입니다. 수직선(number line)을 평면(plane)으로 확장한 것입니다. 실수(real number)는 한 축 위에 있고, 허수(imaginary number)는 다른 축 위에 있습니다. 모든 복소수는 이 평면 위의 점(point)입니다.
복소수 산술(Complex Arithmetic)
덧셈(Addition). 실수부끼리 더하고, 허수부끼리 더합니다.
(a + bi) + (c + di) = (a + c) + (b + d)i
예시: (3 + 2i) + (1 + 4i) = 4 + 6i
곱셈(Multiplication). 분배 법칙(distributive law)을 사용하고, i^2 = -1이라는 사실을 기억합니다.
(a + bi)(c + di) = ac + adi + bci + bdi^2
= ac + adi + bci - bd
= (ac - bd) + (ad + bc)i
예시: (3 + 2i)(1 + 4i) = 3 + 12i + 2i + 8i^2
= 3 + 14i - 8
= -5 + 14i
켤레(Conjugate). 허수부의 부호(sign)를 뒤집습니다.
(a + bi)의 켤레 = a - bi
복소수와 그 켤레의 곱(product)은 항상 실수입니다.
(a + bi)(a - bi) = a^2 + b^2
나눗셈(Division). 분자(numerator)와 분모(denominator)에 분모의 켤레를 곱합니다.
(a + bi) / (c + di) = (a + bi)(c - di) / (c^2 + d^2)
이렇게 하면 분모에서 허수부가 사라지고, 깔끔한 복소수가 나옵니다.
복소평면(Complex Plane)
복소평면은 모든 복소수를 2차원 점(2D point)으로 대응(mapping)시킵니다. 가로축(horizontal axis)은 실수축(real axis)이고, 세로축(vertical axis)은 허수축(imaginary axis)입니다.
z = 3 + 2i 는 점 (3, 2)에 대응됩니다
z = -1 + 0i 는 실수축 위의 점 (-1, 0)에 대응됩니다
z = 0 + 4i 는 허수축 위의 점 (0, 4)에 대응됩니다
복소수는 동시에 점(point)이자 원점(origin)에서 출발하는 벡터(vector)입니다. 이 두 가지 해석이 함께 가능하다는 점 때문에 복소수가 기하학(geometry)에서 유용합니다.
평면 위의 모든 점은 원점에서의 거리(distance)와 양의 실수축(positive real axis)으로부터의 각도(angle)로 표현할 수 있습니다.
z = r * (cos(theta) + i*sin(theta))
여기서:
r = |z| = sqrt(a^2 + b^2) (크기 magnitude, 또는 모듈러스 modulus)
theta = atan2(b, a) (위상 phase, 또는 편각 argument)
직교형(rectangular form, a + bi)은 덧셈에 좋습니다. 극형(polar form, r, theta)은 곱셈에 좋습니다.
극형에서의 곱셈. 크기는 곱하고, 각도는 더합니다.
z1 = r1 * e^(i*theta1)
z2 = r2 * e^(i*theta2)
z1 * z2 = (r1 * r2) * e^(i*(theta1 + theta2))
이것이 복소수가 회전(rotation)에 완벽하게 맞는 이유입니다. 크기가 1인 복소수를 곱하는 것은 순수 회전(pure rotation)입니다.
복소 지수 함수(complex exponential)와 삼각법(trigonometry)을 잇는 다리입니다.
e^(i*theta) = cos(theta) + i*sin(theta)
이 lesson에서 가장 중요한 공식입니다. theta = pi이면 다음과 같습니다.
e^(i*pi) = cos(pi) + i*sin(pi) = -1 + 0i = -1
따라서: e^(i*pi) + 1 = 0
다섯 가지 기본 상수(fundamental constant)인 e, i, pi, 1, 0이 하나의 등식(equation) 안에서 연결됩니다.
머신러닝(ML)에서 오일러 공식이 중요한 이유
오일러 공식은 e^(i*theta)가 theta가 변할 때 단위원(unit circle)을 따라 움직인다고 말합니다. theta = 0이면 (1, 0)에 있습니다. theta = pi/2이면 (0, 1)에 있습니다. theta = pi이면 (-1, 0)에 있습니다. theta = 3*pi/2이면 (0, -1)에 있습니다. 한 바퀴(full rotation)는 theta = 2*pi입니다.
즉 복소 지수 함수는 곧 회전입니다. 그리고 회전은 신호 처리(signal processing)와 머신러닝 곳곳에 있습니다.
2D 회전과의 연결
복소수 (x + yi)에 e^(i*theta)를 곱하면 점 (x, y)가 원점을 중심으로 각도 theta만큼 회전(rotate)합니다.
복소 곱셈에 의한 회전:
(x + yi) * (cos(theta) + i*sin(theta))
= (x*cos(theta) - y*sin(theta)) + (x*sin(theta) + y*cos(theta))i
행렬 곱셈에 의한 회전:
[cos(theta) -sin(theta)] [x] [x*cos(theta) - y*sin(theta)]
[sin(theta) cos(theta)] [y] = [x*sin(theta) + y*cos(theta)]
두 방식은 동일한 결과를 냅니다. 복소 곱셈은 곧 2D 회전입니다. 회전 행렬(rotation matrix)은 행렬 표기법(matrix notation)으로 쓴 복소 곱셈입니다.
graph TD
subgraph "Complex Multiplication = 2D Rotation"
A["z = x + yi<br/>Point (x, y)"] -->|"multiply by e^(i*theta)"| B["z' = z * e^(i*theta)<br/>Point rotated by theta"]
end
subgraph "Equivalent Matrix Form"
C["vector [x, y]"] -->|"multiply by rotation matrix"| D["[x cos theta - y sin theta,<br/> x sin theta + y cos theta]"]
end
B -.->|"same result"| D
페이저(Phasor)와 회전 신호(Rotating Signal)
복소 지수 함수 e^(i*omega*t)는 각진동수(angular frequency) omega로 단위원 위를 도는 점입니다. t가 증가하면 점이 원을 따라 움직입니다.
이 회전하는 점의 실수부는 cos(omega*t)입니다. 허수부는 sin(omega*t)입니다. 사인파 신호(sinusoidal signal)는 회전하는 복소수의 그림자(shadow)입니다.
e^(i*omega*t) = cos(omega*t) + i*sin(omega*t)
실수부: cos(omega*t) -- 코사인 파(cosine wave)
허수부: sin(omega*t) -- 사인 파(sine wave)
이것이 페이저 표현(phasor representation)입니다. 흔들리는 사인파를 직접 추적하는 대신 부드럽게 회전하는 화살표(arrow)를 추적합니다. 위상 변화(phase shift)는 각도 오프셋(angle offset)이 됩니다. 진폭 변화(amplitude change)는 크기 변화(magnitude change)가 됩니다. 신호의 덧셈(signal addition)은 벡터 덧셈(vector addition)이 됩니다.
단위근(Roots of Unity)
N차 단위근(N-th roots of unity)은 단위원 위에 같은 간격으로 놓인 N개의 점입니다.
w_k = e^(2*pi*i*k/N) k = 0, 1, 2, ..., N-1
N = 4이면 단위근은 1, i, -1, -i입니다. 네 방위점(compass point)입니다.
N = 8이면 네 방위점에 네 대각점(diagonal point)이 더해집니다.
단위근은 이산 푸리에 변환(Discrete Fourier Transform)의 토대입니다. DFT는 신호를 N개의 등간격 주파수 성분(equally-spaced frequency component)으로 분해(decompose)합니다.
DFT와의 연결
신호 x[0], x[1], ..., x[N-1]의 이산 푸리에 변환은 다음과 같습니다.
X[k] = sum_{n=0}^{N-1} x[n] * e^(-2*pi*i*k*n/N)
각 X[k]는 신호가 k번째 단위근, 즉 주파수 k의 복소 사인파(complex sinusoid)와 얼마나 상관(correlation)되는지 측정합니다. DFT는 신호를 N개의 회전 페이저로 나누고, 각 페이저의 진폭(amplitude)과 위상(phase)을 알려줍니다.
i가 허수가 아닌 이유
"허수(imaginary)"라는 단어는 역사적 우연입니다. 데카르트(Descartes)가 무시하는 듯한 의미로 사용했습니다. 하지만 i는 음수(negative number)가 처음 거부당하던 시절의 음수보다 더 허구적이지 않습니다. 음수가 "3에서 5를 빼면 무엇인가?"에 답한다면, 허수 단위는 "무엇을 제곱하면 -1이 되는가?"에 답합니다.
더 유용하게는 이렇게 볼 수 있습니다. i는 90도 회전 연산자(90-degree rotation operator)입니다. 실수에 i를 한 번 곱하면 허수축으로 90도 회전합니다. i를 한 번 더 곱하면, 즉 i^2를 만들면 다시 90도 회전하여 음의 실수 방향(negative real direction)을 가리킵니다. 그래서 i^2 = -1입니다. 신비로운 일이 아닙니다. 두 번의 1/4 회전(quarter-turn)으로 만든 1/2 회전(half-turn)입니다.
복소수가 공학(engineering) 곳곳에 등장하는 이유도 이것입니다. 전자기파(electromagnetic wave), 양자 상태(quantum state), 신호 진동(signal oscillation), 위치 인코딩(positional encoding)처럼 회전하는 것은 복소수로 자연스럽게 설명됩니다.
복소 지수 함수와 삼각 함수(Complex Exponential vs Trigonometric Function)
오일러 공식 이전에는 공학자(engineer)들이 신호를 A*cos(omega*t + phi)처럼 썼습니다. 진폭 A, 주파수 omega, 위상 phi입니다. 이 표현도 작동하지만 산술이 불편합니다. 위상이 다른 코사인 두 개를 더하려면 삼각 함수 항등식(trigonometric identity)이 필요합니다.
복소 지수 함수를 사용하면 같은 신호를 A*e^(i*(omega*t + phi))로 표현합니다. 두 신호를 더하는 일은 복소수 두 개를 더하는 일이 됩니다. 곱셈(multiplication), 즉 변조(modulation)는 크기를 곱하고 각도를 더하는 일이 됩니다. 위상 변화는 각도 덧셈(angle addition)이 됩니다. 주파수 변화(frequency shift)는 페이저 곱셈(phasor multiplication)이 됩니다.
신호 처리 전체가 복소 지수 표기법(complex exponential notation)으로 옮겨간 이유는 수학이 더 깔끔하기 때문입니다. "실제 신호(real signal)"는 언제나 복소 표현(complex representation)의 실수부입니다. 허수부는 부기(bookkeeping)로 함께 가져가며, 모든 대수(algebra)가 자연스럽게 작동하게 해줍니다.
사인파 위치 인코딩(Sinusoidal positional encoding)(원래의 Transformer 논문):
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
sin과 cos 쌍은 서로 다른 주파수에 있는 복소 지수 함수의 실수부와 허수부입니다. 각 주파수는 위치 인코딩(position encoding)에 서로 다른 "해상도(resolution)"를 제공합니다. 낮은 주파수(low frequency)는 천천히 변해 거친 위치(coarse position)를 담고, 높은 주파수(high frequency)는 빠르게 변해 미세한 위치(fine position)를 담습니다. 함께 쓰이면 각 위치에 고유한 주파수 지문(frequency fingerprint)을 제공합니다.
RoPE(Rotary Position Embedding)는 이를 더 밀어붙입니다. RoPE는 질의 벡터(query vector)와 키 벡터(key vector)에 복소 회전 행렬(complex rotation matrix)을 명시적으로 곱합니다. 두 토큰(token) 사이의 상대 위치(relative position)는 회전각(rotation angle)이 됩니다. 어텐션(attention)은 이 회전된 벡터를 사용해 계산되며, 모델은 복소 곱셈을 통해 상대 위치에 민감해집니다.
| 연산 | 대수적 형태 | 기하학적 의미 |
|---|
| 덧셈(Addition) | (a+c) + (b+d)i | 평면에서의 벡터 덧셈 |
| 곱셈(Multiplication) | (ac-bd) + (ad+bc)i | 회전 및 크기 조정 |
| 켤레(Conjugate) | a - bi | 실수축에 대한 반사(reflection) |
| 크기(Magnitude) | sqrt(a^2 + b^2) | 원점으로부터의 거리 |
| 위상(Phase) | atan2(b, a) | 양의 실수축으로부터의 각도 |
| 나눗셈(Division) | 켤레를 곱하기 | 역회전 및 크기 재조정 |
| 거듭제곱(Power) | r^n * e^(intheta) | n번 회전하고 r^n만큼 크기 조정 |
graph LR
subgraph "Unit Circle"
direction TB
U1["e^(i*0) = 1"] -.-> U2["e^(i*pi/2) = i"]
U2 -.-> U3["e^(i*pi) = -1"]
U3 -.-> U4["e^(i*3pi/2) = -i"]
U4 -.-> U1
end
subgraph "Applications"
A1["Euler's formula:<br/>e^(i*theta) = cos + i*sin"]
A2["DFT uses roots of unity:<br/>e^(2*pi*i*k/N)"]
A3["RoPE uses rotation:<br/>q * e^(i*m*theta)"]
end
U1 --> A1
U1 --> A2
U1 --> A3
만들어 보기
Step 1: Complex 클래스 만들기
산술, 크기(magnitude), 위상(phase), 직교형과 극형 사이의 변환(conversion)을 지원하는 복소수 클래스를 만듭니다.
import math
class Complex:
def __init__(self, real, imag=0.0):
self.real = real
self.imag = imag
def __add__(self, other):
return Complex(self.real + other.real, self.imag + other.imag)
def __mul__(self, other):
r = self.real * other.real - self.imag * other.imag
i = self.real * other.imag + self.imag * other.real
return Complex(r, i)
def __truediv__(self, other):
denom = other.real ** 2 + other.imag ** 2
r = (self.real * other.real + self.imag * other.imag) / denom
i = (self.imag * other.real - self.real * other.imag) / denom
return Complex(r, i)
def magnitude(self):
return math.sqrt(self.real ** 2 + self.imag ** 2)
def phase(self):
return math.atan2(self.imag, self.real)
def conjugate(self):
return Complex(self.real, -self.imag)
Step 2: 극형 변환과 오일러 공식
def to_polar(z):
return z.magnitude(), z.phase()
def from_polar(r, theta):
return Complex(r * math.cos(theta), r * math.sin(theta))
def euler(theta):
return Complex(math.cos(theta), math.sin(theta))
검증합니다. euler(theta).magnitude()는 항상 1.0이어야 합니다. euler(0)은 (1, 0)을 반환해야 합니다. euler(pi)는 (-1, 0)을 반환해야 합니다.
Step 3: 회전(Rotation)
점 (x, y)를 각도 theta만큼 회전시키는 일은 복소 곱셈 한 번입니다.
point = Complex(3, 4)
rotated = point * euler(math.pi / 4)
크기는 그대로 유지됩니다. 각도만 바뀝니다.
Step 4: 복소 산술로 DFT 구현
def dft(signal):
N = len(signal)
result = []
for k in range(N):
total = Complex(0, 0)
for n in range(N):
angle = -2 * math.pi * k * n / N
total = total + Complex(signal[n], 0) * euler(angle)
result.append(total)
return result
이것은 O(N^2) 복잡도의 DFT입니다. 각 출력 X[k]는 신호 표본(signal sample)에 단위근을 곱한 합(sum)입니다.
Step 5: 역 DFT(Inverse DFT)
역 DFT는 스펙트럼(spectrum)에서 원래 신호를 복원(reconstruct)합니다. 정방향 DFT(forward DFT)와 달라지는 것은 지수(exponent)의 부호를 뒤집고 N으로 나누는 것뿐입니다.
def idft(spectrum):
N = len(spectrum)
result = []
for n in range(N):
total = Complex(0, 0)
for k in range(N):
angle = 2 * math.pi * k * n / N
total = total + spectrum[k] * euler(angle)
result.append(Complex(total.real / N, total.imag / N))
return result
이렇게 하면 완벽한 복원(perfect reconstruction)을 얻습니다. DFT를 적용한 뒤 IDFT를 적용하면 기계 정밀도(machine precision) 수준에서 원래 신호로 돌아옵니다. 정보(information)는 손실되지 않습니다.
Step 6: 단위근(Roots of Unity)
def roots_of_unity(N):
return [euler(2 * math.pi * k / N) for k in range(N)]
두 가지 성질(property)을 검증합니다.
- 모든 단위근의 크기는 정확히 1입니다.
- 모든 N개 단위근의 합은 0입니다. 대칭성(symmetry) 때문에 서로 상쇄(cancel)됩니다.
이 성질이 DFT를 가역(invertible)으로 만듭니다. 단위근은 주파수 영역(frequency domain)의 직교 기저(orthogonal basis)를 형성합니다.
사용하기
Python은 내장 복소수(built-in complex number) 지원을 제공합니다. 리터럴(literal) j가 허수 단위를 나타냅니다.
z = 3 + 2j
w = 1 + 4j
print(z + w)
print(z * w)
print(abs(z))
import cmath
print(cmath.phase(z))
print(cmath.exp(1j * cmath.pi))
배열(array)에서는 numpy가 복소수를 기본(native)으로 다룹니다.
import numpy as np
z = np.array([1+2j, 3+4j, 5+6j])
print(np.abs(z))
print(np.angle(z))
print(np.conj(z))
print(np.real(z))
print(np.imag(z))
signal = np.sin(2 * np.pi * 5 * np.linspace(0, 1, 128))
spectrum = np.fft.fft(signal)
freqs = np.fft.fftfreq(128, d=1/128)
산출물 만들기
code/complex_numbers.py를 실행해 복소 산술, 회전, DFT, 페이저, 위치 인코딩 예제를 확인합니다. 최종 산출물로는 outputs/skill-complex-arithmetic.md가 검수됩니다.
연습문제
-
손으로 풀어보는 복소 산술 (쉬움). (2 + 3i) * (4 - i)를 손으로 계산하고 코드로 검증합니다. 이어서 (5 + 2i) / (1 - 3i)를 계산합니다. 두 결과를 복소평면에 그려 보고, 곱셈이 첫 번째 수를 회전시키고 크기 조정(scale)했는지 확인합니다.
-
회전 수열 (중간). 점 (1, 0)에서 시작합니다. e^(i*pi/6)을 12번 곱합니다. 12번 곱한 뒤 다시 (1, 0)으로 돌아오는지 검증합니다. 각 단계의 좌표를 출력하고 정 12각형(regular 12-gon)을 그리는지 확인합니다.
-
알려진 신호의 DFT (중간). 32개 점에서 표본화한 sin(2*pi*3*t)와 0.5*sin(2*pi*7*t)의 합으로 신호를 만듭니다. DFT를 실행합니다. 크기 스펙트럼(magnitude spectrum)이 주파수 3과 7에서 봉우리(peak)를 갖고, 7의 봉우리 높이가 3의 절반인지 검증합니다.
-
단위근 시각화 (중간). 8차 단위근을 계산합니다. 합이 0인지 검증합니다. 어떤 단위근에 원시근(primitive root) e^(2*pi*i/8)을 곱하면 다음 단위근이 되는지 검증합니다.
-
회전 행렬 동치성 (어려움). 무작위 각도 10개와 무작위 점 10개에 대해 복소 곱셈이 2x2 회전 행렬과 행렬-벡터 곱(matrix-vector multiplication)으로 얻은 결과와 같은지 검증합니다. 최대 수치 오차(maximum numerical difference)를 출력합니다.
핵심 용어
| 용어 | 의미 |
|---|
| 복소수(Complex number) | a + bi 형태의 수입니다. a는 실수부, b는 허수부이며 i^2 = -1입니다. |
| 허수 단위(Imaginary unit) | i^2 = -1로 정의되는 수입니다. 철학적 의미에서 허구적인 것이 아니라 회전 연산자입니다. |
| 복소평면(Complex plane) | x축은 실수, y축은 허수인 2차원 평면입니다. 아르강 평면(Argand plane)이라고도 합니다. |
| 크기(Magnitude, modulus) | 원점으로부터의 거리입니다. sqrt(a^2 + b^2)이며 ` |
| 위상(Phase, argument) | 양의 실수축으로부터의 각도입니다. atan2(b, a)이며 arg(z)로 씁니다. |
| 켤레(Conjugate) | 실수축을 기준으로 거울상(mirror image)을 만드는 연산입니다. a + bi의 켤레는 a - bi입니다. |
| 극형(Polar form) | a + bi 대신 r * e^(i*theta)로 z를 표현하는 방식입니다. 곱셈을 쉽게 만듭니다. |
| 오일러 공식(Euler's formula) | e^(i*theta) = cos(theta) + i*sin(theta)입니다. 지수 함수와 삼각법을 연결합니다. |
| 페이저(Phasor) | 사인파 신호를 나타내는 회전하는 복소수 e^(i*omega*t)입니다. |
| 단위근(Roots of unity) | k = 0부터 N-1까지의 e^(2*pi*i*k/N)입니다. 단위원 위에 같은 간격으로 놓인 N개의 점입니다. |
| DFT(이산 푸리에 변환) | Discrete Fourier Transform입니다. 단위근을 사용해 신호를 복소 사인파 성분으로 분해합니다. |
| RoPE(회전 위치 임베딩) | Rotary Position Embedding입니다. 복소 곱셈을 사용해 transformer의 어텐션에서 상대 위치를 인코딩합니다. |
더 읽을거리