본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 1. · 50 Views
NumPy 심화: 행렬 연산 완벽 가이드
NumPy의 핵심 행렬 연산을 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다. 행렬 곱셈부터 브로드캐스팅, 통계 함수, 난수 생성까지 데이터 분석의 필수 기술을 마스터할 수 있습니다.
목차
1. 행렬 곱셈 (dot, matmul)
김개발 씨는 머신러닝 프로젝트를 진행하던 중, 신경망의 가중치 계산 코드를 작성해야 했습니다. 행렬 곱셈이 필요하다는 건 알겠는데, dot과 matmul 중 어떤 것을 써야 할지 고민이 됩니다.
"둘 다 행렬 곱셈 아닌가요?"라고 선배에게 물었습니다.
행렬 곱셈은 두 행렬을 특정 규칙에 따라 곱하여 새로운 행렬을 만드는 연산입니다. 마치 요리사가 레시피에 따라 재료들을 조합하여 새로운 요리를 만드는 것과 같습니다.
NumPy에서는 dot과 matmul 두 가지 방법을 제공하며, 각각의 용도를 이해하면 더 효율적인 코드를 작성할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
# 2x3 행렬과 3x2 행렬 생성
A = np.array([[1, 2, 3],
[4, 5, 6]])
B = np.array([[7, 8],
[9, 10],
[11, 12]])
# dot을 사용한 행렬 곱셈
result_dot = np.dot(A, B)
print("dot 결과:\n", result_dot)
# matmul을 사용한 행렬 곱셈 (@ 연산자)
result_matmul = A @ B
print("matmul 결과:\n", result_matmul)
김개발 씨는 입사 6개월 차 데이터 엔지니어입니다. 오늘은 추천 시스템의 사용자-아이템 행렬을 다루는 코드를 작성하고 있었습니다.
행렬 곱셈을 해야 하는데, 검색해보니 dot도 있고 matmul도 있고, 심지어 @ 기호까지 있어서 혼란스러웠습니다. 옆자리의 박시니어 씨가 커피를 마시며 다가왔습니다.
"행렬 곱셈 때문에 고민하고 있구나? 차이점을 알려줄게." 그렇다면 행렬 곱셈이란 정확히 무엇일까요?
쉽게 비유하자면, 행렬 곱셈은 마치 공장의 조립 라인과 같습니다. 첫 번째 행렬의 각 행이 두 번째 행렬의 각 열과 만나 새로운 부품을 만들어냅니다.
이 과정에서 첫 번째 행렬의 열 개수와 두 번째 행렬의 행 개수가 반드시 일치해야 합니다. 마치 볼트와 너트의 규격이 맞아야 조립이 되는 것처럼요.
행렬 곱셈이 왜 그렇게 중요할까요? 데이터 과학에서 행렬 곱셈은 거의 모든 곳에서 사용됩니다.
신경망에서 가중치를 계산할 때, 이미지를 변환할 때, 추천 시스템에서 유사도를 계산할 때 모두 행렬 곱셈이 필요합니다. 만약 이 연산을 직접 for 루프로 구현한다면 코드는 복잡해지고 속도는 느려집니다.
NumPy의 dot 함수는 오래전부터 사용되어 온 함수입니다. 1차원 배열에서는 내적을, 2차원 배열에서는 행렬 곱셈을, 더 높은 차원에서는 마지막 축과 뒤에서 두 번째 축의 합 곱을 계산합니다.
다재다능하지만, 그만큼 동작 방식이 복잡할 수 있습니다. 반면 matmul 함수와 @ 연산자는 Python 3.5부터 도입되었습니다.
이들은 순수하게 행렬 곱셈만을 위해 설계되었습니다. 3차원 이상의 배열에서 배치 처리를 할 때 더 직관적으로 동작합니다.
딥러닝에서 여러 샘플을 한 번에 처리할 때 특히 유용합니다. 위 코드를 살펴보겠습니다.
A는 2행 3열, B는 3행 2열 행렬입니다. A의 열 개수(3)와 B의 행 개수(3)가 일치하므로 곱셈이 가능합니다.
결과는 2행 2열 행렬이 됩니다. 실제 계산 과정을 따라가 봅시다.
결과 행렬의 첫 번째 원소는 A의 첫 행 [1, 2, 3]과 B의 첫 열 [7, 9, 11]의 내적입니다. 즉, 1x7 + 2x9 + 3x11 = 58이 됩니다.
이런 계산이 모든 위치에서 일어납니다. 실무에서는 어떤 것을 선택해야 할까요?
대부분의 경우 @ 연산자를 사용하는 것이 좋습니다. 코드가 간결해지고 의도가 명확해집니다.
다만, 스칼라 값과의 연산이 필요하다면 dot을 사용해야 합니다. matmul은 스칼라 연산을 지원하지 않기 때문입니다.
하지만 주의할 점이 있습니다. 행렬의 차원이 맞지 않으면 에러가 발생합니다.
초보 개발자들이 가장 많이 만나는 에러가 바로 "shapes not aligned"입니다. 곱셈 전에 항상 shape을 확인하는 습관을 들이세요.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 듣고 김개발 씨는 @ 연산자로 코드를 깔끔하게 정리했습니다.
"덕분에 코드도 짧아지고 읽기도 쉬워졌네요!"
실전 팁
💡 - 일반적인 2D 행렬 곱셈에서는 @ 연산자를 사용하면 가장 깔끔합니다
- 곱셈 전에 A.shape과 B.shape을 출력해서 차원을 확인하는 습관을 들이세요
- 배치 처리가 필요한 딥러닝 코드에서는 matmul이 더 직관적입니다
2. 브로드캐스팅 원리
김개발 씨는 데이터 전처리 코드를 작성하다가 이상한 현상을 발견했습니다. 분명히 크기가 다른 배열끼리 연산했는데 에러 없이 결과가 나왔습니다.
"이게 왜 되는 거지?" 혼란스러운 표정으로 화면을 바라봅니다.
브로드캐스팅은 크기가 다른 배열 간의 연산을 가능하게 해주는 NumPy의 강력한 기능입니다. 마치 작은 타일을 복사해서 큰 벽면을 채우는 것처럼, 작은 배열을 자동으로 확장하여 큰 배열과 맞춰줍니다.
이 원리를 이해하면 불필요한 메모리 낭비 없이 효율적인 연산이 가능해집니다.
다음 코드를 살펴봅시다.
import numpy as np
# 3x3 행렬 생성
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 1차원 배열 (각 열에 더할 값)
row_vector = np.array([10, 20, 30])
# 브로드캐스팅: 자동으로 row_vector가 확장됨
result = matrix + row_vector
print("행렬 + 행벡터:\n", result)
# 열 벡터와의 브로드캐스팅
col_vector = np.array([[100], [200], [300]])
result2 = matrix + col_vector
print("행렬 + 열벡터:\n", result2)
김개발 씨는 수천 개의 데이터 포인트를 정규화하는 작업을 하고 있었습니다. 각 열에서 평균을 빼야 하는데, for 루프를 돌리자니 코드가 지저분해 보였습니다.
그런데 선배의 코드를 보니 그냥 빼기 연산 한 줄로 끝나 있었습니다. 박시니어 씨가 웃으며 말했습니다.
"그게 바로 브로드캐스팅이야. NumPy가 알아서 맞춰주거든." 브로드캐스팅이란 정확히 무엇일까요?
쉽게 비유하자면, 브로드캐스팅은 복사기와 같습니다. 작은 이미지를 큰 캔버스에 맞게 여러 번 복사해서 붙이는 것처럼, NumPy는 작은 배열을 큰 배열의 크기에 맞게 가상으로 확장합니다.
실제로 메모리를 복사하지 않고 마치 복사한 것처럼 연산을 수행합니다. 브로드캐스팅이 없던 시절에는 어땠을까요?
개발자들은 크기가 다른 배열을 연산하기 위해 직접 배열을 복제해야 했습니다. np.tile이나 np.repeat로 배열을 확장하고, 그 다음에 연산을 수행했습니다.
메모리도 낭비되고, 코드도 길어지고, 실수할 여지도 많았습니다. 브로드캐스팅의 규칙은 생각보다 간단합니다.
두 배열의 차원을 뒤쪽부터 비교합니다. 각 차원에서 크기가 같거나, 둘 중 하나가 1이면 브로드캐스팅이 가능합니다.
크기가 1인 차원은 다른 배열의 해당 차원 크기에 맞게 확장됩니다. 위 코드를 살펴보겠습니다.
matrix는 (3, 3) 형태이고, row_vector는 (3,) 형태입니다. 뒤쪽 차원부터 비교하면 둘 다 3으로 같습니다.
row_vector에는 앞쪽 차원이 없으므로 1로 간주되어 (1, 3)이 됩니다. 그러면 (3, 3)과 (1, 3)의 연산이 되고, 1은 3으로 확장됩니다.
col_vector의 경우는 어떨까요? (3, 1) 형태입니다.
(3, 3)과 비교하면, 첫 번째 차원은 둘 다 3으로 같고, 두 번째 차원은 1과 3입니다. 1이 3으로 확장되어 연산이 수행됩니다.
실무에서 브로드캐스팅은 정말 자주 사용됩니다. 데이터 정규화에서 각 특성별 평균을 빼거나, 이미지 처리에서 각 채널에 다른 가중치를 곱하거나, 배치 데이터에 동일한 바이어스를 더할 때 브로드캐스팅이 빛을 발합니다.
한 줄의 코드로 수백만 개의 연산을 처리할 수 있습니다. 하지만 주의해야 할 점도 있습니다.
브로드캐스팅 규칙에 맞지 않으면 에러가 발생합니다. 예를 들어 (3, 4)와 (5,) 배열은 브로드캐스팅이 불가능합니다.
뒤쪽 차원이 4와 5로 다르고, 둘 다 1이 아니기 때문입니다. 이런 경우 reshape으로 차원을 조정해야 합니다.
더 큰 문제는 의도치 않은 브로드캐스팅입니다. 크기가 다른 배열끼리 연산했는데 에러 없이 결과가 나와서 버그를 발견하지 못하는 경우가 있습니다.
항상 shape을 확인하는 습관이 중요합니다. 김개발 씨는 이제 브로드캐스팅을 활용하여 정규화 코드를 단 두 줄로 줄였습니다.
"메모리도 아끼고 코드도 깔끔해지다니, 정말 마법 같네요!"
실전 팁
💡 - 차원을 맞출 때는 np.newaxis나 reshape을 활용하세요
- 브로드캐스팅 전에 두 배열의 shape을 출력해보는 것이 좋습니다
- 의도치 않은 브로드캐스팅을 막으려면 연산 결과의 shape도 확인하세요
3. reshape과 차원 변환
김개발 씨는 이미지 처리 코드를 작성하다가 막혔습니다. 1차원으로 펼쳐진 이미지 데이터를 다시 2차원으로 바꿔야 하는데, 어떻게 해야 할지 모르겠습니다.
"flatten은 했는데, 다시 원래대로 돌리려면 어떻게 하지?"
reshape은 배열의 원소는 그대로 둔 채 형태만 바꾸는 함수입니다. 마치 레고 블록을 분해하지 않고 다른 모양으로 재조립하는 것과 같습니다.
데이터 전처리, 신경망 입력 준비, 이미지 변환 등 거의 모든 데이터 작업에서 필수적으로 사용됩니다.
다음 코드를 살펴봅시다.
import numpy as np
# 12개의 원소를 가진 1차원 배열
arr = np.arange(12)
print("원본:", arr)
# 3x4 행렬로 변환
matrix_3x4 = arr.reshape(3, 4)
print("3x4 형태:\n", matrix_3x4)
# 2x2x3 3차원 배열로 변환
tensor = arr.reshape(2, 2, 3)
print("2x2x3 형태:\n", tensor)
# -1을 사용한 자동 계산
auto_shape = arr.reshape(4, -1) # 4행, 열은 자동 계산
print("4x? 형태:\n", auto_shape)
김개발 씨는 이미지 분류 모델을 만들고 있었습니다. 28x28 픽셀의 손글씨 이미지를 신경망에 넣으려면 784개의 1차원 벡터로 펼쳐야 합니다.
반대로 출력을 시각화하려면 다시 2차원으로 바꿔야 합니다. reshape 없이는 이 작업이 불가능합니다.
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "reshape을 이해하려면 메모리 구조를 알아야 해." 그렇다면 reshape은 어떻게 작동할까요?
컴퓨터 메모리에서 배열은 항상 일렬로 저장됩니다. 2차원이든 3차원이든 실제로는 1차원으로 쭉 늘어서 있습니다.
reshape은 이 메모리를 그대로 두고, 해석하는 방식만 바꿉니다. 마치 같은 12칸 초콜릿을 3x4로 나눠 먹을지, 2x6으로 나눠 먹을지 정하는 것과 같습니다.
이것이 reshape이 빠른 이유입니다. 데이터를 복사하지 않고 **뷰(view)**만 바꾸기 때문에 아무리 큰 배열도 순식간에 reshape됩니다.
다만, 원본과 reshape된 배열이 같은 메모리를 공유한다는 점은 주의해야 합니다. 하나를 수정하면 다른 하나도 바뀝니다.
위 코드에서 arange(12)는 0부터 11까지 12개의 숫자를 만듭니다. 이것을 (3, 4)로 reshape하면 3행 4열 행렬이 됩니다.
원소의 순서는 그대로이고, 배치만 달라집니다. -1은 마법 같은 숫자입니다.
"나머지는 알아서 계산해줘"라는 의미입니다. 총 12개의 원소가 있고 4행으로 만들고 싶다면, 열은 12/4 = 3으로 자동 계산됩니다.
배치 크기가 유동적인 머신러닝 코드에서 매우 유용합니다. 3차원 배열로의 변환도 살펴봅시다.
(2, 2, 3)은 2개의 "페이지"가 있고, 각 페이지에 2행 3열의 데이터가 있다고 생각하면 됩니다. 이미지 배치를 다룰 때 자주 사용하는 형태입니다.
실무에서 reshape은 정말 많이 사용됩니다. 딥러닝에서 Conv2D 레이어는 4차원 입력을 받고, Dense 레이어는 2차원 입력을 받습니다.
이 둘을 연결하려면 reshape이 필수입니다. 판다스 DataFrame을 NumPy로 변환한 후 모델에 넣을 때도 reshape으로 차원을 맞춥니다.
하지만 주의할 점이 있습니다. reshape의 전후 원소 개수가 반드시 같아야 합니다.
12개의 원소를 (3, 5)로 바꾸려고 하면 에러가 발생합니다. 15칸이 필요한데 12개밖에 없으니까요.
이런 에러를 만나면 먼저 원본 배열의 size를 확인하세요. flatten과 ravel의 차이도 알아두면 좋습니다.
둘 다 1차원으로 펼치지만, flatten은 복사본을, ravel은 가능하면 뷰를 반환합니다. 원본 보존이 중요하면 flatten을, 메모리 효율이 중요하면 ravel을 사용하세요.
김개발 씨는 reshape(-1)로 이미지를 펼치고, reshape(28, 28)로 다시 복원하는 코드를 완성했습니다. "차원 변환이 이렇게 간단하다니!"
실전 팁
💡 - -1을 사용하면 한 차원을 자동 계산할 수 있어 유연한 코드 작성이 가능합니다
- reshape은 뷰를 반환하므로 원본과 메모리를 공유합니다. 독립적인 복사본이 필요하면 .copy()를 사용하세요
- 원소 개수가 맞지 않으면 에러가 발생하니 arr.size로 미리 확인하세요
4. 통계 함수 (mean, std, sum)
김개발 씨는 사용자 행동 데이터를 분석하고 있었습니다. 일별 평균 접속 시간, 사용자별 총 구매액, 데이터의 표준편차까지 구해야 합니다.
"for 루프로 일일이 계산해야 하나..." 한숨을 쉬려던 순간, NumPy 통계 함수를 발견했습니다.
NumPy의 통계 함수들은 대용량 데이터에서 평균, 합계, 표준편차 등을 순식간에 계산해줍니다. 마치 계산기의 통계 모드처럼, 복잡한 수식을 한 줄의 코드로 해결합니다.
특히 axis 매개변수를 이용하면 행별, 열별로 원하는 방향의 통계를 구할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
# 3명의 학생, 4번의 시험 점수
scores = np.array([[85, 90, 78, 92],
[70, 88, 95, 80],
[90, 85, 88, 91]])
# 전체 평균
print("전체 평균:", np.mean(scores))
# 학생별 평균 (axis=1: 행 방향으로 계산)
print("학생별 평균:", np.mean(scores, axis=1))
# 시험별 평균 (axis=0: 열 방향으로 계산)
print("시험별 평균:", np.mean(scores, axis=0))
# 표준편차와 합계
print("전체 표준편차:", np.std(scores))
print("학생별 총점:", np.sum(scores, axis=1))
김개발 씨는 e커머스 회사의 데이터 분석팀에서 일하고 있습니다. 매일 수십만 건의 거래 데이터를 분석해야 하는데, 엑셀로는 한계가 있었습니다.
Python을 배우기 시작했고, 오늘은 NumPy로 통계 분석을 해보려 합니다. 박시니어 씨가 옆에서 조언했습니다.
"NumPy 통계 함수는 정말 강력해. 특히 axis를 이해하면 엑셀보다 훨씬 편해져." 통계 함수는 왜 필요할까요?
데이터 분석의 시작은 항상 기초 통계입니다. 평균은 데이터의 중심을, 표준편차는 데이터의 퍼짐 정도를, 합계는 전체 규모를 알려줍니다.
이 세 가지만 알아도 데이터의 대략적인 모습을 파악할 수 있습니다. axis 매개변수가 핵심입니다.
많은 초보자들이 axis에서 헷갈려합니다. 비유로 설명하자면, axis는 "이 방향을 따라 값들을 모아서 계산하라"는 의미입니다.
axis=0이면 위에서 아래로 내려가며 각 열의 값들을 모읍니다. axis=1이면 왼쪽에서 오른쪽으로 가며 각 행의 값들을 모읍니다.
위 코드를 자세히 살펴봅시다. scores 배열은 3명의 학생이 4번의 시험을 본 결과입니다.
np.mean(scores)는 모든 12개 점수의 평균을 구합니다. np.mean(scores, axis=1)은 각 학생별 평균을 구합니다.
첫 번째 학생 [85, 90, 78, 92]의 평균, 두 번째 학생의 평균... 이렇게 3개의 결과가 나옵니다.
행 방향(가로)으로 값들을 모아 계산하기 때문입니다. np.mean(scores, axis=0)은 각 시험별 평균을 구합니다.
첫 번째 시험에서 세 학생의 점수 [85, 70, 90]의 평균, 두 번째 시험의 평균... 4개의 결과가 나옵니다.
열 방향(세로)으로 값들을 모아 계산합니다. 실무에서는 다양한 통계 함수가 사용됩니다.
np.median은 중앙값을, np.var는 분산을, np.min과 np.max는 최솟값과 최댓값을 구합니다. np.percentile로 사분위수도 구할 수 있습니다.
이상치 탐지, 데이터 정규화, 품질 관리 등 다양한 분야에서 활용됩니다. 주의할 점도 있습니다.
NaN(Not a Number) 값이 있으면 결과도 NaN이 됩니다. 실제 데이터에는 결측치가 많습니다.
이럴 때는 np.nanmean, np.nanstd, np.nansum을 사용하세요. 이 함수들은 NaN을 무시하고 계산합니다.
또 하나 주의할 점은 표준편차의 자유도입니다. np.std의 기본값은 모집단 표준편차(N으로 나눔)입니다.
표본 표준편차(N-1로 나눔)가 필요하면 ddof=1을 지정해야 합니다. 통계학에서 표본 분석할 때는 ddof=1이 올바른 선택입니다.
keepdims 매개변수도 알아두면 좋습니다. True로 설정하면 연산 후에도 차원이 유지됩니다.
브로드캐스팅과 함께 사용할 때 유용합니다. 김개발 씨는 이제 수백만 건의 거래 데이터도 한 줄로 분석할 수 있게 되었습니다.
"axis만 이해하니까 모든 게 쉬워지네요!"
실전 팁
💡 - axis=0은 열 방향(결과 행 수 줄어듦), axis=1은 행 방향(결과 열 수 줄어듦)으로 기억하세요
- 결측치가 있는 실제 데이터에서는 nanmean, nanstd 등 nan 처리 함수를 사용하세요
- 표본 표준편차가 필요하면 np.std(arr, ddof=1)을 사용하세요
5. 조건부 연산과 마스킹
김개발 씨는 센서 데이터를 정제하는 업무를 맡았습니다. 음수 값은 오류이므로 0으로 바꾸고, 100을 넘는 값은 이상치이므로 제거해야 합니다.
"조건문을 써서 하나씩 확인해야 하나..." 머리가 복잡해질 때, 선배가 마스킹을 알려주었습니다.
마스킹은 조건에 맞는 데이터만 선택하거나 변경하는 기법입니다. 마치 스텐실로 원하는 부분만 색칠하는 것처럼, Boolean 배열을 이용해 특정 조건을 만족하는 요소만 골라낼 수 있습니다.
데이터 정제와 필터링에서 가장 자주 사용되는 NumPy의 핵심 기능입니다.
다음 코드를 살펴봅시다.
import numpy as np
# 센서 데이터 (일부 오류값 포함)
data = np.array([23, -5, 45, 120, 67, -2, 89, 150, 34])
# 조건에 맞는 마스크 생성
positive_mask = data > 0
print("양수 마스크:", positive_mask)
# 마스크를 이용한 필터링
positive_data = data[positive_mask]
print("양수만:", positive_data)
# 여러 조건 결합
valid_mask = (data > 0) & (data < 100)
valid_data = data[valid_mask]
print("유효 데이터:", valid_data)
# np.where로 조건부 값 변경
cleaned = np.where(data < 0, 0, data) # 음수면 0, 아니면 원래 값
print("정제된 데이터:", cleaned)
김개발 씨는 IoT 센서에서 들어오는 온도 데이터를 처리하고 있었습니다. 센서 오작동으로 가끔 -999나 999 같은 비정상적인 값이 섞여 들어옵니다.
이런 값들을 걸러내지 않으면 분석 결과가 엉망이 됩니다. 박시니어 씨가 화면을 보며 말했습니다.
"for 루프 쓰지 말고 마스킹을 써봐. 코드가 10배는 짧아질 거야." 마스킹의 원리는 무엇일까요?
비교 연산자를 배열에 적용하면 Boolean 배열이 만들어집니다. data > 0을 실행하면 각 원소가 0보다 큰지 아닌지를 True/False로 담은 배열이 생성됩니다.
이 Boolean 배열을 인덱스로 사용하면 True인 위치의 값만 선택됩니다. 마치 체로 거르는 것과 같습니다.
조건이라는 체에 데이터를 통과시키면 조건을 만족하는 것만 남습니다. 위 코드를 살펴봅시다.
data > 0은 [True, False, True, True, True, False, True, True, True]를 반환합니다. 이것을 data[positive_mask]로 인덱싱하면 True인 위치의 값만 추출됩니다.
여러 조건을 결합할 때는 &(and), |(or), ~(not)를 사용합니다. 주의할 점은 각 조건을 괄호로 감싸야 한다는 것입니다.
연산자 우선순위 때문에 괄호 없이 쓰면 에러가 발생합니다. np.where는 조건부로 값을 선택하거나 변경할 때 사용합니다.
np.where(조건, 참일때값, 거짓일때값) 형태로 사용합니다. 위 코드에서 np.where(data < 0, 0, data)는 음수면 0으로, 아니면 원래 값을 유지합니다.
np.where의 또 다른 용법도 있습니다. 인자를 하나만 주면 조건을 만족하는 인덱스를 반환합니다.
np.where(data > 50)은 50보다 큰 원소의 위치를 알려줍니다. 실무에서 마스킹은 정말 많이 사용됩니다.
이상치 제거, 특정 범주 필터링, 결측치 처리, 조건부 계산 등 데이터 전처리의 거의 모든 작업에 마스킹이 관여합니다. Pandas의 조건부 필터링도 내부적으로 NumPy 마스킹을 사용합니다.
np.clip도 알아두면 좋습니다. 값을 특정 범위로 제한할 때 사용합니다.
np.clip(data, 0, 100)은 0 미만은 0으로, 100 초과는 100으로 바꿉니다. 이미지 처리에서 픽셀 값을 0-255로 제한할 때 자주 사용됩니다.
주의할 점도 있습니다. 마스킹으로 선택한 결과는 원본의 복사본입니다.
data[mask] = 0처럼 대입하면 원본이 변경되지만, filtered = data[mask]로 만든 filtered를 수정해도 원본 data는 바뀌지 않습니다. 또한, 마스킹 결과의 크기는 조건을 만족하는 원소 수에 따라 달라집니다.
원래 배열과 크기가 같을 것이라고 가정하면 안 됩니다. 김개발 씨는 마스킹으로 데이터 정제 코드를 3줄로 줄였습니다.
"이게 바로 벡터 연산의 힘이군요!"
실전 팁
💡 - 여러 조건을 결합할 때는 각 조건을 반드시 괄호로 감싸세요: (a > 0) & (a < 10)
- np.clip은 값의 범위 제한에 매우 편리합니다
- 조건을 만족하는 인덱스가 필요하면 np.where(조건)을 사용하세요
6. 난수 생성 (random 모듈)
김개발 씨는 머신러닝 모델의 테스트 데이터가 필요했습니다. 실제 데이터는 아직 수집 중이라 가상의 데이터를 만들어야 합니다.
"랜덤으로 숫자를 만들면 되지 않을까?" 그런데 매번 실행할 때마다 결과가 달라져서 디버깅이 어려웠습니다.
NumPy의 random 모듈은 다양한 확률 분포에서 난수를 생성합니다. 마치 주사위를 굴리는 것처럼 무작위 값을 만들지만, seed를 설정하면 같은 결과를 재현할 수 있습니다.
시뮬레이션, 테스트 데이터 생성, 모델 초기화 등에 필수적인 기능입니다.
다음 코드를 살펴봅시다.
import numpy as np
# 재현성을 위한 시드 설정
np.random.seed(42)
# 0~1 사이 균등분포 난수 (3x3)
uniform = np.random.rand(3, 3)
print("균등분포:\n", uniform)
# 표준정규분포 난수
normal = np.random.randn(3, 3)
print("정규분포:\n", normal)
# 특정 범위의 정수 난수
integers = np.random.randint(1, 100, size=(3, 3))
print("정수 난수:\n", integers)
# 배열 무작위 섞기
arr = np.arange(10)
np.random.shuffle(arr)
print("섞인 배열:", arr)
# 무작위 샘플링
sample = np.random.choice([10, 20, 30, 40, 50], size=3, replace=False)
print("샘플링:", sample)
김개발 씨는 추천 시스템의 A/B 테스트를 준비하고 있었습니다. 사용자를 무작위로 두 그룹으로 나눠야 하는데, 나중에 결과를 재현할 수 있어야 합니다.
"랜덤인데 어떻게 재현하지?" 고민하던 중 seed의 존재를 알게 되었습니다. 박시니어 씨가 설명했습니다.
"컴퓨터의 난수는 사실 진짜 랜덤이 아니야. 의사난수(pseudo-random)라고 하지.
시드만 같으면 같은 시퀀스가 나와." 난수 생성의 원리를 이해해 봅시다. 컴퓨터는 수학 공식으로 난수처럼 보이는 수열을 만들어냅니다.
이 공식의 시작점이 **시드(seed)**입니다. 같은 시드에서 시작하면 항상 같은 수열이 생성됩니다.
마치 같은 출발점에서 시작하면 같은 길을 걷게 되는 것처럼요. **np.random.seed(42)**에서 42는 특별한 숫자가 아닙니다.
관례적으로 많이 사용될 뿐입니다. 중요한 것은 같은 시드를 사용하면 같은 결과를 얻을 수 있다는 점입니다.
다양한 난수 생성 함수를 살펴봅시다. rand는 0과 1 사이의 균등분포 난수를 생성합니다.
모든 값이 동일한 확률로 나옵니다. rand(3, 3)은 3x3 행렬을 만듭니다.
randn은 표준정규분포(평균 0, 표준편차 1)에서 난수를 생성합니다. 0 근처의 값이 많이 나오고, 멀어질수록 적게 나옵니다.
신경망의 가중치 초기화에 자주 사용됩니다. randint는 정수 난수를 생성합니다.
randint(1, 100, size=(3, 3))은 1 이상 100 미만의 정수로 3x3 행렬을 만듭니다. 주의할 점은 상한값은 포함되지 않는다는 것입니다.
shuffle은 배열을 제자리에서 섞습니다. 원본 배열이 변경되므로 주의하세요.
복사본을 섞고 싶으면 np.random.permutation을 사용하세요. choice는 주어진 배열에서 무작위로 선택합니다.
replace=False면 중복 없이, replace=True(기본값)면 중복을 허용하여 선택합니다. p 매개변수로 각 원소의 선택 확률을 지정할 수도 있습니다.
실무에서 난수는 다양하게 활용됩니다. 데이터셋을 학습/검증/테스트로 분할할 때, 가중치를 초기화할 때, 드롭아웃을 적용할 때, 시뮬레이션을 돌릴 때 모두 난수가 필요합니다.
시드를 기록해두면 나중에 실험을 정확히 재현할 수 있습니다. 최신 NumPy에서는 Generator 방식이 권장됩니다.
np.random.default_rng(42)로 생성기를 만들고, rng.random(), rng.integers() 등을 사용합니다. 전역 상태에 의존하지 않아 더 안전하고 병렬 처리에도 유리합니다.
주의할 점도 있습니다. 시드를 설정하지 않으면 매 실행마다 다른 결과가 나옵니다.
개발 중에는 시드를 고정하고, 배포 시에는 상황에 따라 결정하세요. 보안이 중요한 암호화에는 numpy.random 대신 secrets 모듈을 사용해야 합니다.
김개발 씨는 시드를 설정하여 A/B 테스트의 그룹 분할을 재현 가능하게 만들었습니다. "이제 결과가 이상해도 정확히 같은 상황을 재현할 수 있겠네요!"
실전 팁
💡 - 재현성이 중요한 실험에서는 항상 시드를 설정하고 기록해두세요
- 최신 코드에서는 np.random.default_rng() 방식을 사용하는 것이 권장됩니다
- 보안이 중요한 경우에는 secrets 모듈을 사용하세요, numpy.random은 암호화에 적합하지 않습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
vLLM 통합 완벽 가이드
대규모 언어 모델 추론을 획기적으로 가속화하는 vLLM의 설치부터 실전 서비스 구축까지 다룹니다. PagedAttention과 연속 배칭 기술로 GPU 메모리를 효율적으로 활용하는 방법을 배웁니다.
Web UI Demo 구축 완벽 가이드
Gradio를 활용하여 머신러닝 모델과 AI 서비스를 위한 웹 인터페이스를 구축하는 방법을 다룹니다. 코드 몇 줄만으로 전문적인 데모 페이지를 만들고 배포하는 과정을 초급자도 쉽게 따라할 수 있도록 설명합니다.
Sandboxing & Execution Control 완벽 가이드
AI 에이전트가 코드를 실행할 때 반드시 필요한 보안 기술인 샌드박싱과 실행 제어에 대해 알아봅니다. 격리된 환경에서 안전하게 코드를 실행하고, 악성 동작을 탐지하는 방법을 단계별로 설명합니다.
Voice Design then Clone 워크플로우 완벽 가이드
AI 음성 합성에서 일관된 캐릭터 음성을 만드는 Voice Design then Clone 워크플로우를 설명합니다. 참조 음성 생성부터 재사용 가능한 캐릭터 구축까지 실무 활용법을 다룹니다.
Tool Use 완벽 가이드 - Shell, Browser, DB 실전 활용
AI 에이전트가 외부 도구를 활용하여 셸 명령어 실행, 브라우저 자동화, 데이터베이스 접근 등을 수행하는 방법을 배웁니다. 실무에서 바로 적용할 수 있는 패턴과 베스트 프랙티스를 담았습니다.