[PyTorch] Tensor 연산 / 자동 미분 / Linear Regression / DL Flow

황성미·2023년 11월 5일
0
post-thumbnail
post-custom-banner

✍🏻 5일 공부 이야기.


오늘 공부한 실습 코드는 위 깃허브에 올려두었습니다 :) 사진 클릭시 해당 링크로 이동해요 !


앞서 배웠던 TensorFlow 와 같은 흐름으로 PyTorch 도 배워보자. 둘은 같이 개발되고 있기 때문에 기능이 거의 다 비슷하다.

PyTorch

  1. Tensor 다루기
  2. 연산 정의 및 Modeling
  3. 최적화
  4. 데이터 다루기

4 파트만 익히면 된다!


Tensor 다루기

앞서 Variable과 Constant가 달리 있었던 TensorFlow와 달리 PyTorch에서는 이를 하나로 모두 Tensor로 본다.

  • torch.tensor(변환시킬 데이터) , torch.as_tensor(변환시킬 데이터) : 기존의 데이터를 Tensor로 변환

  • torch.tensor(변환시킬 데이터) , torch.as_tensor(변환시킬 데이터) , torch.from_numpy(변환시킬 데이터) : numpy 데이터를 Tensor로 변환

  • 변환시킬 데이터.numpy() : Tensor -> numpy

  • .shape , .size : Tensor의 속성값 확인

  • 데이터 타입 선언

    • 생성시 선언

      # 생성시 선언 dtype=torch.float32
      torch.randint(10, size=(5,), dtype=torch.float32)
    • 이미 생성된 변수에 선언

      a = torch.randint(10, size=(5,))
      print(a.dtype)
      
      # 텐서이름.type(원하는 데이터타입)
      print(a.type(torch.float32))
      print(a.dtype)
      
      # inplace 명령어가 아니므로 갱신시켜주어야함
      a = a.type(torch.float64)
      print(a.dtype)
  • torch.cuda.is_available() : GPU 사용

  • GPU에서 사용하기 위해 cuda에서 사용하는 데이터 타입으로 바꿔줘야함

    • 방법 1. 생성 시 device 설정

      # 방법 1. device 설정하기.
      x = torch.ones(2, 2, device='cuda')
      
      # 여러개의 GPU 중에 하나의 GPU에 할당하고 싶을 때
      # 번호는 nvidia-smi 명령을 Shell에 입력해서 찾을 수 있음 
      x = torch.ones(2, 2, device='cuda:0')
      
      # device 객체를 입력하는게 기본
      x = torch.ones(2, 2, device=torch.device('cuda'))
    • 방법 2. tensor_var.cuda()

      # 방법 2. .cuda()
      a = torch.rand(10)
      print(a)
      
      a = a.cuda()
      print(a)
    • 방법 3. tensor_var.to(device)

      # 방법 3. .to(device) : 대부분 많이 사용
      a = torch.rand(2)
      print(a)
      
      a = a.to("cuda") # a.to(torch.device("cuda"))
      print(a)

연산

대부분의 연산은 TensorFlow와 동일하다. 몇 가지 특이 케이스만 정리해보았다.

  • tensorflow의 axis -> pytorch dim

  • 크기와 차원 변경

    • torch.reshape
    • 텐서명.view : expand_dims 같은 함수 대신 이걸로 사용
    • torch.transpose
    • torch.sqeeze / torch.unsqueeze
    • 텐서이름.view_as(다른 텐서이름) / 텐서이름.reshape_as(다른 텐서이름) : 이전 shape에 맞춰서 두번째 텐서의 shape이 변경됨
  • 대부분 함수 끝에 _ 를 붙이면 inplace 명령이 됨

  • 아래와 같은 오류가 발생한다면 텐서이름.contiguous() 호출하고 다시 실행시키기



연산 정의 및 Modeling

자동 미분

autograd는 PyTorch에서 핵심적인 기능을 담당하는 하부 패키지이다.

autograd는 텐서의 연산에 대해 자동으로 미분값을 구해주는 기능을 한다.

  • 텐서 자료를 생성할 때, requires_grad인수를 True로 설정하거나
  • .requires_grad_(True)를 실행하면

📌 그 텐서에 행해지는 모든 연산에 대한 미분값을 계산한다.

# requires_grad=True : 미분값을 트래킹할 변수임을 선언
x = torch.rand(2, 2, requires_grad=True)

y = torch.sum(x * 3)
print(y, y.grad_fn) # grad_fn라는 속성값이 생기게 되면서 미분 값을 트래킹할 수 있게됨 

y.backward() # 선언 후(x의 미분값이 자동으로 갱신됨)
x.grad # 미분을 구하고자하는 변수.grad 를 하면 미분값이 구해짐

📌 상황에 따라 특정 연산에서는 미분값을 계산하고 싶지 않은 경우

계산을 멈추고 싶으면 .detach()함수나 with을 이용해 torch.no_grad()를 이용하면 된다.

x_d = x.detach() # 미분값 트래킹 x
torch.sigmoid(x_d)
print(x_d.grad) # None

with torch.no_grad():
    x_d2 = torch.sigmoid(x)
    print(x_d2.grad) # None



Linear Regression

Boston 데이터를 이용해 Linear Regression 을 PyTorch로 구현해보자.

역행렬 존재시 가중치 구하는 방법

위 수식을 이용해보자.

x = torch.tensor(df.values)
y = torch.tensor(y.values).view(-1, 1)

XT = torch.transpose(x, 0, 1) 
# 역행렬이 존재시, 가중치 벡터 구하는 수식
w = torch.matmul(torch.matmul(torch.inverse(torch.matmul(XT, x)), XT), y)
y_pred = torch.matmul(x, w)

print("예측한 집값 :", y_pred[19], "실제 집값 :", y[19])

💻 출력

예측한 집값 : tensor([18.4061], dtype=torch.float64) 실제 집값 : tensor([18.2000], dtype=torch.float64)


Gradient Descent 방법

📌 loss 추출하기

# 가중치 생성
# const 열을 추가했기 때문에 이론 상으로 b는 필요없지만
# 그냥 일반적인 경우를 설명하기 위해 맞춰준 것
w = torch.rand((14, 1), dtype=torch.float64, requires_grad=True)
b = torch.rand((1, 1),  dtype=torch.float64, requires_grad=True)

# linear regression
z = x.mm(w) + b
loss = torch.mean((z - y)**2) 

loss.backward()

# print(loss)
# tensor(303557.1678, dtype=torch.float64, grad_fn=<MeanBackward0>)

# loss 숫자만 추출하는 방법
# 303557.16782532405
print(loss.detach().numpy())  # 방법 1

with torch.no_grad(): # 방법 2
    print(loss.numpy())
# 이렇게 로그를 추출할 때는 숫자만 추출하도록 설정해줌
# 방법 3
loss.item()

📌 추출한 gradient로 가중치 업데이트하기

lr = 3e-7
for epoch in range(100):
    z = x.mm(w) + b
    loss = torch.mean((y-z)**2)

    loss.backward() # 미분값 계산

    w.data -= w.grad * lr # 업데이트
    b.data -= b.grad * lr
    
    # 위 코드와 동일
    ## 미분값 계산
    #grads = torch.autograd.grad(loss, [w, b])
	#
    #w.data -= grads[0] * lr # 업데이트
    #b.data -= grads[1] * lr

    print('{} - loss : {}'.format(epoch, loss.item()))

    # 초기화
    # 누적값을 초기화해주어야함
    w.grad.zero_()
    b.grad.zero_()

optimizer 방법

opt = torch.optim.SGD([w, b], lr=lr)

for epoch in range(100):    
    z = (x.mm(w) + b)
    loss = torch.mean((z - y)**2)  
    
    loss.backward()
    opt.step()
    print("{:3} - loss : {}".format(epoch, loss.item()), end="\r")
    
    opt.zero_grad()
    
y_pred = x.mm(w) + b
print("예측한 집값 :", y_pred[19].item(), "실제 집값 :", y[19].item())

💻 출력

예측한 집값 : 22.069598463384597 실제 집값 : 18.2



Deep Learning Flow

Data Load 및 전처리 -> 데이터 확인 -> 모델 정의 -> 모델 학습 -> 평가

Data Load 및 전처리

batch_size = 32

# 데이터 부르기
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('dataset/',
                   train=True, download=True, # 로컬 환경에 데이터가 없다면 다운로드하라
                   transform=transforms.Compose([ # 가져올 때 미리 아래와 같이 전처리해서 가져오겠다
                       transforms.ToTensor(),
                       transforms.Normalize(mean=(0.5,), std=(0.5,)) # mean이 0.5이고 std가 0.5가 되는 분포가 되도록 정규화시켜라.
                                                                    # 튜플 형태로 넣어주어야함
                   ])),
    batch_size=batch_size,
    shuffle=True)
    
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('dataset/',
                   train=False,
                   transform=transforms.Compose([ 
                       transforms.ToTensor(),
                       transforms.Normalize(mean=(0.5,), std=(0.5,))                                                     
                   ])),
    batch_size=batch_size,
    shuffle=False)

데이터 확인

images, labels = next(iter(train_loader))
images.shape, images.dtype # (torch.Size([32, 1, 28, 28]), torch.float32)
  • TF - (batch, height, width, channel)
  • PyTorch - (batch, channel, height, width)

PyTorch와 TF는 이미지를 표현하는데에 있어서 channel의 위치 차이가 있으니 명심하자!!


모델 정의

주로 nn.Module을 상속 받아서 모델을 정의한다.

from torch import nn # 학습할 파라미터가 있는 것들 # conv2d
import torch.nn.functional as F # 학습할 파라미터가 없는 것들 # maxpool, relu

class Net(nn.Module):
    def __init__(self): # forward에서 사용되는 레이어, 파라미터 정의
        super(Net, self).__init__()
                              # tf에서는 output만 넣어주었다면
                              # pytorch에서는 input과 output을 같이 정의해줌
                              # input, output
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x): # 실제 연산
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = Net().to(device) # 모델을 device로 넘겨줌

model을 이쁘게 정리해서 출력해주는 torchsummary 는 이후 모델링 파트에서 정리할 예정!


학습 및 평가

'''
- epoch
  - batch
    - model
    - loss
    - grad
    - model update
'''

import torch.optim as optim

opt = optim.SGD(model.parameters(), lr=0.003)

for epoch in range(1):
  # 학습
  model.train()
  for batch_idx, (data, target) in enumerate(train_loader):
    data, target = data.to(device) , target.to(device) # 데이터 device로 보내기

    output = model(data)
    loss = F.nll_loss(output, target)
          # 지금 원핫인코딩이 안 되어있는 상태이다.
          # tf 에서는 SparseCategoricalCrossentropy loss를 사용해
          # 원핫 인코딩이 안 되어있는 상태에서도 사용 가능했는데
          # 이를 torch에서 구현하려면 log softmax와 nll_loss를 조합하면 됨.

    loss.backward()

    opt.step() # 업데이트

    print('batch {} loss : {}'.format(batch_idx, loss.item()))

    opt.zero_grad() # 초기화

  # 평가
  model.eval()

  test_loss = 0

  with torch.no_grad():
    for data, target in test_loader:
      output = model(data)
      test_loss += F.nll_loss(output, target).item()
  # 한 epoch 마다 배치 데이터의 평균 loss 값
  test_loss /= (len(test_loader.dataset) // 32)

  print('Epoch {} test loss : {}'.format(epoch, test_loss))
profile
데이터 분석가(가 되고픈) 황성미입니다!
post-custom-banner

0개의 댓글