👩‍💻LEARN : ML&Data/Book Study

[딥러닝 파이토치 교과서] #1. 파이토치 기초

쟈니유 2023. 4. 9. 12:26
728x90

 

텐서에 익숙해져있다가 파이토치로 넘어가려니 아직은 어렵다...

 

+이번 책 끝내고, 나중에 여유 좀 생기면 파이토치 튜토리얼도 봐야지! 

 

PyTorch 소개

Introduction|| Tensors|| Autograd|| Building Models|| TensorBoard Support|| Training Models|| Model Understanding 아래 영상이나 youtube 를 참고하세요. PyTorch Tensors: 03:50 에 시작하는 영상을 시청하세요. PyTorch를 import할 것

tutorials.pytorch.kr

 


#1. 파이토치 기초

 

파이토치 아키텍처

파이토치 API

  • torch : GPU 지원 텐서 패키지 
  • torh.autograd : 자동 미분 패키지. 신경망에 변경이 생길 경우 즉시 계산할 수 있음
  • torch.nn : 신경망 구축 
  • torch.multiprocessing : 서로 다른 프로세스에서 동일한 데이터(텐서)에 접근, 사용할수 있게 함
  • torch.utils : torch.utils.data.DataLoader 를 통해 모델에 데이터 제공, torch.utils.checkpoint 통한 모델 검사 등 수행

이 외 파이토치 엔진, 연산처리 등이 또한 있음 

 

파이토치 기초 문법 

1. 텐서 생성 및 변환 

import torch

#텐서 생성
torch.tensor([[1,2],[3,4]], device = "cuda:0") #GPU에 텐서 생성 
torch.tensor([[1,2],[3,4]], dtype = torch.float64) #유형 지정  

#텐서 배열로 변경
temp = torch.tensor([[1,2],[3,4]])
temp.numpy()

temp2 = torch.tensor([[1,2],[3,4]], device = "cuda:0")
temp.to("cpu").numpy()

2. 텐서 인덱스 조작 

배열과 동일하게 인덱스, 슬라이싱 가능 

3. 텐서 연산 및 차원 조작 

텐서 연산

  • 유의사항 : 텐서 간 타입이 다르면 연산이 불가능 (e.g. FloatTensor(32bit) - DoubleTensor(64bit) 불가능)
v = torch.tensor([1,2,3])
w = torch.tensor([3,4,6])

w-v
#tensor([2,2,3])

텐서 차원 조작 

temp = torch.tensor([[1,2],[3,4]])

temp.view(4,1)    #4*1로 변환
temp.view(-1)     #1차원 벡터로 변환 
temp.view(1,-1)   # (1,-1) = (1,?) 다른 차원으로 부터 해당 값을 유추. 
temp.view(-1,1)

 

데이터 준비 

1. 파일을 불러와서 사용하는 경우 

import pandas as pd
import torch

data = pd.read_csv("파일경로")

#csv 파일의 컬럼을 넘파이 배열로 받아 tensor로 바꿔주는 것 
x = torch.from_numpy(data['불러오려는 열 이름'].values).unsqueez(dim=1).folat()

2. 커스텀 데이터셋을 만들어서 사용하는 경우 

데이터를 한번에 메모리에 불러오면 비효율적이므로 데이터를 조금씩 나눠서 불러오는 방식

import pandas as pd
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

#클래스 정의

class CustomDataset(torch.utils.data.Dataset):
	def__init__(self,csv_file):          #필요한 변수 선언, 데이터 셋 전처리
    	self.label = pd.read_csv 
        
    def__len__(self):           # 데이터셋 총 샘플 수 
    	return len(self.label)
        
    def__getitem__(self.index): #특정 데이터를 가져오는 함수 (인덱스번째 데이터 반환)
    	sample = torch.tensor(self.label.iloc[index, 0:3]).int()
        label = torch.tensor(self.label.iloc[index,3]).int()
        return sample, label 
        
#적용

tensor_dataset = CustomDataset('파일경로')  
dataset = DataLoader(tensor_dataset, batch_size=4, shuffle=True)

3. 파이토치 자체 데이터 사용하는 경우 

#터미널에 리퀘스트 설치
pip install requests

#여기부터 불러오는법

import torchvision.transforms as transforms

mnist_transform = transforms.Compose([
	transforms.ToTensor(),
    transforms.Normalize((0.5,),(1.0,))
    
from torchvision.datasets import MNIST
import requests

dowonload_root = '다운받을 경로'
train_dataset = MNIST(download_root, transform=mnist_transform, train=True,download=True)

 

모델 정의

1. 단순 신경망 정의

nn.Module() 상속 받지 않는 단순한 모델 생성 시 사용 

model = nn.Linear(in_features=1, out_features=1, bias=True)

2. nn.Module() 상속하여 정의

class MLP(Module):
	#모델에서 사용할 모듈 (Linear,Conv2D..)과 활성화 함수 정의 
	def __init__ (self,inputs):
    	super(MLP, self).__init__()    #부모 클래스인 Module에 접근 
        self.layers = Linear(inputs,1) #계층 정의
        self.activation = Sigmoid()
    #모델에서 실행되어야 하는 연산을 정의     
    def forward(self,X):
    	X = self.layer(X)
        X = self.activation(X)
        return X

3. Sequential 신경망 정의

__init__에서 사용할 네트워크 모델 정의해주고 forward 함수에서 모델에서 실행되어야 할 계산을 가독성 높게 작성해줌 

import torch.nn as nn

class MLP(nn.Module):
	def __init__(self):
    	super(MLP,self).__init__()
        self.layer1 = nn.Sequential(
        	nn.Conv2D(in_channels=3, out_channels=64, kernel_size=5),
            nn.Relu(inplace=True),
            nn.MaxPool2d(2))
            
        self.layer2 = nn.Sequential(
        	nn.Conv2D(in_channels=64, out_channels=30, kernel_size=5),
            nn.Relu(inplace=True),
            nn.MaxPool2d(2))
            
        self.layer3 = nn.Sequential(
        	nn.Linear(in_features=30*5*5, out_features=10, bias=True),
            nn.Relu(inplace=True),)            
            
    def forward(self,x):
    	x = self.layer1(x)
        x = self.layer2(x)
        x = x.view(x.shape[0],-1)
        x = self.layer3(x) 
        return x

4. 함수로 신경망 정의 

변수에 저장한 계층들을 재사용할 수 있으나 모델이 복잡해질 수 있음 

def MLP(in_features = 1, hidden_featrues = 20, out_features=1):
	hidden = nn.Linear(in_features = in_features, out_features = hidden_features, bias = True)
    activation = nn.ReLU()
    output = nn.Linear(in_features = hidden_features, out_features=out_features, bias= True)
    net = nn.Sequential(hidden,activation,output)
    return net

 

모델의 파라미터 정의 

  • 손실함수
    • BCELoss : 이진분류 시 사용
    • CrossEntropy : 다중 클래스 분류 시 사용
    • MSELoss : 회귀 시 사용 
  • 옵티마이저
    • step() 메서드를 통해 파라미터를 전달받게됨. 모델 파라미터 별로 다른 기준을 적용시킬 수 있음
    • torch.optim.Optimizer(params,defaults)  옵티마이저 기본 클래스 
    • zero_grad() 메서드는 옵티마이저에 사용된 파라미터들의 기울기를 0 으로 만듬 
    • torch.optim.lr_scheduler : 에포크에 따라 학습률 조정 가능 
    • optim.Adam, optim.SparseAdma, optim.Adamax, optim.Adagrad, optim.ASGD, optim.LBFGS, optim.SGD ..
  • 학습률 스케쥴러 : 미리 지정한 횟수의 에포크를 지날 때 마다 학습률을 감소시켜서 학습 초기에는 빠른 학습을 진행하다 전역 최소점(global minima) 근처에서 학습률을 줄여서 최적점을 찾아갈 수 있게 함 
    • optim.lr_scheduler.LambdaLR : lambda 이용하여 그 함수 결과를 학습률로 설정
    • optim.lr_scheduler.StepLR : 특정 step 마다 학습률을 감마 비율만큼 감소 
    • optim.lr_scheduler.MultiStepLR : 특정 단계가 아닌 지정된 에포크 마다 학습률을 감마 비율로 감소 
    • optim.lr_scheduler.ExponentialLR : 에포크 마다 이전 학습률에 감마 만큼 곱함
    • optim.lr_scheduler.CosineAnnealingLR : 학습률을 코사인 함수 형태처럼 변화시킴 와리가리
    • optim.lr_shceduler.ReduceLROnPlateau : 학습이 잘 되고 있는지 여부에 따라 동적으로 학습률 변화시킴 
  • 지표 : 훈련과 테스트 단계 모니터링 

 

모델 훈련 

  • 모델 학습 과정 
    • 모델, 손실 함수, 옵티마이저 정의
    • optimizer.zero_grad()로 기울기 초기화 
      • 순환신경망(RNN)에서는 새로운 기울기 값을 이전 기울기값에 누적하여 계산하기 때문에 이를 사용할 필요 없음
    • ouput = model(input) : 출력 계산하며 전방향 학습
    • loss = loss_fn(output,target) : 오차 계산 
    • loss.backward() : 역전파 학습. 기울기 자동 계산. 이 때 배치가 반복될때마다 오차가 중첩적으로 쌓이므로 매번 기울기 초기화
    • optimizer.step() : 기울기 업데이트 
for epoch in range(100):

	yhat = model(x_train) #출력값
    loss = criterion(yhat, y_train) #yhat:output, y_train:target
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

 

모델 평가 

#터미널에 평가 위해 설치 

pip install torchmetrics 

#1. 함수 사용하여 평가하는 경우 

import torch
import torchmetrics 

preds = torch.radn(10,5).softmax(dim=1)
target = torch.randint(5,(10,))
acc = torchmetrics.functional.accuracy(preds,target)

#2. 모듈 사용하여 평가하는 경우 

import torch
import torchmetrics 
metric = torchmetrics.Accuracy() #초기화

n_batches = 10
for i in range(n_batches):
	preds = torch.randn(10,5).softmax(dim=1)
    target = torch.randint(5,(10,))
    
    acc = metric(preds,target)
    print(f"accuracy on batch {i} : {acc}")    #현재 배치에서의 모델 평가 정확도

acc= metric.compute()
 print(f"accuracy on all data: {acc}")  # 전체 모델 평가 정확도

 

실습 

1. 데이터 전처리

import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

dataset = pd.read_csv('~/chap02/data/car_evaluation.csv')

categorical_columns = ['price', 'maint', 'doors', 'persons', 'lug_capacity', 'safety']

#1. 카테고리 컬럼을 astype을 통해 범주형으로 변환 
for category in categorical_columns :
    dataset[category] = dataset[category].astype('category')

#2. 범주형 데이터(단어)를 숫자(넘파이 배열)로 변환하기 위해 cat.codes 사용 
price = dataset['price'].cat.codes.values
maint = dataset['maint'].cat.codes.values
doors = dataset['doors'].cat.codes.values
persons = dataset['persons'].cat.codes.values
lug_capacity = dataset['lug_capacity'].cat.codes.values
safety = dataset['safety'].cat.codes.values

#3. 넘파이 객체들을 합치기 위해 np.stack 사용 (np.stack 은 새로운 차원을 생성해주며 합치는 배열간 차원 즉 Shape가 같아야 함)
categorical_data = np.stack([price,maint,doors,persons,lug_capacity,safety],1)
# 확인을 원한다면 이 코드로 확인 categorical_data[:10]

#4. 넘파이 배열을 텐서로 변환 
categorical_data = torch.tensor(categorical_data, dtype = torch.int64)

#5. label 텐서로 변환 
outputs = pd.get_dummies(dataset.output)
outputs = outputs.values
outputs = torch.tensor(outputs).flatten()  #1차원으로 변환 

#6. 범주형 칼럼을 N차원으로 변환 
###이미 앞에서 범주형 데이터들 배열로 만들어줬는데, 단어 간의 세부 관계?를 잘 파악하기 위해서 기존 데이터셋에서 워드임베딩을 한다고함...
###일단 그래서 넘파이 배열 만든 걸 차원으로 변환...보통 칼럼의 고유값수, 차원의수(고유값수 절반)이 대세인가봄..
categorical_columns_sizes = [len(dataset[column].cat.categories) for column in categorical_columns]
categorical_embedding_sizes = [(col_size,min(50,(col_size+1)//2)) for col_size in categorical_columns_sizes]

#7. 데이터셋 분리 

total_records = 1728
test_records = int(total_records*.2)

categorical_train_data = categorical_data[:total_records-test_records]      #0~80%
categorical_test_data = categorical_data[total_records-test_records:total_records]  #80~100
train_outputs = outputs[:total_records-test_records]
test_outputs = outputs[total_records-test_records:total_records]

 

2. 모델 네트워크 생성

class Model(nn.Module):
    def __init__(self,embedding_size,output_size,layers,p=0.4):     #p=dropout
        super().__init__() #부모클래스인 self에 접근
        self.all_embeddings = nn.ModuleList([nn.Embedding(ni,nf) for ni,nf in embedding_size])
        self.embedding_dropout = nn.Dropout(p)
        
        all_layers=[]
        num_categorical_cols = sum((nf for ni,nf in embedding_size))
        input_size = num_categorical_cols
        
        for i in layers:
            all_layers.append(nn.Linear(input_size,i))
            all_layers.append(nn.ReLU(inplace=True))
            all_layers.append(nn.BatchNorm1d(i))      #배치정규화 
            all_layers.append(nn.Dropout(p))
            input_size = i
            
        all_layers.append(nn.Linear(layers[-1], output_size))
        self.layers = nn.Sequential(*all_layers)
        
    def forward(self,x_categorical):
        embeddings = []
        for i,e in enumerate(self.all_embeddings):
            embeddings.append(e(x_categorical[:,i]))
            
        x = torch.cat(embeddings,1)
        x = self.embedding_dropout(x)
        x = self.layers(x)
        
        return x

 

3. 모델 훈련

model = Model(categorical_embedding_sizes,4,[200,100,50],p=0.4) #임베딩 크기, 출력값크기, 은닉층 뉴런, 드롭아웃
print(model)
Model(
  (all_embeddings): ModuleList(
    (0): Embedding(4, 2)
    (1): Embedding(4, 2)
    (2): Embedding(4, 2)
    (3): Embedding(3, 2)
    (4): Embedding(3, 2)
    (5): Embedding(3, 2)
  )
  (embedding_dropout): Dropout(p=0.4, inplace=False)
  (layers): Sequential(
    (0): Linear(in_features=12, out_features=200, bias=True)
    (1): ReLU(inplace=True)
    (2): BatchNorm1d(200, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.4, inplace=False)
    (4): Linear(in_features=200, out_features=100, bias=True)
    (5): ReLU(inplace=True)
    (6): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.4, inplace=False)
    (8): Linear(in_features=100, out_features=50, bias=True)
    (9): ReLU(inplace=True)
    (10): BatchNorm1d(50, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (11): Dropout(p=0.4, inplace=False)
    (12): Linear(in_features=50, out_features=4, bias=True)
  )
)

 

4. 파라미터 정의

#손실함수, 옵티마이저
loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr=0.001)

#GPU사용 여부 
if torch.cuda.is_available():
    device = torch.device('cuda')
else:
    device = torch.device('cpu')

 

5. 모델 학습

epochs = 500

aggregated_losses=[]
train_outputs = train_outputs.to(device=device, dtype=torch.int64)

for i in range(epochs):
    i+=1
    y_pred = model(categorical_train_data).to(device)
    single_loss = loss_function(y_pred,train_outputs)
    aggregated_losses.append(single_loss)
    
    if i%25==1:
        print(f'epoch:{i:3} loss:{single_loss.item():10.8f}')
        
    optimizer.zero_grad()
    single_loss.backward()
    optimizer.step()

 

5. 테스트 데이터셋으로 모델 예측 : 오차 

test_outputs = test_outputs.to(device=device, dtype=torch.int64)
with torch.no_grad():
    y_val = model(categorical_test_data)
    loss = loss_function(y_val,test_outputs)
print(f'Loss:{loss:.8f}')
#Loss:0.55113798

 

6. 모델 예측값 확인 

#가장 큰 값을 갖는 인덱스 확인 
###인덱스가 0인 값이 인덱스가 1인 값보다 크기 때문에 처리된 출력??이???0이다???
y_val = np.argmax(y_val,axis=1)
print(y_val[:5])

#정확도, 정밀도, 재현율 확인 
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print(confusion_matrix(test_outputs,y_val))
print(classification_report(test_outputs,y_val))
print(accuracy_score(test_outputs, y_val))
[[259   0]
 [ 85   1]]
              precision    recall  f1-score   support

           0       0.75      1.00      0.86       259
           1       1.00      0.01      0.02        86

    accuracy                           0.75       345
   macro avg       0.88      0.51      0.44       345
weighted avg       0.81      0.75      0.65       345

0.7536231884057971

딥러닝 분류 모델의 성능 평가 지표 

 

  • 정확도(Accuracy) : 전체 예측 건수에서 정답을 맞춘 건수의 비율 
    • (True Positive + True Negative) / (모든 경우의 수)
  • 재현율(Recall) : 실제 정답이 1일때, 모델이 1로 예측한 비율 (처음부터 데이터가 1일 확률이 적을 때 사용)
    • True Positive / True Positive + False Negative 
  • 정밀도(Precision) : 모델이 1로 예측한 것 중에 실제로 정답이 1인 비율
    • True Positive / True Positive + False Positive 
  • F1 Score : 정밀도-재현율의 트레이드 오프 문제를 해결하기 위해 조화 평균을 구한 것 
    • 2 * (Precision * Recall ) / (Precision + Recall)