# Tensor의 생성과 초기화

PyTorch에서 Tensor는 다차원 배열을 나타내며, 이를 통해 다양한 수치 연산을 수행할 수 있습니다. Tensor는 다양한 방법으로 생성하고 초기화할 수 있으며, 그 형태와 데이터 타입은 사용자가 원하는 대로 설정할 수 있습니다. 이 장에서는 Tensor를 생성하고 초기화하는 여러 가지 방법을 다룹니다.

### Tensor 생성 방법

#### 1. 기본 생성자 함수

PyTorch에서는 `torch.Tensor()`를 이용해 Tensor를 생성할 수 있습니다. 이 생성자는 주어진 데이터로부터 Tensor를 생성합니다. 예를 들어, 다음과 같이 Python 리스트로부터 Tensor를 생성할 수 있습니다.

```python
import torch

# 1차원 Tensor 생성
tensor1 = torch.Tensor([1, 2, 3, 4, 5])

# 2차원 Tensor 생성
tensor2 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
```

위의 코드에서 `tensor1`은 1차원 Tensor이고, `tensor2`는 2차원 Tensor입니다. `torch.Tensor()`는 입력 데이터를 그대로 복사하여 새로운 Tensor를 생성하므로, 원본 데이터가 변경되어도 Tensor에는 영향을 주지 않습니다.

#### 2. Numpy 배열로부터 생성

Numpy 배열을 PyTorch Tensor로 변환하려면 `torch.from_numpy()` 함수를 사용합니다. 이 함수는 Numpy 배열을 공유 메모리 방식으로 변환하므로, Numpy 배열과 Tensor 간의 데이터 변경이 서로 영향을 미칩니다.

```python
import numpy as np

# Numpy 배열 생성
np_array = np.array([1.0, 2.0, 3.0])

# Tensor로 변환
tensor_from_numpy = torch.from_numpy(np_array)
```

위의 예제에서 Numpy 배열 `np_array`와 Tensor `tensor_from_numpy`는 메모리를 공유하므로, `np_array`의 값을 변경하면 `tensor_from_numpy`의 값도 변경됩니다.

#### 3. 특정 값으로 초기화된 Tensor 생성

PyTorch에서는 특정 값으로 초기화된 Tensor를 생성할 수 있는 다양한 함수들이 제공됩니다. 주로 사용되는 함수는 다음과 같습니다.

* `torch.zeros()`: 모든 요소가 0인 Tensor 생성
* `torch.ones()`: 모든 요소가 1인 Tensor 생성
* `torch.full()`: 사용자가 지정한 값으로 모든 요소가 채워진 Tensor 생성

예를 들어, 다음 코드는 모두 크기가 (3 \times 3)인 Tensor를 생성하지만, 초기화 방법이 다릅니다.

```python
# 모든 요소가 0인 3x3 Tensor
zero_tensor = torch.zeros(3, 3)

# 모든 요소가 1인 3x3 Tensor
one_tensor = torch.ones(3, 3)

# 모든 요소가 7인 3x3 Tensor
full_tensor = torch.full((3, 3), 7)
```

#### 4. 랜덤 값으로 초기화된 Tensor 생성

모델 학습 초기 단계에서 랜덤 값으로 Tensor를 초기화하는 것은 일반적인 방법입니다. PyTorch는 다음과 같은 랜덤 초기화 함수들을 제공합니다.

* `torch.randn()`: 평균이 0이고 분산이 1인 정규분포에서 샘플링된 값을 가지는 Tensor 생성
* `torch.rand()`: (\[0, 1)) 범위에서 균등분포로 샘플링된 값을 가지는 Tensor 생성
* `torch.randint()`: 지정된 범위 내의 정수 값으로 구성된 Tensor 생성

```python
# 정규분포에서 샘플링된 값으로 초기화된 Tensor
randn_tensor = torch.randn(3, 3)

# 균등분포에서 샘플링된 값으로 초기화된 Tensor
rand_tensor = torch.rand(3, 3)

# 범위 내의 정수 값으로 초기화된 Tensor
randint_tensor = torch.randint(0, 10, (3, 3))
```

이와 같이 랜덤 값으로 초기화된 Tensor는 주로 딥러닝 모델의 가중치를 초기화할 때 사용됩니다.

### 초기화 옵션과 데이터 타입

#### 1. 데이터 타입 지정

PyTorch에서 생성된 Tensor의 데이터 타입은 기본적으로 `float32`입니다. 다른 데이터 타입을 사용하려면 `dtype` 인자를 명시적으로 지정해야 합니다. 예를 들어, `int64` 타입의 Tensor를 생성하려면 다음과 같이 할 수 있습니다.

```python
# int64 타입의 Tensor 생성
int_tensor = torch.zeros(3, 3, dtype=torch.int64)
```

데이터 타입을 명시적으로 지정하는 것은 메모리 사용량을 줄이거나 계산 속도를 개선하는 데 유용할 수 있습니다. 또한, 딥러닝 모델에서 입력 데이터와 가중치의 데이터 타입이 일치해야 하므로, 이를 조정하는 데도 중요합니다.

#### 2. 장치 옵션

PyTorch는 Tensor를 CPU와 GPU 모두에서 생성할 수 있습니다. GPU를 활용하여 연산 속도를 개선하려면 `device` 옵션을 지정해야 합니다.

```python
# GPU가 사용 가능한 경우, GPU 장치에 Tensor 생성
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tensor_on_gpu = torch.ones(3, 3, device=device)
```

위의 코드에서는 GPU가 사용 가능한 경우 GPU에, 그렇지 않으면 CPU에 Tensor를 생성합니다.

### Tensor의 형태와 크기 지정

Tensor를 생성할 때는 그 형태(shape)를 지정할 수 있습니다. 형태는 Tensor의 각 차원의 크기를 의미하며, 일반적으로 다음과 같은 함수들을 통해 원하는 크기의 Tensor를 쉽게 생성할 수 있습니다.

#### 1. `torch.empty()`

`torch.empty()`는 초기화되지 않은 Tensor를 생성합니다. 초기화되지 않았다는 것은 메모리의 임의의 값을 그대로 가지고 있기 때문에, 특정 값으로 채워져 있지 않음을 의미합니다. 이는 단순히 메모리를 할당하고 초기화 과정을 생략함으로써 속도를 개선할 수 있습니다.

```python
# 초기화되지 않은 3x3 Tensor 생성
empty_tensor = torch.empty(3, 3)
```

#### 2. `torch.eye()`

단위 행렬(identity matrix)을 생성하는 함수입니다. 단위 행렬은 대각선 성분이 1이고 나머지 성분이 0인 정사각 행렬입니다.

\[ \mathbf{I}\_n = \begin{bmatrix} 1 & 0 & \cdots & 0 \ 0 & 1 & \cdots & 0 \ \vdots & \vdots & \ddots & \vdots \ 0 & 0 & \cdots & 1 \end{bmatrix} ]

다음 코드는 크기가 (4 \times 4)인 단위 행렬을 생성합니다.

```python
# 4x4 단위 행렬 생성
identity_tensor = torch.eye(4)
```

#### 3. `torch.arange()`와 `torch.linspace()`

특정 간격으로 값을 채운 1차원 Tensor를 생성할 때 사용되는 함수들입니다.

* `torch.arange(start, end, step)`: 지정한 간격(`step`)으로 값을 생성합니다.
* `torch.linspace(start, end, steps)`: 시작과 끝 값을 포함하여 일정한 간격으로 값을 생성합니다.

```python
# 0에서 10까지 2씩 증가하는 값으로 채워진 Tensor
arange_tensor = torch.arange(0, 10, 2)

# 0에서 1까지 5개의 값을 선형 간격으로 채운 Tensor
linspace_tensor = torch.linspace(0, 1, 5)
```

`torch.arange()`는 주로 정수 배열을 생성할 때 사용되며, `torch.linspace()`는 두 지점 사이를 일정한 간격으로 나눌 때 유용합니다.

### Tensor의 형태 변환

생성된 Tensor의 형태를 변경해야 하는 경우가 자주 발생합니다. PyTorch에서는 Tensor의 차원을 재배열하거나 변경할 수 있는 다양한 방법을 제공합니다.

#### 1. `torch.reshape()`

`reshape()` 함수는 Tensor의 원소 수를 유지한 채로 새로운 형태를 지정할 수 있게 해줍니다. 예를 들어, (1 \times 12) 형태의 Tensor를 (3 \times 4)로 변환할 수 있습니다.

\[ \mathbf{t} = \begin{bmatrix} 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 \end{bmatrix} \rightarrow \mathbf{T} = \begin{bmatrix} 1 & 2 & 3 & 4 \ 5 & 6 & 7 & 8 \ 9 & 10 & 11 & 12 \end{bmatrix} ]

```python
# 1x12 Tensor 생성
original_tensor = torch.arange(1, 13)

# 3x4 형태로 재구성
reshaped_tensor = original_tensor.reshape(3, 4)
```

#### 2. `torch.view()`

`view()`는 `reshape()`와 비슷하지만, 원래 데이터와 메모리를 공유합니다. 따라서, 하나의 Tensor를 변경하면 다른 Tensor에도 영향을 미칩니다. 이 점을 고려하여 사용할 필요가 있습니다.

```python
# 3x4 형태로 재구성 (메모리 공유)
view_tensor = original_tensor.view(3, 4)
```

#### 3. 차원 추가 및 제거: `torch.unsqueeze()`와 `torch.squeeze()`

`unsqueeze()`는 Tensor에 새로운 차원을 추가합니다. 반대로, `squeeze()`는 크기가 1인 차원을 제거합니다.

\[ \mathbf{x} = \begin{bmatrix} 1 & 2 & 3 \end{bmatrix} \rightarrow \mathbf{y} = \begin{bmatrix} \begin{bmatrix} 1 \end{bmatrix} \ \begin{bmatrix} 2 \end{bmatrix} \ \begin{bmatrix} 3 \end{bmatrix} \end{bmatrix} ]

```python
# 1차원 Tensor
x = torch.tensor([1, 2, 3])

# 2차원으로 확장
y = x.unsqueeze(1)

# 다시 1차원으로 압축
z = y.squeeze(1)
```

### 초기화 전략

모델 학습에서 Tensor의 초기화는 매우 중요한 역할을 합니다. 잘못된 초기화는 학습 속도를 저하시킬 수 있으며, 최적의 가중치를 찾는 것을 어렵게 할 수 있습니다. 초기화 전략에는 여러 가지가 있으며, 각 전략은 특정한 조건에서 더 나은 성능을 발휘할 수 있습니다.

#### 1. Xavier 초기화

Xavier 초기화는 특정 활성화 함수(주로 `tanh`)를 사용할 때 신경망의 학습을 안정적으로 만들어줍니다. 이 방식은 가중치가 (U(-\sqrt{\frac{6}{n\_{in} + n\_{out}}}, \sqrt{\frac{6}{n\_{in} + n\_{out}}}))로 초기화됩니다.

\[ W\_{i,j} \sim U\left(-\sqrt{\frac{6}{n\_{in} + n\_{out}}}, \sqrt{\frac{6}{n\_{in} + n\_{out}}}\right) ]

여기서 (n\_{in})은 입력의 크기, (n\_{out})은 출력의 크기입니다.

```python
# Xavier 초기화를 적용한 Tensor 생성
xavier_tensor = torch.empty(3, 3)
torch.nn.init.xavier_uniform_(xavier_tensor)
```

#### 2. He 초기화

He 초기화는 ReLU와 같은 활성화 함수에 적합한 초기화 방법입니다. 이는 Xavier 초기화의 변형으로, 가중치를 (\mathcal{N}(0, \frac{2}{n\_{in}})) 분포에서 샘플링하여 초기화합니다. 이 방식은 활성화 함수의 특성상 많은 뉴런이 활성화되지 않는 문제를 완화하는 데 도움이 됩니다.

\[ W\_{i,j} \sim \mathcal{N}(0, \frac{2}{n\_{in}}) ]

여기서 (n\_{in})은 입력의 크기입니다.

```python
# He 초기화를 적용한 Tensor 생성
he_tensor = torch.empty(3, 3)
torch.nn.init.kaiming_normal_(he_tensor, mode='fan_in', nonlinearity='relu')
```

#### 3. 정규분포 및 균등분포 초기화

가중치를 초기화할 때 기본적인 방법으로 정규분포나 균등분포를 사용할 수도 있습니다. PyTorch는 이를 쉽게 설정할 수 있는 함수들을 제공합니다.

* `torch.nn.init.normal_(tensor, mean=0.0, std=1.0)`: 평균과 표준편차를 지정한 정규분포로 초기화
* `torch.nn.init.uniform_(tensor, a=0.0, b=1.0)`: (\[a, b]) 범위의 균등분포로 초기화

```python
# 평균이 0이고 표준편차가 1인 정규분포로 초기화된 Tensor
normal_tensor = torch.empty(3, 3)
torch.nn.init.normal_(normal_tensor, mean=0.0, std=1.0)

# 0에서 1 사이의 균등분포로 초기화된 Tensor
uniform_tensor = torch.empty(3, 3)
torch.nn.init.uniform_(uniform_tensor, a=0.0, b=1.0)
```

### 다양한 형태의 Tensor

#### 1. 스칼라, 벡터, 행렬, 고차원 Tensor

Tensor는 수학에서의 다양한 객체(스칼라, 벡터, 행렬, 고차원 배열)를 일반화한 형태입니다.

* **스칼라 (Scalar)**: 차원이 없는 0차원 Tensor \[ s = 5 ]
* **벡터 (Vector)**: 1차원 Tensor \[ \mathbf{v} = \begin{bmatrix} 1 & 2 & 3 \end{bmatrix} ]
* **행렬 (Matrix)**: 2차원 Tensor \[ \mathbf{M} = \begin{bmatrix} 1 & 2 \ 3 & 4 \end{bmatrix} ]
* **고차원 Tensor (Higher-Dimensional Tensor)**: 3차원 이상의 Tensor \[ \mathbf{T}\_{ijk} = \text{3D Tensor} ]

각 차원에 따라 Tensor의 연산 방법과 응용 분야가 달라지며, PyTorch는 모든 차원의 Tensor를 효율적으로 처리할 수 있도록 설계되었습니다.

#### 2. Batch와 고차원 Tensor

딥러닝에서 고차원 Tensor는 주로 이미지나 시퀀스 데이터와 같은 고차원 데이터를 처리하는 데 사용됩니다. 예를 들어, 이미지는 일반적으로 (C \times H \times W) 형태로 표현되며, 여기서 (C)는 채널 수, (H)는 높이, (W)는 너비를 나타냅니다.

```python
# 3채널 RGB 이미지의 Batch를 표현하는 4차원 Tensor 생성
image_batch = torch.randn(32, 3, 224, 224)
```

여기서 `image_batch`는 크기가 ((32, 3, 224, 224))인 Tensor이며, 이는 32개의 RGB 이미지로 구성된 Batch를 나타냅니다. 각 이미지의 크기는 (224 \times 224)이며, 각 픽셀은 3개의 채널(R, G, B)을 가지고 있습니다.

#### 3. Tensor의 복사와 변경

Tensor를 생성하고 초기화한 후에는 데이터를 복사하거나 부분적으로 변경하는 작업이 필요할 수 있습니다. PyTorch에서는 다양한 방법으로 이를 처리할 수 있습니다.

* `clone()`: 기존 Tensor의 데이터를 복사하여 새로운 Tensor를 생성
* `copy_()`: 기존 Tensor의 값을 다른 Tensor에 복사

```python
# 기존 Tensor를 복사하여 새로운 Tensor 생성
original_tensor = torch.tensor([1, 2, 3])
copied_tensor = original_tensor.clone()

# 원본 Tensor의 값을 다른 Tensor에 복사
target_tensor = torch.tensor([0, 0, 0])
target_tensor.copy_(original_tensor)
```

`clone()`과 `copy_()`는 Tensor를 다룰 때 자주 사용되며, 데이터의 독립성을 보장하거나 특정 조건을 충족하기 위해 활용됩니다.

### Tensor의 초기화 실습

Tensor의 초기화는 다양한 조건에 따라 다르게 적용할 수 있습니다. 예를 들어, 특정 범위 내의 난수를 사용하여 초기화하거나, 복잡한 연산을 통해 초기 값을 결정할 수 있습니다. 이러한 실습을 통해 Tensor 초기화 방법을 구체적으로 이해할 수 있습니다.

```python
# 0과 1 사이의 난수로 초기화된 3x3 Tensor
random_tensor = torch.rand(3, 3)

# 정규분포의 평균과 표준편차를 지정하여 초기화
gaussian_tensor = torch.randn(3, 3) * 0.01 + 0.5
```

위의 코드 예제들은 PyTorch에서 다양한 초기화 방법을 실습하는 과정입니다. 이러한 실습을 통해 Tensor의 생성과 초기화를 명확하게 이해할 수 있으며, 이는 모델 학습의 효율성과 성능 향상에 기여할 수 있습니다.
