티스토리 뷰

반응형

딥러닝 모델 저장

model1.save('./my_first_save.keras')

 

모델 불러오기

clear_session()
model3 = keras.models.load_model('./my_first_save.keras')
model3.summary()

 

모델 가중치 불러오기

model3.get_weights()[0][0][0]

 

이미지를 저장하기 위한 연결

from google.colab import drive
drive.mount('/content/drive')

!ls

!cd /content/drive/MyDrive/my_data; ls

 

이미지 불러오기

import glob
from keras.preprocessing import image

files = glob.glob('/content/drive/MyDrive/my_data/my_mnist/*')
files

img = image.load_img(files[0], color_mode='grayscale', target_size=(28,28) )
img = image.img_to_array(img)

plt.imshow(img.reshape(img.shape[0], img.shape[1]), cmap='gray')
plt.show()

 

AI hub

ai 연구를 위한 곳. 데이터가 엄청나게 많다. 코랩에서도 안 돌아간다.

연구원일 때 써라.

cifar = 5만~6만장

 

이미지 데이터를 폴더 별로 정리한 상태라면?

 

from keras.utils import image_dataset_from_directory
# idfd_train, idfd_valid = image_dataset_from_directory('/content/drive/MyDrive/my_data/my_mnist2',
#                                                       label_mode='categorical',
#                                                       color_mode='grayscale',
#                                                       image_size=(28,28),
#                                                       seed=2024,
#                                                       validation_split=0.2,
#                                                       subset='both'
#                                                       )

idfd_train = image_dataset_from_directory('/content/drive/MyDrive/my_data/my_mnist2',
                                                      label_mode='categorical',
                                                      color_mode='grayscale',
                                                      image_size=(28,28),
                                                      )

idfd_train을 활용한다.

clear_session()

model5 = keras.models.load_model('./model1.keras')

# model5.summary()

model5.fit(idfd_train,
        #    validation_data=idfd_valid,
           epochs=100, verbose=1)

 

 

전이학습(transfer learning)

그를 위해 우리는 모델이 일반적인 상황에서 잘 동작하기를 기대한다(Robust-강건성)이 좋기를 기대한다.

잘 만든 모델(pretrained model)을 가져다가, 레이어를 추가로 붙여서 사용하거나 혹은 중간의 가중치를 초기화해서 사용할 수 있다.

이를 전이학습이라고 한다.

 

1 사분면 : 전체 재학습

큰 데이터셋이지만, pre-trained model의 데이터셋이랑 다를 경우 

전체 모델을 재학습한다.

 

2 사분면: 조금의 레이어만 재학습 - 파인 튜닝

큰 데이터셋이고, pre-trained model의 데이터셋이랑 비슷할 경우

 

3 사분면 : 절반 이상의 레이어 재학습 -파인 튜닝

작은 데이터셋이고, pred-trained model의 데이터셋이랑 다를 경우

 

4 사분면 : 베이스는 유지하고, 레이어 추가 - 사전학습

작은 데이터셋이고,  pre-trained model의 데이터셋이랑 비슷할 경우

 

경우에 따라서 전이학습을 활용하는 용도가 다르다.

 

aumentation의 한계점

  • 원본과 확연히 다른 새로운 특징은 만들어 낼 수 없다.
  • 애초에 적절한 데이터인지를 모른다.(양과 질을 모두 충족하는)

 

소규모 연구자가 거대한 모델을 운용하기에는 너무 힘들다.

그래서 조금만 고쳐 사용하는 걸 목표로 사용한 게 근간이다.

 

여기서는 4 사분면의 케이스만 설명한다.

 

vgg 모델을 가져온다.

vgg_model = VGG16(include_top=True,       # VGG16 모델의 아웃풋 레이어까지 전부 불러오기
                  weights='imagenet',     # ImageNet 데이터를 기반으로 학습된 가중치 불러오기
                  input_shape=(224,224,3) # 모델에 들어가는 데이터의 형태
                  )

 

plot 모델을 사용하면 해당 레이어들이 어떻게 되어 있는지 볼 수 있다.

from keras.utils import plot_model

plot_model(vgg_model, show_shapes=True, show_layer_names=True)

 

img1 폴더에서, files 변수에 있는 모든 이미지 데이터를 불러온다.

import glob
from keras.preprocessing import image
files = glob.glob('/content/drive/MyDrive/my_data/img1/*')
files

img = image.load_img(files[-1], color_mode='rgb', target_size = (224,224) )
img = image.img_to_array(img)
img = img.reshape((-1,224,224,3))
print(f'preprocess 전: 최대값={np.max(img)}, 최소값={np.min(img)}')

img = preprocess_input(img)
print(f'preprocess 후: 최대값={np.max(img)}, 최소값={np.min(img)}')

features = vgg_model.predict(img)
print(decode_predictions(features, top=3))

plt.imshow(image.load_img(files[-1]))
plt.show()

 

files에 있는 path에 대해서, 각 이미지를 preprocessing 한다.

images = []

for path in files :
    img = image.load_img(path, color_mode='rgb', target_size=(224,224) )
    img = image.img_to_array(img)
    img = preprocess_input(img)
    images.append(img)

images = np.array(images)

 

그리고 나서 input으로 만든다.

features = vgg_model.predict(images)
predictions = decode_predictions(features, top=3)

for i in range(images.shape[0]) :
    print(predictions[i])
    plt.imshow(image.load_img(files[i]))
    plt.show()

 

불러온 모델 자체의 prediction으로도 웬만한 결과를 낼 수 있다.

 

가중치 고치기

backprop에 해당하는 과정이다.

 

https://www.tensorflow.org/api_docs/python/tf/keras/applications

 

해당 링크에서 keras에서 쓸 수 있는 pretrained learning 모델들을 볼 수 있다.

 

ConvNeXtBase(...): Instantiates the ConvNeXtBase architecture.

ConvNeXtLarge(...): Instantiates the ConvNeXtLarge architecture.

ConvNeXtSmall(...): Instantiates the ConvNeXtSmall architecture.

ConvNeXtTiny(...): Instantiates the ConvNeXtTiny architecture.

ConvNeXtXLarge(...): Instantiates the ConvNeXtXLarge architecture.

DenseNet121(...): Instantiates the Densenet121 architecture.

DenseNet169(...): Instantiates the Densenet169 architecture.

DenseNet201(...): Instantiates the Densenet201 architecture.

EfficientNetB0(...): Instantiates the EfficientNetB0 architecture.

EfficientNetB1(...): Instantiates the EfficientNetB1 architecture.

EfficientNetB2(...): Instantiates the EfficientNetB2 architecture.

EfficientNetB3(...): Instantiates the EfficientNetB3 architecture.

EfficientNetB4(...): Instantiates the EfficientNetB4 architecture.

EfficientNetB5(...): Instantiates the EfficientNetB5 architecture.

EfficientNetB6(...): Instantiates the EfficientNetB6 architecture.

EfficientNetB7(...): Instantiates the EfficientNetB7 architecture.

EfficientNetV2B0(...): Instantiates the EfficientNetV2B0 architecture.

EfficientNetV2B1(...): Instantiates the EfficientNetV2B1 architecture.

EfficientNetV2B2(...): Instantiates the EfficientNetV2B2 architecture.

EfficientNetV2B3(...): Instantiates the EfficientNetV2B3 architecture.

EfficientNetV2L(...): Instantiates the EfficientNetV2L architecture.

EfficientNetV2M(...): Instantiates the EfficientNetV2M architecture.

EfficientNetV2S(...): Instantiates the EfficientNetV2S architecture.

InceptionResNetV2(...): Instantiates the Inception-ResNet v2 architecture.

InceptionV3(...): Instantiates the Inception v3 architecture.

MobileNet(...): Instantiates the MobileNet architecture.

MobileNetV2(...): Instantiates the MobileNetV2 architecture.

MobileNetV3Large(...): Instantiates the MobileNetV3Large architecture.

MobileNetV3Small(...): Instantiates the MobileNetV3Small architecture.

NASNetLarge(...): Instantiates a NASNet model in ImageNet mode.

NASNetMobile(...): Instantiates a Mobile NASNet model in ImageNet mode.

ResNet101(...): Instantiates the ResNet101 architecture.

ResNet101V2(...): Instantiates the ResNet101V2 architecture.

ResNet152(...): Instantiates the ResNet152 architecture.

ResNet152V2(...): Instantiates the ResNet152V2 architecture.

ResNet50(...): Instantiates the ResNet50 architecture.

ResNet50V2(...): Instantiates the ResNet50V2 architecture.

VGG16(...): Instantiates the VGG16 model.

VGG19(...): Instantiates the VGG19 model.

Xception(...): Instantiates the Xception architecture.

 

 

실제 코드

inception 모델을 쓴다.

 

import keras

from keras.applications.inception_v3 import InceptionV3
from keras.applications.inception_v3 import preprocess_input
from keras.applications.inception_v3 import decode_predictions
from keras.callbacks import EarlyStopping, ReduceLROnPlateau

from keras.preprocessing import image
from keras.utils import to_categorical
from keras.layers import GlobalAveragePooling2D, Dense

from sklearn.model_selection import train_test_split

import random
import numpy as np
import matplotlib.pyplot as plt
import glob

 

colab을 기준으로 설명한다.

from google.colab import drive
drive.mount('/content/drive')

!cd /content/drive/MyDrive/my_data/; ls  #폴더에 들어 있는 파일 확인

!cd /content/drive/MyDrive/my_data/transfer; ls #폴더에 들어 있는 파일들 확인

files = glob.glob('/content/drive/MyDrive/my_data/transfer/*/*')
files #파일 경로 할당하기

files[-1].split('/')[-2] #파일 대분류 확인하기(폴더 이름 확인하기)

name_cnt = {}

for x in files :
    name_cnt[x.split('/')[-2]] = name_cnt.get(x.split('/')[-2], 0) + 1
    #대분류 이름이 각각 몇 개 있는지 확인하기

name_cnt
{'모닝': 10, 'ev6': 9, 'k5': 10}

 

그리고 나서 각 레이블에 대해 레이블 인코딩을 해준다.

i = 0
names = {}

for key in name_cnt :
    names[key] = i     # names_cnt의 key값에 새로운 값 부여
    i += 1             # 클래스 수만큼 i값 증가

names
{'모닝': 0, 'ev6': 1, 'k5': 2}

 

그렇게 받은 이미지를 array로 바꿔준다.

images = []
labels = []

for path in files:
    img = image.load_img(path, target_size=(299,299) )
    img = image.img_to_array(img)

    images.append(img)
    labels.append(names[path.split('/')[-2]])  #names에 해당하는 이름에 해당하는 0,1,2를 넣는다.

    plt.imshow(image.load_img(path))
    plt.show()

images_arr = np.array(images)
labels_arr = np.array(labels) #array로 만든다.

print(images_arr.shape)
print(labels_arr.shape)

print(labels_arr)
label_v = len(np.unique(labels_arr))
label_v
> [0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2] = labels_arr
3 -> 이게 클래스의 수

### 라벨링
y = to_categorical(labels, label_v)  #그래서 labels를 label_v로 라벨링한다.

print(y[:3])
y.shape

>[[1. 0. 0.]
 [1. 0. 0.]
 [1. 0. 0.]]
(29, 3)

이렇게 되면 각 y(타겟) 이미지가 라벨링이 된 것이다.

 

Dataset split

각 이미지 그룹별 분할을 위해서는 코드가 복잡하다.

name_cnt.values()
>dict_values([10, 9, 10])
각 values의 숫자가 있다.

temp = []
init_v = 0

for v in name_cnt.values() : #10이 v로 들었갔다면 10만큼 반복
    temp.append( (images[init_v:init_v+v], y[init_v:init_v+v]) ) #temp에 0부터 1이 들어간다.
    init_v += v #그리고 나서 각 init_v는 v를 더해준다.
    0:1~1:2~2:3...이런 순으로 반복
    
for i in range(len(temp)) :
    x_to_array = np.array(temp[i][0])  #첫번쨰는 image
    y_to_array = np.array(temp[i][1]) #두번쨰는 y

    train_x, test_x, train_y, test_y =\
        train_test_split(x_to_array, y_to_array, test_size=0.2, random_state=2024) #y,x에 대해서 image를 split 한다.

    train_x, valid_x, train_y, valid_y =\
        train_test_split(train_x, train_y, test_size=0.2, random_state=2024)

    if i==0 :
        first_tr_x, first_va_x, first_te_x = train_x.copy(), valid_x.copy(), test_x.copy() #i=0이라면 각 copy를 만든다.
        first_tr_y, first_va_y, first_te_y = train_y.copy(), valid_y.copy(), test_y.copy()

    elif i==1 :
        new_tr_x, new_tr_y = np.vstack((first_tr_x, train_x)), np.vstack((first_tr_y, train_y))
        new_va_x, new_va_y = np.vstack((first_va_x, valid_x)), np.vstack((first_va_y, valid_y)) #i=1이라면 첫번째와 stack
        new_te_x, new_te_y = np.vstack((first_te_x, test_x)), np.vstack((first_te_y, test_y))

    else :
        new_tr_x, new_tr_y = np.vstack((new_tr_x, train_x)), np.vstack((new_tr_y, train_y)) #아니라면 다시 스택을 쌓는다.
        new_va_x, new_va_y = np.vstack((new_va_x, valid_x)), np.vstack((new_va_y, valid_y))
        new_te_x, new_te_y = np.vstack((new_te_x, test_x)), np.vstack((new_te_y, test_y))
        
        
new_tr_x.shape, new_tr_y.shape, new_va_x.shape, new_va_y.shape, new_te_x.shape, new_te_y.shape

>((17, 299, 299, 3),
 (17, 3),
 (6, 299, 299, 3),
 (6, 3),
 (6, 299, 299, 3),
 (6, 3))
 
 x와 y, val x와 y, test x와 y가 나뉘어졌다.

# 전처리 하지 않은 파일 따로 시각화 해두기
train_xv, valid_xv, test_xv = train_x.copy(), valid_x.copy(), test_x.copy()

 

 

데이터 프로세싱

train 데이터에 대해서는 스케일링을 해줘야 한다.

new_tr_x.max(), new_tr_x.min()
>(255.0, 0.0)

new_tr_x = preprocess_input(new_tr_x) #모델에서 제공하는 프리프로세싱
new_va_x = preprocess_input(new_va_x)
new_te_x = preprocess_input(new_te_x)

new_tr_x.max(), new_tr_x.min()
>(1.0, -1.0)

 

ImageNet 불러오기

https://image-net.org/challenges/LSVRC/2014/browse-synsets

이미지 넷 대회의 해당 클래스

keras.backend.clear_session()

base_model = InceptionV3(weights='imagenet',       # ImageNet 데이터를 기반으로 미리 학습된 가중치 불러오기
                         include_top=False,        # InceptionV3 모델의 아웃풋 레이어는 제외하고 불러오기
                         input_shape= (299,299,3)) # 입력 데이터의 형태

new_output = GlobalAveragePooling2D()(base_model.output)
new_output = Dense(3, # class 3개   클래스 개수만큼 진행한다.
                  activation = 'softmax')(new_output)

model = keras.models.Model(base_model.inputs, new_output)

model.summary()

사전 학습 모델의 output을 GlobalAveragePolling으로 불러오고, dense를 3으로 잡아주었다.

GolbalAveragePooling은 차원을 엄청나게 줄여준다.

 

 

각 채널마다 평균값을 모아서 1*1*n의 feature map으로 바꿔준다.

  • 4x4x512를 flatten하면 8192개. feature map의 개수를 128개로만 잡아줘도 8192*128 = 1,048,576개의 weight
  • 4x4x512→GlobalAverage를 사용하여 1x1x512로 만든다.
  • Dense Layer에 입력 시 512*128 = 65536개의 weight
  • flatten 사용보다 파라미터가 확 줄어든다.

 

from keras.utils import plot_model
plot_model(model, show_shapes=True, show_layer_names=True)
print(f'모델의 레이어 수 : {len(model.layers)}')

 

plot 모델 및 레이어를 확인할 수 있다.

 

파인 튜닝

모델의 가중치를 그대로 사용할 레이어와, 추가 학습할 레이어를 선택한다.

model.layers

for idx, layer in enumerate(model.layers) :
    if idx < 213 :
        layer.trainable = False
    else :
        layer.trainable = True
        
for layer in model.layers:
    print(layer.name, layer.trainable) #레이어 보기

 

레이어의 개수를 선택해서 layer.trainable에 저장해놓는다.

여기서는 213 레이어 후부터 다시 학습하는 걸로 선택되었다.

 

# 처음부터 학습시키는 것도 아니고,
# 마지막 100개의 레이어만 튜닝 할 것이므로 learning rate를 조금 크게 잡아본다.

model.compile(loss='categorical_crossentropy', metrics=['accuracy'],
             optimizer=keras.optimizers.Adam(learning_rate=0.001) )
             
lr_reduction = ReduceLROnPlateau(monitor='val_loss',
                                 patience=4,
                                 verbose=1,
                                 factor=0.5,
                                 min_lr=0.000001)

es = EarlyStopping(monitor='val_loss',
                   min_delta=0, # 개선되고 있다고 판단하기 위한 최소 변화량
                   patience=4,  # 개선 없는 epoch 얼마나 기다려 줄거야
                   verbose=1,
                   restore_best_weights=True)

 

lr_reduction은 학습률을 증가시켰다가 감소시키는 방법으로, 동적 학습률 변화를 만든다.

 

# 데이터를 넣어서 학습시키자!
hist = model.fit(train_x, train_y,
                 validation_data=(valid_x, valid_y),
                 epochs=1000, verbose=1,
                 callbacks=[es, lr_reduction]
                 )

 

ResNET

vanishing gradient 문제는 레이어를 지날 때 마다 점점 기울기가 소실되는 것이다.

그래서 원본 x를 처음부터 빼가지고, output 자체를 원본 x에 맞춘다.

한마디로 H(x)가 output이라면, H(x)- x (=F(x), 다른 이름으로는 잔차)가 최소화되도록 하는 것이다.

 

이렇게 하게 되면 장점이 H(x)가 0이 되더라도 무조건 F(x)(=H(x)-x)의 기울기는 1이 된다.

즉, 기울기 소실이 일어날 수가 없는 환경으로 귀결되므로, 최악의 경우에도 전의 가중치가 다음의 가중치로 전달 될 뿐, 기울기 소실이 일어나지는 않는다.

 

논문 자체에서는 편미분으로 표현하지만(  F'(w,b) ), 역전파 연쇄과정에서 Loss/y * y/w 가 되기 때문에 Loss/y 필요하고 y로 미분하면 미분값이 최소 1은 확보라고 한다.

 

import tensorflow as tf
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, Add

def residual_block(input_tensor, filters):
    x = Conv2D(filters, (3, 3), padding='same')(input_tensor)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Conv2D(filters, (3, 3), padding='same')(x)
    x = BatchNormalization()(x)
    x = Add()([x, input_tensor])
    x = Activation('relu')(x)
    return x

def create_resnet(input_shape, num_classes):
    inputs = tf.keras.Input(shape=input_shape)
    x = Conv2D(64, (7, 7), strides=(2, 2), padding='same')(inputs)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = residual_block(x, 64)
    x = residual_block(x, 64)
    x = residual_block(x, 64)
    x = tf.keras.layers.GlobalAveragePooling2D()(x)
    outputs = tf.keras.layers.Dense(num_classes, activation='softmax')(x)
    model = tf.keras.Model(inputs=inputs, outputs=outputs)
    return model

# 예시: 32x32 크기의 이미지에 대한 ResNet 모델 생성
model = create_resnet((32, 32, 3), 10)

 

핵심은 바로 이 부분이다.

    x = Add()([x, input_tensor])

Add 레이어는 케라스(Keras)의 기능 중 하나로, 두 개의 입력 텐서를 요소별(element-wise)로 더하는 역할을 합니다.

  1. Add 레이어는 두 입력 텐서의 형상(shape)이 동일한지 확인합니다. 만약 다르다면 에러를 발생시킵니다.
  2. 두 입력 텐서의 요소를 하나씩 더합니다. 즉, 첫 번째 텐서의 첫 번째 요소와 두 번째 텐서의 첫 번째 요소를 더하고, 두 번째 요소들을 더하는 식입니다.
    1. 요소별 덧셈 결과를 새로운 출력 텐서로 반환합니다.
x = [[1, 2], 
     [3, 4]]

input_tensor = [[5, 6],
                [7, 8]]

이라고 가정하면, 요소별 덧셈 연산을 수행한다.

  • output[0, 0] = x[0, 0] + input_tensor[0, 0] = 1 + 5 = 6
  • output[0, 1] = x[0, 1] + input_tensor[0, 1] = 2 + 6 = 8
  • output[1, 0] = x[1, 0] + input_tensor[1, 0] = 3 + 7 = 10
  • output[1, 1] = x[1, 1] + input_tensor[1, 1] = 4 + 8 = 12
output = [[6, 8],
          [10, 12]]

아웃풋은 이렇게 나온다.

 

이렇게 나온 결과를 activation relu에 통과시키면, 그 결과가 해당 공식의 잔차를 최소화하는 방식이 된다.

즉, x가 0이 되더라도, 무조건 input_tensor 값은 나오므로, input_tensor에 대한 activation은 구할 수 있게 된다.

 

keras.backend.clear_session()

base_model = ResNet50(weights='imagenet',       # ImageNet 데이터를 기반으로 미리 학습된 가중치 불러오기
                         include_top=False,        # Restnet 모델의 아웃풋 레이어는 제외하고 불러오기
                         input_shape= (32,32,3)
                         pooling='avg' #global average pooling 2D이다.
                      ) # 입력 데이터의 형태

# new_output = GlobalAveragePooling2D()(base_model.output) #pooling 'avg여서 없어도 된다.
new_output = Dense(10, # class 10개   클래스 개수만큼 진행한다.
                  activation = 'softmax')(base_model.output)

model = keras.models.Model(base_model.inputs, new_output)

model.summary()

 

 

반응형