꺼내먹는지식 준
Multi layer Perceptron 간단 구현 본문
Multi Layer Perceptron 에 대한 설명은 블로그의 글을 참고하자.
Pytorch 로 Multi layer Perceptron 의 간단 구현 내용만 정리할 예정이다.
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
%matplotlib inline
from torchvision import datasets,transforms
mnist_train = datasets.MNIST(root='./data/',train=True,transform=transforms.ToTensor(),download=True)
mnist_test = datasets.MNIST(root='./data/',train=False,transform=transforms.ToTensor(),download=True)
print ("mnist_train:\n",mnist_train,"\n")
print ("mnist_test:\n",mnist_test,"\n")
'''
mnist_train:
Dataset MNIST
Number of datapoints: 60000
Root location: ./data/
Split: Train
StandardTransform
Transform: ToTensor()
mnist_test:
Dataset MNIST
Number of datapoints: 10000
Root location: ./data/
Split: Test
StandardTransform
Transform: ToTensor()
'''
MNIST 데이터를 다운 받자.
BATCH_SIZE = 256
train_iter = torch.utils.data.DataLoader(mnist_train,batch_size=BATCH_SIZE,shuffle=True,num_workers=1)
test_iter = torch.utils.data.DataLoader(mnist_test,batch_size=BATCH_SIZE,shuffle=True,num_workers=1)
Batch size 256 으로 dataloader 를 만들자.
dataloader 의 설명은 해당 블로그의 다음의 글을 참고하자.
mnist_train[0][0].shape
#torch.Size([1, 28, 28])
형태를 찍어보니 다음과 같이 생겼다.
class MultiLayerPerceptronClass(nn.Module):
"""
Multilayer Perceptron (MLP) Class
"""
def __init__(self,name='mlp',xdim=784,hdim=256,ydim=10):
super(MultiLayerPerceptronClass,self).__init__()
self.name = name
self.xdim = xdim # 28 \times 28
self.hdim = hdim
self.ydim = ydim
self.lin_1 = nn.Linear(xdim, hdim)
self.lin_2 = nn.Linear(hdim, ydim)
self.init_param() # initialize parameters
def init_param(self):
nn.init.kaiming_normal_(self.lin_1.weight)
nn.init.zeros_(self.lin_1.bias)
nn.init.kaiming_normal_(self.lin_2.weight)
nn.init.zeros_(self.lin_2.bias)
def forward(self,x):
net = x
net = self.lin_1(net)
net = F.relu(net)
net = self.lin_2(net)
return net
M = MultiLayerPerceptronClass(name='mlp',xdim=784,hdim=256,ydim=10).to(device)
loss = nn.CrossEntropyLoss()
optm = optim.Adam(M.parameters(),lr=1e-3)
xdim = 784인 이유는 28 $\times$ 28 = 784 이기 때문이다.
Linear layer 두개를 통하여
기존
batch_size $\times$ 784 형태의 데이터가
Layer_1
$\rightarrow$ batch_size $\times$ 256
Layer_2
$\rightarrow$ batch_size $\times$ 10
로 변형된다.
이 부분이 이해가 안간다면 Pytorch 튜토리얼 하나정도는 공부를 해보고 오는 것을 추천한다.
학습 시키기 전에 forward 를 한번 해보자 .
x_numpy = np.random.rand(2,784) # 그냥 forward 해보는 것 batch size 2
x_torch = torch.from_numpy(x_numpy).float().to(device)
y_torch = M.forward(x_torch) # forward path
y_numpy = y_torch.detach().cpu().numpy() # torch tensor to numpy array
print ("x_numpy:\n",x_numpy)
print ("x_torch:\n",x_torch)
print ("y_torch:\n",y_torch)
print ("y_numpy:\n",y_numpy)
'''
x_numpy:
[[0.04766383 0.70261635 0.27965152 ... 0.18159078 0.61542671 0.14078455]
[0.97280622 0.85804207 0.49590762 ... 0.64504606 0.97776317 0.26329326]]
x_torch:
tensor([[0.0477, 0.7026, 0.2797, ..., 0.1816, 0.6154, 0.1408],
[0.9728, 0.8580, 0.4959, ..., 0.6450, 0.9778, 0.2633]],
device='cuda:0')
y_torch:
tensor([[-0.9576, 0.6574, 1.8398, -1.0339, 0.1606, 0.2491, 0.7136, -0.6153,
-1.2875, -0.7939],
[-0.6136, 0.2710, 0.3711, -0.2268, 0.2433, 0.7747, 0.8244, -0.1935,
-0.4220, 0.0093]], device='cuda:0', grad_fn=<AddmmBackward0>)
y_numpy:
[[-0.95763147 0.6573881 1.8398474 -1.0338606 0.16059192 0.24914053
0.7136306 -0.6153022 -1.2874693 -0.79394203]
[-0.61359006 0.2709848 0.37112164 -0.2267636 0.24327134 0.7746552
0.8243533 -0.1935375 -0.42197427 0.00931841]]
'''
단순하게 batch _size 2의 데이터에 난수를 넣어서 생성
데이터를 torch 데이터로 바꾼 후, GPU 에 올린다.
그 후 forward
GPU 에 있던 데이터를 CPU로 옮기며 numpy 형태의 데이터로 재 변환해보았다.
출력 연산은 위와 같다.
np.set_printoptions(precision=3)
n_param = 0
for p_idx,(param_name,param) in enumerate(M.named_parameters()):
param_numpy = param.detach().cpu().numpy()
print("before", param_numpy.shape)
print("after", param_numpy.reshape(-1).shape)
n_param += len(param_numpy.reshape(-1))
print ("[%d] name:[%s] shape:[%s]."%(p_idx,param_name,param_numpy.shape))
print (" val:%s"%(param_numpy.reshape(-1)[:5]))
print ("Total number of parameters:[%s]."%(format(n_param,',d')))
'''
before (256, 784)
after (200704,)
[0] name:[lin_1.weight] shape:[(256, 784)].
val:[ 0.013 -0.061 0.065 -0.053 -0.01 ]
before (256,)
after (256,)
[1] name:[lin_1.bias] shape:[(256,)].
val:[0. 0. 0. 0. 0.]
before (10, 256)
after (2560,)
[2] name:[lin_2.weight] shape:[(10, 256)].
val:[-0.067 0.127 0.044 -0.044 -0.082]
before (10,)
after (10,)
[3] name:[lin_2.bias] shape:[(10,)].
val:[0. 0. 0. 0. 0.]
Total number of parameters:[203,530].
'''
detach()와 clone()은 기존 Tensor를 복사하는 방법 중 하나
detach() : 기존 Tensor에서 gradient 전파가 안되는 텐서 생성 단 storage를 공유하기에 detach로 생성한 Tensor가 변경되면 원본 Tensor도 똑같이 변한다. (미분을 하고 싶지 않을 때 사용한다.)
clone() : 기존 Tensor와 내용을 복사한 텐서 생성
흥미로운 사실은 다음과 같이 parameter 마다 어디 parameter 인지 name 이 달린다는 것이다.
[0] before (256, 784) after (200704,) - linear 1 weight
[1] before (256,) after (256,) - linear 1 bias
[2] before (10, 256) after (2560,) - linear 2 weight
[3] before (10,) after (10,) - linear 2 bias
궁금한 점은 왜 데이터의 shape 이 (256, 10) 형태가 아니고 (10, 256) 으로 나타나는가 이다.
$y = W^TX +b$
def func_eval(model,data_iter,device):
with torch.no_grad():
model.eval() # evaluate (affects DropOut and BN)
n_total,n_correct = 0,0
for batch_in,batch_out in data_iter:
y_trgt = batch_out.to(device)
model_pred = model(batch_in.reshape(-1, 28*28).to(device))
_,y_pred = torch.max(model_pred.data,1)
n_correct += (y_trgt == y_pred).sum().item()
n_total += batch_in.size(0)
val_accr = (n_correct/n_total)
model.train() # back to train mode
return val_accr
print ("Done")
https://coffeedjimmy.github.io/pytorch/2019/11/05/pytorch_nograd_vs_train_eval/
Pytorch에서 no_grad()와 eval()의 정확한 차이는 무엇일까?
.
coffeedjimmy.github.io
torch.no_grad()
no_grad() with statement에 포함시키게 되면 Pytorch는 autograd engine을 꺼버린다. 즉 더 이상 자동으로 gradient를 트랙킹하지 않는다.
다음과 같은 의문이 생긴다.
loss.backward()를 통해 backpropagation을 진행하지 않는다면 뭐 gradient를 계산하든 상관이 없는 것이 아닌가?
torch.no_grad()의 주된 목적은 autograd를 끔으로써 메모리 사용량을 줄이고 연산 속도를 높히기 위함이다. 사실상 안쓸 gradient이기에 inference시에 굳이 계산할 필요가 없다.
따라서 일반적으로 inference를 진행할 때는 torch.no_grad() with statement로 감싼다.
model.eval()
torch.no_grad() model.eval() 중 하나만 쓰면 되는거 아닐까 라는 의문이 생긴다.
더 이상 gradient를 계산 하지 않는 상황이다. 그러나, model.eval()은 다른 역할이 있다.
2019년 시점에서는 모델링 시 training과 inference시에 다르게 동작하는 layer들이 존재한다.
예를 들면, Dropout layer는 학습시에는 동작해야하지만, inference시에는 동작하지 않는다.
BatchNorm같은 경우도 마찬가지다.
사실상 model.eval()는 이런 layer들의 동작을 inference(eval) mode로 바꿔준다는 목적으로 사용된다.
즉, 우리가 보통 원하는 모델의 동작을 위해서는 위의 두 가지를 모두 사용해야하는 것이 맞다.
model_pred = model()
model의 예측 값을 뽑아내기 위하여 batch_in 즉 x 데이터의 크기를 조정하자.
test = np.random.rand(2, 28, 28)
print(test.shape)
test = torch.tensor(test,dtype = float)
flat = test.flatten()
print(flat.shape)
test2 = test.reshape(-1, 28*28)
print(test2.shape)
#(2, 28, 28)
#torch.Size([1568])
#torch.Size([2, 784])
다음을 통해 flatten, shape 의 용례를 볼 수 있다.
flatten은 무조건 1차원으로 만들고, reshape 는 원하는 크기를 지정해준다.
(-1 ,28*28) 은 28* 28 크기로 만들고, 앞은 자동으로 설정해달라는 것이다.
여기서는 앞의 크기가 2인걸 알 수 있으니, 2, 28* 28 로 설정을 해도 같은 값을 얻는다.
28* 28을 하는 이유는 linear layer 에 값을 넣기전에 1차원으로 펴주는 것이다.
model의 예측 값을 뽑아내기 위하여 batch_in 즉 x 데이터의 크기도
model(batch_in.reshape(-1, 28*28).to(device))
다음과 같이 조정한 후, GPU에 데이터를 보낸다.
_,y_pred = torch.max(model_pred.data,1)
이 부분이 어떻게 동작하는지 보기 위하여 출력을 해보면,
model_pred tensor([[-0.2008, -0.0820, -0.4944, ..., 0.8697, 0.5560, -0.5379],
[ 0.1313, -0.4353, -0.7848, ..., 1.0962, 1.4326, -0.4965],
[-0.0088, -0.6488, 0.4914, ..., 0.0558, 0.3663, 0.1666],
...,
[-0.3926, -0.2366, 0.3806, ..., 0.7538, 0.7431, 0.5881],
[-0.1847, -0.4935, 0.7928, ..., 0.4356, 0.0267, 0.2794],
[-0.1382, -0.8324, -0.0323, ..., -0.0894, 0.6018, -0.3278]],
device='cuda:0')
model_pred torch.Size([256, 10])
model_pred_data tensor([[-0.2008, -0.0820, -0.4944, ..., 0.8697, 0.5560, -0.5379],
[ 0.1313, -0.4353, -0.7848, ..., 1.0962, 1.4326, -0.4965],
[-0.0088, -0.6488, 0.4914, ..., 0.0558, 0.3663, 0.1666],
...,
[-0.3926, -0.2366, 0.3806, ..., 0.7538, 0.7431, 0.5881],
[-0.1847, -0.4935, 0.7928, ..., 0.4356, 0.0267, 0.2794],
[-0.1382, -0.8324, -0.0323, ..., -0.0894, 0.6018, -0.3278]],
device='cuda:0')
model_pred_data torch.Size([256, 10])
다음과 같이 나온다.
.data 를 찍은적이 없어서 왜 찍나 했더니 찍어도 답은 같다.
Variable: Autograd를 사용하기 위해서 사용되던 타입
.data: was an attribute of Variable .data was giving access to the Variable's underlying Tensor.
_, y_pred
_ tensor([0.4458, 0.9402, 0.2166, 0.3555, 0.7043, 0.7101, 1.0372, 0.7721, 0.5913,
0.5785, 0.4768, 0.5433, 0.5444, 0.4072, 0.6635, 0.4522, 0.6160, 0.3828,
0.6511, 0.5560, 0.8973, 0.7037, 0.5236, 0.4730, 0.6280, 0.4249, 0.6309,
0.7169, 0.5403, 0.4684, 0.4467, 0.9218, 0.3392, 0.8856, 0.5226, 0.4155,
0.6868, 0.8346, 0.4009, 0.8945, 0.4440, 0.6207, 0.6010, 0.7491, 1.1306,
1.3887, 0.5099, 0.5879, 0.8838, 0.8132, 0.2857, 1.0466, 0.7881, 0.6883,
0.4078, 0.3603, 0.6019, 0.4300, 0.3244, 0.7179, 0.2729, 0.6095, 0.4961,
0.4270, 0.3699, 0.6238, 0.6053, 0.4752, 0.5606, 0.9802, 1.2274, 0.3124,
0.4728, 0.4525, 0.6024, 0.4887, 0.5622, 0.3852, 0.5672, 0.6185, 0.5630,
0.3662, 0.8080, 0.3059, 0.8252, 0.4029, 0.2571, 0.5638, 0.9654, 0.4417,
0.5648, 0.2933, 0.7516, 0.3360, 0.5205, 0.5950, 0.4676, 0.8256, 0.3657,
0.7677, 0.8974, 0.7889, 0.5428, 0.8620, 0.5779, 0.2859, 0.5567, 0.5423,
0.9986, 0.4407, 0.5320, 0.4327, 0.8218, 0.3180, 0.6851, 0.8853, 0.7914,
0.4320, 0.2766, 0.7703, 0.3916, 0.4222, 0.3194, 0.5610, 0.7592, 0.2574,
0.3847, 0.3912, 0.3112, 0.4751, 0.5393, 0.4387, 0.5139, 0.4799, 0.9775,
0.3133, 0.5114, 0.3917, 0.7176, 0.3475, 0.4703, 0.4209, 0.6939, 0.3517,
0.7038, 0.8314, 0.4239, 0.4940, 0.5099, 0.4263, 0.2201, 0.2160, 0.7527,
0.4083, 0.8923, 0.5674, 0.7967, 0.4816, 0.4352, 0.5858, 0.3713, 0.4743,
0.4862, 0.4965, 0.6468, 0.7897, 1.1825, 0.6154, 0.7954, 0.8075, 0.5808,
0.2218, 0.3927, 0.5274, 0.5405, 0.4739, 0.6539, 0.4159, 0.2526, 0.5355,
0.5122, 0.2832, 0.5248, 0.5905, 0.6070, 0.5967, 0.4743, 0.3684, 0.2653,
0.2610, 0.8917, 0.8896, 0.3948, 0.8535, 0.4613, 0.3834, 0.8345, 0.4971,
0.4593, 0.7614, 0.3758, 0.7552, 0.7312, 0.4280, 0.5967, 0.9705, 0.4785,
0.1025, 0.4535, 1.2390, 0.5229, 0.8008, 0.1830, 0.2971, 0.5038, 0.3948,
1.2431, 0.5013, 1.0201, 0.9590, 0.5549, 0.5690, 0.7390, 0.4561, 0.9169,
0.5852, 0.4328, 0.3972, 0.6197, 0.8379, 1.1024, 0.8831, 0.4761, 0.4592,
0.4137, 0.4054, 0.5375, 0.3926, 0.5505, 1.0909, 0.4182, 0.4786, 0.9461,
0.5340, 0.9165, 0.4234, 0.8766, 0.7150, 0.4905, 0.4962, 0.4583, 0.5833,
0.9644, 0.4583, 0.7019, 0.8724], device='cuda:0')
y_pred tensor([1, 1, 8, 3, 8, 3, 0, 8, 8, 8, 5, 1, 2, 3, 1, 1, 5, 0, 0, 1, 6, 1, 3, 3,
9, 2, 7, 0, 2, 0, 0, 1, 2, 1, 1, 1, 1, 1, 0, 1, 0, 6, 1, 1, 9, 6, 2, 7,
6, 1, 0, 6, 8, 1, 0, 3, 8, 5, 8, 1, 6, 1, 0, 6, 1, 0, 5, 1, 1, 1, 1, 1,
0, 2, 6, 3, 0, 2, 3, 2, 0, 1, 1, 8, 1, 2, 0, 9, 1, 5, 7, 0, 1, 6, 0, 1,
2, 8, 8, 0, 3, 1, 2, 1, 1, 6, 6, 3, 1, 2, 1, 0, 1, 8, 1, 1, 1, 0, 0, 1,
5, 5, 7, 2, 6, 2, 7, 2, 9, 1, 0, 3, 1, 0, 1, 3, 1, 8, 1, 0, 1, 1, 3, 8,
0, 0, 0, 0, 1, 6, 5, 2, 0, 1, 1, 8, 1, 7, 0, 1, 2, 2, 7, 3, 2, 3, 6, 1,
3, 1, 1, 7, 0, 1, 1, 3, 0, 8, 0, 0, 7, 0, 1, 2, 0, 8, 1, 6, 6, 6, 1, 6,
0, 0, 0, 6, 2, 0, 9, 6, 6, 6, 2, 8, 0, 1, 0, 7, 1, 1, 1, 0, 1, 0, 0, 2,
1, 0, 1, 6, 8, 0, 1, 6, 1, 1, 7, 0, 6, 1, 6, 6, 3, 2, 0, 6, 1, 2, 8, 1,
2, 4, 1, 0, 1, 7, 8, 3, 1, 0, 0, 2, 0, 6, 1, 0], device='cuda:0')
다음과 같은 값이 들어간다.
이를 이해하기 위해서는 torch.max() 의 용례를 이해해야 한다.
_,y_pred = torch.max(model_pred.data,1)
는 model_pred.data 에서 1차원의 데이터 중, 가장 큰 값의 index를 반환하라는 뜻이다. (_ 에는 그때의 값들이 들어간다. 우리에게는 필요없는 정보)
즉, class 10 개 (0~9) 중 어느 class에 해당되는지 파악하는 classification 문제다.
n_correct += (y_trgt == y_pred).sum().item()
는 타겟과 예측값이 같은 index의 값은 True 즉 1로 설정하고 그 후 다 더하고 item() 으로 torch를 value로 뽑아내는 것이다.
batch_in.size(0)
batch_in.shape[0]
동일하다.
M.init_param() # initialize parameters
train_accr = func_eval(M,train_iter,device)
test_accr = func_eval(M,test_iter,device)
print ("train_accr:[%.3f] test_accr:[%.3f]."%(train_accr,test_accr))
#train_accr:[0.075] test_accr:[0.076].
대충 10% 정도 나온다. (class 10 개 찍으면 10% 정도)
print ("Start training.")
M.init_param() # initialize parameters
M.train()
EPOCHS,print_every = 10,1
for epoch in range(EPOCHS):
loss_val_sum = 0
for batch_in,batch_out in train_iter:
# Forward path
y_pred = M.forward(batch_in.view(-1, 28*28).to(device))
loss_out = loss(y_pred,batch_out.to(device))
optm.zero_grad()
loss_out.backward()
optm.step()
#추후 찍어보자.
loss_val_sum += loss_out
loss_val_avg = loss_val_sum/len(train_iter)
# Print
if ((epoch%print_every)==0) or (epoch==(EPOCHS-1)):
train_accr = func_eval(M,train_iter,device)
test_accr = func_eval(M,test_iter,device)
print ("epoch:[%d] loss:[%.3f] train_accr:[%.3f] test_accr:[%.3f]."%
(epoch,loss_val_avg,train_accr,test_accr))
optimizer 의 gradient 를 초기화 하고,
loss.backward()를 호출할때 초기설정은 매번 gradient를 더해주는 것으로 설정되어있다.
https://tutorials.pytorch.kr/beginner/blitz/autograd_tutorial.html
torch.autograd 에 대한 간단한 소개 — PyTorch Tutorials 1.10.2+cu102 documentation
Note Click here to download the full example code torch.autograd 에 대한 간단한 소개 torch.autograd 는 신경망 학습을 지원하는 PyTorch의 자동 미분 엔진입니다. 이 단원에서는 autograd가 신경망 학습을 어떻게 돕는
tutorials.pytorch.kr
그렇기 때문에 학습 loop를 돌때 이상적으로 학습이 이루어지기 위해선 한번의 학습이 완료되어지면(즉, Iteration이 한번 끝나면) gradients를 항상 0으로 만들어 주어야 한다. 만약 gradients를 0으로 초기화해주지 않으면 gradient가 의도한 방향이랑 다른 방향을 가르켜 학습이 원하는 방향으로 이루어 지지 않는다.
loss 값을 backward 한다.
step optimizer 에게 loss function을 효율적으로 최소화 할 수 있게 파라미터를 수정 위탁한다.
When you call loss.backward(), all it does is compute gradient of loss w.r.t all the parameters in loss that have requires_grad = True and store them in parameter.grad attribute for every parameter. optimizer.step() updates all the parameters based on parameter.grad
backward 는 왜 축적될까? 왜 default 로 축적할까?
pytorch의 autograd에 대해 알아보자
다음은 by Daniel Voigt Godoy 를 참고하여 번역한 내용입니다. 주관적인 해석이 섞여있습니다. autograd pytorch의 Automatic differentiation(Gradient) package이다. 이 친구가 하는일은, neural ne
velog.io
circumvent harware limitation 이 있을 때 유용하기 때문이라고 한다.
엄청나게 커다란 모델을 돌릴때, mini-batch안에 필요한 데이터 포인트의 수가 너무너무 커서 우리가 가진 하드웨어 자원을 초과할 수 있다. 좋은 그래픽 카드를 살 돈이 없다면 어떻게 해야할까? Mini-batch임에도 우리의 컴퓨터에게는 너무 커다란 뭉탱이니까, 이를 sub-mini-batch로 다시 쪼개는 것이다. 이 sub-mini-batch를 계산해서, 축적한다면, full mini-batch의 gradient를 계산할 수 있게 되는 것이다. 그래서 축적이 필요한 것이다! 돈 없는 나같은 사람들에게는 참 마음 따뜻한 배려인 것이다.
'AI > 딥러닝 기초' 카테고리의 다른 글
신경망 개념 완벽 이해 (0) | 2022.02.12 |
---|---|
딥러닝 Optimization (최적화) 및 최적화 용어 정리 (0) | 2022.02.07 |
Neural Net, Multi-layer Perceptron (0) | 2022.02.07 |
딥러닝 역사, 기본 및 용어 (0) | 2022.02.07 |
Multi_GPU, 분산 학습, 데이터 병렬화, 파라미터서버 (0) | 2022.01.28 |