จากใน ep เรื่อง AI จำแนกรูปภาพ Image Classification หมา แมว 37 สายพันธุ์ ใน ep นี้เราจะมาเรียนรู้เทคนิคเพิ่มเติม ในเรื่อง Data Augmentation คือ Mixup และ Loss Function คือ Label Smoothing เพื่อแก้ปัญหาบางอย่างในการเทรนโมเดล Machine Learning ให้มีประสิทธิภาพดีขึ้น

Mixup คืออะไร

ตรงตามชื่อ Mixup คือ การผสมข้อมูลตัวอย่างในการเทรนโมเดล เช่น เอารูปใน Training Set มาผสมกัน โดยไม่สนใจว่าจะอยู่ Class เดียวกันหรือไม่ นำข้อมูลรูปมารวมกันแบบเส้นตรง

new_image = t * image1 + (1-t) * image2

สมมติ t เป็นเลขทศนิยม มีค่าระหว่าง 0 และ 1 ส่วน Label ก็ทำเหมือนกัน ด้วย t จากข้างบน

new_label = t * label1 + (1-t) * label2

โดยสมมติว่า Label เป็น One-Hot Encoding

หมา 70% + แมว 30%

Mixup Dog 70% + Cat 30% Overlay
Mixup Dog 70% + Cat 30% Overlay

รูปด้านบนนี้คือรูปอะไร? หมาหรือแมว? คำตอบคือ หมาพันธุ์ปั๊ก 70% + แมวพันธุ์ Birman 30%

ดังรูปด้านบน เป็นการยาก สำหรับสายตามนุษย์ ที่จะทำความเข้าใจกับรูปแบบนี้ (ถึงจะมองเห็นลาง ๆ แต่ก็ยังยากอยู่ดี) แต่สำหรับ AI / Machine Learning เป็นอะไรที่ง่ายมาก แถมยังช่วยให้สามารถเทรน / เรียนรู้ อย่างมีประสิทธิภาพเพิ่มมากขึ้นอีกด้วย

ความแตกต่างที่เห็นชัดเจนก็คือ Loss จะมากกว่า เทรนแบบปกติ ถึงแม้ Accuracy จะมากกว่าก็ตาม หมายความว่าโมเดลอาจจะทำนายด้วยความมั่นใจน้อยลงกว่าปกติ

โดยใน Implementation ก็มีรายละเอียดปลีกย่อยลงไปอีก เช่น การ Random ไม่ได้ผสมทุกครั้ง และไม่ได้ใช้ 0.7 / 0.3 ตลอด

เทคนิคใกล้เคียง Mixup เช่น AugMix, CutOut และ CutMix

Visual comparison of data augmentation techniques. AugMix A Simple Data Processing Method to Improve Robustness and Uncertainty. Credit https://arxiv.org/abs/1912.02781
Visual comparison of data augmentation techniques. AugMix A Simple Data Processing Method to Improve Robustness and Uncertainty. Credit https://arxiv.org/abs/1912.02781

มีเทคนิคอื่น ๆ ที่คล้ายกันกับ Mixup เรียกว่า AugMix, CutOut และ CutMix แต่ใช้การตัดแปะรูป หรือผสมค่าสีที่ซับซ้อนมากขึ้น จะอธิบายต่อไป

Label Smoothing คืออะไร

Strawberry Lemon Smoothie. Credit https://commons.wikimedia.org/wiki/File:Strawberry_and_lemon_smoothie_(14430283996).jpg
Strawberry Lemon Smoothie. Credit https://commons.wikimedia.org/wiki/File:Strawberry_and_lemon_smoothie_(14430283996).jpg

จากการรวมภาพ รวม Label ข้างบน แทนที่จะ Label จะเป็น One-Hot Encoding แบบปกติ ใช้กับ Cross Entropy Loss Function ในงาน Classification เหมือน ep ที่ผ่าน ๆ มา

Label กลายเป็นค่าเฉลี่ย Weight ตาม t เช่น 0.7 / 0.3 ไม่ได้เป็น 0 / 1 สุดโต่งเหมือนอย่างเคย

Dataset ข้อมูลคุณภาพไม่ค่อยดี

นักวิจัยพบว่า ในเคสที่ Dataset ข้อมูลคุณภาพไม่ค่อยดี Label มีความผิดพลาด ไม่ตรง 100% ถ้าเราบังคับให้ Label เป็น 0 / 1 กับข้อมูลที่ผิด จะทำให้โมเดลเรียนรู้ได้แย่ลง Loss Function จะ Penalty มากเกินไป โมเดลจะเรียนรู้จาก Label ผิด ๆ เหล่านั้นอย่างมั่นใจเกินไป

วิธีแก้ในเคสนี้ คือ Label Smoothing แทนที่เราจะใช้ One-Hot Encoding แบบปกติ 0 / 1 สำหรับ Dataset ข้อมูลที่คุณภาพไม่ค่อยดี เราจะใช้ 0.1 / 0.9 แทน

โดยใน Implementation ก็มีรายละเอียดปลีกย่อยลงไปอีก เช่น การ Random ไม่ได้ใช้ 0.1 / 0.9 ทุกครั้ง เป็นต้น

เรามาเริ่มกันเลยดีกว่า

Open In Colab

จากใน ep เรื่อง AI จำแนกรูปภาพ Image Classification หมา แมว 37 สายพันธุ์ ใน ep นี้เราจะมาเรียนรู้เทคนิดเพิ่มเติมในเรื่อง Data Augmentation คือ Mixup และ Loss Function คือ Label Smoothing

0. Magic Commands

ให้ใส่ไว้บนสุดทุก Notebook เป็นการสั่งให้ Notebook ก่อนรัน ให้รีโหลด Library ภายนอกที่เรา import ไว้ใหม่โดยอัตโนมัติ

และให้พล็อตกราฟ matplotlib ใน Output ของ cell แบบ code ได้เลย

In [0]:
%reload_ext autoreload
%autoreload 2
%matplotlib inline

1. Import Library

Import Library ที่เราจะใช้ ในที่นี้คือ fastai version 1, fastai มี package ย่อย ๆ หลายอัน แต่ที่เราจะใช้ใน Notebook นี้คือ vision สำหรับงานเกี่ยวกับ Computer Vision และ metrics สำหรับวิเคราะห์ประสิทธิภาพของ Model

การ import * หมายความว่า import ทุกอย่างที่อยู่ใน package ทำให้เราไม่ได้ต้องมา import ทีละ class การ import แบบนี้ เหมาะสำหรับการทดลองอะไรใหม่ ๆ เพราะเราไม่ต้องย้อนมาแก้ import ทุกครั้งเมื่อต้องการใช้ class ใหม่ ๆ แต่ไม่แนะนำสำหรับใช้งานจริงบน Production

In [0]:
 !curl -s https://course.fast.ai/setup/colab | bash
Updating fastai...
Done.
In [0]:
from fastai import *
from fastai.vision import *
from fastai.metrics import accuracy
In [0]:
! nvidia-smi
Sat Dec 14 10:08:02 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.44       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla P100-PCIE...  Off  | 00000000:00:04.0 Off |                    0 |
| N/A   34C    P0    25W / 250W |      0MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

2. ข้อมูล

ในเคสนี้ เราจะใช้ข้อมูลจาก Oxford-IIIT Pet Dataset by O. M. Parkhi et al., 2012 ซึ่งเป็นชุดข้อมูลรูปภาพหมา 25 พันธุ์ และรูปแมว 12 พันธุ์ รวมเป็น 37 หมวดหมู่

อ้างอิงจากใน paper ในปี 2012 โมเดลที่ดีที่สุด สามารถทำนายพันธุ์สัตว์ ได้ถูกต้อง 59.21% โดยโมเดลนั้น ออกแบบมาเฉพาะทาง สำหรับแยกรูปสัตว์ หัวสัตว์ ตัวสัตว์ โดยเฉพาะ (สมัยนันยังไม่ใช่ Deep Learning)

เรามาดูกันว่าเราจะใช้ Deep Learning ทำนายพันธุ์หมาแมวจากรูป ได้ถูกต้องแม่นยำกว่าหรือไม่

URL ของ Dataset ที่เราจะ Download มาใช้

In [0]:
URLs.PETS
Out[0]:
'https://s3.amazonaws.com/fast-ai-imageclas/oxford-iiit-pet'

ฟังก์ชัน untar_data จะดาวน์โหลด Dataset และ Extract ไฟล์ให้เราอัตโนมัติ ไว้ใน $HOME/.fastai/data เวลาเรารันใหม่ จะได้ไม่ต้อง Download ใหม่ทุกครั้ง เมื่อแตกไฟล์ออกมาแล้ว ก็ ls ดูว่ามีโฟล์เดอร์อะไรอยู่บ้าง

In [0]:
path = untar_data(URLs.PETS)
path.ls()
Downloading https://s3.amazonaws.com/fast-ai-imageclas/oxford-iiit-pet
Out[0]:
[PosixPath('/root/.fastai/data/oxford-iiit-pet/images'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations')]

ที่เราสนใจตอนนี้มี 2 path คือ images ที่เก็บรูป และ annotations ที่เก็บข้อมูลประกอบ

In [0]:
path_annotations = path/'annotations'
path_images = path/'images'

ลอง ls ดูใน path_annotations จะเห็นว่ามีไฟล์ข้อมูลประกอบรูปหลายไฟล์ด้วยกัน

In [0]:
path_annotations.ls()
Out[0]:
[PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/trainval.txt'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/._trimaps'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/list.txt'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/xmls'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/README'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/trimaps'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/annotations/test.txt')]

Label ชื่อพันธุ์ อยู่ในชื่อไฟล์

ดึงรายชื่อไฟล์จาก path_images และแสดงรายการ 10 ไฟล์แรก

สังเกตว่าทั้งหมดเป็นรูปนามสกุล jpg อยู่ใน path ด้วยกัน ชื่อไฟล์เป็นชื่อพันธุ์ ตามด้วย _ ตามด้วย เลข Running ตามด้วย .jpg

In [0]:
filenames = get_image_files(path_images)
filenames[:10]
Out[0]:
[PosixPath('/root/.fastai/data/oxford-iiit-pet/images/British_Shorthair_38.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/keeshond_23.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/leonberger_62.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/chihuahua_151.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/samoyed_85.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/Russian_Blue_100.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/Ragdoll_100.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/chihuahua_67.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/newfoundland_31.jpg'),
 PosixPath('/root/.fastai/data/oxford-iiit-pet/images/havanese_40.jpg')]

3. เตรียมข้อมูล

ก่อนนำข้อมูลใด ๆ มาเทรน Model เราต้องมีการเตรียมข้อมูล กำหนด Data Pipeline ให้อยู่ในรูปแบบที่เหมาะสมเสียก่อน

batchsize หมายถึงให้ป้อนข้อมูลให้ Model ทีละ 64 records, GPU สามารถทำงานได้รวดเร็ว เพราะ ข้อได้เปรียบของ GPU คือมี Core จำนวนมาก การป้อนข้อมูลรูปแบบคล้าย ๆ กันให้ GPU ไปทำงานพร้อม ๆ กันหลาย ๆ Record ในทีเดียว จะเร็วกว่าใช้ CPU ทำงานไปทีละ Record มากกว่า 100 เท่า

random seed หมายถึง กำหนดค่าเริ่มต้นของการสุ่ม เพื่อให้รันทุกครั้งแล้วได้เหมือนเดิมทุกครั้ง จะได้สะดวกในการเปรียบเทียบ แต่ไม่แนะนำสำหรับงาน Production

regexpattern หมายถึง เราจะใช้ Regular Expression ตัดชื่อไฟล์ดูจากท้ายสุด ($), ดู .jpg, ดูตัวเลข (d+), ดู แล้วเอาตัวอักษรทั้งหมด ก่อนถึง / มาเป็น label ชื่อพันธุ์

In [0]:
batchsize = 64
np.random.seed(42)
regex_pattern = r'/([^/]+)_\d+.jpg$'

from_name_re คือ Factory Method กำหนด Data Pipeline ในการสร้าง DataBunch โดยใช้ regex_pattern (Regular Expression) จาก ชื่อไฟล์มาเป็น labe

  • ds_tfms คือ Dataset Transforms การแปลงข้อมูลรูปภาพ เช่น ย่อขยาย crop ปรับสี ความสว่าง etc. จะพูดถึงในเรื่อง Data Augmentation ที่ไว้เราจะอธิบายต่อไป แต่ตอนนี้เพื่อให้เปรียบเทียบได้ชัด เราจะไม่ทำ Data Augmentation เลย
  • size คือ resize รูปทั้งหมดให้เป็น 224 x 224 pixels
  • bs คือ batchsize
  • normalize คือการแปลงข้อมูล ตาม imagenet_stats จากรูปสี จาก 0-255 x 3 channels RGB ให้มี Mean = 0 และ Standard Deviation = 1 ที่ไว้เราจะอธิบายต่อไป

ที่ไม่ได้ใส่เป็น parameter ได้แก่ valid_pct คือ ค่า Percent สำหรับ แบ่ง train/valid split ซึ่งค่า Default คือ 20%

In [0]:
tfms_none = [[], []]
databunch = ImageDataBunch.from_name_re(path_images, filenames, 
                                   regex_pattern, ds_tfms=tfms_none, 
                                   size=224, bs=batchsize).normalize(imagenet_stats)

เราจะได้ DataBunch บรรจุ train Dataset จำนวน 5912 record และ validation Dataset จำนวน 1478 record หรือ 1478 / (1478+5912) = 20%

In [0]:
len(databunch.train_ds), len(databunch.valid_ds)
Out[0]:
(5912, 1478)

สำรวจข้อมูล

เรียกข้อมูลจาก DataLoader ที่อยู่ใน DataBunch ลองดูข้อมูล Batch แรก ด้วย show_batch สั่งให้แสดง รูป พร้อม label ชื่อพันธุ์ไว้บนรูป แสดง 3 แถว (rows) ในกรอบขนาด 9 x 9 นิ้ว (figsize)

เราสามารถรัน cell นี้หลายครั้ง เพื่อเรียกดู batch ต่อ ๆ ไป ได้เรื่อย ๆ เป็นการสำรวจข้อมูล

In [0]:
databunch.show_batch(rows=3, figsize=(9, 9))