본문 바로가기

Machine, Deep Learning/Machine, Deep Learning 실습

Kaggle - MINST 예측 모델 생성 by Keras (2)

반응형
SMALL

본 글은 Kaggle MINST Competition에서의 Introduction to CNN Keras - 0.997 (top 6%)의 커널을 참고하여 작성하였습니다.


  • 1. 소개 (Introduction)
  • 2. 데이터 준비 (Data Preparation)
    • 2.1 데이터 로드 (Data Load)
    • 2.2 널 데이터 확인 (Check for null and missing values)
    • 2.3 데이터베이스 정규화 (Normalization)
    • 2.4 재구조화 (Reshape)
    • 2.5 라벨 인코딩 (Label Encoding)
    • 2.6 훈련과 확인 셋 나누기 (Split training and valdiation set)
  • 3. CNN
    • 3.1 모델 정의 (Define the model)
    • 3.2 최적화 (Set the optimizer and annealer)
    • 3.3 데이터 확보 (Data augmentation)
  • 4. 모델 평가 (Evaluatae the model)
    • 4.1 커브 확인과 훈련 (Training and validation curves)
    • 4.2 컨퓨전 행렬 (Confusion Matrix)
  • 5. 예측과 제출 (Prediction and subition)
    • 5.1 결과 예측 및 제출 (Predict and Submit results)

3. CNN (Convolutional Neural Network)

3.1 모델 정의하기 (Define the Model)

이 글에서는 Keras API를 사용했는데, 입력부터 시작하여 한 번에 한 레이어씩 추가하면 됩니다.

첫번째로는 Convolutional 층(Conv2D) 입니다. 두 개 중 첫 번째 레이어에 있어서 32개의 필터를 설정하고 나머지 레이어에 64 필터를 설정했습니다. 각 필터는 커널 필터를 사용하여 이미지를 변환합니다. 커널필터 행렬은 전체의 이미지에 적용됩니다. 즉, 필터는 이미지의 변환으로 볼 수 있습니다. (이 부분은 설명이 어렵군요 ㅠ)

CNN은 어디에서나 유용한 특징들을 변형된 이미지에서부터 분리할 수 있습니다.

두번째는 CNN에서 중요한 층인 Pooling layer(MaxPool2D)입니다. 이층은 단순히 Downsampling filter의 역할을 합니다. 즉, 2개의 이웃한 픽셀에서 더 큰 values를 선택합니다. 이것은 계산을 줄여주고, 과적합(Overfitting)을 줄여줍니다. 우리는 매번 풀링되는 면적 사이즈를 골라야합니다. 풀링의 차원이 높으면 Downsampling 의 역할이 중요해집니다.

이렇게 컨볼루션 층과 Pooling 층을 결합하는 CNN은 지역적 특징을 결합하고 이미지의 특징을 더 많이 배울 수 있습니다.

드롭아웃(Dropout)은 정규화된 방법으로 노드들을 무작위로 무시하는 방법입니다. 이 방법은 정규화를 향상시키고 과적합(Overfitting)을 줄여줍니다.

'relu'는 활성화 함수로 비선형 네트워크에서 사용되는 방법입니다. (이외에도 시그모이드 함수가 있습니다.) (활성화 함수 외에도 오차 함수, 최적화 함수가 있습니다, 대표적인 오차함수 : 평균 절대 오차, 대표적인 활성화 함수 : 경사 하강법)

플래튼 레이어(Flatten layer)는 최종적인 형상 maps를 1차원 벡터로 변환할 때 사용합니다. 이 Flattening step은 convolutional/maxpool layers를 fully connected layers로 사용하기 위해서 필요로 하는 작업입니다. 즉, 모든 특징들을 결합한다고 생각하시면 됩니다.

마지막으로 두 개의 fully connected layers를 사용했는데 ANN 분류기입니다. 마지막 계층에서 (Dense(1, activation="softmax") 각 클래스의 확률 분포를 보여주는 층입니다.

인자들의 설명을 자세히 알고 싶으시면 https://tykimos.github.io/2017/01/27/CNN_Layer_Talk/ 사이트를 참고하시면 됩니다.

# Set the CNN model
# CNN architechture is In = > [[Conv2D=>relu]*2 => MaxPool2D => Dropout]*2 => Flatten => Dense =>
# Dropout => => Out

model = Sequential()

# filter : 컨볼루션 필터의 수입니다.
# kernel_size : 컨볼루션 커널의 (행, 열) 입니다. => 마스크 틀 크기입니다.
# padding : 경계 처리 방법을 정의합니다.
#			'valid' : 유효한 영역만 출력이 됩니다. 따라서 출력 이미지 사이즈는 입력 사이즈보다 작습니다.
#			'same' : 출력 이미지 사이즈가 입력 이미지 사이즈와 동일합니다.
# input_shape : 샘플 수를 제외한 입력 형태를 정의합니다. 모델에서 첫 레이어일 때만 정의하면 됩니다.
# 				(행, 열, 채널 수)로 정의합니다. 흑백영상인 경우 채널이 1이고, RGB 영상이면 3으로 설정합니다.
# activation : 활성화 함수를 설정합니다.
#			'linear' : 디폴트 값, 입력 뉼ㄴ과 가중치로 계산된 결과값이 그대로 출력으로 나옵니다.
#			'relu' : rectifier 함수, 은닉층에 주로 쓰입니다.
#			'sigmoid' : 시그모이드 함수, 이진 분류 문제에서 출력층에 주로 쓰입니다.
#			'softmax' : 소프트맥스 함수, 다중 클래스 분류 문제에서 출력층에 주로 쓰입니다.
model.add(Conv2D(filter = 32, kernel_size = (5, 5), padding = 'Same', activation = 'relu',
	input_shape = (28, 28, 1)))
model.add(Conv2D(filter = 32, kernel_size = (5, 5), padding = 'Same', activation = 'relu')
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(filter = 64, kernel_size = (3, 3), padding = 'Same', activation = 'relu'))
model.add(Conv2D(filter = 64, kernel_size = (3, 3), padding = 'Same', activation = 'relu'))
model.add(MaxPool2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256, activation = "relu"))
model.add(Dropout(0.5))
model.add(Dense(10, activation = "softmax"))

3.2 최적화 (Set the Optimizer and Annealer)

일단 층들이 model에 추가가 되면, score function, loss function을 set up 하는 것이 필요가 됩니다. Loss function은 얼마나 모델이 잘 수행하는지를 측정해주는 함수입니다. 관측된 labels과 예측된 labels를 비교해서 오류율을 구하는 함수입니다. 우리는 범주형 교차 엔트로피(categorical_crossentropy)라는 함수가 대표적이고 이 글에선 이 방법을 고르겠습니다.

함수의 가장 중요한점은 최적화입니다. 이 함수는 손실을 최소화하기 위해서 매개변수를 반복적으로 계산합니다. (bias와 weights 그리고 filters kernel values 등의 값들을 계산합니다)

저희는 RMSProp(default 값입니다)를 사용할 것입니다. RMSprop는 학습 속도를 줄이기 위해 매우 간단한 방법을 사용합니다. Adagrad 방법이 있지만 이를 보완해서 사용하는 것이 RMSProp입니다.

또한, 확률적 경사 하강법(Stochastic Gradient Descent, SGD)라고 불리는 최적화를 쓸 수 있지만 이는 RMSProp보다 느립니다.

Metiric function의 "accuracy"는 모델의 성능을 평가하는데 사용합니다. Loss Function가 유사합니다. metric evaluation에서 나온 결과는 model을 training 할 때 사용되진 않습니다.

# Define the optimizer
optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)

# Compile the model
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metircs=["accuracy"])

최적화가 좀 더 빠르게 수렴하고 오차 함수의 최소값을 찾기 위해서 학습률을 사용했습니다.

학습률(Learing Rate)는 최적화가 오차 함수 위를 걷는 보폭입니다. 만약, Learning Rate가 높으면 보폭이 커져 좀더 빠르게 수렴이 가능할 것입니다. 하지만 Learning Rate가 크면 저희가 원하는 값을 놓칠 수 있는 가능성이 커집니다.

따라서 감소된 학습률을 가지는 것이 training 중에 효과적으로 오차 함수의 최소값에 도달하는데 도움이 됩니다.높은 학습률로 계산 시간을 빠르게 하고 싶으면, 동적으로 모든 Epochs 마다 LR을 줄일 수도 있습니다. (단, 정확도가 향상되지 않은 경우에만)

Keras.callbacks 에서의 ReduceLROnPlateau function을 사용하여, 정확도가 3 epochs 후에 증가하지 않는다면 LR을 반으로 줄이도록 하겠습니다.

즉, 우선은 계산을 빠르게 하고 만약, 3번의 Epochs 마다 정확도가 향상이 되지 않는다면 학습률을 줄여서 정확한 계산을 요구하겠단 뜻입니다.

# Set a learning rate annealer
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', patience=3, verbose=1, factor=0.5, min_lr=0.00001)

epochs = 2 # Turn epochs to 30 to get 0.9967 accuracy
batch_size = 86

3.3 데이터 확보 (Data augmentation)

과적합(Overfitting) 문제를 피하기 위해선, 필기 데이터 세트를 인위적으로 확장해야합니다. 그러기 위해선 현재 존재하는 Dataset을 더 크게 만들 수 있습니다. 아이디어는 누군가가 숫자를 쓸 때 발생하는 변형을 재현하기 위해 작은 변형으로 훈련 데이터를 변경하는 것입니다. 예를 들어 숫자는 중앙에 작성되지 않고, 그 크기도 사람마다 작고 / 크게 작성했을 것입니다.

Label을 동일하게 유지하면서 배열 표현을 바꾸는 방식으로 학습 데이터를 변경하는 접근 법을 데이터 증가 기법이라고 합니다. 사람들이 사용하는 인기 기법으론 수평 플립, 수직 플립, 회색 음영, 임의 작물, 색상 불안감, 변환, 회전 등입니다.

이 두 가지 변형을 교육 데이터에 적용함으로서 교육 사례의 수를 두 배 세 배로 만들어서 매우 견고한 모델을 만들 수 있습니다.

만약 Data augmentation을 사용하지 않았다면 98.114%의 정확성, Data augmentation을 했을 시 99.67%의 정확성을 이룰 수 있습니다.

결론을 말씀 드리자면 기존에 있는 데이터들을 약간씩 조작해서 또 하나의 학습을 시키는 방법입니다.

# Without data augmentation we obtained an accuracy of 0.98114
# history = model.fit(X_train, Y_train, batch_size = batch_size, epochs = epochs, validation_data = (X_val, Y_val), verbose = 2)
# With data augmentation to prevent overfitting (accuracy 0.99286)

datagen = ImageDataGenerator(featurewise_center=False, # set input mean to 0 over the dataset,
	samplewise_center=False, # set each sample mean to 0
    featurewise_std_normalization=False, # divide inpts by std of the dataset
    samplewise_std_normalization=False, # divide each input by its std
    zca_whitening=False, # apply ZCA whitening
    rotation_range=10, # randomly rotate images in the range (degrees, 0 to 180)
    zoom_range=0.1, # Randomly zoom image
    width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
    height_shift_range=0.1, # randomly shift images vertically (fraction of total height)
    horizontal_flip=False, # randomly flip images
    vertical_flip=False) # randomly flip images
    
datagen.fit(X_train)

Data Augmentation을 위해서, 이 글에서 사용한 것은 아래와 같습니다.

  • 무작위로 이미지들을 10도씩 회전시킵니다.
  • 무작위로 10%씩 이미지를 확대시킵니다.
  • 무작위로 수평으로 10%씩 이미지를 이동합니다.
  • 무작위로 수직으로 10%씩 이미지를 이동합니다.

Vertical_flip과 Horizontal_flip을 사용하지 않았습니다. 왜냐하면 6이랑 9가 비슷하기 때문에 오해를 살 수 있습니다.

우리의 모델이 준비가 됐으니, training dataset를 fit 해봅시다.

# Fit the model
history = model.fit_generator(datagen.flow(X_train, Y_train, batch_size=batch_size),
	epochs = epochs, validation_data = (X_val, Y_val),
    verbose = 2,
    steps_per_epoch=X_train.shape[0] // batch_size,
    callbacks=[learning_rate_reduction])

4. 모델 평가 (Evaluate the model)

4.1 커브 확인과 훈련 (Training and validation curves)

# Plot the loss and accurac curves for training and validation
fig, ax = plt.subplots(2, 1)
ax[0].plot(history.history['loss'], color = 'b', label = "Training loss")
ax[0].plot(history.history['val_loss'], color='r', label = "validation loss", axes = ax[0])
legend = ax[0].legend(loc='best', shadow = True)

ax[1].plot(history.history['acc'], color = 'b', label = "Training accuracy")
ax[1].plot(history.history['val_acc'], color = 'r', label = "Validation accuracy")
legend = ax[1].legend(loc = 'best', shadow = True)

epochs = 2를 했었는데, epochs = 30이면 (2h 30)이 걸리고, 정확도가 훨씬 올라간다. validation accuracy가 training accuracy보다 훨씬 큽니다. (다시 30으로 하고 그래프를 그렸습니다.)

4.2 컨퓨전 행렬 (Confusion matrix)

컨퓨전 행렬은 모델이 잘 작동되는지 확인할 수 있는 좋은 방법이다. validation results의 컨퓨전 행렬을 만들어 볼까요?

# Look at confusion matrix

def plot_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues):
	"""
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting 'normalize=True'.
    """
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks=np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)
    
    if normalize:
    	cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
        
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
    	plt.text(j, i, cm[i, j], horizontalalignment="center", color="white" if cm[i, j] > thresh else "black")
    
    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
    
# Predict the values from the validation dataset
Y_pred = model.predict(X_val)
# Convert predictions classes to one hot vectors
Y_pred_classes = np.argmax(Y_pred, axis = 1)
# Convert validation observations to one hot vectors
Y_tru = np.argmax(Y_val, axis = 1)
# compute the confusion matrix
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes)
# plot the confusion matrix
plot_confusion_matrix(confusion_mtx, classes = range(10))

저희는 CNN performs이 매우 잘 되었다는 것을 볼 수 있습니다. 단 몇 개의 오류도 볼 수 있었습니다.

참고로 우리의 코드는 4와 9를 휘갈겨 썼을 때 구분하기 되게 어려워합니다. 그렇다면 에러를 조사해보도록 하겠습니다.

가장 중요한 에러가 무엇인지 보도록 해보겠습니다. 그러기 위해선 실제 value의 확률과 예상하는 확률 사이의 차이를 구할 필요가 있습니다.

# Display some error results

# Errors are difference between predicted labels and true labels
errors = (Y_pred_classes - Y_true != 0)

Y_pred_classes_errors = Y_pred_classes[errors]
Y_pred_errors = Y_pred[errors]
Y_true_errors = Y_true[errors]
X_val_errors = X_val[errors]

def display_errors(errors_index, img_errors, pred_errors, obs_errors):
	""" This function shows 6 images with their predicted and real labels"""
    n = 0
    nrows = 2
    ncols = 3
    fig, ax = plt.subplots(nrows, ncols, sharex=True, sharey=True)
    for row in range(nrows):
    	for col in range(ncols):
        	error = errors_index[n]
            ax[row, col].imshow((img_errors[error]).reshape((28, 28)))
            ax[row, col].set_title("Predicted label : {}\nTrue label : {}".format(pred_errors[error], obs_errors[error]))
            n += 1
    
# Probabilities of the wrong predicted numbers
Y_pred_errors_prob = np.max(Y_pred_errors, axis = 1)

# Predicted probabilities of the true values in the error set
true_prob_errors = np.diagonal(np.take(Y_pred_errors, Y_true_errors, axis=1))

# Difference between the probability of the predicted label and the true label
delta_pred_true_errors = Y_pred_errors_prob - true_prob_errors

# Sorted list of the delta prob errors
sorted_dela_errors = np.argsort(delta_pred_true_errors)

# Top 6 errors
most_important_errors = sorted_dela_errors[-6:]

# Show the top 6 errors
display_errors(most_important_errors, X_val_errors, Y_pred_classes_errors, Y_true_errors)

보다시피 날림체로 쓴 글들이 전부 에러가 떴습니다. 4를 6으로 보이게 작성한 것을 볼 수 있고 9를 0으로 보이게 작성한 것을 확인할 수 있습니다.

# Predict results
results = model.predict(test)

# select the index with the maximum probability
results = np.argmax(results, axis = 1)

results = pd.Series(results, name="Label")
submission = pd.concat([pd.Series(range(1, 28001), name = "ImageId"), results], axis = 1)

submission.to_csv("cnn_mnist_datagen.csv", index = False)

여기까지 하신다면 제출 csv 파일이 완성되었습니다 !!


이상 Kaggle - MINST 예측 모델 생성 by Keras (2) 였습니다 ^_^

반응형
LIST