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에 치우쳐져 있지 않고 어느정도 고르게 분포되어 있음을 확인하여, 추후 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
--- 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
<기존 학습데이터에도 동일하게 진행>
-추가되는 부분은 학습용 데이터이기 때문에 앞에 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)
#회전시킨 데이터를 추후 분석할 때 활용하기 위해 따로 저장해둔다.
<증강 학습데이터에도 동일하게 진행>
-기존데이터는 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)
#증강시킨 데이터셋을 추후 분석할때 활용하기 위해 따로 저장해둔다.
<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))
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)])
-어떤 주성분들로 구성됐는지 실제 이미지로 확인해보자.
(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))
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))
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)
마무리
kaggle 참가자들의 코드를 참고하면서 내가 이부분(data augmentation & pipeline)을 보완해서 더 높은 성능의 모델을 만들어봐야겠다는 호기심으로 진행했는데, 예상한 결과에 맞게 매우 높은 성능지표는 아니지만 보완된 지표를 얻을 수 있어서 큰 성취감을 느낄 수 있었다. 컴퓨터 비전의 기초 MNIST 데이터를 활용해보면서 추후에 이 경험을 바탕으로 다양한 분야에서 활용해봐야겠다는 생각하였다. 성과지표를얻는 것으로 끝나지 않고, 직접 goodnote에 손글씨를 써서 구축한 모델이 잘 예측하나 확인해보는 과정까지 진행하여 만족스러웠고 또 잘 예측하여 뿌듯했다. 이미지 예측에서 사용하는 개념들(평가지표들의 의미, opencv 등)을 알아보는 경험을 할 수 있었다. 확실히 인공지능, 데이터사이언스 분야는 배우는 것보다는 실습으로 학습하면서 공부하는 것이 훨씬 얻는 게 많구나를 느꼈다.
'Data_study > DATA_PROJECT' 카테고리의 다른 글
[Project] 휠체어 목발 이용자 인식 yolo v5 - 2(모델,결과) (0) | 2022.10.04 |
---|---|
[Project] 휠체어 목발 이용자 인식 yolo v5 - 1(수집,증강) (0) | 2022.08.20 |
[Project] 사용자가 원하는 노트북 스펙 비교 분석 (0) | 2022.06.25 |