2022년 8월 6일 토요일

tensorflow keras / 영상 분류, 영상 불량 검출, 개 고양이 분류 image classification


들어가는 글

Pytorch보다는 Keras가 대중적으로 많이 사용하고 있어서 Pytorch 아래 예제를 Keras 를 이용해서 변환하는 작업을 진행하였습니다. 

https://swlock.blogspot.com/2022/07/machine-learning-pytorch.html

작업을 하면서 Keras에는 이미 ImageDataGenerator 가 따로 있는데 이것을 이용하지 않고 CustomDataLoader를 만들어 사용하다 보니 막히는 곳이 많아서 구현에 어려움이 있었습니다.

(tf.keras.preprocessing.image 모듈의 ImageDataGenerator 클래스가 준비되어 있으며 예제가 많으니 쉽게 응용이 가능할 겁니다. 여기서는 해당 클래스를 사용하지 않았습니다.)

여기에서 하는 동작은 개 이미지와 고양이 이미지를 미리 훈련해서 주어지는 이미지를 보고 고양이인지 개인지 맞추는 것입니다. 즉 불량과 양품 이미지를 학습하고 결과를 출력하는 것과 비슷하다고 생각 할 수 있습니다.

참고로 여기에서는 Regression에서 많이 사용하는 MSE loss함수를 사용하였습니다. loss 함수가 개 고양이 분류하는데 적절하지 않을 수는 있긴 하지만 그건 읽는 분 몫으로 남겨둡니다. https://keras.io/api/losses/


개 고양이 분류

캐글에서 있던 부분이고 좀 더 자세한 내용은 아래 링크를 참고 하시면 됩니다.

https://www.kaggle.com/competitions/dogs-vs-cats

여기에서 사용된 자료는 아래 링크로부터 받았습니다.

https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip

위 링크에서 직접 받을 필요는 없고 다음 python 스크립트로 자동으로 받아옵니다.


훈련 데이터 이미지 가져오기

zip파일을 다운로드 받고 폴더에 압축을 풉니다. 실행시켜도 폴더가 존재하거나 하면 다시 다운로드 하지 않습니다.

from requests import get
from os.path import exists
import zipfile


def download(url, file_name=None):
	if not file_name:
		file_name = url.split('/')[-1]

	with open(file_name, "wb") as file:
		response = get(url)
		file.write(response.content)


if __name__ == '__main__':
	zip_folder = r"cats_and_dogs_filtered"
	url = "https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip"

	if not exists(zip_folder+".zip"):
		print("download data")
		download(url)

	if not exists(zip_folder):
		print("unzip")
		zip_ref = zipfile.ZipFile(zip_folder+".zip", 'r')
		zip_ref.extractall()
		zip_ref.close()


배치(batch)와 데이터 로딩

데이터를 읽어오는 부분은 이전 포스팅을 조금 참조 부탁드립니다.

배치란 물건을 놓는것을 말하는건 아니고 일괄 작업의 단위를 말하는 것입니다.

이미지는 아래 폴더에 존재합니다.

        cats_and_dogs_filtered/train/cats
        cats_and_dogs_filtered/train/dogs
        cats_and_dogs_filtered/validation/cats
        cats_and_dogs_filtered/validation/dogs

훈련이나 검증이냐에 따라서 base_path를 지정해서 class를 호출해 줄겁니다.

즉 cats_and_dogs_filtered/train , cats_and_dogs_filtered/validation 여기까지는 호출하는 쪽에서 경로를 알주고 cats, dogs라는 경로를 붙여 파일 목록을 구해냅니다.

데이터를 넘겨야 하는 __getitem__ 에서는 cats+dogs 이미지가 포함되어 있으므로 하위순위에서는 cats 이미지를 상위 숫자에서는 dogs 이미지를 넘기도록 합니다. 목표값도 개는 0,1을 고양이는 1,0을 리턴 하도록 구현 하였습니다.

def __getitem__(self, index):
        if index >= len(self.cat_fnames):
            # dog
        else:
            # cat

transform 처리가 있는데, 학습을 위해서 이미지 그대로 학습을 할 수 없습니다. 이미지를 Tensor로 변환하는 작업도 해야하고 또 다른 중요한 이유는 학습 데이터를 늘리기 위해 이미지에 변형을 하는 작업을 합니다.

아래 부분이 transform 부분입니다. 이미지를 array로 변환하고 255로 나누어 주는 부분이 있는데 

        if self.transform:
            image = tf.keras.preprocessing.image.img_to_array(image)
            image = image.astype('float32')
            image /= 255.0
            image = self.transform(image)

이 부분을 설명하자면 이미지는 일반적으로 R,G,B 3개의 채널을 가지고 있고 하나의 채널에 대해서 0~255 까지 1byte의 숫자로 표시됩니다. 따라서 1개의 채널에 대해서 255로 나누어 주면 값 범위가 0~1 사이의 숫자로 표현됩니다. 학습에 있어서 도움이 되기 때문에 이렇게 구현하였습니다. 

아래는 인자로 넘겨준 부분에 의해서 이미지 변형이 일어나는 구간입니다.

image = self.transform(image)

호출 쪽에서는 다음과 같은 형태로 호출하게 되는데

    train_dataset = CustomDataset("cats_and_dogs_filtered/train", transform=train_data_augmentation)
    val_dataset = CustomDataset("cats_and_dogs_filtered/validation", transform=val_processing)

이 부분은 tf.keras.Sequential 을 이용해서 구현하였습니다. 

val_processing 부분과 train_data_augmentation 두개로 분리해서 검증용 일때는 random으로 이미지 변형이 되지 않도록 하였습니다.

val_processing = tf.keras.Sequential([
    layers.Resizing(IMG_SIZE, IMG_SIZE),
    layers.CenterCrop(IMG_SIZE, IMG_SIZE),
    layers.Normalization(mean=[0.485, 0.456, 0.406], variance=[0.229, 0.224, 0.225]),
])

train_data_augmentation = tf.keras.Sequential([
    layers.RandomFlip("horizontal_and_vertical"),
    val_processing,
])


다음은 CustomDataset 의 전체 소스입니다.

class CustomDataset:
    def __init__(self, base_path, transform=None, target_transform=None, base_idx=None, cnt=None, file_name=None):
        self.file_name = None
        self.base_idx = base_idx
        self.cnt = cnt
        self.base_dir = base_path
        self.transform = transform
        self.cat_fnames = []
        self.dog_fnames = []
        self.target_transform = target_transform
        # 훈련에 사용되는 고양이/개 이미지 경로
        self.cats_dir = os.path.join(self.base_dir, 'cats')
        self.dogs_dir = os.path.join(self.base_dir, 'dogs')
        if file_name is not None:
            self.file_name = file_name
            self.total_len = 1
            return
        '''
        cats_and_dogs_filtered/train/cats
        cats_and_dogs_filtered/train/dogs
        cats_and_dogs_filtered/validation/cats
        cats_and_dogs_filtered/validation/dogs
        '''
        # os.listdir() 경로 내에 있는 파일의 이름을 리스트의 형태로 반환합니다.
        # [...'cat.102.jpg', 'cat.103.jpg', .... ]
        self.cat_fnames = os.listdir(self.cats_dir)
        self.dog_fnames = os.listdir(self.dogs_dir)
        if self.base_idx is None:
            self.total_len = len(self.cat_fnames) + len(self.dog_fnames)
        else:
            self.total_len = cnt

        print(f"total images:{self.total_len}")

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        cat[xxx]
        cat[xxx]
        dog[xxx]
        dog[xxx]
        """
        if self.base_idx is not None:
            index = index + self.base_idx

        if self.file_name is not None:
            y_data = np.array([0.0, 1.0])
            image = Image.open(self.file_name)
        elif index >= len(self.cat_fnames):
            # dog
            x_data = self.dog_fnames[index - len(self.cat_fnames)]
            y_data = np.array([0.0, 1.0])
            img_path = os.path.join(self.dogs_dir, x_data)
            image = Image.open(img_path)
            # plt.imshow(image)
            # print(y_data)
            # plt.show()
        else:
            # cat
            x_data = self.cat_fnames[index]
            y_data = np.array([1.0, 0.0])
            img_path = os.path.join(self.cats_dir, x_data)
            image = Image.open(img_path)
            # plt.imshow(image)
            # print(y_data)
            # plt.show()

        if self.transform:
            image = tf.keras.preprocessing.image.img_to_array(image)
            image = image.astype('float32')
            image /= 255.0
            image = self.transform(image)
            # plt.imshow(image)
            # plt.show()

        if self.target_transform:
            y_data = self.target_transform(y_data)
        return image, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


class CustomDataloader(Sequence):
    def __init__(self, _dataset, batch_size=1, shuffle=False):
        self.dataset = _dataset
        self.batch_size = batch_size
        self.total_len = math.ceil(len(self.dataset) / self.batch_size)
        self.shuffle = shuffle
        self.indexer = np.arange(len(self.dataset))
        self.on_epoch_end()

    def __getitem__(self, index):
        indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size]
        batch_x = [self.dataset[i][0] for i in indexer]
        batch_y = [self.dataset[i][1] for i in indexer]
        batch_x = np.array(batch_x)
        batch_y = np.array(batch_y)
        return batch_x, batch_y

    def __len__(self):
        return self.total_len

    def on_epoch_end(self):
        if self.shuffle:
            np.random.shuffle(self.indexer)


이미지 모델

이미지 모델은 keras.Sequential 을 이용해서 만들었습니다. 

Conv2D이 핵심이 되며 구조는 Pytorch때와 동일하게 구현하였습니다.

    # https://keras.io/api/layers/
    model = keras.Sequential()
    model.add(keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3)))
    model.add(layers.Conv2D(16, kernel_size=3, activation='relu'))
    model.add(layers.MaxPooling2D(pool_size=(8, 8)))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(2, activation='relu'))
    model.summary()

이미지 처리에 있어서 Conv2D 이걸 빼면 학습이 잘 되지 않습니다.


LOSS함수와 optim

Loss함수도 많긴 하지만 클래스 분류에는 일반적으로 BCELoss 나 CrossEntropyLoss 함수를 많이 사용하는데 꼭 해당 함수를 사용해야 하는것은 아닙니다. 여기에서는 이전 기초에서 했던것도 있어서 단순하게 출력단을 2가지 0,1 을 목표값으로 설정해서 진행했습니다. 

    # https://keras.io/api/optimizers/
    # https://keras.io/api/losses/
    INPUT_OPTIMIZER = tf.keras.optimizers.SGD(learning_rate=lr)
    LOSS = tf.keras.losses.MeanSquaredError(reduction="auto", name="mean_squared_error")
    model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS)
    model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader)


전체소스

from tensorflow import keras
from tensorflow.keras.utils import Sequence
from tensorflow.keras import layers
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
from PIL import Image
import math

batch_size = 20
lr = 1e-3
n_epochs = 20
IMG_SIZE = 150

# fig = plt.gcf()


class CustomDataset:
    def __init__(self, base_path, transform=None, target_transform=None, base_idx=None, cnt=None, file_name=None):
        self.file_name = None
        self.base_idx = base_idx
        self.cnt = cnt
        self.base_dir = base_path
        self.transform = transform
        self.cat_fnames = []
        self.dog_fnames = []
        self.target_transform = target_transform
        # 훈련에 사용되는 고양이/개 이미지 경로
        self.cats_dir = os.path.join(self.base_dir, 'cats')
        self.dogs_dir = os.path.join(self.base_dir, 'dogs')
        if file_name is not None:
            self.file_name = file_name
            self.total_len = 1
            return
        '''
        cats_and_dogs_filtered/train/cats
        cats_and_dogs_filtered/train/dogs
        cats_and_dogs_filtered/validation/cats
        cats_and_dogs_filtered/validation/dogs
        '''
        # os.listdir() 경로 내에 있는 파일의 이름을 리스트의 형태로 반환합니다.
        # [...'cat.102.jpg', 'cat.103.jpg', .... ]
        self.cat_fnames = os.listdir(self.cats_dir)
        self.dog_fnames = os.listdir(self.dogs_dir)
        if self.base_idx is None:
            self.total_len = len(self.cat_fnames) + len(self.dog_fnames)
        else:
            self.total_len = cnt

        print(f"total images:{self.total_len}")

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        cat[xxx]
        cat[xxx]
        dog[xxx]
        dog[xxx]
        """
        if self.base_idx is not None:
            index = index + self.base_idx

        if self.file_name is not None:
            y_data = np.array([0.0, 1.0])
            image = Image.open(self.file_name)
        elif index >= len(self.cat_fnames):
            # dog
            x_data = self.dog_fnames[index - len(self.cat_fnames)]
            y_data = np.array([0.0, 1.0])
            img_path = os.path.join(self.dogs_dir, x_data)
            image = Image.open(img_path)
            # plt.imshow(image)
            # print(y_data)
            # plt.show()
        else:
            # cat
            x_data = self.cat_fnames[index]
            y_data = np.array([1.0, 0.0])
            img_path = os.path.join(self.cats_dir, x_data)
            image = Image.open(img_path)
            # plt.imshow(image)
            # print(y_data)
            # plt.show()

        if self.transform:
            image = tf.keras.preprocessing.image.img_to_array(image)
            image = image.astype('float32')
            image /= 255.0
            image = self.transform(image)
            # plt.imshow(image)
            # plt.show()

        if self.target_transform:
            y_data = self.target_transform(y_data)
        return image, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


class CustomDataloader(Sequence):
    def __init__(self, _dataset, batch_size=1, shuffle=False):
        self.dataset = _dataset
        self.batch_size = batch_size
        self.total_len = math.ceil(len(self.dataset) / self.batch_size)
        self.shuffle = shuffle
        self.indexer = np.arange(len(self.dataset))
        self.on_epoch_end()

    def __getitem__(self, index):
        indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size]
        batch_x = [self.dataset[i][0] for i in indexer]
        batch_y = [self.dataset[i][1] for i in indexer]
        batch_x = np.array(batch_x)
        batch_y = np.array(batch_y)
        return batch_x, batch_y

    def __len__(self):
        return self.total_len

    def on_epoch_end(self):
        if self.shuffle:
            np.random.shuffle(self.indexer)


"""
# https://keras.io/api/layers/preprocessing_layers/image_augmentation/
https://www.tensorflow.org/tutorials/images/data_augmentation?hl=ko
참고: data_augmentation 데이터 증강은 테스트할 때 비활성화되므로 입력 이미지는 model.fit(model.evaluate 또는 model.predict가 아님) 호출 중에만 증강됩니다.
RandomCrop layer
RandomFlip layer
RandomTranslation layer
RandomRotation layer
RandomZoom layer
RandomHeight layer
RandomWidth layer
RandomContrast layer
RandomBrightness layer
"""


val_processing = tf.keras.Sequential([
    layers.Resizing(IMG_SIZE, IMG_SIZE),
    layers.CenterCrop(IMG_SIZE, IMG_SIZE),
    layers.Normalization(mean=[0.485, 0.456, 0.406], variance=[0.229, 0.224, 0.225]),
])

train_data_augmentation = tf.keras.Sequential([
    layers.RandomFlip("horizontal_and_vertical"),
    val_processing,
])

if __name__ == '__main__':
    train_dataset = CustomDataset("cats_and_dogs_filtered/train", transform=train_data_augmentation)
    val_dataset = CustomDataset("cats_and_dogs_filtered/validation", transform=val_processing)

    train_loader = CustomDataloader(_dataset=train_dataset, batch_size=batch_size, shuffle=True)
    val_loader = CustomDataloader(_dataset=val_dataset, batch_size=batch_size, shuffle=True)


    # https://keras.io/api/layers/
    model = keras.Sequential()
    model.add(keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3)))
    model.add(layers.Conv2D(16, kernel_size=3, activation='relu'))
    model.add(layers.MaxPooling2D(pool_size=(8, 8)))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(2, activation='relu'))
    model.summary()

    # https://keras.io/api/optimizers/
    # https://keras.io/api/losses/
    INPUT_OPTIMIZER = tf.keras.optimizers.SGD(learning_rate=lr)
    LOSS = tf.keras.losses.MeanSquaredError(reduction="auto", name="mean_squared_error")
    model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS)
    model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader)
    model.save("model.keras")


훈련 결과

total images:2000
total images:1000
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 148, 148, 16)      448       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 18, 18, 16)       0         
 )                                                               
                                                                 
 flatten (Flatten)           (None, 5184)              0         
                                                                 
 dense (Dense)               (None, 512)               2654720   
                                                                 
 dense_1 (Dense)             (None, 2)                 1026      
                                                                 
=================================================================
Total params: 2,656,194
Trainable params: 2,656,194
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
100/100 [==============================] - 29s 293ms/step - loss: 0.3131 - val_loss: 0.2775
Epoch 2/20
100/100 [==============================] - 32s 318ms/step - loss: 0.2806 - val_loss: 0.2635
Epoch 3/20
100/100 [==============================] - 61s 607ms/step - loss: 0.2680 - val_loss: 0.2556
Epoch 4/20
100/100 [==============================] - 47s 469ms/step - loss: 0.2581 - val_loss: 0.2543
Epoch 5/20
100/100 [==============================] - 33s 327ms/step - loss: 0.2535 - val_loss: 0.2477
Epoch 6/20
100/100 [==============================] - 32s 324ms/step - loss: 0.2474 - val_loss: 0.2436
Epoch 7/20
100/100 [==============================] - 33s 326ms/step - loss: 0.2450 - val_loss: 0.2413
Epoch 8/20
100/100 [==============================] - 32s 320ms/step - loss: 0.2425 - val_loss: 0.2379
Epoch 9/20
100/100 [==============================] - 32s 319ms/step - loss: 0.2360 - val_loss: 0.2377
Epoch 10/20
100/100 [==============================] - 33s 333ms/step - loss: 0.2353 - val_loss: 0.2386
Epoch 11/20
100/100 [==============================] - 34s 342ms/step - loss: 0.2317 - val_loss: 0.2347
Epoch 12/20
100/100 [==============================] - 32s 320ms/step - loss: 0.2326 - val_loss: 0.2316
Epoch 13/20
100/100 [==============================] - 33s 329ms/step - loss: 0.2298 - val_loss: 0.2310
Epoch 14/20
100/100 [==============================] - 32s 321ms/step - loss: 0.2297 - val_loss: 0.2395
Epoch 15/20
100/100 [==============================] - 32s 320ms/step - loss: 0.2215 - val_loss: 0.2326
Epoch 16/20
100/100 [==============================] - 32s 325ms/step - loss: 0.2241 - val_loss: 0.2275
Epoch 17/20
100/100 [==============================] - 34s 336ms/step - loss: 0.2231 - val_loss: 0.2281
Epoch 18/20
100/100 [==============================] - 32s 323ms/step - loss: 0.2217 - val_loss: 0.2266
Epoch 19/20
100/100 [==============================] - 31s 308ms/step - loss: 0.2207 - val_loss: 0.2265
Epoch 20/20
100/100 [==============================] - 31s 311ms/step - loss: 0.2192 - val_loss: 0.2249


Test

이제는 원하는 이미지를 넣어서 어떤 결과가 나오는지 테스트 해볼 시간입니다.

import tensorflow as tf
import trainer


if __name__ == '__main__':
    model = tf.keras.models.load_model("model.keras")
    model.summary()

    train_dataset = trainer.CustomDataset("cats_and_dogs_filtered/train", transform=trainer.val_processing)
    val_dataset = trainer.CustomDataset("cats_and_dogs_filtered/validation", transform=trainer.val_processing)

    train_loader = trainer.CustomDataloader(_dataset=train_dataset, batch_size=1, shuffle=True)
    val_loader = trainer.CustomDataloader(_dataset=val_dataset, batch_size=1, shuffle=True)

    # test
    test_images = ["cats_and_dogs_filtered/train/cats/cat.1.jpg",
                   "cats_and_dogs_filtered/train/cats/cat.2.jpg",
                   "cats_and_dogs_filtered/train/dogs/dog.1.jpg",
                   "cats_and_dogs_filtered/train/dogs/dog.2.jpg",
                   "cats_and_dogs_filtered/validation/cats/cat.2001.jpg",
                   "cats_and_dogs_filtered/validation/dogs/dog.2001.jpg",
                   "cats_and_dogs_filtered/validation/cats/cat.2004.jpg",
                   "cats_and_dogs_filtered/validation/dogs/dog.2004.jpg",
                   ]

    for test_image in test_images:
        print(test_image)
        dataset = trainer.CustomDataset("cats_and_dogs_filtered/train", transform=trainer.val_processing, file_name=test_image)
        dataloader = trainer.CustomDataloader(_dataset=dataset, batch_size=1, shuffle=False)
        x_prd = dataloader[0][0]  # batch 0 , x
        print(x_prd.shape)
        y = model.predict(x_prd)
        print("result:", y)
        if y[0][0] > y[0][1]:
            print("cat")
        else:
            print("dog")


실행 결과

Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 148, 148, 16)      448       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 18, 18, 16)       0         
 )                                                               
                                                                 
 flatten (Flatten)           (None, 5184)              0         
                                                                 
 dense (Dense)               (None, 512)               2654720   
                                                                 
 dense_1 (Dense)             (None, 2)                 1026      
                                                                 
=================================================================
Total params: 2,656,194
Trainable params: 2,656,194
Non-trainable params: 0
_________________________________________________________________
total images:2000
total images:1000
cats_and_dogs_filtered/train/cats/cat.1.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 60ms/step
result: [[0.48046866 0.43707907]]
cat
cats_and_dogs_filtered/train/cats/cat.2.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 12ms/step
result: [[0.74900883 0.4924001 ]]
cat
cats_and_dogs_filtered/train/dogs/dog.1.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 12ms/step
result: [[0.45707452 0.7523619 ]]
dog
cats_and_dogs_filtered/train/dogs/dog.2.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 12ms/step
result: [[0.15739511 0.7101962 ]]
dog
cats_and_dogs_filtered/validation/cats/cat.2001.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 10ms/step
result: [[0.63321704 0.2585028 ]]
cat
cats_and_dogs_filtered/validation/dogs/dog.2001.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 12ms/step
result: [[0.1274486  0.65480846]]
dog
cats_and_dogs_filtered/validation/cats/cat.2004.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 11ms/step
result: [[0.73557734 0.3226891 ]]
cat
cats_and_dogs_filtered/validation/dogs/dog.2004.jpg
(1, 150, 150, 3)
1/1 [==============================] - 0s 12ms/step
result: [[0.48342577 0.7902105 ]]
dog

cat 사진을 넣었을때 cat이 제대로 나오는지, dog를 넣었을때 dog가 제대로 나오는지 보면됩니다. 위 결과로는 모두 제대로 나오고 있습니다.

사실 Pytorch와 비슷하게 RandomZoom을 넣었었는데... (Pytorch에서는 RandomResize항목이 있었음) 학습이 제대로 안되는 경우가 많아서 해당 부분을 뺐습니다.


sourcecode/keras/02_image_class at main · donarts/sourcecode · GitHub



댓글 없음:

댓글 쓰기