본문 바로가기
ML/Tensorflow

[tensorflow, numpy] Tensor 데이터 구조 - 1

by 리나그(ReenAG) 2025. 12. 6.
728x90
반응형

 내 기술 스택에 있어서 크게 변화가 있었던 건 3학년 1학기의 중간고사 전쯤이었을 것이다. 아... 그렇게 보니 정말 오래되긴 했구나. 그때  학부연구생으로 지원을 했다. 거기서 머신러닝과 관련된 지식을 계속 학습을 하고 있다. 처음에는 "AI 같은 어려운걸 내가 이해할 수 있을까? 난 그렇게 까지 머리가 좋진 않은데..."라고 생각했다. 다만 걱정한 것 치고는 재미있는 주제였다.

 

 tensorflow는 구글에서 만든 Python라이브러리로, Graph를 미리 만들어두는 특성이 있는, ML을 위한 라이브러리이다. 이번 기초기계학습 시간에 이것에 대해서 많이 배웠고, 학부연구생하면서 배운 것도 있다. 오늘은 계속 학기 중에 배운 것을 정리하면서 기말고사를 대비하고자 한다.

 

  우선 keras에 관한 이야기도 빼놓을 수 없다. 원래 tensorflow 와 keras는 서로 별개의 라이브러리였고, tensorflow가 기본적인 것들을 지원했다면 keras는 이것을 이용한 고수준 API(Layer라던가, Model 같은 큼직한 것들을 간단히 선언만으로 사용)였다. 언제인지 구체적으로 기억은 안 나는데 tensorflow가 keras를 공식적인 고수준 API로 채택하고, keras도 tensorflow를 유일한 공식 back-end로 채택하면서 Merge 되어버린 녀석이다.

 

 중간에 

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(512, activation="relu"),
    layers.Dense(10, activation="softmax")
])

해서 "keras"라는 이름이 보인다면 다 그런 이유가 있는 것이다.

 

 "오 텐서플로우 좋네요"라고 생각할 수도 있는데, 음 정작 대세는 "pytorch"라는 라이브러리라고 한다. 둘 모두 graph를 이용하되, tensorflow는 미리 만들어 두는 걸 선호하고(eager), pytorch는 실행할 때 알아서 만드는 걸 선호한다. 옛적의 1.0 tensorflow는 이런 그래프가 아예 중심이 될 만큼 큰 역할을 담당했는데, 요즘 2.0부터는 엔진에 내장되어서 알아서 생성한다. 둘 다 서로에게 이런저런 영향을 주고받는 머신러닝 라이브러리라고 생각하면 될 성싶다. (pytorch도 2.0 이후로 빨라지고, tensorflow에 비해서 직접 제어가 더 간편한 경향이 있어서 연구자들이 더 많이 쓰는 것 같다.)

 

 하지만 이렇게 정작 설명을 해놓고 나서 오늘 주로 다룰 것은 "numpy"이다. 이유는 tensorflow의 "tensor"가 이러한 numpy의 와의 호환성을 중요하게 생각하고 있기도 하거니와 기초적인 행렬 연산을 설명하기 편하기 때문이다. 결론은 기초의 기초를 학습하기 위해서라고 할 수 있겠다.

 

 1. 스칼라 & 벡터 & 행렬 & 텐서

일부러 스칼라 -> 벡터 -> 행렬 > 텐서 순으로 적어 두었다. 눈치 빠른 사람들이라면 이미 가면 갈수록 차원이 늘어나는 순으로 정리되었다고 생각할 것이다.

실제로 아래와 일맥 상통하다 :

 

스칼라 = Rank-0 텐서

벡터 = Rank-1 텐서

행렬 = Rank-2 텐서

 

텐서는 예외적으로 이 전체를 포괄할 수 있는 개념이라고 할 수 있겠다. 문제는 이게 말장난을 하기 좋다는 건데, rank-2인 텐서 = 행렬이나 마찬가지라서 좀 쉽게 풀어서 쓸 때는 행렬로 바꿔줄 필요가 있다.

Rank-2인 텐서를 Input으로 받아 Convolution Layer에 적용, 다른...

말을 들으면 뭔지 이해가 안 될 수도 있는걸 그냥 머릿속에서 행렬로 바꾸면 아, 그냥 행렬값 계산이구나, Convolution이구나, 행렬곱이구나으로 생각하기 편하다.

 

스칼라는 

\begin{bmatrix}
0
\end{bmatrix}

벡터는

\begin{bmatrix}
0 & \cdots & 0
\end{bmatrix}

행렬은

\begin{bmatrix}
0 & \cdots & 0 \\
\vdots & \ddots & \vdots \\
0 & \cdots & 0
\end{bmatrix}

이런 식이다. 

rank-3 이후부터는 특별한 이름은 없고 그냥 rank-n인 텐서~ 이런 식으로 부른다.

강의록 어딘가...

2. numpy에서 표현

# Rank 0: 스칼라 (scalar)
tensor0 = np.array(7)

# Rank 1: 벡터 (vector)
tensor1 = np.array([1.2, -3.4, 5.6])

# Rank 2: 행렬 (matrix)
tensor2 = np.array([
    [1, 2, 3],
    [4, 5, 6]
])

# Rank 3: 3차원 텐서 (예: 이미지 2개(2), 높이 3, 너비 3)
tensor3 = np.random.randint(0, 10, size=(2, 3, 3))

주피터에서 실행하면 이렇게 된다. array를 통해서 직접 지정하거나, random을 통해서 대충 만들 수도 있다.

3. MNIST로 찍먹

조금이라도 머신러닝에 관해서 배웠다면 안 들어 볼 수 없는 바로 그! 데이터셋이다. NIST라는 미국국립표준기술연구소(하여간에 연구소임)에서 만든 데이터 셋이다.

 

우선 로딩 부터하자. 만약 colab에서 실행하면 바로 될 거고, jupyter를 깔아보는 것도 도움이 되므로 해보는 걸 추천한다.

from tensorflow.keras.datasets import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

 

그런 다음 이걸 또 실행해 주자 :

import matplotlib.pyplot as plt

digit = train_images[12]
plt.imshow(digit, cmap=plt.cm.binary)
plt.show()

대충 "뭔가 원하는 정보를 담고 있긴 한데 바로 전산화하긴 곤란한 거" -> "실제 타깃"으로 이루어진 게 기본적인 머신러닝에 필요한 데이터들이다. images에는 인간이 보기엔 누가 봐도 "3"이 적힌 이미지가 있지만, 이걸 인식하는 python 코드를 짜려고 들면 아주 귀찮을 것이다. 머신 러닝은 그런 문제를 풀기 적합하다. 문제 -> 정답 세트가 있으면 더 좋다. 아래의 labels에는 어떤 사람이 친절하게 "자, 정답은 3이야"라고 입력을 해둔 정답들이 모여있다. 여기서는 13번째 데이터를 가져왔을 뿐이지만, 이런 게 70000개는 있는 게 MNIST이다. 

 

4. MNIST를 갈라버리기

이런 numpy의 텐서들은 익히 여러분들이 아는 python의 "slice"연산도 지원한다. 여기선 13번째 이미지부터 15번째 이미지까지, 3개의 이미지만 떠먹어 보자.

import matplotlib.pyplot as plt

my_slice = train_images[12:15]
fig, axes = plt.subplots(1, 3, figsize=(12, 4))

for i in range(len(my_slice)):
    curr_img = my_slice[i]
    axes[i].imshow(curr_img, cmap=plt.cm.binary)
    axes[i].set_title(f"Image {i}")
    axes[i].axis("off")


plt.show()

잘 가져와진 듯하다. 좀 더 고급진 방법으로는 여기서 이미지 자체를 slice 하는 방법도 있을 것이다.

import matplotlib.pyplot as plt

my_slice = train_images[12:15, 3:17, 12:25]
fig, axes = plt.subplots(1, 3, figsize=(12, 4))

for i in range(len(my_slice)):
    curr_img = my_slice[i]
    axes[i].imshow(curr_img, cmap=plt.cm.binary)
    axes[i].set_title(f"Image {i}")
    axes[i].axis("off")


plt.show()

 

중요한 부분은 여기 :

my_slice = train_images[12:15, 3:17, 12:25]

이 부분이다. 보면 이미지 중에서 일정 부분을 잘라서 가져온 것처럼 되었고, 실제로도 그렇게 된다. x축으로 보면 3~16픽셀, y축으로 보면 12~24픽셀까지만 포함해서 가져온 셈이다. slice연산을 할 때 기본적으로 앞의 숫자는 포함, 뒤의 숫자는 미포함하는 구간까지 가져오는 걸 명심하자.

 

방금 전처럼 특정한 범위를 지정하지 않게 되는 slice는 다음과도 같다.

#이 둘은 똑같은 표현임
my_slice = train_images[12:15]
my_slice = train_images[12:15, :, :]

빈 곳에는 자동적으로 가능한 최솟값(0), 최댓값(length - 1)이 들어갈 것이다. 음수 인덱스도 마찬가지로 사용 가능하다.

 

5. channel-last vs channel-first

tensorflow에서는 channel-last 방식(NHWC)을 이용한다고 한다. 이게 뭔 소리냐, 

이런 텐서의 shape를 우린 (4, 3, 2)로 표현하지 않고, (2, 3, 4)로 표현하겠다는 거다. 순서만 바뀐 거 아니냐고? 맞다! 반대로 (4, 3, 2)같이 표기하는 방식을 channel-first 방식(NCWH)이라고 한다. 둘의 차이는 제일 앞에 제일 고차원의 입력정보가 오는가?, 혹은 제일 저차원의 입력정보가 오는가?이다. 메모리에 MSB(Most Significant Bit), LSB(Least Significant Bit)가 어떤 순서로 저장되는가와 비슷하다고 생각하면 된다. 처음 보면 앞에다 늘어나는 걸 붙일 수 있게 (4, 3, 2)가 자연스러워 보일 수도 있지만, MSB랑 비슷하네? 하고 생각하면 이게 훨씬 자연스럽다는 걸 알 수 있다.

 

 channel-last 방식을 채택하는 실용적인 이유도 있는데, MNIST 같은 이미지 데이터도 결국 (Height, Width, Channel) 형태로 디코딩되는 것도 있다. 그냥 그대로 따라가면 reshape비용을 날릴 수 있다. 또 NVIDIA에서 제공하는 CUDA의 최신 커널 자체가 이걸 더 지원하는 것으로 보인다. GPU가 그렇다는데 따르는 게 머리가 덜 아프다.

 

다만! 전에는 channel-first 한 방식도 더 많이 쓰였다. pytorch는 아직 이 방식이 default라고 하니, 둘 중 하나가 꼭 좋다고 하긴 어려운 것이다!(그니까 그냥 환경 차이임)

6. Broadcasting

broadcasting은 개념 자체는 매우 간단하다. 작은 텐서가 있고 그것보다는 차원 수가 많은 큰 텐서가 있다고 하자. 작은 텐서의 값을 이용해서 큰 텐서에 더하기, 빼기, 곱하기 등의 연산을 하고 싶을 수 있다. 하지만 서로 다른 차원의 텐서는 원칙적으로 연산할 수 없기 때문에, 복붙을 이용해서 작은 텐서를 늘려버리는 것이다.

 

예시는 다음과 같다.

y의 shape를 우선 늘려준다.

X에 y의 값을 써서 더하고 싶다. 그럼 원래 shape가 (10, )인 벡터는 (32, 10)인 행렬에 더할 수는 없으므로, 일단 행렬로 만들어준다. expand_dims를 쓰면 axis 수만큼 늘려준다. 하지만 아직 더할 수는 없다. (1, 10)인 행렬은 (32, 10)인 행렬에 아직 더할 수 없다.

shape가 (32, 10)이 된 모습

그래서 concatenate연산을 서서 axis = 0에 더 붙여준다. 이제 Y의 shape가 같아져서 정상적으로 더할 수 있다.

더하기 성공!

그럼 이렇게 concatenate를 쓰면 되는 것처럼 보이지만... 사실은 그럴 필요 없다. 이걸 봐보자. 

사실은 그냥 됨 ㅋ
심지어는 값도 같다.

여러분은 적어도 shape의 마지막 부분이 같은지만 확인하면 된다. 그럼 우리가 방금 전, concatenate 연산을 이용한 것과 같은 결과의 텐서로 연산을 진행한 것처럼 알아서 처리해 줄 것이다. 오히려 이렇게 하는 것이 메모리도 덜 낭비할 수 있으므로 이렇게 바로 연산을 진행해 주도록 하자. concatenate는 어디까지나 설명을 위해서 진행을 한 것으로 생각하면 된다.

 

shape가 맞지 않으면 이렇게 된다.

 

- 다음글

 

 

[tensorflow, numpy] Tensor 데이터 구조 - 2. 연산

- 이전글 [tensorflow, numpy] Tensor 데이터 구조 - 1내 기술 스택에 있어서 크게 변화가 있었던 건 3학년 1학기의 중간고사 전쯤이었을 것이다. 아... 그렇게 보니 정말 오래되긴 했구나. 그때 학부연구

passingprogram.tistory.com

 

728x90
반응형

'ML > Tensorflow' 카테고리의 다른 글

[tensorflow, numpy] Tensor 데이터 구조 - 2. 연산  (1) 2025.12.06