Pneumonia หรือ โรคปอดอักเสบเรื้อรัง ปอดบวม เป็นโรคที่พบได้ประมาณร้อยละ 8-10 ของผู้ป่วยที่มีการติดเชื้อเฉียบพลันระบบหายใจ นับเป็นสาเหตุการตายอันดับหนึ่งของโรคติดเชื้อในเด็กอายุต่ำกว่า 5 ปี เกิดจากสาเหตุหลัก 2 กลุ่ม คือ ปอดอักเสบที่เกิดจากการติดเชื้อและปอดอักเสบที่ไม่ได้เกิดจากการติดเชื้อ ถ้าเราสามารถพัฒนาระบบ AI ช่วยวินิจฉัยโรคเบื้องต้น จำแนกชนิดของโรค Pneumonia จะมีประโยชน์ในการวินิจฉัย และดูแลรักษาตั้งแต่แรก

Pneumonia คืออะไร

Figure A shows the location of the lungs and airways in the body. This figure also shows pneumonia affecting the lower lobe of the left lung. Figure B shows normal alveoli. Figure C shows infected alveoli. Credit https://commons.wikimedia.org/wiki/File:Lobar_pneumonia_illustrated.jpg
Figure A shows the location of the lungs and airways in the body. This figure also shows pneumonia affecting the lower lobe of the left lung. Figure B shows normal alveoli. Figure C shows infected alveoli. Credit https://commons.wikimedia.org/wiki/File:Lobar_pneumonia_illustrated.jpg

ปอดอักเสบจากการติดเชื้อ หรือ pneumonia (ปอดบวม) เป็นโรคที่เกิดจากการอักเสบของเนื้อปอดบริเวณหลอดลมฝอยส่วนปลาย (terminal และ respiratory bronchiole) ถุงลม (alveoli) และเนื้อเยื่อรอบถุงลม (interstitium เป็นชนิดของปอดอักเสบที่พบได้บ่อยที่สุด โดยเชื้อโรคที่เข้าสู่ปอดและทำให้เกิดการอักเสบของถุงลมปอดและเนื้อเยื่อโดยรอบ ได้แก่ เชื้อไวรัส เชื้อแบคทีเรีย และเชื้อรา

Streptococcus pneumoniae bacteria causes meningitis. Credit https://commons.wikimedia.org/wiki/File:Streptococcus_pneumoniae_-_A_causative_bacteria_of_meningitis.jpg
Streptococcus pneumoniae bacteria causes meningitis. Credit https://commons.wikimedia.org/wiki/File:Streptococcus_pneumoniae_-_A_causative_bacteria_of_meningitis.jpg

ซึ่งเชื้อที่พบจะแตกต่างกันในแต่ละกลุ่มอายุ และสภาพแวดล้อมที่เกิดโรค เป็นโรคที่พบได้ประมาณร้อยละ 8-10 ของผู้ป่วยที่มีการติดเชื้อเฉียบพลันระบบหายใจ นับเป็นสาเหตุการตายอันดับหนึ่งของโรคติดเชื้อในเด็กอายุต่ำกว่า 5 ปี เช่น ได้รับเชื้อจากที่ชุมชนทั่วไป หรือจากภายในโรงพยาบาล

Scanning Electron Micrograph of Streptococcus pneumoniae. Pneumococcus, Streptococci. Credit https://en.wikipedia.org/wiki/File:Streptococcus_pneumoniae.jpg
Scanning Electron Micrograph of Streptococcus pneumoniae. Pneumococcus, Streptococci. Credit https://en.wikipedia.org/wiki/File:Streptococcus_pneumoniae.jpg

ทั้งนี้ เชื้อแบคทีเรียที่พบมักได้แก่ เชื้อ Streptococcus pneumoniae, เชื้อ Haemophilus influenzae type b, เชื้อ Chlamydia pneumoniae, เชื้อ Legionella spp. และเชื้อ Mycoplasma pneumoniae ส่วนเชื้อไวรัส ได้แก่ เชื้อ Respiratory Syncytial Virus (RSV), เชื้อ Influenza หรือเชื้อไข้หวัดใหญ่ และเชื้อราจากมูลนกหรือซากพืชซากสัตว์

Right middle lobe pneumonia in a child Credit https://en.wikipedia.org/wiki/File:RtPneuKidMark.png
Right middle lobe pneumonia in a child Credit https://en.wikipedia.org/wiki/File:RtPneuKidMark.png

วิธีการติดต่อ จากการสำลักเชื้อที่สะสมรวมกลุ่มอยู่บริเวณทางเดินหายใจส่วนบน (upper airway colonization) เชื้อแบคทีเรียส่วนใหญ่ทำให้เกิดปอดอักเสบในชุมชนและปอดอักเสบในโรงพยาบาลจากการสำลักเชื้อที่สะสมรวมกันอยู่บริเวณหลอดคอ (oropharyngeal aspiration) ลงไปสู่เนื้อปอด เช่นสำลักน้ำลาย อาหาร หรือสารคัดหลั่งในทางเดินอาหาร หากในระยะนั้นผู้ป่วยมีร่างกายอ่อนแอ มีการติดเชื้อของทางเดินหายใจส่วนบน เป็นผู้สูงอายุ หรือมีโรคเรื้อรังทางอายุรกรรมร่วมด้วยก็จะทำให้เกิดปอดอักเสบได้

Pneumonia Oxygen unable to reach blood stream. Credit https://commons.wikimedia.org/wiki/File:New_Pneumonia_cartoon.png
Pneumonia Oxygen unable to reach blood stream. Credit https://commons.wikimedia.org/wiki/File:New_Pneumonia_cartoon.png

หรือการหายใจนำเชื้อเข้าสู่ปอดโดยตรง การสูดหายใจเอาเชื้อที่อยู่ในอากาศในรูปละอองฝอยขนาดเล็ก (droplet nuclei) เป็นวิธีสำคัญที่ทำให้เกิดปอดอักเสบจากเชื้อกลุ่ม atypical organisms เชื้อไวรัส เชื้อวัณโรค และเชื้อรา จึงทำให้เกิดการแพร่ระบาดของเชื้อเหล่านี้ได้ง่ายในกลุ่มคนที่อยู่รวมกัน โดยเฉพาะครอบครัว ชั้นเรียน ห้องทำงาน สถานรับเลี้ยงเด็กก่อนวัยเรียน โรงแรม หอพัก กองทหาร ค่ายผู้อพยพ คุก หรือในบริเวณที่มีคนอยู่แออัด

Main symptoms of infectious pneumonia. Credit https://commons.wikimedia.org/wiki/File:Symptoms_of_pneumonia.svg
Main symptoms of infectious pneumonia. Credit https://commons.wikimedia.org/wiki/File:Symptoms_of_pneumonia.svg

อาการของ Pneumonia หรือ โรคปอดอักเสบเรื้อรัง ปอดบวม ได้แก่ ไข้ ไอ หายใจเร็วอาจมีอาการหอบ หายใจลำบาก มี chest retraction, nasal flaring หรือ อาการอื่นๆของภาวะหัวใจล้มเหลว ฟังเสียงปอดอาจได้ยินเสียงกรอบแกรบ (tine or medium crepitations) อาจได้ยินเสียง rhonchi ร่วมด้วย ในกรณีที่พยาธิสภาพเป็นแบบ consolidation อาจได้ยินเสียง bronchial breath sound มีอาการแสดงอื่นๆที่ไม่จำเพาะ เช่น ท้องอืด อาเจียน ซึมโดยเฉพาะเด็กเล็ก

Illustrative Examples of Chest X-Rays in Patients with Pneumonia. Credit https://www.kaggle.com/paultimothymooney/chest-xray-pneumonia
Illustrative Examples of Chest X-Rays in Patients with Pneumonia. Credit https://www.kaggle.com/paultimothymooney/chest-xray-pneumonia

การวินิจฉัย Pneumonia หรือ โรคปอดอักเสบเรื้อรัง ปอดบวม จากอาการแสดงคือ ไข้ ไอ หายใจเร็ว ร่วมกับฟังปอดได้ยินเสียง crepitations หรือ bronchial breath sounds และ ภาพเอ็กซ์เรย์รังสีทรวงอก Chest X-Ray ช่วยยืนยันการวินิจฉัยในผู้ป่วยที่ประวัติและการตรวจร่างกายไม่ชัดเจน ในรายที่มั่นใจในการวินิจฉัยแล้วไม่จำเป็นต้องถ่ายภาพรังสีทรวงอก นอกจากต้องการประเมินว่าผู้ป่วยมีภาวะแทรกซ้อนจากปอดอักเสบหรือไม่

Weighted Cross Entropy Loss

ในเคสนี้จำนวนข้อมูลตัวอย่าง ในแต่ละ Class แตกต่างกันมาก เรียกว่า Class Imbalance เราจะใช้ Loss Function แบบใหม่ ชื่อว่า Weighted Cross Entropy Loss

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

Open In Colab

ปอดอักเสบ เป็นโรคที่พบได้ประมาณร้อยละ 8-10 ของผู้ป่วยที่มีการติดเชื้อเฉียบพลันระบบหายใจ นับเป็นสาเหตุการตายอันดับหนึ่งของโรคติดเชื้อในเด็กอายุต่ำกว่า 5 ปี เกิดจากสาเหตุหลัก 2 กลุ่ม คือ ปอดอักเสบที่เกิดจากการติดเชื้อและปอดอักเสบที่ไม่ได้เกิดจากการติดเชื้อ ถ้าเราสามารถพัฒนาระบบ AI ช่วยวินิจฉัยโรคเบื้องต้น จำแนกชนิดของโรค จะมีประโยชน์ในการวินิจฉัย และดูแลรักษาตั้งแต่แรก

Pneumonia คืออะไร

ปอดอักเสบจากการติดเชื้อ หรือ pneumonia (ปอดบวม) เป็นโรคที่เกิดจากการอักเสบของเนื้อปอดบริเวณหลอดลมฝอยส่วนปลาย (terminal และ respiratory bronchiole) ถุงลม (alveoli) และเนื้อเยื่อรอบถุงลม (interstitium เป็นชนิดของปอดอักเสบที่พบได้บ่อยที่สุด โดยเชื้อโรคที่เข้าสู่ปอดและทำให้เกิดการอักเสบของถุงลมปอดและเนื้อเยื่อโดยรอบ ได้แก่ เชื้อไวรัส เชื้อแบคทีเรีย และเชื้อรา

ซึ่งเชื้อที่พบจะแตกต่างกันในแต่ละกลุ่มอายุ และสภาพแวดล้อมที่เกิดโรค เป็นโรคที่พบได้ประมาณร้อยละ 8-10 ของผู้ป่วยที่มีการติดเชื้อเฉียบพลันระบบหายใจ นับเป็นสาเหตุการตายอันดับหนึ่งของโรคติดเชื้อในเด็กอายุต่ำกว่า 5 ปี เช่น ได้รับเชื้อจากที่ชุมชนทั่วไป หรือจากภายในโรงพยาบาล

ทั้งนี้ เชื้อแบคทีเรียที่พบมักได้แก่ เชื้อ Streptococcus pneumoniae, เชื้อ Haemophilus influenzae type b, เชื้อ Chlamydia pneumoniae, เชื้อ Legionella spp. และเชื้อ Mycoplasma pneumoniae ส่วนเชื้อไวรัส ได้แก่ เชื้อ Respiratory Syncytial Virus (RSV), เชื้อ Influenza หรือเชื้อไข้หวัดใหญ่ และเชื้อราจากมูลนกหรือซากพืชซากสัตว์

เช็ค GPU

In [ ]:
! nvidia-smi
Thu Jul  2 05:12:45 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.36.06    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. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla P100-PCIE...  Off  | 00000000:00:04.0 Off |                    0 |
| N/A   35C    P0    25W / 250W |      0MiB / 16280MiB |      0%      Default |
|                               |                      |                 ERR! |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

0. Magic Command

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

1. Install Library

Install Library ที่จำเป็น ในที่นี้เราจะใช้ fastai2

In [ ]:
# ## Colab
# ! pip install fastai2 kornia -q

2. Import Library

Import Library ที่จำเป็น รวมถึง sklearn ในการคำนวน Metrics

In [ ]:
import gc

from fastai2.basics import *
from fastai2.callback.all import *
from fastai2.metrics import *
from fastai2.vision.all import *

import kornia

import pandas as pd
from sklearn.metrics import *

กำหนด Random Seed จะได้ Reproduce ได้ค่าเดิมทุกครั้งที่รัน

In [ ]:
seed=123456
set_seed(seed)

3. Dataset

ในเคสนี้เราจะใช้ Dataset ฟิล์ม Chest X-Ray Images (Pneumonia) จาก Kaggle

เราจะ Mount Drive ไปยัง Google Drive ที่เก็บ Token File ไว้

In [ ]:
dataset = 'paultimothymooney/chest-xray-pneumonia'

# Google Colab
config_path = Path('/content/drive')
learner_path = config_path/"My Drive"
data_path_base = Path('/content/datasets/')

path = data_path_base/dataset

from google.colab import drive

drive.mount(str(config_path))
os.environ['KAGGLE_CONFIG_DIR'] = f"{config_path}/My Drive/.kaggle"
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).

ในการจะ Download ข้อมูลจาก Kaggle ต้องใช้ Token ดูวิธีได้ใน ep ก่อน

In [ ]:
# !kaggle datasets download {dataset} -p "{path}" --unzip

ls ดูว่าได้ Folder อะไรมาบ้าง

In [ ]:
path.ls()
Out[ ]:
(#1) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray')]
In [ ]:
(path/'chest_xray').ls()
Out[ ]:
(#5) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/chest_xray'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/__MACOSX')]

ดูข้อมูลใน Training Folder

In [ ]:
(path/'chest_xray/train').ls()
Out[ ]:
(#2) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA')]
In [ ]:
(path/'chest_xray/train/PNEUMONIA').ls()
Out[ ]:
(#3875) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person479_virus_978.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person420_bacteria_1848.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person82_virus_155.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person848_virus_1493.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person997_bacteria_2926.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person452_bacteria_1943.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person594_virus_1145.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person315_bacteria_1464.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person1227_virus_2078.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person1376_virus_2367.jpeg')...]
In [ ]:
(path/'chest_xray/train/NORMAL').ls()
Out[ ]:
(#1341) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0539-0001-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0487-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0477-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0535-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0364-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0998-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0464-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0605-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0523-0001-0003.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0797-0001.jpeg')...]

ใช้ฟังก์ชัน get_image_files ดึงไฟล์ทั้งหมดมาใส่ List ไว้ก่อน

In [ ]:
items = get_image_files(path/'chest_xray/train')
items
Out[ ]:
(#5216) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0539-0001-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0487-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0477-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0535-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0364-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0998-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0464-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0605-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0523-0001-0003.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0797-0001.jpeg')...]

4. Data

4.1 Image

ดูรูปภาพ

In [ ]:
patient = 55

เคสนี้ไฟล์ถูกแปลงเป็น jpg แล้ว ไม่ใช่ DICOM

In [ ]:
items[patient]
Out[ ]:
Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0500-0001.jpeg')
In [ ]:
item = PILImage.create(items[patient])
item.show()
Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f367ffdcb00>

Metadata เป็นรูปสี RGB 3 Channel แบบสี่เหลี่ยมผืนผ้า

In [ ]:
str(item)
Out[ ]:
'PILImage mode=RGB size=1400x1239'

Label อยู่ในชื่อ Parent Folder

In [ ]:
parent_label(items[patient])
Out[ ]:
'NORMAL'

5. Data Pipeline

5.1 Exploratory Data Analysis (EDA)

สำรวจข้อมูล Exploratory Data Analysis จะเห็นว่า มี Class Imbalance แตกต่างกันในแต่ละ Set ประมาณ 1 ต่อ 3, 1 ต่อ 1 และ 1 ต่อ 1.7

In [ ]:
trn0 = get_image_files(path/'chest_xray/train/NORMAL')
trn1 = get_image_files(path/'chest_xray/train/PNEUMONIA')

val0 = get_image_files(path/'chest_xray/val/NORMAL')
val1 = get_image_files(path/'chest_xray/val/PNEUMONIA')

tst0 = get_image_files(path/'chest_xray/test/NORMAL')
tst1 = get_image_files(path/'chest_xray/test/PNEUMONIA')

trn0, trn1, val0, val1, tst0, tst1
Out[ ]:
((#1341) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0539-0001-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0487-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0477-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0535-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0364-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0998-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0464-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0605-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/IM-0523-0001-0003.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/NORMAL/NORMAL2-IM-0797-0001.jpeg')...],
 (#3875) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person479_virus_978.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person420_bacteria_1848.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person82_virus_155.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person848_virus_1493.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person997_bacteria_2926.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person452_bacteria_1943.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person594_virus_1145.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person315_bacteria_1464.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person1227_virus_2078.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/person1376_virus_2367.jpeg')...],
 (#8) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1427-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1436-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1442-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1437-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1438-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1430-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1440-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1431-0001.jpeg')],
 (#8) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1950_bacteria_4881.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1954_bacteria_4886.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1952_bacteria_4883.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1946_bacteria_4875.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1951_bacteria_4882.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1946_bacteria_4874.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1947_bacteria_4876.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/val/PNEUMONIA/person1949_bacteria_4880.jpeg')],
 (#234) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0036-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0093-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0011-0001-0002.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/NORMAL2-IM-0171-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/NORMAL2-IM-0345-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/NORMAL2-IM-0317-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0025-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/NORMAL2-IM-0195-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0001-0001.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/NORMAL/IM-0059-0001.jpeg')...],
 (#390) [Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person91_bacteria_448.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person85_bacteria_423.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person93_bacteria_454.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person143_bacteria_689.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person1661_virus_2872.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person158_bacteria_744.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person99_bacteria_473.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person1685_virus_2903.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person88_bacteria_438.jpeg'),Path('/content/datasets/paultimothymooney/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/person152_bacteria_721.jpeg')...])

เนื่องจากใน val Folder มีแค่ class ละ 8 ไฟล์ เราจะรวม train กับ val Folder เข้าด้วยกัน แล้ว Split เอง 80/20

5.2 DataBlock

สร้าง DataBlock และ DataLoader โดยทำ Data Augmentation ตาม Default

ใช้ Data Echoing เพิ่มความเร็วในการเทรน

In [ ]:
class EchoingTransform(ItemTransform):
    order = 2
    split_idx = 0
    def __init__(self, e): self.e = e
    def encodes(self, x):
        img, lbl = x
        # print(img.shape)
        # print(lbl.shape)
        if self.e > 1:
            img = img.repeat(self.e, 1, 1, 1)
            lbl = lbl.repeat(self.e)
        return img, lbl

สร้าง DataBlock โดยใช้ Partial กำหนดให้ใช้ข้อมูลจาก Folder train และ val

In [ ]:
def getDataLoaders(bs, size, e):
    pneumonia = DataBlock(blocks=(ImageBlock(), CategoryBlock), 
                         get_items=partial(get_image_files, folders=['train', 'val']), 
                         get_y=parent_label, 
                         splitter=RandomSplitter(), 
                         item_tfms=RandomResizedCrop(size, min_scale=0.8), 
                         batch_tfms=[EchoingTransform(e), *aug_transforms()]
                         )
    # pneumotpneumoniahorax.summary(path/'chest_xray')
    dls = pneumonia.dataloaders(path/'chest_xray', bs=bs)
    return dls    

สร้าง DataLoader ด้วย size 224 และ Batch Size 64 แล้วแสดงข้อมูลตัวอย่าง ใน Batch

In [ ]:
bs, size, e = 64, 224, 3
In [ ]:
dls = getDataLoaders(bs, size, e)
dls.show_batch(max_n=16)

เช็คว่ามี 2 Class

In [ ]:
dls.vocab
Out[ ]:
(#2) ['NORMAL','PNEUMONIA']

6. Model

กำหนด Loss Function

In [ ]:
class XFocalLoss(kornia.losses.FocalLoss):
    y_int = True
    def __init__(self, alpha: float, gamma: float = 2.0,
                 reduction: str = 'none') -> None:
        super(XFocalLoss, self).__init__(alpha, gamma, reduction)

    def forward(  # type: ignore
            self,
            input: torch.Tensor,
            target: torch.Tensor) -> torch.Tensor:       

        # set_trace()
        # print(input.shape)
        # print(target.shape)
        return super().forward(input, target)      

    def decodes(self, x):    return x.argmax(dim=1)
    def activation(self, x): return F.softmax(x, dim=1)               

เนื่องจาก Class Imbalance เราจะใช้ Weighted Cross Entropy Loss โดยให้น้ำหนักกับ Normal มากกว่า Pneumonia

In [ ]:
weights = torch.tensor([[1.8]*1 + [0.4]]).cuda()
loss_func = CrossEntropyLossFlat(weight=weights)

# loss_func = CrossEntropyLossFlat()

# loss_func = XFocalLoss(alpha=1.0, gamma=2.0, reduction='mean')

ใช้ Convolutional Neural Network สถาปัตยกรรม ResNet34 เวอร์ชันพิเศษของ fastai ชื่อว่า xresnet

In [ ]:
arch = xresnet34(pretrained=False, c_in=3, act_cls=Mish, sa=True, n_out=2)

ดู Activation Function เป็น Mish แทนที่จะเป็น ReLU ตามปกติ

In [ ]:
arch[0]
Out[ ]:
ConvLayer(
  (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
  (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): Mish()
)

สร้าง Learner จาก Architecture ด้านบน โดยมี Metrics เช่น Accuracy, F1 Score และ Recall

In [ ]:
# learn = cnn_learner(dls, arch=arch, metrics=accuracy, 
#                     loss_func=loss_func, opt_func=ranger, 
#                     cbs=[ShowGraphCallback])

learn = Learner(dls, arch, loss_func=loss_func, opt_func=ranger, 
                cbs=[ShowGraphCallback], path=learner_path, 
                metrics=[accuracy, F1Score(axis=1), Recall(axis=1)])

7. Train

7.1 Fine-tune

เริ่มต้นเทรนตั้งแต่ต้น From Scratch ทั้งโมเดล ด้วย fit_flat_cos เนื่องจากเราใช้ Mish Activation Function

In [ ]:
# learn.lr_find()
In [ ]:
learn.fit_flat_cos(1, lr=slice(1e-4, 1e-2))
epochtrain_lossvalid_lossaccuracyf1_scorerecall_scoretime
00.3328670.2278940.8604210.8963070.82054601:28
In [ ]:
learn.fit_flat_cos(12, lr=slice(3e-5, 3e-3))
epochtrain_lossvalid_lossaccuracyf1_scorerecall_scoretime
00.2069640.1643790.9292540.9502020.91807501:27
10.1788070.2223400.8212240.8617890.75812701:27
20.1730670.4047590.8804970.9183540.91417401:27
30.1600130.2275200.9608030.9739680.99739901:27
40.1423400.1000970.9760990.9837560.98439501:27
50.1416870.0993820.9655830.9764090.96879101:27
60.1428730.1416680.9713190.9806950.99089701:26
70.1264430.0908120.9780110.9850940.98829601:27
80.1122790.1177930.9101340.9349930.87906401:27
90.1035460.0830560.9732310.9817470.97919401:27
100.0920320.0687680.9789670.9857700.99089701:27
110.0774320.0423340.9866160.9908620.98699601:27

ได้ F1 Score 98.6% เซฟโมเดลไว้ก่อน

In [ ]:
learn.save("01k_224-1")

7.2 Progressive Resizing

สร้าง Data Loader ใหม่ ด้วย size รูปที่ใหญ่ขึ้น เป็นขนาด 384 x 384 Pixel และ ลดขนาด Batch Size ลงเป็นเท่ากับ 32 เพื่อไม่ให้ GPU Memory เต็ม เราสามารถเช็คขนาดได้ด้วยคำสั่ง nvidia-smi

In [ ]:
learn = None
dls = None
gc.collect()
torch.cuda.empty_cache()
In [ ]:
bs, size, e = 32, 384, 2
In [ ]:
dls = getDataLoaders(bs, size, e)

สร้าง Learner ใหม่จาก Data Loader ด้านบน

In [ ]:
# learn = cnn_learner(dls, arch=arch, metrics=accuracy, 
#                     loss_func=loss_func, opt_func=ranger, 
#                     cbs=[ShowGraphCallback])

learn = Learner(dls, arch, loss_func=loss_func, opt_func=ranger, 
                cbs=[ShowGraphCallback], path=learner_path, 
                metrics=[accuracy, F1Score(axis=1), Recall(axis=1)])

โหลดโมเดล 224 ขึ้นมา ทำ Transfer Learning เทรนต่อ

In [ ]:
learn.load("01k_224-1")
Out[ ]:
<fastai2.learner.Learner at 0x7f367d2d4860>

Freeze โมเดล ยกเว้น Layer Group สุดท้าย

In [ ]:
learn.freeze()

เทรนต่อ

In [ ]:
# learn.lr_find()
In [ ]:
learn.fit_flat_cos(8, lr=slice(3e-4))
epochtrain_lossvalid_lossaccuracyf1_scorerecall_scoretime
00.1329860.0870220.9741870.9824790.97930101:57
10.1036490.1033070.9760990.9839430.99094401:56
20.1031440.0881720.9799240.9864600.98965101:56
30.0906160.0739290.9799240.9863900.98447601:56
40.0863680.0914800.9799240.9864950.99223801:57
50.0900800.0928610.9789670.9858610.99223801:59
60.0836400.0858970.9808800.9871130.99094401:58
70.0699820.0809660.9827920.9884020.99223801:58

เซฟไว้ก่อน

In [ ]:
learn.save("01k_384-1")
In [ ]:
# learn.load("01k_384-1");

Unfreeze แล้วเทรนต่อทั้งโมเดล

In [ ]:
learn.unfreeze()
In [ ]:
# learn.lr_find()
In [ ]:
learn.fit_flat_cos(4, lr=slice(1e-6, 1e-4))
epochtrain_lossvalid_lossaccuracyf1_scorerecall_scoretime
00.0658260.1076740.9799240.9864950.99223801:57
10.0797540.1049480.9799240.9865130.99353201:57
20.0797500.0881690.9827920.9884020.99223801:58
30.0635060.0709070.9856600.9903160.99223801:59
In [ ]:
learn.save("01k_384-2")
In [ ]:
# learn.load("01k_384-2");

7.3 Data Augmentation Annealing

ก่อนจบ เราจะเทรนแบบไม่ใช้ Data Augmentation กันอีกสักหน่อย

In [ ]:
bs, size = 32, 384 

pneumonia = DataBlock(blocks=(ImageBlock(), CategoryBlock), 
                         get_items=partial(get_image_files, folders=['train', 'val']), 
                         get_y=parent_label, 
                         splitter=RandomSplitter(valid_pct=0.05), 
                         item_tfms=Resize(size, method='squish'), 
                         batch_tfms=[]
                         )
# pneumonia.summary(path/'chest_xray')
dls = pneumonia.dataloaders(path/'chest_xray', bs=bs)

สร้าง Learner ใหม่จาก Data Loader ด้านบน

In [ ]:
# learn = cnn_learner(dls, arch=arch, metrics=accuracy, 
#                     loss_func=loss_func, opt_func=ranger, 
#                     cbs=[ShowGraphCallback])

learn = Learner(dls, arch, loss_func=loss_func, opt_func=ranger, 
                cbs=[ShowGraphCallback], path=learner_path, 
                metrics=[accuracy, F1Score(axis=1), Recall(axis=1)])

โหลดโมเดลด้านบนขึ้นมา

In [ ]:
learn.load("01k_384-2");
learn.unfreeze()
In [ ]:
learn.fit_flat_cos(1, lr=slice(3e-7, 3e-5))
epochtrain_lossvalid_lossaccuracyf1_scorerecall_scoretime
00.0508850.0560790.9770120.9850750.97058801:48

ได้ Accuracy 97.7%, F1 Score 98.5% และ Recall 97.0%

In [ ]:
learn.save("01k_384-3")
In [ ]:
# learn.load("01k_384-3");

แสดงผลลัพธ์การทำงาน

In [ ]:
learn.show_results(max_n=16)