Data_study/DATA_PROJECT

[Project] kaggle_compitition : Digit Recognizer

onsemiro 2022. 7. 10. 17:11

Digit Recognizer

Learn computer vision fundamentals with the famous MNIST data

 

Goal

: 이 대회의 목표는 손으로 쓴 한 자리 숫자의 이미지를 가져와 그 숫자가 무엇인지 확인하는 것입니다.

테스트 세트의 모든 항목에 대해 올바른 레이블을 예측합니다.

Metric

: 이 경쟁은 예측의 분류 정확도(정확한 이미지의 비율)로 평가됩니다.

 

마지막 제출파일 : sample.submission.csv

평가용 픽셀 데이터-> 구축한 모델에 구동 -> 나온 예측값 -> label열에 넣어 제출

Label을 가장 잘 예측한 모델이 승자인 대회.

 

1 . Import Libraries & Load Data

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import sys
import os

from skimage.transform import rotate # data_augmentation을 하기 위해 사이킷런이미지의 transform에서 rotate를 불러왔다.
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report
data_train = pd.read_csv('data_story/BigdataAnalysis/digit-recognizer/train.csv')
data_test = pd.read_csv('data_story/BigdataAnalysis/digit-recognizer/test.csv')

labels = data_train["label"] #dataset에서 label 열만 추출하여 labels 객체로 할당시킴
labels = labels.astype('int') 

features = data_train.drop("label", axis = 1) #실제로 train할

> train.csv파일 불러와 라벨 열만 추출하여 라벨 객체로 할당

> 제거한 데이터를 features객체로 할당(학습할 데이터)

> 실제 라벨값은 labels객체로 따로 할당.(학습 데이터의 정답)

 

 

2 . Data Check

 

#각 레이블 별 데이터 개수 확인 -> 각 레이블마다 데이터가 몇개로 구성되어 있는지. (불균형한 데이터인지 확인한 과정)
sns.countplot(y='label',data=data_train,order= list(data_train.label.value_counts().index).sort())

각 label별 데이터 분포 확인

>>> 한쪽 label에 치우쳐져 있지 않고 어느정도 고르게 분포되어 있음을 확인하여, 추후 dataset을 나눌때 무작위로 나눠도 무관하겠구나 판단함.

 

print(features.isna().sum().sum())
print(data_test.isna().sum().sum())

>>>print 결과 features 데이터셋과 data_test 셋에 결측치 값은 존재하지 않음을 확인함. (출력값 각각 0)

 

 

3. Exploration 

--임의의 몇개 image 데이터를 확인해보자.--

 

get_image_matrix 함수 생성

이유 : 784개 픽셀 데이터를 그리기 위해서는 (28,28) 크기로 재형성해줘야 하기 때문

 

def get_image_matrix(row, dataset):
    row_values = dataset.iloc[row].values
    image_matrix = row_values.reshape(28, 28)
    return image_matrix

fig, ax = plt.subplots(1, 5, figsize=(5, 1), dpi=200)
plt.setp(ax, xticks=[], yticks=[])

ax_num=0
for i in [1,42,783,27000,62]:
    ax[ax_num].imshow(get_image_matrix(i, features))
    ax_num+=1

-> 검은 배경에 중간에 숫자가 배치되어 있음을 확인.

 

4. Data Augmentation 

기존데이터는 0-255까지 명도를 수치로 표현한 행렬데이터다.

data augmentation에서 회전한 데이터의 value들을 확인해보면, 실수형의 소숫값으로 형성되어있기 때문에 기존데이터도 1도만 변화해주는 과정을 진행하여 동일한 형태의 value를 구성하도록 하였다.

data_test.iloc[0].values

기존 데이터 구성

data_aug_1.iloc[0].values

1도 회전시킨 데이터 value값 형태

 

--- rotate 함수로 회전시킨후, image_aug에 변환된 값들을 넣어주고 dataframe형태로 변환시켜 data_test로 할당한다. ---

image_aug = []
for x in range(len(data_test)):
    output_s = rotate(get_image_matrix(x,data_test),1, resize=False,cval=0)
    image_aug.append(output_s.reshape(784)) #(28,28)의 형태를 (1,784)로 reshape진행
    plt.imshow(output_s)
    
#변환된 값들이 들어가있는 image_aug를 dataframe으로 만들어준다.
data_test = pd.DataFrame(image_aug)
data_test.columns = features.columns
data_test

data_test

 

<기존 학습데이터에도 동일하게 진행>

-추가되는 부분은 학습용 데이터이기 때문에 앞에 label열을 insert함수로 추가한다.

image_aug = []
for x in range(len(features)):
    output_s = rotate(get_image_matrix(x,features),1, resize=False,cval=0)
    image_aug.append(output_s.reshape(784))
    plt.imshow(output_s)
    
data_aug_1 = pd.DataFrame(image_aug)
data_aug_1.columns = features.columns
data_aug_1.insert(0,"label",labels,True)
data_aug_1

# data_aug_1.to_csv('data_story/BigdataAnalysis/digit-recognizer/data_aug_1.csv', index=False)
#회전시킨 데이터를 추후 분석할 때 활용하기 위해 따로 저장해둔다.

data_aug_1

 

<증강 학습데이터에도 동일하게 진행>

-기존데이터는 1도를 회전시켰고 증강데이터에는 45도를 회전시킨다. rotate에서 1을 45로만 변경하여 동일하게 진행한다.

image_aug_45 = []
for x in range(len(features)):
    output_s_45 = rotate(get_image_matrix(x,features),45, resize=False,cval=0)
    image_aug_45.append(output_s_45.reshape(784))
    plt.imshow(output_s_45)
    
data_aug_45 = pd.DataFrame(image_aug_45)
data_aug_45.columns = features.columns
data_aug_45.insert(0,"label",labels,True)
data_aug_45

# data_aug_45.to_csv('data_story/BigdataAnalysis/digit-recognizer/data_aug_45.csv', index=False)
#증강시킨 데이터셋을 추후 분석할때 활용하기 위해 따로 저장해둔다.

data_aug_45

 

<1도 회전시킨 기존데이터와 45도 회전시킨 증강데이터를 합친다.>

-pandas의 concat함수를 통해 데이터 결합 진행.

data_aug_1= pd.read_csv('data_story/BigdataAnalysis/digit-recognizer/data_aug_1.csv')
data_aug_45= pd.read_csv('data_story/BigdataAnalysis/digit-recognizer/data_aug_45.csv')
data_aug = pd.concat([data_aug_1,data_aug_45])
data_aug.index = [id_ for id_ in range(84000)]
data_aug

 

--- 학습을 하기전에 label은 따로 빼두는 과정을 진행해야하므로, 아래 코드 실행---

labels_aug = data_aug["label"]
labels_aug = labels_aug.astype('int')

features_aug = data_aug.drop("label", axis = 1)

 

---label과 pexel 값들이 잘 배치되어있는지 확인해봄.---

def get_image_matrix(row, dataset):
    row_values = dataset.iloc[row].values
    image_matrix = row_values.reshape(28, 28)
    return image_matrix
print(labels_aug[81999])
plt.imshow(get_image_matrix(81999,features_aug))

다양한 무작위 행 index를 넣어보며 진행

 

  5. Principal Component Analysis  (PCA 주성분 분석)

 

: 학습용 데이터와 평가용 데이터에  PCA를 진행하여 784개였던 피처수를 60개로 줄여 과대적합을 예방해보자.

n_comp = 60
pca = PCA(n_components=n_comp) #,whiten=True)
pca_train = pca.fit_transform(features_aug)
pca_test = pca.transform(data_test)

result_train = pd.DataFrame(pca_train, columns=['PCA_%i' % i for i in range(n_comp)])
result_test = pd.DataFrame(pca_test, columns =['PCA_%i' % i for i in range(n_comp)])

PCA 주성분 분석 진행 60개의 열로 축소

 

-어떤 주성분들로 구성됐는지 실제 이미지로 확인해보자.

(60개를 모두 보려고 하니 너무 많아 잘보이지 않아서 주성분중 앞에 15개만 확인)

fig , axes = plt.subplots(3,5,figsize=(20,12))
for i, (component,ax) in enumerate(zip(pca.components_,axes.ravel())):
    ax.imshow(component.reshape(28,28),cmap = 'viridis')
    ax.set_title("pca {}".format(i+1))

PCA 15개 주성분 이미지

 

 6. Model (Cross Validation)

 

: make_pipeline을 활용해서 최대한 test셋을 건들지 않고 분석을 진행하였다.

 

x_train,x_test,y_train,y_test = train_test_split(result_train,labels_aug,test_size=0.3,random_state=42)

 

<SVM (Support Vector Machine)>

from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline

pipe_model = make_pipeline(StandardScaler(),SVC(random_state=42))
svm_cross = cross_val_score(pipe_model, x_train, y_train, cv=3, scoring="accuracy")
print(svm_cross)

아래는 출력값.

[0.96892857 0.96928571 0.97      ]

 

<RandomForestClassifier>

from sklearn.ensemble import RandomForestClassifier

pipe_model = make_pipeline(StandardScaler(),RandomForestClassifier(random_state=42))
forest_cross = cross_val_score(pipe_model,x_train,y_train,cv=3,scoring='accuracy')
print(forest_cross)

아래는 출력값.

[0.93       0.93234694 0.93193878]

 

<KNeighborsClassifier>

from sklearn.neighbors import KNeighborsClassifier

pipe_model = make_pipeline(StandardScaler(),KNeighborsClassifier(n_neighbors=5, weights='distance'))
knn_acc = cross_val_score(pipe_model, x_train, y_train, cv=3, scoring="accuracy")
print(knn_acc)

아래는 출력값.

[0.94561224 0.94566327 0.94760204]

 

<GaussianNB>

from sklearn.naive_bayes import GaussianNB

pipe_model = make_pipeline(StandardScaler(),GaussianNB())
nb_acc = cross_val_score(pipe_model, x_train, y_train, cv=3, scoring="accuracy")
print(nb_acc)

아래는 출력값.(아래 교차검증 결과에서 볼 수 있듯이 나이브 베이즈 분류는 확실히 일반화 성능이 낮다.)

[0.80887755 0.81244898 0.81020408]

 

<GradientBoostingClassifier>

from sklearn.ensemble import GradientBoostingClassifier

pipe_model = make_pipeline(StandardScaler(),GradientBoostingClassifier(random_state=0))
gbrt_acc = cross_val_score(pipe_model, x_train, y_train, cv=3, scoring="accuracy")

print(gbrt_acc)

아래는 출력값.

[0.88397959 0.88739796 0.88719388]

 

<MLPClassifier>

from sklearn.neural_network import MLPClassifier

pipe_model = make_pipeline(StandardScaler(),MLPClassifier(random_state=42))
mlp_acc = cross_val_score(pipe_model, x_train, y_train, cv=3, scoring="accuracy")

print(mlp_acc)

아래는 출력값.

[0.9525     0.9527551  0.95392857]

 

--> 나이브 베이즈에서 가우디안과 그래디언트 부스터, MLP를 써보았지만 SVM보단 낮은 성능을 보임.

--> 많은 모델중에서 SVM이 성능이 가장 좋았음!

(이유)

피처가 많아도 복잡한 결정 경계를 만들어낼 수 있고 다양한 데이터 세트에서 잘 적용하기 때문에 이번 데이터에서 가장 좋은 성능을 볼 수 있었던 것으로 생각됨.

 

 7. Hyperparameter Tuning

위 다양한 알고리즘중 서포터 백터머신(SVM)이 가장 높은 수치를 보여줬다.

SVM에 그리드 서치를 진행하여 가장 적합한 파라미터를 찾아보자.

 

<gridsearch 진행>

: 가장 높은 평가치를 보여준 SVM을 활용하여 gridsearch를 진행한다.

-> 가장 적합한 파라미터를 확인하고 정도율과 재현율, f1 score값을 확인하자.

 

-결과가 보기 좋게 출력되기 위해 따로 함수를 먼저 생성함.

def print_grid_results(grid):
    # print best parameter after tuning
    print('*'*75)
    print(grid.best_params_)
    print('*'*75)

    # print classification report
    print(classification_report(y_test, grid_p_predictions))

 

pipe_model = make_pipeline(StandardScaler(),SVC())
x_train_p,x_test_p,y_train_p,y_test_p = train_test_split(result_train, labels_aug, test_size=0.3, random_state=42)

param_grid = {'svc__C':[0.1, 1, 10],
               'svc__gamma':['scale'], 
               'svc__kernel':['rbf'],
               'svc__random_state':[42]
               }
grid_p = GridSearchCV(pipe_model, param_grid, refit=True, verbose=0)

grid_p.fit(x_train_p, y_train_p)

grid_p_predictions = grid_p.predict(x_test_p)

print_grid_results_p(grid_p)

 svc_gamma : 어차피 위에서 standardscaler를 진행하였기 때문에 auto와 scale의 기능은 동일화된다.

                        auto는 1/features개수 & scale은 1/(features개수 * std값)

svc_kernel : 가우디안과 poly, sigmoid, linear 가 있는데 가우디안으로 진행. 따로 sigmoid와 linear도 실행해봤지만 rdf가 가장 성능                    

                     이 높았음.

***************************************************************************
{'svc__C': 10, 'svc__gamma': 'scale', 'svc__kernel': 'rbf', 'svc__random_state': 42}
***************************************************************************
              precision    recall  f1-score   support

           0       0.99      0.99      0.99      2466
           1       0.99      0.99      0.99      2855
           2       0.96      0.98      0.97      2463
           3       0.98      0.97      0.97      2608
           4       0.97      0.98      0.98      2489
           5       0.98      0.98      0.98      2269
           6       0.98      0.99      0.99      2466
           7       0.97      0.97      0.97      2604
           8       0.98      0.97      0.97      2471
           9       0.97      0.96      0.97      2509

    accuracy                           0.98     25200
   macro avg       0.98      0.98      0.98     25200
weighted avg       0.98      0.98      0.98     25200

정도율 : 평균 98%          재현율 : 평균 98%          f1 score : 평균 98%          정확도 : 98%

 

 8. Prediction

- sample submission에 test.csv 데이터를 예측한 값을 label에 넣어보자.

 

가장 높은 성능을 보여준 파라미터를 입력하여 모델 학습 진행. -> 평가 지표(정확도)도 확인.

svm = SVC(C=10, gamma='scale', kernel='rbf', random_state=42)

svm.fit(x_train, y_train)
accuracy_train = round(svm.score(x_train, y_train) * 100, 2)
accuracy_test = round(svm.score(x_test, y_test) * 100, 2)

print("Train Accuracy: % {}".format(accuracy_train))
print("Testing Accuracy: % {}".format(accuracy_test))

아래는 출력값.

Train Accuracy: % 99.94
Testing Accuracy: % 98.03

 

 

가장 높았던 성능의 모델에 주성분 분석까지만 진행된 평가용 파일 데이터를 넣어 예측한 값을 predictions 객체로 할당.

 

sample submission파일을 불러와 label 컬럼에 predictions를 넣음.

 

그후, 따로 서브미션2 파일로 저장.

predictions = grid_p.predict(result_test) #test 데이터 예측 진행

data_submission = pd.read_csv("data_story/BigdataAnalysis/digit-recognizer/sample_submission.csv")
data_submission['Label'] = predictions
data_submission.to_csv('data_story/BigdataAnalysis/digit-recognizer/submission2.csv', index=False)

 

[] : predictions

[] : array([2, 0, 9, ..., 3, 9, 2])

predictions 객체를 확인해보면 각 예측한 숫자들이 입력되어있음.

 

-실제로 같은 인덱스에 있는 이미지와 predictions value를 가져와서 확인해보았다.

(i를 바꿔가면서 확인해봤는데 대부분 일치했음)

i = 180
print(predictions[i])
plt.imshow(get_image_matrix(i,data_test))

predictions & get_image_matrix(i,data_test)

  8 - Apply goodnote image 

직접 굿노트에 쓴 숫자 이미지를 모델에 넣어 예측을 잘하는지 확인해보았다.

 

goodnote 이미지 하나하나를 불러올 때는 PCA를 진행할 수 없기 때문에, pca를 진행하지 않고 모델을 구축하여 예측해야한다.

 

data_train_sa = scaler.fit_transform(features_aug)
x_train_ps,x_test_ps,y_train_ps,y_test_ps = train_test_split(data_train_sa, labels_aug, test_size=0.3, random_state=42)
svm_sample = SVC(C=10, gamma='scale', kernel='rbf', random_state=42)
svm_sample.fit(x_train_ps, y_train_ps)

accuracy_s_train = round(svm_sample.score(x_train_ps, y_train_ps) * 100, 2)
accuracy_s_test = round(svm_sample.score(x_test_ps, y_test_ps) * 100, 2)

print("Train Accuracy: % {}".format(accuracy_s_train))
print("Testing Accuracy: % {}".format(accuracy_s_test))

아래는 출력값.

Train Accuracy: % 99.9
Testing Accuracy: % 96.69

이 결과를 보면 알 수 있듯이, PCA를 하였을때, 더 일반화에 강하다는 것을 확인할 수 있다. pca를 진행한 모델의 testing accuracy는 98%였기 때문이다.

 

 

-goodnote의 손글씨 이미지를 가지고 오기위해 opencv를 활용하였고,

현재 모델을 학습시킨 이미지들이 다 검은 화면에 하얀 글씨로 숫자가 그려져 있기 때문에,

cv2.COLOR_BGR2GRAY를 통해 흑백으로 이미지를 변환시켰다.

 

-같은 값의 형태를 만들기 위해서 rotate 1도를 진행해주었다.

 

-모델이 학습한 데이터 크기가 (28,28)이었기 때문에 goodnote 이미지도 cv2.resize를 활용해서 변형해주었다.

import cv2
import matplotlib.pyplot as plt
src = cv2.imread("Downloads/3.png",cv2.IMREAD_COLOR)
dst = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
image = cv2.resize(dst, (28, 28),interpolation=cv2.INTER_LINEAR)
image = 255 - image
data_image = scaler.fit_transform(image)
a_r = rotate(data_image,1, resize=False)

a_r_df = pd.DataFrame(a_r.reshape(1,784), columns = ['pixel'+str(i) for i in range(784)])

불러온 goodnote 이미지를 예측한 결과와 이미지를 출력하도록 했다.

predictions_grid = grid_ps.predict(a_r_df)
predictions_svm = svm_sample.predict(a_r_df)

print(predictions_grid)
print(predictions_svm)

goodnote에서 그린 이미지 저장
이미지를 불러와 예측하고 출력함.

 

 

 

마무리

kaggle 참가자들의 코드를 참고하면서 내가 이부분(data augmentation & pipeline)을 보완해서 더 높은 성능의 모델을 만들어봐야겠다는 호기심으로 진행했는데, 예상한 결과에 맞게 매우 높은 성능지표는 아니지만 보완된 지표를 얻을 수 있어서 큰 성취감을 느낄 수 있었다. 컴퓨터 비전의 기초 MNIST 데이터를 활용해보면서 추후에 이 경험을 바탕으로 다양한 분야에서 활용해봐야겠다는 생각하였다. 성과지표를얻는 것으로 끝나지 않고, 직접 goodnote에 손글씨를 써서 구축한 모델이 잘 예측하나 확인해보는 과정까지 진행하여 만족스러웠고 또 잘 예측하여 뿌듯했다. 이미지 예측에서 사용하는 개념들(평가지표들의 의미, opencv 등)을 알아보는 경험을 할 수 있었다. 확실히 인공지능, 데이터사이언스 분야는 배우는 것보다는 실습으로 학습하면서 공부하는 것이 훨씬 얻는 게 많구나를 느꼈다.

 

반응형