아래 코드로 내용을 대체합니다.

더 많은 층의 output을 보고 싶으면, feature_maps를 for-loop로 구현하면 됩니다.

# 신경망 시각화(조휘용)
import tensorflow as tf

get_layer_name = [layer.name for layer in model.layers]
get_output = [layer.output for layer in model.layers]

# 모델 전체에서 output을 가져올 수 있습니다.
visual_model = tf.keras.models.Model(inputs = model.input, outputs = get_output)

test_img = np.expand_dims(testX[0], axis = 0)
feature_maps = visual_model.predict(test_img)

# 첫 번째 컨볼루션 층의 특징맵을 시각화합니다.
conv_featuremap = feature_maps[0]
conv_name = get_layer_name[0]

img_size = conv_featuremap.shape[1]
img_features = conv_featuremap.shape[-1]

display_grid = np.zeros((img_size, img_size * img_features))

for i in range(img_features):
    x = conv_featuremap[:, :, :, i]
    x -= x.mean(); x /= x.std()
    x *= 64
    x += 128
    x = np.clip(x, 0, 255).astype('uint8')
    display_grid[:, i * img_size : (i + 1) * img_size] = x
    
plt.figure(figsize = (20,20))
plt.title(conv_name)
plt.grid(False)
plt.imshow(display_grid, cmap = 'viridis')

 

이미지 제네레이터와 활용하고 싶은 데이터를 포함한 데이터 제네레이터의 구현 코드입니다.

이미지는 이미지데이터 제네레이터를 통해 불러오며, 활용하고 싶은 데이터인 color는 직접 인덱스를 통해 배치 크기만큼 부르는 것을 볼 수 있습니다.

import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator

class DataGenerator(tf.keras.utils.Sequence):
    def __init__(self, df, batch_size = 32, target_size = (112, 112), shuffle = True):
        self.len_df = len(df)
        self.batch_size = batch_size
        self.target_size = target_size
        self.shuffle = shuffle
        self.class_col = ['black', 'blue', 'brown', 'green', 'red', 'white', 
             'dress', 'shirt', 'pants', 'shorts', 'shoes']
        self.generator = ImageDataGenerator(rescale = 1./255)
        self.df_generator = self.generator.flow_from_dataframe(dataframe=df, 
                                                          directory='',
                                                            x_col = 'image',
                                                            y_col = self.class_col,
                                                            target_size = self.target_size,
                                                            color_mode='rgb',
                                                            class_mode='other',
                                                            batch_size=self.batch_size,
                                                            seed=42)
        self.colors_df = df['color']
        self.on_epoch_end()
        
    def __len__(self):
        return int(np.floor(self.len_df) / self.batch_size)
    
    def on_epoch_end(self):
        self.indexes = np.arange(self.len_df)
        if self.shuffle:
            np.random.shuffle(self.indexes)
        
    def __getitem__(self, index):
        indexes = self.indexes[index * self.batch_size : (index + 1) * self.batch_size]
        colors = self.__data_generation(indexes)
        
        images, labels = self.df_generator.__getitem__(index)
        
        # return multi-input and output
        return [images, colors], labels
    
    def __data_generation(self, indexes):
        colors = self.colors_df[indexes].to_numpy()
        # 또는
        # colors = np.array([self.colors_df[k] for k in indexes])
        
        return colors

 

1 - https://hwiyong.tistory.com/241

1x1 Convolution은 Network in network 논문에서 주로 다룬 개념입니다. 

Lin, M., Chen, Q., & Yan, S. (2013). Network in network. arXiv preprint arXiv:1312.4400.

 

이 글에서는 MNIST 데이터셋을 Dense 층(fully-connected layer)를 사용하지 않고 학습하는 방법을 다루겠습니다.

코딩 방법에 따라 2가지로 나뉘어서 모델을 구성할 수 있습니다. 직접 돌려보니 정확도는 약 98%정도 나왔습니다.


1. 단순하게 Global Avaerage Pooling만 사용

import tensorflow as tf

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

x_train = x_train.reshape(-1,28, 28, 1)
x_train = x_train / 255
x_test = x_test.reshape(-1, 28, 28, 1)
x_test = x_test / 255

from tensorflow.keras.layers import Input, Conv2D, GlobalAveragePooling2D, MaxPooling2D
from tensorflow.keras.models import Model


inputs = Input(shape = (28, 28, 1))
x = Conv2D(32, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(inputs)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(64, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(x)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(64, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(x)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(10, (1, 1), activation = 'softmax')(x)
x = GlobalAveragePooling2D()(x)

model = Model(inputs = inputs, outputs = x)

model.compile(optimizer = 'adam', 
              loss = 'sparse_categorical_crossentropy', 
              metrics = ['acc'])
model.fit(x_train, y_train, 
          epochs = 10, batch_size = 32)

2. 적절한 Reshape 혼합

import tensorflow as tf

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

x_train = x_train.reshape(-1,28, 28, 1)
x_train = x_train / 255
x_test = x_test.reshape(-1, 28, 28, 1)
x_test = x_test / 255

from tensorflow.keras.layers import Input, Conv2D, Reshape
from tensorflow.keras.layers import GlobalAveragePooling2D, MaxPooling2D
from tensorflow.keras.models import Model


inputs = Input(shape = (28, 28, 1))
x = Conv2D(32, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(inputs)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(64, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(x)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(64, (3, 3), strides = (1, 1), padding = 'same', activation = 'relu')(x)
x = MaxPooling2D(strides = (2, 2))(x)
x = Conv2D(128, (1, 1), padding = 'same', activation = 'relu')(x)
x = GlobalAveragePooling2D()(x)
x = Reshape((1, 1, 128))(x)
x = Conv2D(10, (1, 1), padding = 'same', activation = 'softmax')(x)
x = Reshape((10,))(x)

model = Model(inputs = inputs, outputs = x)

model.compile(optimizer = 'adam', 
              loss = 'sparse_categorical_crossentropy', 
              metrics = ['acc'])
model.fit(x_train, y_train, 
          epochs = 10, batch_size = 32)

 

위에서 소개한 2가지 방법의 차이는 거의 없습니다. 이렇게도 할 수 있구나...를 보여주기 위해서

1x1 conv에 익숙하지 않은 분들에게 도움이 되길 바랍니다. 

두 가지 방법으로 정의하여 사용할 수 있습니다.

예시로는 이 글이 작성된 2019/10/27일을 기준으로 가장 최신의 활성화 함수라고 말할 수 있는 Mish Activation을 사용하였습니다.

[해당 논문]: https://arxiv.org/abs/1908.08681


케라스의 Activation 함수에 그대로 넣어서 사용하기

import tensorflow as tf
import tensorflow.keras.backend as K

from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Input, Dense, Flatten, Activation
from tensorflow.keras.models import Model

(x_train, y_train), (x_test, y_test) = mnist.load_data()

def mish(x):
    return x * K.tanh(K.softplus(x))

inputs = Input(shape = (28, 28))
x = Flatten()(inputs)
x = Dense(50)(x)
x = Activation(mish)(x)
x = Dense(30)(x)
x = Activation(mish)(x)
x = Dense(10, activation = 'softmax')(x)

model = Model(inputs = inputs, outputs = x)
model.compile(optimizer = 'adam',
             loss = 'sparse_categorical_crossentropy')

model.fit(x_train, y_train)

위의 코드와 같이 직접 activation 함수에 쓰일 연산을 정의하여 인자로 넘겨줄 수 있습니다.


함수를 등록하여 케라스의 특징인 문자열 형태로 제공하여 쓰기

import tensorflow as tf
import tensorflow.keras.backend as K

from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Input, Dense, Flatten, Activation
from tensorflow.keras.models import Model

from tensorflow.keras.utils import get_custom_objects

(x_train, y_train), (x_test, y_test) = mnist.load_data()

class Mish(Activation):
    def __init__(self, activation, **kwargs):
        super(Mish, self).__init__(activation, **kwargs)
        self.__name__ = 'Mish'

def mish(x):
    return x * K.tanh(K.softplus(x))

get_custom_objects().update({'mish': Mish(mish)})

inputs = Input(shape = (28, 28))
x = Flatten()(inputs)
x = Dense(50)(x)
x = Activation('mish')(x)
x = Dense(30)(x)
x = Activation('mish')(x)
x = Dense(10, activation = 'softmax')(x)

model = Model(inputs = inputs, outputs = x)
model.compile(optimizer = 'adam',
             loss = 'sparse_categorical_crossentropy')

model.fit(x_train, y_train)

위와 같이 클래스로 정의한 뒤, get_custom_objects를 사용해 등록하여 사용할 수 있습니다.

경우에 따라 1번과 2번 중에 편리한 것이 있을 수 있으니 선택하여 사용하시면 될 것 같네요.

Custom data generator를 만들 때는 keras.utils.Sequence 클래스를 상속하는 것으로 시작합니다.

Sequence는 __getitem__, __len__, on_epoch_end, __iter__를 sub method로서 가지고 있습니다.

따라서, 이들을 우리의 데이터에 맞게 변형하여 사용하게 됩니다.


MNIST는 예를 들기 위해 사용했습니다.

import tensorflow as tf
from tensorflow.keras.utils import Sequence
from tensorflow.keras.utils import to_categorical
import numpy as np

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

class DataGenerator(Sequence):
    def __init__(self, X, y, batch_size, dim, n_channels, n_classes, shuffle = True):
        self.X = X
        self.y = y if y is not None else y
        self.batch_size = batch_size
        self.dim = dim
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.on_epoch_end()
        
    def on_epoch_end(self):
        self.indexes = np.arange(len(self.X))
        if self.shuffle:
            np.random.shuffle(self.indexes)
            
    def __len__(self):
        return int(np.floor(len(self.X) / self.batch_size))
    
    def __data_generation(self, X_list, y_list):
        X = np.empty((self.batch_size, *self.dim))
        y = np.empty((self.batch_size), dtype = int)
        
        if y is not None:
            # 지금 같은 경우는 MNIST를 로드해서 사용하기 때문에
            # 배열에 그냥 넣어주면 되는 식이지만,
            # custom image data를 사용하는 경우 
            # 이 부분에서 이미지를 batch_size만큼 불러오게 하면 됩니다. 
            for i, (img, label) in enumerate(zip(X_list, y_list)):
                X[i] = img
                y[i] = label
                
            return X, to_categorical(y, num_classes = self.n_classes)
        
        else:
            for i, img in enumerate(X_list):
                X[i] = img
                
            return X
        
    def __getitem__(self, index):
        indexes = self.indexes[index * self.batch_size : (index + 1) * self.batch_size]
        X_list = [self.X[k] for k in indexes]
        
        if self.y is not None:
            y_list = [self.y[k] for k in indexes]
            X, y = self.__data_generation(X_list, y_list)
            return X, y
        else:
            y_list = None
            X = self.__data_generation(X_list, y_list)
            return X

__data_generation부분을 수정해서 사용하면 됩니다. 원래 같은 경우는 X를 정의할 때, (batch_size, *dim, n_channel)로 정의하나 편의를 위해 지웠습니다. MNIST 데이터셋은 (28, 28, 1)이 아닌 (28, 28)로 인식되기 때문. 필요에 따라 수정하시면 됩니다.


dg = DataGenerator(x_train, y_train, 4, (28, 28), 1, 10)

import matplotlib.pyplot as plt

for i, (x, y) in enumerate(dg):
    if(i <= 1):
        x_first = x[0]
        plt.title(y[0])
        plt.imshow(x_first)