ลองเล่น style transfer
วันก่อนผมพึ่งพบว่าใน Keras มี model zoo ให้โหลดมาเล่นด้วย อาจจะมีนานแระ แต่ผมพึ่งเห็น 555
ตัวอย่างการใช้งานเช่น VGG16
from keras.applications.vgg16 import VGG16
org_model = VGG16(weights=’imagenet’)
ซึ่ง Keras จะ download weights มาเอง แต่ไม่รู้เก็บไว้ไหน แต่ถ้าเราต้องการเราสามารถสั่ง save weights เองลงไฟล์ที่ต้องการก็ได้
เมื่อเราโหลด VGG16 มาได้ เราก็สามารถเอามันมาใช้ classify object ในรูปก็ได้ หรือจะใช้อย่างอื่นก็ได้
ไหน ๆ จะเล่นแล้วก็ลองทำโจทย์ที่เคยอยากลองแต่หา model VGG ไม่ได้ละกัน นั่นคือ style transfer จากเปเปอร์ต้นฉบับ A Neural Algorithm of Artistic Style (https://arxiv.org/pdf/1508.06576.pdf)
แนวความคิดหลักของ style transfer คือ
1) ภาพที่มี content คล้าย ๆ กัน น่าจะมี output ของ layer ภายในเหมือน ๆ กัน
2) เราสามารถมอง convolution layer ว่าเป็น filters bank ก็ได้ ภาพที่มี style คล้าย ๆ กัน ก็น่าจะมี response กัย filters เหล่านี้เหมือน ๆ กัน แต่มี trick เล็กน้อยนั่นคือเราไม่วัดค่าโดยตรงแบบ content แต่เราจะดู correlation ระหว่าง filters มากกว่า นั่นคือสมมติว่า layer หนึ่งส่งออก tensor ขนาด (w,w,c) นั่นคือมี c channels ขนาด wxw เราจะพิจารณาผลจาก dot product ระหว่าง c channels นี้เป็นตัววัด correlation
3) เราใช้ gradient เป็นหลักในการปรับรูปนำเข้าให้ได้ทั้ง content และ style ที่ต้องการ
ปรับขนาด input
ก่อนจะไปดูการสร้าง style และ content เรามีปัญหาเล็กน้อยที่ต้องแก้ก่อน นั่นคือ VGG16 นั้นถูกสร้างมาเพื่อ process ภาพขนาด 224x224 ซึ่งเล็กไปหน่อย ถ้าเราต้องการ process รูปใหญ่กว่านี้การลดขนาดลงก็จะทำให้ได้รูป output ที่เล็กไป ไม่น่าสน
เพื่อสร้าง output รูปใหญ่ เราต้องสร้าง CNN ใหม่ที่รับ input ใหญ่ขึ้น แต่ยังคงใช้ weights ของ VGG16 เหมือนเดิม เราทำได้เพราะจำนวน parameters ของชั้น convolution นั้นขึ้นกับขนาด kernel size และจำนวน channels ของ layer ก่อนหน้ามัน และไม่ได้ขึ้นกับขนาด (กว้างคูณยาว) ของ layer เหล่านั้น
ก่อนจะสร้าง CNN ใหม่เรามาลองดูโครงสร้างของ VGG16 ก่อน
>>> for l in org_model.layers:
… print(l.name, l)
…
input_1 <keras.engine.topology.InputLayer object at 0x10d580f98>
block1_conv1 <keras.layers.convolutional.Conv2D object at 0x11a416ac8>
block1_conv2 <keras.layers.convolutional.Conv2D object at 0x11a416d30>
block1_pool <keras.layers.pooling.MaxPooling2D object at 0x11a416da0>
block2_conv1 <keras.layers.convolutional.Conv2D object at 0x11a469b70>
block2_conv2 <keras.layers.convolutional.Conv2D object at 0x11a456a58>
block2_pool <keras.layers.pooling.MaxPooling2D object at 0x11a470be0>
block3_conv1 <keras.layers.convolutional.Conv2D object at 0x11a49eba8>
block3_conv2 <keras.layers.convolutional.Conv2D object at 0x11a460eb8>
block3_conv3 <keras.layers.convolutional.Conv2D object at 0x11a4bf908>
block3_pool <keras.layers.pooling.MaxPooling2D object at 0x11a4b74e0>
block4_conv1 <keras.layers.convolutional.Conv2D object at 0x11a4e8550>
block4_conv2 <keras.layers.convolutional.Conv2D object at 0x11a4f9278>
block4_conv3 <keras.layers.convolutional.Conv2D object at 0x11a507b38>
block4_pool <keras.layers.pooling.MaxPooling2D object at 0x11a5020b8>
block5_conv1 <keras.layers.convolutional.Conv2D object at 0x11a531e48>
block5_conv2 <keras.layers.convolutional.Conv2D object at 0x11a542ba8>
block5_conv3 <keras.layers.convolutional.Conv2D object at 0x11a552710>
block5_pool <keras.layers.pooling.MaxPooling2D object at 0x11a569ba8>
flatten <keras.layers.core.Flatten object at 0x11a58aeb8>
fc1 <keras.layers.core.Dense object at 0x11a590ef0>
fc2 <keras.layers.core.Dense object at 0x11a5aa470>
predictions <keras.layers.core.Dense object at 0x11a5c6358>
จะเห็นว่า layer ต่าง ๆ ใน VGG16 นั้นมีชื่อด้วย ซึ่งชื่อนี้จะถูกใช้ภายหลังในการสร้าง style model และ content model
เราสร้าง model ใหม่ชื่อ base_model ทีละ layer โดยตั้งค่าตัวแปรต่าง ๆ แบบเดียวกับ org_model ตามโค้ดข้างล่างนี้ โดยเมื่อสร้างเสร็จแล้ว เราจะ freeze weights ของ base_model ก่อนจะนำไปใช้งาน
Content model
สมมติว่าเราจะเอาชั้น ‘block4_conv2’ และ ‘block5_conv2’ เป็นตัวหลักในการตัดสินใจเรื่อง content เราสามารถทำได้โดยใช้ Model ใน Keras ซึ่งเป็นวิธีที่ผมมักใช้สร้าง model ที่มีหลาย branch เช่น
ในโค้ดนี้เราสร้าง Model m4 และ m5 จากการไป link ชั้นของ base_model ดังนั้น inputs ของ m4 และ m5 จึงเป็น base_model.input แต่เมื่อสร้างเสร็จแล้วเรานำมันไปใช้กับ input_c ซึ่งเราต้องแน่ใจว่ามันเป็นขนาดเดียวกับ base_model.input
เมื่อเราได้ output จากชั้นทั้งสองแล้วเราก็นำเอามาต่อกัน (concatenate) เป็นค่าส่งออกจาก style model นี้
Style model
การสร้าง style model นั้น tricky ตรงที่เราต้องสร้างชั้นที่เป็น Gram matrix หรือ correlation ระหว่าง channel ต่าง ๆ ใน output เดียวกัน ใน Keras เราสามารถคำนวณ Gram matrix นี้ได้โดยใช้ layer ชื่อ Dot
ในการใช้งาน Dot layer นี้เราต้องกำหนดก่อนว่าเราจะคำนวณ dot ระหว่างแกนใด แต่ชั้นที่ส่งค่าให้ Dot นี้ปกติแล้วจะมี shape เป็น (w,w,c) ดังนั้นในโค้ดเราเราจึง reshape ชั้นก่อนหน้ามันให้เหลือ 2 แกนก่อนคือ (w*w, c) ก่อนจะคำนวณ dot บนแกนแรก
ของที่วุ่น ๆ อีกอย่างคือการดึง shape ของ output ชั้นก่อนหน้ามาเพื่อ reshape ซึ่งเท่าที่ลองบน Theano และ Tensorflow มีการเรียกต่างกันบ้าง สุดท้ายเลยต้องเรียกผ่าน int_shape ของ Backend เพื่อให้ใช้ได้ทั้งสองแบบ
สุดท้ายคือเปเปอร์ต้นฉบับนั้นบอกให้เรา scale output ของ Dot layer นี้ด้วย เพราะชั้นที่ต่างกันก็ย่อมมีขนาดและจำนวน channel ต่างกัน ซึ่งเมื่อนำมาผสมจะมีปัญหาได้ ผมหาวิธี scale ง่าย ๆ ไม่ได้ สุดท้ายเลยมาใช้ trick ของการคำนวณ Conv2D ขนาด 1x1 โดยใส่ค่า scale เป็น weight ของ Conv2D นี้
เราสร้าง model ตามโค้ดข้างล่างนี้จากชั้น ‘block1_conv1’, ‘block2_conv1’ และ ‘block3_conv1’ และเอา output ที่ได้มาต่อกันแบบเดียวกับ content model
Merge model
เมื่อเราได้ model ทั้งสองแล้ว เราก็นำมาต่อกันโดยให้มีชั้นแรกที่จะเป็นภาพที่เราสร้างขึ้นนั้นเป็นชั้น Dense จากนั้นจึงส่งค่าไปยัง style model และ content model ก่อนจะส่งค่าออก 2 ทาง
ในงานนี้เราใช้ mean squared error ทั้งสองทางโดยเราให้ weight ของฝั่ง content สูงกว่า อันนี้ดูจะต่างจากเปเปอร์ต้นฉบับ ค่านี้มาจาก trial and error โดยสังเกตเอาจากค่าของ loss ทั้งสอง ผมเดาว่าบางทีในเปเปอร์นั้นขนาดของ loss ฝั่ง style อาจจะเล็กมากก็ได้จึงต้องใส่ weight ให้เยอะ ๆ
เมื่อเราสร้าง model ครบแล้ว (style model, content model และ merge model) เราก็สามารถทำ style transfer ได้โดย
ผล
ผมลองปรับเล่นหลายภาพพบว่า
- ขนาดของภาพต้นฉบับทั้ง style และ content นั้นสำคัญอยู่ถ้าเราต้อง scale ภาพ style ลงจะทำให้ได้ texture ที่แปลก ๆ ดูไม่เหมือนจริงเท่าไร
- ภาพที่ได้มักจะดูซีด ๆ สีไม่เด่น ตอนนี้ผมทำ scaling อย่างง่าย ๆ ให้ภาพ output มี mean กับ sd คล้าย ๆ ภาพ style แต่ก็ยังดูไม่สด ยังไม่รู้แก้ไง
- ผมลองเอา style ของ อาจารย์เฉลิมชัย มาใช้ ดูเหมือนว่าเทคนิคนี้ไม่เหมาะกับ style ที่อิงรายละเอียดมาก ๆ อย่างของ อ.เฉลิมชัย มันเหมาะกับ style ที่อิง texture มากกว่า
ว่าจะเอาไฟล์ใส่ Github แต่ทำไม่เป็น บนเว็บบอก command line ให้ทำแต่ก็ดันจำ password ไม่ได้อีก 555 เลยเอาใส่ Github Gist แทนละกัน
Have Fun :D