Chuyển kiểu thần kinh

Xem trên TensorFlow.org Chạy trong Google Colab Xem trên GitHub Tải xuống sổ ghi chép Xem mô hình TF Hub

Hướng dẫn này sử dụng học sâu để tạo một hình ảnh theo phong cách của một hình ảnh khác (bạn đã bao giờ ước mình có thể vẽ như Picasso hoặc Van Gogh?). Đây được gọi là chuyển kiểu thần kinh và kỹ thuật này được nêu trong Một thuật toán thần kinh của phong cách nghệ thuật (Gatys et al.).

Để có ứng dụng chuyển kiểu đơn giản, hãy xem hướng dẫn này để tìm hiểu thêm về cách sử dụng mô hình Cách điệu hình ảnh tùy ý được đào tạo trước từ TensorFlow Hub hoặc cách sử dụng mô hình chuyển kiểu với TensorFlow Lite .

Truyền kiểu thần kinh là một kỹ thuật tối ưu hóa được sử dụng để chụp hai hình ảnh — hình ảnh nội dung và hình ảnh tham chiếu kiểu (chẳng hạn như tác phẩm nghệ thuật của một họa sĩ nổi tiếng) —và kết hợp chúng với nhau để hình ảnh đầu ra trông giống như hình ảnh nội dung nhưng “được vẽ” theo kiểu của hình ảnh tham chiếu kiểu.

Điều này được thực hiện bằng cách tối ưu hóa hình ảnh đầu ra để khớp với thống kê nội dung của hình ảnh nội dung và thống kê kiểu của hình ảnh tham chiếu kiểu. Các số liệu thống kê này được trích xuất từ ​​các hình ảnh bằng cách sử dụng một mạng phức hợp.

Ví dụ: hãy chụp ảnh con chó này và Sáng tác 7 của Wassily Kandinsky:

Yellow Labrador Looking , từ Wikimedia Commons của Elf . Giấy phép CC BY-SA 3.0

Bây giờ sẽ như thế nào nếu Kandinsky quyết định vẽ bức tranh Con chó này độc quyền với phong cách này? Một cái gì đó như thế này?

Thành lập

Nhập và định cấu hình mô-đun

import os
import tensorflow as tf
# Load compressed models from tensorflow_hub
os.environ['TFHUB_MODEL_LOAD_FORMAT'] = 'COMPRESSED'
import IPython.display as display

import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12, 12)
mpl.rcParams['axes.grid'] = False

import numpy as np
import PIL.Image
import time
import functools
def tensor_to_image(tensor):
  tensor = tensor*255
  tensor = np.array(tensor, dtype=np.uint8)
  if np.ndim(tensor)>3:
    assert tensor.shape[0] == 1
    tensor = tensor[0]
  return PIL.Image.fromarray(tensor)

Tải xuống hình ảnh và chọn hình ảnh kiểu và hình ảnh nội dung:

content_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')
style_path = tf.keras.utils.get_file('kandinsky5.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg
196608/195196 [==============================] - 0s 0us/step
204800/195196 [===============================] - 0s 0us/step

Hình dung đầu vào

Xác định một hàm để tải một hình ảnh và giới hạn kích thước tối đa của nó là 512 pixel.

def load_img(path_to_img):
  max_dim = 512
  img = tf.io.read_file(path_to_img)
  img = tf.image.decode_image(img, channels=3)
  img = tf.image.convert_image_dtype(img, tf.float32)

  shape = tf.cast(tf.shape(img)[:-1], tf.float32)
  long_dim = max(shape)
  scale = max_dim / long_dim

  new_shape = tf.cast(shape * scale, tf.int32)

  img = tf.image.resize(img, new_shape)
  img = img[tf.newaxis, :]
  return img

Tạo một chức năng đơn giản để hiển thị hình ảnh:

def imshow(image, title=None):
  if len(image.shape) > 3:
    image = tf.squeeze(image, axis=0)

  plt.imshow(image)
  if title:
    plt.title(title)
content_image = load_img(content_path)
style_image = load_img(style_path)

plt.subplot(1, 2, 1)
imshow(content_image, 'Content Image')

plt.subplot(1, 2, 2)
imshow(style_image, 'Style Image')

png

Truyền nhanh kiểu dáng bằng TF-Hub

Hướng dẫn này trình bày thuật toán chuyển kiểu ban đầu, thuật toán này tối ưu hóa nội dung hình ảnh theo một kiểu cụ thể. Trước khi đi vào chi tiết, hãy xem cách mô hình TensorFlow Hub thực hiện điều này:

import tensorflow_hub as hub
hub_model = hub.load('https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2')
stylized_image = hub_model(tf.constant(content_image), tf.constant(style_image))[0]
tensor_to_image(stylized_image)

png

Xác định nội dung và trình bày phong cách

Sử dụng các lớp trung gian của mô hình để có được nội dungkiểu thể hiện của hình ảnh. Bắt đầu từ lớp đầu vào của mạng, một số kích hoạt lớp đầu tiên đại diện cho các tính năng cấp thấp như các cạnh và kết cấu. Khi bạn lướt qua mạng, một vài lớp cuối cùng đại diện cho các tính năng cấp cao hơn — các bộ phận của đối tượng như bánh xe hoặc mắt . Trong trường hợp này, bạn đang sử dụng kiến ​​trúc mạng VGG19, một mạng phân loại hình ảnh được đào tạo trước. Các lớp trung gian này là cần thiết để xác định sự thể hiện của nội dung và phong cách từ hình ảnh. Đối với hình ảnh đầu vào, hãy cố gắng khớp các biểu diễn mục tiêu nội dung và kiểu tương ứng tại các lớp trung gian này.

Tải VGG19 và chạy thử nghiệm nó trên hình ảnh của chúng tôi để đảm bảo nó được sử dụng đúng cách:

x = tf.keras.applications.vgg19.preprocess_input(content_image*255)
x = tf.image.resize(x, (224, 224))
vgg = tf.keras.applications.VGG19(include_top=True, weights='imagenet')
prediction_probabilities = vgg(x)
prediction_probabilities.shape
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5
574717952/574710816 [==============================] - 17s 0us/step
574726144/574710816 [==============================] - 17s 0us/step
TensorShape([1, 1000])
predicted_top_5 = tf.keras.applications.vgg19.decode_predictions(prediction_probabilities.numpy())[0]
[(class_name, prob) for (number, class_name, prob) in predicted_top_5]
[('Labrador_retriever', 0.493171),
 ('golden_retriever', 0.2366529),
 ('kuvasz', 0.036357544),
 ('Chesapeake_Bay_retriever', 0.024182785),
 ('Greater_Swiss_Mountain_dog', 0.0186461)]

Bây giờ tải một VGG19 mà không có đầu phân loại và liệt kê tên lớp

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

print()
for layer in vgg.layers:
  print(layer.name)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 2s 0us/step
80150528/80134624 [==============================] - 2s 0us/step

input_2
block1_conv1
block1_conv2
block1_pool
block2_conv1
block2_conv2
block2_pool
block3_conv1
block3_conv2
block3_conv3
block3_conv4
block3_pool
block4_conv1
block4_conv2
block4_conv3
block4_conv4
block4_pool
block5_conv1
block5_conv2
block5_conv3
block5_conv4
block5_pool

Chọn các lớp trung gian từ mạng để thể hiện phong cách và nội dung của hình ảnh:

content_layers = ['block5_conv2'] 

style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1', 
                'block4_conv1', 
                'block5_conv1']

num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

Các lớp trung gian cho phong cách và nội dung

Vậy tại sao các kết quả đầu ra trung gian này trong mạng phân loại hình ảnh được đào tạo trước của chúng tôi lại cho phép chúng tôi xác định các biểu diễn phong cách và nội dung?

Ở mức độ cao, để một mạng thực hiện phân loại ảnh (mà mạng này đã được đào tạo để làm) thì nó phải hiểu được ảnh. Điều này đòi hỏi phải lấy hình ảnh thô làm pixel đầu vào và xây dựng một biểu diễn bên trong để chuyển đổi pixel hình ảnh thô thành một sự hiểu biết phức tạp về các đặc điểm có trong hình ảnh.

Đây cũng là lý do tại sao mạng nơ-ron tích tụ có khả năng tổng quát hóa tốt: chúng có thể nắm bắt các bất biến và xác định các tính năng bên trong các lớp (ví dụ: mèo và chó) không có khả năng chống lại tiếng ồn xung quanh và các phiền toái khác. Do đó, ở đâu đó giữa nơi hình ảnh thô được đưa vào mô hình và nhãn phân loại đầu ra, mô hình đóng vai trò như một bộ trích xuất tính năng phức tạp. Bằng cách truy cập các lớp trung gian của mô hình, bạn có thể mô tả nội dung và kiểu dáng của hình ảnh đầu vào.

Xây dựng mô hình

Các mạng trong tf.keras.applications được thiết kế để bạn có thể dễ dàng trích xuất các giá trị lớp trung gian bằng cách sử dụng API chức năng Keras.

Để xác định một mô hình bằng cách sử dụng API chức năng, hãy chỉ định các đầu vào và đầu ra:

model = Model(inputs, outputs)

Hàm sau đây xây dựng mô hình VGG19 trả về danh sách các đầu ra của lớp trung gian:

def vgg_layers(layer_names):
  """ Creates a vgg model that returns a list of intermediate output values."""
  # Load our model. Load pretrained VGG, trained on imagenet data
  vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
  vgg.trainable = False

  outputs = [vgg.get_layer(name).output for name in layer_names]

  model = tf.keras.Model([vgg.input], outputs)
  return model

Và để tạo mô hình:

style_extractor = vgg_layers(style_layers)
style_outputs = style_extractor(style_image*255)

#Look at the statistics of each layer's output
for name, output in zip(style_layers, style_outputs):
  print(name)
  print("  shape: ", output.numpy().shape)
  print("  min: ", output.numpy().min())
  print("  max: ", output.numpy().max())
  print("  mean: ", output.numpy().mean())
  print()
block1_conv1
  shape:  (1, 336, 512, 64)
  min:  0.0
  max:  835.5256
  mean:  33.97525

block2_conv1
  shape:  (1, 168, 256, 128)
  min:  0.0
  max:  4625.8857
  mean:  199.82687

block3_conv1
  shape:  (1, 84, 128, 256)
  min:  0.0
  max:  8789.239
  mean:  230.78099

block4_conv1
  shape:  (1, 42, 64, 512)
  min:  0.0
  max:  21566.135
  mean:  791.24005

block5_conv1
  shape:  (1, 21, 32, 512)
  min:  0.0
  max:  3189.2542
  mean:  59.179478

Tính toán phong cách

Nội dung của hình ảnh được thể hiện bằng các giá trị của các bản đồ đối tượng trung gian.

Hóa ra, phong cách của một hình ảnh có thể được mô tả bằng các phương tiện và mối tương quan trên các bản đồ đối tượng địa lý khác nhau. Tính toán ma trận Gram bao gồm thông tin này bằng cách lấy tích ngoài của vectơ đặc trưng với chính nó tại mỗi vị trí và lấy trung bình sản phẩm bên ngoài đó trên tất cả các vị trí. Ma trận Gram này có thể được tính toán cho một lớp cụ thể như sau:

\[G^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ}\]

Điều này có thể được thực hiện ngắn gọn bằng cách sử dụng hàm tf.linalg.einsum :

def gram_matrix(input_tensor):
  result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor)
  input_shape = tf.shape(input_tensor)
  num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32)
  return result/(num_locations)

Trích xuất phong cách và nội dung

Xây dựng một mô hình trả về kiểu và nội dung.

class StyleContentModel(tf.keras.models.Model):
  def __init__(self, style_layers, content_layers):
    super(StyleContentModel, self).__init__()
    self.vgg = vgg_layers(style_layers + content_layers)
    self.style_layers = style_layers
    self.content_layers = content_layers
    self.num_style_layers = len(style_layers)
    self.vgg.trainable = False

  def call(self, inputs):
    "Expects float input in [0,1]"
    inputs = inputs*255.0
    preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
    outputs = self.vgg(preprocessed_input)
    style_outputs, content_outputs = (outputs[:self.num_style_layers],
                                      outputs[self.num_style_layers:])

    style_outputs = [gram_matrix(style_output)
                     for style_output in style_outputs]

    content_dict = {content_name: value
                    for content_name, value
                    in zip(self.content_layers, content_outputs)}

    style_dict = {style_name: value
                  for style_name, value
                  in zip(self.style_layers, style_outputs)}

    return {'content': content_dict, 'style': style_dict}

Khi được gọi trên một hình ảnh, mô hình này trả về ma trận gam (kiểu) của style_layers và nội dung của content_layers :

extractor = StyleContentModel(style_layers, content_layers)

results = extractor(tf.constant(content_image))

print('Styles:')
for name, output in sorted(results['style'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
  print()

print("Contents:")
for name, output in sorted(results['content'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
Styles:
   block1_conv1
    shape:  (1, 64, 64)
    min:  0.0055228462
    max:  28014.557
    mean:  263.79022

   block2_conv1
    shape:  (1, 128, 128)
    min:  0.0
    max:  61479.496
    mean:  9100.949

   block3_conv1
    shape:  (1, 256, 256)
    min:  0.0
    max:  545623.44
    mean:  7660.976

   block4_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  4320502.0
    mean:  134288.84

   block5_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  110005.37
    mean:  1487.0378

Contents:
   block5_conv2
    shape:  (1, 26, 32, 512)
    min:  0.0
    max:  2410.8796
    mean:  13.764149

Chạy dốc xuống

Với trình trích xuất kiểu và nội dung này, bây giờ bạn có thể triển khai thuật toán chuyển kiểu. Thực hiện điều này bằng cách tính toán sai số bình phương trung bình cho đầu ra hình ảnh của bạn so với từng mục tiêu, sau đó lấy tổng có trọng số của những tổn thất này.

Đặt các giá trị mục tiêu nội dung và phong cách của bạn:

style_targets = extractor(style_image)['style']
content_targets = extractor(content_image)['content']

Xác định một tf.Variable để chứa hình ảnh cần tối ưu hóa. Để thực hiện việc này nhanh chóng, hãy khởi tạo nó bằng hình ảnh nội dung ( tf.Variable phải có cùng hình dạng với hình ảnh nội dung):

image = tf.Variable(content_image)

Vì đây là hình ảnh nổi, hãy xác định một hàm để giữ các giá trị pixel trong khoảng từ 0 đến 1:

def clip_0_1(image):
  return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

Tạo một trình tối ưu hóa. Bài báo đề xuất LBFGS, nhưng Adam cũng hoạt động ổn:

opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

Để tối ưu hóa điều này, hãy sử dụng kết hợp có trọng số của hai tổn thất để có được tổng tổn thất:

style_weight=1e-2
content_weight=1e4
def style_content_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) 
                           for name in style_outputs.keys()])
    style_loss *= style_weight / num_style_layers

    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) 
                             for name in content_outputs.keys()])
    content_loss *= content_weight / num_content_layers
    loss = style_loss + content_loss
    return loss

Sử dụng tf.GradientTape để cập nhật hình ảnh.

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

Bây giờ hãy chạy một vài bước để kiểm tra:

train_step(image)
train_step(image)
train_step(image)
tensor_to_image(image)

png

Vì nó đang hoạt động, hãy thực hiện tối ưu hóa lâu hơn:

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='', flush=True)
  display.clear_output(wait=True)
  display.display(tensor_to_image(image))
  print("Train step: {}".format(step))

end = time.time()
print("Total time: {:.1f}".format(end-start))

png

Train step: 1000
Total time: 21.3

Tổng số biến thể mất mát

Một nhược điểm của việc triển khai cơ bản này là nó tạo ra rất nhiều tạo tác tần số cao. Giảm những điều này bằng cách sử dụng thuật ngữ chính quy rõ ràng trên các thành phần tần số cao của hình ảnh. Trong chuyển kiểu, điều này thường được gọi là tổng tổn thất biến thể :

def high_pass_x_y(image):
  x_var = image[:, :, 1:, :] - image[:, :, :-1, :]
  y_var = image[:, 1:, :, :] - image[:, :-1, :, :]

  return x_var, y_var
x_deltas, y_deltas = high_pass_x_y(content_image)

plt.figure(figsize=(14, 10))
plt.subplot(2, 2, 1)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Original")

plt.subplot(2, 2, 2)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Original")

x_deltas, y_deltas = high_pass_x_y(image)

plt.subplot(2, 2, 3)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Styled")

plt.subplot(2, 2, 4)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Styled")

png

Điều này cho thấy các thành phần tần số cao đã tăng lên như thế nào.

Ngoài ra, thành phần tần số cao này về cơ bản là một bộ dò cạnh. Bạn có thể nhận được đầu ra tương tự từ bộ dò cạnh Sobel, ví dụ:

plt.figure(figsize=(14, 10))

sobel = tf.image.sobel_edges(content_image)
plt.subplot(1, 2, 1)
imshow(clip_0_1(sobel[..., 0]/4+0.5), "Horizontal Sobel-edges")
plt.subplot(1, 2, 2)
imshow(clip_0_1(sobel[..., 1]/4+0.5), "Vertical Sobel-edges")

png

Sự mất mát chính quy liên quan đến điều này là tổng bình phương của các giá trị:

def total_variation_loss(image):
  x_deltas, y_deltas = high_pass_x_y(image)
  return tf.reduce_sum(tf.abs(x_deltas)) + tf.reduce_sum(tf.abs(y_deltas))
total_variation_loss(image).numpy()
149402.94

Điều đó đã chứng minh những gì nó làm. Nhưng không cần phải tự mình triển khai, TensorFlow bao gồm một triển khai tiêu chuẩn:

tf.image.total_variation(image).numpy()
array([149402.94], dtype=float32)

Chạy lại tối ưu hóa

Chọn trọng số cho total_variation_loss :

total_variation_weight=30

Bây giờ hãy đưa nó vào hàm train_step :

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)
    loss += total_variation_weight*tf.image.total_variation(image)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

Khởi động lại biến tối ưu hóa:

image = tf.Variable(content_image)

Và chạy tối ưu hóa:

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='', flush=True)
  display.clear_output(wait=True)
  display.display(tensor_to_image(image))
  print("Train step: {}".format(step))

end = time.time()
print("Total time: {:.1f}".format(end-start))

png

Train step: 1000
Total time: 22.4

Cuối cùng, lưu kết quả:

file_name = 'stylized-image.png'
tensor_to_image(image).save(file_name)

try:
  from google.colab import files
except ImportError:
   pass
else:
  files.download(file_name)

Tìm hiểu thêm

Hướng dẫn này trình bày thuật toán chuyển kiểu ban đầu. Để có ứng dụng chuyển kiểu đơn giản, hãy xem hướng dẫn này để tìm hiểu thêm về cách sử dụng mô hình chuyển kiểu hình ảnh tùy ý từ TensorFlow Hub .