앞선 '타이타닉 생존자 예측하기' 글과 마찬가지로 이번에도 캐글에서 제공하는 데이터셋을 이용한 실습을 진행해봤습니다.😊 앞선 글은 '어떤 모델을 사용할 수 있는가?'에 초점을 맞춘 글이었습니다. 이번 글은 '케라스, 파이토치에서 어떻게 이미지 분류를 할 수 있는지'에 더 초점을 맞춘 글이 될 것 같습니다.
목차
- Data Dictionary
- Data Load
- Data Preprocessing
- solution1 : DNN With Keras
- solution2 : CNN With Keras
- solution3 : CNN With Pytorch
Data Dictionary
fashion mnist 데이터셋은 28*28 픽셀로 이루어진 흑백 이미지 70,000장을 제공한다.
각 이미지는 10개의 class로 분류될 수 있으며 각 번호에 대한 정보는 다음과 같다.
- 0: T-shirt/top
- 1: Trouser
- 2: Pullover
- 3: Dress
- 4: Coat
- 5: Sandal
- 6: Shirt
- 7: Sneaker
- 8: Bag
- 9: Ankle boot
Data Load
fashion mnist 데이터는 from keras.datasets import fashion\_mnist
으로도 로드할 수 있지만,
본 실습에서는 캐글에서 다운로드한 파일을 드라이브를 통해 직접 로드하였다.
from google.colab import drive
drive.mount('/content/drive')
import pandas as pd
train = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/fashion-mnist_train.csv')
test = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/fashion-mnist_test.csv')
train.head()
test.head()
Data Preprocessing
1) 지금의 데이터셋은 라벨과 픽셀 데이터가 같이 있으므로 이를 분리해줄 필요가 있다.
- iloc를 사용하여 전체 행에 대해 0번 열은 y, 나머지 열은 x로 분류한다.
2) data frame을 바로 신경망에 입력할 수 없으므로 데이터의 형태를 바꿔준다.
- 픽셀 데이터는 np.array()를 이용해 data frame에서 벡터로 바꾼다.
- 신경망에 이미지 형태로 입력하기 위해 벡터에서 (28,28) 형태로 reshape한다.
- 라벨은 to_categorical()으로 원-핫 인코딩한다.
3) 픽셀 데이터를 정규화한다.
- 0 ~ 254의 값을 갖는 픽셀 데이터를 0 ~ 1 사이의 값을 갖도록 255로 나눈다.
from tensorflow.keras.utils import to_categorical
import numpy as np
# 데이터 전처리 함수
def data_prep(raw):
out_y = to_categorical(np.array(raw.iloc[:, 0])) # 라벨 원-핫 인코딩
x_as_array = np.array(raw.iloc[:, 1:]) # 픽셀 데이터 분리
num_images = raw.shape[0]
x_shaped_array = x_as_array.reshape(num_images, 28, 28) # 28*28 이미지로 reshape
out_x = x_shaped_array / 255 # 정규화
return out_x, out_y
x_train, y_train = data_prep(train)
x_test, y_test = data_prep(test)
불러온 데이터를 출력하면 아래와 같다.
import matplotlib.pyplot as plt
plt.imshow(x_train[1])
plt.title('Class: {}'.format(np.argmax(y_train[1])))
plt.figure()
y_train[1]
>> array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.], dtype=float32)
픽셀 데이터가 잘 이미지화되었으며, 라벨도 잘 인코딩 되었음을 확인할 수 있다.
solution1 - DNN With Keras
Dense 층으로만 구성된 모델 사용한다.
케라스에서 제공하는 라이브러리를 이용해 모델을 만드는 순서는 다음과 같다.
패키지 임포트 >> 모델 정의 >> 모델 컴파일 >> 모델 학습(fitting) >> 모델 평가
참고 : https://github.com/Tec4Tric/Clothing_Image_Classification/blob/master/Using_ANN.py
# Importing Packages
import numpy as np
import keras
from keras.models import Sequential
from keras.layers import Flatten, Dense
import matplotlib.pyplot as plt
%matplotlib inline
# Defining the Model
model = Sequential()
model.add(Flatten(input_shape=((28,28))))
model.add(Dense(200, activation="relu"))
model.add(Dense(10, activation="softmax"))
# Compiling the Model
model.compile(optimizer = "adam", loss="categorical_crossentropy", metrics = ["accuracy"])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
dense (Dense) (None, 200) 157000
dense_1 (Dense) (None, 10) 2010
=================================================================
Total params: 159,010
Trainable params: 159,010
Non-trainable params: 0
_________________________________________________________________
# Fitting the Model
history = model.fit(x_train, y_train, epochs = 20)
# Evaluating on the Test Data
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
>> Test loss: 0.33452051877975464
>> Test accuracy: 0.8934999704360962
solution2 - CNN With Keras
케라스에서 제공하는 Conv2D, MaxPooling2D, Dropout, Flatten, Dense 함수를 사용해 CNN을 생성한다.
또한, 앞의 solution1과 달리 model.fit 함수에 batch, validation data라는 옵션을 주어 더 정교하게 학습할 수 있게 한다.
참고 : https://www.kaggle.com/bugraokcu/cnn-with-keras
# Importing Packages
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import BatchNormalization
# Hyper Parameter
batch_size = 256
num_classes = 10
epochs = 50
input_shape = (28, 28, 1)
# Defining the Model
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
kernel_initializer='he_normal',
input_shape= input_shape))
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(Dropout(0.4))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(num_classes, activation='softmax'))
👾 사용된 함수와 매개변수
1) Conv2D : 합성곱 층
첫 번째 인자로 커널의 수를, 두 번째 인자로 커널의 사이즈를 받고 이후 padding, input_shape, activation 등을 매개변수로 받는다.
2) MaxPooling2D : 풀링 층
pool_size(풀링에 사용되는 커널의 크기), strides, padding 등을 매개변수로 받는다
3) Dropout
dropout_rate(드랍할 확률)을 매개변수로 받는다.
4) Dense
입력과 출력을 fully connected 시키는 계층이다. 출력 뉴런과 입력 뉴런의 수, 활성화 함수를 매개변수로 받는다.
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
max_pooling2d (MaxPooling2D (None, 13, 13, 32) 0
)
dropout (Dropout) (None, 13, 13, 32) 0
conv2d_1 (Conv2D) (None, 11, 11, 64) 18496
max_pooling2d_1 (MaxPooling (None, 5, 5, 64) 0
2D)
dropout_1 (Dropout) (None, 5, 5, 64) 0
conv2d_2 (Conv2D) (None, 3, 3, 128) 73856
dropout_2 (Dropout) (None, 3, 3, 128) 0
flatten_1 (Flatten) (None, 1152) 0
dense_2 (Dense) (None, 128) 147584
dropout_3 (Dropout) (None, 128) 0
dense_3 (Dense) (None, 10) 1290
=================================================================
Total params: 241,546
Trainable params: 241,546
Non-trainable params: 0
_________________________________________________________________
# Compiling the Model
model.compile(optimizer = "adam", loss="categorical_crossentropy", metrics = ["accuracy"])
# Validation Data
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=13)
# Fitting the Model
history = model.fit(X_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=0, # 0 : hide accuracy, 1 : show accuracy per epoch
validation_data=(X_val, y_val))
# Evaluating on the Test Data
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
>> Test loss: 0.1968483328819275
>> Test accuracy: 0.9289000034332275
에폭이 증가함에 따라 loss는 줄어들고 accuracy는 증가하는지 그래프로 검증해보자.
케라스에서 모델을 학습시킬 때 사용하는 fit 함수는 history 객체를 반환한다.
history 객체는 아래의 정보를 담고 있다.
- 각 에폭의 훈련 손실값 (loss)
- 각 에폭의 훈련 정확도 (acc)
- 각 에폭의 검증 손실값 (val_loss)
- 각 에폭의 검증 정확도 (val_acc)
수치들은 각 에폭이 끝날 때마다 값이 추가되어 배열 형태로 저장되어 있다.
이러한 수치들을 그래프로 표시하여 비교하면 학습 상태를 직관적으로 이해하기 쉽다.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (12, 5))
# 검증 오차
y_vloss = history.history['val_loss']
# 훈련 오차
y_loss = history.history['loss']
# 그래프로 표현
x_len = np.arange(len(y_loss))
ax1.plot(x_len, y_vloss, marker = '.', c="red", label='Testset_loss')
ax1.plot(x_len, y_loss, marker = '.', c='blue', label = 'Trainset_loss')
# 그래프에 그리드를 주고 레이블을 표시
ax1.legend(loc='upper right')
ax1.grid()
ax1.set(xlabel='epoch', ylabel='loss')
# 검증 정확도
y_vaccuracy = history.history['val_accuracy']
# 검증 정확도
y_accuracy = history.history['accuracy']
# 그래프로 표현
x_len = np.arange(len(y_accuracy))
ax2.plot(x_len, y_vaccuracy, marker = '.', c="red", label='Testset_accuracy')
ax2.plot(x_len, y_accuracy, marker = '.', c='blue', label = 'Trainset_accuracy')
# 그래프에 그리드를 주고 레이블을 표시
ax2.legend(loc='lower right')
ax2.grid()
ax2.set(xlabel='epoch', ylabel='accuracy')
# draw gridlines
ax2.grid(True)
plt.show()
solution3 - CNN whith Pytorch
# Importing Packages
import pandas as pd
import numpy as np
from torch.utils.data import Dataset, DataLoader
from torch import nn
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import torch
Data Load
케라스를 통한 실습에서는 csv 데이터를 (-1,28,28,1) 형태로 만들어 (28,28,1) 이미지를 모델에 입력하였다.
하지만 파이토치에서는 일반적으로 데이터를 데이터 세트, 데이터 로더를 통해 다루므로
본 실습에서는 데이터 세트와 데이터 로더를 이용해 모델에 데이터를 전달해보고자 한다.
참고 : https://sjkoding.tistory.com/10
1. csv 파일 로드
import pandas as pd
train = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/fashion-mnist_train.csv')
test = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/fashion-mnist_test.csv')
2. 파이토치에서 제공하는 데이터셋 이용
# transform = transforms.Compose(transforms.ToTensor())
# trainset = datasets.FashionMNIST('~/.pytorch/F_MNIST_data/', download=True, train=True, transform=transform)
# testset = datasets.FashionMNIST('~/.pytorch/F_MNIST_data/', download=True, train=False, transform=transform)
Data Preprocess
1) 사용자 정의 Data set에서 픽셀 데이터와 라벨 데이터를 슬라이싱으로 분리하고 픽셀 데이터를 (-1,28,28,1)로 reshape 한다.
2) Data set으로 데이터를 불러올 때, transform의 ToTensor로 이미지 데이터의 범위를 [0,1] 사이로 정규화한다.
3) 배치 사이즈, 셔플 옵션과 함께 Data loader에 Data set을 전달하여 모델이 효과적으로 학습하게 한다.
cf. ToTensor가 [0,1] 범위로 데이터를 변환하지 않는 오류 해결 :
https://discuss.pytorch.org/t/does-pytorch-automatically-normalizes-image-to-0-1/40022/3
class Dataset(Dataset):
def __init__(self, data, transform = None):
self.fashion_mnist = list(data.values)
self.transform = transform
label, img = [], []
for one_line in self.fashion_mnist: # 인덱스 슬라이싱으로 label, img 분리
label.append(one_line[0])
img.append(one_line[1:])
self.label = np.array(label)
self.img = np.array(img).reshape(-1, IMAGE_SIZE, IMAGE_SIZE, CHANNEL).astype('uint8') # reshape
def __len__(self):
return len(self.label)
def __getitem__(self, idx):
label, img = self.label[idx], self.img[idx]
if self.transform:
img = self.transform(img)
return label, img
# hyper parameter
BATCH_SIZE = 25
LR = 5e-3
NUM_CLASS = 10
IMAGE_SIZE = 28
CHANNEL = 1
Train_epoch = 30
My_transform = transforms.Compose([
transforms.ToTensor() # 범위를 [0, 255] 에서 [0.0, 1.0]로 정규화
])
Train_data = Dataset(train, transform=My_transform)
Test_data = Dataset(test, transform=My_transform)
Train_dataloader = DataLoader(dataset=Train_data,
batch_size = BATCH_SIZE,
shuffle=True)
Test_dataloader = DataLoader(dataset=Test_data,
batch_size = 1,
shuffle=False)
Modelling
nn.Module을 상속받는 사용자 모델을 정의하자.
총 3개의 합성곱 계층과 fc층으로 구성된 모델이다.
- Conv2d : 입력 체널, 출력 체널, 커널 사이즈 등을 매개변수로 받는다.
- BatchNorm2d : 활성 함수의 출력 값을 정규화하는 작업을 한다. 입력 채널 수를 매개변수로 받는다.
- ReLU : 비선형성을 위한 활성화 함수이다.
- MaxPool2d : max pooling으로 이미지의 사이즈를 줄인다
참고 : https://gaussian37.github.io/dl-pytorch-conv2d/
https://wjddyd66.github.io/pytorch/Pytorch-Problem/
# Defining the Model
class My_model(nn.Module):
def __init__(self, num_of_class):
super(My_model, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1), # 28 * 28 * 16
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)) # 14 * 14 * 16
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1), # 14 * 14 * 32
nn.BatchNorm2d(32),
nn.ReLU()
# nn.MaxPool2d(kernel_size=2, stride=2) # 7 * 7 * 32
) # 14 * 14 * 32
self.layer3 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
) # 7 * 7 * 64
self.fc = nn.Linear(7 * 7 * 64, num_of_class)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out
위에서 정의된 모델을 통해 학습을 진행하자.
가장 성능이 좋은 모델을 구하기 위해 min_Loss를 갱신하며 가장 작은 loss를 갖는 model을 저장한다.
def train():
# 모델 선언
model = My_model(NUM_CLASS)
# 옵티마이저 선언
optimizer = torch.optim.Adam(model.parameters(), lr = LR)
# 손실함수 선언
criterion = nn.CrossEntropyLoss()
min_Loss = 100000
for epoch in range(1, Train_epoch + 1):
for batch_id, (label, image) in enumerate(Train_dataloader):
# 모델에 데이터 넣기
output = model(image)
# 로스 구하기
loss = criterion(output, label)
# 옵티마이저 기울기 0으로 맞추기
optimizer.zero_grad()
# 역전파 진행
loss.backward()
# 최적화
optimizer.step()
if batch_id % 1000 == 0:
print('Loss :{:.4f} Epoch[{}/{}]'.format(loss.item(), epoch, Train_epoch))
# loss가 가장 적은 모델 저장
if loss.item() < min_Loss:
torch.save(model, '/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/best_model.pt')
min_Loss = loss.item() # min_Loss 갱신
print('Model save!! found minimum loss :', min_Loss)
return model
위에서 찾은 best model과 test데이터 로더를 이용해 모델을 평가해보자.
model = train()
def test(model):
# best model load
model = torch.load('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/best_model.pt')
print('success load best_model')
pred = []
with torch.no_grad():
correct = 0
total = 0
for label, image in Test_dataloader:
outputs = model(image)
predicted = np.array(torch.argmax(outputs, dim=1))
pred.append(predicted)
print(np.array(pred).flatten().shape)
return np.array(pred).flatten()
pred = test(model)
>> success load best_model
>> (10000,)
예측 값과 실제 값을 확인해보자.
# 예측 값
pred[:10]
>> array([0, 1, 2, 6, 3, 6, 8, 2, 5, 0])
# 실제 값
Test_data.label[:10]
>> array([0, 1, 2, 2, 3, 2, 8, 6, 5, 0])
10개 중 7개를 맞게 예측하였다.
# score
correct = pred == Test_data.label
accuracy = correct.sum().item() / len(pred)
print(accuracy)
0.8836
Sunbmission
가장 accuracy가 높은 solution2를 이용해 제출 파일을 만들어보자
submission = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/sample_submission.csv')
submission['label'] = pred
submission.to_csv('/content/drive/MyDrive/Colab Notebooks/21 동계 학부연구생/kaggle-fashion_mnist/submission.csv', index=False)
'ML' 카테고리의 다른 글
[ML] 데이터 증강 (Data Augmentation) (0) | 2022.03.27 |
---|---|
[ML] 교차 검증 (0) | 2022.02.20 |
[ML] 타이타닉 생존자 예측하기 (0) | 2022.01.30 |
[ML] CNN (0) | 2022.01.23 |
[ML] 소프트맥스 회귀 (Softmax Regression) (1) | 2022.01.09 |