경사 하강법
경사 하강법(Gradient Descent) : 함수의 기울기가 낮은 곳으로 계속 이동하는 것을 반복해 극값에 도달하는 알고리즘.
함수의 기울기가 가장 낮은 곳에 도달해야 최적의 해를 갖기 때문에, 경사 하강법을 사용한다.
다음 수식은 경사 하강법의 적용 방식을 설명한다.
$$ W_{0} = \text{Initial Value} $$
$$ W_{i+1} = W_{i} - \alpha \nabla f \left(W_{i} \right)$$
경사 하강법 등의 최적화 함수들은 초깃값($ W_{0} $)을 설정한 뒤, 다음 가중치($ W_{1}, W_{2}, \cdots $)를 찾는다.
새로운 가중치는 기울기($ \nabla f \left(W_{i} \right) $)의 부호와 관계 없이, 기울기가 0인 방향으로 학습을 진행한다.
$ \alpha $ 를 이용해 기울기가 한 번에 이동하는 간격을 조정한다.
가중치 갱신 방법
가설을 $ \hat{Y_i} = W \times x + b_i $ 로 세우면, 손실 함수는 다음과 같이 정리할 수 있다.
$$ \hat{Y_i} = W_{i} \times x + b_i $$
$$ MSE(W, b) = \frac{1}{n} \sum_{i=1}^n \left({Y_i} - {\hat{Y_i}} \right)^2 $$
이제 이 손실 함수를 위의 경사 하강법 수식에 적용해 가중치를 갱신한다.
이때 가중치의 기울기를 확인하기 위해 W에 대해 편미분을 진행한다.
$$ \begin{align*}
W_{i+1} &= W_{i} - \alpha \frac{\partial}{\partial W} MSE \left(W, b \right) \\
&= W_{i} - \alpha \frac{\partial}{\partial W} \frac{1}{n} \sum_{i=1}^n\left({Y_i} - {\hat{Y_i}} \right)^2 \\
&= W_{i} - \alpha \frac{\partial}{\partial W} \sum_{i=1}^n\left[ \frac{1}{n} \left\{ Y_{i} - \left(W_{i} \times x + b_{i} \right)\right\}^2\right] \\
&= W_{i} - \alpha \times \frac{2}{n} \sum_{i=1}^n\left[Y_{i} - \left(W_{i} \times x + b_{i} \right) \times \left(-x \right)\right] \\
&= W_{i} - \alpha \times \frac{2}{n} \sum_{i=1}^n \left({Y_i} - {\hat{Y_i}} \right) \times \left (-x \right) \\
&= W_{i} - \alpha \times \frac{2}{n} \sum_{i=1}^n \left({\hat{Y_i}} - {Y_i} \right) \times x \\
&= W_{i} - \alpha \times 2E \left[\left({\hat{Y_i}} - {Y_i} \right) \times x \right] \\
\end{align*} \\ $$
$$ \therefore W_{i+1} = W_{i} - \alpha \times E \left [ \left({\hat{Y_i}} - {Y_i} \times x \right)\right] $$
경사 하강법을 적용한 새로운 가중치 수식은 $ W_{i+1} = W_{i} - \alpha \times E \left [ \left({\hat{Y_i}} - {Y_i} \times x \right)\right] $ 의 형태로 정리할 수 있다.
임의의 값인 초기 가중치 $ W_{0} $ 를 위 수식에 대입하면 $ W_{1} $ 을 구할 수 있고, 이 과정을 반복해 최적의 가중치를 찾는다.
평균을 계산할 때, 2의 값은 갱신 과정에 큰 영향을 주지 않기 때문에 생략하기도 한다.
가중치 갱신 방법
위의 $ \alpha $ 값을 머신러닝에서는 학습률(Learning Rate) 이라 부른다.
초깃값($W_{0}$)을 임의의 값으로 지정하듯이, 학습률($\alpha$)도 임의의 값으로 설정한다.
학습률에 따라 다음 가중치($ W_{1}, W_{2}, \cdots $)의 변화량과 최적의 해를 찾기 위한 반복 횟수가 결정된다.
적절하지 않은 학습률을 선택하면 너무 많은 반복을 하게 되거나, 값이 발산해버리므로, 적절한 학습률을 선택하는 것이 중요하다.

최적화 문제
위에서 언급했듯이, 학습률을 너무 낮거나 높게 잡으면, 최적의 가중치를 찾는 시간이 오래 걸리거나, 그래프가 발산하는 문제점이 있다.
하지만, 항상 학습률을 너무 낮게 설정하면, 아래의 그래프와 같이 극솟값이 있는 그래프의 극소 지점을 넘지 못하는 문제점이 발생한다.

파이토치로 구현하는 단순 선형 회귀
파이토치는 확률적 경사 하강법(torch.optim.SGD) 클래스를 이용해 경사 하강법 기법을 간편하게 사용할 수 있다.
확률적 경사 하강법(Stochastic Gradient Descent, SGD) : 모든 데이터에 대해 연산을 진행하지 않고, 일부 데이터만 계산해 빠르게 최적화된 값을 찾는 기법
torch.optim.SGD는 역전파의 결과로 얻어진 기울기를 활용해, 파라미터를 업데이트한다.
# 데이터 선언
x = torch.FloatTensor([
[1], [2], [3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19], [20],
[21], [22], [23], [24], [25], [26], [27], [28], [29], [30],
])
y = torch.FloatTensor([
[0.94], [1.98], [2.88], [3.92], [3.96], [4.55], [5.64], [6.3], [7.44], [9.1],
[8.46], [9.5], [10.67], [11.16], [14], [11.83], [14.4], [14.25], [16.2], [16.32],
[17.46], [19.8], [18], [21.34], [22], [22.5], [24.57], [26.04], [21.6], [28.8]
])
# 하이퍼파라미터 초기화
#requires_grad는 자동 미분의 사용 여부를 체크하는 파라미터로, 이를 이용해 학습을 간편하게 진행할 수 있음.
weight = torch.zeros(1, requires_grad=True)
bias = torch.zeros(1, requires_grad=True)
learning_rate = 1e-3
# 옵티마이저 선언
optimizer = optim.SGD([weight, bias], lr=learning_rate) # 확률적 경사 하강법 함수, 최적화하고자 하는 변수와 학습률을 인자로 사용.
에폭 (Epoch): 인공 신경망에서 모델 연산을 할 때, 전체 데이터세트가 1회 통과하는 것을 의미하며,
각 에폭은 모델이 데이터를 학습하고 가중치를 갱신하는 단계를 나타낸다.
에폭값이 너무 적으면 학습이 제대로 되지 않는 과소적합(Underfitting) 이 발생하지만,
그렇다고 에폭값을 지나치게 크게 잡으면 과대적합(Overfitting) 이 발생하므로, 에폭값을 적절하게 조정하는 것이 중요하다.
# 에폭, 가설, 손실 함수 선언
for epoch in range(10000):
y_hat = weight * x + bias # 가설
cost = torch.mean((y_hat - y) ** 2) # 손실 함수
가중치와 편향을 갱신하기 위해서 zero_grad(), backward(), step() 이 세 가지 메소드를 사용한다.
텐서의 기울기는 grad 속성에 누적해서 더해지기 때문에, zero_grad() 를 이용해 0으로 초기화하는 과정을 거친다.
(weight = x가 아닌, weight += x의 방식으로 저장되기 때문)
그 다음, backward() 메소드를 통해 역전파를 수행해, optimizer에 포함된 변수들의 기울기를 새로 계산하고, step() 메소드로 이를 갱신한다.
# weight와 bias 갱신
optimizer.zero_grad() # 옵티마이저를 0으로 초기화
cost.backward() # 역전파를 통한 손실 함수 계산
optimizer.step() # 옵티마이저 갱신
# 단순 선형 회귀 (전체 코드)
import torch
import torch.optim as optim # 다양한 최적화 함수를 사용하기 위한 모듈
# 데이터 선언
x = torch.FloatTensor([
[1], [2], [3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19], [20],
[21], [22], [23], [24], [25], [26], [27], [28], [29], [30],
])
y = torch.FloatTensor([
[0.94], [1.98], [2.88], [3.92], [3.96], [4.55], [5.64], [6.3], [7.44], [9.1],
[8.46], [9.5], [10.67], [11.16], [14], [11.83], [14.4], [14.25], [16.2], [16.32],
[17.46], [19.8], [18], [21.34], [22], [22.5], [24.57], [26.04], [21.6], [28.8]
])
weight = torch.zeros(1, requires_grad=True)
bias = torch.zeros(1, requires_grad=True)
learning_rate = 1e-3
optimizer = optim.SGD([weight, bias], lr=learning_rate)
for epoch in range(10000):
y_hat = weight * x + bias
cost = torch.mean((y_hat - y) ** 2)
optimizer.zero_grad()
cost.backward()
optimizer.step()
if (epoch + 1) % 1000 == 0:
print(f"Epoch: {epoch + 1: 4d}, Weight: {weight.item(): .3f}, Bias: {bias.item(): .3f}, Cost: {cost.item(): .3f}")
Epoch: 1000, Weight: 0.864, Bias: -0.138, Cost: 1.393
Epoch: 2000, Weight: 0.870, Bias: -0.251, Cost: 1.380
Epoch: 3000, Weight: 0.873, Bias: -0.321, Cost: 1.375
Epoch: 4000, Weight: 0.875, Bias: -0.364, Cost: 1.373
Epoch: 5000, Weight: 0.877, Bias: -0.391, Cost: 1.373
Epoch: 6000, Weight: 0.878, Bias: -0.408, Cost: 1.372
Epoch: 7000, Weight: 0.878, Bias: -0.419, Cost: 1.372
Epoch: 8000, Weight: 0.878, Bias: -0.425, Cost: 1.372
Epoch: 9000, Weight: 0.879, Bias: -0.429, Cost: 1.372
Epoch: 10000, Weight: 0.879, Bias: -0.432, Cost: 1.372
zero_grad(), backward(), step() 톺아보기
weight = torch.zeros(1, requires_grad=True)
bias = torch.zeros(1, requires_grad=True)
learning_rate = 1e-3
optimizer = optim.SGD([weight, bias], lr=learning_rate)
for epoch in range(3):
y_hat = weight * x + bias
cost = torch.mean((y_hat - y) ** 2)
print(f"Epoch: {epoch + 1}")
print(f"Before: Gradient: {weight.grad}, Weight: {weight.item(): .5f}")
optimizer.zero_grad()
print(f"After zero_grad(): Gradient: {weight.grad}, Weight: {weight.item(): .5f}") # Gradient가 None으로 초기화됨
cost.backward()
print(f"After backward(): {weight.grad}, Weight: {weight.item(): .5f}") # 역전파를 통한 Gradient 계산
optimizer.step()
print(f"After step(): {weight.grad}, Weight: {weight.item(): .5f}") # 계산 결과를 Weight에 갱신
Epoch: 1
Before: Gradient: None, Weight: 0.00000
After zero_grad(): Gradient: None, Weight: 0.00000
After backward(): tensor([-540.4854]), Weight: 0.00000
After step(): tensor([-540.4854]), Weight: 0.54049
Epoch: 2
Before: Gradient: tensor([-540.4854]), Weight: 0.54049
After zero_grad(): Gradient: None, Weight: 0.54049
After backward(): tensor([-198.9818]), Weight: 0.54049
After step(): tensor([-198.9818]), Weight: 0.73947
Epoch: 3
Before: Gradient: tensor([-198.9818]), Weight: 0.73947
After zero_grad(): Gradient: None, Weight: 0.73947
After backward(): tensor([-73.2604]), Weight: 0.73947
After step(): tensor([-73.2604]), Weight: 0.81273
신경망 패키지를 이용한 단순 선형 회귀
가중치와 편향을 각각의 변수로 선언하는 대신, 신경망(Neural Networks) 패키지 를 이용해 모델을 빠르고 간편하게 구현할 수 있다.
선형 변환 클래스는 $y = Wx + b$ 형태의 선형 변환을 입력 데이터에 적용할 수 있게 한다.
입력·출력 데이터 차원 크기를 설정해 인스턴스를 생성하며, 생성된 인스턴스는 입력 데이터 차원 크기와 동일한 텐서만을 입력으로 받으며,
입력된 텐서는 순방향 연산을 통해 출력 데이터 차원 크기의 차원으로 변환된다.
# 선형 변환 클래스
layer = torch.nn.Linear(
in_features=None, # 입력 데이터의 차원 크기
out_features=None, # 출력 데이터의 차원 크기
bias=True, # bias 사용 여부
device=None, # device와 dtype은 텐서의 매개변수와 동일함
dtype=None
)
model = torch.nn.Linear(1, 1, bias=True)
torch.nn.MSELoss는 평균 제곱 오차 손실 함수를 구현한 클래스로,
$\frac{1}{n}\sum_{i=1}^n\left(Y_{i} - \hat{Y_{i}}\right)^2$ 형태의 평균 제곱 오차를 계산한다.
선형 변환 클래스와 마찬가지로, 순방향 연산을 통해 평균 제곱 오차를 계산한다.
criterion = torch.nn.MSELoss() # 평균 제곱 오차 클래스
# 순방향 연산
for epoch in range(10000):
output = model(x)
cost = criterion(output, y)
# 신경망 패키지를 이용한 단순 선형 회귀 (전체 코드)
import torch
import torch.nn as nn
import torch.optim as optim
# 데이터 선언
x = torch.FloatTensor([
[1], [2], [3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19], [20],
[21], [22], [23], [24], [25], [26], [27], [28], [29], [30],
])
y = torch.FloatTensor([
[0.94], [1.98], [2.88], [3.92], [3.96], [4.55], [5.64], [6.3], [7.44], [9.1],
[8.46], [9.5], [10.67], [11.16], [14], [11.83], [14.4], [14.25], [16.2], [16.32],
[17.46], [19.8], [18], [21.34], [22], [22.5], [24.57], [26.04], [21.6], [28.8]
])
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1e-3)
for epoch in range(10000):
output = model(x)
cost = criterion(output, y)
optimizer.zero_grad()
cost.backward()
optimizer.step()
if (epoch + 1) % 1000 == 0:
print(f"Epoch: {epoch + 1: 4d}, Model: {list(model.parameters())}, Cost: {cost: .3f}") # weight와 bias 변수가 없으므로 model.parameters()를 대신 출력
Epoch: 1000, Model: [Parameter containing:
tensor([[0.8856]], requires_grad=True), Parameter containing:
tensor([-0.5711], requires_grad=True)], Cost: 1.377
Epoch: 2000, Model: [Parameter containing:
tensor([[0.8830]], requires_grad=True), Parameter containing:
tensor([-0.5200], requires_grad=True)], Cost: 1.374
Epoch: 3000, Model: [Parameter containing:
tensor([[0.8815]], requires_grad=True), Parameter containing:
tensor([-0.4883], requires_grad=True)], Cost: 1.373
(생략)
Epoch: 9000, Model: [Parameter containing:
tensor([[0.8790]], requires_grad=True), Parameter containing:
tensor([-0.4390], requires_grad=True)], Cost: 1.372
Epoch: 10000, Model: [Parameter containing:
tensor([[0.8790]], requires_grad=True), Parameter containing:
tensor([-0.4378], requires_grad=True)], Cost: 1.372'ML · DL > Pytorch 공부' 카테고리의 다른 글
| [파이토치 기초] 데이터세트 분리 (0) | 2025.01.07 |
|---|---|
| [파이토치 기초] 모듈 (0) | 2025.01.04 |
| [파이토치 기초] 데이터세트 & 데이터로더 (0) | 2024.12.30 |
| [파이토치 기초] 손실 함수 (0) | 2024.12.24 |
| [파이토치 기초] 텐서 (0) | 2024.12.16 |