이진 분류 (Binary classification) 는 두 가지 범주 사이의 관계를 학습하여 데이터를 입력받았을 때 두 범주 중 하나로 분류하는 것이다. 이메일 스팸 분류 (스팸 / 스팸 아님), 병 진단 (양성 / 음성) 등이 이진 분류의 예시이다.
이진 분류에서는 목표 변수를 이산형 라벨로 매핑하는 것이 중요하다. 예를 들어 병 진단 자료라면, positive / negative 로 표현된 데이터를 negative → 0, positive → 1로 변환해주는 것이다.
# df = data to use [pandas DataFrame]
df.loc[:, 'Diagnosis'] = df['Diagnosis'].map({'Negative': 0, 'Positive': 1})
그리고 데이터를 특징 변수와 목표 변수로 나누자.
x = df[['Germ']].values
y = df[['Diagnosis']].values.astype(int)
전체 데이터를 training set, test set으로 나누어야 한다.
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)
위의 코드로 전체 데이터를 training set : test set = 8 : 2의 비율로 나눌 수 있다.
데이터 분할은 엄밀하게 따지면 보다 복잡한데, 더 자세한 내용은 cross validation을 참고하자.
☆ 검증 데이터 (validation set) 도 있는데, 모델의 학습 과정 중 성능을 평가하는 데 사용되는 데이터로서, 모델의 1 에폭마다 overfitting 여부를 확인하기 위해 사용한다.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)
y의 경우 0, 1로만 표현되는 데이터이므로 표준화(스케일링)가 필요없다.
이제 데이터를 tensor로 만들자.
x_train = torch.tensor(x_train, dtype=torch.float32)
x_test = torch.tensor(x_test, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1)
y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1)
이진 분류에는 Logistic regression이 사용된다. (자세한 것은 여기의 logistic regression 부분 참조) logistic regression은 sigmoid function ()를 사용하여 분류 경계를 설정하고 입력된 값을 0과 1 사이의 값으로 변환하여, 0에 가까운 값을 A group, 1에 가까운 값을 B group으로 분류한다.
다만 최적의 분류 경계를 설정하기 위해서 우선 선형 경계 찾기(→ linear regression)를 먼저 수행하게 된다. 이를 이용해 이진 분류 모델을 PyTorch로 구현한 코드는 아래와 같다.
import torch.nn as nn
class BinaryClassificationModel(nn.Module): # 부모 class의 기능을 상속받은 자식 class
def __init__(self):
super(BinaryClassificationModel, self).__init__() # 부모 class인 nn.Module의 생성자를 호출하여 상속받은 모든 초기화 작업을 수행
self.layer_1 = nn.Linear(1, 1) # 입력 차원과 출력 차원이 1
self.sigmoid = nn.Sigmoid()
def forward(self, x):
z = self.layer_1(x)
y = self.sigmoid(z)
return y
model = BinaryClassificationModel()
이진 분류 모델에서 sigmoid function을 사용하면 최종 출력값이 0~1 사이의 값이 된다. 따라서 선형 회귀 때와는 다르게 이진 교차 엔트로피 (Binary Cross Entropy; BCE) 를 손실 함수로 사용한다.
= { + }
BCE가 왜 이런 형태인지는 조건부 확률, maximum likelihood estimation 을 참고.
PyTorch에서 BCE를 구현한 코드는 아래와 같이 사용할 수 있다.
import torch.optim as optim
loss_function = nn.BCELoss()
본격적으로 학습을 하기 전에, Dataset과 DataLoader로 정리한 데이터를 이용하도록 하자.
from torch.utils.data import Dataset, DataLoader
class MyDataset(Dataset):
def __init__(self, x_tensor, y_tensor):
self.x = x_tensor
self.y = y_tensor
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
train_dataset = MyDataset(x_train, y_train)
test_dataset = MyDataset(x_test, y_test)
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
이제 본격적으로 학습 데이터로 최적화를 시켜 학습을 진행한다.
# optimizer로 stochastic gradient descent를 사용해 학습하자.
optimizer = optim.SGD(model.parameters(), lr=0.01)
num_epochs = 100
loss_list = []
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for x, y in train_loader:
optimizer.zero_grad()
outputs = model(x)
loss = loss_function(outputs, y)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
loss_list.append(epoch_loss / len(train_loader))
model.eval()
with torch.no_grad():
prediction = model(x_test)
predicted_labels = (prediction > 0.5).float() # 0.5보다 크면 1로 분류, 0.5보다 작거나 같으면 0으로 분류