머신러닝 스터디 3주차 - Simple CNN 모델로 이미지 학습하기
03/20
교재(컴퓨터 비전과 딥러닝 - 라쟈링가파 샨무갸마니 p.97)
개와 고양이를 예측하는 모델 훈련시키기
1. Kaggle에서 개와 고양이 이미지를 다운받는다.
(https://www.kaggle.com/c/dogs-vs-cats/data)
2. 다운받은 이미지 중에서 train 폴더에 있는 이미지의 일부만 사용한다.
3. 교재의 simple cnn 코드를 활용해서 이미지 학습을 시킨다.
# 이미지 데이터 출처 kaggle.com/c/dogs-vs-cats/data | |
# tensorflow, pillow, SciPy | |
print("==========================") | |
print("====loading settings======") | |
import tensorflow as tf | |
import os | |
import shutil | |
work_dir = 'C:/Users/BY/Downloads/dogs-vs-cats/train/' #디렉토리 지정 | |
image_names = sorted(os.listdir(os.path.join(work_dir, 'train'))) | |
def copy_files(prefix_str, range_start, range_end, target_dir): | |
image_paths = [os.path.join(work_dir, 'train', prefix_str+'.'+str(i)+'.jpg') | |
for i in range(range_start, range_end)] | |
dest_dir = os.path.join(work_dir, 'data', target_dir,prefix_str) | |
os.makedirs(dest_dir) | |
for image_path in image_paths: | |
shutil.copy(image_path, dest_dir) | |
# 실습을 위해 고양이와 개 이미지를 1,000개만 사용 | |
print("==========================") | |
print("====copy image files======") | |
copy_files('dog', 0, 10, 'train') | |
copy_files('cat', 0, 10, 'train') | |
copy_files('dog', 10, 14, 'test') | |
copy_files('cat', 10, 14, 'test') | |
# 간단한 CNN으로 벤치마킹. 교재의 simple_cnn 모델 | |
print("==========================") | |
print("====define cnn model======") | |
image_height, image_width = 150, 150 | |
train_dir = os.path.join(work_dir+'data/', 'train') | |
test_dir = os.path.join(work_dir+'data/', 'test') | |
no_classes = 2 | |
no_validation = 8 #800 | |
epochs = 2 | |
batch_size = 2 #200 | |
no_train = 20 #2000 | |
no_test = 8 #800 | |
input_shape = (image_height, image_width, 3) | |
epoch_steps = no_train // batch_size # 여기가 0이 되어버리면 | |
# AttributeError: 'ProgbarLogger' object has no attribute 'log_values' 에러 발생 | |
test_steps = no_test // batch_size | |
def simple_cnn(input_shape): | |
model = tf.keras.models.Sequential() | |
model.add(tf.keras.layers.Conv2D( | |
filters=75, # 64 | |
kernel_size=(3,3), | |
activation='relu', | |
input_shape=input_shape | |
)) | |
model.add(tf.keras.layers.Conv2D( | |
filters=150, # 128 | |
kernel_size=(3,3), | |
activation='relu', | |
)) | |
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2,2))) | |
model.add(tf.keras.layers.Dropout(rate=0.3)) | |
model.add(tf.keras.layers.Flatten()) | |
model.add(tf.keras.layers.Dense(units=32, activation='relu')) # 1024 | |
# tensorflow.python.framework.errors_impl.ResourceExhaustedError: OOM when allocating tensor with shape[799350,1024] and type float on /job:localhost/replica:0/task:0/device:CPU:0 by allocator cpu | |
model.add(tf.keras.layers.Dropout(rate=0.3)) | |
model.add(tf.keras.layers.Dense(units=no_classes, activation='softmax')) | |
model.compile(loss=tf.keras.losses.categorical_crossentropy, | |
optimizer=tf.keras.optimizers.Adam(), | |
metrics=['accuracy']) | |
return model | |
simple_cnn_model = simple_cnn(input_shape) | |
# 한번에 이미지 묶음 하나씩만 로드. | |
# tf.keras에 ImageDataGenerator라는 클래스는 필요할 때마다 이미지를 읽음 | |
generator_train = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. /255) | |
generator_test = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255) | |
# 디렉터리로부터 이미지를 읽어오는 flow_from_directory 메소드 | |
train_images = generator_train.flow_from_directory( | |
train_dir, | |
batch_size = batch_size, | |
target_size = (image_width, image_height)) | |
test_images = generator_test.flow_from_directory( | |
test_dir, | |
batch_size = batch_size, | |
target_size = (image_width, image_height)) | |
# generator (생성기)는 모델을 학습시키기 위해 직접적으로 사용할 수 있다. | |
print("==========================") | |
print("====fitting cnn model=====") | |
simple_cnn_model.fit_generator( | |
train_images, | |
steps_per_epoch=epoch_steps, | |
epochs=epochs, | |
validation_data=test_images, | |
validation_steps=test_steps) |
코드 업로드 (https://github.com/BY1994/TIL/blob/master/Python/Tensorflow/07_Dogs_Cats_simple_cnn_keras.py)
4. 학습 결과
- 정확도는 거의 50% 안팎으로 나와서, 학습이 잘 되지 않았다.
- Anaconda를 설치하지 않아서 tensorflow 외에 이미지 처리를 위한 pillow와 연산을 위한 SciPy를 직접 설치해줘야했다! (cmd로 pip install을 사용했다)
- 또한 CPU의 부담이 너무 커서 python shell이 자꾸 죽었기 때문에 이미지는 교재의 가이드처럼 1000개를 사용하지 못하고 개수를 계속 줄여서 학습시켰다.
- shell을 죽지 않게 하는데 가장 큰 영향을 준 것은 model.add(tf.keras.layers.Dense(units=32, activation='relu')) 이 부분의 units가 원래 1024개였는데, 이것을 크게 줄였더니 코드가 정상적으로 실행되었다.
- 교재의 코드는 변수가 하드코딩되어있기 때문에, 이미지의 개수를 다르게 할 경우 no_validation, batch_size, no_train, no_test를 일일히 바꾸어줘야한다.
+ 데이터셋 확장하기
훈련 중 노이즈를 유발해서 데이터 셋의 크기를 늘린다.
generator_train 부분의 코드만 변경하면 데이터셋 크기를 늘려서 학습시킬 수 있다.
(https://github.com/BY1994/TIL/blob/master/Python/Tensorflow/08_Dogs_Cats_simple_cnn_keras_data_augmentation.py)
+ MNIST 데이터 API를 이용해 학습시키고 시각화 하기
""" | |
텐서플로 레이어 API에서 제공하는 메소드 이용 | |
""" | |
import tensorflow as tf | |
from tensorflow.examples.tutorials.mnist import input_data | |
mnist_data = input_data.read_data_sets('MNIST_data', one_hot = True) | |
input_size = 784 | |
no_classes = 10 | |
batch_size = 100 | |
total_batches = 200 | |
x_input = tf.placeholder(tf.float32, shape=[None, input_size]) | |
y_input = tf.placeholder(tf.float32, shape=[None, no_classes]) | |
# 텐서보드를 사용해 훈련 프로세스를 시각화 | |
# 변수의 통계를 시각화하려면 tf.summary에 변수의 통계값이 추가되어야한다. | |
def add_variable_summary(tf_variable, summary_name): | |
with tf.name_scope(summary_name + '_summary'): | |
mean = tf.reduce_mean(tf_variable) | |
tf.summary.scalar('Mean', mean) | |
with tf.name_scope('standard_deviation'): | |
standard_deviation = tf.sqrt(tf.reduce_mean( | |
tf.square(tf_variable - mean))) | |
tf.summary.scalar('StandardDeviation', standard_deviation) | |
tf.summary.scalar('Maximum', tf.reduce_max(tf_variable)) | |
tf.summary.scalar('Minimum', tf.reduce_min(tf_variable)) | |
tf.summary.histogram('Histogram', tf_variable) | |
# 이전 모델과 달리 MNIST 데이터를 정사각형 형태로 크기를 변경하고 2차원 이미지 형태로 사용 | |
# 이미지를 28 x 28 이미지 픽셀 크기로 변형 | |
x_input_reshape = tf.reshape(x_input, [-1, 28, 28, 1], | |
name = 'input_reshape') | |
# => 차원값이 -1이라는 것은 배치 크기가 임의의 수가 될 수 있음을 의미 | |
# => name은 텐서보드 그래프에 반영돼 쉽게 이해할 수 있음 | |
# 입력, 필터, 커널, 활성화가 정의된 2D 컨볼루션 레이어 정의 | |
def convolution_layer(input_layer, filters, kernel_size = [3, 3], | |
activation = tf.nn.relu): | |
layer = tf.layers.conv2d( | |
inputs = input_layer, | |
filters = filters, | |
kernel_size = kernel_size, | |
activation = activation, | |
) | |
add_variable_summary(layer, 'convolution') | |
return layer | |
# => kernel_size와 activation에 대한 디폴트 값을 줌 | |
# => 요약 데이터는 레이어에 추가되고, 해당 레이어는 반환됨 | |
# => 함수를 호출할 때마다 input_layer가 매개변수로 주입되어야함 | |
# 풀링 레이어에 대한 함수 | |
def pooling_layer(input_layer, pool_size = [2,2], strides = 2): | |
layer = tf.layers.max_pooling2d( | |
inputs = input_layer, | |
pool_size = pool_size, | |
strides = strides | |
) | |
add_variable_summary(layer,'pooling') | |
return layer | |
# => pool_size와 strides 디폴트 값이 주어지지만 필요한 경우 변경 가능 | |
# => 마찬가지로 레이어에 요약 데이터 추가됨 | |
# 밀집 레이어 정의 | |
def dense_layer(input_layer, units, activation=tf.nn.relu): | |
layer = tf.layers.dense( | |
inputs = input_layer, | |
units = units, | |
activation = activation | |
) | |
add_variable_summary(layer, 'dense') | |
return layer | |
# => activation에 대한 기본값을 갖고 있으며, 변수 요약 데이터 추가 | |
# 레이어들은 그래프로 연결되었으며, 아직 초기화되기 전이다. | |
# 첫번째 컨볼루션 레이어에서 샘플링된 특성을 더 나은 특성으로 변환하기 위해 | |
# 새로운 컨볼루션 레이어 추가 가능 | |
convolution_layer_1 = convolution_layer(x_input_reshape, 64) | |
pooling_layer_1 = pooling_layer(convolution_layer_1) | |
convolution_layer_2 = convolution_layer(pooling_layer_1, 128) | |
pooling_layer_2 = pooling_layer(convolution_layer_2) | |
flattened_pool = tf.reshape(pooling_layer_2, [-1, 5*5*128], | |
name='flattened_pool') | |
dense_layer_bottleneck = dense_layer(flattened_pool, 1024) | |
# => 컨볼루션 레이어 간의 유일한 차이점은 필터 크기 | |
# => 커널과 스트라이드 매개변수 값을 선택하는 것은 임의이며 경험에 의해 선택 | |
# => 밀집 레이어 API는 단일 차원의 벡터를 특정 개수의 숨겨진 유닛(1024개)에 매핑시킨다. | |
# => 숨겨진 레이어는 ReLU 활성화 함수로 이어져 비선형 연산이 되다. | |
# 드롭아웃 확률을 가진 드롭아웃 레이어 | |
# 훈련 모드는 드롭아웃 적용 여부에 따라 True 또는 False로 설정할 수 있으며, 훈련을 위해 | |
# True로 설정한다. 하지만 정확도 계산시 해당 값이 변경되어야해서 부울 값을 저장해둔다. | |
dropout_bool = tf.placeholder(tf.bool) | |
dropout_layer = tf.layers.dropout( | |
inputs = dense_layer_bottleneck, | |
rate = 0.4, | |
training = dropout_bool | |
) | |
# 드롭아웃 레이어를 로짓이라는 밀집 레이어에 주입 | |
# 로짓은 클래스 개수로 이어지는 활성화 함수를 가진 마지막 레이어 | |
logits = dense_layer(dropout_layer, no_classes) | |
# 이전처럼 로짓이 소프트맥스 레이어를 통과한 후 교차 엔트로피 계산을 수행 | |
# 텐서보드의 좀 더 나은 시각화를 위해 이름 범주에 추가 | |
with tf.name_scope('loss'): | |
softmax_cross_entropy = tf.nn.softmax_cross_entropy_with_logits( | |
labels=y_input, logits=logits) | |
loss_operation = tf.reduce_mean(softmax_cross_entropy, name='loss') | |
tf.summary.scalar('loss', loss_operation) | |
# 손실 함수를 tf.train API의 메소드를 이용해 최적화 | |
with tf.name_scope('optimiser'): | |
optimiser = tf.train.AdamOptimizer().minimize(loss_operation) | |
# 정확한 예측값과 정확도 계산을 위해 이름 범주를 추가 | |
# 정확도에 대한 스칼라 요약 데이터도 추가 | |
with tf.name_scope('accuray'): | |
with tf.name_scope('correct_prediction'): | |
predictions = tf.argmax(logits, 1) | |
correct_predictions = tf.equal(predictions, tf.argmax(y_input, 1)) | |
with tf.name_scope('accuracy'): | |
accuracy_operation = tf.reduce_mean( | |
tf.cast(correct_predictions, tf.float32)) | |
tf.summary.scalar('accuracy', accuracy_operation) | |
# 세션을 시작하고 변수를 초기화 | |
session = tf.Session() | |
session.run(tf.global_variables_initializer()) | |
# 요약 데이터를 모두 합쳐야하며, 훈련 요약 데이터와 테스팅 요약 데이터를 작성 | |
# 하기 위한 파일도 정의 | |
merged_summary_operation = tf.summary.merge_all() | |
train_summary_writer = tf.summary.FileWriter('/tmp/train', session.graph) | |
test_summary_writer = tf.summary.FileWriter('/tmp/test') | |
# 배치에서 데이터가 로드되고 훈련을 시작 | |
test_images, test_labels = mnist_data.test.images, mnist_data.test.labels | |
for batch_no in range(total_batches): | |
mnist_batch = mnist_data.train.next_batch(batch_size) | |
train_images, train_labels = mnist_batch[0], mnist_batch[1] | |
_, merged_summary = session.run([optimiser, merged_summary_operation], | |
feed_dict={ | |
x_input: train_images, | |
y_input: train_labels, | |
dropout_bool: True | |
}) | |
train_summary_writer.add_summary(merged_summary, batch_no) | |
if batch_no % 10 == 0: | |
merged_summary, _ = session.run([merged_summary_operation, | |
accuracy_operation], feed_dict={ | |
x_input: test_images, | |
y_input: test_labels, | |
dropout_bool: False | |
}) | |
test_summary_writer.add_summary(merged_summary, batch_no) |
(코드 업로드: https://github.com/BY1994/TIL/blob/master/Python/Tensorflow/05_MNIST_Multilayer_convolution.py)
위의 코드를 실행시킨 후, cmd 창에서 다음과 같은 주소로 이동한다.
=> tensorboard --logdir=/tmp/train을 하면 training 과 관련된 시각화 결과를,
tensorboard --logdir=/tmp/test를 하면 test와 관련된 시각화 결과를 볼 수 있다.
그리고 브라우저에서 localhost:6006 을 입력하면 시각화된 결과물을 확인할 수 있다.
이는 Scalar 탭의 결과물이고, Graph 탭에서는 다음과 같은 모델의 시각화를 볼 수 있다.