데이터 분석 및 프로젝트

[딥러닝]- VGG19 모델 활용한 이미지 콜라보 만들기

막막한 2023. 5. 29. 19:50

 

https://www.kaggle.com/code/basu369victor/style-transfer-deep-learning-algorithm

 

Style Transfer Deep Learning Algorithm

Explore and run machine learning code with Kaggle Notebooks | Using data from multiple data sources

www.kaggle.com

위 사진 처럼 

두 작품을 콜라보 하는  딥러닝 알고리즘이다 

사용하는 모델은 vgg 19 모델이다 

 

vgg 뒤에 붙은 숫자는 레이어의 개수이다 

VGG 16  VGG19 는 16개의 레이어, 19개의 레이어를 갖는다 

 

VGG19는 레이어가 19개 레이어로, 메모리 등 자원을 많이 소모한다 

 

특징은 VGG 16이랑 비슷하다 

VGG 16의 특징에 Conv 레이어가 3개 추가된 것

 

 

vgg 19  구조

 

vggnet은 3*3 필터, 모든 conv 레이어에 사용한다 --> 작은 필터를 사용함으로 더 많은 ReLU함수를 사용할 수 있다 (많은 비선형성 확보)

 

import pandas as pd
import numpy as np
import os
from keras import backend as K
from keras.preprocessing.image import load_img, save_img, img_to_array
import matplotlib.pyplot as plt
from keras.applications import vgg19
from keras.models import Model
from scipy.optimize import fmin_l_bfgs_b

print9os.lisedir("../input"))

기본 이미지와 스타일 이미지 경로

StylePath = '../input/best-artworks-of-all-time/images/images/'
ContentPath = '../input/totoro/'


base_image_path = ContentPath + '84.jpg'
style_image_path = StylePath+ 'Pablo_Picasso/Pablo_Picasso_92.jpg'

width, height = load_img(base_image_path).size
img_nrows = 400
img_ncols = int(width * img_nrows/height)

vgg 19로 이미지 전처리

vgg 19는 가중치 imagenet.imagenet에서 사전 훈련된 모델 레이블 지정된 고해상도 이미지 데이터 세트

 

def preprocess_image(image_path):
	from keras.applications import vgg19
    img = load_img(image_path, target_size=(img_norws, img_ncols))
    img = img_to_array(img)
    img = np.expand_dims(img, axis =0)
    img = vgg19.preprocess_input(img)
    return img

이 함수는 load_img()함수를 사용해 image_path에서 이미지 로드하고 대상크기(img_nrows, img_ncols)로 크기 조정하고 

img_to_array()를 사용해 numpy 배열로 변환한다

 

넘파이 배열은 (batch_size, img_nrows, img_ncols, channels)인 vgg19 모델에 대한 입력의 예상 모양과 일치하도록 expand_dims()사용해 추가 차원 확장 

 

plt.figure()
plt.title('Base Image', fontsize=20)
img1 = load_img(ContentPath+'84.jpg')
plt.imshow(img1)

 

 

plt.figure()
plt.title("Style Image",fontsize=20)
img1 = load_img(StylePath+'Pablo_Picasso/Pablo_Picasso_92.jpg')
plt.imshow(img1)

 

이미지 텐서 표현 

base_image = K.variable(preprocess_image(base_image_path))
style_reference_image = K.variable(preprocess_image(style_image_path))

K.variable() 함수는 넘파이 배열중 keras 변수 만드는데 사용된다 

두 개의 keras 변수(base_image , style_reference_image) (사전처리된 이미지 포함하는 )있어야 코드실행

K.image_data_format()

위 함수는 keras 백엔드에 사용 중인 현재 데이터 형식 규칙 나타내는 문자열 반환

 

 

tensorflow 변수 프로그래밍 언어에서 사용하는 일반 변수 자리표시자는 초기값 불필요함

 

# 생성된 이미지 포함 
if K.image_data_format() == 'channels_first':
	combination_image = K.placeholder((1,3,img_nrows, img_ncols))
else:
	combination_image = K.placeholder((1,img_nrows, img_ncols, 3))

만약 데이터 형식이 channels_first인 경우, 채널 차원이 첫 번째 차원임을 의미 

(1,3,img_nrows, img_ncols) = 자리 표시자가 1개의 샘플(배치크기), 3개의 채널 및 지정된 이미지 치수가 있는 4d 텐서 

 

데이터 형식이 channels_last인 경우,

1, img_nrows, img_ncols, 3)인 Keras 자리 표시자로 생성/ 1개의 샘플(배치크기), 지정된 이미지 크기 및 3개의 채널이 있는 4d 텐서 

# 3개의 이미지 단일 keras tensor로 결합

input_tensor = K.concatenate([base_image,
                             style_reference_image,
                             combination_image
                             ], axis=0)

아k.concatenate() 함수 세 개의 이미지를 하나의 텐서로 연결하기 위해 호출 

 

[ ] 사용해 K.concatenate()함수에 리스트로 전달 / 함수에서 axis 매개변수 0으로 설정 --> 이렇게 하면 이미지 결과 텐서에서 다른 샘플로 결합될 수 있도록 첫 번째 축 따라 연결 수행되어야 함을 지정 

 

vgg 19 모델 만들기 

 

# building the vgg 19 model - 3개 이미지 
# 모델은 사전훈련된 imagenet 가중치 

from keras.applications.vgg19 import VGG19
vgg19_weights= '../input/vgg19/vgg19_weihts_tf_dim_ordering_tf_kernels_notop.h5'
model = VGG19(input_tensor = input_tensor,
			include_top= False,
            weights = vgg19_weights)
print('model loaded.')

keras 애플리케이션 모듈에서 vgg19 모델 클래스 가져옴 (클래스는 이미지 분류나 특징 추출과 같은 다양한 작업에서 사용할 수 있는 사전 훈련된 vgg 19 모델 제공)

 

vgg19_weight에 훈련된 가중치 파일 경로 할당 / 가중치는 모델 이미지에 의미 있는 기능 추출할 수 있도록 미리 훈련된 값으로 모델 초기화 하는데 사용 

 

VGG19() 함수는 모델의 인스턴스 생성하기 위해 호출 / input_tensor 매개변수는 모델에 대한 입력으로 사용되는 이전에 정의된 input_tensor로 설정

 

include_top 매개변수 false 설정 = 분류 담당하는 vgg19 모델의 완전 연결계층(상위계층) 포함되지 않아야 함 나타낸다

최상위 레이어 제외하고 vgg19 모델 기능 추출과 같은 작업에 사용하거나 더 큰 모델 아키텍처의 일부로 사용

 

 

 

 

 

 

 

 

vgg 19는 원래 분류 목적, 하지만 우리는 분류가 아닌 이미지 변환이므로 vgg19의 모든 레이어는 필요하지 않다 

 

분류에 사용되는 레이어 제거 

 

 

content_layers = ['block5_conv2']

style_layers =[ 'block1_conv',
						'block2_conv',
                        'block3_conv',
                        'block4_conv',
                        'block5_conv']
num_contetn_layers = len(content_layers)
num_style_layers = len(style_layers)

 

outputs_dict = dict([(layer.namer, layer.output) for layer in model.layers])
print(outputs_dict['block5_conv2']

출력값 

> Tensor("block5_conv2/Relu:0", shape =(3,25,40, 512), dtype=float32)

 

The content loss

def get_content_loss(base_content, target):
	return K.sum(K.square(target - base_contetn))

The style loss

import tensorflow as tf

def gram_matrix(input_tensor) :
	assert K.ndim(input_tensor)==3
    channels = int(input_tensorshape[-1])
    a = tf.reshape(input_tensor, [-1, channels])
    n = tf.shape(a)[0]
    gram = tf. matmul(a, a, transpose_a=True)
    return gram
    
def get_style_loss(style, combination):
	assert K.ndim(style) ==3
    assert K.ndim(combination) ==3
    S = gram_matrix(style)
    C = gram_matrix(combination) ==3
    channels =3
    size= img_nrows *img_ncols
    return K.sum(K.square(S-C))

content_weight = 0.025
style_weight = 1.0

#combine these loss functions into a single scalar
loss= K.variable(0.0)
layer_features = outputs_dict['block5_conv2']
base_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
print('layer feature for content layers :: '+str(layer_features))
print('base image feature ::'+str(base_image_features))
print('combination image feature for content layers:: '+str(combination_features)+'\n')
loss += content_weight * get_content_loss(base_image_features,
                                         combination_features)

feature_layers = ['block1_conv1', 'block2_conv1',
                 'block3_conv1','block4_conv1',
                 'block5_conv1']
for layer_name in feature_layers:
    layer_features = outputs_dict[layer_name]
    style_reference_features = layer_features[1, :, :, :]
    combination_features = layer_features[2, :,:,:]
    print('Layer Feature for style layers:: '+str(layer_features))
    print('style image feature ::'+str(style_reference_features))
    print('combination image feature for style layers:: '+str(combination_features)+'\n')
    sl = get_style_loss(style_reference_features, combination_features)
    loss += (style_weight /len(feature_layers))*sl

|

|

|

|

|

생략 

 

 

다양한 옵티마이저 사용했지만

ADAM 옵티마이저 사용  => 손실 최적화

 

최종 이미지 얻기

 

x_opt = preprocess_image(base_image_path)

def eval_loss_and_grads(x):
    if K.image_data_format() == 'channels_first':
        x = x.reshape((1,3, img_nrows, img_ncols))
    else:
        x = x.reshape((1, img_nrows, img_ncols, 3))
    outs = f_outputs([x])
    loss_value = outs[0]
    if len(outs[1:]) ==1:
        grad_values = outs[1].flatten().astype('float64')
    else:
        grad_values = np.array(outs[1:]).flatten().astype('float64')
    return loss_value, grad_values
plt.figure(figsize=(30,30))
plt.subplot(5,5,1)
plt.title('base image', fontsize=20)
img_base= load_img(base_image_path)
plt.axis('off')
plt.imshow(img_base)

plt.subplot(5,5,1+1)
plt.title(' style image', fontsize=20)
img_style = load_img(style_image_path)
plt.axis('off') # 숫자 제거  
plt.imshow(img_style)

plt.subplot(5,5,1+2)
plt.title('final image', fontsize=20)
plt.axis('off')
plt.imshow(imgx);

 

 

 

run_style_transfer 함수는 위 셀에서 부분적으로 논의된 위의 모든 코드의 조합

두 이미지 간의스타일 변환 후 최종 이미지 

 

 

 

 

위의 예시처럼 완벽하지는 않다..

 코드가 워낙 어려워 이해하기도 힘들다 ..!

모델을 돌려본 것에 만족 !