from sklearn import datasets
import torch
import torch.nn as nn
import torch.optim as optim
torch의 nn모듈에는 신경망을 구현하는 데 필요한 기능들이,
optim 모듈에는 다양한 옵티마이저가 구현되어 있다.
from sklearn.datasets import fetch_openml
dataset = fetch_openml('boston', as_frame=True)
사이킷런에서 보스턴 주택 데이터 세트를 불러온다.
기존에 사용한 datasets.load_boston() 함수는 삭제되어,
위와 같은 방식으로 불러오자.
x,y = dataset['data'], dataset['target']
이 때 X는 506 rows × 13 columns의 데이터프레임, y는 Length: 506, dtype: float64인 torch.Tensor이다.
import numpy as np
X = X.astype(np.float64)
X = torch.FloatTensor(X.values)
X
X를 실수형 텐서로 만들고
y = torch.FloatTensor(y).unsqueeze(-1)
y
(506,) 형태의 1차원 배열이었던 y를 (506,1) 형태의 2차원 배열로 만들었다.
선형 회귀 모델은 데이터 범위에 민감하다. 데이터 범위 조정을 위해 표준화를 적용하자.
X = (X - torch.mean(X)) / torch.std(X)
이 때 표준화는 사이킷런의 StandardScaler를 사용해도 되지만 2차원 배열까지만 지원하므로
표준화를 직접 하는 것에 익숙해지도록 하자.
model = nn.Linear(13,1)
nn모듈의 Linear 클래스는 선형 모델을 구현한 것으로, 파라미터에는 각각 입력값, 출력값의 개수를 전달한다.
입력받을 데이터 개수는 입력 데이터의 특정개수를 의미하므로 13이고,
출력 데이터 개수를 1로 설정하면 선형 회귀의 개념이 적용된다.
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr = 0.01)
회귀 모델을 학습시키기 위한 손실 함수로 평균제곱오차를 사용한다. 평균제곱오차는 nn모듈에 MSELoss 클래스로 구현되어 있다.
그리고 확률적 경사 하강법 옵티마이저 객체를 생성하고,
첫번째 파라미터에 가중치를 전달하고,
lr 파라미터에 학습률을 0.01로 지정했다.
def train(model, criterion, optimizer, X, y):
optimizer.zero_grad() #기울기 초기화
hypothesis = model(X) #모델을 사용해 타깃 추론
loss = criterion(hypothesis, y) #오차 계산
loss.backward() #경사하강법으로 가중치 수정
optimizer.step()
return loss.item() #현재 에포크의 오차 반환
학습함수를 정의하고, 에포크를 100번 반복하여 모델을 학습시켜보자.
n_epochs = 100
for epoch in range(1, n_epochs+1):
loss = train(model, criterion, optimizer, X, y)
if epoch % 10 == 0:
print('epoch : {}, loss : {:.4f}'.format(epoch,loss))
에포크가 반복될 수록 오차가 감소하는 것을 볼 수 있다.
선형 회귀와 달리 로지스틱 회귀는 이진 분류 모델로, 선형 회귀에 시그모이드 함수를 추가한 형식이다.
이진 분류 모델을 학습시킬 때에는 이진 크로스 엔트로피 손실 함수를 사용한다.
dataset = datasets.load_breast_cancer()
X,y = dataset['data'], dataset['target']
X = torch.FloatTensor(X)
y = torch.FloatTensor(y).view(-1,1)
view 메서드의 두번째 파라미터를 1로 지정하여 타깃 데이터의 길이를 1로 변환했다
위의 unsqueeze와 같은 결과이다.
X = (X - torch.mean(X)) / torch.std(X)
model = nn.Sequential(
nn.Linear(30,1),
nn.Sigmoid()
)
유방암 진단 데이터세트의 입력 데이터의 특성이 30개이므로,
30개의 값을 입력받는 로지스틱 회귀 모델 객체를 생성하고,
선형회귀의 결과에 시그모이드 함수를 취했다.
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr = 0.1)
분류 모델을 학습시킬 것이므로, 손실함수로 이진 크로스 엔트로피를 사용한다.
또 모델 가중치와 학습률을 파라미터로 지정해서, SGD 옵티마이저 객체를 생성한다.
def train(model, criterion, optimizer, X, y):
optimizer.zero_grad()
hypothesis = model(X)
loss = criterion(hypothesis,y)
loss.backward()
optimizer.step()
return loss.item()
위의 모델 학습을 위한 train 함수와 동일하다.
n_epochs = 100
for epoch in range(1, n_epochs+1):
loss = train(model, criterion, optimizer, X,y)
if epoch % 10 == 0:
print('epoch : {}, loss : {:.4f}'.format(epoch,loss))
이제 학습된 모델을 사용해서 타겟을 추론하고 정확도를 계산해보자.
y_predicted = (model(X) >= 0.5).float()
score = (y_predicted == y).float().mean()
print('accuracy: {:.2f}'.format(score))
정확도를 계산하기 위해 모델을 사용해서 타깃을 추론한다.
이 때 0.5 이상이면 True(1), 미만이면 False(0)으로 정확도를 계산한다.
그런데 사실 여기서 학습 세트와 테스트 세트를 나누지 않았기 때문에 이 정확도는 큰 의미는 없다..
심층신경망(딥러닝)은 모델의 구조가 복잡하기 때문에
모델의 클래스를 직접 정의하는 것이 코드를 작성하고 관리하기 훨씬 효율적이다.
앞에서 선형 회귀 모델의 객체를 다음과 같이 생성했다.
model = nn.Linear(13,1)
이 선형 모델을 클래스로 작성하면 다음과 같다.
#n개의 값을 입력받는 선형 회귀 모델 클래스 정의
class LinearRegression(nn.Module):
def __init__(self, num_features): #생성자에서 모델의 구조 정의
super().__init__() #상속받아 생성한 객체이므로 부모 nn.Module의 생성자 호출
self.linear = nn.Linear(num_features,1) #num_features개의 특성을 입력받는 선형 모델 객체 생성
def forward(self, X): #순전파 정의
out = self.linear(X) #생성자에서 만든 선형 모델로 타깃을 추론하고 반환
return out
model = LinearRegression(13) #13개의 값을 입력받는 선형 회귀모델 생성
복잡한 모델의 구조에서 클래스를 직접 정의하면 일관성 있는 방식으로 코드를 작성할 수 있다.
먼저 파이토치에서 모델 클래스를 정의할 때에는 nn.Module클래스를 상속받아야하고, 순전파를 반드시 정의해야한다.
선형 회귀 모델은 몇개의 특성을 입력받는지에 따라 가중치의 개수가 달라지기 때문에
num_features 파라미터로 특성의 개수를 전달받도록 정의했다.
순전파 메서드는 입력데이터의 파라미터 이름을 X로 정의했고, 선형모델이 저장된 변수 linear를 이용해 타깃을 추론하고 결과를 반환했다.
이번에는 로지스틱 회귀 모델 클래스도 정의해보자.
model = nn.Sequential(
nn.Linear(30,1),
nn.Sigmoid()
)
class LogisticRegression(nn.Module):
def __init__(self, num_features):
super().__init__()
self.linear = nn.Linear(num_features,1)
self.sigmoid = nn.Sigmoid()
def forward(self,X):
out = self.linear(X)
out = self.sigmoid(out)
return out
model = LogisticRegression(30)
마찬가지로 nn.Module클래스를 상속받았고, num_features 파라미터로 특성의 개수를 전달받도록 정의했다.
순전파 과정에서는 선형모델과 시그모이드 함수를 순서대로 사용해 타깃을 추론하고, 선형 모델로 얻은 결과값을 변수 out에 저장하고, out은 다시 시그모이드의 입력이 된다.
from sklearn import datasets
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset,DataLoader
데이터를 불러오고, 위에서 했듯 텐서 자료구조로 변환/표준화를 적용하자.
dataset = datasets.load_breast_cancer()
X,y = dataset['data'], dataset['target']
X = torch.FloatTensor(X)
y = torch.FloatTensor(y).view(-1,1)
X = (X - torch.mean(X)) / torch.std(X)
텐서로 변환한 입력데이터와 타깃을 TensorDataset클래스를 이용해서 파이토치 데이터세트로 만들자.
그리고 배치 크기를 256으로 지정하여 한번에 256개의 데이터 샘플을 사용하도록 하고,
에포크마다 데이터 순서를 무작위로 변경하도록 하였다.
dset = TensorDataset(X,y)
loader = DataLoader(dset, batch_size = 256, shuffle = True)
class NeuralNetwork(nn.Module):
def __init__(self,num_features):
super().__init__()
self.linear1 = nn.Linear(num_features,4)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(4,1)
self.sigmoid = nn.Sigmoid()
def forward(self,X):
out = self.linear1(X)
out = self.relu(out)
out = self.linear2(out)
out = self.sigmoid(out)
입력받는 데이터의 특성 개수, 출력하는 데이터의 특성 개수(노드 개수)를 전달받는다.
self.linear1 = nn.Linear(num_features,4)
self.relu = nn.ReLU()
에서는 num_features개의 특성을 입력받는 은닉층 노드를 4개 생성하고, 렐루를 활성화 함수로 사용해서 하나의 은닉층을 구성하였다.
self.linear2 = nn.Linear(4,1)
self.sigmoid = nn.Sigmoid()
에서는 4개의 값을 입력받는 출력층 노드 1개를 생성하고, 시그모이드 함수를 활성화 함수로 사용해서 출력층을 구성했다.
def forward(self,X):
out = self.linear1(X)
out = self.relu(out)
out = self.linear2(out)
out = self.sigmoid(out)
순전파 메서드에서는 입력 데이터 X를 은닉층과 출력층 노드를 통과시킨 후 타깃을 추론하고 반환한다.
model = NeuralNetwork(30)
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)
이제 클래스를 사용해서 신경망 객체를 생성하고,
입력 데이터의 특성 개수를 30으로 지정했다.
진단이 양성인지 음성인지 분류하는 이진 분류 문제이므로
이진 크로스 엔트로피 손실함수 객체를 생성하고
SGD 옵티마이저 객체를 생성했다.
def train(model, criterion, optimizer, loader):
epoch_loss = 0
for X_batch, y_batch in loader:
optimizer.zero_grad()
hypothesis = model(X_batch)
loss = criterion(hypothesis, y_batch)# 손실 함수로 오차를 계산
loss.backward() #기울기 계산
optimizer.step() #경사하강법으로 가중치 수정
epoch_loss += loss.item()
return epoch_loss / len(loader)
위에서는 def train(model, criterion, optimizer, X, y)으로 정의했는데
이번에는 데이터로더를 저장한 변수 loader를 사용했다.
데이터로더를 이용해 에포크마다 256개 배치 단위로 학습시킨다.
n_epochs = 100
for epoch in range(1, n_epochs+1):
loss = train(model, criterion, optimizer, loader)
if epoch % 10 == 0:
print('epoch: {}, loss: {:.4f}'.format(epoch, loss))
y_predicted = (model(X) >= 0.5).float()
score = (y_predicted==y).float().mean()
print('accuracy: {:.2f}'.format(score))
모델을 이용해서 타깃을 추론하고 실제 타깃과 비교해 정확도를 계산하니 0.92가 나왔다.
은닉층 노드 개수가 많아지면 가중치 개수도 많아지므로 학습 데이터의 정보를 더 많이 기억할 수 있지만
노드 개수가 너무 많아지면 과적합 문제가 발생할 수도 있다.
모델 객체의 state_dict 메서드로 학습된 모델의 가중치를 가진 파이썬 딕셔너리를 불러올 수 있다.
즉 가중치 딕셔너리를 파일로 저장했다가 필요할 때 불러와서 사용한다.
위에서 배치학습에 사용한 코드를 그대로 복붙했다.
dataset = datasets.load_breast_cancer()
X,y = dataset['data'], dataset['target']
X = torch.FloatTensor(X)
y = torch.FloatTensor(y).view(-1,1)
X = (X - torch.mean(X)) / torch.std(X)
dset = TensorDataset(X,y)
loader = DataLoader(dset, batch_size = 256, shuffle = True)
class NeuralNetwork(nn.Module):
def __init__(self,num_features):
super().__init__()
self.linear1 = nn.Linear(num_features,4)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(4,1)
self.sigmoid = nn.Sigmoid()
def forward(self,X):
out = self.linear1(X)
out = self.relu(out)
out = self.linear2(out)
out = self.sigmoid(out)
model = NeuralNetwork(30)
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)
def train(model, criterion, optimizer, loader):
epoch_loss = 0
for X_batch, y_batch in loader:
optimizer.zero_grad()
hypothesis = model(X_batch)
loss = criterion(hypothesis, y_batch)# 손실 함수로 오차를 계산
loss.backward() #기울기 계산
optimizer.step() #경사하강법으로 가중치 수정
epoch_loss += loss.item()
return epoch_loss / len(loader)
n_epochs = 100
for epoch in range(1, n_epochs+1):
loss = train(model, criterion, optimizer, loader)
if epoch % 10 == 0:
print('epoch: {}, loss: {:.4f}'.format(epoch, loss))
이제 모델을 사용해 타깃을 추론하고,
정확도를 계산한 후에
현재 경로에 학습된 모델을 저장해보자.
y_predicted1 = (model(X) >= 0.5).float()
score_of_trained_model = (y_predicted1==y).float().mean()
print('accuracy of trained model: {:.2f}'.format(score_of_trained_model))
torch.save(model.state_dict(), './trained_model.pt')
참고로 파이토치 모델을 저장할 때 확장자로 pt 또는 pth를 사용한다.
이제 저장된 모델을 불러오기 위해 신경망 모델의 객체를 새로 만들고
가중치를 불러와 복원한 후
불러온 모델을 사용해 타깃을 추론하고 모델의 정확도를 다시 측정해보자.
loaded_model = NeuralNetwork(30)
loaded_model.load_state_dict(torch.load('./trained_model.pt'))
y_predicted2 = (loaded_model(X) >= 0.5).float()
score_of_loaded_model = (y_predicted2==y).float().mean()
print('accuracy of loaded model: {:.2f}'.format(score_of_loaded_model))
이전과 같은 결과가 나오는 것을 확인할 수 있다.
출처: 이토록 쉬운 머신러닝&딥러닝 입문 with 사이킷런