ใน ep.4 นี้เราจะมาลองสร้างชุดข้อมูลปัญหาผิวพรรณของเราขึ้นมาเองแบบง่าย ๆ ด้วย Google Images Search หรือถ้าใครมี Domain Expertise เชี่ยวชาญทางด้านไหน เช่น การแพทย์ การผลิต การตลาด การเกษตร การเงิน แฟชั่น etc. ก็สามารถนำมาใช้ได้ ไม่จำกัด แล้วสร้างโมเดล Deep Learning ด้วย Python ให้เรียนรู้จากรูปในอินเตอร์เน็ต ดูว่าความแม่นยำจะเป็นอย่างไร

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

Open In Colab

ใน ep.4 นี้เราจะมาลองสร้าง Dataset ของตัวเอง เพื่อเทรนโมเดล เราเลือกที่จะสร้างชุดข้อมูลปัญหาผิวหน้า ที่พบบ่อยในคนทั่วไป

  • Acne สิว
  • Melasma ฝ้า
  • Freckle กระ

เราเข้าไปที่ Google Images แล้ว Search รูปที่ต้องการ เลือกเป็น Size ใหญ่ แล้วกดปุ่ม F12 แล้วก็อปปี้โค้ดด้านล่างไปแปะ แล้วกด Enter

urls = Array.from(document.querySelectorAll('.rg_di .rg_meta')).map(el=>JSON.parse(el.textContent).ou); window.open('data:text/csv;charset=utf-8,' + escape(urls.join('\n')));

เราจะได้ไฟล์ที่เก็บรายชื่อ URL ของรูปทั้งหมดในผลการค้นหา ให้เรา Save และนำมา Upload ไว้ใน Folder data/skin

0. Magic Commands

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

1. Import Library

In [2]:
from fastai import *
from fastai.vision import *
from fastai.metrics import accuracy

2. ข้อมูล

ให้เราใส่ชื่อไฟล์แ และชื่อ Folder ให้ถูกต้องตาม Category ที่เราจะทำ Classifier ในที่นี้ เรามี 3 หมวดหมู่ คือ สิว acne, ฝ้า melasma, กระ freckle

In [3]:
folder = 'acne'
file = 'urls_acne.txt'
In [6]:
folder = 'freckle'
file = 'urls_freckle.txt'
In [9]:
folder = 'melasma'
file = 'urls_melasma.txt'

สั่งให้สร้าง Folder และ Download รูป ตาม URL ที่อยู่ในไฟล์ จำนวน max_pics = 500 รูป ไปใส่ไว้ Folder dest

หมายเหตุ ให้เรา 1. เลือก Category ในช่องด้านบน แล้วกด Run แล้วจึง 2. มาเลือกช่องด่านล่าง สั่ง Run เพื่อ Download

In [4]:
path = Path('data/skin')
download_path = path/'downloads'
dest = download_path/folder
dest.mkdir(parents=True, exist_ok=True)
In [11]:
download_images(path/file, dest, max_pics=500, max_workers=8)

วนลูปตรวจสอบไฟล์ เนื่องจาก Google Image บอกว่ามีรูปนี้อยู่ แต่จริง ๆ รูปอาจจะไม่ได้อยู่แล้ว บางทีเราจะได้ไฟล์ขยะมาแทน delete=True คือถ้าเจอไฟล์ขยะให้ลบทิ้ง และให้ย่อรูปให้มีขนาดไม่เกิน max_size ทั้งกว้างและยาว Pixel

In [5]:
classes = ['acne', 'freckle', 'melasma']
In [13]:
for c in classes:
    print(c)
    verify_images(download_path/c, delete=True, max_size=480)

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

ใน ep ก่อน ๆ เราใช้ขนาดรูปแค่ 224, 299, 28 Pixel เท่านั้น แต่ในเคสนี้ ถ้ารูปเล็กเกินไปจะทำให้เห็นแต่รูปร่างรวม ๆ ไม่เห็นผิว ไม่เห็นความผิดปกติที่เกิดขึ้นบนผิว เราจึงปรับความละเอียดของรูปที่ใช้เทรน เพิ่มเป็น 400 การปรับจูนพารามิเตอร์ของโมเดลแบบนี้ เรียกว่า Hyperparameter Tuning มีหลายตัวด้วยกัน ซึ่งเราจะอธิบายต่อไป

In [6]:
batchsize = 16
np.random.seed(42)

databunch = ImageDataBunch.from_folder(download_path, train='.', 
                                       valid_pct=0.2, 
                                       ds_tfms=get_transforms(), 
                                       size=400, bs=batchsize).normalize()

# ### อ่านให้จบ ข้อ 8 ก่อน ค่อยย้อนขึ้นมาดูใหม่
# databunch = ImageDataBunch.from_csv(path, csv_labels='cleaned.csv', 
#                                        valid_pct=0.2, 
#                                        ds_tfms=get_transforms(), 
#                                        size=400, bs=batchsize).normalize()

สำรวจข้อมูล

ลองดูข้อมูล Batch แรก ด้วย show_batch สั่งให้แสดง รูป พร้อม label

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

In [7]:
databunch.classes
Out[7]:
['acne', 'freckle', 'melasma']
In [8]:
databunch.show_batch(rows=2, figsize=(8, 8))
In [9]:
databunch.show_batch(rows=2, figsize=(8, 8))
In [10]:
databunch.classes, databunch.c, len(databunch.train_ds), len(databunch.valid_ds)
Out[10]:
(['acne', 'freckle', 'melasma'], 3, 1092, 273)

คุณภาพของรูปจากอินเตอร์เน็ต

จะเห็นได้ว่ารูปที่ดาวน์โหลดมาจาก Google Image คุณภาพค่อนข้างหลากหลาย มีทั้งรูปสต็อก รูปศิลปะ รูปโฆษณา รูปดารา รูปผลิตภัณฑ์ รูปวิชาการ etc. แต่ไม่เป็นไรเราจะลองเทรนโมเดลด้วยรูปเหล่านี้ดู

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

In [11]:
learner = cnn_learner(databunch, models.resnet50, 
                      metrics=accuracy, 
                      callback_fns=ShowGraph).to_fp16()

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

เทรนด้วยค่า Default ไป 8 Epoch

In [12]:
learner.fit_one_cycle(8)
epochtrain_lossvalid_lossaccuracytime
00.9460260.7774300.68498200:38
10.8040340.6879910.73992700:30
20.7092960.6555950.72893800:30
30.6360750.5604170.78022000:30
40.5591830.5680820.78388300:30
50.4817110.5295830.79120900:30
60.4526160.5220620.79853500:30
70.4194530.5251260.77655700:30

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

In [13]:
learner.save('01d-resnet50-1')

โหลดโมเดลที่เรา Save ไว้ ขึ้นมาใหม่ เตรียมเทรนในขึ้นตอนถัดไป (ที่คอมเม้นท์ไว้ เพราะถ้าไม่ได้เปลี่ยนอะไรก็จะได้ไม่ต้องรัน)

In [14]:
# learner.load('01d-resnet50-1')

6. เทรนต่อ

unfreeze layer ทุก Layer ให้สามารถเทรนได้ แล้วเทรนต่อทั้งโมเดล

In [15]:
learner.unfreeze()
In [16]:
learner.fit_one_cycle(6, max_lr=slice(3e-6,1e-3))
epochtrain_lossvalid_lossaccuracytime
00.4285190.4858470.79853500:42
10.4045740.4997530.80952400:40
20.3811400.5126360.83150200:40
30.3029950.4717510.82051300:40
40.2115160.4615730.83150200:40
50.1650150.4717330.82783900:40

สำเร็จแล้ว

เพียงแค่เวลา 7 นาทีเศษ เราเทรน Model ตามวิธีเดิม ด้วยรูปที่คุณภาพหลายหลาย จำนวนแค่ 500 x 3 = 1,500 รูป

แต่โมเดลสามารถเรียนรู้จากรูปเท่าที่มี แยกแยะ Noise ที่ไม่เกี่ยวข้องออกไป ทำให้เราได้ accuracy ประมาณ 0.83 หรือ ความแม่นยำประมาณ 83%

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

In [17]:
learner.save('01d-resnet50-2')
In [18]:
# learner.load('01d-resnet50-2')

7. ดูผลลัพธ์

In [21]:
interpretation = ClassificationInterpretation.from_learner(learner.to_fp32())

โมเดลจำแนกผิวได้ถูกต้อง

สั่งให้ plot_top_losses ด้วย largest=False คือ การแสดง record ที่ ค่า loss น้อยที่สุด หรือพูดง่าย ๆ ว่าแสดงรายการที่โมเดลทายถูกอย่างมั่นใจ 9 อันดับแรก โดยมีการแสดง heat map สีแดง ให้ดูด้วยว่า model พิจารณาเน้นจากส่วนไหนของรูปเป็นหลัก เรียกว่า Attention ที่ไว้เราจะอธิบายต่อไป

In [22]:
interpretation.plot_top_losses(9, figsize=(8, 8), largest=False)