【Pytorch】 Pytorch 튜토리얼 3 - pytorch로 딥러닝하기
(Pytorch) Pytorch 튜토리얼 3 - pytorch로
NEURAL NETWORKS 구성
"""
# nn 패키지를 사용한 신경망 생성
신경망의 학습 과정
1. 학습 가능한 매개변수(또는 가중치(weight))를 갖는 신경망을 정의합니다.
2. 데이터셋(dataset)을 이용해 아래의 과정을 반복한다.
3. 입력을 신경망에서 전파(process == forward)합니다.
4. 손실을 계산합니다.
5. 변화도(gradient)를 신경망의 매개변수들에 역으로 전파 -> d_Loss / d_parametar 를 구한다.
6. 신경망의 가중치를 갱신합니다. weight = weight - learning_rate * gradient
"""
pass
import torch
import torch.nn as nn
import torch.nn.functional as F
# F를 import하는 이유 : Implements forward and backward definitions of an autograd operation.
class Net(nn.Module): # nn.Module 클래스를 상속하여 Net 클래스를 생성한다.
def __init__(self): # para 없음
super(Net,self).__init__() # child class에서 parent class의 __init__ 내용들을 사용하고 싶다.
self.conv1 = nn.Conv2d(1,6,kernel_size = 3)
self.conv2 = nn.Conv2d(6,16,kernel_size = 3)
self.relu = nn.ReLU()
self.maxP1 = nn.MaxPool2d(kernel_size=2,stride=2)
self.fc1 = nn.Linear(16*6*6, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x): # para에 x가 들어간다!
'''[홈페이지 방법]
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
print(x.size()) # 1*6*15*15
'''
x = self.maxP1(self.relu(self.conv1(x)))
# print(x.size()) # masP1의 stride가 1일 때. -> 1*6*29*29. // stride가 2일 때. -> 1*6*15*15
x = F.max_pool2d(F.relu(self.conv2(x)), 2) # maxpool은 정사각형이기 때문에 2만 넣어도 (2,2)로 인식한다.
x = x.view(-1, self.num_flat_features(x)) # torch.Size([1, 16, 6, 6]) -> num_flat_feature를 하고 난 후의 x의 torch.Size([1, 576])
# x = F.relu(self.fc1(x)) [홈페이지 방법]
# print("num_flat_feature를 하고 난 후의 x의" , x.shape)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension (3차원이면 channel, 4차원이면 이미지 갯수)
num_features = 1
for s in size:
num_features *= s # 예를 들어 tensor sief (120, 6, 6) 라면, 6*6 = 36 을 return 한다.
return num_features
net = Net()
print(net) # __init__ 에 정의한 내용이 나타난다. forward 아님!
"""
forward 함수만 정의하고 나면, (변화도를 계산하는) backward 함수는 autograd 를 사용하여 자동으로 정의된다.
모델의 학습가능한 매개변수들은 nn.Module 클래스의 parameters 매소드를 통해 확인할 수 있다.
"""
pass
Net(
(conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
(relu): ReLU()
(maxP1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(fc1): Linear(in_features=576, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
params = list(net.parameters())
print(len(params)) # 10개에는 conv filter의 parameter, FC의 parameter, bias 등이 포함되어 있다. 자세한 건 나중에 더 자세히 알아보기
print(params[0].size()) # conv1's .weight
10
torch.Size([6, 1, 3, 3])
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
"""
*** NOTE 중요 ***
굳이 1*1*32*32의 이미지를 넣는 이유 :
torch.nn 은 미니-배치(mini-batch)만 지원합니다. torch.nn 패키지 전체는 하나의 샘플이 아닌, 샘플들의 미니-배치만을 입력으로 받습니다.
만약 하나의 샘플만 있다면, input.unsqueeze(0) 을 사용해서 가짜 차원을 추가합니다.
"""
pass
num_flat_feature를 하기 전의 x의 torch.Size([1, 16, 6, 6])
num_flat_feature를 하고 난 후의 x의 torch.Size([1, 576])
tensor([[ 0.0136, 0.0084, 0.1542, -0.0920, -0.0778, 0.0385, -0.0898, 0.0524,
-0.0445, 0.0961]], grad_fn=<AddmmBackward>)
net.zero_grad() # 1. Sets gradients of all model parameters to zero.
out.backward(torch.randn(1, 10)) # # 2. Backpropa. 1*10 백터를 정규분포에서 랜덤한 값을 가져와 생성한 tensor.
# print(input.grad) -> None 이라고 나온다. 아직은 뭔지 모르겠으니 좀 더 공부해보자.
Loss Function
https://pytorch.org/docs/stable/nn.html#loss-functions
여기에 존재하는 다양한 loss functions 중에서 하나를 사용해보자.
MSEloss는 가장 단순한 mean-squared error Loss이다. (평균제곱오차)
input = torch.randn(1, 1, 32, 32)
out = net(input) # 예측값
target = torch.randn(10) # 정답값
# target = target.unsqueeze(1) -> tensor.size([10,1])
target = target.view(1,-1)
print("out의 사이즈 : {} ==(같아야 한다) target의 사이즈 {}".format(out.size(), target.size()))
criterion = nn.MSELoss() # 이 loss function을 기준으로 삼겠다.
loss = criterion(out, target)
print("{} type, loss : {}".format(type(loss),loss))
out의 사이즈 : torch.Size([1, 10]) ==(같아야 한다) target의 사이즈 torch.Size([1, 10])
<class 'torch.Tensor'> type, loss : 1.5556150674819946
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
# 다음과 같은 방법으로, 최근에 한 마지막 연산들을 찾아갈 수 있다.
# 아래의 grad_fn 과 next_function은 torch document에도 없고 여기에만 있다...
print(loss.grad_fn) # MSELoss // # 사용자가 마지막으로 이 변수를 만들 때 사용하는 Function을 참조한다. (기억해 놓는다.)
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions) # ReLU
<MseLossBackward object at 0x000001915F1C0A88>
<AddmmBackward object at 0x000001915F1C0C88>
((<AccumulateGrad object at 0x000001915F1C0A88>, 0), (<ReluBackward0 object at 0x000001915F1C83C8>, 0), (<TBackward object at 0x000001915F1C8908>, 0))
오차(error)를 역전파하기 위해서는 loss.backward() 만 해주면 된다.
과거의 gradient를 없애는 작업이 필요한데( .zero_grad() ), 그렇지 않으면 gradient가 기존의 것과 누적되기 때문이다.
***
각 파라미터(가중치, 편향)에 대한 Loss 미분값을 알아내는 방밥
# 각 파라미터(가중치, 편향)에 대한 Loss 미분값을 알아내는 방밥
net.zero_grad() # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad) # 이렇게 method를 사용하는 것도,
loss.backward()
print('\nconv1.bias.grad after backward') # d_loss / d_bias_at-conv1
print(net.conv1.bias.grad)
print("\n bias 들의 실제 값 = ", net.conv1.bias)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0211, -0.0160, 0.0067, -0.0190, -0.0056, 0.0125])
ias 들의 실제 값 = Parameter containing:
tensor([-0.2747, -0.2196, -0.2772, -0.1524, 0.2187, 0.0836],
requires_grad=True)
위에서 net.conv1.bias 는 그저 tensor이다. (https://pytorch.org/docs/stable/nn.html#conv2d 에서 확인가능 conv2d.weight도 사용 가능.)
그리고 grad는 torch.autograd.grad 를 이용한 것이다. (https://pytorch.org/docs/stable/autograd.html 에서 확인가능)
grad 함수 : xxx.grad에서 d_? / d_xxx 값을 tensor형태로 뱉어 준다.
가중치 갱신
확률적 경사하강법(SGD; Stochastic Gradient Descent)
# 아주 단순하게 가중치 갱신하는 방법
learning_rate = 0.01
# for를 돌아서, 내가 가진 모든 layers의 파라메터를 모두, 한번 갱신한다.
for i, f in enumerate( net.parameters() ):
print("{}번째 parameters 갱신 완료: {}".format(i+1, f.size()))
f.data.sub_(f.grad.data * learning_rate)
# 굳이 data method를 사용하는 이유는 모르겠다.
# https://pytorch.org/docs/stable/tensors.html?highlight=sub_#torch.Tensor.sub_
# net.parameters를 이용해도 되고, 위에 처럼 net.conv1을 이용해도 좋다. print를 통해 알아보자.
print("\n", type(list(net.parameters())[0]), '\n',
type(list(net.parameters())[0].grad), '\n',
type(list(net.parameters())[0].grad.data), '\n',
list(net.parameters())[1].grad, '\n',
list(net.parameters())[1].grad.data )
1번째 parameters 갱신 완료: torch.Size([6, 1, 3, 3])
2번째 parameters 갱신 완료: torch.Size([6])
3번째 parameters 갱신 완료: torch.Size([16, 6, 3, 3])
4번째 parameters 갱신 완료: torch.Size([16])
5번째 parameters 갱신 완료: torch.Size([120, 576])
6번째 parameters 갱신 완료: torch.Size([120])
7번째 parameters 갱신 완료: torch.Size([84, 120])
8번째 parameters 갱신 완료: torch.Size([84])
9번째 parameters 갱신 완료: torch.Size([10, 84])
10번째 parameters 갱신 완료: torch.Size([10])
<class 'torch.nn.parameter.Parameter'>
<class 'torch.Tensor'>
<class 'torch.Tensor'>
tensor([ 0.0211, -0.0160, 0.0067, -0.0190, -0.0056, 0.0125])
tensor([ 0.0211, -0.0160, 0.0067, -0.0190, -0.0056, 0.0125])
# Optimizer를 사용해서 갱신하는 방법. https://pytorch.org/docs/stable/optim.html#algorithms -> 여기에 있는 가중치 갱신 알고리즘 사용할 것.
# 위의 과정이 optimizer.stop() 이라는 것 하나로 그대로 잘 동작한다.
import torch.optim as optim
# Optimizer를 생성합니다.
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 학습 과정(training loop)에서는 다음과 같습니다:
optimizer.zero_grad() # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update. 다양한 종류의 optimizer 들은 모두 자신만의 step method가 구현되어 있다.