Create your first Image Recognition Classifier using CNN, Keras and Tensorflow backend
Getting Started — Dog or Cat
본 튜토리얼에서 일단 주어진 이미지가 개인지 고양이인지 구분하는 이미지 분류기를 만들고자 한다. 그리고 추후 더 큰 규모로 확장해보자.
Tools And Technologies
본 튜토리얼 진행을 위해서는 다음의 도구들이 필요하다. 아래는 Ubuntu 18.04 환경에서 구현되었다.
Anaconda 아나콘다는 데이터터 사이언스와 머신러닝 관련 어플리케이션 제작 관련 패키지 관리 및 배포를 도와주는 오픈 소스 프로그램이다. 설치는 다음을 참조한다.
https://www.anaconda.com/download/
Tensorflow 텐서플로우는 데이터플로우 프로그래밍을 위한 오픈 소스 소프트웨어 라이브러리이다. 설치는 다음을 참조한다.
https://www.tensorflow.org/install/
Keras 케라스는 파이썬으로 작성된된 오픈 소스 신경망이다. 텐서플로우 환경을 활성화시킨 뒤 pip install keras 를 이용하여 케라스를 설치한다.
CNN Convolution Neural network 의 약자로 합성곱 신경망이라고 부른다. 여러 층으로 형성된 feed-forward 인공신경망으로, 대부분 시각 이미지 분석에 적용된다. 합성곱 신경망에 대한 직관적인 설명을 담은 포스팅이 아래에 있다.
https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/
Plan of Attack
작업 순서는 다음가 같다.
- Collecting the Dataset
- Importing Libraries and Splitting the Dataset
- Building the CNN
- Full Connection
- Data Augmentation
- Training our Network
- Testing
Step 1 Collecting the Dataset
머신을 훈련시키기 위해서는 어마어마한 양의 데이터를 필요로 한다. 앞으로 만들 모델이 데이터로부터 특정한 관계를 도출해내고, 해당 객체가 가진 공통적인 특징을 알아내기 위해서 말이다.
다행스럽게도 이미 인터넷 상에는 그러한 데이터셋이 수없이 많이 올라와 있다. 여기서는 고양이와 개의 이미지 각각 5,000개, 합 10,000개로 구성된 데이터셋을 이용해보기로 하자.
아래 링크에서 다운로드 받을 수 있다.
http://www.superdatascience.com/wp-content/uploads/2017/03/Convolutional-Neural-Networks.zip
Step 2 Importing Libraries and Splitting the Dataset
라이브러리를 가져오자. 라이브러리를 가져온 다음에는, 앞선 데이터를 훈련셋트와 검증셋트 이렇게 2개로 나눈다.
1 2 3 4 5 6 | # Importing the Keras libraries and packages from keras.models import Sequential from keras.layers import Convolution2D from keras.layers import MaxPooling2D from keras.layers import Flatten from keras.layers import Dense | cs |
Using TensorFlow backend.
앞서 다운로드받은 파일을 압축해제를 해보면, 이미 데이터셋이 2 파트로 나눠져 있음을 알 수 있다. 훈련셋트에 각가 4,000개의 고양이와 개 이미지가, 그리고 검증셋트에는 각 1,000개의 이미지가 들어가 있다.
Step 3 Buliding the CNN
가장 중요한 부분이다. 이 단계는 Convolution, Polling, Flattening 의 3부분으로 구성된다.
Convolution 의 주요 목적은 인풋 이미지의 특성을 추출해내는 것이다. Convolution 은 입력된 데이터의 작은 사각형을 사용하여 이미지의 특징을 학습함으로써 픽셀간의 공간적 관계를 보존한다.
모든 이미지는 픽셀값의 행렬로 간주할 수 있다. 픽셀의 값이 0과 1인 아래와 같은 5 x 5 이미지를 생각해보자.
https://cdn-images-1.medium.com/max/800/0*JsCxaZb5AIm6YFlB
그리고 아래와 같은 3 x 3 행렬도 생각해보자.
https://cdn-images-1.medium.com/max/800/0*TG6RffpzcJBzYsEk
그러면, 5 x 5 이미지와 3 x 3 행렬의 컨벌루션은 아래 그림과 같이 계산될 수 있다.
https://cdn-images-1.medium.com/max/800/0*KdJv2eWBC1qUs3Po
결과로 획득된 행렬은 보통 특성맵이라고 불리는 것으로, 컨벌루션 진행후에는 ReLU 라는 추가적인 작업이 이루어진다. 다음 단계는 pooling 이다.
Pooling (서브샘플링, 다운샘플링이라고 불리기도 한다) 은 각 특성맵의 차원수를 줄인다. 하지만 가장 중요한 정보는 그대로 유지한다. Max Pooling 의 경우, 공간이웃 (예를 들어, 2×2 윈도우) 을 정의하고, 그 윈도우 안에 있는 정류된 특성맵으로부터 가장 큰 원소를 취한다. 가장 큰 원소를 취하는 대신 평균값을 취하는 경우를 Average Pooling 이라고 하고, 모든 원소의 합을 취할 수도 있다. 하지만 실제로는 Max Pooling 의 성능이 가장 괜찮은 것으로 나온다.
https://cdn-images-1.medium.com/max/800/0*6ED-178t3tjE0Wo6
Pooling 다음 단계는 flattening 이다. 신경망의 노드에 집어넣을 수 있도록 행렬을 선형 배열 변환시킨다.
코드로 구현해보자.
1 2 3 4 5 6 7 8 9 10 11 | #Initialize the CNN classifier = Sequential() # Step1 - Convolution classifier.add(Convolution2D(32, 3, 3, input_shape = (64, 64, 3), activation = 'relu')) # Step 2 - Pooling classifier.add(MaxPooling2D(pool_size = (2, 2))) # Step 3 - Flattening classifier.add(Flatten()) | cs |
/home/founder/anaconda3/envs/tfKeras/lib/python3.6/site-packages/ipykernel_launcher.py:5: UserWarning: Update your `Conv2D` call to the Keras 2 API: `Conv2D(32, (3, 3), input_shape=(64, 64, 3..., activation="relu")` """
따라서 현재까지의 CNN 네트워크는 다음과 같은 모습이다.
https://cdn-images-1.medium.com/max/800/1*DoRz0KaV1jwCttVDxqJflw.png
Step 4 Full Connection
Full connection 은 컨벌루션널 네트워크를 신경망과 연결한 다음 해당 네트워크를 컴파일링한다.
1 2 3 4 5 6 | # Step 4 - Full Connection classifier.add(Dense(output_dim = 128, activation = 'relu')) classifier.add(Dense(output_dim = 1, activation = 'sigmoid')) # Compling the CNN classifier.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy']) | cs |
/home/founder/anaconda3/envs/tfKeras/lib/python3.6/site-packages/ipykernel_launcher.py:2: UserWarning: Update your `Dense` call to the Keras 2 API: `Dense(activation="relu", units=128)`
/home/founder/anaconda3/envs/tfKeras/lib/python3.6/site-packages/ipykernel_launcher.py:3: UserWarning: Update your `Dense` call to the Keras 2 API: `Dense(activation="sigmoid", units=1)
This is separate from the ipykernel package so we can avoid doing imports until
해당 오브젝트가 개인지 고양이인지에 대한 확률을 찾기 위해 마지막 층에 시그모이드 함수를 활성화함수로 하는 신경망을 만든다.
따라서 최종 네트워크는 다음과 같은 모양을 갖추게 된다.
https://cdn-images-1.medium.com/max/800/1*aAz7Nrx4IkdEViyBknpH9Q.png
Step 5 Data Augmentation
데이터 훈련에는 많은 수의 데이터가 필요하다. 만약 제한된 수의 이미지만 네트워크상에 가지고 있다고 가정해보자. 이 경우 어떻게 해야할까?
기존의 데이터셋에 넣기 위해 새로운 이미지들을 찾아나설 필요는 없다. 왜냐요? 그 이유는 신경망은 그정도로 스마트하지는 않기 때문이다.
예를 들어, 허접하게 훈련된 신경망의 경우 아래의 3개의 테니스공을 서로 다른 것으로 판단한다.
https://cdn-images-1.medium.com/max/800/1*L07HTRw7zuHGT4oYEMlDig.jpeg
그러므로 더욱 많은 데이터를 얻기 위해서는 단지 기존 데이터셋에 약간의 변경만 가해도 충분하다는 것이다. 예를 들어 뒤집거나 회전시키거너 하는 등의 변화 정도 말이다. 어쨌든 우리의 신경망은 이러한 이미지들을 서로 다르게 간주한다.
데이터 증대는 모델의 과적합을 줄이기 위한 방법으로, 우리의 훈련 데이터의 정보만을 사용하여 훈련 데이터의 양을 증가시킨다.
사실 데이터 증대라는 영역은 새로운 것이 아니며, 다수의 데이터 증대 테크닉이 특정 문제 해결에 적용되어 왔다.
코드를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # Part 2 - Fitting the CNN to the images from keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator( rescale = 1./255, shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True) test_datagen = ImageDataGenerator(rescale = 1./255) training_set = train_datagen.flow_from_directory( '../input/Convolutional_Neural_Networks/dataset/training_set', target_size = (64,64), batch_size = 32, class_mode = 'binary') test_set = test_datagen.flow_from_directory( '../input/Convolutional_Neural_Networks/dataset/test_set', target_size =(64, 64), batch_size = 32, | cs |
Found 8000 images belonging to 2 classes. Found 2000 images belonging to 2 classes.
이제 많은 데이터를 가지게 되었다. 트레이닝을 시작해보자.
Step 6 Training our Network
이로써 구축에 필요한 모든 단계를 마쳤다. 이제 모델을 훈련시킬 타이밍이다. 훈련에 앞서 훈련과정을 텐서보드를 통해 확인해보자. 다음과 같이 구성한다.
1234 # Early stopping callbackfrom keras.callbacks import EarlyStopping, TensorBoardPATIENCE = 10early_stopping = EarlyStopping(monitor='loss', min_delta=0, patience=PATIENCE, verbose=0, mode='auto')cs
로그가 저장될 디렉토리를 생성한 후 다음과 같이 로그 루트 디렉토리에 해당 경로를 넣는다.
123456 # TensorBoard callbackfrom datetime import datetimeLOG_DIRECTORY_ROOT = '/home/founder/tfKeras/tensorlog'now = datetime.utcnow().strftime("%Y%m%d%H%M%S")log_dir = "{}/run-{}/".format(LOG_DIRECTORY_ROOT, now)tensorboard = TensorBoard(log_dir=log_dir, write_graph=True, write_images=True)cs
12 # Place the callbacks in a listcallbacks = [early_stopping, tensorboard]cs
먼저 에포크를 10으로 설정해서 진행한다.
12345678910 from IPython.display import displayfrom PIL import Imageclassifier.fit_generator(training_set,steps_per_epoch = 8000,epochs = 10,validation_data = test_set,validation_steps = 800,callbacks = callbacks)cs
이 단계에서 ModuleNotFoundError: No module named 'PIL' 과 UnsatisfiableError 를 발생시키는 경우 다음 링크를 참고한다.
ModuleNotFoundError: No module named 'PIL' https://antilibrary.org/1976
텐서보드를 실행하자.
1 (tfKeras) founder@hilbert:~/tfKeras$ tensorboard --logdir /home/founder/tfKeras/tensorlogcs
훈련과정에서 GPU 환경여부에 따라 실행속도에 큰 차이를 나는 것을 확인할 수 있다. 본 튜토리얼은 구글 클라우드 플랫폼의 VM 에서 구동하고 있다.
n1-standard-2(vCPU 1개, 3.75GB 메모리) 의 경우 CPU가 100% 를 찍고 있다.
이에 n1-standard-2(vCPU 2개, 7.5GB 메모리) 으로 변경, 80~80% 대로 떨어졌다.
위 환경에서 소요된 시간을 보면, 에포크 당 약 50여분이 소요되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Epoch 1/10 8000/8000 [==============================] - 3144s 393ms/step - loss: 0.5088 - acc: 0.7426 - val_loss: 0.5119 - val_acc: 0.7744 Epoch 2/10 8000/8000 [==============================] - 3153s 394ms/step - loss: 0.3907 - acc: 0.8200 - val_loss: 0.5637 - val_acc: 0.7650 Epoch 3/10 8000/8000 [==============================] - 3126s 391ms/step - loss: 0.3391 - acc: 0.8464 - val_loss: 0.6233 - val_acc: 0.7619 Epoch 4/10 8000/8000 [==============================] - 3108s 388ms/step - loss: 0.2962 - acc: 0.8681 - val_loss: 0.7462 - val_acc: 0.7465 Epoch 5/10 8000/8000 [==============================] - 3094s 387ms/step - loss: 0.2576 - acc: 0.8894 - val_loss: 0.8374 - val_acc: 0.7563 Epoch 6/10 8000/8000 [==============================] - 3080s 385ms/step - loss: 0.2246 - acc: 0.9057 - val_loss: 1.0228 - val_acc: 0.7480 Epoch 7/10 8000/8000 [==============================] - 3140s 392ms/step - loss: 0.1961 - acc: 0.9187 - val_loss: 1.1654 - val_acc: 0.7290 Epoch 8/10 8000/8000 [==============================] - 3160s 395ms/step - loss: 0.1720 - acc: 0.9300 - val_loss: 1.1737 - val_acc: 0.7429 Epoch 9/10 8000/8000 [==============================] - 3065s 383ms/step - loss: 0.1526 - acc: 0.9385 - val_loss: 1.0695 - val_acc: 0.7506 Epoch 10/10 8000/8000 [==============================] - 3027s 378ms/step - loss: 0.1386 - acc: 0.9450 - val_loss: 1.3196 - val_acc: 0.7425 <keras.callbacks.History at 0x7efbf67989e8> |
이번에는 에포크를 20으로 설정해서 진행하였다. 그리고 에포크 수가 증가할수록, 정확도 역시 향상됨을 알 수 있다.
1234567891011121314151617181920212223242526272829303132333435363738394041 Epoch 1/208000/8000 [==============================] - 2926s 366ms/step - loss: 0.4128 - acc: 0.8060 - val_loss: 0.5941 - val_acc: 0.7483Epoch 2/208000/8000 [==============================] - 2935s 367ms/step - loss: 0.3268 - acc: 0.8540 - val_loss: 0.7432 - val_acc: 0.7540Epoch 3/208000/8000 [==============================] - 2934s 367ms/step - loss: 0.2607 - acc: 0.8873 - val_loss: 0.8729 - val_acc: 0.7516Epoch 4/208000/8000 [==============================] - 2932s 366ms/step - loss: 0.2155 - acc: 0.9094 - val_loss: 1.0187 - val_acc: 0.7384Epoch 5/208000/8000 [==============================] - 2927s 366ms/step - loss: 0.1759 - acc: 0.9282 - val_loss: 1.2049 - val_acc: 0.7494Epoch 6/208000/8000 [==============================] - 2922s 365ms/step - loss: 0.1451 - acc: 0.9420 - val_loss: 1.3610 - val_acc: 0.7400Epoch 7/208000/8000 [==============================] - 2918s 365ms/step - loss: 0.1249 - acc: 0.9511 - val_loss: 1.2979 - val_acc: 0.7379Epoch 8/208000/8000 [==============================] - 2923s 365ms/step - loss: 0.1087 - acc: 0.9578 - val_loss: 1.5250 - val_acc: 0.7383Epoch 9/208000/8000 [==============================] - 2922s 365ms/step - loss: 0.0970 - acc: 0.9632 - val_loss: 1.7719 - val_acc: 0.7266Epoch 10/208000/8000 [==============================] - 2917s 365ms/step - loss: 0.0853 - acc: 0.9680 - val_loss: 1.7015 - val_acc: 0.7451Epoch 11/208000/8000 [==============================] - 2912s 364ms/step - loss: 0.0771 - acc: 0.9719 - val_loss: 1.6849 - val_acc: 0.7402Epoch 12/208000/8000 [==============================] - 2910s 364ms/step - loss: 0.0687 - acc: 0.9750 - val_loss: 1.7218 - val_acc: 0.7428Epoch 13/208000/8000 [==============================] - 2906s 363ms/step - loss: 0.0620 - acc: 0.9776 - val_loss: 1.8727 - val_acc: 0.7567Epoch 14/208000/8000 [==============================] - 2902s 363ms/step - loss: 0.0567 - acc: 0.9798 - val_loss: 2.1399 - val_acc: 0.7255Epoch 15/208000/8000 [==============================] - 2911s 364ms/step - loss: 0.0517 - acc: 0.9821 - val_loss: 1.9402 - val_acc: 0.7369Epoch 16/208000/8000 [==============================] - 2914s 364ms/step - loss: 0.0477 - acc: 0.9833 - val_loss: 2.0261 - val_acc: 0.7390Epoch 17/208000/8000 [==============================] - 2920s 365ms/step - loss: 0.0456 - acc: 0.9847 - val_loss: 2.1458 - val_acc: 0.7324Epoch 18/208000/8000 [==============================] - 2917s 365ms/step - loss: 0.0429 - acc: 0.9852 - val_loss: 2.1210 - val_acc: 0.7402Epoch 19/208000/8000 [==============================] - 2920s 365ms/step - loss: 0.0400 - acc: 0.9868 - val_loss: 2.1081 - val_acc: 0.7333Epoch 20/208000/8000 [==============================] - 2924s 366ms/step - loss: 0.0373 - acc: 0.9874 - val_loss: 2.0623 - val_acc: 0.7377<keras.callbacks.History at 0x7f8716a21668>cs
위의 훈련과정을 텐서보드를 통해 확인해보자.
웹브라우저에서 http://your_ip:6006 로 접속한다.
이제 파일 형태로 저장해보자. 모델을 json 파일로, 그리고 가중치를 h5 파일로 저장한다. 다음과 같다.
123 classifier_json = classifier.to_json()with open("classifier.json", "w") as json_file :json_file.write(classifier_json)cs
12 classifier.save_weights("model.h5")print("Saved classifier to disk")cs Saved classifier to disk
이제 이미지를 랜덤하게 선택하여 테스트해보자.
https://cdn-images-1.medium.com/max/800/1*Fm-xFCVAjnB4WPH5NV0eRQ.png
1 2 3 4 5 6 7 8 9 10 11 12 13 | import numpy as np from keras.preprocessing import image test_image = image.load_img('random.jpg', target_size = (64, 64)) test_image = image.img_to_array(test_image) test_image = np.expand_dims(test_image, axis = 0) result = classifier.predict(test_image) training_set.class_indices if result[0][0] >= 0.5: prediction = '개' else: prediction = '고양이' print(prediction) | cs |
개
“Be quiet, darling. Let pattern recognition have its way.”
― William Gibson, The Peripheral
튜토리얼 원문출처