코딩초보 김씨

[DL] 합성곱 신경망 CNN 본문

Python/Deep Learning

[DL] 합성곱 신경망 CNN

초보김씨 2021. 7. 10. 21:22

합성곱 신경망 CNN (Convolutional neural networks)

뇌가 물체를 인식할 때 동작하는 방식에서 영감을 얻은 모델. 이미지 처리에 사용

합성곱 연산 : 가중치를 공유하여 근처에 있는 뉴런들만 연산

 

 

이번에도 이 정도로만 알고, 자유 미션때 작성한 코드로 설명하겠다.

 

 


[ 데이터 수집 및 전처리 ]

#이미지 스크래퍼 라이브러리 설치
!pip install -q jmd_imagescraper 

# directory 설정
base_dir = "/content/images"

# 수집할 이미지 개수
num_images_per_class = 500

# 이미지 수집
# res = duckduckgo_search(base_dir, "폴더명", "검색 키워드", max_results=num_images_per_class, uuid_names=False)
res = duckduckgo_search(base_dir, "Tulip", "flower Tulip", max_results=num_images_per_class, uuid_names=False)
res = duckduckgo_search(base_dir, "Rose", "flower Rose", max_results=num_images_per_class, uuid_names=False)
res = duckduckgo_search(base_dir, "Sunflower", "flower Sunflower", max_results=num_images_per_class, uuid_names=False)
res = duckduckgo_search(base_dir, "Mistflower", "flower Mistflower", max_results=num_images_per_class, uuid_names=False)

class_names = ["Tulip", "Sunflower", "Rose", "Mistflower"]

# 훈련, 검증, 테스트 분할을 위한 폴더 생성
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

for class_name in class_names:
  train_class_dir = os.path.join(train_dir, class_name)
  os.mkdir(train_class_dir)

  validation_class_dir = os.path.join(validation_dir, class_name)
  os.mkdir(validation_class_dir)

  test_class_dir = os.path.join(test_dir, class_name)
  os.mkdir(test_class_dir)


# 데이터를 훈련/검정/테스트 폴더로 분배
num_train = int(num_images_per_class*0.6) #전체 데이터의 60%를 훈련 데이터로
num_valid = int(num_images_per_class*0.2) #전체 데이터의 20%를 검증 데이터로
num_test = int(num_images_per_class*0.2) #전체 데이터의 20%를 테스트 데이터로

# 각 파일명 앞의 "0" 제거
for class_name in class_names:
  class_dir = os.path.join(base_dir, class_name)
  for filename in os.listdir(class_dir):
    org_filename = os.path.join(class_dir,filename)
    new_filename = os.path.join(class_dir,filename.lstrip("0"))
    os.rename(org_filename, new_filename)

# 훈련 데이터를 train 폴더에 복사
for class_name in class_names:
  class_dir = os.path.join(base_dir, class_name)
  train_class_dir =  os.path.join(train_dir, class_name)
  fnames = ['{}.jpg'.format(i+1) for i in range(num_train)]
  for fname in fnames:
    src = os.path.join(class_dir, fname)
    dst = os.path.join(train_class_dir, fname)
    if os.path.isfile(src):
      shutil.copyfile(src, dst)

# 검증 데이터를 valid 폴더에 복사
for class_name in class_names:
  class_dir = os.path.join(base_dir, class_name)
  valid_class_dir =  os.path.join(validation_dir, class_name)
  fnames = ['{}.jpg'.format(i+1) for i in range(num_train, num_train+num_valid)]
  for fname in fnames:
    src = os.path.join(class_dir, fname)
    dst = os.path.join(valid_class_dir, fname)
    if os.path.isfile(src):
      shutil.copyfile(src, dst)

# 테스트 데이터를 test 폴더에 복사
for class_name in class_names:
  class_dir = os.path.join(base_dir, class_name)
  test_class_dir =  os.path.join(test_dir, class_name)
  fnames = ['{}.jpg'.format(i+1) for i in range(num_train+num_valid, num_train+num_valid+num_test)]
  for fname in fnames:
    src = os.path.join(class_dir, fname)
    dst = os.path.join(test_class_dir, fname)
    if os.path.isfile(src):
      shutil.copyfile(src, dst)

 


 방법 1. 기본 CNN 모델

[ 0. dataset generator 정의 ]

# 데이터셋 제네레이터 정의
train_datagen = ImageDataGenerator(rescale=1./255)
validation_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=40)
validation_generator = validation_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=40)

test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(150, 150),
        batch_size=40)
        
# 미니 배치 데이터 크기 및 레이블 크기 확인
for data_batch, labels_batch in train_generator:
    print('배치 데이터 크기:', data_batch.shape)
    print('배치 레이블 크기:', labels_batch.shape)
    break

 

 

[ 1. 모델 생성 및 확인 ]

 

* Conv2D Layer : 합성곱 연산 층

* Pooling Layer : 이미지 크기를 줄이는 층. 이미지 왜곡에도 비슷하며 과적합 방지(최대/평균)

# 입력 데이터의 모양이 (150,150,3)과 동일한지, 최종 출력층의 크기가 클래스 개수와 동일한지 확인합니다.

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Conv2D(40, (3, 3), activation='relu', padding='same', input_shape=(150, 150, 3)))
model.add(tf.keras.layers.MaxPooling2D((3, 3)))
model.add(tf.keras.layers.Conv2D(70, (3, 3), activation='relu', padding='same'))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Conv2D(100, (3, 3), activation='relu', padding='same'))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dropout(0.6))
model.add(tf.keras.layers.Dense(100, activation='relu'))
model.add(tf.keras.layers.Dense(4, activation='softmax'))

model.summary()

 

Parameter 의미
(3, 3) 필터의 크기 3*3
padding='same' 패딩의 사이즈는 출력값과 입력값의 크기가 동일하도록
패딩이란? 가장자리 데이터 정보 손실을 막기위해 데이터 주변에 0으로 둘러 싸는 것
stride 필터가 입력 이미지에서 이동하는 칸 수. 
input_shape=(150, 150, 3) 이미지가 150*150 크기인데, 컬러 이미지기 때문에 채널이 "3"개라는 의미

 

 

[ 2. 모델 컴파일 ]

# 팁: loss는 'categorical_crossentropy'를 사용합니다
# (데이터셋 제네레이터는 기본적으로 클래스 종류가 여러개인 상황인 'categorical'로 정의되어 있음)

model.compile(loss='categorical_crossentropy',
              optimizer='Adam',
              metrics=['accuracy'])

 

 

[ 3. 모델 학습 ]

history = model.fit(
      train_generator,
      epochs=15,
      validation_data=validation_generator)

 

 

[ 4. 결과 확인 ]

hist = history.history
x_arr = np.arange(len(hist['loss'])) + 1

fig = plt.figure(figsize=(12, 4))
ax = fig.add_subplot(1, 2, 1)
ax.plot(x_arr, hist['loss'], '-o', label='Train loss')
ax.plot(x_arr, hist['val_loss'], '--<', label='Validation loss')
ax.set_xlabel('Epoch', size=15)
ax.set_ylabel('Loss', size=15)
ax.legend(fontsize=15)
ax = fig.add_subplot(1, 2, 2)
ax.plot(x_arr, hist['accuracy'], '-o', label='Train acc.')
ax.plot(x_arr, hist['val_accuracy'], '--<', label='Validation acc.')
ax.legend(fontsize=15)
ax.set_xlabel('Epoch', size=15)
ax.set_ylabel('Accuracy', size=15)

plt.show()

test_results = model.evaluate(test_generator)
print('\n테스트 정확도 {:.2f}%'.format(test_results[1]*100))

 => 테스트 정확도 75.57%


 

방법 2. 이미지 증식

[ 0. 이미지 전처리 및 dataset generator 정의 ]

from tensorflow.keras.preprocessing.image import ImageDataGenerator

data_aug_gen = ImageDataGenerator(
      rescale=1./255,    # 데이터 0~1사이의 값 갖도록 정규화
      rotation_range=40, # 랜덤하게 사진을 회전시킬 각도 범위(0-180 사이)
      width_shift_range=0.2, # 사진을 수평으로 랜덤하게 평행 이동시킬 범위(전체 넓이에 대한 비율).
      height_shift_range=0.2, # 사진을 수직으로 랜덤하게 평행 이동시킬 범위(전체 높이에 대한 비율).
      shear_range=0.2,   # 사진을 랜덤하게 전단(비틀기)할 각도 범위
      zoom_range=0.2,    # 사진을 랜덤하게 확대할 범위
      horizontal_flip=True,   # 랜덤하게 이미지를 수평으로 반전
      fill_mode='nearest')   # 회전이나 가로/세로 이동으로 인해 새롭게 생성해야 할 픽셀을 채우는 방법.
      
# 데이터 증식하는 data_aug_gen으로 train data augmented generator 정의
train_aug_generator = data_aug_gen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=40,
        class_mode='categorical')

 

 

[ 1. 모델 생성 ]

구조는 기본 모델과 동일하게 생성함

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Conv2D(40, (3, 3), activation='relu', padding='same', input_shape=(150, 150, 3)))
model.add(tf.keras.layers.MaxPooling2D((3, 3)))
model.add(tf.keras.layers.Conv2D(70, (3, 3), activation='relu', padding='same'))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Conv2D(100, (3, 3), activation='relu', padding='same'))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dropout(0.6))
model.add(tf.keras.layers.Dense(100, activation='relu'))
model.add(tf.keras.layers.Dense(4, activation='softmax'))

 

 

 

[ 2. 모델 컴파일 및 학습 ]

model.compile(loss='categorical_crossentropy',
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])
              
history = model.fit(
      train_aug_generator,
      epochs=15,
      validation_data=validation_generator)

 

[ 3. 결과 확인 ]

test_results = model.evaluate(test_generator)
print('\n테스트 정확도 {:.2f}%'.format(test_results[1]*100))

 => 테스트 정확도 77.08%

 


 

방법 3. 트랜스퍼 러닝 (VGG16 모델 사용)

[ 0. VGG 16 모델 불러오기 ]

from tensorflow.keras.applications import VGG16

conv_base = VGG16(weights='imagenet', #모델을 초기화할 가중치 체크포인트를 지정
                  include_top=False, #신경망 최상위의 분류기를 불러올지 여부 결정 (기본은 Imagenet의 1000개 클래스 구분 분류기)
                  input_shape=(150, 150, 3)) #입력 이미지 크기

 

 

[ 1. end to end 모델 생성 ]

end to end : 추가한 모델의 가중치만 업데이트하여 학습

미세조정 end to end : 베이스 모델의 가중치의 일부도 함께 학습

model = tf.keras.models.Sequential()
model.add(conv_base) # 베이스 모델
#분류 모델 추가
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(100, activation='relu', input_dim=4 * 4 * 512))
model.add(tf.keras.layers.Dropout(0.6))
model.add(tf.keras.layers.Dense(4, activation='softmax'))

# VGG 16의 가중치는 train 불가하도록 False로 지정
conv_base.trainable = False

model.summary()

 

 

[ 2. 모델 컴파일 및 학습 ]

model.compile(optimizer=tf.keras.optimizers.Adam(),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(
      train_aug_generator, #학습 데이터 증식 제네레이터
      epochs=15,
      validation_data=validation_generator)

 

 

[ 3. 결과 확인 ]

test_results = model.evaluate(test_generator)
print('\n테스트 정확도 {:.2f}%'.format(test_results[1]*100))

 => 테스트 정확도 83.12%

 

 

[ 4. 미세조정 end to end 모델 설정 ]

model = tf.keras.models.Sequential()
model.add(conv_base) # 베이스 모델
#분류 모델 추가
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(100, activation='relu', input_dim=4 * 4 * 512))
model.add(tf.keras.layers.Dropout(0.6))
model.add(tf.keras.layers.Dense(4, activation='softmax'))

#베이스 모델의 block5(3개의 합성곱층+풀링)만 동결 해제
conv_base.trainable = True

for layer in conv_base.layers:
    if layer.name in ['block5_conv1','block5_conv2','block5_conv3']:
        layer.trainable = True
    else:
        layer.trainable = False

conv_base.summary()
model.summary()

 

 

[ 5. 모델 컴파일 및 학습 ]

model.compile(loss='categorical_crossentropy',
              optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5), # 기존에 학습된 가중치를 과하게 조정하지 않기 위해 학습률을 낮춤 (기본 = 1e-3)
              metrics=['accuracy'])

history = model.fit(
      train_aug_generator, #학습 데이터 증식 제네레이터
      epochs=15,
      validation_data=validation_generator)

 

[ 6. 결과 확인 ]

test_results = model.evaluate(test_generator)
print('\n테스트 정확도 {:.2f}%'.format(test_results[1]*100))

 => 테스트 정확도 87.66%

 

 


 

결론 : 내가 용써서 새로운 모델을 만드는 것 보다 기존 좋은 성능의 모델을 가져다가 쓰는 것이 훨~ 씬 정확도가 높다!

'Python > Deep Learning' 카테고리의 다른 글

[DL] 딥러닝 개요 - 기초 모델  (0) 2021.07.10
Comments