Convolutional Autoencoder: Clustering Images with Neural Networks
Convolutional Autoencoder: Clustering Images with Neural Networks
Autoencoder: Neural Networks For Unsupervised Learning에서 MNIST에 합성곱 오토인코더(convolutional autoencoder)를 적용했다. 동일한 모델을 사기 또는 이상 탐지(abnormaly detection) 같은 이미지가 아닌 문제에 적용할 수도 있다. 만약 문제가 픽셀기반이라면 CNN이 합성곱보다 좀더 성공적이라는 것을 기억하자. 그러자 우리는 레이블링된 supervised 학습 문제에서 이를 테스트했다. 질문은 CNN을 군집(clustering)을 위해 레이블링되지 않은 이미지에 적용할 수 있을까이다. 물론 적용할 수 있다. 이러한 CNN의 맞춤 형식이 합성곱 오토인코더이다.
Remembering regular autoencoders
Autoencoder: Neural Networks For Unsupervised Learning를 기억해 보자. 네트워크 디자인은 중심을 기준으로 대칭이고 노드의 수는 왼쪽에서 중앙으로는 감소하고 중앙에서 오른쪽으로는 증가한다. 중앙의 레이어는 압축된 표현이다. 우리는 CNN에 대해 동일한 절차를 적용한다. 추가로 합성곱 오토인코더에 합성곱(convolution), 활성화(activation), 풀링(pooling)을 사용한다.
Convolutional autoencoder
왼쪽에서 중앙쪽으로는 컨볼루션(convolution)이라고 할 수 있고 중앙에서 오른쪽은 디컨볼루션(deconvolution)이라고 할 수 있다. 디컨볼루션쪽은 또한 업샘플링(upsampling) 또는 전치(transpose) 컨볼루션으로 알려져 있다. 풀링(pooling) 연산은 기본적인 축소(reduction) 연산이다. 어떻게 반대 연산을 적용할 수 있을까? 약간 헤깔리수도 있겠지만, 업샘플링을 설명한 애니메이션을 여기찾을 수 있다.
2 X 2 크기의 입력 행렬(파란색)이 4 X 4 크기의 행렬(청록색)으로 디컨볼루션 된다. 이를 하기 위해서 우리는 가상 요소(예를 들면 0)를 기본 행렬에 더할 수 있고 이는 6 X 6 크기의 행렬로 변환된다.
Upsampling
다시 MNIST로 작업해 보자. 아래와 같이 합성곱 오토인코더(convolutional autoencoder)의 구조를 만든다.
model = Sequential()
#1st convolution layer
#16 is number of filters and (3, 3) is the size of the filter.
model.add(Conv2D(16, (3, 3), padding='same', input_shape=(28,28,1)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), padding='same'))
#2nd convolution layer
model.add(Conv2D(2,(3, 3), padding='same')) # apply 2 filters sized of (3x3)
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), padding='same'))
#here compressed version
#3rd convolution layer
model.add(Conv2D(2,(3, 3), padding='same')) # apply 2 filters sized of (3x3)
model.add(Activation('relu'))
model.add(UpSampling2D((2, 2)))
#4rd convolution layer
model.add(Conv2D(16,(3, 3), padding='same'))
model.add(Activation('relu'))
model.add(UpSampling2D((2, 2)))
model.add(Conv2D(1,(3, 3), padding='same'))
model.add(Activation('sigmoid'))
아래 코드로 만들어진 네트워크 구조를 요약해 볼 수 있다.
model.summary()
이 명령은 아래와 같이 출력한다. 기본 입력은 시작지점에서 28 X 28 크기이다. 처음 2개의 레이어가 축소(reduction)을 담당한다. 다음 2개의 레이어는 복원 담당이다. 마지막 레이어는 보이는 것처럼 입력과 동일한 크리를 복원한다.(** 여기서는 레이어를 아래의 한 줄이 아닌 convolution + activation 셋으로 보는게 맞는것 같다.)
_____________
Layer (type) Output Shape Param #
========
conv2d_1 (Conv2D) (None, 28, 28, 16) 160
_____________
activation_1 (Activation) (None, 28, 28, 16) 0
_____________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 16) 0
_____________
conv2d_2 (Conv2D) (None, 14, 14, 2) 290
_____________
activation_2 (Activation) (None, 14, 14, 2) 0
_____________
max_pooling2d_2 (MaxPooling2 (None, 7, 7, 2) 0
_____________
conv2d_3 (Conv2D) (None, 7, 7, 2) 38
_____________
activation_3 (Activation) (None, 7, 7, 2) 0
_____________
up_sampling2d_1 (UpSampling2 (None, 14, 14, 2) 0
_____________
conv2d_4 (Conv2D) (None, 14, 14, 16) 304
_____________
activation_4 (Activation) (None, 14, 14, 16) 0
_____________
up_sampling2d_2 (UpSampling2 (None, 28, 28, 16) 0
_____________
conv2d_5 (Conv2D) (None, 28, 28, 1) 145
_____________
activation_5 (Activation) (None, 28, 28, 1) 0
========
이제 훈련을 시작할 수 있다.
model.compile(optimizer='adadelta', loss='binary_crossentropy')
model.fit(x_train, x_train, epochs=3, validation_data=(x_test, x_test))
훈련과 테스트셋 대한 손실 값 모두 만족스럽다.
loss: 0.0968 – val_loss: 0.0926
복원된 몇개를 시각화해 보자.
restored_imgs = model.predict(x_test)
for i in range(5):
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
plt.show()
plt.imshow(restored_imgs[i].reshape(28, 28))
plt.gray()
plt.show()
Testing
복원은 만족스럽다. 아래 이미지에서 왼쪽은 원본 오른쪽은 압축된 표현으로부터 복원된 이미지이다.
Some restorations of convolutional autoencoder
5번째 레이어(max_pooling2d_2)는 압축된 표현(compressed representation) 상태이고 (None, 7, 7, 2) 크기이다. 이 작업은 우리가 작은 손실로 7 X 7 X 2 크기의 행렬에서 28 X 28 픽셀 이미지를 복원할 수 있다는 것을 보여준다. 즉, 압축된 표현은 원본 이미지보다 8배 작은 공간을 차지한다.
Compressed Representations
아마도 어떻게 압축된 표현이 추출되는지 궁금할 것이다.
compressed_layer = 5
get_3rd_layer_output = K.function([model.layers[0].input], [model.layers[compressed_layer].output])
compressed = get_3rd_layer_output([x_test])[0]
#flatten compressed representation to 1 dimensional array
compressed = compressed.reshape(10000,7*7*2)
이제 우리는 압축된 표현에 군집화(clustering)를 적용할 수 있다. 이 글에서는 k-means clustering을 사용한다.
from tensorflow.contrib.factorization.python.ops import clustering_ops
import tensorflow as tf
def train_input_fn():
data = tf.constant(compressed, tf.float32)
return (data, None)
unsupervised_model = tf.contrib.learn.KMeansClustering(
10 #num of clusters
, distance_metric = clustering_ops.SQUARED_EUCLIDEAN_DISTANCE
, initial_clusters=tf.contrib.learn.KMeansClustering.RANDOM_INIT
)
unsupervised_model.fit(input_fn=train_input_fn, steps=1000)
훈련이 끝났다. 이제 모든 테스트 셋에 대해 군집을 확인할 수 있다.
clusters = unsupervised_model.predict(input_fn=train_input_fn)index = 0
for i in clusters:
current_cluster = i['cluster_idx']
features = x_test[index]
index = index + 1
예를 들면, 6번째 군집은 46개의 아이템으로 구성된다. 이 군집의 분포는 22개는 4, 14개는 9, 7개는 7, 1개는 5이다. 마치 거의 4와 9가 이 군집에 포함된 것 같아 보인다.
이 글에서 이미지기반 데이터에서 정보 감소를 위해 CNN과 오토인코더 아이디어를 통합했다. 이것은 군집화를 위한 전처리 단계가 될 수 있다. 이런 방법으로 784개의 특성(28 X 28 = 784) 대신 98개(7 X 7 X 2 = 98)의 특성으로 k-means clustering을 적용할 수 있다. 이는 레이블되지 않은 데이터에 대한 레이블링 작업을 빠르게 할 수 있다. 물론, 오토인코딩으로 빠른 속도가 된다.
여기에서 사용한 소스코드를 찾을 수 있다.