# PyTorch의 기본 개념: Tensor, Autograd, Module

### Tensor

PyTorch에서 가장 기본이 되는 데이터 구조는 텐서(Tensor)이다. 텐서는 수학에서의 다차원 배열을 의미하며, 이는 스칼라(0차원), 벡터(1차원), 행렬(2차원), 그리고 그 이상의 고차원 배열을 포함한다. 일반적으로 딥러닝 모델에서는 학습 데이터와 파라미터를 텐서로 표현하여 모델의 학습 및 추론을 수행한다.

#### 텐서의 정의

수학적으로, 텐서는 $n$차원 배열로 정의할 수 있다. 예를 들어, $n=0$이면 스칼라, $n=1$이면 벡터, $n=2$이면 행렬이다. 이를 일반화하면 다음과 같이 표현할 수 있다.

* **스칼라**: $x \in \mathbb{R}$
* **벡터**: $\mathbf{x} \in \mathbb{R}^{n}$
* **행렬**: $\mathbf{X} \in \mathbb{R}^{m \times n}$
* **3차원 이상의 텐서**: $\mathbf{T} \in \mathbb{R}^{d\_1 \times d\_2 \times \cdots \times d\_n}$

#### 텐서 연산

PyTorch에서는 다양한 텐서 연산을 지원한다. 이러한 연산은 텐서의 기본적인 사칙연산에서부터 행렬 곱셈, 텐서 결합, 차원 변경 등 고급 연산을 포함한다. 예를 들어, 두 개의 벡터 $\mathbf{a}$, $\mathbf{b}$의 내적은 다음과 같이 표현할 수 있다.

\[ \mathbf{a} \cdot \mathbf{b} = \sum\_{i=1}^{n} a\_i b\_i ]

행렬 곱셈도 지원하며, 두 행렬 $\mathbf{A} \in \mathbb{R}^{m \times n}$, $\mathbf{B} \in \mathbb{R}^{n \times p}$에 대해 다음과 같이 정의된다.

\[ \mathbf{C} = \mathbf{A} \mathbf{B}, \quad \mathbf{C} \in \mathbb{R}^{m \times p} ]

PyTorch에서는 이러한 연산이 직관적으로 구현 가능하며, GPU 가속을 통해 빠르게 수행할 수 있다.

#### 텐서의 GPU 지원

PyTorch의 텐서는 CPU뿐만 아니라 GPU에서도 연산을 수행할 수 있다. `torch.Tensor` 객체를 `.to(device)` 메서드를 사용해 GPU로 전송하면, 대부분의 연산은 GPU에서 수행되며 이는 학습 속도를 크게 향상시킨다. 예를 들어, 텐서를 GPU로 이동시키는 코드는 다음과 같다.

```python
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tensor = torch.tensor([1, 2, 3]).to(device)
```

### Autograd

딥러닝에서 중요한 개념은 모델 학습 시의 **자동 미분(Automatic Differentiation)** 기능이다. PyTorch는 이를 위해 `autograd` 모듈을 제공한다. 이는 텐서의 연산을 기록하고, 연산 그래프를 생성하여 역전파(Backpropagation) 시 자동으로 미분값을 계산할 수 있도록 한다.

#### 연산 그래프

딥러닝 모델의 학습은 보통 손실 함수의 미분을 구하고, 이 값을 통해 모델의 파라미터를 업데이트하는 방식으로 이루어진다. PyTorch의 `autograd`는 이를 위해 \*\*동적 연산 그래프(Dynamic Computational Graph)\*\*를 사용한다. 이 그래프는 연산이 수행될 때마다 생성되며, 이후 역전파를 통해 자동으로 미분이 계산된다.

수학적으로, 파라미터 $\theta$에 대한 손실 함수 $L(\theta)$의 기울기를 구하는 과정은 다음과 같다.

\[ \frac{\partial L}{\partial \theta} ]

PyTorch는 이 과정을 자동으로 처리해주므로, 사용자는 직접 미분식을 계산할 필요가 없다. 예를 들어, 다음 코드는 손실 함수에 대한 파라미터의 미분값을 계산하는 방법을 보여준다.

```python
import torch
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
y.backward()
print(x.grad)  # 출력: tensor([4.])
```

이 코드에서 `x`는 `requires_grad=True`로 설정되었기 때문에, 이 변수로부터 생성되는 모든 연산은 그래프에 기록되며, `y.backward()`를 호출하면 $\frac{\partial y}{\partial x}$ 값이 자동으로 계산되어 `x.grad`에 저장된다.

#### Autograd의 기본 개념

PyTorch의 `autograd`는 텐서에 대해 자동으로 기울기(Gradient)를 계산하는 기능이다. 이 기능은 텐서가 생성될 때 `requires_grad=True`로 설정되면 활성화된다. `requires_grad=True`로 설정된 텐서는 모든 연산이 추적되며, 연산 그래프가 생성된다. 이후, 역전파(`backward()`) 메서드를 호출하면 연산 그래프를 따라 자동으로 기울기가 계산된다.

#### 연산 그래프의 동작 원리

연산 그래프는 \*\*그래디언트 전파(Gradient Propagation)\*\*를 위해 만들어진다. 예를 들어, 다음과 같은 수식이 있을 때:

\[ z = f(x, y) = x^2 + 2xy + y^2 ]

이 수식의 $x$와 $y$에 대한 기울기(편미분)를 계산해야 한다면, 다음과 같이 파라미터의 미분값을 구할 수 있다.

\[ \frac{\partial z}{\partial x} = 2x + 2y, \quad \frac{\partial z}{\partial y} = 2x + 2y ]

이 연산은 역전파 과정에서 수행되며, PyTorch는 이를 자동으로 처리한다. 더 복잡한 다층 신경망에서도 `autograd`는 연산 그래프를 통해 각 레이어의 기울기를 계산하고, 이를 기반으로 학습 파라미터를 업데이트한다.

### Module

딥러닝 모델의 구축에 있어 핵심적인 개념 중 하나는 \*\*모듈(Module)\*\*이다. 모듈은 모델의 레이어(Layer)와 같은 구성 요소로 생각할 수 있으며, 신경망에서 자주 사용되는 기본 연산 및 레이어를 정의하고 구성하는 데 사용된다. PyTorch에서는 `torch.nn.Module` 클래스를 상속하여 사용자 정의 모듈을 만들 수 있다.

#### Module의 정의

`torch.nn.Module`은 PyTorch에서 모든 네트워크 레이어와 모델의 기반이 되는 클래스이다. 사용자가 새로운 네트워크를 정의하고 싶다면, 이 클래스를 상속받아 새로운 클래스 내에서 모델의 구조를 정의하면 된다. 예를 들어, 단순한 다층 퍼셉트론(Multi-Layer Perceptron, MLP)은 다음과 같이 정의할 수 있다.

```python
import torch.nn as nn

class SimpleMLP(nn.Module):
    def __init__(self):
        super(SimpleMLP, self).__init__()
        self.layer1 = nn.Linear(10, 50)
        self.layer2 = nn.Linear(50, 1)

    def forward(self, x):
        x = torch.relu(self.layer1(x))
        x = self.layer2(x)
        return x
```

이 코드에서 `SimpleMLP` 클래스는 `torch.nn.Module`을 상속받아, 두 개의 선형 레이어를 포함하는 신경망을 정의한다. `forward` 메서드는 입력 데이터를 받아 네트워크의 출력을 계산하는 역할을 한다.

#### 모듈의 재귀적 특성

PyTorch의 모듈은 재귀적으로 구성될 수 있다. 이는 큰 네트워크를 여러 개의 작은 모듈로 나누어 정의할 수 있게 해준다. 예를 들어, 각 레이어가 하나의 모듈로 정의된 더 큰 네트워크는 여러 모듈을 합쳐서 구성할 수 있다. 이를 통해 코드의 재사용성과 유지보수성을 높일 수 있다.

#### 매개변수 관리

모듈은 내부에 정의된 모든 파라미터(Weights, Bias 등)를 자동으로 관리한다. 각 레이어의 파라미터는 `parameters()` 메서드를 통해 접근할 수 있으며, 학습 과정에서 이러한 파라미터들은 업데이트된다. 예를 들어, 모든 파라미터를 확인하고자 할 때는 다음과 같이 사용할 수 있다.

```python
model = SimpleMLP()
for param in model.parameters():
    print(param)
```

이를 통해 신경망의 파라미터를 쉽게 조작하거나 초기화할 수 있다.

#### Module의 매개변수 초기화

딥러닝 모델의 성능은 초기 파라미터 값에 큰 영향을 받을 수 있다. PyTorch에서는 `torch.nn.init` 모듈을 통해 다양한 초기화 방법을 제공한다. 모델의 각 레이어에 대해 직접 초기화를 수행하거나, 기본 초기화를 사용할 수도 있다. 예를 들어, 가중치를 정규분포로 초기화하려면 다음과 같은 방법을 사용할 수 있다.

```python
import torch.nn.init as init

class SimpleMLP(nn.Module):
    def __init__(self):
        super(SimpleMLP, self).__init__()
        self.layer1 = nn.Linear(10, 50)
        self.layer2 = nn.Linear(50, 1)
        init.normal_(self.layer1.weight, mean=0, std=0.01)
        init.constant_(self.layer1.bias, 0)
        init.xavier_uniform_(self.layer2.weight)
        init.constant_(self.layer2.bias, 0)
```

이 코드에서는 `layer1`의 가중치는 평균 0, 표준편차 0.01인 정규분포에서 샘플링된 값으로 초기화되었고, `layer2`의 가중치는 Xavier Uniform 초기화 방법을 사용했다. 이처럼 다양한 초기화 방법을 사용하여 모델의 학습 성능을 높일 수 있다.

### PyTorch의 모듈 간 상호작용

모델을 구축할 때, 여러 모듈이 상호작용하여 최종 출력을 생성한다. 이러한 상호작용은 PyTorch의 `forward` 메서드를 통해 정의되며, 모든 모듈은 입력 텐서를 받아 일련의 연산을 수행한 후 결과를 반환한다. 이 과정은 직관적이며, 신경망 구조를 간결하게 표현할 수 있도록 도와준다.

예를 들어, CNN 모델을 구성할 때는 여러 `nn.Conv2d`, `nn.ReLU`, `nn.MaxPool2d`와 같은 모듈이 연속적으로 연결되며, 모델의 `forward` 메서드에서 이들 모듈을 결합하여 데이터의 전처리 및 특징 추출을 수행한다.

### Module의 장점

#### 코드 재사용성

PyTorch의 모듈을 사용하면 복잡한 모델을 여러 개의 작은 단위로 나눌 수 있으므로 코드 재사용성이 높아진다. 이는 다양한 실험에서 빠르게 모델을 수정하고, 새로운 네트워크 아키텍처를 시도할 때 매우 유용하다.

#### 확장성과 유연성

PyTorch의 모듈 시스템은 매우 유연하며, 새로운 연산이나 레이어를 손쉽게 추가할 수 있다. 예를 들어, 표준 레이어 외에도 사용자 정의 레이어를 만들고, 이를 기존 모델과 결합하여 복잡한 네트워크를 설계할 수 있다.

#### 동적 계산 그래프

PyTorch는 동적 계산 그래프(Define-by-Run)를 사용하므로, 연산이 실행될 때 그래프가 동적으로 생성된다. 이로 인해 연산의 순서를 유연하게 변경할 수 있으며, RNN이나 트리 구조와 같은 복잡한 네트워크를 손쉽게 구현할 수 있다. 이러한 동적 특성은 모델의 디버깅을 용이하게 해주고, 다양한 데이터 형태에 대해 더 나은 처리를 가능하게 한다.

### 텐서와 모듈의 조합: 연산 흐름의 이해

딥러닝 모델의 학습 과정은 기본적으로 아래와 같은 단계로 이루어진다.

1. **데이터 준비**: 입력 데이터는 텐서 형태로 준비되며, GPU로 전송된다.
2. **모델 정의**: 사용자가 정의한 모듈이 입력 데이터를 받아 일련의 연산을 수행한다.
3. **손실 계산**: 모델의 출력과 실제 값을 비교하여 손실 함수를 통해 오차를 계산한다.
4. **역전파(Backpropagation)**: `autograd`를 사용해 손실 함수에 대한 파라미터의 기울기를 계산한다.
5. **파라미터 업데이트**: 옵티마이저를 통해 모델의 파라미터를 업데이트한다.

이 과정을 반복하면서 모델은 입력 데이터에 적합한 파라미터를 학습하게 된다. PyTorch는 이 모든 과정을 단순화하고, 코드로 구현하기 쉽게 설계되어 있어 학습 과정의 이해와 조작이 용이하다.
