2.1. Data Manipulation

Eunjin Kim·2022년 4월 21일
0

Dive into Deep Learning

목록 보기
4/14

강의: https://d2l.ai/chapter_preliminaries/ndarray.html

2.1. Data Manipulation

어떤 일이든 해내기 위해서는, 데이터를 저장하거나 다루는 어떤 방법이 필요하다. 일반적으로 우리가 데이터를 가지고 해야할 두가지 중요한 일이 있다: (i) 데이터를 얻기 (ii) 컴퓨터에 들어오면 데이터를 처리하기. 데이터를 저장할 방법이 없다면 데이터를 획득하는 것은 의미가 없기 때문에, 합성 데이터를 가지고 놀면서 먼저 손을 더럽히자. 시작하기 위해, tensor 이라 불리는, nn-차원 배열을 소개한다.

만약 파이썬에서 가장 널리 쓰이는 scientific computing package인 NumPy를 다뤄봤다면, 이번 장은 익숙하다고 느낄 것이다. 어떤 프레임워크를 사용하든지, tensor class (ndarray in MXNet, Tensor in both PyTorch and TensorFlow)는 NumPy의 ndarray와 비슷하다. 첫째, GPU는 계산을 가속화하기 위해 잘 지원되지만 NumPy는 CPU 계산만 지원한다. 둘째, tensor class는 자동 미분을 지원한다. 이러한 특성은 tensor class를 딥러닝에 적합하게 만든다. 이 책 전체에서 tensor라고 말할 때, 우리는 달리 명시되지 않는 한 tensor class의 인스턴스를 참조한다.

2.1.1 Getting Started

이 섹션에서는 기본 수학 및 수치 계산 도구를 제공하여 이 책을 통해 학습할 수 있도록 지원합니다.

import troch

tensor는 (아마도 다차원) 숫자 값의 배열을 나타낸다. 하나의 축에서, tensor는 vector 라고 불린다. 두개의 축을 가진 tensor를 matrix이라고 한다. kk > 2 인 축에서, kthk^{th} order tensor 라고 한다.

PyTorch는 값이 미리 채워진 새로운 tensors를 생성하기 위해 다양한 함수를 재공한다. 예 : arange(n)

x = torch.arange(12, dtype=torch.float32)
x		# tensor([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])

tensor의 shape(the length along each axis)

x.shape		# torch.Size([12])

tensor 전체 원소 수만 알고 싶다면, 여기서는 벡터를 다루기 때문에, 그 모양의 단일 요소는 그것의 크기와 같습니다.

x.numel()		# 12

원소의 수나 값을 변경하지 않고 텐서의 모양을 바꾸려면, reshape 함수가 있다. 예를 들어, x 라는 tensor를 row vector (12,) 에서 행렬(3, 4)로 바꿀 수 있다. 이 새로운 tensor는 같은 값을 가지지만 3개의 행 4개의 열로 이뤄진 행렬을 보여준다. 다시 한 번 말하지만, 모양은 변했지만, 요소들은 변하지 않았다. 크기는 reshaping에 의해 변경되지 않는다.

X = x.reshape(3, 4)
X

#tensor([[ 0.,  1.,  2.,  3.],
#        [ 4.,  5.,  6.,  7.],
#        [ 8.,  9., 10., 11.]])

모든 차원을 수동으로 지정해 모양을 변경할 필요는 없습니다. 만약 target 모양이 (height, width) 모양의 행렬이라면, width를 알고나서는 height는 바로 구해질 수 있다. 왜 스스로 나누기를 해야할까? 위 예제에서 3개의 행을 갖는 행렬을 위해, 둘 다 3행 4열로 지정해야했다. 다행이, 텐서들은 한 차원을 주면 나머지는 자동적으로 수행할 수 있다. 텐서가 자동으로 추론하기를 원하는 차원에 대해 -1로 설정해 이 기능을 수행할 수 있다. 우리와 같은 경우에는, x.reshape(3, 4)를 할게 아니라, x.reshape(-1, 4) 또는 x.reshape(3, -1)로 호출할 수 있다.

일반적으로 행렬은 0, 1, 다른 상수 또는 특정 분포에서 랜덤하게 샘플링된 숫자로 초기화되어야 한다. 우리는 아래와 같이 모든 원소를 0으로 설정하고 (2, 3, 4)의 형상을 가진 텐서를 만들 수 있다.

torch.zeros((2, 3, 4))

# tensor([[[0., 0., 0., 0.],
#         [0., 0., 0., 0.],
#         [0., 0., 0., 0.]],

#        [[0., 0., 0., 0.],
#         [0., 0., 0., 0.],
#         [0., 0., 0., 0.]]])

비슷하게, 1로 채우고 싶으면 아래와 같이 한다.

torch.ones((2, 3, 4))

# tensor([[[1., 1., 1., 1.],
#         [1., 1., 1., 1.],
#         [1., 1., 1., 1.]],

#        [[1., 1., 1., 1.],
#         [1., 1., 1., 1.],
#         [1., 1., 1., 1.]]])

종종, 우리는 무작위로 텐서에 약간의 확률 분포에서 각 요소에 값을 조사하고 싶다. 예를 들어, 신경망에서 매개변수로 제공될 배열을 구성할 때, 일반적으로 그 값들을 랜덤하게 초기화 할것이다. 아래는 모양 (3,4)의 텐서를 만듭니다. 각 구성 요소는 0의 평균 표준과 1의 표준 편차와 함께 Gaussian 분배기준으로 랜덤하게 지정될 것이다.

torch.randn(3, 4)

# tensor([[ 0.3795,  0.7821,  0.6188,  1.0498],
#        [-1.0893, -2.8073,  0.9372,  0.5871],
#        [-1.4883,  1.0893, -0.5943,  0.3536]])

수치 값을 포함하는 파이썬 list (또는 list of lists)로 원하는 텐서의 각 요소에 대한 정확한 값을 지정할 수 있다. 여기서, 가장 바깥쪽 리스트는 축 0에 해당하고, 안쪽 리스트는 축 1에 해당된다.

torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])

# tensor([[2, 1, 4, 3],
#         [1, 2, 3, 4],
#         [4, 3, 2, 1]])

2.1.2 Operations

이 책은 소프트웨어 엔지니어링에 관한 것이 아니다. 우리의 관심은 단지 배열로부터/에서 데이터를 읽고 쓰는 것에 한정된 것이 아니다. 우리는 그 배열들에서 수학적 연산을 수행하고 싶어한다. 가장 단순하고 유용한 연산들은 elementwise operations 이다. 이것은 배열의 각 요소에 표준 스칼라 연산을 적용한다. 두 개의 배열을 입력으로 사용하는 함수의 경우, elementwise operations(요소별 연산)은 두 배열의 각 요소 쌍에 표준 이진 연산자를 적용한다. 우리는 스칼라에서 스칼라로 매핑되는 함수로부터 요소별 함수를 만들 수 있다.

수학적 표기법에서, 우리는 그러한 unary(단항) 스칼라 연산자(입력 한 개를 받는 것)를 기호 f:RRf : ℝ → ℝ로 나타낼 것이다. 이는 함수가 임의의 실수(R)에서 다른 실수(R)로 매핑된다는 것을 의미한다. 마찬가지로 binary(이항) 스칼라 연산자(입력 두개를 받고 하나의 출력값 내는 것)는 기호 f:R,RRf : ℝ,ℝ → ℝ로 표현한다. 같은 모양의 uv 벡터 두개와 이항 연산자 ff 가 주어졌을 때, 모든 ii에 대해 cif(ui,vi)c_i ← f(u_i, v_i) 로 설정함으로써 벡터 c = FF(u, v)를 만들 수 있다. (ci,ui,vic_i, u_i, v_i 는 벡터 c, u, vithi^{th} 요소)

2.1.2.1 Operations

일반적인 표준 산술 연산자(+, -, *, / 및 ** )가 모두 elementwise operations으로 lifted된다.

x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y  # The ** operator is exponentiation

# (tensor([ 3.,  4.,  6., 10.]),
#  tensor([-1.,  0.,  2.,  6.]),
#  tensor([ 2.,  4.,  8., 16.]),
#  tensor([0.5000, 1.0000, 2.0000, 4.0000]),
#  tensor([ 1.,  4., 16., 64.]))

지수화와 같은 단항 연산자를 포함해 더 많은 연산을 적용할 수 있다.

torch.exp(x)

# tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])

우리는 또한 여러 개의 텐서를 concatenate(연결)하여 더 큰 텐서를 형성하기 위해 end-to-end로 쌓을 수 있다. 텐서 list와 시스템에 연결할 축을 알려주면 됩니다. 아래 예제에서는 행(첫 번째 요소인 축 0)과 열(두 번째 요소인 축 1)을 따라 두 행렬을 연결할 때 어떤 일이 일어나는지 보여 주고있다. 우리는 첫 번째 출력 텐서의 축 -0 길이 6이 두 입력 텐서의 축-0 길이의 합(3 + 3)이다. 두 번째 출력 텐서의 축-1 길이 8은 두 입력 텐서의 축-1 길이(4 + 4)의 합이다.

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)

#(tensor([[ 0.,  1.,  2.,  3.],
#        [ 4.,  5.,  6.,  7.],
#        [ 8.,  9., 10., 11.],
#        [ 2.,  1.,  4.,  3.],
#        [ 1.,  2.,  3.,  4.],
#        [ 4.,  3.,  2.,  1.]]),
# tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
#        [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
#        [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]]))

때때로, 우리는 logical statements을 통해 이진 텐서를 구성한다. X == Y를 예로 들어보자. 각 위치에서 X와 Y가 같으면 새로운 텐서의 해당 항목은 1을 취하며, 이는 논리문 X == Y가 해당 위치에서 참임을 의미하며, 그렇지 않으면 해당 위치는 0을 취한다.

X == Y

# tensor([[False,  True, False,  True],
#         [False, False, False, False],
#         [False, False, False, False]])

모든 요소를 더하면 텐서는 하나의 요소를 가진다.

X.sum()

# tensor(66.)

2.1.3 Broadcasting Mechanism

특정 조건에서는 shape가 다르더라도 Broadcasting Mechanism을 호출하여 연산을 수행할 수 있다. 이 메커니즘은 다음과 같이 작동한다: 첫째, 변환 후 두 텐서가 동일한 모양을 갖도록 요소를 적절히 복사하여 하나 또는 두 배열 모두를 확장한다. 둘째, 결과 배열에서 요소별 작업을 수행합니다.

a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b

# (tensor([[0],
#          [1],
#          [2]]),
#  tensor([[0, 1]]))

a 와 b가 각각 3 \times$1, 1\times2행렬이기때문에,덧셈을하려고했을때모양이맞지않는다.우리는두행렬의항목을다음과같이더큰32 행렬이기 때문에, 덧셈을 하려고 했을 때 모양이 맞지 않는다. 우리는 두 행렬의 항목을 다음과 같이 더 큰 3\times$ 2 행렬로 Broadcast한다. 행렬 a의 경우 열을 복제하고 행렬 b의 경우 두 요소를 합하기 전에 행을 복제한다.

a + b

# tensor([[0, 1],
#         [1, 2],
#         [2, 3]])

2.1.4 Indexing and Slicing

다른 파이썬 배열과 마찬가지로 텐서의 요소들도 인덱스로 접근할 수 있다. 파이썬 배열에서와 마찬가지로 첫 번째 요소는 인덱스 0을 가지며, 범위는 첫 번째 요소보다 마지막 요소 앞까지이다. 표준 Python list와 같이, 우리는 - 인덱스를 사용하여 목록 끝에 대한 상대적인 위치에 따라 요소에 액세스할 수 있다.

X[-1], X[1:3]

# (tensor([ 8.,  9., 10., 11.]),
#  tensor([[ 4.,  5.,  6.,  7.],
#          [ 8.,  9., 10., 11.]]))

읽기뿐 아니라, indices를 지정하여 행렬의 요소를 작성할 수도 있다.

X[1, 2] = 9
X

# tensor([[ 0.,  1.,  2.,  3.],
#         [ 4.,  5.,  9.,  7.],
#         [ 8.,  9., 10., 11.]])

여러 요소에 동일한 값을 할당하려면 모든 요소를 인덱싱한 다음 해당 값을 할당하면 된다. 예를 들어 [0:2, :]는 첫 번째 행과 두 번째 행에 액세스한다. 여기서 :는 축 1(열)을 따라 모든 요소를 취한다. 행렬에 대한 인덱싱에 대해 논의했지만, 이는 분명히 2차원이 넘는 벡터와 텐서에도 적용된다.

X[0:2, :] = 12
X

# tensor([[12., 12., 12., 12.],
#         [12., 12., 12., 12.],
#         [ 8.,  9., 10., 11.]])

2.1.5. Saving Memory

2.1.6. Conversion to Other Python Objects

NumPy 텐서(ndarray)로 변환하거나 그 반대로 변환하는 것은 쉽다. Torch Tensor와 Numpy 배열은 기본 메모리 위치를 공유하며, in-place 연산을 통해 하나를 변경하면 다른 것도 변경된다.

A = X.numpy()
B = torch.from_numpy(A)
type(A), type(B)

# (numpy.ndarray, torch.Tensor)

size-1 텐서를 파이썬 스칼라로 변환하기 위해 item 함수 또는 파이썬의 내장 함수를 호출할 수 있다.

a = torch.tensor([3.5])
a, a.item(), float(a), int(a)

# (tensor([3.5000]), 3.5, 3.5, 3)

2.1.7. Summary

The main interface to store and manipulate data for deep learning is the tensor (nn-dimensional array). It provides a variety of functionalities including basic mathematics operations, broadcasting, indexing, slicing, memory saving, and conversion to other Python objects.

2.1.8. Exercises

  1. Run the code in this section. Change the conditional statement X == Y in this section to X < Y or X > Y, and then see what kind of tensor you can get.

  2. Replace the two tensors that operate by element in the broadcasting mechanism with other shapes, e.g., 3-dimensional tensors. Is the result the same as expected?

profile
ALL IS WELL🌻

0개의 댓글