Kaggle-KR

Statoil/C-CORE 튜토리얼 - Image recognition, binary classfication 본문

Kaggle 한글 커널 with Python/개인 커널

Statoil/C-CORE 튜토리얼 - Image recognition, binary classfication

알 수 없는 사용자 2018. 7. 1. 23:20
tutorial
  • 안녕하세요!. 이번에는 빙하와 배를 구분하는 문제를 풀어보려고 합니다. 캐나다 동쪽 해안에서는 표류하는 빙하가 자칫 위험이 될 수 있다고 합니다. 그래서 많은 연구기관, 기업들이 이 위험을 피할 방방법을 찾기 위해 노력하고 있다고 하네요. 위성사진을 이용해 지속적으로 모니터링을 한다고 합니다. Statoil 이라는 국제 에너지 기업과 C-CORE 라는 위성 기업이 이 컴퍼티션을 주최하였는데, 이들이 제공한 위성사진을 가지고 배(ship)와 빙하(iceberg)를 잘 구분해내는 머신러닝, 딥러닝 모델을 세우는 게 목표입니다.
  • 이번 컴퍼티션은 image classification 이며, binary classfication 입니다. 그래서 본 튜토리얼은 이 컴퍼티션을 공부하며 keras를 사용한 2-D convolutional neural network(CNN) 을 배울 것입니다. 그리고 plotly 도 만나보실 겁니다.
  • plotly 는 interactive 한 visualization 을 가능케하는 라이브러리입니다. plotly 사이트에 가입하면, plotly 에서 관리하는 클라우드에 자신이 그린 이미지(graph, plot)을 관리할 수 있습니다.
  • keras 는 유명한 딥러닝 프레임워크로, 딥러닝 모델을 쉽고 빠르게 구축하게 합니다. 그럼 시작하겠습니다.
In [1]:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt

#plt.rcParams['figure.figsize'] = 12, 8
%matplotlib inline

#Take a look at a iceberg
import plotly.offline as py
import plotly.graph_objs as go
from plotly import tools

py.init_notebook_mode(connected=True) # plotly 를 jupyter notebook 에 사용하려면 이 커맨드를 입력해야 합니다.
  • 본 컴퍼티션의 데이터는 json 형태로 주어져 있습니다. pandas 는 json type 의 데이터도 쉽게 읽어낼 수 있습니다.
In [2]:
#Load data
train = pd.read_json("../input/data/processed/train.json")
test = pd.read_json("../input/data/processed/test.json")
train.inc_angle = train.inc_angle.replace('na', 0)
train.inc_angle = train.inc_angle.astype(float).fillna(0.0)
print("done!")
done!
In [3]:
train.head()
Out[3]:
band_1 band_2 id inc_angle is_iceberg
0 [-27.878360999999998, -27.15416, -28.668615, -... [-27.154118, -29.537888, -31.0306, -32.190483,... dfd5f913 43.9239 0
1 [-12.242375, -14.920304999999999, -14.920363, ... [-31.506321, -27.984554, -26.645678, -23.76760... e25388fd 38.1562 0
2 [-24.603676, -24.603714, -24.871029, -23.15277... [-24.870956, -24.092632, -20.653963, -19.41104... 58b2aaa0 45.2859 1
3 [-22.454607, -23.082819, -23.998013, -23.99805... [-27.889421, -27.519794, -27.165262, -29.10350... 4cfc3a18 43.8306 0
4 [-26.006956, -23.164886, -23.164886, -26.89116... [-27.206915, -30.259186, -30.259186, -23.16495... 271f93f4 35.6256 0
  • 보시게 되면, id가 있고, feature 로는 band_1, band_2, inc_angle 이 있습니다. 우리가 맞추려는 target 은 is_iceberg 이며 1 == iceberg, 0 == ship 입니다.
  • https://www.kaggle.com/c/statoil-iceberg-classifier-challenge#Background 에 보시면 대략적인 데이터 설명이 있습니다. 위성사진은 위성에서 쏜 radar 가 특정 object 를 만나면 bounce 하여 다시 돌아오게 되는 데 이를 image로 저장한 것이라고 합니다.
  • 이 때, object 가 solid 할수록(land, islands, sea ice, icebergs, ships) 더 강한 rader energy 가 reflection 하므로 실제 이미지에서 밝게 나온다고 하네요. 이 이너지를 backscatter 라고 합니다.
  • 이 backscatter 는 주변 환경에 영향을 받는데, 주변에 바람이 강할수록 이미지가 밝아지고, 바람이 약할수록 이미지가 어두워진다고 하는 군요. 아마 강한 바람에 담긴 여러 분자들의 운동에너지가 reflection 되는 radar 에 담기나 봅니다.
  • 지금 주어진 이미지를 찍는 위성은 Sentinel-1 인데 측방 감시 레이더라고 합니다. 즉 특정 angle 로 이미지를 보는 것이죠. 주어진 inc_angle 은 band_1, band_2(이미지) 를 바라보는 angle을 의미합니다. 그러니까 band_1, band_2 는 상대적인 이미지라고 볼 수 있겠네요.
  • 일반적으로 높은 incidence angle 일수록, ocean background 가 어두워진다고 합니다.
  • band_1 과 band_2 는 HH(transmit/receive horizontally)와 HV(transmit horizontally and receive vertically) 로 얻어진 radar 데이터를 정제하여 얻은 coefficient 들이며, 이미지로 생각하시면 됩니다.
  • 당연한 이야기지만, 어떤 컴퍼티션이든 domain knowledge 가 필요하긴 합니다. 아래 커널에 보시면, 관련 정보가 모여있으니 한번 확인해보셔도 좋을 듯 합니다. https://www.kaggle.com/dimitrif/domain-knowledge
  • 캐글을 하게 되면, domain knowlegde가 있으면 도움이 되는 경우가 많습니다. 하지만 없다고 해서 못하는 것이 아닙니다. domain knowledge를 잘 몰라도 데이터 속에서 domain knowledge 를 찾아내는 것도 재미니까요!.
  • 이제 band_1과 band_2를 75x75 로 바꾸고, 2D CNN 학습을 위하여 [N, height, width, channel]의 차원을 가지게 바꿔줍니다. np.newaxis 를 사용하면 쉽게 할 수 있습니다.
In [4]:
#Generate the training data
#Create 3 bands having HH, HV and avg of both
X_band_1=np.array([np.array(band).astype(np.float32).reshape(75, 75) for band in train["band_1"]])
X_band_2=np.array([np.array(band).astype(np.float32).reshape(75, 75) for band in train["band_2"]])
X_train = np.concatenate([X_band_1[:, :, :, np.newaxis], X_band_2[:, :, :, np.newaxis],((X_band_1+X_band_2)/2)[:, :, :, np.newaxis]], axis=-1)

X_band_test_1=np.array([np.array(band).astype(np.float32).reshape(75, 75) for band in test["band_1"]])
X_band_test_2=np.array([np.array(band).astype(np.float32).reshape(75, 75) for band in test["band_2"]])
X_test = np.concatenate([X_band_test_1[:, :, :, np.newaxis]
                          , X_band_test_2[:, :, :, np.newaxis]
                         , ((X_band_test_1+X_band_test_2)/2)[:, :, :, np.newaxis]], axis=-1)
  • 이제 한번 ship, iceberg 가 가지는 band_1, 2가 어떻게 다른지 확인해봅시다.
  • plotly 의 surface plot 을 활용하면, 멋진 3-D plot 을 얻을 수 있으며, 마우스로 이리저리 움직여볼 수 있습니다.
  • plotly 경우 다른 튜토리얼에서 한번 다뤄보도록 하겠습니다. 처음에는 사용법이 어려워보이지만, documentation 및 example 이 잘 되어 있어서 반복해 따라하다보면 익숙해집니다. https://plot.ly/api/ 에 들어가시면 python, matlab, R, javascript 로 plotly를 쓰는 방법이 잘 나와 있습니다.
  • 그리고 동일한 band_1 과 band_2 를 2D 로도 그려보겠습니다. 이것은 matplotlib 의 imshow 로 쉽게 그릴 수 있습니다.
  • Python visualization 은 matplotlib, seaborn, plotly 가 대표적인데, 셋다 다룰 줄 아시면 이쁘고 멋진 데이터 분석 보고서를 작성하실 수 있습니다.
In [17]:
def plot_contour_2d(band1, band2, label):
    fig = tools.make_subplots(rows=1, cols=2,  specs=[[{'is_3d': True}, {'is_3d': True}]])
    fig.append_trace(dict(type='surface', z=band1, colorscale='RdBu',
                          scene='scene1', showscale=False), 1, 1)
    fig.append_trace(dict(type='surface', z=band2, colorscale='RdBu',
                          scene='scene2', showscale=False), 1, 2)


    fig['layout'].update(title='3D surface plot for "{}" (left is from band1, right is from band2)'.format(label), titlefont=dict(size=30), height=800, width=1200)

    py.iplot(fig)

    fig, ax = plt.subplots(1, 2, figsize=(16, 10))
    ax[0].imshow(X_band_1[num, :, :])
    ax[0].set_title('Image from band_1', fontsize=15)
    ax[1].imshow(X_band_2[num, :, :])
    ax[1].set_title('Image from band_2', fontsize=15)
    plt.show()
  • 아래 두 band_1, band_2 는 ship 을 나타냅니다.
  • 현재 이미지는 75x75 인데, 그래프는 x, y 축으로 75 x 75 grid 가 그려지고, 각 점의 z value 가 band_1 또는 band_2 가 됩니다.
  • 빨간색 봉우리가 ship 의 모양을 나타내며, HH, HV 에 따라 radar 를 보는 방향이 달라 조금 다르게 나오지만, 결국 같은 것을 나타냄을 알 수 있습니다.
  • 제법 깔끔한 이미지가 나옵니다.
In [19]:
num = 0
label = 'iceberg' if (train['is_iceberg'].values[num] == 1) else'ship'
plot_contour_2d(X_band_1[num,:,:], X_band_2[num,:,:], label)
This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) scene2 ]

In [7]:
num = 100
label = 'iceberg' if (train['is_iceberg'].values[num] == 1) else'ship'
plot_contour_2d(X_band_1[num,:,:], X_band_2[num,:,:], label)
This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) scene2 ]

  • 아래 두 set 은 iceberg 를 나타냅니다.
  • 붉은 봉우리 이외에도 여러 뾰족한 봉우리들이 보입니다. iceberg 옆에 떠다니는 자그마한 iceberg 일 수도 있겠네요.
In [8]:
num = 2
label = 'iceberg' if (train['is_iceberg'].values[num] == 1) else'ship'
plot_contour_2d(X_band_1[num,:,:], X_band_2[num,:,:], label)
This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) scene2 ]

In [9]:
num = 125
label = 'iceberg' if (train['is_iceberg'].values[num] == 1) else'ship'
plot_contour_2d(X_band_1[num,:,:], X_band_2[num,:,:], label)
This is the format of your plot grid:
[ (1,1) scene1 ]  [ (1,2) scene2 ]

  • 자 이제, 본격적으로 딥러닝을 해봅시다. 데이터셋이 준비되어 있으니, 신경망 모델을 설계하면 됩니다.
  • 이미지니까 2D CNN 을 가지고 해보겠습니다. keras 에는 딥러닝에 사용되는 여러 layer 들이 내장되어 있어서 우리는 그저 블록처럼 쌓기만 하면 됩니다.
  • 김태영의 케라스 블로그 https://tykimos.github.io/ 에 가면 케라스에 대해 배워볼 수 있으며, 케라스 본 documentation 도 자세한 설명 및 예제가 많으므로 공부하기 좋습니다. 구글링하시면 많은 깃허브들도 있으니 마음껏 즐기세요!
In [10]:
#Import Keras.
from matplotlib import pyplot
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dense, Dropout, Input, Flatten, Activation
from keras.layers import GlobalMaxPooling2D
from keras.layers.normalization import BatchNormalization
from keras.layers.merge import Concatenate
from keras.models import Model
from keras import initializers
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, Callback, EarlyStopping
/usr/local/lib/python3.5/dist-packages/h5py/__init__.py:36: FutureWarning:

Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.

Using TensorFlow backend.
  • 본격적으로 모델을 만듭니다. 한번 훑어보세요. 일관적인 패턴이 보이시지 않나요? 용어만 아셔도 어떤 네트워크를 만들었는 지 감이 오실 수 있습니다.
  • callback 을 사용하여, loss 가 더 줄지 않을 때 학습을 멈춰줍니다.
In [11]:
#define our model
def getModel():
    #Building the model
    gmodel=Sequential()
    #Conv Layer 1
    gmodel.add(Conv2D(64, kernel_size=(3, 3),activation='relu', input_shape=(75, 75, 3)))
    gmodel.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
    gmodel.add(Dropout(0.2))

    #Conv Layer 2
    gmodel.add(Conv2D(128, kernel_size=(3, 3), activation='relu' ))
    gmodel.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    gmodel.add(Dropout(0.2))

    #Conv Layer 3
    gmodel.add(Conv2D(128, kernel_size=(3, 3), activation='relu'))
    gmodel.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    gmodel.add(Dropout(0.2))

    #Conv Layer 4
    gmodel.add(Conv2D(64, kernel_size=(3, 3), activation='relu'))
    gmodel.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    gmodel.add(Dropout(0.2))

    #Flatten the data for upcoming dense layers
    gmodel.add(Flatten())

    #Dense Layers
    gmodel.add(Dense(512))
    gmodel.add(Activation('relu'))
    gmodel.add(Dropout(0.2))

    #Dense Layer 2
    gmodel.add(Dense(256))
    gmodel.add(Activation('relu'))
    gmodel.add(Dropout(0.2))

    #Sigmoid Layer
    gmodel.add(Dense(1))
    gmodel.add(Activation('sigmoid'))

    mypotim=Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
    gmodel.compile(loss='binary_crossentropy',
                  optimizer=mypotim,
                  metrics=['accuracy'])
    gmodel.summary()
    return gmodel


def get_callbacks(filepath, patience=2):
    es = EarlyStopping('val_loss', patience=patience, mode="min")
    msave = ModelCheckpoint(filepath, save_best_only=True)
    return [es, msave]
file_path = ".model_weights.hdf5"
callbacks = get_callbacks(filepath=file_path, patience=5)
  • 더 나은 모델을 위하여 바로 test를 하는 것이 아니라, 기존 train set 을 train, valid로 나눠서 먼저 모델을 평가해봅니다.
  • Sklearn 내장 함수인 train_test_split 을 이용하면 쉽게 할 수 있습니다.
In [12]:
target_train=train['is_iceberg']
X_train, X_valid, y_train, y_valid = train_test_split(X_train, target_train, random_state=1, train_size=0.8)
/home/youhanlee/.local/lib/python3.5/site-packages/sklearn/model_selection/_split.py:2010: FutureWarning:

From version 0.21, test_size will always complement train_size unless both are specified.

  • 학습을 진행하겠습니다. 블로그에 게시해야하니 epoch 은 10으로 하겠습니다. 실제로 실습하실 때는 더 큰 epoch 으로 하셔서 좋은 결과 얻으세요!
In [13]:
#Without denoising, core features.
gmodel=getModel()
gmodel.fit(X_train, y_train,
          batch_size=24,
          epochs=10,
          verbose=1,
          validation_data=(X_valid, y_valid),
          callbacks=callbacks)
WARNING:tensorflow:From /usr/local/lib/python3.5/dist-packages/keras/backend/tensorflow_backend.py:1255: calling reduce_prod (from tensorflow.python.ops.math_ops) with keep_dims is deprecated and will be removed in a future version.
Instructions for updating:
keep_dims is deprecated, use keepdims instead
WARNING:tensorflow:From /usr/local/lib/python3.5/dist-packages/keras/backend/tensorflow_backend.py:1340: calling reduce_mean (from tensorflow.python.ops.math_ops) with keep_dims is deprecated and will be removed in a future version.
Instructions for updating:
keep_dims is deprecated, use keepdims instead
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 73, 73, 64)        1792      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 17, 17, 128)       0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 15, 15, 128)       147584    
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 7, 7, 128)         0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 5, 5, 64)          73792     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 2, 2, 64)          0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 2, 2, 64)          0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               131584    
_________________________________________________________________
activation_1 (Activation)    (None, 512)               0         
_________________________________________________________________
dropout_5 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 256)               131328    
_________________________________________________________________
activation_2 (Activation)    (None, 256)               0         
_________________________________________________________________
dropout_6 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 257       
_________________________________________________________________
activation_3 (Activation)    (None, 1)                 0         
=================================================================
Total params: 560,193
Trainable params: 560,193
Non-trainable params: 0
_________________________________________________________________
Train on 1283 samples, validate on 321 samples
Epoch 1/10
1283/1283 [==============================] - 3s 2ms/step - loss: 0.8111 - acc: 0.5511 - val_loss: 0.5916 - val_acc: 0.6698
Epoch 2/10
1283/1283 [==============================] - 1s 470us/step - loss: 0.5772 - acc: 0.6672 - val_loss: 0.5653 - val_acc: 0.6698
Epoch 3/10
1283/1283 [==============================] - 1s 459us/step - loss: 0.5348 - acc: 0.6882 - val_loss: 0.5303 - val_acc: 0.7040
Epoch 4/10
1283/1283 [==============================] - 1s 503us/step - loss: 0.4952 - acc: 0.7311 - val_loss: 0.4921 - val_acc: 0.7882
Epoch 5/10
1283/1283 [==============================] - 1s 503us/step - loss: 0.4756 - acc: 0.7545 - val_loss: 0.4786 - val_acc: 0.7944
Epoch 6/10
1283/1283 [==============================] - 1s 506us/step - loss: 0.4812 - acc: 0.7662 - val_loss: 0.4579 - val_acc: 0.7788
Epoch 7/10
1283/1283 [==============================] - 1s 482us/step - loss: 0.4707 - acc: 0.7514 - val_loss: 0.5456 - val_acc: 0.7788
Epoch 8/10
1283/1283 [==============================] - 1s 504us/step - loss: 0.4264 - acc: 0.7849 - val_loss: 0.4405 - val_acc: 0.7913
Epoch 9/10
1283/1283 [==============================] - 1s 480us/step - loss: 0.4445 - acc: 0.7701 - val_loss: 0.4666 - val_acc: 0.7913
Epoch 10/10
1283/1283 [==============================] - 1s 503us/step - loss: 0.4218 - acc: 0.7950 - val_loss: 0.4130 - val_acc: 0.8287
Out[13]:
<keras.callbacks.History at 0x7f563f692a90>
  • 저장된 weight 를 다시 불러와서 validation set 에 대한 loss 와 accuracy 를 확인합니다.
In [14]:
gmodel.load_weights(filepath=file_path)
score = gmodel.evaluate(X_valid, y_valid, verbose=1)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
321/321 [==============================] - 0s 214us/step
Test loss: 0.41295462995303384
Test accuracy: 0.8286604361370716
In [15]:
predicted_test = gmodel.predict_proba(X_test)
8424/8424 [==============================] - 2s 208us/step
In [16]:
submission = pd.DataFrame()
submission['id']=test['id']
submission['is_iceberg']=predicted_test.reshape((predicted_test.shape[0]))
submission.to_csv('sub.csv', index=False)
Comments