import torch
import matplotlib.pyplot as plt
from fastai.data.all import *
import torchvision
Stochastic Gradient Descent
1. Imports
2. Stochastic Gradient Descent
전체 데이터셋이 아니라 일부 샘플(1개 또는 소수의 샘플)만 사용하여 파라미터를 업데이트하는 것
-
SGD가 왜 필요해?
GPU는 사기니까 GPU만 사용하면 될 것 같음 그런데 GPU는 너무 비싸다… 80GB에 3000만원씩 하기때문…
80GB는 금방 차버리니까 그럼 GPU메모리를 어떻게든 아껴서 잘 사용해야하네?
어떻게 해야할까?
= torch.linspace(-10,10,100000).reshape(-1,1)
x = torch.randn(100000).reshape(-1,1)
eps = x*2 + eps y
'o',alpha=0.05)
plt.plot(x,y,2*x,'--') plt.plot(x,
그렇게 어려운 계산도 아닌데 10만개의 데이터를 모두 GPU로 보내서 연산시키면 너무 낭비라는 생각이 듦…
A. X,y 데이터를 모두 굳이 GPU에 넘겨야 하는가?
데이터셋을 짝홀로 나누어서 번갈아가면서 GPU에 올렸다 내렸다하면 안되나?
-
아래의 알고리즘을 생각해보자.
- 데이터를 반으로 나누고
- 짝수 obs의 x,y,net의 모든 parameters을 GPU에 올린다
- yhat,loss,grad,update 수행
- 홀수 obs의 x,y를 GPU메모리에서 내린다. 그리고 짝수 obs의 x,y를 GPU메모리에 올린다
- yhat,loss,grad,update 수행
- 반복
B. 미니배치 경사하강법
그럼 홀수 짝수로 나누는 건 2로 나누는 건데 굳이 2로만 나누어야하나? 더 쪼갤 수 있지 않나?
-
gradient descent : 10개의 sample data가 있다고 할 때 모든 sample을 이용하여 slope계산
-
stochastic gradient descent with batch size = 1 : 10개의 smaple data를 하나씩으로 모두 쪼개서 slope계산
stochastic gradient descent with batch size = 1의 경우는 epoc을 10번 하면 총 100번 epoc을 돌리는 것과 같다
-
stochastic gradient descent : m개의 sample을 이용하여 slope 계산
그럼 stochastic gradient descent의 경우는 epoc을 10번 하면 총 40번 epoc을 돌리는 것과 같다.
C. Dataset(ds) , DataLoader(dl)
stochastic gradient descent를 수행하기 위해서 파이토치에서는 ds와 dl라는 오브젝트를 준비했다.
=torch.tensor(range(10)).float().reshape(-1,1)
x=torch.tensor([1.0]*5+[0.0]*5).reshape(-1,1)
y=1) torch.concat([x,y],axis
tensor([[0., 1.],
[1., 1.],
[2., 1.],
[3., 1.],
[4., 1.],
[5., 0.],
[6., 0.],
[7., 0.],
[8., 0.],
[9., 0.]])
= torch.utils.data.TensorDataset(x,y) ds
dir(ds)를 살펴보면 __getitem__이 있다 이러면 섭스크립터블하다는 것이다.
# 튜플 언패킹으로 뽑을 수 있을 거 같음 ds.tensors
(tensor([[0.],
[1.],
[2.],
[3.],
[4.],
[5.],
[6.],
[7.],
[8.],
[9.]]),
tensor([[1.],
[1.],
[1.],
[1.],
[1.],
[0.],
[0.],
[0.],
[0.],
[0.]]))
0] , (x,y)[0] ds[
((tensor([0.]), tensor([1.])),
tensor([[0.],
[1.],
[2.],
[3.],
[4.],
[5.],
[6.],
[7.],
[8.],
[9.]]))
그런데 일반적인 튜플의 인덱싱과는 다르게 동작함
=torch.utils.data.DataLoader(ds,batch_size=3)
dl dl
<torch.utils.data.dataloader.DataLoader at 0x7fdf9fc2fec0>
dl : 섭스크립터블하지 않지만 이터러블 함 즉, for문을 사용할 수 있음
for xi,yi in dl:
print(xi,yi)
tensor([[0.],
[1.],
[2.]]) tensor([[1.],
[1.],
[1.]])
tensor([[3.],
[4.],
[5.]]) tensor([[1.],
[1.],
[0.]])
tensor([[6.],
[7.],
[8.]]) tensor([[0.],
[0.],
[0.]])
tensor([[9.]]) tensor([[0.]])
10을 3으로 나누면 마지막에 하나 남는데 그건 어떻게 해? -> 그냥 하나 남으면 그것만 계산한다
D. ds,dl을 이용한 MNIST구현
-
목표 : 확률적경사하강법과 그냥 경사하강법의 성능을 ’동일 반복횟수’로 비교해보자
-
그냥 경사하강법 - mini-batch쓰지 않는 학습
= untar_data(URLs.MNIST)
path = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/0').ls()]))
X0 = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/1').ls()]))
X1 = torch.concat([X0,X1],axis=0).reshape(-1,1*28*28)/255
X = torch.tensor([0.0]*len(X0) + [1.0]*len(X1)).reshape(-1,1)
y
21345)
torch.manual_seed(= torch.nn.Sequential(
net 1*28*28,32),
torch.nn.Linear(
torch.nn.ReLU(),32,1),
torch.nn.Linear(
torch.nn.Sigmoid()
)
= torch.nn.BCELoss()
loss_fn = torch.optim.Adam(net.parameters())
optimizr
for epoc in range(700):
= net(X)
yhat = loss_fn(yhat,y)
loss
loss.backward()
optimizr.step()
optimizr.zero_grad()
> 0.5)*1.0 == y).float().mean() ((yhat
tensor(0.9998)
plt.plot(y)'.') plt.plot(yhat.data,
-
‘확률적’ 경사하강법 - mini-batch사용하는 학습
= torch.utils.data.TensorDataset(X,y)
ds = torch.utils.data.DataLoader(ds,batch_size=2048)
dl
= untar_data(URLs.MNIST)
path = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/0').ls()]))
X0 = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/1').ls()]))
X1 = torch.concat([X0,X1],axis=0).reshape(-1,1*28*28)/255
X = torch.tensor([0.0]*len(X0) + [1.0]*len(X1)).reshape(-1,1)
y
21345)
torch.manual_seed(= torch.nn.Sequential(
net 1*28*28,32),
torch.nn.Linear(
torch.nn.ReLU(),32,1),
torch.nn.Linear(
torch.nn.Sigmoid()
)
= torch.nn.BCELoss()
loss_fn = torch.optim.Adam(net.parameters())
optimizr
for epoc in range(100):
for xi,yi in dl:
= net(xi)
netout = loss_fn(netout,yi)
loss
loss.backward()
optimizr.step()
optimizr.zero_grad()
> 0.5)*1.0 == y).float().mean() ((net(X)
tensor(0.9992)
plt.plot(y)'.') plt.plot(yhat.data,
-
GPU를 활용하는 ‘확률적’ 경사하강법 - 실제로는 이게 최종 알고리즘
= torch.utils.data.TensorDataset(X,y)
ds = torch.utils.data.DataLoader(ds,batch_size=2048)
dl
= untar_data(URLs.MNIST)
path = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/0').ls()]))
X0 = torch.stack(([torchvision.io.read_image(str(fname)) for fname in (path/'training/1').ls()]))
X1 = torch.concat([X0,X1],axis=0).reshape(-1,1*28*28)/255
X = torch.tensor([0.0]*len(X0) + [1.0]*len(X1)).reshape(-1,1)
y
21345)
torch.manual_seed(= torch.nn.Sequential(
net 1*28*28,32),
torch.nn.Linear(
torch.nn.ReLU(),32,1),
torch.nn.Linear(
torch.nn.Sigmoid()"cuda:0")
).to(
= torch.nn.BCELoss()
loss_fn = torch.optim.Adam(net.parameters())
optimizr
for epoc in range(100):
for xi,yi in dl:
= loss_fn(net(xi.to("cuda:0")),yi.to("cuda:0"))
loss
loss.backward()
optimizr.step()
optimizr.zero_grad()
"cpu")
net.to(
> 0.5)*1.0 == y).float().mean() ((net(X)
tensor(0.9992)
plt.plot(y)'.') plt.plot(net(X).data.data,
5. 다중클래스 분류
A. 결론
-
2개의 class를 구분하는 게 아니라 k개의 class를 구분해야 한다면?
y의 형태 : (n,) vector + int형 // (n,k) one-hot encoded matrix + float형
손실함수 : torch.nn.BCEWithLogitsLoss, -> torch.nn.CrossEntropyLoss
마지막층의 선형변환 : torch.nn.Linear(?,1) -> torch.nn.Linear(?,k)
마지막층의 활성화 : NONE -> NONE (손실함수에 이미 포함되어있음)
B. 실습 : 3개의 클래스를 구분
## Step1: 데이터준비
= untar_data(URLs.MNIST)
path = torch.stack([torchvision.io.read_image(str(fname)) for fname in (path/'training/0').ls()])
X0 = torch.stack([torchvision.io.read_image(str(fname)) for fname in (path/'training/1').ls()])
X1 = torch.stack([torchvision.io.read_image(str(fname)) for fname in (path/'training/2').ls()])
X2 = torch.concat([X0,X1,X2]).reshape(-1,1*28*28)/255
X = torch.tensor([0]*len(X0) + [1]*len(X1)+ [2]*len(X2))
y ## Step2: 학습가능한 오브젝트 생성
43052)
torch.manual_seed(= torch.nn.Sequential(
net 784,32),
torch.nn.Linear(
torch.nn.ReLU(),32,3), # class = 3
torch.nn.Linear(# torch.nn.Softmax()
)= torch.nn.CrossEntropyLoss()
loss_fn = torch.optim.Adam(net.parameters())
optimizr ## Step3: 적합
for epoc in range(100):
## step1
= net(X)
netout ## step2
= loss_fn(netout,y)
loss ## step3
loss.backward()## step4
optimizr.step()
optimizr.zero_grad()## Step4: 적합 (혹은 적합결과확인)
=1) == y).float().mean() (netout.argmax(axis
tensor(0.9827)
파이토치에서 CrossEntropyLoss를 사용하면 one-hot 인코딩을 해준다. float형도 자동으로 맞춰줌
D.정리
-
결론
- 소프트맥스는 시그모이드의 확장이다.
- 클래스의 수가 2개일 경우에는 (Sigmoid, BCEloss) 조합을 사용해야 하고 클래스의 수가 2개보다 클 경우에는 (Softmax, CrossEntropyLoss) 를 사용해야 한다.
-
그런데 사실.. 클래스의 수가 2개일 경우일때 (Softmax, CrossEntropyLoss)를 사용해도 그렇게 큰일나는것은 아니다. (그냥 좀 비효율적인 느낌이 드는 것 뿐임. 흑백이미지를 칼라잉크로 출력하는 느낌)