[개인공부] 신경망 데이터와 텐서 연산

Jajuna_99·2022년 10월 18일
0

수학적 관점보다 데이터 엔지니어로서 딥러닝 모델 구축 때 도움이 될 만한 내용들이다.

신경망 데이터

신경망에서 사용할 수 있는 여러 데이터 종류와, 이것을 담는 텐서 계산을 수행하는 텐서 연산을 공부한다.

텐서(tensor) : 넘파이 배열이고 데이터를 위한 컨테이너이다. 텐서에 담아서 텐서 연산을 수행한다. 임의의 차원 수를 가지는 행렬의 일반화된 모습니다.

차원(dimension) : (axis)라고도 하고, 텐서에서의 차원의 수(dimensionality)는 "특정 축을 따라 놓인 원소의 개수"(5D vector) 혹은 "텐서의 축 개수"(5D tensor)를 의미한다.

스칼라(0D 텐서)

#scalar
x = np.array(12)
print(x.ndim) ## 0

벡터(1D 텐서)

#vector
x = np.array([12, 3, 6, 14, 7])
print(x.ndim) ## 1

행렬(2D 텐서)

#matrix
x = np.array([[5, 78, 2, 34, 0],
             [6, 79, 3, 35, 1],
             [7, 80, 4, 36, 2]])
print(x.ndim) ## 2

3D 텐서 & 고차원 텐서

#3D tenser
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]]])
print(x.ndim) ##4

위 3D 텐서들을 하나의 배열로 합치면 4D 텐서를 만드는 식으로 이어진다.

딥러닝은 보통 4D텐서 까지 다루고, 동영상 데이터를 다룰 경우에는 5D 텐서까지도 다룬다.

데이터 핵심 속성

  • 축의 개수(랭크) : 차원 수
  • 크기(shape) : 각 축을 따라 얼마나 많은 차원이 있는지를 나타낸 튜플
  • 데이터 타입 : 텐서에 포함된 데이터 타입. float32, float64, uint8 등이 있고, 드물게 char 타입이 있는데 넘파이 배열은 가변 길이의 문자열을 지원하지 않는다.
### dimensionality, array shape, data type
print(train_images.ndim, train_images.shape, train_images.dtype) #3, (60000, 28, 28), uint8

슬라이싱으로 텐서 조작

### slicing
my_slice = train_images[10:100]
print(my_slice.shape)  ## 90, 28, 28
my_slice = train_images[10:100, :, :]  #same
my_slice = train_images[10:100, 0:28, 0:28]  #same

my_slice = train_images[:, 14:, 14:]	#이미지 오른쪽 아래 14x14 픽셀 선택
my_slice = train_images[:, 7:-7, 7:-7]	#음수 인덱스도 선택 가능, 정중앙에 14x14 픽셀 선택

":"은 전체 인덱스를 선택한다.

배치 데이터

딥러닝에서는 데이터를 나눠서 이때 나눠지는 단위가 배치이다.

### batch
batch = train_images[:128]
batch = train_images[128:256]
batch = train_images[128 * n : 128 * (n+1)]

입력 데이터에 따라서 텐서의 축도 증감된다. (시계열 데이터는 3D 텐서, 이미지 데이터는 4D 텐서)

텐서 연산

케라스 API 구현을 파이썬 구현 단계에서 살펴본다. 예를 들어

keras.layers.Dense(512, activation='relu')

output = relu(dot(W, input) + b)	#relu(x) = max(x, 0) 이다.

위에 케라스로 구현한 2D 텐서 층은 입력 텐서의 또 다른 표현의 2D 텐서를 반환하는 함수로 해석할 수 있고, 파이썬으로 구현하면 밑에 연산과 같겠다.

(W는 2D 텐서, b는 벡터이다. (가중치와 벡터))

원소별 연산

relu 함수와 덧셈은 원소별 연산(element-wise operation)이다. 이 연산은 텐서에 있는 각 원소에 독립적으로 적용된다.

### relu
def naive_relu(x):
  assert len(x.shape) == 2

  x = x.copy()  # 입력 텐서 자체를 바꾸지 않도록 복사
  for i in range(x.shape[0]):
    for j in range(x.shape[1]):
      x[i, j] = max(x[i, j], 0)
  
  return x

### add
def naive_add(x, y):
  assert len(x.shape) == 2  # x, y는 2D 넘파이 배열이다.
  assert x.shape == y.shape

  x = x.copy()
  for i in range(x.shape[0]):
    for j in range(x.shape[1]):
      x[i, j] += y[i, j]

  return x

넘파이는 효율을 위해 시스템에 설치된 BLAS(Basic Linear Algebra subprogram) 구현에 위임한다.

BLAS : 고도로 병렬화되고 효율적인 저수준의 텐서 조작 루틴, 포트란, C로 구현되어 있다.

(아키텍처상 GPU는 CUDA, CPU는 BLAS)

넘파이를 활용해서 구현하면

### numpy를 활용해 구현
z = x + y  # 원소별 덧셈

z = np.maximum(z, 0.) # 원소별 relu

브로드캐스팅

크기가 다른 텐서 연산을 할 때 작은 텐서가 큰 테서의 크기의 맞추어 하는 연산이 브로드캐스트이다.

  • 큰 텐서의 ndim에 맞도록 작은 텐서에 축(브로드캐스팅 축)을 추가.
  • 작은 텐서가 새 축을 따라서 큰 텐서의 크기에 맞도록 반복된다.
## 브로드캐스팅
def naive_add_matrix_and_vector(x, y):
  assert len(x.shape) == 2
  assert len(y.shpae) == 1
  assert x.shape[1] == y.shape[0]

  x = x.copy()
  for i in range(x.shape[0]):
    for j in range(x.shape[1]):
      x[i, j] += y[j]

  return x

### numpy 활용 브로드캐스팅
x = np.random.random((64, 3, 32, 10))
y = np.random.random((32, 10))

z = np.maximum(x, y)

텐서 점곱

원소별 연산과 반대로 입력 텐서의 원소들을 결합시키는 연산이 텐서 곱셈(tensor product)이다.

주의할 점은 텐서의 차원(ndim)이 다르다면 dot 연산에 교환 법칙이 성립되지 않아 계산이 되지 않는다.

## 텐서 점곱
z = np.dot(x, y)

텐서 크기 변환

텐서 크기 변환(tensor reshaping)과 전치(transposition)도 자주 사용하는 연산이다.

텐서 크기 변환 : 텐서의 크기를 특정 크기에 맞게 열과 행을 재배열하는 연산

전치 : 행과 열을 바꾸는 연산

## 텐서 크기 변환, 전치
x = np.array([[0., 1.],
             [2., 3.],
             [4., 5.]])

x = x.reshape((6, 1))

x = np.transpose(x)
profile
Learning bunch, mostly computer and language

0개의 댓글