TIL/[TIL]

[TIL]NumPy 심화, 영화 추천 실습, GPT 기초 구조

namerong 2026. 7. 16. 15:59

이번 학습에서는 NumPy 배열을 더 깊게 다루는 방법과 벡터 유사도를 활용한 간단한 추천 시스템, GPT의 기본 동작 개념을 정리했다.
이전에는 NumPy 배열 생성과 기본 속성을 봤다면, 이번에는 인덱싱, 슬라이싱, 연산, reshape, 집계, 벡터 유사도처럼 실제 데이터 분석과 AI 기초에 연결되는 내용을 다뤘다.

1. NumPy 인덱싱과 슬라이싱

NumPy 배열도 Python list처럼 인덱싱과 슬라이싱이 가능하다.

1차원 배열에서는 위치 번호로 값을 꺼낼 수 있다.

scores = np.array([90, 80, 75, 100, 85])

scores[0]
scores[-1]

2차원 배열에서는 [행, 열] 형태로 접근한다.

scores = np.array([
    [90, 80, 50],
    [85, 95, 75],
    [100, 90, 80]
])

scores[0, 0]
scores[1, 2]

슬라이싱에서는 :가 전체 범위를 의미한다.

students[0, :]   # 첫 번째 행 전체
students[:, 0]   # 첫 번째 열 전체
students[:, 1:]  # 두 번째 열부터 끝까지

또한 원하는 위치만 골라 가져오는 Fancy Indexing과 조건에 맞는 값만 가져오는 Boolean Indexing도 사용했다.

numbers[[1, 3, 5]]
scores[scores >= 80]
numbers[numbers % 2 == 0]

Boolean Indexing은 조건 검색이나 필터링에 자주 쓰인다.


2. NumPy 배열 연산

NumPy 배열은 list와 다르게 요소별 연산이 가능하다.

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

a + b
a - b
a * b
a / b

Python list에서 +는 리스트 연결이지만, NumPy 배열에서 +는 같은 위치의 값끼리 더한다.

스칼라 연산도 가능하다.

scores = np.array([80, 90, 70])

scores + 10
scores * 2
scores / 10

배열 전체에 같은 값을 한 번에 적용할 수 있어 반복문 없이 계산할 수 있다.


3. Broadcasting

Broadcasting은 모양이 다른 배열끼리도 특정 규칙에 맞으면 자동으로 크기를 맞춰 연산하는 기능이다.

grid = np.array([
    [80, 90, 70],
    [85, 95, 75]
])

bonus = np.array([5, 10, 0])

grid + bonus

grid는 (2, 3)이고 bonus는 (3,)이다.
끝 차원이 같기 때문에 각 행에 bonus가 적용된다.

weight = np.array([[1], [2]])
grid * weight

weight는 (2, 1)이라 각 행마다 다른 가중치를 줄 수 있다.

Broadcasting 규칙은 뒤쪽 차원부터 비교했을 때

  • 차원이 같거나
  • 둘 중 하나가 1이면

연산이 가능하다고 이해하면 된다.


4. reshape, flatten, ravel, transpose

reshape()는 배열의 모양을 바꾼다.

numbers = np.arange(12)

numbers.reshape(3, 4)
numbers.reshape(2, 6)

중요한 점은 전체 요소 개수가 맞아야 한다는 것이다.
12개 요소를 3 x 4, 2 x 6으로 바꿀 수는 있지만 3 x 5로는 바꿀 수 없다.

-1을 사용하면 해당 차원을 NumPy가 자동 계산한다.

numbers.reshape(3, -1)
numbers.reshape(-1, 2)

다차원 배열을 1차원으로 펼칠 때는 flatten() 또는 ravel()을 사용한다.

matrix.flatten()
matrix.ravel()

차이는 원본 영향 여부다.

  • flatten()은 새 배열을 만든다.
  • ravel()은 가능한 경우 원본 메모리를 공유한다.

행과 열을 바꾸는 전치는 transpose() 또는 .T를 사용한다.

matrix.transpose()
matrix.T

5. 집계 함수와 axis

NumPy는 합계, 평균, 최대값, 최소값 등을 쉽게 구할 수 있다.

scores.sum()
scores.mean()
scores.max()
scores.min()

최대값과 최소값의 위치는 argmax(), argmin()으로 구한다.

scores.argmax()
scores.argmin()

2차원 배열에서는 axis가 중요하다.

scores.mean(axis=0)
scores.mean(axis=1)
  • axis=0: 세로 방향 계산, 열별 평균
  • axis=1: 가로 방향 계산, 행별 평균

성적표 예시로 보면 axis=0은 과목별 평균, axis=1은 학생별 평균이 된다.

표준편차는 std()로 구한다.

scores.std()

표준편차가 클수록 값들이 평균에서 멀리 흩어져 있다는 의미다.


6. 벡터, 내적, Norm

NumPy에서 1차원 배열은 벡터처럼 사용할 수 있다.

v = np.array([1, 2, 3])

벡터의 크기는 Norm이라고 하며 np.linalg.norm()으로 구한다.

np.linalg.norm([3, 4])

[3, 4]의 크기는 피타고라스 계산으로 5가 된다.

내적은 같은 위치끼리 곱한 뒤 모두 더하는 연산이다.

a = np.array([1, 2, 3])
b = np.array([3, 4, 5])

np.dot(a, b)
a @ b

벡터 유사도, 추천 시스템, attention 계산에서 자주 등장하는 개념이다.


7. 코사인 유사도

코사인 유사도는 두 벡터가 얼마나 비슷한 방향을 바라보는지 계산한다.

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

공식은 다음과 같다.

cos(A, B) = (A · B) / (|A| * |B|)

결과가 1에 가까우면 방향이 비슷하고, 0에 가까우면 관련성이 낮고, -1에 가까우면 반대 방향이다.

단순 내적은 값의 크기에 영향을 크게 받는다.
반면 코사인 유사도는 벡터의 크기보다 방향을 보기 때문에 [1, 1]과 [100, 100]도 같은 방향이면 유사도가 1에 가깝게 나온다.


8. 영화 추천 실습

영화 추천 실습에서는 영화의 특징과 사용자의 취향을 벡터로 표현했다.

# [액션, 스릴러, 코미디, 감동]
movies = np.array([
    [1, 1, 0, 0],
    [1, 0, 1, 1],
    [0, 0, 0, 1],
    [0, 0, 1, 1]
])

사용자도 같은 기준으로 입력을 받아 벡터로 만들었다.

user = np.array([
    action,
    thriller,
    comedy,
    emotion
])

이후 사용자 벡터와 각 영화 벡터의 코사인 유사도를 계산하고, 가장 높은 점수를 가진 영화를 추천했다.

index = np.argmax(scores)

이 실습에서 중요한 점은 “추천”이라는 기능도 결국 데이터를 숫자 벡터로 바꾸고, 벡터 간 유사도를 계산하는 방식으로 만들 수 있다는 점이다.


9. GPT 기초 구조

GPT 기초에서는 token, embedding, attention, softmax 흐름을 간단히 구현했다.

Token

Token은 문장을 모델이 처리할 수 있는 단위로 나눈 것이다.

def tokenize(text):
    return text.split()

실제 GPT의 토큰화는 훨씬 복잡하지만, 기본 개념은 문장을 작은 단위로 쪼개는 것이다.

Embedding

Embedding은 단어를 숫자 벡터로 표현하는 것이다.

embeddings = {
    "사과": np.array([0.9, 0.1, 0.2]),
    "바나나": np.array([0.8, 0.1, 0.3]),
    "자동차": np.array([0.1, 0.9, 0.7])
}

단어를 숫자 벡터로 바꾸면 단어끼리 유사도 계산을 할 수 있다.
예를 들어 사과와 바나나는 자동차보다 더 비슷한 방향의 벡터로 표현될 수 있다.

Attention

Attention은 현재 관심 있는 정보와 관련 있는 토큰에 더 높은 가중치를 주는 방식이다.

구성 요소는 크게 세 가지다.

구분 의미
Query 지금 찾고 싶은 정보
Key 각 토큰이 가진 특징
Value 실제로 가져올 정보

흐름은 다음과 같다.

Query와 Key를 비교해 관련도 점수 계산
-> softmax로 가중치 변환
-> 가중치만큼 Value를 섞어 context 생성

즉 attention은 문장 안에서 어떤 정보에 더 집중할지 계산하는 구조라고 볼 수 있다.

Softmax

Softmax는 여러 점수를 확률처럼 바꾸는 함수다.

def softmax(scores):
    shifted = scores - np.max(scores)
    exp_scores = np.exp(shifted)
    return exp_scores / exp_scores.sum()

점수가 높을수록 더 큰 확률을 갖게 된다.
다음 토큰 예측에서도 후보 토큰들의 점수를 softmax로 바꾼 뒤 가장 가능성이 높은 토큰을 고를 수 있다.


10. 간단한 챗봇 실습

OpenAI API를 사용해 간단한 챗봇 구조도 작성했다.

핵심 흐름은 다음과 같다.

.env에서 API Key 로드
-> 사용자 입력 받기
-> 대화 기록에 user 메시지 추가
-> system prompt와 최근 대화 기록을 API에 전달
-> 응답을 출력하고 assistant 메시지로 저장

대화 기록은 계속 무한히 보내지 않고 최근 10개만 사용하도록 했다.

messages = [{"role": "system", "content": system_prompt}] + history[-10:]

이렇게 하면 토큰 사용량을 줄이면서도 간단한 대화 맥락은 유지할 수 있다.


11. 핵심 정리

  1. NumPy 배열은 인덱싱, 슬라이싱, Fancy Indexing, Boolean Indexing으로 원하는 데이터를 뽑을 수 있다.
  2. NumPy 배열 연산은 요소별로 동작하며, 반복문 없이 대량 계산이 가능하다.
  3. Broadcasting은 배열 모양이 달라도 규칙에 맞으면 자동으로 계산해준다.
  4. reshape()는 배열 모양을 바꾸며, 전체 요소 개수가 맞아야 한다.
  5. flatten()은 새 배열을 만들고, ravel()은 가능한 경우 원본 메모리를 공유한다.
  6. axis=0은 열 방향, axis=1은 행 방향 계산으로 이해할 수 있다.
  7. 벡터의 크기는 Norm, 벡터 간 곱의 합은 내적이다.
  8. 코사인 유사도는 두 벡터의 방향이 얼마나 비슷한지 계산한다.
  9. 영화 추천 실습은 사용자 취향과 영화 특징을 벡터로 표현하고 유사도를 계산하는 방식이었다.
  10. GPT는 문장을 토큰으로 나누고, 토큰을 벡터로 바꾸며, attention으로 중요한 정보를 가중합한다.
  11. Softmax는 점수를 확률처럼 바꿔 다음 토큰 선택 등에 활용된다.