🖇 1. 텐서란 무엇인가
🖇 2. 텐서의 차원별 구조
🖇 3. 텐서 생성
🖇 4. 텐서 변환
🖇 5. 텐서 연산
딥러닝을 이해하려면 텐서(Tensor)의 개념부터 정확히 짚고 넘어가야 한다.
텐서는 딥러닝 모델에서 데이터를 표현하고 전달하는 기본 단위로, 스칼라부터 고차원 배열까지 다양한 형태로 존재한다. TensorFlow에서는 텐서를 정의하고 연산하는 방식이 직관적이지만 연산 중 발생할 수 있는 타입 오류나 차원 불일치는 종종 혼란을 야기할 수 있다.
이 글에서는 텐서의 구조와 연산 방식, 자주 사용하는 함수들을 코드와 함께 정리하고, 실제 연산 결과를 통해 동작 원리를 직관적으로 이해하는 데 초점을 맞추었다. 텐서의 기본기가 제대로 잡혀있어야 이후의 모델링과 실험에서 시행착오를 줄일 수 있을 거라 생각한다.
텐서(Tensor)는 데이터 구조이면서 딥러닝 모델의 핵심 데이터 단위로 작동한다.
입력 데이터, 가중치, 출력, 손실값 등 모든 요소가 텐서 형태로 표현되며 이들은 GPU에서의 연산에 최적화되어 빠르고 효율적으로 처리된다.
간단히 말하면 텐서는 수치 데이터를 표현하는 다차원 배열(multidimensional array) 이다. Python의 리스트나 Numpy 배열과 유사하지만 텐서는 딥러닝 연산을 위한 특화 기능과 속성을 포함하고 있다. 일반적으로 수치형 데이터를 저장하고, 동적 크기를 가진다. 텐서 구조를 제대로 이해하는 것은 딥러닝 모델을 자유자재로 구성하고 디버깅하는 데에 꼭 필요하다.

Rank: 축(axis)의 개수, 즉 차원 수를 의미한다. 예를 들어 0D는 스칼라, 1D는 벡터, 2D는 행렬, 3D는 시계열 데이터, 4D는 이미지 등이다.
Shape: 각 축의 크기를 나타내는 튜플이다. 예를 들어 (3, 4)는 3행 4열의 2D 텐서를 뜻한다.
Data Type (dtype): 저장된 값의 자료형으로 float32, int32, bool, string 등 다양한 타입이 존재한다.
텐서를 제대로 이해하기 위해서는 다양한 차원의 구조를 직접 눈으로 보고 경험하는 것이 필요하다.
텐서의 차원 수는 곧 데이터의 구조를 의미한다. 데이터가 어떤 형태를 가지고 있는지 이해하려면 각 차원의 의미를 알아야 한다.
아래는 차원별 텐서의 구조와 예시를 코드와 살펴본 것이다.
# 0D 텐서 (스칼라)
scalar = tf.constant(7)
print(scalar.shape) # 출력: ()
# 1D 텐서 (벡터)
vector = tf.constant([1.0, 2.0, 3.0])
print(vector.shape) # 출력: (3,)
# 2D 텐서 (행렬)
matrix = tf.constant([[1, 2, 3], [4, 5, 6]])
print(matrix.shape) # 출력: (2, 3)
# 3D 텐서 (시계열 데이터)
time_series = tf.random.normal(shape=(10, 5, 2)) # 10개 샘플, 5개의 타임스텝, 2개 특성
print(time_series.shape) # 출력: (10, 5, 2)
# 4D 텐서 (이미지 배치)
images = tf.random.normal(shape=(32, 64, 64, 3)) # 32개의 RGB 이미지
print(images.shape) # 출력: (32, 64, 64, 3)
# 5D 텐서 (비디오)
videos = tf.random.normal(shape=(16, 10, 64, 64, 3)) # 16개 비디오, 각 10프레임, 64x64 RGB
print(videos.shape) # 출력: (16, 10, 64, 64, 3)
이처럼 차원이 올라갈수록 더 복잡한 데이터를 표현할 수 있고, 실제 모델에서 자주 사용되는 구조도 함께 익혀두는 것이 좋다. 아래에서 각 차원별 텐서 구조의 특징을 조금 더 자세히 살펴보자.
tf.constant(1)t0 = tf.constant(1)
print(t0) # 출력: 1
print(tf.rank(t0)) # 출력: 0
tf.constant([1, 2, 3])t1 = tf.constant([1, 2, 3])
print(t1)
print(tf.rank(t1)) # 출력: 1
t2 = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(t2)
print(tf.rank(t2)) # 출력: 2
t3 = tf.constant([[[1, 1], [2, 2]], [[3, 3], [4, 4]]])
print(t3)
print(tf.rank(t3)) # 출력: 3
tf.constant()는 텐서를 생성하는 가장 기본적인 방법으로, 초기값을 고정된 값으로 설정할 때 사용된다.
데이터 타입(dtype)을 명시하면 연산 중 타입 불일치를 방지할 수 있고, 모델의 메모리 효율성도 조절할 수 있다.
# 정수형 텐서 (기본: int32)
i = tf.constant(2)
print(i)
# 실수형 텐서 (기본: float32)
f = tf.constant(2.)
print(f)
# 문자열 텐서
s = tf.constant("Suan")
print(s)
# dtype 명시 생성
f16 = tf.constant(2., dtype=tf.float16)
print(f16)
i8 = tf.constant(2, dtype=tf.int8)
print(i8)
텐서의 타입
Data Type TF Type 설명 DT_FLOAT tf.float32 32비트 부동 소수 DT_FLOAT64 tf.float64 64비트 부동 소수 DT_INT8 tf.int8 8비트 정수 DT_INT32 tf.int32 32비트 정수 (기본값) DT_UINT8 tf.uint8 부호 없는 8비트 정수 DT_STRING tf.string 문자열 (바이트 배열 형태) DT_BOOL tf.bool 불리언 타입 DT_COMPLEX64 tf.complex64 복소수 (실수+허수 각 32비트) DT_QINT8 tf.qint8 정수형 양자화 연산용 DT_QUINT8 tf.quint8 부호 없는 양자화 정수
tf.cast()는 텐서의 데이터 타입을 변환할 때 사용된다.
int와 float 타입을 더하려고 할 때 오류가 발생할 수 있는데, 이때 타입을 맞춰주는 것이 중요하다.tf.float32, tf.int32 등 원하는 타입으로 명시적 변환이 필요하다.f32 = tf.cast(f16, tf.float32)
print(f32)
tf.reshape()은 텐서의 전체 원소 수를 유지하면서 형태만 바꿔준다.
Flatten하거나, RNN에 넣기 위해 (batch, time, feature) 형태로 바꿀 때 유용하다.x = tf.constant([[1], [2], [3]])
print(x.shape)
y = tf.reshape(x, [1, 3])
print(y)
tf.transpose()는 행과 열 또는 다차원 텐서의 축을 뒤집을 때 사용된다.
print(tf.transpose(y))
tf.squeeze()는 차원 중 1인 차원을 제거한다.
반대로, tf.expand_dims()는 새로운 축을 추가한다.
(28, 28) 이미지를 CNN에 넣기 위해 (28, 28, 1)로 확장하는 것이 대표적이다.print(tf.squeeze([[1], [2]]))
print(tf.expand_dims([1, 2], axis=0))
print(tf.expand_dims([1, 2], axis=1))
tf.split()은 하나의 텐서를 여러 조각으로 나누고,
tf.concat()은 여러 텐서를 하나로 붙일 수 있다.
print(tf.split(x, 3)) # 3개로 분리
print(tf.concat([x, x], axis=0)) # 연결 (행 방향)
print(tf.concat([x, x], axis=1)) # 연결 (열 방향)
딥러닝 모델은 텐서를 중심으로 구성되기 때문에 텐서 연산은 데이터를 변환하고 모델을 구성하는 데 중요하게 작용한다.
0차원(스칼라) 텐서 간 연산은 Python의 기본 수치 연산과 매우 유사하게 동작한다. 연산 결과 역시 0D 스칼라로 반환되고, 텐서플로우 내부에서는 모두 tf.Tensor 객체로 처리된다.
print(tf.constant(2) + tf.constant(2))
print(tf.constant(2) - tf.constant(2))
print(tf.multiply(2, 3))
print(tf.divide(4, 2))
TensorFlow는 타입이 다른 텐서 간 연산을 자동으로 허용하지 않기 때문에 연산 전 tf.cast()로 명시적 형 변환이 필요히다. 모델 구현 중에서도 정수형 인덱스와 실수형 확률값을 혼용할 경우 이런 오류가 자주 발생한다.
# 오류: int + float
# tf.constant(2) + tf.constant(2.2)
# 해결: 타입 캐스팅
print(tf.cast(tf.constant(2), tf.float32) + tf.constant(2.2))
연산 대상이 2D 텐서인 경우, 같은 위치에 있는 원소끼리 연산이 수행된다. 단, 두 텐서의 shape이 다르더라도 연산이 가능한 경우가 있는데 이를 브로드캐스팅(broadcasting) 이라고 한다.
(2, 3) 텐서와 (1, 3) 텐서를 더하면 (2, 3) 형태로 자동 확장되어 연산된다.# 2D 텐서
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])
print(a + b)
print(a - b)
print(a * b)
print(a @ b) # 행렬곱
print(a / b)
Python 연산자(+, -, *, /, @)를 직접 사용해도 되지만, 동일한 기능을 수행하는 TensorFlow의 함수형 API(tf.add(), tf.matmul() 등)를 사용하는 것이 가독성과 확장성 측면에서 유리할 때가 많다.
print(tf.add(a, b))
print(tf.subtract(a, b))
print(tf.multiply(a, b))
print(tf.matmul(a, b))
print(tf.divide(a, b))
x = tf.constant([[4.0, 5.0, 6.0], [10.0, 9.0, 8.0]])
print(tf.reduce_max(x)) # 최대값
print(tf.argmax(x)) # 최대값의 위치
print(tf.nn.softmax(x)) # 소프트맥스 결과
텐서 함수 정리
복잡한 모델을 구현할 때 텐서의 모양을 바꾸거나 데이터를 나누고 붙이는 작업이 빈번하게 발생한다. 이때 유용하게 사용되는 주요 함수들이다.
함수 설명 tf.shape 텐서 구조 확인 tf.size 전체 원소 수 확인 tf.rank 차원 수 확인 tf.reshape 형상 재구성 tf.squeeze 1인 차원 제거 tf.expand_dims 축 추가 tf.slice 슬라이싱 tf.split 분할 tf.concat 연결 tf.tile 반복 생성 tf.reverse 역순 배열 tf.transpose 전치 tf.gather 인덱싱 수집
텐서 연산 정리
앞서 실습한 텐서 연산들을 기호별로 정리하였다.
연산 기호 함수 설명 +add() 더하기 -subtract() 빼기 *multiply() 곱하기 /divide() 나누기 @matmul() 행렬곱 reduce_max() 최대값 argmax() 최대값 위치
TensorFlow의 텐서 구조와 연산을 실습하면서 내부 작동 원리를 직관적으로 이해할 수 있었다. 막연하게 딥러닝에서 사용되는 라이브러리라고 이해하고 있었는데 제대로 짚고 넘어갈 수 있었다.
0D부터 2D까지 다양한 차원의 텐서를 다뤄보며 shape와 rank의 개념이 확실히 정리되었고, 타입 미스매치나 차원 불일치로 인한 오류 발생 시 해결 방향도 생각해 볼 수 있었다.
연산 방식의 차이(연산자 vs 함수형 API)를 비교하면서 코드 가독성과 안정성을 어떻게 고려할 수 있을지도 중요한 포인트라고 생각한다. 실제 모델링 전에 텐서 조작 흐름을 전체적으로 정리할 수 있는 유익한 시간이었다.