반응형

Keras에서 이미지 데이터셋을 불러오는 두가지 방법인  flow_from_directory와 flow_from_dataframe에 대한 간단한 예제코드입니다.



2022. 4. 30  최초작성

2024. 4. 21




데이터셋으로 다음 링크에 있는 것을 사용했습니다.

https://www.microsoft.com/en-us/download/details.aspx?id=54765



소스코드가 있는 폴더에 다음처럼 데이터셋이 위치하는 상태에서 코드를 실행해야 합니다.



이미지를 불러오는 두가지 방식을 테스트로 동작시켜보는 코드이기 때문에 CNN의 레이어는 단순하게 되어있고 에포크는 20번으로 되어있습니다.



데이터셋을 사용시 다음 에러가 발생하기 때문에 이 데이터셋을 사용하기 전에 오류가 있는 이미지 파일을 삭제합니다.

 

PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x17c6d8540>



다음 코드를 사용하여 오류있는 이미지를 제거합니다.

 

from PIL import Image
import PIL
import os

def load_and_delete_invalid_images(root_directory):
    for dirpath, _, filenames in os.walk(root_directory):
        for filename in filenames:
            file_path = os.path.join(dirpath, filename)
            try:
                with Image.open(file_path) as img:
                    img.verify()  # 이미지 파일이 유효한지 확인
            except PIL.UnidentifiedImageError:
                print(f"Invalid image file detected and deleted: {file_path}")
                os.remove(file_path)  # 파일 삭제


root_directory = './PetImages'
load_and_delete_invalid_images(root_directory)




실행 결과입니다.

 

 % /Users/webnautes/miniforge3/envs/tensorflow-dev/bin/python /Users/webnautes/Python_Example/check.py

Invalid image file detected and deleted: ./PetImages/.DS_Store

Invalid image file detected and deleted: ./PetImages/Cat/Thumbs.db

Invalid image file detected and deleted: ./PetImages/Cat/666.jpg

/Users/webnautes/miniforge3/envs/tensorflow-dev/lib/python3.10/site-packages/PIL/TiffImagePlugin.py:870: UserWarning: Truncated File Read

  warnings.warn(str(msg))

Invalid image file detected and deleted: ./PetImages/Dog/Thumbs.db

Invalid image file detected and deleted: ./PetImages/Dog/11702.jpg





이제 이미지 데이터를 불러오는 다음 두 가지 메소드에 대해 다룹니다.

 

flow_from_directory

flow_from_dataframe



flow_from_directory

 

flow_from_directory를 사용하면 지정한 디렉토리에 있는 서브 디렉토리 이름으로 클래스를 구분하여 이미지를 가져옵니다.

 

import tensorflow as tf
from tensorflow.python.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt



input_shape = (64, 64, 3)
image_size = (64, 64)
batch_size = 32


# Generator를 사용하여 특정 디렉토리에 있는 이미지 데이터를 가져옵니다.
train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
                                                                    # 전체 데이터의 20%를 검증 데이터로 설정합니다.
                                                                    validation_split=0.2
                                                                )

train_generator = train_datagen.flow_from_directory(
    "PetImages",             # 이미지 데이터셋은 이 경로에 디렉토리로 구분되어 저장되어 있습니다.
    color_mode="rgb",        # 이미지를 rgb 포맷으로 불러옴
    target_size=image_size,  # 이미지 크기
    batch_size=batch_size,   # batch_size 단위로 데이터를 가져옴
    class_mode='binary',     # 2개의 클래스가 있음
    subset='training')       # train 데이터셋

validation_generator = train_datagen.flow_from_directory(
    "PetImages",
    color_mode="rgb",
    target_size=image_size,
    batch_size=batch_size,
    class_mode='binary',
    subset='validation')


# CNN 모델을 구성합니다.
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(256 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu' , input_shape = input_shape))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(64, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Conv2D(128 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu' ))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(64, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(32, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(units = 64, activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.1))
model.add(tf.keras.layers.Dense(units = 2, activation = 'softmax'))
model.compile(optimizer = 'adam' , loss = 'categorical_crossentropy' , metrics = ['accuracy'])
model.summary()
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 검증 손실(val_loss)이 개선되지 않을 때 학습률을 동적으로 조정하여 모델의 학습을 개선합니다.
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=1, min_delta=0.0001)
# 검증 손실이 특정 에포크 동안 개선되지 않을 경우 조기에 훈련을 중단하고, 가장 좋은 가중치를 복원합니다.
early_stop= EarlyStopping(monitor = "val_loss", mode="min", min_delta=0.0001, restore_best_weights=True,
                            patience=10, verbose=1)

# 모델을 학습합니다.
history=model.fit(train_generator,  # train 데이터셋
          epochs=20,   # 전체 데이터셋을 몇번 학습에 사용할지 지정
          steps_per_epoch=train_generator.samples//batch_size, 
          validation_data=validation_generator, # validation 데이터셋
          validation_steps=validation_generator.samples//batch_size,
          callbacks = [early_stop, reduce_lr]) # 콜백 함수 지정


# 학습 히스토리(accuracy/loss)
fig, axs = plt.subplots(1,2,figsize=(12,5))
axs[0].plot(history.history["loss"], label="Training Loss")
axs[0].plot(history.history["val_loss"], label="Validation Loss")
axs[0].legend()
axs[1].plot(history.history["accuracy"], label="Training Accuracy")
axs[1].plot(history.history["val_accuracy"], label="Validation Accuracy")
axs[1].legend()
plt.suptitle("dog and cat")
plt.show()


# 검증 데이터셋을 사용하여 최종 정확도 계산하여 출력
loss,acc = model.evaluate(validation_generator)
print(f'Accuracy : {acc}')



학습 실행 결과입니다.

 

Epoch 1/20

625/625 [==============================] - 66s 103ms/step - loss: 0.8353 - accuracy: 0.6199 - val_loss: 0.6844 - val_accuracy: 0.5811 - lr: 0.0010

Epoch 2/20

625/625 [==============================] - 64s 103ms/step - loss: 0.5995 - accuracy: 0.7059 - val_loss: 0.5648 - val_accuracy: 0.7013 - lr: 0.0010

Epoch 3/20

625/625 [==============================] - 64s 103ms/step - loss: 0.5158 - accuracy: 0.7544 - val_loss: 0.5362 - val_accuracy: 0.7250 - lr: 0.0010

Epoch 4/20

625/625 [==============================] - 64s 103ms/step - loss: 0.4467 - accuracy: 0.7961 - val_loss: 0.5104 - val_accuracy: 0.7526 - lr: 0.0010

Epoch 5/20

625/625 [==============================] - 64s 102ms/step - loss: 0.4058 - accuracy: 0.8163 - val_loss: 0.5163 - val_accuracy: 0.7462 - lr: 0.0010

Epoch 6/20

625/625 [==============================] - 64s 102ms/step - loss: 0.3255 - accuracy: 0.8584 - val_loss: 0.3674 - val_accuracy: 0.8317 - lr: 1.0000e-04

Epoch 7/20

625/625 [==============================] - 64s 102ms/step - loss: 0.3022 - accuracy: 0.8681 - val_loss: 0.3584 - val_accuracy: 0.8395 - lr: 1.0000e-04

Epoch 8/20

625/625 [==============================] - 64s 102ms/step - loss: 0.2848 - accuracy: 0.8759 - val_loss: 0.3580 - val_accuracy: 0.8427 - lr: 1.0000e-04

Epoch 9/20

625/625 [==============================] - 65s 104ms/step - loss: 0.2780 - accuracy: 0.8820 - val_loss: 0.3369 - val_accuracy: 0.8568 - lr: 1.0000e-04

Epoch 10/20

625/625 [==============================] - 66s 106ms/step - loss: 0.2686 - accuracy: 0.8852 - val_loss: 0.3356 - val_accuracy: 0.8572 - lr: 1.0000e-04

Epoch 11/20

625/625 [==============================] - 66s 106ms/step - loss: 0.2620 - accuracy: 0.8888 - val_loss: 0.3286 - val_accuracy: 0.8634 - lr: 1.0000e-04

Epoch 12/20

625/625 [==============================] - 66s 105ms/step - loss: 0.2574 - accuracy: 0.8914 - val_loss: 0.3289 - val_accuracy: 0.8582 - lr: 1.0000e-04

Epoch 13/20

625/625 [==============================] - 65s 104ms/step - loss: 0.2415 - accuracy: 0.8993 - val_loss: 0.3262 - val_accuracy: 0.8590 - lr: 1.0000e-05

Epoch 14/20

625/625 [==============================] - 65s 104ms/step - loss: 0.2378 - accuracy: 0.9004 - val_loss: 0.3281 - val_accuracy: 0.8562 - lr: 1.0000e-05

Epoch 15/20

625/625 [==============================] - 67s 106ms/step - loss: 0.2369 - accuracy: 0.9010 - val_loss: 0.3263 - val_accuracy: 0.8594 - lr: 1.0000e-06

Epoch 16/20

625/625 [==============================] - 68s 108ms/step - loss: 0.2362 - accuracy: 0.9036 - val_loss: 0.3251 - val_accuracy: 0.8584 - lr: 1.0000e-07

Epoch 17/20

625/625 [==============================] - 66s 106ms/step - loss: 0.2348 - accuracy: 0.9021 - val_loss: 0.3259 - val_accuracy: 0.8584 - lr: 1.0000e-07

Epoch 18/20

625/625 [==============================] - 66s 105ms/step - loss: 0.2381 - accuracy: 0.9001 - val_loss: 0.3257 - val_accuracy: 0.8594 - lr: 1.0000e-08

Epoch 19/20

625/625 [==============================] - 66s 105ms/step - loss: 0.2343 - accuracy: 0.8979 - val_loss: 0.3249 - val_accuracy: 0.8602 - lr: 1.0000e-09

Epoch 20/20

625/625 [==============================] - 66s 105ms/step - loss: 0.2375 - accuracy: 0.9011 - val_loss: 0.3263 - val_accuracy: 0.8592 - lr: 1.0000e-09

157/157 [==============================] - 7s 43ms/step - loss: 0.3266 - accuracy: 0.8587

Accuracy : 0.8587434887886047






flow_from_dataframe

 

flow_from_dataframe는 이미지 파일 경로와 라벨을 데이터프레임에 저장하여 사용합니다.

 

import tensorflow as tf
from tensorflow.python.keras.callbacks import EarlyStopping, ReduceLROnPlateau
import pandas as pd
import os
import matplotlib.pyplot as plt



# 이미지 경로와 라벨을 데이터프레임에 저장합니다.
# csv 파일에 기록한 것을 로드하여 사용해도 됩니다.
data = []

for root, subdirs, files in os.walk('./PetImages'):
   
    for d in subdirs:
        label = d
        fullpath = root + '/' + d

    if len(files) > 0:
        for f in files:
            # 디렉토리 이름을 라벨로 사용합니다.
            label = root.split('/')[-1]
            data.append([root + '/' + f, label])

df = pd.DataFrame(data=data, columns=['files', 'label'])
print(df)


input_shape = (64, 64, 3)
image_size = (64, 64)
batch_size = 32


# Generator를 사용하여 특정 디렉토리에 있는 이미지 데이터를 가져옵니다.
train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
                                                                    # 전체 데이터의 20%를 검증 데이터로 설정합니다.
                                                                    validation_split=0.2
                                                                )


train_generator = train_datagen.flow_from_dataframe(dataframe=df,
                                              directory='.', # df_train에 있는 이미지 파일 경로의 root 디렉토리를 적으면 됩니다. 현재 폴더에 위치하므로 .을 사용합니다.
                                              x_col="files", # 데이터프레임에서 이미지 파일 경로가 저장된 컬럼 이름입니다.
                                              y_col="label", # 데이터프레임에서 라벨이 저장된 컬럼 이름입니다.
                                              class_mode="binary",    #  2개의 클래스가 있음
                                              target_size=image_size, # 이미지 크기
                                              batch_size=batch_size, # batch_size 단위로 데이터를 가져옴
                                              rescale=1.0/255,       # 이미지의 픽셀값을 정규화함
                                              seed=2024)             # 랜덤시드

validation_generator = train_datagen.flow_from_dataframe(dataframe=df,
                                              directory='.', # df에 있는 이미지 파일 경로의 root 디렉토리를 적으면 됩니다. 현재 폴더에 위치하므로 .을 사용합니다.
                                              x_col="files",
                                              y_col="label",
                                              class_mode="binary",
                                              target_size=image_size,
                                              batch_size=batch_size,
                                              rescale=1.0/255,
                                              seed=2024)

model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(256 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu' , input_shape = input_shape))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(64, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Conv2D(128 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu' ))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(64, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Conv2D(32, (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(units = 64, activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.1))
model.add(tf.keras.layers.Dense(units = 2, activation = 'softmax'))
model.compile(optimizer = 'adam' , loss = 'categorical_crossentropy' , metrics = ['accuracy'])
model.summary()
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 검증 손실(val_loss)이 개선되지 않을 때 학습률을 동적으로 조정하여 모델의 학습을 개선합니다.
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=1, min_delta=0.0001)
# 검증 손실이 특정 에포크 동안 개선되지 않을 경우 조기에 훈련을 중단하고, 가장 좋은 가중치를 복원합니다.
early_stop= EarlyStopping(monitor = "val_loss", mode="min", min_delta=0.0001, restore_best_weights=True,
                            patience=10, verbose=1)

history=model.fit(train_generator,  # train 데이터셋
          epochs=20,    # 전체 데이터셋을 몇번 학습에 사용할지 지정
          steps_per_epoch=train_generator.n//train_generator.batch_size,
          validation_data=validation_generator,  # validation 데이터셋
          validation_steps=validation_generator.n//validation_generator.batch_size,
          callbacks = [early_stop, reduce_lr]) # 콜백 함수 지정


# 학습 히스토리(accuracy/loss)
fig, axs = plt.subplots(1,2,figsize=(12,5))
axs[0].plot(history.history["loss"], label="Training Loss")
axs[0].plot(history.history["val_loss"], label="Validation Loss")
axs[0].legend()
axs[1].plot(history.history["accuracy"], label="Training Accuracy")
axs[1].plot(history.history["val_accuracy"], label="Validation Accuracy")
axs[1].legend()
plt.suptitle("dog and cat")
plt.show()


# 검증 데이터셋을 사용하여 최종 정확도 계산하여 출력
loss,acc = model.evaluate(validation_generator)
print(f'Accuracy : {acc}')





실행결과입니다.

 

Epoch 1/20

781/781 [==============================] - 110s 140ms/step - loss: 0.8120 - accuracy: 0.6206 - val_loss: 0.7061 - val_accuracy: 0.5659 - lr: 0.0010

Epoch 2/20

781/781 [==============================] - 104s 133ms/step - loss: 0.6482 - accuracy: 0.6749 - val_loss: 0.6020 - val_accuracy: 0.6648 - lr: 0.0010

Epoch 3/20

781/781 [==============================] - 104s 133ms/step - loss: 0.5964 - accuracy: 0.7046 - val_loss: 0.6558 - val_accuracy: 0.6077 - lr: 0.0010

Epoch 4/20

781/781 [==============================] - 104s 133ms/step - loss: 0.5187 - accuracy: 0.7451 - val_loss: 0.5329 - val_accuracy: 0.7248 - lr: 1.0000e-04

Epoch 5/20

781/781 [==============================] - 107s 136ms/step - loss: 0.4926 - accuracy: 0.7607 - val_loss: 0.4968 - val_accuracy: 0.7596 - lr: 1.0000e-04

Epoch 6/20

781/781 [==============================] - 108s 138ms/step - loss: 0.4789 - accuracy: 0.7696 - val_loss: 0.4728 - val_accuracy: 0.7756 - lr: 1.0000e-04

Epoch 7/20

781/781 [==============================] - 106s 135ms/step - loss: 0.4709 - accuracy: 0.7770 - val_loss: 0.4934 - val_accuracy: 0.7542 - lr: 1.0000e-04

Epoch 8/20

781/781 [==============================] - 102s 131ms/step - loss: 0.4529 - accuracy: 0.7850 - val_loss: 0.4703 - val_accuracy: 0.7761 - lr: 1.0000e-05

Epoch 9/20

781/781 [==============================] - 103s 131ms/step - loss: 0.4506 - accuracy: 0.7882 - val_loss: 0.4833 - val_accuracy: 0.7644 - lr: 1.0000e-05

Epoch 10/20

781/781 [==============================] - 102s 131ms/step - loss: 0.4499 - accuracy: 0.7878 - val_loss: 0.4744 - val_accuracy: 0.7726 - lr: 1.0000e-06

Epoch 11/20

781/781 [==============================] - 102s 131ms/step - loss: 0.4494 - accuracy: 0.7877 - val_loss: 0.4745 - val_accuracy: 0.7722 - lr: 1.0000e-07

Epoch 12/20

781/781 [==============================] - 102s 131ms/step - loss: 0.4482 - accuracy: 0.7907 - val_loss: 0.4768 - val_accuracy: 0.7701 - lr: 1.0000e-08

Epoch 13/20

781/781 [==============================] - 103s 131ms/step - loss: 0.4510 - accuracy: 0.7876 - val_loss: 0.4754 - val_accuracy: 0.7716 - lr: 1.0000e-09

Epoch 14/20

781/781 [==============================] - 103s 132ms/step - loss: 0.4488 - accuracy: 0.7886 - val_loss: 0.4754 - val_accuracy: 0.7713 - lr: 1.0000e-10

Epoch 15/20

781/781 [==============================] - 103s 132ms/step - loss: 0.4444 - accuracy: 0.7918 - val_loss: 0.4753 - val_accuracy: 0.7715 - lr: 1.0000e-11

Epoch 16/20

781/781 [==============================] - 103s 132ms/step - loss: 0.4479 - accuracy: 0.7910 - val_loss: 0.4753 - val_accuracy: 0.7715 - lr: 1.0000e-12

Epoch 17/20

781/781 [==============================] - 103s 132ms/step - loss: 0.4517 - accuracy: 0.7879 - val_loss: 0.4748 - val_accuracy: 0.7723 - lr: 1.0000e-13

Epoch 18/20

781/781 [==============================] - ETA: 0s - loss: 0.4502 - accuracy: 0.7873Restoring model weights from the end of the best epoch.

781/781 [==============================] - 103s 132ms/step - loss: 0.4502 - accuracy: 0.7873 - val_loss: 0.4752 - val_accuracy: 0.7714 - lr: 1.0000e-14

Epoch 00018: early stopping

782/782 [==============================] - 37s 47ms/step - loss: 0.4703 - accuracy: 0.7761

Accuracy : 0.7761421203613281

 

반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts