[DACON] 손동작 분류 경진대회 with Pytorch
from google.colab import drive
drive.mount('/content/drive')
데이터를 구글 드라이브에 올려놓고, 코랩과 연동합니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings("ignore")
path = '/content/drive/MyDrive/hand_classification/'
train = pd.read_csv(path + 'train.csv')
test = pd.read_csv(path + 'test.csv')
sample_submission = pd.read_csv(path + 'sample_submission.csv')
train.head()
데이터를 판다스로 읽고 잘 읽혔는지 확인합니다. path는 데이터 파일이 저장되어있는 경로를 의미합니다.
print(train.shape)
print(test.shape)
데이터의 칼럼수는 타겟 열 제외하고 33개이고 트레인 데이터 2335개. 테스트 데이터 9343개 입니다.
데이터 개수가 2335개면 파라미터가 많은 모델을 사용하기 조금 적은 데이터이고, 테스트 데이터가 훨씬 많은 것도 특징입니다.
train.info()
데이터 칼럼명과 결측치를 확인하기 위한 함수입니다. 결측치는 없는 것으로 확인됩니다.
train['target'].value_counts()
우리가 맞춰야 하는 타겟 값의 분포입니다. 상당히 골고루 분포되어있는 걸 알 수 있습니다.
불균형하게 분포되어있다면 별도의 조정이 필요합니다만 그럴 필요는 없어보입니다.
plt.figure(figsize=[12,8])
plt.text(s="Target variables",x=0,y=1.3, va='bottom',ha='center',color='#189AB4',fontsize=25)
plt.pie(train['target'].value_counts(),autopct='%1.1f%%', pctdistance=1.1)
plt.legend(['3', '2', '1', '0'], loc = "upper right",title="Programming Languages", prop={'size': 15})
plt.show()
방금 살펴본 내용을 파이 그래프를 통해 시각화 하였습니다.
train.describe().T
데이터의 분포를 보다 자세히 살펴보기 위해 describe 함수를 사용했습니다. 칼럼수가 다소 많기 때문에 보기 편하기 위해 T 함수를 사용했습니다.
변수들의 이름이 모두 sensor_ 형태로 구성되어 있습니다. 또 평균값이 모두 0 근처에 있군요.
변수들마다 조금씩 차이는 있지만 대체로 최솟값은 -130 밑으로는 안떨어지고 최댓값도 +130 위로는 올라가지 않습니다.
변수들의 분포가 상당히 유사해 보이는데요. 이미지의 하나의 픽셀값 같이 하나의 데이터를 32분할한 데이터로 추측됩니다.
이런 데이터는 변수간 일관성이 있기 때문에 딥러닝 기반 모델이 효율적일 것으로 판단됩니다.
train_x = train.drop(['id', 'target'], axis = 1)
test_x = test.drop(['id'], axis = 1)
mins = train_x.min()
maxs = train_x.max()
mins[:5]
데이터 내 칼럼별로 최솟값, 최댓값을 추출했습니다. 데이터들을 스케일링 하기 위한 목적입니다.
train_x = (train_x - mins) / (maxs - mins)
test_x = (test_x - mins) / (maxs - mins)
train_x.describe().T[['min', 'max']]
(데이터 - 최솟값) / (최댓값 - 최솟값) 연산을 거치게 되면 데이터 값들이 모두 0~1 사이로 가지게 됩니다.
딥러닝에서 입력값을 표준화 시키는 것이 상당히 중요합니다.
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import random
random.seed(42)
torch.manual_seed(42)
딥러닝에 필요한 파이토치 관련 패키지들을 설치합니다.
train_x = torch.from_numpy(train_x.to_numpy()).float()
train_y = torch.tensor(train['target'].to_numpy(), dtype = torch.int64)
test_x = torch.from_numpy(test_x.to_numpy()).float()
train_x
데이터가 판다스의 데이터 프레임 형식으로 저장되어 있습니다. 데이터 프레임을 넘파이로, 다시 넘파이를 텐서 형태로 바꾼 모습입니다.
파이토치 모델을 이용하기 위해서 데이터는 텐서형태로 변환해주어야 합니다.
train_dataset = TensorDataset(train_x, train_y)
print(train_dataset.__len__())
print(train_dataset.__getitem__(1))
입력한 데이터를 파이토치에서 지원하는 TensorDataset 함수를 사용해 데이터 셋 형태로 만들었습니다.
데이터가 데이터 셋 내 잘 입력 됬는지 len, getitem 함수를 통해 확인했네요.
바로 뒤에 나오는 데이터 로더를 사용하기 위해선 데이터 셋 형태로 데이터를 만들어야 합니다.
train_dataloader = DataLoader(train_dataset, batch_size = 16, shuffle = True)
for batch_idx, samples in enumerate(train_dataloader):
if batch_idx > 0:
break
print(samples[0].shape)
print(samples[1])
데이터 셋을 파이토치 내 DataLoader 함수에 넣어 데이터 로더를 만들었습니다.
데이터 로더 형식을 이용하면 배치단위로 데이터를 모델에 넣을 수 있고 shuffle 인자를 사용해 데이터를 쉽게 섞을 수도 있습니다.
class Models(nn.Module):
def __init__(self):
super().__init__()
self.linear_relu_stack = nn.Sequential(
nn.Linear(32, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(64, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 4),
)
def forward(self, x):
x = self.linear_relu_stack(x)
return x
model = Models()
print(model)
간단한 딥러닝 모델을 직접 제작했습니다. 파이토치 내 nn.Module 클래스를 상속받았습니다.
32개에 입력 데이터를 받아 64개, 128개로 은닉 층 내 노드 개수를 늘리다가 예측 값이 4개이기 때문에 최종 출력 노드는 4개로 구성했습니다.
BatchNorm1d와 Dropout을 이용해 배치 정규화와 드롭아웃 기능을 사용했으며 활성화 함수로는 Relu를 사용했습니다.
데이터가 적기 때문에 파라미터수가 많으면 안될 것 같아서 층을 적게 쌓았습니다.
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
for epoch in range(20):
running_loss = 0.0
accuracy = 0
for i, data in enumerate(train_dataloader, 0):
inputs, labels = data
optimizer.zero_grad() # 매개변수를 0으로 만듭니다. 매 학습시 초기화해줘야합니다.
outputs = model(inputs) # 입력값을 넣어 순전파를 진행시킵니다.
loss = criterion(outputs, labels) # 모델 출력값와 실제값을 손실함수에 대입합니다.
loss.backward() # 손실함수에서 역전파 수행합니다.
optimizer.step() # 옵티마이저를 사용해 매개변수를 최적화합니다.
running_loss += loss.item()
_, predictions = torch.max(outputs, 1)
for label, prediction in zip(labels, predictions):
if label == prediction:
accuracy = accuracy + 1
print(f'{epoch + 1} 에포크 loss: {running_loss / i:.3f}')
print(f'{epoch + 1} 에포크 정확도: {accuracy / (i * 16):.3f}')
20 에포크를 진행했으며 에포크마다 학습이 잘 되어가는지를 평가했습니다. 옵티마이저로 무난한 Adam을 사용했습니다.
손실함수는 CrossEntropyLoss을 사용했는데, 타겟값을 원-핫 인코딩 하지 않아도 알아서 적용시켜주며 소프트맥스함수까지 알아서 적용해줘서 손실값을 구해주는 편리한 함수 입니다.
model.eval() # 모델을 평가모드로 바꿉니다. dropout이 일어나지 않습니다.
with torch.no_grad(): # 이 안의 코드는 가중치 업데이트가 일어나지 않습니다.
outputs = model(test_x)
_, pred = torch.max(outputs, 1)
pred
앞서 구한 모델에 테스트 데이터를 넣어서 결과를 출력합니다. torch.max 함수가 매우 편리합니다.
sample_submission['target'] = pred.numpy()
sample_submission['target'].value_counts()
테스트 데이터의 타겟 예측 값 분포 입니다. 다소 불균형이 있지만 그래도 모델이 어느정도 기능을 하는 것 같습니다.
sample_submission.to_csv('dacon_hands_4.csv',index=False)
최종 결과를 csv 형태로 저장합니다.
역시 간간히 진행하지 않으면 실력이 금방 녹쓰는 것 같습니다. 간단한 딥러닝 코드를 쓰는 것도 쉽지 않네요.
모델을 더 최적화 시킬수 있을 것 같습니다. 층 개수 조정, 히든층 노드 수 변경 등 여러가지를 시도할 수 있겠네요.
데이터가 적어서 딥러닝 모델이 제 성능을 발휘할까 의심이 있었는데 꽤 괜찮은 모습을 보인 것 같습니다.
딥러닝 코드는 여기서 많이 참고했습니다. 감사합니다.