2 시작하기 전에: 신경망의 수학적 구성 요소 | 목차 | 2.3 신경망의 톱니바퀴: 텐서 연산
이전 예제에서 텐서tensor라 부르는 다차원 넘파이 배열에 데이터를 저장하는 것부터 시작했습니다.8 최근의 모든 머신 러닝 시스템은 일반적으로 텐서를 기본 데이터 구조로 사용합니다. 텐서는 머신 러닝의 기본 구성 요소입니다. 구글의 텐서플로 이름을 여기에서 따왔습니다. 그럼 텐서는 무엇일까요?
핵심적으로 텐서는 데이터를 위한 컨테이너container입니다. 거의 항상 수치형 데이터를 다루므로 숫자를 위한 컨테이너입니다. 아마 2D 텐서인 행렬에 대해 이미 알고 있을 것입니다. 텐서는 임의의 차원 개수를 가지는 행렬의 일반화된 모습입니다(텐서에서는 차원dimension을 종종 축axis이라고 부릅니다).
2.2.1 스칼라(0D 텐서)
하나의 숫자만 담고 있는 텐서를 스칼라scalar 스칼라 텐서, 0차원 텐서, 0D 텐서)라고 부릅니다. 넘파이에서는 float32나 float64 타입의 숫자가 스칼라 텐서(또는 배열 스칼라array scalar 9입니다. ndim 속성을 사용하면 넘파이 배열의 축 개수를 확인할 수 있습니다. 스칼라 텐서의 축 개수는 0입니다(ndim = = 0). 텐서의 축 개수를 랭크rank라고도 부릅니다.10 다음이 스칼라 텐서입니다.
>>> import numpy as np >>> x = np.array(12) >>> x array(12) >>> x.ndim 0
2.2.2 벡터(1D 텐서)
숫자의 배열을 벡터vector 또는 1D 텐서라고 부릅니다. 1D 텐서는 딱 하나의 축을 가집니다. 넘파이에서 벡터를 나타내면 다음과 같습니다.
>>> x = np.array([12, 3, 6, 14, 7]) >>> x array([12, 3, 6, 14, 7]) >>> x.ndim 1
이 벡터는 5개의 원소를 가지고 있으므로 5차원 벡터라고 부릅니다. 5D 벡터와 5D 텐서를 혼동하지 마세요! 5D 벡터는 하나의 축을 따라 5개의 차원을 가진 것이고 5D 텐서는 5개의 축을 가진 것입니다(텐서의 각 축을 따라 여러 개의 차원을 가진 벡터가 놓일 수 있습니다). 차원수dimensionality는 특정 축을 따라 놓인 원소의 개수(5D 벡터와 같은 경우)이거나 텐서의 축 개수(5D 텐서와 같은 경우)를 의미하므로 가끔 혼동하기 쉽습니다. 후자의 경우 랭크5 인 텐서라고 말하는 것이 기술적으로 좀 더 정확합니다(텐서의 랭크가 축의 개수입니다). 그럼에도 5D 텐서처럼 모호한 표기가 통용됩니다.
2.2.3 행렬(2D 텐서)
벡터의 배열이 행렬matrix 또는 2D 텐서입니다. 행렬에는 2개의 축이 있습니다(보통 행row과 열column이라고 부릅니다). 행렬은 숫자가 채워진 사각 격자라고 생각할 수 있습니다. 넘파이에 서 행렬을 나타내면 다음과 같습니다.
>>> x = np.array([[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]]) >>> x.ndim 2
첫 번째 축에 놓여 있는 원소를 행이라 부르고, 두 번째 축에 놓여 있는 원소를 열이라 부릅니다. 앞의 예에서는 x의 첫 번째 행은 [5, 78, 2, 34, 0]이고, 첫 번째 열은 [5, 6, 7]입니다.
2.2.4 3D 텐서와 고차원 텐서
이런 행렬들을 하나의 새로운 배열로 합치면 숫자가 채워진 직육면체 형태로 해석할 수 있는 3D 텐서가 만들어집니다. 넘파이에서 3D 텐서를 나타내면 다음과 같습니다.
>>> x = np.array([[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]]]) >>> x.ndim 3
3D 텐서들을 하나의 배열로 합치면 4D 텐서를 만드는 식으로 이어집니다. 딥러닝에서는 보통 0D에서 4D까지의 텐서를 다룹니다. 동영상 데이터를 다룰 경우에는 5D 텐서까지 가기도 합니다.
2.2.5 핵심 속성
텐서는 3개의 핵심 속성으로 정의됩니다.
- 축의 개수(랭크): 예를 들어 3D 텐서에는 3개의 축이 있고, 행렬에는 2개의 축이 있습니다. 넘파이 라이브러리에서는 ndim 속성에 저장되어 있습니다.
- 크기shape: 텐서의 각 축을 따라 얼마나 많은 차원이 있는지를 나타낸 파이썬의 튜플tuple입니다. 예를 들어 앞에 나온 행렬의 크기는 (3, 5)이고 3D 텐서의 크기는 (3, 3, 5)입니다. 벡터의 크기는 (5,)처럼 1개의 원소로 이루어진 튜플입니다. 배열 스칼라는 ()처럼 크기가 없습니다.
- 데이터 타입(넘파이에서는 dtype에 저장됩니다): 텐서에 포함된 데이터의 타입입니다. 예를 들어 텐서의 타입은 float32, uint8, float64 등이 될 수 있습니다. 드물게 char 타입을 사용합니다. 텐서는 사전에 할당되어 연속된 메모리에 저장되어야 하므로 넘파이 배열은 (그리고 대부분 다른 라이브러리는) 가변 길이의 문자열을 지원하지 않습니다.
이를 구체적으로 확인해 보기 위해서 MNIST 예제에서 사용했던 데이터를 다시 들여다봅시다. 먼저 MNIST 데이터셋을 불러들입니다.
from keras.datasets import mnist (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
그다음 train_images 배열의 ndim 속성으로 축의 개수를 확인합니다.
>>> print(train_images.ndim)
3
다음은 배열의 크기입니다.
>>> print(train_images.shape)
(60000, 28, 28)
dtype 속성으로 데이터 타입을 확인합니다.
>>> print(train_images.dtype)
uint8
이 배열은 8비트 정수형 3D 텐서입니다. 좀 더 정확하게는 28×28 크기의 정수 행렬 6만 개가 있는 배열입니다. 각 행렬은 하나의 흑백 이미지고, 행렬의 각 원소는 0에서 255 사이의 값을 가집니다.
이 3D 텐서에서 다섯 번째 샘플을 (파이썬의 표준 과학 라이브러리 중 하나인) 맷플롯립Matplotlib 라이브러리를 사용해서 확인해 봅시다11(그림 2–2 참고).
코드 2-6 다섯 번째 이미지 출력하기
digit = train_images[4] import matplotlib.pyplot as plt plt.imshow(digit, cmap=plt.cm.binary) plt.show()

그림 2-2 데이터셋에 있는 다섯 번째 샘플
2.2.6 넘파이로 텐서 조작하기
이전 예제에서 train_images[i] 같은 형식으로 첫 번째 축을 따라 특정 숫자를 선택했습니다. 배열에 있는 특정 원소들을 선택하는 것을 슬라이싱slicing이라고 합니다. 넘파이 배열에서 할 수 있는 슬라이싱 연산을 살펴보겠습니다.
다음 예는 11번째에서 101번째까지(101번째는 포함하지 않고) 숫자를 선택하여 (90, 28, 28) 크기의 배열을 만듭니다.
>>> my_slice = train_images[10:100] >>> print(my_slice.shape) (90, 28, 28)
동일하지만 조금 더 자세한 표기법은 각 배열의 축을 따라 슬라이싱의 시작 인덱스와 마지막 인덱스를 지정하는 것입니다. :(콜론)은 전체 인덱스를 선택합니다.
>>> my_slice = train_images[10:100, :, :] 이전 예와 동일합니다. >>> my_slice.shape (90, 28, 28) >>> my_slice = train_images[10:100, 0:28, 0:28] 역시 이전과 동일합니다. >>> my_slice.shape (90, 28, 28)
일반적으로 각 배열의 축을 따라 어떤 인덱스 사이도 선택할 수 있습니다. 예를 들어 이미지의 오른쪽 아래 14×14픽셀을 선택하려면 다음과 같이 합니다.
my_slice = train_images[:, 14:, 14:]
음수 인덱스도 사용할 수 있습니다. 파이썬 리스트의 음수 인덱스와 마찬가지로 현재 축의 끝에서 상대적인 위치를 나타냅니다. 정중앙에 위치한 14×14픽셀 조각을 이미지에서 잘라 내려면 다음과 같이 합니다.
my_slice = train_images[:, 7:-7, 7:-7]
2.2.7 배치 데이터
일반적으로 딥러닝에서 사용하는 모든 데이터 텐서의 첫 번째 축(인덱스가 0부터 시작하므로 0번째 축)은 샘플 축sample axis입니다(이따금 샘플 차원sample dimension이라고도 부릅니다). MNIST 예제에서는 숫자 이미지가 샘플입니다.
딥러닝 모델은 한 번에 전체 데이터셋을 처리하지 않습니다. 그 대신 데이터를 작은 배치batch로 나눕니다. 구체적으로 말하면 MNIST 숫자 데이터에서 크기가 128인 배치 하나는 다음과 같습니다.
batch = train_images[:128]
그다음 배치는 다음과 같습니다.
batch = train_images[128:256]
그리고 n번째 배치는 다음과 같습니다.
batch = train_images[128 * n:128 * (n + 1)]
이런 배치 데이터를 다룰 때는 첫 번째 축(0번 축)을 배치 축batch axis 또는 배치 차원batch dimension이라고 부릅니다. 케라스나 다른 딥러닝 라이브러리를 사용할 때 이런 용어를 자주 만날 것입니다.
2.2.8 텐서의 실제 사례
앞으로 보게 될 텐서의 몇 가지 예를 통해 좀 더 확실하게 알아보겠습니다. 우리가 사용할 데이터는 대부분 다음 중 하나에 속할 것입니다.
- 벡터 데이터: (samples, features) 크기의 2D 텐서
- 시계열 데이터 또는 시퀀스sequence 데이터: (samples, timesteps, features) 크기의 3D 텐서
- 이미지: (samples, height, width, channels) 또는 (samples, channels, height, width) 크기의 4D 텐서
- 동영상: (samples, frames, height, width, channels) 또는 (samples, frames, channels, height, width) 크기의 5D 텐서
2.2.9 벡터 데이터
대부분의 경우에 해당됩니다. 이런 데이터셋에서는 하나의 데이터 포인트가 벡터로 인코딩될 수 있으므로 배치 데이터는 2D 텐서로 인코딩될 것입니다(즉 벡터의 배열입니다). 여기서 첫 번째 축은 샘플 축이고, 두 번째 축은 특성 축feature axis입니다.
2개의 예를 살펴보겠습니다.
- 사람의 나이, 우편 번호, 소득으로 구성된 인구 통계 데이터. 각 사람은 3개의 값을 가진 벡터로 구성되고 10만 명이 포함된 전체 데이터셋은 (100000, 3) 크기의 텐서에 저장될 수 있습니다.
- (공통 단어 2만 개로 만든 사전에서) 각 단어가 등장한 횟수로 표현된 텍스트 문서 데이터셋. 각 문서는 2만 개의 원소(사전에 있는 단어마다 하나의 원소에 대응합니다)를 가진 벡터로 인코딩될 수 있습니다. 500개의 문서로 이루어진 전체 데이터셋은 (500, 20000) 크기의 텐서로 저장됩니다.
2.2.10 시계열 데이터 또는 시퀀스 데이터
데이터에서 시간이 (또는 연속된 순서가) 중요할 때는 시간 축을 포함하여 3D 텐서로 저장됩니다. 각 샘플은 벡터(2D 텐서)의 시퀀스로 인코딩되므로 배치 데이터는 3D 텐서로 인코딩될 것입니다(그림 2–3 참고).

그림 2-3 3D 시계열 데이터 텐서
관례적으로 시간 축은 항상 두 번째 축(인덱스가 1인 축)입니다.12 몇 가지 예를 들어 보겠습니다.
- 주식 가격 데이터셋: 1분마다 현재 주식 가격, 지난 1분 동안에 최고 가격과 최소 가격을 저장합니다. 1분마다 데이터는 3D 벡터로 인코딩되고 하루 동안의 거래는 (390, 3) 크기의 2D 텐서로 인코딩됩니다(하루의 거래 시간은 390분입니다13). 250일치의 데이터는 (250, 390, 3) 크기의 3D 텐서로 저장될 수 있습니다. 여기에서 1일치 데이터가 하나의 샘플이 됩니다.
- 트윗 데이터셋: 각 트윗은 128개의 알파벳으로 구성된 280개의 문자 시퀀스입니다. 여기에서는 각 문자가 128개의 크기인 이진 벡터로 인코딩될 수 있습니다(해당 문자의 인덱스만 1이고 나머지는 모두 0인 벡터). 그러면 각 트윗은 (280, 128) 크기의 2D 텐서로 인코딩될 수 있습니다. 100만 개의 트윗으로 구성된 데이터셋은 (1000000, 280, 128) 크기의 텐서에 저장됩니다.
2.2.11 이미지 데이터
이미지는 전형적으로 높이, 너비, 컬러 채널의 3차원으로 이루어집니다. (MNIST 숫자처럼) 흑백 이미지는 하나의 컬러 채널만을 가지고 있어 2D 텐서로 저장될 수 있지만 관례상 이미지 텐서는 항상 3D로 저장됩니다. 흑백 이미지의 경우 컬러 채널의 차원 크기는 1입니다. 256×256 크기의 흑백 이미지에 대한 128개의 배치는 (128, 256, 256, 1) 크기의 텐서에 저장될 수 있습니다. 컬러 이미지에 대한 128개의 배치라면 (128, 256, 256, 3) 크기의 텐서에 저장될 수 있습니다(그림 2–4 참고).

그림 2-4 4D 이미지 데이터 텐서(채널 우선 표기)14
이미지 텐서의 크기를 지정하는 방식은 두 가지입니다. (텐서플로에서 사용하는) 채널 마지막channel-last 방식과 (씨아노에서 사용하는) 채널 우선channel-first 방식입니다. 구글의 텐서플로 머신 러닝 프레임워크는 (samples, height, width, color_depth)처럼 컬러 채널의 깊이를 끝에 놓습니다. 반면에 씨아노는 (samples, color_depth, height, width)처럼 컬러 채널의 깊이를 배치 축 바로 뒤에 놓습니다. 씨아노 방식을 사용하면 앞선 예는 (128, 1, 256, 256)과 (128, 3, 256, 256)이 됩니다. 케라스 프레임워크는 두 형식을 모두 지원합니다.15
2.2.12 비디오 데이터
비디오 데이터는 현실에서 5D 텐서가 필요한 몇 안 되는 데이터 중 하나입니다. 하나의 비디오는 프레임의 연속이고 각 프레임은 하나의 컬러 이미지입니다. 프레임이 (height, width, color_depth)의 3D 텐서로 저장될 수 있기 때문에 프레임의 연속은 (frames, height, width, color_depth)의 4D 텐서로 저장될 수 있습니다. 여러 비디오의 배치는 (samples, frames, height, width, color_depth)의 5D 텐서로 저장될 수 있습니다.
예를 들어 60초짜리 144×256 유튜브 비디오 클립을 초당 4프레임으로 샘플링하면 240프레임이 됩니다. 이런 비디오 클립을 4개 가진 배치는 (4, 240, 144, 256, 3) 크기의 텐서에 저장될 것입니다. 총 106,168,320개의 값이 있습니다! 이 텐서의 dtype을 float32로 했다면16 각 값이 32비트로 저장될 것이므로 텐서의 저장 크기는 405MB가 됩니다. 아주 크네요! 실생활에서 접하는 비디오는 float32 크기로 저장되지 않기 때문에 훨씬 용량이 적고, 일반적으로 높은 압축률로 (MPEG 포맷 같은 방식을 사용하여) 압축되어 있습니다.
8 역주 텐서플로를 비롯하여 딥러닝 라이브러리들은 종종 다차원 배열을 텐서라고 부릅니다. 이 책에서는 넘파이 배열도 텐서라고 하지만 사실 파이썬 커뮤니티에서 넘파이 배열을 텐서라고 부르지는 않습니다. 번역서에서는 문맥에 잘 어울리도록 적절하게 단어를 선택했습니다.
9 역주 넘파이의 배열 스칼라(array scalar)는 수정할 수 없는 0차원의 넘파이 배열이며 프로그래밍 언어의 스칼라 변수와는 다릅니다.
10 역주 여기서 랭크는 선형대수에서 행렬의 선형 독립 행이나 열을 나타내는 계수(rank)와는 다릅니다.
11 역주 배열의 인덱스는 0부터 시작하므로 다섯 번째 이미지의 인덱스가 4입니다.
12 역주 시간 축이 두 번째 축이면 그림 2-3에서 타임스텝이 세로 축에 놓여야 하지만 가로 축에 표기되어 있습니다. 관례적으로 시간의 흐름을 가로 방향으로 놓기 때문에 편의상 텐서 축의 순서와 맞지 않게 그려져 있습니다.
13 역주 미국의 증권거래소 개장 시간은 오전 9:30분부터 오후 16:00까지 6시간 반으로 390분 동안입니다.
14 역주 일상생활에서 이미지의 크기는 보통 너비 × 높이로 말하지만 행렬에서는 행이 먼저 나오므로 높이 × 넓이로 표현됩니다.
15 역주 케라스 설정 파일 keras.json에서 “image_data_format” 옵션을 “channels_last” 또는 “channels_first”로 지정할 수 있습니다.
16 역주 케라스의 부동 소수 기본 설정은 float32입니다. 역시 keras.json에서 “floatx” 옵션을 “float16”이나 “float64”로 바꿀 수 있습니다.
2 시작하기 전에: 신경망의 수학적 구성 요소 | 목차 | 2.3 신경망의 톱니바퀴: 텐서 연산