ลองเล่น Torch ผ่าน Lua
ย้ายค่ายอีกแระ
เมื่อปลายปีก่อนเริ่มเล่น Caffe พอผ่านไปซักพักก็รู้สึกว่ามันไม่ค่อยยืดหยุ่น เลยหันมาเล่น Python โดยใช้ Keras+Theano เป็นหลัก ช่วง 1–2 เดือนที่ผ่านมาเริ่มอยากทดลองใช้ ResNet หรือ Residual CNN บ้าง ปรากฏว่าผลที่ได้จาก Keras บน Cifar10 นั้นไม่ดีเท่าใน paper ต่างๆ ต่ำกว่าอยู่ 1–2% เลยลองพยายามปรับนู่นนี้อยู่พักใหญ่ก็ไม่ได้ เลยลองๆ google ดู พบว่ามี code ของ ResNet บนเว็บหลายเจ้าอยู่ โดยเฉพาะจาก Facebook (https://github.com/facebook/fb.resnet.torch) ที่ดูน่าสนใจ แต่ code เหล่านี้ใช้ Torch ที่เป็นภาษา Lua เลยลังเลอยู่นาน ตอนแรกก็ลองหา docker ของ torch มาเล่น แต่พอจะ run จริงปรากฏว่า docker cuda-torch เอามา run บน server ของ lab ไม่ได้อีก มีปัญหาเรื่อง version ของ CUDA โชคดีที่ณัฐชัย ลง Torch บน server ให้ไม่ยากนักเลยได้ทดลอง ปรากฏว่าเร็วกว่า Keras เยอะ และให้ accuracy เท่าๆ กับใน paper อีก เลยมาลองแกะๆ ดูละกัน
สำหรับพื้นฐานภาษา Lua นั้นดูได้จาก http://tylerneylon.com/a/learn-lua/ ซึ่งจริงๆ แล้วตัวภาษาก็ไม่ยากนัก ที่ยากกว่าคือการใช้งาน Torch
จากที่ลองใช้ดูพบว่าเจอปัญหาคล้ายๆ กับ Keras/Theano เช่นการ debug ดู shape ของ Tensor ต่างๆ ซึ่งดูวุ่นๆ ไม่แพ้กัน
จากที่ลองๆ แกะ code ของ fb.resnet.torch ดูรู้สึกว่า Torch นั้นใช้ยากกว่า Keras สำหรับคนพึ่งหัดเขียน program หรืออยากลองสร้าง CNN แบบง่ายๆ นั้นผมว่าใช้ Keras จะดีกว่าเพราะ code ที่ได้นั้นอ่านง่ายกว่า นอกจากนี้ในส่วนของการ load dataset มาตรฐาน เช่นการ load Cifar10 นั้นทำได้เพียงเรียกคำสั่ง
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
หรือการ gen ตัวอย่างเพิ่มสำหรับ train นั้นก็ไม่ยากเช่น
datagen = ImageDataGenerator(featurewise_center=True, featurewise_std_normalization=True, rotation_range=10, width_shift_range=0.125, height_shift_range=0.125, horizontal_flip=True)
และการเรียกใช้
history = model.fit_generator(datagen.flow(X_train_tr, Y_train_tr, batch_size=100), samples_per_epoch=len(X_train_tr), nb_epoch=200, validation_data=(X_train_val, Y_train_val), verbose=1)
สำหรับ Torch นั้นเราต้องทำขั้นตอนเหล่านี้เอง แต่โชคดีที่ code เหล่านี้มีใน fb.resnet.torch ให้ใช้ได้เลย แต่หากเราต้องการดึงเอา code บางส่วนออกไปใช้กับ code ข้างนอกจะค่อนข้างยากเพราะมันขึ้นกับกันเยอะ และมีไฟล์ที่เกียวข้องหลายส่วน
โชคดีที่หากเราต้องการทดสอบง่ายๆ เช่นทดสอบ โครงสร้าง CNN ใหม่เพื่อ test ใน framework ที่เขาเตรียมให้นั้น เราก็สามารถทำได้ไม่ยากนัก วิธีทำคือ
- เขียน CNN ของตัวเองใส่ไฟล์ .lua เช่นของผมใช้ชื่อ ‘peune.lua’ แล้วเอาไปใส่ใน sub-directory ‘models’
- ในไฟล์ ‘peune.lua’ นี้ (เท่าที่เข้าใจ) ให้ return function ที่รับ argument 1 ค่าคือ opt ไว้ตอนท้ายไฟล์ ซึ่ง fb.resnet.torch จะเรียกใช้ฟังก์ชันนี้ในการสร้าง model ของเรา
สิ่งที่ดูแปลกคือ Lua สามารถเรียกฟังก์ชั่นนี้แบบ implicit ผ่านชื่อไฟล์ได้เลย เช่นในกรณีการสร้าง model นี้ใน ‘models/init.lua’ นั้นทำได้จาก
model = require(‘models/’ .. opt.netType)(opt)
ถ้าตามนี้ก็แปลว่าเราน่าจะสามารถเรียก require(‘models/peune’)(opt) ได้เลย ซึ่งแปลกดี
3. แก้ไฟล์ ‘opts.lua’ โดยเพิ่มชื่อไฟล์ตัวเองเข้าไปใน ‘netType’ ในกรณีผมคือ
cmd:option(‘-netType’, ‘resnet’, ‘Options: resnet | preresnet | peune’)
เราสามารถสั่งทดสอบ model ของเราได้จาก command line โดยใช้ flag ‘-netType peune’ เช่น
th main.lua -netType peune -data cifar10 …
ใน ‘peune.lua’ นั้นผมลองเขียน ResNet แบบเดียวกับที่ผมเขียนใน Keras ดูก่อนโดยดูตัวอย่างคำสั่งจาก ‘resnet.lua’ และ document ของ Torch บนเว็บเอา
ดูๆ แล้วเหมือนกับ code ของ ‘resnet.lua’ จะต่างอยู่เล็กน้อย ดูเหมือนว่าในส่วน shortcut path นั้นเขาจะใช้ 1x1 average pooling ในขณะที่ผมใช้ 2x2 แต่ผลที่ได้ไม่ต่างกันนัก และตอนนี้ก็ได้เท่าๆ ใน paper ที่อ่านแล้ว
สิ่งที่ดูเหมือนว่าจะต่างกันหลักๆ ระหว่าง Keras/Theano กับ Torch คือ 1) การ train ทั้งที่ทั้งคู่น่าจะใช้ algo classic เหมือนๆ กัน และ 2) การ gen ตัวอย่างเพิ่ม อันนี้คงต้องดูเพิ่มว่า Keras ทำยังไง เดาว่าความต่างนี้แหละที่ทำให้ Torch ได้ผลดีกว่า ~2%
Code ข้างล่างนี้เป็นการสร้าง basic block สำหรับ ResNet สำหรับ Keras บน Python และ Torch บน Lua จะเห็นว่าถ้าเราเข้าใจโครงสร้าง CNN และเรารู้คำสั่งที่เหมาะสม จะสร้างบนภาษาอะไรก็ไม่ต่างกันนัก
- การใส่ชื่อ argument ของ functionตอนเรียกใน Python ทำให้อ่าน code ได้ง่าย เช่นในการสร้าง Convolution layer เป็นต้น บน Lua หากเราให้ kernel size (ksize) =3 เป็นค่า default เช่นใน fb.resnet.torch เราจะได้
Convolution(n_input_plane, n_kernel, 3,3,1,1,1,1)
ซึ่งหากเราไม่เคยใช้ tool นี้ต่อให้เรารู้จัก Convolution layer อยู่แล้ว เราต้องไปค้น document ดูเพิ่ม ใน Keras เราใช้ subsample=(1,1) และ border_mode=’same’ แทนในกรณีนี้ ซึ่งทำให้อ่านได้ง่ายกว่า
- บน Keras นั้นเราสามารถสร้าง layer ต่างๆ พร้อมกำหนดวิธี initialize weights ต่างๆ ได้เลย แต่เท่าที่เข้าใจสำหรับ Torch เราต้อง init เองหลังจากสร้างแล้ว
- บน Torch ให้เราเรียก ‘model:cuda()’ ในตอนท้ายของการสร้าง model เห็นว่าเพื่อแปลง code ให้เป็น CUDA
ตอนนี้ถ้าจะใช้งานก็เหลือต้องลอง save และ load model ที่ train แล้ว ลองทำ prediction บน data อื่น และหาวิธีนำเข้าข้อมูลอื่นเพื่อ train etc.