인공지능

Image Data - 꽃 이미지 분류 모델(MobileNetV2)

별은_StarIs_Dev 2025. 2. 9. 21:29
반응형

최종 폴더 구조

 

목적

 - 꽃이미지를 분류하는 모델을 만들어보자(MobileNetV2 사용)

 

1. 필요한 라이브러리 불러오기

import os
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

 

2. 이미지 폴더 생성

# 꽃 종류 리스트
categories = ['daisy', 'dandelion', 'tulips', 'roses', 'sunflowers']
base_dir = 'IMAGE'

# 각 카테고리 폴더 생성
for category in categories:
    os.makedirs(os.path.join(base_dir, category), exist_ok=True)

#(1) IMAGE/Negative 와 IMAGE/Positive에 파일 분류해서 넣기
import shutil

# 이미지 폴더 있는지 확인
if not os.path.exists('IMAGE'):
    os.mkdir('IMAGE')
if not os.path.exists('IMAGE/daisy'):
    os.mkdir('IMAGE/daisy')
if not os.path.exists('IMAGE/dandelion'):
    os.mkdir('IMAGE/dandelion')
if not os.path.exists('IMAGE/tulips'):
    os.mkdir('IMAGE/tulips')
if not os.path.exists('IMAGE/roses'):
    os.mkdir('IMAGE/roses')
if not os.path.exists('IMAGE/sunflowers'):
    os.mkdir('IMAGE/sunflowers')

count=0
for filename in os.listdir("./IMAGE_src/cjk_src"):
    if filename.endswith('.jpg'):
        label=filename.split("_")[-1].split(".")[0]
        if(label=="daisy"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/daisy", filename))
        elif(label=="dandelion"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/dandelion", filename))
        elif(label=="tulips"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/tulips", filename))
        elif(label=="roses"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/roses", filename))
        elif(label=="sunflowers"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/sunflowers", filename))

 

3. 하이퍼파라미터 설정

num_epochs = 4
batch_size = 32
learning_rate = 0.001

 

4. 데이터 전처리 및 증강

# ImageDataGenerator를 사용하여 학습 데이터를 변형 및 증강(Augmentation)
image_datagen = ImageDataGenerator(
    preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,
    validation_split=0.2,       # 20% 데이터를 검증용으로 사용
    rotation_range=20,          # 이미지 회전
    width_shift_range=0.2,      # 가로 이동
    height_shift_range=0.2,     # 세로 이동
    shear_range=0.2,            # 시어 변환
    zoom_range=0.2,             # 확대/축소
    horizontal_flip=True,       # 좌우 반전
    fill_mode='nearest'         # 빈 공간을 주변 색으로 채움
)

# 학습 데이터 로드
train_generator = image_datagen.flow_from_directory(
    base_dir,
    target_size=(224, 224),
    batch_size=batch_size,
    class_mode='categorical',
    subset='training'  # 학습 데이터
)

# 검증 데이터 로드
val_generator = image_datagen.flow_from_directory(
    base_dir,
    target_size=(224, 224),
    batch_size=batch_size,
    class_mode='categorical',
    subset='validation'  # 검증 데이터
)

 

5. MobileNetV2 모델 불러오기;

# 사전 훈련된 MobileNetV2 모델 사용
base_model = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
base_model.trainable = False  # 사전 훈련된 가중치는 그대로 유지

 

6. 사용자 정의 레이어 추가

x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)  # 평균 풀링
output = tf.keras.layers.Dense(len(categories), activation='softmax')(x)  # 꽃 5종 분류

# 최종 모델 구성
model = tf.keras.Model(inputs=base_model.input, outputs=output)

 

7. 모델 컴파일

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

 

8. 콜백 설정

# EarlyStopping (과적합 방지)
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=30)

# 모델 가중치 저장
checkpoint = ModelCheckpoint(filepath="my_checkpoint.weights.h5",
                             save_weights_only=True,
                             save_best_only=True,
                             monitor='val_loss',
                             verbose=1)

# 학습률 감소 (성능 개선이 없을 경우)
lrReducer = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=0.0001)

# 콜백 리스트
callbacks = [es, checkpoint, lrReducer]

 

9. 모델 학습

history = model.fit(
    train_generator,
    validation_data=val_generator,
    epochs=num_epochs,
    callbacks=callbacks
)

 

10. 학습 결과 시각화

plt.plot(history.history['accuracy'], label='Train Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Model Accuracy')
plt.show()

 

11. 모델 저장 및 불러오기

# 모델 저장
model.save('final_model.h5')

# 모델 불러오기
loaded_model = tf.keras.models.load_model('final_model.h5')

 

12. 테스트 데이터 준비

# 정답 데이터 생성
target=[]
label=[]
result=[]
for item in os.listdir("./IMAGE_src/cjk_test"):
    target.append(item.split(".")[0])
    if(item.split("_")[-1].split(".")[0]=="daisy"):
        label.append('daisy')
    elif(item.split("_")[-1].split(".")[0]=="dandelion"):
        label.append('dandelion')
    elif(item.split("_")[-1].split(".")[0]=="roses"):
        label.append('roses')
    elif(item.split("_")[-1].split(".")[0]=="sunflowers"):
        label.append('sunflowers')
    elif(item.split("_")[-1].split(".")[0]=="tulips"):
        label.append('tulips')
    result.append("")

df = pd.DataFrame({"target":target, "label":label, "result":result})
df.to_csv("test.csv")
final=pd.read_csv("test.csv")

 

13. 모델 예측 및 평가

# 클래스 매핑
answer_class = {0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}

right = 0
total = 0

# 테스트 이미지 개별 예측
for image, label in zip(final['target'], final['label']):
    total += 1
    test_image_path = f"./IMAGE_src/cjk_test/{image}.jpg"
    
    # 이미지 불러오기
    img = load_img(test_image_path, target_size=(224, 224))
    img_array = img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array)
    
    # 예측
    predictions = loaded_model.predict(img_array)
    predicted_class = np.argmax(predictions, axis=1)[0]
    pred_label = answer_class[predicted_class]

    # 정답 비교
    if label == pred_label:
        right += 1

# 최종 점수 출력
print(f"점수는: {right / total * 100:.2f}점")

 

 

 

최종코드

import os
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

# 꽃 종류 리스트
categories = ['daisy', 'dandelion', 'tulips', 'roses', 'sunflowers']
base_dir = 'IMAGE'

# 각 카테고리 폴더 생성
for category in categories:
    os.makedirs(os.path.join(base_dir, category), exist_ok=True)

#(1) IMAGE/Negative 와 IMAGE/Positive에 파일 분류해서 넣기
import shutil

# 이미지 폴더 있는지 확인
if not os.path.exists('IMAGE'):
    os.mkdir('IMAGE')
if not os.path.exists('IMAGE/daisy'):
    os.mkdir('IMAGE/daisy')
if not os.path.exists('IMAGE/dandelion'):
    os.mkdir('IMAGE/dandelion')
if not os.path.exists('IMAGE/tulips'):
    os.mkdir('IMAGE/tulips')
if not os.path.exists('IMAGE/roses'):
    os.mkdir('IMAGE/roses')
if not os.path.exists('IMAGE/sunflowers'):
    os.mkdir('IMAGE/sunflowers')

count=0
for filename in os.listdir("./IMAGE_src/cjk_src"):
    if filename.endswith('.jpg'):
        label=filename.split("_")[-1].split(".")[0]
        if(label=="daisy"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/daisy", filename))
        elif(label=="dandelion"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/dandelion", filename))
        elif(label=="tulips"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/tulips", filename))
        elif(label=="roses"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/roses", filename))
        elif(label=="sunflowers"):
          shutil.copy(os.path.join("./IMAGE_src/cjk_src", filename), os.path.join("IMAGE/sunflowers", filename))
num_epochs = 4
batch_size = 32
learning_rate = 0.001

# ImageDataGenerator를 사용하여 학습 데이터를 변형 및 증강(Augmentation)
image_datagen = ImageDataGenerator(
    preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,
    validation_split=0.2,       # 20% 데이터를 검증용으로 사용
    rotation_range=20,          # 이미지 회전
    width_shift_range=0.2,      # 가로 이동
    height_shift_range=0.2,     # 세로 이동
    shear_range=0.2,            # 시어 변환
    zoom_range=0.2,             # 확대/축소
    horizontal_flip=True,       # 좌우 반전
    fill_mode='nearest'         # 빈 공간을 주변 색으로 채움
)

# 학습 데이터 로드
train_generator = image_datagen.flow_from_directory(
    base_dir,
    target_size=(224, 224),
    batch_size=batch_size,
    class_mode='categorical',
    subset='training'  # 학습 데이터
)

# 검증 데이터 로드
val_generator = image_datagen.flow_from_directory(
    base_dir,
    target_size=(224, 224),
    batch_size=batch_size,
    class_mode='categorical',
    subset='validation'  # 검증 데이터
)

# 사전 훈련된 MobileNetV2 모델 사용
base_model = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
base_model.trainable = False  # 사전 훈련된 가중치는 그대로 유지

x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)  # 평균 풀링
output = tf.keras.layers.Dense(len(categories), activation='softmax')(x)  # 꽃 5종 분류

# 최종 모델 구성
model = tf.keras.Model(inputs=base_model.input, outputs=output)

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


# EarlyStopping (과적합 방지)
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=30)

# 모델 가중치 저장
checkpoint = ModelCheckpoint(filepath="my_checkpoint.weights.h5",
                             save_weights_only=True,
                             save_best_only=True,
                             monitor='val_loss',
                             verbose=1)

# 학습률 감소 (성능 개선이 없을 경우)
lrReducer = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=0.0001)

# 콜백 리스트
callbacks = [es, checkpoint, lrReducer]

history = model.fit(
    train_generator,
    validation_data=val_generator,
    epochs=num_epochs,
    callbacks=callbacks
)

plt.plot(history.history['accuracy'], label='Train Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Model Accuracy')
plt.show()

# 모델 저장
model.save('final_model.h5')

# 모델 불러오기
loaded_model = tf.keras.models.load_model('final_model.h5')

# 정답 데이터 생성
target=[]
label=[]
result=[]
for item in os.listdir("./IMAGE_src/cjk_test"):
    target.append(item.split(".")[0])
    if(item.split("_")[-1].split(".")[0]=="daisy"):
        label.append('daisy')
    elif(item.split("_")[-1].split(".")[0]=="dandelion"):
        label.append('dandelion')
    elif(item.split("_")[-1].split(".")[0]=="roses"):
        label.append('roses')
    elif(item.split("_")[-1].split(".")[0]=="sunflowers"):
        label.append('sunflowers')
    elif(item.split("_")[-1].split(".")[0]=="tulips"):
        label.append('tulips')
    result.append("")

df = pd.DataFrame({"target":target, "label":label, "result":result})
df.to_csv("test.csv")
final=pd.read_csv("test.csv")

# 클래스 매핑
answer_class = {0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}

right = 0
total = 0

# 테스트 이미지 개별 예측
for image, label in zip(final['target'], final['label']):
    total += 1
    test_image_path = f"./IMAGE_src/cjk_test/{image}.jpg"
    
    # 이미지 불러오기
    img = load_img(test_image_path, target_size=(224, 224))
    img_array = img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array)
    
    # 예측
    predictions = loaded_model.predict(img_array)
    predicted_class = np.argmax(predictions, axis=1)[0]
    pred_label = answer_class[predicted_class]

    # 정답 비교
    if label == pred_label:
        right += 1

# 최종 점수 출력
print(f"점수는: {right / total * 100:.2f}점")
반응형