Neural Style Transfer

  |   Source
In [1]:
import keras
keras.__version__
Using TensorFlow backend.
Out[1]:
'2.2.4'

الشرح التالي يمكن تشغيله تفاعليا بزيارة الرابط

وهو من ضمن سلسلة الدروس هذه

نقل النمط العصبوني

إلى جانب الحلم العميق ، تطور كبير آخر حدث في صيف عام 2015 وهو نقل النمط العصبوني المتعلق بالتعديل على الصور وكان من أوائل من بحثوا فيه Gatys et al.

خوارزمية نقل النمط العصبوني تخللها العديد من التحسينات والتعديلات مما أعطى أنواعاً مختلفة منها منذ تقديمها الأولي بما يتضمن تطبيق هاتف ذكي منتشر ، يدعى بريزما. سيركز هذا القسم للتبسيط فقط على الصياغة المقدمة بالورقة الأصلية.

يتكون نقل النمط العصبوني من عملية نقل النمط من صورة مرجعية أو الأسلوب لصورة هدف ، مع الحفاظ على محتوى الصورة الهدف.

المقصود بالنمط هو النسيج واللون والنمط البصري بالصورة ، بأحجام مكانية مختلفة بينما المحتوى هو البنى الأعلى للصورة. مثلاً ضربات الفرشاة الزرقاء والحمراء تعتبر نمطاً style كما يبدو بالصورة أعلاه للوحة فان جوخ، بينما البناء بالصورة الأخرى هو المحتوى content.

فكرة نقل النمط والمرتبطة بشكل وثيق بتوليد النسيج كانت موضع بحث بشكل سابق كثيراً بمجالات معالجة الصور قبل إنشاء الشبكة العصبونية المرتبطة بذلك . أياً يكن فإن تطبيقات نقل النمط القائمة على التعلم العميق أعطت نتائج غير مسبوقة مقارنة بالحلول الناتجة بمجال رؤية الحاسب وأحدثت ثورة رائعة بمجال التطبيقات الإبداعية بمجال رؤية الحاسب.

النقطة الأساسية لتطبيقات نقل النمط هي نفسها لكل خوارزميات التعلم العميق وهي تعريف تابع خسارة يحدد المرغوب بإنجازه كمسألة تصغير . و نريد إنجازه هنا للإبقاء على المحتوى للصورة الهدف مع تبني نمط الصورة المرجعية. وإذا أمكننا التعبير رياضياً عن المحتوى والنمط لكان تابع الخسارة المطلوب كالتالي:

``` loss = distance(style(reference_image) - style(generated_image)) + distance(content(original_image) - content(generated_image)) ```

حيث distance هو تابع ناظم لحساب المسافة ، أما content هو تابع يأخذ الصورة ويحسب تمثيلاً لمحتواها و style هو تابع يأخذ صورة ويحسب تمثيلاً لنمطها.

وتقليل الخطأ سيعني جعل نمط الصورة الناتجة أقرب ما يكون لنمط الصورة المرجعية وبنفس الوقت جعل محتوى الصورة الناتجة أقرب مايكون لمحتوى الصورة الأصلية وبالتالي إنجاز نقل المحتوى حسب التعريف.

ملاحظة أساسية من قبل غاتس هي أن الشبكات الترابطية العصبونية العميقة تقدم وسيلة دقيقة لتعريف المحتوى والنمط بدقة بشكل رياضي, فلنر كيف.

خسارة المحتوى:

كما مر مسبقاً فقيم التفعيل (الخرج) للطبقات الأولى بالشبكة تحتوي على معلومات محلية بينما قيم التفعيل للطبقات الأعلى تحتوي على معلومات أكثر تجريداً و شمولاً عن البيانات . وبصياغة هذا بطريقة أخرى يمكن القول أن التفعيلات activations للطبقات المختلفة بالشبكة الترابطية تقدم فصلاً لمحتوى الصورة على قياسات مكانية مختلفة . وبالتالي نتوقع أن محتوى الصورة وهو أكثر شمولية وتجرداً سيتم التقاطه من قبل الطبقة العليا للشبكة

أحدى الإمكانات لتابع خسارة المحتوى سيكون الفرق بين قيم التفعيل للطبقة الأحيرة بشبكة مسبقة التدريب عند ادخال الصورة الهدف ، وقيم الخرج لنفس الشبكة عند ادخال الصورة الناتجة. هذا سيضمن أن المحتوى باعتبار أنه ما تراه الطبقة الأخيرة من تلك الشبكة سيكون أقرب ما يكون بين تلك الصورتين وهذا بالتالي سيعمل كطريقة للحفاظ على محتوى الصورة .

خسارة النمط

بينما كانت خسارة المحتوى تحدد من قبل طبقة واحدة ، فخسارة النمط حسب تعريف المؤلف الأصلي لهذه الطريقة ستعرف على أكثر من طبقة للشبكة الترابطية والهدف من ذلك هو التقاط النمط عبر كل القياسات للطبقات وليس فقط قياساً واحداً.

بالنسبة لخسارة النمط فقد تم استخدام جداء غرام gram لتفعيل الطبقة كدلالة على ذلك وهو الجداء الداخلي لخرائط السمات لنفس الطبقة المعطاة . ويمكن فهم الجداء الداخلي كخريطة للتداخلات بين سمات طبقة ما . وهذه التداخلات للسمات تلتقط احصائيات حول الانماط الموجودة عند قياس محدد ، والتي تعود عملياً لظهور نسيج عند ذلك القياس.

وبالتالي فإن خسارة النمط تهدف للحفاظ على تداخلات متشابهة ضمن خرج الطبقات المختلفة عبر الصورة المرجعية للنمط والصورة الناتجة . وهذا بالتالي سيضمن أن النسيج الموجود على سويات مكانية مختلفة سيبدو متشابهاً عبر الصورة المرجعية الصورة الناتجة.

باختصار

باختصار يمكننا استخدام شبكة ترابطية مسبقة التدريب لتعريف خسارة تقوم ب::

  • حفظ المحتوى بالابقاء على تفعيلات متشابهة بالطبقة العليا لكل من صورة الهدف والصورة المولدة . الشبكة الترابطية يجب أن ترى كلاً من الصورةين بنفس المحتوى حسب هذا المعيار.

  • حفظ النمط بالابقاء على تداخلات متشابهة ضمن التفعيلات لكل من الطبقات السفلية والعلوية . وبالفعل فتداخلات السمات تلتقط النسيج . والصورة المرجعية والناتجة يجب أن تتشاركا نفس النسيج على مختلف القياسات.

والآن لنلقي نظرة على التطبيق البرمجي للشبكة الأصلية لنقل النمط من عام 2015. وكما سيبدو فإن الشبكة تتشارك العديد من نقاط التشابه مع خوارزمية الحلم العميق.

نقل النمط العصبوني في كيراس:

يمكن تطبيق الطريقة باستخدام أي شبكة ترابطية مسبقة التدريب . وهنا سنستخدم شبكة VGG19 المستخدمة بالبحث الأصلي وهي نسخة مبسطة عن شبكة VGG16 .

التالي هو الطريقة العامة:

  • جهز الشبكة VGG19 التي يجب أن تعطي تفعيل جميع الطبقات لكل من الصورة المرجعية والصورة الهدف والصورة الناتجة بنفس الوقت.

  • استخدم تفعيلات الطبقات المحسوبة لتلك الصور الثلاثة لتعريف تابع الخسارة الموصوف أعلاه والذي سيتم البحث عن قيمته الأصغرية للحصول على نقل النمط.

  • أعد عملية التدرج التنازلي gardiant decent لتقليص تابع الخسارة.

فلنبدأ بتحديد مساري الصورتين المرجعية والهدف . ولنتأكد من أن جميع الصور متقاربة القياسات لأن الاختلاف الكبير بالقياسات سيجعل عملية نقل النمط أصعب ، فسنقوم بإعادة تكبير أو تصغير الصور لارتفاع مشترك وهو 720 بكسل.

In [3]:
from google.colab import drive
drive.mount('/content/drive')
Go to this URL in a browser:

Enter your authorization code:
··········
Mounted at /content/drive
In [0]:
from keras.preprocessing.image import load_img, img_to_array

# This is the path to the image you want to transform.
target_image_path = 'drive/My Drive/Colab Notebooks/images/target.jpg'
# This is the path to the style image.
style_reference_image_path = 'drive/My Drive/Colab Notebooks/images/style_ref.jpg'

# Dimensions of the generated picture.
width, height = load_img(target_image_path).size
img_height = 720
img_width = int(width * img_height / height)

وسنحتاج بعض التوابع المساعدة لتحميل والمعالجة المسبقة للصوروالمعالجة اللاحقة والتي ستدخل للشبكة وتخرج منها وهي شبكة VGG19.

In [0]:
import numpy as np
from keras.applications import vgg19

def preprocess_image(image_path):
    img = load_img(image_path, target_size=(img_height, img_width))
    img = img_to_array(img)
    img = np.expand_dims(img, axis=0)
    img = vgg19.preprocess_input(img)
    return img

def deprocess_image(x):
    # Remove zero-center by mean pixel
    x[:, :, 0] += 103.939
    x[:, :, 1] += 116.779
    x[:, :, 2] += 123.68
    # 'BGR'->'RGB'
    x = x[:, :, ::-1]
    x = np.clip(x, 0, 255).astype('uint8')
    return x

لاعداد الشبكة VGG19 يجب ادخال ثلاث صور معاً لها، الصورة المرجعية reference والصورة الهدف target وحافظ المكان للصورة التي سيتم توليدها placeholder . وحافظ المكان هو ببساطة مصفوفة رمزية ، والقيم لها مقدمة خارجياً عبر مصفوفة Numpy . الصورتين المرجعية والهدف ثابتين وبالتالي نعرفهما بوساطة K.constant بينما القيم الموجودة في حافظ المكان للصورة المولدة ستتغير مع الزمن.

In [9]:
from keras import backend as K

target_image = K.constant(preprocess_image(target_image_path))
style_reference_image = K.constant(preprocess_image(style_reference_image_path))

# This placeholder will contain our generated image
combination_image = K.placeholder((1, img_height, img_width, 3))

# We combine the 3 images into a single batch
input_tensor = K.concatenate([target_image,
                              style_reference_image,
                              combination_image], axis=0)

# We build the VGG19 network with our batch of 3 images as input.
# The model will be loaded with pre-trained ImageNet weights.
model = vgg19.VGG19(input_tensor=input_tensor,
                    weights='imagenet',
                    include_top=False)
print('Model loaded.')
WARNING: Logging before flag parsing goes to stderr.
W0724 15:00:30.995568 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:517: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

W0724 15:00:31.003159 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:74: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.

W0724 15:00:31.005022 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4138: The name tf.random_uniform is deprecated. Please use tf.random.uniform instead.

W0724 15:00:31.041902 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3976: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.

Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 1s 0us/step
W0724 15:00:32.949206 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:174: The name tf.get_default_session is deprecated. Please use tf.compat.v1.get_default_session instead.

W0724 15:00:32.950293 140440832923520 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:181: The name tf.ConfigProto is deprecated. Please use tf.compat.v1.ConfigProto instead.

Model loaded.

فلنعرف تابع خسارة المحتوى والذي يهدف لجعل الطبقة الأخيرة لشبكة VGG19 تتشارك نفس النظرة لكل من الصورة الهدف والصورة المولدة.

In [0]:
def content_loss(base, combination):
    return K.sum(K.square(combination - base))

ومن ثم نعرف تابع خسارة النمط، وهو يستخدم تابعاً مساعداً لحساب مصفوفة غرام لمصفوفة دخل أي خريطة التداخلات الموجودة بمصفوفة السمات الأصلية .

In [0]:
def gram_matrix(x):
    features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))
    gram = K.dot(features, K.transpose(features))
    return gram


def style_loss(style, combination):
    S = gram_matrix(style)
    C = gram_matrix(combination)
    channels = 3
    size = img_height * img_width
    return K.sum(K.square(S - C)) / (4. * (channels ** 2) * (size ** 2))

وسنضيف للخسارتين أعلاه تابعاً إضافياً لخسارة التغايرات الكلية . والمقصود من هذا هو تعزيز الاستمرارية المكانية بالصورة الناتجة وبالتالي تجنب النتائج الكثيرة التشوهات . ويمكنك تفسيره أيضاً كخسارة تنظيم.

In [0]:
def total_variation_loss(x):
    a = K.square(
        x[:, :img_height - 1, :img_width - 1, :] - x[:, 1:, :img_width - 1, :])
    b = K.square(
        x[:, :img_height - 1, :img_width - 1, :] - x[:, :img_height - 1, 1:, :])
    return K.sum(K.pow(a + b, 1.25))

الخسارة التي نصغرها هي متوسط موزون لتلك الخسارت الثلاث . ولحساب خسارة المحتوى نأخذ فقط الطبقة الأخيرة وهي block5_conv2 . بينما لخسارة النمط سنستخدم قائمة من الطبقات تمتد من المستوى المنخفض للمرتفع. وسنضيف الخسارة الكلية للتغايرات بالنهاية.

بالاعتماد على الصورة المرجعية للنمط والصورة للمحتوى فإنه قد يكون من المناسب ضبط قيمة content_weight وهو يمثل وزن مساهمة خسارة المحتوى نسبة للخسارة الكلية، فقيمة كبيرة له تعني أن الصورة الهدف ستبدو بشكل أوضح بالنتيجة.

In [13]:
# Dict mapping layer names to activation tensors
outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])
# Name of layer used for content loss
content_layer = 'block5_conv2'
# Name of layers used for style loss
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1',
                'block4_conv1',
                'block5_conv1']
# Weights in the weighted average of the loss components
total_variation_weight = 1e-4
style_weight = 1.
content_weight = 0.025

# Define the loss by adding all components to a `loss` variable
loss = K.variable(0.)
layer_features = outputs_dict[content_layer]
target_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss += content_weight * content_loss(target_image_features,
                                      combination_features)
for layer_name in style_layers:
    layer_features = outputs_dict[layer_name]
    style_reference_features = layer_features[1, :, :, :]
    combination_features = layer_features[2, :, :, :]
    sl = style_loss(style_reference_features, combination_features)
    loss += (style_weight / len(style_layers)) * sl
loss += total_variation_weight * total_variation_loss(combination_image)
W0724 15:01:16.424152 140440832923520 variables.py:2429] Variable += will be deprecated. Use variable.assign_add if you want assignment to the variable value or 'x = x + y' if you want a new python Tensor object.

وأخيراً، تبدأ عملية التدرج التنازلي gradiant decent . بالورقة الأصلية تم استخدام خوارزمية L-BFGS وهذا أيضاً ما سنستخدمه هنا. وهذا اختلاف أساسي عن طريقة الحلم العميق. هذه الخوارزمية تأتي جاهزة مع مكتبة Scipy أياً يكن هناك نقطتان تحدان من فعالية التنفيذ وهما:

  • تتطلب هذه الطريقة تمرير تابع الخسارة بالإضافة لتابع التدرج كقيمتين منفصلتين

  • يمكن لها أن تطبق لأشعة أحادية البعد فقط , ولكن مالدينا هنا هو صور ممثلة كمصفوفات بثلاثة أبعاد.

سيكون من غير الفعال نهائياً حساب تابع الخسارة وتابع التدرج بشكل منفصل عن بعضهما، لأن الحسابات ستكرر بينهما . وسيكون هذا أبطأ مرتين من حسابهما معاً بشكل مشترك. ولحل هذه المشكلة , سنستخدم صفاً Evaluator لحساب تابع الخسارة والتدرج بنفس الوقت ، حيث سيعيد قيمة الخسارة عند مناداته للمرة الأولى ويخزن قيمة التدرج للمناداة التالية.

In [14]:
# Get the gradients of the generated image wrt the loss
grads = K.gradients(loss, combination_image)[0]

# Function to fetch the values of the current loss and the current gradients
fetch_loss_and_grads = K.function([combination_image], [loss, grads])


class Evaluator(object):

    def __init__(self):
        self.loss_value = None
        self.grads_values = None

    def loss(self, x):
        assert self.loss_value is None
        x = x.reshape((1, img_height, img_width, 3))
        outs = fetch_loss_and_grads([x])
        loss_value = outs[0]
        grad_values = outs[1].flatten().astype('float64')
        self.loss_value = loss_value
        self.grad_values = grad_values
        return self.loss_value

    def grads(self, x):
        assert self.loss_value is not None
        grad_values = np.copy(self.grad_values)
        self.loss_value = None
        self.grad_values = None
        return grad_values

evaluator = Evaluator()
W0724 15:01:23.026882 140440832923520 deprecation.py:323] From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_grad.py:1205: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where

أخيراً، يمكننا تشغيل عملية التدرج التصاعدي باستخدام خوارزمية L-BFGS مع حفظ الصورة التي يتم توليدها بعد كل تكرار للخوارزمية ، هنا تكرار واحد سيمثل عشرين خطوة للتدرج التصاعدي.

In [16]:
from scipy.optimize import fmin_l_bfgs_b
from imageio import imsave
import time

result_prefix = 'style_transfer_result'
iterations = 20

# Run scipy-based optimization (L-BFGS) over the pixels of the generated image
# so as to minimize the neural style loss.
# This is our initial state: the target image.
# Note that `scipy.optimize.fmin_l_bfgs_b` can only process flat vectors.
x = preprocess_image(target_image_path)
x = x.flatten()
for i in range(iterations):
    print('Start of iteration', i)
    start_time = time.time()
    x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x,
                                     fprime=evaluator.grads, maxfun=20)
    print('Current loss value:', min_val)
    # Save current generated image
    img = x.copy().reshape((img_height, img_width, 3))
    img = deprocess_image(img)
    fname = result_prefix + '_at_iteration_%d.png' % i
    imsave(fname, img)
    end_time = time.time()
    print('Image saved as', fname)
    print('Iteration %d completed in %ds' % (i, end_time - start_time))
Start of iteration 0
Current loss value: 512375260.0
Image saved as style_transfer_result_at_iteration_0.png
Iteration 0 completed in 33s
Start of iteration 1
Current loss value: 258191840.0
Image saved as style_transfer_result_at_iteration_1.png
Iteration 1 completed in 22s
Start of iteration 2
Current loss value: 190112450.0
Image saved as style_transfer_result_at_iteration_2.png
Iteration 2 completed in 22s
Start of iteration 3
Current loss value: 156493230.0
Image saved as style_transfer_result_at_iteration_3.png
Iteration 3 completed in 23s
Start of iteration 4
Current loss value: 135198930.0
Image saved as style_transfer_result_at_iteration_4.png
Iteration 4 completed in 22s
Start of iteration 5
Current loss value: 121511290.0
Image saved as style_transfer_result_at_iteration_5.png
Iteration 5 completed in 22s
Start of iteration 6
Current loss value: 111693880.0
Image saved as style_transfer_result_at_iteration_6.png
Iteration 6 completed in 22s
Start of iteration 7
Current loss value: 104104480.0
Image saved as style_transfer_result_at_iteration_7.png
Iteration 7 completed in 22s
Start of iteration 8
Current loss value: 97795760.0
Image saved as style_transfer_result_at_iteration_8.png
Iteration 8 completed in 22s
Start of iteration 9
Current loss value: 92751070.0
Image saved as style_transfer_result_at_iteration_9.png
Iteration 9 completed in 22s
Start of iteration 10
Current loss value: 88204340.0
Image saved as style_transfer_result_at_iteration_10.png
Iteration 10 completed in 22s
Start of iteration 11
Current loss value: 84270200.0
Image saved as style_transfer_result_at_iteration_11.png
Iteration 11 completed in 22s
Start of iteration 12
Current loss value: 80856530.0
Image saved as style_transfer_result_at_iteration_12.png
Iteration 12 completed in 22s
Start of iteration 13
Current loss value: 78069990.0
Image saved as style_transfer_result_at_iteration_13.png
Iteration 13 completed in 22s
Start of iteration 14
Current loss value: 74613130.0
Image saved as style_transfer_result_at_iteration_14.png
Iteration 14 completed in 22s
Start of iteration 15
Current loss value: 71759180.0
Image saved as style_transfer_result_at_iteration_15.png
Iteration 15 completed in 22s
Start of iteration 16
Current loss value: 69113590.0
Image saved as style_transfer_result_at_iteration_16.png
Iteration 16 completed in 23s
Start of iteration 17
Current loss value: 66832824.0
Image saved as style_transfer_result_at_iteration_17.png
Iteration 17 completed in 23s
Start of iteration 18
Current loss value: 64623156.0
Image saved as style_transfer_result_at_iteration_18.png
Iteration 18 completed in 22s
Start of iteration 19
Current loss value: 62755680.0
Image saved as style_transfer_result_at_iteration_19.png
Iteration 19 completed in 23s

وهنا تبدو النتيجة لكل من صورة المحتوى والنمط والصورة الناتجة

In [17]:
from matplotlib import pyplot as plt

# Content image
plt.imshow(load_img(target_image_path, target_size=(img_height, img_width)))
plt.figure()

# Style image
plt.imshow(load_img(style_reference_image_path, target_size=(img_height, img_width)))
plt.figure()

# Generate image
plt.imshow(img)
plt.show()

لاحظ أن ما تقوم به هذه الطريقة هو فقط إعادة صياغة نسيج صورة أو نقل النسيج. وستعمل بشكل أفضل مع صور مرجعية للنسيج واضحة النسيج ومتكررة النمط ومع صور محتوى أي هدف لا تطلب مستوى عالي من التفاصيل لتكون واضحة. وهكذا فلن يكون من الممكن إنجاز نقل نمط مجرد عند نقل نمط صورة شخصية لصورة شخصية اخرى مثلاً . وهذه الخوارزمية أقرب لمعالجة الإشارة الكلاسيكية من الذكاء الصنعي لذلك لا تنتظر منها العمل كالسحر.

لاحظ أيضاً أن هذا البرنامج بطيء. ولكن هذا التحويل المجرى هنا هو بسيط لدرجة تمكن شبكة ترابطية أمامية صغيرة من تعلمه طالما كان لديك بيانات تدريب كافية. وبالتالي يمكن انجاز نقل النمط السريع أولاً عبر صرف الكثير من حلقات الحساب لإعطاء بيانات دخل وخرج لصورة نمط مرجعية محددة باستخدام طريقة كأعلاه ومن ثم تدريب شبكة ترابطية لتعلم هذا التحويل محدد النمط. وبعد إنجاز هذا فإن نقل نمط لصورة ما يتم مباشرة وسيكون فقط تمرير أمامي لهذه الشبكة الترابطية الصغيرة.

الاستنتاجات:

  • نقل النمط يعني إنشاء صورة جديدة تحفظ محتوى الصورة الأصلية الهدف وتبقي أيضاً على نمظ الصورة المرجعية.

  • المحتوى يمكن أن يتم التقاطه بالتفعيل للطبقات العليا للشبكة الترابطية

  • يمكن للنمط أن يتم التعبير عنه بالتداخلات الداخلية لتفعيلات الطبقات المختلفة للشبكة الترابطية.

  • وهكذا يمكن للتعلم العميق أن يحول مسألة نقل النمط لمسألة أمثلة باستخدام تابع خسارة معرف على شبكة ترابطية مسبقة التدريب.

  • بدءاً من الفكرة الأصلية العديد من الطرق والتحسينات يمكن تطويرها للحصول على تطبيقات إضافية.

Comments powered by Disqus