ML/AI/SW Developer

Pytorch(2)-AutoGrad & Optimizer

1. Intro

1.1 딥러닝 모델은?

  • Layer = Block
    • 논문에 있는 모델 => 수많은 반복의 연속
    • Block들을 조립한것

1.2 Base classes

  • torch.nn.Module
    • Layer의 base class
    • Input, output, Forward, Backward(AutoGrad) 정의
    • 학습 대상(parameter-tensor) 정의
  • nn.Parameter
    • Tensor 객체의 상속 객체
    • nn.Module 내에 attribute가 되면 학습되상이 된다(required_grad=True)
    • 대부분의 layer에는 이미 잘 지정되어있음(Dense, Conv, …)

1.3 Custom Layer 만들기

class CustomLayer(nn.Module):
    def __init__(self, in_ft, out_ft, bias=True):
        super().__init__()  # 부모 __init__() 호출
        self.in_ft = in_ft  # 입력 피쳐 크기
        self.out_ft = out_ft# 출력 피쳐 크기

        # 파라미터 설정
        # 평균이 0이고 표준편차가 1인 가우시안 정규분포를 이용해 난수 생성
        self.W = nn.Parameter(torch.randn(in_ft, out_ft))
        self.b = nn.Parameter(torch.randn(out_ft))

    def forwrad(self, x):
        return x @ self.W + self.b # 선형회귀 계산

# layer 선언!
Mylayer = CustomLayer(5, 10)

# 설정한 파라미터 확인 가능 
# Layer 구현시 parameter를 단순 Tensor로 선언시 parameter로 반영되지 않는다
for v in Mylayer.parameters():
    print(v)

1.4 Backward

  • Parameter들을 미분
  • Forward의 결과값과 실제값의 차이에 대해 미분, 이 값으로 parameter 업데이트
  • Loss functions
# criterion => [Loss functions]
criterion = torch.nn.MSELoss()

# optimizer 정하기
# 기본 인자: 모델 파라미터와, learning rate
# optimizer에 따라 다양한 인자가 있음
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

for e in range(epochs):
    # grad 초기화
    # 이전 epoch의 grad값에 대한 정보가 남아있음.
    # 영향을 주길 원하지 않으면 초기화 필수
    optimizer.zero_grad()

    # Forward 연산
    outputs = model(inputs)

    # 예측과 실제값 차이 계산
    loss = criterion(outputs, labels)
    
    # Parmeter로 설정한 변수들 미분값 계산
    loss.backward()

    # Parameter 업데이트
    optimizer.step()

# Disabling gradient calculation is useful for inference
# The Variable API has been deprecated: Variables are no longer necessary to use autograd with tensors. 
# autograd_tensor = torch.randn((2, 3, 4), requires_grad=True)
from torch.autograd import Variable
with torch.no_grad():
    if torch.cuda.is_available():
        # .cpu(): Cpu 메모리로 해당 object 복사
        # .data Object 내용/값
        # .numpy() numpy array로 변환해 반환
        pred = model(Variable(torch.from_numpy(x_train).cuda())).cpu().data.numpy()
        #pred = model(torch.from_numpy(x_train).cuda()).cpu().data.numpy()
    else: 
        pred = model(Variable(torch.from_numpy(x_train))).data.numpy()
        #pred = model(torch.from_numpy(x_train)).data.numpy() 
    '''
    +a .detach()
    gradient가 계산될 tensor의 경우 graph로 기록되어있다
    node: Tensor
    edge: 입력 Tensor로 부터 출력 Tensor 생성
    .cpu().detach() 로 사용하면 cpu를 만드는 edge가 생성된다.
    .detach().cpu() 로 사용하면 추가적으로 edge가 생성되지 않는다.
    그래서 주로, .detach().cpu() 순으로 사용한다.

1.5 Logistic Regression 구현해보기

class LR(nn.Module):
    def __init__(self, dim, lr=torch.scalar_tensor(0.01)):
        super(LR, self).__init__()
        # intialize parameters
        # 직접 미분하기 때문에 parameter로 선언할 필요 없음
        self.w = torch.zeros(dim, 1, dtype=torch.float).to(device)
        self.b = torch.scalar_tensor(0).to(device)
        self.grads = {"dw": torch.zeros(dim, 1, dtype=torch.float).to(device)      ,"db": torch.scalar_tensor(0).to(device)}
        self.lr = lr.to(device)

    def forward(self, x):
        ...
        return output

    def sigmoid(self, z):
        return 1/(1 + torch.exp(-z))

    def backward(self, x, yhat, y):
        ## compute backward
        self.grads["dw"] = (1/x.shape[1]) * torch.mm(x, (yhat - y).T)
        self.grads["db"] = (1/x.shape[1]) * torch.sum(yhat - y)
    
    def optimize(self):
        ## optimization step
        self.w = self.w - self.lr * self.grads["dw"]
        self.b = self.b - self.lr * self.grads["db"]