จาก ep ที่แล้ว AI จำแนกรูปภาพ หมา แมว 37 สายพันธุ์ เราได้ใช้ fastai version 1 ในการทำ Image Classification ได้ผลลัพธ์แม่นยำ 94% โดยใช้เวลาเทรนเพียงแค่ไม่เกิน 5 นาที กับ Code หลัก ๆ เพียงแค่ 3 บรรทัด เวลาผ่านไปหลายเดือน ขณะนี้ fastai ออกเวอร์ชันใหม่ เป็น fastai2 มี API ที่เปลี่ยนไปเล็กน้อย เน้นความยืดหยุ่นมากขึ้น ช่วยให้เราเทรนโมเดล และข้อมูลที่มีความซับซ้อนได้อย่างสะดวกยิ่งขึ้น

Dataset

Sample Data from Pets Dataset. Oxford-IIIT Pet Dataset by O. M. Parkhi et al., 2012 Credit http://www.robots.ox.ac.uk/~vgg/data/pets/
Sample Data from Pets Dataset. Oxford-IIIT Pet Dataset by O. M. Parkhi et al., 2012 Credit http://www.robots.ox.ac.uk/~vgg/data/pets/

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

Model Architecture

ResNet34 Architecture Zoom In Deep Residual Learning for Image Recognition Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun https://arxiv.org/abs/1512.03385
ResNet34 Architecture Zoom In Deep Residual Learning for Image Recognition Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun https://arxiv.org/abs/1512.03385

เราจะใช้ resnet34 และ resnet50 ซึ่งเป็นโมเดลแบบ Convolutional Neural Network (CNN, ConvNet) ที่ถูกเทรนกับ ImageNet Dataset มาเรียบร้อยแล้ว โมเดลในตระกูล ResNet เป็นโมเดลที่ได้รับความนิยม และมีประสิทธิภาพสูง

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

Open In Colab

จาก ep ที่แล้ว AI จำแนกรูปภาพ หมา แมว 37 สายพันธุ์ เราได้ใช้ fastai version 1 ในการทำ Image Classification ได้ผลลัพธ์แม่นยำ 94% โดยใช้เวลาเทรนเพียงแค่ไม่เกิน 5 นาที กับ Code หลัก ๆ เพียงแค่ 3 บรรทัด เวลาผ่านไปหลายเดือน ขณะนี้ fastai ออกเวอร์ชันใหม่ เป็น fastai2 มี API ที่เปลี่ยนไปเล็กน้อย เน้นความยืดหยุ่นมากขึ้น ช่วยให้เราเทรนโมเดล และข้อมูลที่มีความซับซ้อนได้อย่างสะดวกยิ่งขึ้น

เช็ค GPU ด้วย คำสั่ง nvidia-smi

In [0]:
# !nvidia-smi

0. Magic Commands

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

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

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

1. Install & Import Library

ติดตั้ง fastai2 หรือ fastai version 2 และ Import Library ที่เราจะใช้

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

In [4]:
!pip install fastai2 -q
     |████████████████████████████████| 184kB 43.9MB/s 

Import 3 Package ย่อย คือ basics, vision.all, callback.all

In [0]:
from fastai2.basics import *
from fastai2.vision.all import *
from fastai2.callback.all import *

เราจะกำหนด Random Seed จะได้ผลลัพธ์ที่เหมือนกันทุกครั้ง จะได้สะดวกในการเปรียบเทียบ

In [0]:
np.random.seed(5555)

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 [7]:
URLs.PETS
Out[7]:
'https://s3.amazonaws.com/fast-ai-imageclas/oxford-iiit-pet.tgz'

untar_data จะดาวน์โหลดไฟล์มาเก็บไว้ แตกไฟล์ แล้ว return path ที่เก็บไฟล์เหล่านั้นไว้

In [8]:
path = untar_data(URLs.PETS)
path
Out[8]:
Path('/root/.fastai/data/oxford-iiit-pet')

List ดูว่ามีโฟลเดอร์อะไรบ้าง

In [9]:
path.ls()
Out[9]:
(#2) [Path('/root/.fastai/data/oxford-iiit-pet/annotations'),Path('/root/.fastai/data/oxford-iiit-pet/images')]

เข้าไป List ดูไฟล์ในโฟลเดอร์ annotations

In [10]:
(path/"annotations").ls()
Out[10]:
(#7) [Path('/root/.fastai/data/oxford-iiit-pet/annotations/trimaps'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/README'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/xmls'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/test.txt'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/._trimaps'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/trainval.txt'),Path('/root/.fastai/data/oxford-iiit-pet/annotations/list.txt')]

เข้าไป List ดูไฟล์ในโฟลเดอร์ images ชื่อพันธุ์จะอยู่ในชื่อไฟล์รูปภาพ

In [11]:
(path/"images").ls()
Out[11]:
(#7393) [Path('/root/.fastai/data/oxford-iiit-pet/images/Russian_Blue_98.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/english_setter_78.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/leonberger_177.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/miniature_pinscher_81.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Egyptian_Mau_223.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/pug_1.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/wheaten_terrier_170.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/British_Shorthair_75.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Birman_122.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Sphynx_19.jpg')...]

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

ก่อนนำข้อมูลใด ๆ มาเทรน Model เราต้องมีการเตรียมข้อมูล กำหนด Data Pipeline ให้อยู่ในรูปแบบที่เหมาะสมเสียก่อน ใน fastai2 นี้ มีการเปลี่ยนแปลง Data Pipeline จาก fastai version 1 หลายอย่าง จะอธิบายต่อไป

นำรายชื่อไฟล์รูปภาพทั้งหมด มาใส่ไว้ใน ตัวแปร fnames

In [12]:
fnames = get_image_files(path/"images")
fnames
Out[12]:
(#7390) [Path('/root/.fastai/data/oxford-iiit-pet/images/Russian_Blue_98.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/english_setter_78.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/leonberger_177.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/miniature_pinscher_81.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Egyptian_Mau_223.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/pug_1.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/wheaten_terrier_170.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/British_Shorthair_75.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Birman_122.jpg'),Path('/root/.fastai/data/oxford-iiit-pet/images/Sphynx_19.jpg')...]

กำหนด Pattern ของ Regex สำหรับสกัดข้อมูล ชื่อสายพันธุ์ จากชื่อไฟล์รูปภาพ

In [13]:
pat = r"([^/]+)_\d+.*$"
re.findall(pat, str(fnames[0]))
Out[13]:
['Russian_Blue']

กำหนด Data Augmentation สำหรับ การ Transformation ระดับ Item เช่น การย่อรูปภาพ การสุ่ม Crop บางส่วนของรูป การปรับสัดส่วนกว้างยาวของรูป ให้เป็นรูปขนาดเท่า ๆ กัน เพื่อส่งไป batch_tfms โดย Transformation เหล่านี้จะถูกประมวลผลบน CPU ทีละรูป

In [0]:
item_tfms = RandomResizedCrop(460, min_scale=0.8, ratio=(1., 1.))

กำหนด Data Augmentation สำหรับ การ Transformation ระดับ Batch เช่น การ Normalize และ Data Augmentation ทั้ง Batch โดย Transformation เหล่านี้จะถูกประมวลผลบน GPU เป็น Batch

In [0]:
batch_tfms = [*aug_transforms(size=224, max_warp=0.15), Normalize.from_stats(*imagenet_stats)]

?? ด้านล่างคือ help ให้ Uncomment ทีละบรรทัด แล้ว Run Cell

In [0]:
# aug_transforms??
# RandomResizedCrop??

กำหนด ขนาด Batch Size ที่จะ Feed ให้โมเดล

In [0]:
bs = 64

3.1 ImageDataLoaders

สร้าง ImageDataLoaders จากชื่อไฟล์ และ RegEx

ใน fastai version 1 ที่เคยใช้ ImageDataBunch.from_name_re จะกลายเป็น ImageDataLoaders.from_name_re ใน fastai2

In [0]:
dls = ImageDataLoaders.from_name_re(path, fnames, pat, bs=bs, 
                                     batch_tfms=batch_tfms, item_tfms=item_tfms)
In [0]:
# ImageDataLoaders.from_name_re??

show_batch แสดงข้อมูลรูปภาพ พร้อม Label จำนวน 1 Batch

In [21]:
dls.show_batch(max_n=9, figsize=(8, 9))

3.2 DataBlock

อีกวิธีหนึ่ง คือ เราสามารถ สร้าง Data Pipeline ด้วย DataBlock API เวอร์ชันใหม่ ที่มีความยืดหยุ่นมากกว่า ImageDataLoaders

In [0]:
pets = DataBlock(blocks=(ImageBlock, CategoryBlock), 
                 get_items=get_image_files, 
                 splitter=RandomSplitter(), 
                 get_y=RegexLabeller(pat=r"/([^/]+)_\d+.*"), 
                 item_tfms=item_tfms, 
                 batch_tfms=batch_tfms)

ข้างบนเราจะใช้ RandomSplitter ในการ Split ข้อมูลใน Dataset ออกเป็น Traning Set และ Validation Set แบบ Random โดยระบุให้ Split valid_pct 80/20

In [24]:
splitter = RandomSplitter(valid_pct=0.2, seed=5555)
splitter(fnames)
Out[24]:
((#5912) [5355,321,5134,134,6524,2441,4612,320,746,2333...],
 (#1478) [950,1053,979,285,440,3041,4225,3800,6844,1079...])

ได้ List แรก จำนวน 5912 Item ระบุ Index ของ fnames ที่จะอยู่ใน Training Set ส่วน List ที่สอง จำนวน 1478 Item ระบุ Index ของ fnames ที่จะอยู่ใน Validation Set

กำหนด path ของรูปภาพ

In [0]:
path_im = path/"images"

สร้าง DataLoaders จาก DataBlock ด้านบน ให้ไปอ่านไฟล์รูปภาพจาก path ที่กำหนด ด้วย Batch Size = bs

In [0]:
dls = pets.dataloaders(path_im, bs=bs)

แสดงข้อมูล Batch ต่อไป แต่จำกัดไว้ที่ 9 Item

เราสามารถรัน Cell นี้ได้หลายครั้ง

In [27]:
dls.show_batch(max_n=9, figsize=(8, 9))

Class ทั้งหมด จะถูกเก็บอยู่ใน List ชื่อ .vocab ให้เราสามารถเข้าถึงได้ตาม Index

In [28]:
dls.vocab[2]
Out[28]:
'Birman'
In [29]:
dls.vocab
Out[29]:
(#37) ['Abyssinian','Bengal','Birman','Bombay','British_Shorthair','Egyptian_Mau','Maine_Coon','Persian','Ragdoll','Russian_Blue'...]

ในการแปลงชื่อ Class กลับเป็นเลข Index เราสามารถใช้ Dictionary ชื่อ .vocab.o2i

In [30]:
dls.vocab.o2i.get("Birman")
Out[30]:
2
In [31]:
dls.vocab.o2i
Out[31]:
{'Abyssinian': 0,
 'Bengal': 1,
 'Birman': 2,
 'Bombay': 3,
 'British_Shorthair': 4,
 'Egyptian_Mau': 5,
 'Maine_Coon': 6,
 'Persian': 7,
 'Ragdoll': 8,
 'Russian_Blue': 9,
 'Siamese': 10,
 'Sphynx': 11,
 'american_bulldog': 12,
 'american_pit_bull_terrier': 13,
 'basset_hound': 14,
 'beagle': 15,
 'boxer': 16,
 'chihuahua': 17,
 'english_cocker_spaniel': 18,
 'english_setter': 19,
 'german_shorthaired': 20,
 'great_pyrenees': 21,
 'havanese': 22,
 'japanese_chin': 23,
 'keeshond': 24,
 'leonberger': 25,
 'miniature_pinscher': 26,
 'newfoundland': 27,
 'pomeranian': 28,
 'pug': 29,
 'saint_bernard': 30,
 'samoyed': 31,
 'scottish_terrier': 32,
 'shiba_inu': 33,
 'staffordshire_bull_terrier': 34,
 'wheaten_terrier': 35,
 'yorkshire_terrier': 36}

4. สร้างโมเดล

สร้าง CNN Learner ด้วยฟังก์ชัน cnn_learner ระบุ DataLoaders ของข้อมูลที่จะใช้เทรน, Model Architecture, ต้องการใช้ Pretrained Model ในการทำ Transfer Learning หรือไม่, metrics การวัดผลในเคสนี้เราจะใช้ Error Rate, และ เทรนแบบ Mixed Precision ด้วย .to_fp16()

In [32]:
learn = cnn_learner(dls, resnet34, pretrained=True, metrics=error_rate).to_fp16()
Downloading: "https://download.pytorch.org/models/resnet34-333f7ec4.pth" to /root/.cache/torch/checkpoints/resnet34-333f7ec4.pth

โดย default cnn_learner จะลบ layer สุดท้ายของ model ที่ดาวน์โหลดมาทิ้งไป และแทนที่ด้วย Dense Layer ใหม่ เป็น Head ที่มี 37 Output ตรงกับ .vocab ของเรา model จะถูก Save ไว้ที่ $HOME/.cache/torch/checkpoints/ เมื่อรันอีกครั้งจะไม่ได้ต้องดาวน์โหลดใหม่

5. เริ่มต้นเทรนโมเดล

ลอง fit ด้วย ค่า default จำนวน 4 epoch

  • 1 epoch คือ ใช้ป้อนข้อมูล หมด Dataset 1 รอบ
  • fit_one_cycle คือ การเทรนแบบพิเศษ คือมีการ Schedule Learning Rate เพิ่มลด ตามแต่ละช่วงของการเทรน
In [33]:
learn.fit_one_cycle(4)
epochtrain_lossvalid_losserror_ratetime
01.9031740.3452670.11637301:03
10.6868680.2469930.07983801:03
20.3906740.2096680.06833601:03
30.2888750.2065120.06698201:02

สำเร็จแล้ว

ด้วยพลังของ GPU เพียงแค่เวลาไม่ถึง 4 นาที เราเทรน Model ได้ Error Rate 0.07 หรือ Accuracy ความแม่นยำประมาณ 93%

เปรียบเทียบกับ paper เมื่อปี 2012 ที่ได้ 59.21% หรือเปรียบเทียบกับมนุษย์ คงเป็นเรื่องยาก ถ้าให้เวลาเรา 4 นาที ดูรูปหมาแมว แล้วให้ทายว่าเป็นพันธุ์อะไร 1 ใน 37 สายพันธุ์ แต่ Model Deep Learning ของเรา นี้สามารถทายได้ถูกต้องถึง 93% โดยใช้เวลาในการเรียนรู้เพียงแค่ 4 นาทีเท่านั้น

เรามา Save Model ที่เราเพิ่งเทรนไปเก็บไว้ก่อน

In [0]:
learn.save("01i-stage1")

6. ดูสถิติของโมเดล

เรามาดูกราฟ Training Loss และ Validation Loss ต่อจำนวน Batch ข้อมูลที่เราป้อนให้โมเดล

In [35]:
learn.recorder.plot_loss()

กราฟค่า lr Learning Rate และ mom Momentum ตามจำนวน Batch / Iteration ของการเทรน จะเห็นได้ว่า Learning Rate และ Momentum ไม่คงที มีขึ้นมีลง ตั้งแต่ประมาณ 0.0003-0.001, 0.95-0.85 เป็นความพิเศษของ fit_one_cycle

In [36]:
learn.recorder.plot_sched()