선형 회귀를 직접 구현할 때는 가중치와 편향을 따로 만들고, 예측식과 갱신 대상도 직접 연결한다. 모델의 구성 요소가 늘어나면 이들을 하나의 단위로 관리할 필요가 있다. PyTorch에서는 nn.Module을 이용해 모델이 가진 매개변수와 입력에서 출력을 만드는 계산을 묶는다.
모듈(Module)은 계산과 그 계산에 필요한 상태를 함께 관리하는 PyTorch의 구성 단위이다. nn.Module은 이러한 모듈을 만드는 기반 클래스이다. 직접 모델 클래스를 작성할 때 이를 상속하면, 모델의 계산을 결정하는 값인 매개변수(Parameter)와 하위 모듈을 등록하고 관리할 수 있다.
모델 안에 들어가는 개별 층(Layer)도 모듈이고, 여러 층을 포함하는 전체 모델도 모듈이 될 수 있다. 이 중첩 구조 덕분에 전체 모델을 통해 내부 층의 매개변수에 접근할 수 있다.
nn.Linear는 PyTorch가 제공하는 모듈 중 하나이다. 가중합과 편향을 계산하는 선형층(Linear Layer)으로, 선형 회귀에서는 이 층 하나를 모델로 사용할 수 있다. 이를 직접 만든 클래스 안에 넣으면 계산에 이름과 구조를 부여하고, 이후 필요한 층이나 연산을 같은 모델 안에 추가할 수 있다.
클래스로 묶는다고 예측식이나 학습 원리가 달라지는 것은 아니다. 무엇을 보관하고 어떤 계산을 수행하는지 모델 자체가 관리하도록 구현 방식을 정리하는 것이다.
nn.Linear(in_features, out_features)의 두 인자는 각각 표본 하나의 입력 특성 수와 출력값 수이다. 여기서 특성(Feature)은 모델에 입력하는 개별 변수이다. 한 번에 넣는 표본의 개수는 이 인자에 포함하지 않는다.
표본 개의 입력을 행으로 쌓은 행렬을 라 하자. 표본마다 입력 특성이 개이고 출력이 개라면, nn.Linear(d, k)의 계산은 다음과 같다.
여기서 는 가중치(Weight), 는 편향(Bias), 는 예측값이다. PyTorch가 저장하는 가중치는 출력별로 한 행을 가지므로, 행렬 곱에서는 전치한 를 사용한다.
| 대상 | shape | 의미 |
|---|---|---|
입력 x | (n, d) | 표본마다 입력 특성 개 |
linear.weight | (k, d) | 출력 하나를 계산하는 가중치가 한 행 |
linear.bias | (k,) | 출력마다 편향 하나 |
출력 linear(x) | (n, k) | 표본마다 예측값 개 |
편향은 각 표본의 계산 결과에 같은 값이 더해지도록 브로드캐스팅된다. 가중치와 편향도 모든 표본에 공통으로 사용된다. 따라서 표본 수 이 바뀌어도 매개변수의 개수는 바뀌지 않는다.
입력 변수 하나로 값 하나를 예측하는 단순 선형 회귀에는 nn.Linear(1, 1)을 사용한다. 입력 변수 여러 개로 값 하나를 예측하는 다중 선형 회귀(Multiple Linear Regression)에는 nn.Linear(d, 1)을 사용한다. 입력 변수가 늘어나는 것은 입력 특성 축의 크기가 커지는 것이며, 입력 텐서의 축 수가 늘어난다는 뜻은 아니다.
기본값인 bias=True에서는 편향을 포함하므로 매개변수 원소 수는 개이다. bias=False로 만들면 편향 없이 가중합만 계산한다. 엄밀히 말해 편향을 포함하는 연산은 아핀 변환(Affine Transformation), 즉 선형 변환에 일정한 이동을 더한 형태이다.
직접 모델을 만들 때는 nn.Module을 상속하고, __init__에서 구성 요소를 준비한 뒤 forward에서 계산 과정을 정의한다.
__init__: 사용할 층을 만들고 보관한다__init__은 모델 객체를 생성할 때 실행되는 초기화 메서드이다. 먼저 super().__init__()을 호출해 부모인 nn.Module의 초기화를 수행한다. 그래야 매개변수와 하위 모듈을 등록하는 내부 구조가 준비된다.
이후 self.linear = nn.Linear(d, 1)처럼 층을 모델의 속성에 대입한다. self.linear에는 생성한 층이 보관되고, 이 층은 모델의 하위 모듈로 등록된다. 가중치와 편향은 그 층에 속한다.
층을 한 번 만들어 보관한다는 점이 중요하다. 입력이 들어올 때마다 forward 안에서 새로운 선형층을 만들면 매번 새 매개변수로 계산하게 된다. 학습으로 갱신한 값을 다음 계산에도 사용하려면 같은 층을 계속 호출해야 한다.
forward: 준비한 층으로 출력을 계산한다순전파(Forward Pass)는 입력으로부터 출력을 계산하는 과정이다. forward(self, x)에는 입력 x가 어떤 연산을 거쳐 출력이 되는지 작성한다. 선형층 하나를 사용하는 모델이라면 self.linear(x)의 결과를 반환하면 된다.
실제 모델을 사용할 때는 model(x)로 호출한다. 이 호출은 forward를 실행하면서, 호출 전후에 등록된 부가 처리인 훅(Hook) 등 nn.Module의 호출 절차도 거친다. model.forward(x)를 직접 호출하면 이 절차를 우회한다.
이렇게 나누면 모델을 읽을 때 __init__에서는 무엇을 가지고 있는지, forward에서는 그것을 어떻게 사용하는지 확인할 수 있다.
nn.Linear의 가중치와 편향은 nn.Parameter로 관리된다. nn.Parameter는 모듈의 매개변수로 등록할 수 있도록 만든 텐서의 하위 클래스이다. 이를 모듈의 속성에 대입하면 등록되며, 일반 텐서는 requires_grad=True여도 같은 방식으로 자동 등록되지 않는다.
이는 기울기를 계산할지와 모델의 매개변수로 관리할지가 서로 다른 기준이기 때문이다. 선형층을 사용하면 가중치와 편향이 이미 등록되어 있으므로, 이를 직접 nn.Parameter로 다시 감쌀 필요는 없다.
model.parameters()는 모델에 등록된 매개변수를 하나씩 꺼낼 수 있게 한다. 기본적으로 하위 모듈 안의 매개변수까지 포함하므로, self.linear에 속한 가중치와 편향도 가져온다. 매개변수의 이름까지 확인하려면 model.named_parameters()를 사용한다. 다만 이 메서드들은 requires_grad=False인 매개변수도 포함하므로, 기울기를 계산하는 값만 선별하는 기능으로 이해해서는 안 된다.
이제 torch.optim.SGD(model.parameters(), lr=...)처럼 옵티마이저(Optimizer)에 갱신할 매개변수와 학습률을 전달할 수 있다. 옵티마이저는 전달받은 매개변수를 정해진 규칙에 따라 갱신한다. 모델을 클래스로 작성해도 학습의 흐름은 다음과 같이 유지된다.
| 단계 | 호출 | 역할 |
|---|---|---|
| 예측 | prediction = model(x) | 현재 매개변수로 예측값 계산 |
| 손실 계산 | loss = F.mse_loss(prediction, y) | 예측값과 관측값의 차이 평가 |
| 기울기 초기화 | optimizer.zero_grad() | 이전 단계의 기울기가 누적되지 않도록 정리 |
| 기울기 계산 | loss.backward() | 손실을 매개변수로 미분해 기울기 계산 |
| 매개변수 갱신 | optimizer.step() | 계산한 기울기를 이용해 값 갱신 |
여기서 F는 torch.nn.functional의 별칭이다. F.mse_loss는 평균 제곱 오차(Mean Squared Error, MSE)를 계산하는 함수이다. 기본 설정인 reduction='mean'에서는 대응하는 원소의 차이를 제곱한 뒤 전체 원소에 대해 평균을 낸다. 출력이 하나라면 표본별 제곱 오차의 평균과 같다.
예측값과 관측값은 같은 shape로 맞춘다. 출력이 (n, 1)이면 관측값도 (n, 1)로 준비해야 한다. 관측값을 (n,)로 두면 브로드캐스팅 때문에 의도하지 않은 표본 간 비교가 생길 수 있다.
nn.Module은 구성과 매개변수를 관리하고, forward는 계산을 정의한다. 자동 미분은 기울기를 계산하며, 옵티마이저는 값을 갱신한다. 역할이 나뉘어 있으므로 model(x)를 실행하는 것만으로 학습이 이루어지지는 않는다.
앞의 구조를 입력 특성 수를 지정할 수 있는 선형 회귀 클래스로 구현하면 다음과 같다. 코드는 모델을 만들고, 표본 두 개를 입력한 뒤 출력과 매개변수의 shape를 확인한다.
import torch
from torch import nn
class LinearRegressor(nn.Module):
def __init__(self, in_features):
super().__init__()
self.linear = nn.Linear(in_features, 1)
def forward(self, x):
return self.linear(x)
model = LinearRegressor(in_features=3)
x = torch.tensor([[1.0, 2.0, 3.0],
[2.0, 0.0, 1.0]])
print("output:", tuple(model(x).shape))
for name, parameter in model.named_parameters():
print(name, tuple(parameter.shape))
output: (2, 1)
linear.weight (1, 3)
linear.bias (1,)
위 출력은 PyTorch 2.13.0의 CPU 환경에서 실행해 확인했다. 입력의 두 행은 표본 두 개이고, 세 열은 각 표본의 특성 세 개이다. 모델은 각 행에서 값 하나를 계산하므로 출력은 (2, 1)이다.
등록된 텐서는 가중치와 편향 두 개이며, 그 안의 매개변수 원소는 가중치 세 개와 편향 한 개로 총 네 개이다. linear.weight라는 이름은 가중치가 linear 하위 모듈에 속한다는 것도 보여준다.
nn.Linear는 가중치와 편향을 무작위로 초기화한다. 아래에서는 계산을 확인하기 위해 그 값을 별도로 지정했다고 가정한다.
각 표본에 같은 가중치와 편향을 적용하면 다음 예측값을 얻는다.
관측값이 각각 , 이면 평균 제곱 오차는 다음과 같다.
이를 F.mse_loss로 계산할 때 예측값과 관측값은 모두 (2, 1)로 준비한다. 이 손실로 backward()를 수행하면 linear.weight와 linear.bias에 대한 기울기를 구할 수 있고, 옵티마이저는 전달받은 이 매개변수를 갱신한다. 이후 다시 model(x)를 호출하면 갱신된 값으로 계산한다.
입력 특성이 하나인 경우에는 같은 클래스에 in_features=1을 전달하고 입력을 (n, 1)로 준비하면 된다. 입력 특성 수에 맞춰 내부 가중치의 크기가 달라지며, 모델 호출과 학습 과정의 연결 방식은 동일하다.
nn.Module은 모델의 매개변수와 하위 모듈을 관리하는 기반 클래스이다.nn.Linear(d, k)는 표본마다 특성 개를 받아 값 개를 계산한다. 가중치의 shape는 (k, d)이며 표본 수와는 무관하다.__init__에서 사용할 층을 준비하고, forward에서 그 층을 이용한 계산을 정의한다. 모델은 model(x)로 호출한다.model.parameters()로 등록된 매개변수를 옵티마이저에 전달한다. 예측, 손실 계산, 미분, 갱신은 각 역할에 맞게 연결한다.모델을 직접 작성하거나 기존 코드를 읽을 때는 층이 올바르게 등록되어 있는지, 입력의 마지막 축이 층의 입력 크기와 맞는지, forward가 의도한 계산을 반환하는지 확인하면 된다. 이 구조를 유지하면 층이 늘어나도 모델의 구성과 학습 과정을 구분해 다룰 수 있다.
forward, parameters, named_parameters.