gans 11

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

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

 نقل النمط العصبي (Neural Style Transfer)

الفكرة ببساطة  أننا نريد تقليل دالة الخسارة التي هي مجموع موزون لثلاثة أجزاء متميزة:

  • خسارة المحتوى (Content loss)

نود أن تحتوي الصورة المدمجة على نفس محتوى الصورة الأساسية.

  • خسارة النمط (Style loss)

نود أن يكون للصورة المدمجة نفس النمط العام لصورة النمط.

  • إجمالي خسارة التباين (Total variance loss)

نود أن تظهر الصورة المدمجة بشكل سلس وانسيابي و غير مبكسل.

نقوم بتقليل هذه الخسارة من خلال النزول الاشتقاقي (gradient descent)  – أي أننا نقوم بتحديث قيمة كل نقطة ضوئية (Pixel) بمقدار يتناسب مع الإشتقاق  السالب لدالة الخسارة ، على مدى العديد من التكرارات. بهذه الطريقة ، تتناقص الخسارة تدريجياً مع كل تكرار وينتهي بنا الأمر بصورة تدمج محتوى صورة مع نمط أخرى.

يختلف تحسين الناتج المولد عن طريق النزول الاشتقاقي من الطريقة التي تعاملنا بها مع مسائل النمذجة التوليدية حتى الآن. في السابق ، قمنا بتدريب شبكة عصبية عميقة مثل شبكات الترميز التلقائي المتغيره ( VAE ) أو  شبكات الخصومة التوليدية (GAN) عن طريق الإنتشار العكسي (backpropagating)  للخطأ  عبر الشبكة بأكملها للتعلم من مجموعة البيانات التدريبية وتعميم المعلومات التي تم تعلمها لإنشاء صور جديدة. هنا ، لا يمكننا اتباع هذا النهج لأن لدينا صورتين فقط للعمل بهما ، الصورة الأساسية وصورة النمط. ومع ذلك ، كما سنرى ، لا يزال بإمكاننا الاستفادة من شبكة عصبية عميقة مدربة مسبقًا لتوفير معلومات حيوية حول كل صورة داخل دالة الخسارة.

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

خسارة المحتوى (Content Loss)

تقيس خسارة المحتوى مدى اختلاف الصورتين من حيث الموضوع والموضع العام لمحتواهما.

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

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

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

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

تسمى الشبكة المدربة مسبقاً التي سنستخدمها VGG19. هذه شبكة عصبية تلافيفية مكونة من 19 طبقة تم تدريبها على تصنيف الصور إلى ألف فئة  مدربة على أكثر من مليون صورة من مجموعة بيانات ImageNet

و هنا جزء من البرمجة المستخدمة لحساب  خسارة المحتوى بين صورتين :

from keras.applications import vgg19 #1
from keras import backend as K

base_image_path = '/path_to_images/base_image.jpg'
style_reference_image_path = '/path_to_images/styled_image.jpg'

content_weight = 0.01

base_image = K.variable(preprocess_image(base_image_path)) 
style_reference_image = K.variable(preprocess_image(style_reference_image_path))#2
combination_image = K.placeholder((1, img_nrows, img_ncols, 3))

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

model = vgg19.VGG19(input_tensor=input_tensor,
                    weights='imagenet', include_top=False) #4

outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])
layer_features = outputs_dict['block5_conv2'] #5

base_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :] #6

def content_loss(content, gen):
    return K.sum(K.square(gen - content))

content_loss = content_weight * content_loss(base_image_features
                                           , combination_features) #7

1- تحتوي مكتبة Keras على نموذج VGG19 مُدرب مسبقًا يمكن استيراده.

2- نحدد متغيرين من Keras للاحتفاظ بالصورة الأساسية وصورة النمط والعنصر النائب الذي سيحتوي على الصورة المدمجة التي تم إنشاؤها.

3-مدخل نموذج VGG19 عبارة عن وصل الصور الثلاث.

4- نقوم هنا بتحديد  الإدخال والأوزان التي نرغب في إستخدامهاا. المعييار include_top = False يعني أننا لا نحتاج إلى تحميل أوزان الطبقات الكثيفة (كاملة الإتصال) النهائية  للشبكات التي تؤدي إلى تصنيف الصورة. هذا لأننا مهتمون فقط بطبقات اللف الرياضية السابقة ، التي تلتقط السمات عالية المستوى لصورة الإدخال ، وليس الاحتمالات الفعلية التي تم تدريب النموذج الأصلي على إخراجها.

5- الطبقة التي نستخدمها لحساب خسارة المحتوى هي الطبقة كاملة الإتصال الثانية من المجموعة الخامسة. يؤثر اختيار طبقة عند نقطة ضحلة أو أعمق في الشبكة على كيفية تعريف دالة الخسارة “المحتوى” وبالتالي تغيير خصائص الصورة المدمجة التي تم إنشاؤها.

6- نقوم هنا باستخراج سمات الصورة الأساسية و سمات الصورة المدمجة من المدخل الذي تم تغذيته عبر شبكة VGG19.

7- خسارة المحتوى هي مجموع مسافة المربعات بين مخرجات الطبقة المختارة لكلتا الصورتين ، مضروبة في معييار الوزن.

خسارة النمط (Style Loss)

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

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

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

يمكننا أن نرى الصورتين 1 و 2 متشابهان في النمط  فكلاهما عشبي. تختلف الصورة 3 قليلاً في نمطها عن الصور 1 و 2. إذا نظرنا إلى خرائط السمات، يمكننا أن نرى أن القناتين الخضراء والشائكة غالبًا ما تتنشط كلاهما  في نفس النقطة الموضعية في الصورتين  1 و 2 ، ولكن ليس في الصورة 3 . 

على العكس من ذلك ، غالبًا ما يتم تنشيط القناتين البني والشائك معًا في نفس النقطة في الصورة 3 ، ولكن ليس في الصورتين 1 و 2 إذا كانت القيمة الناتجة عالية ، فإن خرائط السمات تكون شديدة الارتباط ؛ إذا كانت القيمة منخفضة ، فإن خرائط السمات غير مرتبطة.

يمكننا تحديد مصفوفة تحتوي على حاصل الضرب النقطي بين جميع أزواج السمات الممكنة في الطبقة. هذا يسمى مصفوفة جرام (Gram matrix). 

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

لذلك ، لحساب خسارة النمط ، كل ما نحتاجه هو حساب مصفوفة غرام (GM) لمجموعة من الطبقات عبر الشبكة لكل من الصورة الأساسية والصورة المدمجة ومقارنة التشابه بينهما باستخدام مجموع الأخطاء التربيعية. جبريًا ، يمكن كتابة خسارة النمط بين الصورة الأساسية (S) والصورة المولدة (G) لطبقة معينة (i) بحجم Mi (الارتفاع × العرض) مع قنوات Ni على النحو التالي:

 \left.L_{G M}(S, G, l)=\frac{1}{4 N_{l}^{2} M_{l}^{2}} \sum_{i j}(G M \mid l](S)_{i j}-G M[l](G)_{i j}\right)^{2}

لاحظ كيف يتم القياس هنا لمراعاة عدد القنوات (Nl) وحجم الطبقة (Ml). هذا لأننا نحسب خسارة النمط الإجمالية كمجموع موزون عبر عدة طبقات ، وكل منها لها أحجام مختلفة. ثم يتم حساب إجمالي خسارة النمط على النحو التالي:

 L_{\text {styde }}(S, G)=\sum_{l=0}^{L} w_{i} L_{G \text { M }}(S, G, l)

في Keras ، يمكن برمجة حسابات خسارة النمط كما هو موضح في

style_loss = 0.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_nrows * img_ncols
    return K.sum(K.square(S - C)) / (4.0 * (channels ** 2) * (size ** 2))


feature_layers = ['block1_conv1', 'block2_conv1',
                  'block3_conv1', 'block4_conv1',
                  'block5_conv1'] #1

for layer_name in feature_layers:
    layer_features = outputs_dict[layer_name]
    style_reference_features = layer_features[1, :, :, :] #2
    combination_features = layer_features[2, :, :, :]
    sl = style_loss(style_reference_features, combination_features)
    style_loss += (style_weight / len(feature_layers)) * sl #3

1- يتم حساب خسارة النمط على مدى خمس طبقات – الطبقة التلافيفية الأولى في كل من المجموعات الخمس لنموذج VGG19.

2- نحن هنا نستخرج سمات صورة النمط و سمات الصورة المدمجة من تنسور الإدخال الذي تم تغذيته عبر شبكة VGG19.

3- يتم قياس خسارة النمط بواسطة معييار موزون وعدد الطبقات التي يتم حسابها عليها.

إجمالي خسارة التباين (Total Variance Loss)

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

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

في Keras ، يمكن برمجة إجمالي خسارة التباين كما هو موضح 

def total_variation_loss(x):
    a = K.square(
        x[:, :img_nrows - 1, :img_ncols - 1, :] - x[:, 1:, :img_ncols - 1, :]) #1
    b = K.square(
        x[:, :img_nrows - 1, :img_ncols - 1, :] - x[:, :img_nrows - 1, 1:, :]) #2
    return K.sum(K.pow(a + b, 1.25)) 

tv_loss = total_variation_weight * total_variation_loss(combination_image) #3

loss = content_loss + style_loss + tv_loss #4

1- تحريك الاختلاف التربيعي بين الصورة ونفس الصورة بمقدار  نقطة ضوئية واحدة لأسفل.

2- تحريك الاختلاف التربيعي بين الصورة ونفس الصورة بمقدار  نقطة ضوئية واحدة لليمين.

3- يتم قياس خسارة التباين الإجمالية بواسطة بمعيار موزون .

4- الخسارة الإجمالية هي مجموع خسائر المحتوى و النمط والتباين الكلي.

تدريب نقل النمط العصبي

تتضمن عملية التعلم إستخدام النزول الاشتقاقي لتقليل دالة الخسارة هذه ، فيما يتعلق بالنقاط الضوئية في الصورة المدمجة. و هنا مقتطف في برمجة حلقة التدريب : 

from scipy.optimize import fmin_l_bfgs_b

iterations = 1000
x = preprocess_image(base_image_path) #1

for i in range(iterations):
    x, min_val, info = fmin_l_bfgs_b( #2
        evaluator.loss  #3
        , x.flatten()
        , fprime=evaluator.grads #3
        , maxfun=20
        )

1- تتم تهيئة العملية بالصورة الأساسية كصورة البداية المدمجة.

2- في كل تكرار ، نقوم بتمرير الصورة المدمجة الحالية (المسطحة) إلى دالة التحسين ، fmin_l_bfgs_b من الحزمة scipy.optimize ، التي تؤدي خطوة  النزول الإشتقاقي واحدة وفقًا لخوارزمية L-BFGS-B.

3- هنا ، المقيّم (evaluator)  يحتوي على طرق تحسب الخسارة الإجمالية ، كما هو موضح سابقًا ، و إشتقاق الخسارة فيما يتعلق بالصورة المدخلة.

تحليل نموذج نقل النمط العصبي

توضح الصورة التالية  مخرجات عملية نقل النمط العصبي في ثلاث مراحل مختلفة من عملية التعلم ، مع المعايير التالية:

وزن المحتوى: 1

وزن النمط: 100

إجمالي التباين الموزون : 20

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

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

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

ختاماً 

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

الكود المستخدم هنا هو نفسه الموجود في صفحة كيراس الرئيسية على github

https://github.com/keras-team/keras/blob/master/examples/neural_style_transfer.py

إضافة تعليق