7 분 소요

☀️공지 사항☀️ 해당 게시글은 SK 쉴더스 루키즈, 생성형AI 활용 사이버보안 전문인력 양성과정 을 수업을 듣고 정리한 글입니다. 자세한 교육 정보는 SK 쉴더스 홈페이지 를 확인해주세요.

# 학습 내용 요약 (25/11/03)

NumPy 기초 강의 노트 (1일차)

오늘부터는 파이썬의 기본 문법을 넘어, 데이터 분석을 배우게 됐습니다. 파이썬이 데이터 과학 분야에서 강력한 언어로 자리 잡게 한 핵심 라이브러리들을 배우게 되는데, 그 첫 번째 주자가 바로 NumPy입니다.

데이터 분석의 전체적인 흐름을 NumPy → Pandas → Matplotlib/Seaborn (시각화) → Streamlit (웹 대시보드) 순서로 공부할 예정입니다.

오늘은 모든 데이터 분석의 기초가 되는 NumPy 배열의 개념과 사용법을 확실히 익히는 것을 목표로 합니다.


1. 왜 파이썬 리스트 대신 NumPy 배열을 사용할까?

학습에 앞서서 “왜 이미 list라는 좋은 자료구조가 있는데 굳이 NumPy의 array를 배워야 할까?”라는 근본적인 질문을 던져볼 수 있습니다.

강사님께서는 대용량의 숫자 데이터를 다룰 때 list는 속도와 메모리 측면에서 비효율적이라고 설명하셨습니다. 반면, NumPy의 배열(ndarray)은 다음과 같은 특징 덕분에 훨씬 효율적입니다.

  • 동일한 자료형: 배열의 모든 요소는 같은 자료형이어야 합니다. 이 제약 덕분에 메모리를 연속된 공간에 효율적으로 할당하고, C언어 수준에서 매우 빠른 연산이 가능해집니다.
  • 고정된 크기: 한번 생성된 배열의 크기는 변경할 수 없습니다.
  • 벡터화 연산: 반복문 없이 배열 전체에 대한 수학적 연산을 한 번에 처리할 수 있어 코드가 간결하고 속도가 매우 빠릅니다.

중요!: NumPy의 핵심은 대용량 숫자 데이터를 적은 메모리로 빠르게 처리하는 것입니다. list가 유연성을 제공한다면, array는 숫자 데이터 처리에 특화된 성능을 제공합니다.


2. NumPy의 핵심: ndarray 배열

NumPy를 사용하기 위해서는 먼저 라이브러리를 import 해야 합니다. 전 세계적으로 np라는 별칭(alias)을 사용하는 것이 관례입니다.

import numpy as np

2.1 배열 생성 및 기본 정보 확인

파이썬 list를 np.array() 함수에 전달하여 간단하게 NumPy 배열을 만들 수 있습니다.

# 파이썬 리스트
lst = [1, 2, 3, 4, 5]

# 리스트를 NumPy 배열로 변환
arr = np.array(lst)

print(f"파이썬 리스트: {lst}, 타입: {type(lst)}")
# 출력: 파이썬 리스트: [1, 2, 3, 4, 5], 타입: <class 'list'>

print(f"NumPy 배열: {arr}, 타입: {type(arr)}")
# 출력: NumPy 배열: [1 2 3 4 5], 타입: <class 'numpy.ndarray'>

노트: 출력 결과에서 list는 쉼표(,)로 구분되지만, NumPy 배열은 공백으로 구분되는 것을 볼 수 있습니다.

배열을 다룰 때는 다음 세 가지 속성을 항상 확인하는 습관을 들이는 것이 좋습니다.

  • shape: 배열의 형태 (예: (5,)는 5개의 요소를 가진 1차원 배열, (2, 3)은 2행 3열의 2차원 배열)
  • ndim: 배열의 차원 수 (number of dimensions)
  • dtype: 배열 요소의 데이터 타입 (예: int64, float64)
def array_info(array):
    """NumPy 배열의 주요 정보를 출력하는 함수"""
    print(f"Shape (형태): {array.shape}")
    print(f"Dimension (차원): {array.ndim}")
    print(f"Data Type (자료형): {array.dtype}")

array_info(arr)
# 출력:
# Shape (형태): (5,)
# Dimension (차원): 1
# Data Type (자료형): int64

3. 핵심 개념: 벡터화(Vectorization) 연산

벡터화는 NumPy를 사용하는 가장 큰 이유 중 하나입니다. 백터화는 반복문을 사용하지 않고 배열의 모든 요소에 대해 연산을 수행하는 기능입니다.

예를 들어, 리스트의 모든 요소에 2를 곱하려면 for 루프가 필요합니다.

lst = [1, 2, 3, 4, 5]
result_list = []
for element in lst:
    result_list.append(element * 2)
# result_list -> [2, 4, 6, 8, 10]

하지만 NumPy 배열을 사용하면, 그냥 배열에 2를 곱하기만 하면 됩니다.

arr = np.array([1, 2, 3, 4, 5])
result_arr = arr * 2
# result_arr -> [ 2  4  6  8 10]

이처럼 코드가 훨씬 간결해지고, 내부적으로는 최적화된 C 코드로 실행되기 때문에 속도도 비교할 수 없을 정도로 빠릅니다. 이러한 벡터화 연산은 사칙연산뿐만 아니라 비교 연산, 논리 연산 등에도 모두 적용됩니다.


4. 조건에 맞는 데이터 선택: 불리언 마스킹과 팬시 인덱싱

4.1. 불리언 마스킹 (Boolean Masking)

배열에 비교 연산을 적용하면, 각 요소가 조건을 만족하는지에 대한 True/False 값을 담은 불리언 배열이 반환됩니다.

arr = np.array([1, 2, 3, 4, 5])
bool_mask = arr > 3
print(bool_mask)
# 출력: [False False False  True  True]

이 불리언 배열을 ‘마스크’처럼 사용하여 원본 배열의 인덱스로 전달하면, True에 해당하는 위치의 값들만 추출할 수 있습니다.

print(arr[bool_mask])
# 출력: [4 5]

# 한 줄로 표현
print(arr[arr > 3])
# 출력: [4 5]

4.2. 팬시 인덱싱 (Fancy Indexing)

팬시 인덱싱은 특정 위치의 값 여러 개를 한 번에 추출하고 싶을 때 사용합니다. 인덱스 번호를 담은 리스트나 NumPy 배열을 인덱스로 전달하면 됩니다.

arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]

# 0, 3, 8번 인덱스의 값을 추출
indices = [0, 3, 8]
print(arr[indices])
# 출력: [0 3 8]

중요!: 불리언 마스킹과 팬시 인덱싱은 데이터 분석에서 원하는 데이터를 필터링하고 추출하는 데 매우 핵심적인 기술입니다.


5. 2차원 배열 (행렬) 다루기

데이터는 대부분 1차원이 아닌 2차원 테이블 형태(행과 열)로 존재합니다. NumPy에서는 리스트의 리스트(list of lists)를 이용해 2차원 배열, 즉 행렬(Matrix)을 생성할 수 있습니다.

list_of_list = [[1, 2, 3], [4, 5, 6]]
arr_2d = np.array(list_of_list)

array_info(arr_2d)
# 출력:
# Shape (형태): (2, 3)  <- 2행 3열
# Dimension (차원): 2
# Data Type (자료형): int64

2차원 배열의 특정 요소에 접근할 때는 arr[행, 열] 형식을 사용합니다.

# 1행 0열의 값 (4)에 접근
print(arr_2d[1, 0])
# 출력: 4

6. 배열의 형태 변환 및 조작

arange와 reshape

  • np.arange(n): 0부터 n-1까지의 정수를 담은 배열을 생성합니다.
  • array.reshape(행, 열): 배열의 형태를 바꿉니다.

이 두 함수를 조합하면 원하는 형태의 배열을 쉽게 만들 수 있습니다.

# 0부터 11까지의 1차원 배열을 3행 4열의 2차원 배열로 변형
arr_reshaped = np.arange(12).reshape(3, 4)
print(arr_reshaped)
# 출력:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

노트: reshape에서 행 또는 열 중 하나의 값에 -1을 넣으면, 다른 차원의 크기에 맞춰 자동으로 계산해줍니다. (예: reshape(3, -1))

6.1. 전치 행렬 (.T)과 flatten

  • array.T: 배열의 행과 열을 뒤집는 전치(Transpose)를 수행합니다.
  • array.flatten(): 다차원 배열을 1차원 배열로 펼칩니다.
arr_2d = np.arange(6).reshape(2, 3)
# [[0 1 2]
#  [3 4 5]]

# 전치 행렬 (2x3 -> 3x2)
print(arr_2d.T)
# [[0 3]
#  [1 4]
#  [2 5]]

# 1차원으로 펼치기
print(arr_2d.flatten())
# [0 1 2 3 4 5]

7. 통계 함수로 데이터 요약하기

NumPy는 다양한 통계 함수를 제공하며, 이들은 대부분 차원 축소(Dimension Reduction) 함수의 역할을 합니다. 즉, 여러 개의 데이터를 하나의 요약된 값으로 만듭니다.

  • np.sum(): 합계
  • np.mean(): 평균
  • np.max() / np.min(): 최댓값 / 최솟값
  • np.std() / np.var(): 표준편차 / 분산

2차원 배열에 이 함수들을 적용할 때 axis 매개변수가 매우 중요합니다.

  • axis=0: 열 방향으로 연산 (각 열의 합, 평균 등)
  • axis=1: 행 방향으로 연산 (각 행의 합, 평균 등)
stats_arr = np.arange(1, 10).reshape(3, 3)
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

# 각 열의 합
print(np.sum(stats_arr, axis=0))
# 출력: [12 15 18]  (1+4+7, 2+5+8, 3+6+9)

# 각 행의 합
print(np.sum(stats_arr, axis=1))
# 출력: [ 6 15 24]  (1+2+3, 4+5+6, 7+8+9)

7.1. argmax, argmin: 값 대신 인덱스를!

np.argmax()와 np.argmin()은 최댓값/최솟값 자체가 아닌, 그 값이 위치한 인덱스를 반환합니다. “어떤 데이터가 가장 큰가?”가 아니라 “어디에 가장 큰 값이 있는가?”를 알고 싶을 때 매우 유용합니다.


보안 노트: np.load 사용 시 주의점

강의에서 직접 다루진 않았지만, NumPy로 외부 파일(.npy, .npz)을 다룰 때 보안상 주의가 필요합니다. 특히 np.load() 함수는 기본적으로 pickle 모듈을 사용하여 객체를 불러올 수 있는데, 악의적으로 조작된 파일은 임의의 코드를 실행하는 보안 취약점으로 이어질 수 있습니다.

# # 악의적인 코드가 포함된 npy 파일을 로드하는 위험한 예시
# # 이 코드는 시스템에 해를 끼칠 수 있으므로 절대 실행하지 마세요.
# np.load('untrusted_file.npy', allow_pickle=True)

중요!: 신뢰할 수 없는 출처의 .npy 파일을 로드할 때는 반드시 allow_pickle=False 옵션을 사용해야 합니다. 이 옵션은 객체 배열의 로드를 막아 잠재적인 위험을 차단합니다.

# 안전한 파일 로드 방식
np.load('untrusted_file.npy', allow_pickle=False)

복합 및 심화 예제: 로그 데이터 분석 및 AI 연동

지금까지 배운 NumPy 개념을 종합하여, 서버 로그 데이터를 분석하고 그 결과를 가상의 AI 서비스에 연동하는 시나리오를 구현해보겠습니다.

1. 데이터 준비

서버에서 수집된 로그 데이터를 NumPy 배열로 시뮬레이션합니다. 각 행은 [타임스탬프, 상태코드, 응답시간, 심각도]를 나타냅니다.

# severity: 0=INFO, 1=WARN, 2=ERROR, 3=CRITICAL
log_data = np.array([
    [1672531201, 200, 150, 0],
    [1672531202, 503, 2500, 3],
    [1672531203, 200, 200, 0],
    [1672531204, 404, 50, 1],
    [1672531205, 500, 1800, 2],
    [1672531206, 200, 120, 0],
    [1672531207, 503, 3000, 3],
])

2. 데이터 분석 함수

불리언 마스킹을 사용해 심각도가 가장 높은 ‘CRITICAL’ (값 3) 로그만 필터링하고, 이들의 발생 횟수와 평균 응답 시간을 계산하는 함수를 작성합니다.

def analyze_critical_logs(logs):
    # 4번째 열(심각도)이 3인 로그만 추출
    critical_mask = logs[:, 3] == 3
    critical_logs = logs[critical_mask]

    if critical_logs.shape[0] > 0:
        # 평균 응답 시간 계산 (벡터화 연산)
        avg_response_time = np.mean(critical_logs[:, 2])
        count = critical_logs.shape[0]
        
        analysis = {
            "level": "CRITICAL",
            "count": count,
            "avg_response_time_ms": round(avg_response_time, 2)
        }
        return analysis
    return {"level": "CRITICAL", "count": 0}

critical_analysis = analyze_critical_logs(log_data)
print(critical_analysis)
# 출력: {'level': 'CRITICAL', 'count': 2, 'avg_response_time_ms': 2750.0}

3. AI 연동 시뮬레이션

분석 결과를 json 형식으로 변환하여 AI 서비스에 보내고, AI의 응답을 받아 처리하는 과정을 시뮬레이션합니다.

import json

# 3.1. 분석 결과를 JSON으로 변환 (AI 요청)
ai_request_payload = json.dumps(critical_analysis, indent=2)
print("---" + " AI 요청 페이로드" + "---")
print(ai_request_payload)

# 3.2. AI가 분석 결과를 JSON으로 응답했다고 가정
ai_response_json = '''
{
  "summary": "Multiple service unavailable (503) errors detected.",
  "risk_level": "High",
  "recommended_actions": [
    "1. Check health of upstream services.",
    "2. Notify on-call engineer."
  ]
}
'''

# 3.3. AI 응답을 파싱하여 후속 조치 실행
incident_report = json.loads(ai_response_json)
print("\n" + "--- AI 분석 기반 후속 조치 ---")
print(f"위험도: {incident_report['risk_level']}")
print("추천 조치:")
for action in incident_report['recommended_actions']:
    print(f"- {action}")

이처럼 NumPy는 대량의 데이터를 효율적으로 분석하고, 그 결과를 다른 시스템(AI, 웹 서비스 등)과 연동하기 위한 기본적이면서도 강력한 도구입니다.

공부 후기

오늘은 데이터 분석의 첫날 수업으로 Numpy에 대해서 자세히 배웠다. 사실 5년 전에 코인 자동 매매를 구현해보겠다고 파이썬 기초 지식도 없이 들이 받았을때 봤었던 기억이 있다..ㅋㅋ(만들긴 했는데 손실만 봐서 포기함..ㅜ). 아무튼 오늘 Numpy에 대해서 배우면서 느낀점은 Numpy가 진짜 데이터 분석의 기초이구나라고 생각됐다. 이거 모르면 데이터 분석 및 시각화는 커녕 데이터 다듬기도 못할 것 같다.

제대로 다루는 건 처음이라 아직 괄호나 점, 쉼표가 많이 헷갈리지만, 계속 연습하고 복습하면서 익숙해지는 수 밖에 없을 것 같다..(아니 괄호 왜케 다양하고 많은거지..ㅠㅠ 그리고 또 어떨땐 점 쓰고 어떨땐 언더바쓰고 너무 헷갈림;;)

수업 때 보면 전공생들이 대답도 너무 잘하고 잘 따라가서 조급한 마음이 들지만,, 비전공자의 끈기를 보여줘야겠다. 모두 같이 화이팅~!

태그: ,

카테고리:

업데이트:

댓글 남기기