{"cells":[{"metadata":{},"cell_type":"markdown","source":"สวัสดีครับ ขอต้อนรับทุกท่านเข้าสู่อีก workshop นึง ของ [ThAIKeras](https://thaikeras.com/) ใน workshop นี้ เราจะมาลองทำ image segmentation กัน บนข้อมูล [iMaterialist - Fashion 2019](https://github.com/visipedia/imat_comp) ซึ่งเป็นข้อมูลที่ใช้ใน[การแข่งขัน](https://www.kaggle.com/c/imaterialist-fashion-2019-FGVC6)ของ Kaggle ที่เพิ่งจะจบไปไม่นานนี้เองครับ"},{"metadata":{},"cell_type":"markdown","source":"สำหรับ kernel นี้ จะแบ่งออกเป็นสองส่วนใหญ่ๆ โดยในส่วนแรก จะเป็นการเกริ่นนำถึงปัญหา image segmentation และอธิบายเกี่ยวกับ [Mask R-CNN](https://arxiv.org/abs/1703.06870) ซึ่งเป็นโมเดลที่เราจะใช้ในงานนี้ และในส่วนที่สองจะเป็นส่วนของการเขียนโปรแกรมครับ"},{"metadata":{},"cell_type":"markdown","source":"***"},{"metadata":{},"cell_type":"markdown","source":"# 1. ภาคทฤษฎี"},{"metadata":{},"cell_type":"markdown","source":"## 1.1 Mask R-CNN"},{"metadata":{},"cell_type":"markdown","source":"### 1.1.1 งานด้านต่างๆ ในทาง computer vision"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/zsla1eq.png\" style=\"height:300px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์คอร์ส CS231n](http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture12.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"งานหลักๆ ในทาง computer vision สามารถแบ่งออกได้เป็นสี่อย่าง ตามรูปด้านบนนี้ ได้แก่\n* __Classification__ คือการบอกว่ารูปที่เห็นอยู่นี้เป็นรูปอะไร\n* __Semantic Segmentation__ คือการจำแนกแต่ละ pixel ในรูป ว่าเป็น pixel ของวัตถุใด\n* __Object Detection__ คือการหาตำแหน่งของวัตถุต่างๆ ภายในรูป\n* __Instance Segmentation__ คือการหาว่าวัตถุต่างๆ ภายในรูป มี pixel ไหนบ้าง ซึ่ง Mask R-CNN คือโมเดลที่ออกแบบมาเพื่อใช้สำหรับงานนี้โดยเฉพาะ\n\nงาน instance segmentation เหมือนเป็นการรวม object detection และ semantic segmentation เข้าไว้ด้วยกัน ดังรูปด้านล่างนี้ครับ"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/Z24CDIV.png\" style=\"height:200px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"ซึ่งก่อนหน้าที่ Mask R-CNN จะถือกำเนิดขึ้นมา งานด้าน object detection จะมีโมเดล [Faster R-CNN](https://arxiv.org/abs/1506.01497) ซึ่งใช้ทำงานนี้ได้ดี ส่วนงานด้าน semantic segmentation ก็จะมีโมเดล [FCN](https://arxiv.org/abs/1411.4038) (Fully Convolutional Network) ที่ใช้ทำงานนี้ได้ดีเช่นเดียวกัน โมเดล Mask R-CNN เหมือนเป็นการรวมทั้ง Faster R-CNN และ FCN เข้าไว้ด้วยกัน หรืออาจจะมองว่า Mask R-CNN ใช้ Faster R-CNN เป็นพื้นฐาน และนำ FCN มาเพิ่มเติมก็ได้ ดังรูปด้านล่างนี้ครับ"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/p8EuAP5.png\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"### 1.1.2 Faster R-CNN"},{"metadata":{},"cell_type":"markdown","source":"Faster R-CNN เป็นโมเดลที่พัฒนาต่อเนื่องมาจาก R-CNN และ Fast R-CNN ท่านที่สนใจเรียนรู้เรื่องนี้เพิ่มเติม สามารถอ่านได้ที่[บล็อกของคุณ Lilian Weng](https://lilianweng.github.io/lil-log/2017/12/31/object-recognition-for-dummies-part-3.html) หรือ [lecture ของคอร์ส CS231n](https://www.youtube.com/watch?v=nDPWywWRIRo) ก็ได้ครับ สำหรับในที่นี้จะขอกล่าวถึงเพียง Faster R-CNN ซึ่่งสามารถสรุปได้ดังรูปด้านล่างนี้"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/slKHttx.png\"  style=\"height:400px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์คอร์ส CS231n](http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture12.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"การทำ object detection ของตระกูล R-CNN จะเรียกว่า two-stage detection คือมีการแบ่งการประมวลผลเป็นสองขั้นตอน ดังนี้ครับ\n\n* __ขั้นตอนแรก (first stage)__ เป็นการหาขอบเขต (region หรือ bounding box) ที่น่าจะมีวัตถุอยู่ เพื่อจะส่งเข้าสู่ขั้นตอนที่สองต่อไป ในขั้นตอนนี้จะมีส่วนประกอบที่สำคัญคือ\n\n    * __backbone network__ เป็น neural network ที่รับรูปภาพ input และประมวลผลจนได้เป็น feature map ออกมา โดย backbone network จะใช้ architecture สำหรับงาน computer vision แบบใดก็ได้ เช่น [ResNet](http://www.arxiv.org/abs/1512.03385) หรือ [ResNeXt](https://arxiv.org/abs/1611.05431) หรือถ้าใช้ [Feature Pyramid](https://arxiv.org/abs/1612.03144) ร่วมด้วย ก็จะทำให้ได้ความถูกต้องที่สูงขึ้น\n    \n    * __region proposal network__ เป็น neural network ที่ทำ classification เพื่อจำแนกว่าขอบเขตไหนที่น่าจะมีวัตถุอยู่ รวมทั้งมีการทำ regression เพื่อที่จะได้ขอบเขตที่แม่นยำขึ้น สำหรับ region proposal network นี้เอง เป็นสิ่งที่ทำให้ Faster R-CNN แตกต่างจาก R-CNN ที่ผ่านมา ซึ่งใช้ region proposal algorithm ภายนอก อย่าง [Selective Search](http://www.huppelen.nl/publications/selectiveSearchDraft.pdf) เข้ามาช่วย\n    \n    ขอบเขตที่ region proposal network นำมาพิจารณา (อาจเรียกว่า anchor box) คือกล่องสี่เหลี่ยมหลายๆ ขนาด ที่ครอบคลุม feature map ในตำแหน่งต่างๆ ดังเช่นรูปด้านล่างนี้แสดงตัวอย่างการทำงานของขั้นตอนแรก เมื่อรูปแมวเหมียวขนาด 640x480 ผ่าน CNN ที่เป็น backbone network เข้ามา จะได้ feature map ขนาด 20x15 และมีจำนวน channel เท่ากับ 512 \n    \n    กำหนดให้แต่ละจุดของ feature map มี anchor box จำนวน K ขนาด จะได้ว่ามีจำนวน anchor box ที่ต้องพิจารณาทั้งหมดเท่ากับ Kx20x15 และในจำนวน anchor box ทั้งหมดนี้ ในส่วนของ region proposal network ก็จะทำการหาขอบเขตที่น่าจะเป็น object มากที่สุดมาจำนวนนึง เช่น 300 ขอบเขต เพื่อส่งเป็น region proposal หรือ RoI (Region of Interest) สำหรับขั้นตอนต่อไปครับ\n    \n"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/2UA5zwG.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์คอร์ส CS231n](http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture12.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"* __ขั้นตอนที่สอง (second stage)__ ประกอบด้วยกระบวนการต่างๆ ดังนี้ครับ\n\n    * __RoI Pool__ เป็นการทำ RoI ของ feature map ที่มีขนาดแตกต่างกัน ให้มีขนาดเดียวกัน เพื่อเป็น input ให้กับ neural network ในชั้นต่อไป โดย RoI Pool จะใช้ max pooling เลย แต่ Mask R-CNN จะเปลี่ยนขั้นตอนนี้เป็น RoI Align ซึ่งจะกล่าวถึงในหัวข้อถัดไปครับ\n    \n    * ส่วนสุดท้ายจะเป็นหัว classification ที่ทำการจำแนกว่าเป็น object คลาสอะไร (หัว class) และหัว regression ที่ทำการปรับขอบเขตของ RoI ให้แม่นยำขึ้น (หัว box)\n    \n    "},{"metadata":{},"cell_type":"markdown","source":"### 1.1.3 FCN\n\nสำหรับ FCN เป็น neural network ที่มีลักษณะดังรูปด้านล่างนี้ โดยจะรับ input เข้ามาเป็นรูปภาพ และให้ output เป็นการจำแนกคลาสของแต่ละ pixel ในรูปภาพ โดย feature map ของแต่ละ layer จะมีขนาดเล็กลงเรื่อยๆ (downsampling) จนถึงตรงกลาง แล้วค่อยขยายให้ใหญ่ขึ้น (upsampling) ที่เป็นเช่นนี้เพราะว่า ถ้าทำ convolution ขนาดเดียวกันกับรูปภาพทั้งหมด จะสิ้นเปลืองการคำนวณมาก นอกจากนี้ยังมีเหตุผลเช่นเดียวกับการทำ [Autoencoder](https://en.wikipedia.org/wiki/Autoencoder) นั่นคือในส่วนของ downsampling จะช่วยคัดกรองข้อมูลที่สำคัญเกี่ยวกับความหมายของรูปภาพออกมา (what part) ก่อนที่จะทำการ upsampling เพื่อเก็บรายละเอียดเกี่ยวกับตำแหน่งให้แม่นยำขึ้น (where part) นอกจากนี้จะมี skip connection เพื่อรักษาข้อมูลไม่ให้สูญหายในขั้น downsampling ด้วย"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/4Blooki.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์คอร์ส CS231n](http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture12.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"### 1.1.4 RoI Align\nในกรณีที่ RoI ของรูปภาพ input ไม่ตรงกับ grid ของ feature map ถ้าใช้ RoI Pool ธรรมดา จะต้องทำการเลื่อนให้ RoI มาอยู่ตรงกับ grid ซึ่งตรงนี้เองที่ทำให้มีความคลาดเคลื่อนของตำแหน่งเกิดขึ้นได้ ดังรูปด้านล่างนี้\n\n<img src=\"https://i.imgur.com/6QJy11h.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"RoI Align แก้ปัญหาของ RoI Pool โดยวิธีการที่แสดงดังตัวอย่างในรูปด้านล่าง เช่นเมื่อมี feature map ขนาด 5x5 และ RoI คือสี่เหลี่ยมใหญ่สีส้ม ถ้าต้องการให้ feature map ของ RoI นี้เหลือขนาด 2x2 ก็จะทำการแบ่ง RoI ตั้งต้นเป็นสี่ส่วนเท่าๆ กัน แล้วกำหนดจุดภายในขึ้นมาสี่จุดในแต่ละส่วน และคำนวณค่า feature ของจุดทั้งหมด โดยใช้ bilinear interpolation จากค่า feature จริงที่อยู่ใกล้เคียง จากนั้นจึงทำ max pooling ในแต่ละส่วน ก็จะได้ feature map ขนาด 2x2 ตามที่ต้องการครับ\n\n<img src=\"https://i.imgur.com/mIKiNwx.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"### 1.1.5 Mask Head\n\nนอกจาก RoI Align แล้ว สิ่งที่ทำให้ Mask R-CNN ต่างจาก Faster R-CNN ก็คือการเพิ่มหัว mask เข้ามา จากหัว class และหัว box ที่มีอยู่เดิม เพื่อทำหน้าที่ทำนาย mask ที่อยู่ภายใน RoI ของทุกคลาส ดังรูปด้านล่าง โดยในรูปนี้เป็นการแสดงหัว mask มีขนาด 28x28 และมี 80 คลาส\n\n<img src=\"https://i.imgur.com/LQvDjFk.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"หลังจากนั้นจะทำการปรับสเกลของ mask ให้มีขนาดเท่ากับ RoI ของรูปต้นฉบับ เพื่อเป็น output สำหรับส่วนของงาน segmentation ดังรูปด้านล่างนี้ครับ\n\n<img src=\"https://i.imgur.com/eeC0SBh.png\" style=\"height:250px\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[สไลด์ของ Kaiming He](http://kaiminghe.com/iccv17maskrcnn/maskrcnn_iccv2017_oral_kaiminghe.pdf))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"## 1.2 iMaterialist - Fashion 2019"},{"metadata":{},"cell_type":"markdown","source":"### 1.2.1 แนะนำการแข่งขัน"},{"metadata":{},"cell_type":"markdown","source":"สำหรับการแข่งขันในครั้งนี้ จัดเป็นงาน instance segmentation เพื่อแยกประเภทของเครื่องแต่งกายที่ปรากฏอยู่ภายในรูปภาพ ตัวอย่างเช่นในรูปด้านล่างนี้ โดยข้อมูลที่ให้มา จะมี label อยู่สองส่วน ได้แก่\n* __ส่วนของ class__ คลาสในที่นี้จะประกอบไปด้วย category และ attribute สำหรับ category คือประเภทของเครื่องแต่งกาย เช่น เสื้อ กางเกง ส่วน attribute จะเป็นลักษณะของ category นั้น เช่น (กางเกง) ฟิต เหนือเอว ลายเรียบ เป็นต้น\n* __ส่วนของ mask__ จะบอกว่าวัตถุคลาสต่างๆ ประกอบด้วย pixel ไหนบ้าง\n\nการแข่งขันครั้งนี้เป็นส่วนนึงของ [FGVC6 workshop](https://sites.google.com/view/fgvc6/) ซึ่งจัดขึ้นรวมกับ [CVPR conference ปีนี้](http://cvpr2019.thecvf.com/) ด้วยครับ"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/lrssrix.png\">\n<div style=\"text-align: right\"><font size=\"2\">(ภาพจาก[ข้อมูล iMaterialist - Fashion 2019](https://github.com/visipedia/imat_comp))</font></div>"},{"metadata":{},"cell_type":"markdown","source":"### 1.2.2 การวัดผล"},{"metadata":{},"cell_type":"markdown","source":"โดยทั่วไปในงาน image segmentation จะใช้ค่า IoU (Intersection over Union) ในการวัดผล โดยค่า IoU ก็คือ\n\n$$IoU(G, P) = \\frac{\\left | G\\cap P \\right |}{\\left | G\\cup P \\right |}$$\n\nเมื่อ $G$ คือเซ็ตของ pixel ที่เป็น ground truth\nและ $P$ คือเซ็ตของ pixel ที่เป็น prediction ของเรา\n\nสำหรับค่า IoU สามารถเขียนให้อยู่ในอีกรูปนึงได้เป็น\n\n$$IoU = \\frac{TP}{TP+FP+FN}$$\n\nเมื่อ TP, FP และ FN คือจำนวน pixel ที่เป็น true positive, false positive และ false negative ตามลำดับ\n\nโดยในงานนี้จะเพิ่มค่า threshold ระดับต่างๆ เข้ามาด้วย โดยมี 10 ระดับ คือ {0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95} แล้วในแต่ละระดับ ถ้าค่า IoU ไม่เกิน threshold ที่กำหนด ค่า TP ที่ threshold นี้ ($TP(t)$) จะกลายเป็น 0 เลย และการวัดผลจริงๆ จะนำค่าของทุก threshold มาเฉลี่ยกัน ดังสูตรด้านล่างนี้ครับ\n\n$$\\frac{1}{\\left | threshold \\right |}\\sum_{t}\\frac{TP(t)}{TP(t)+FP(t)+FN(t)}$$"},{"metadata":{},"cell_type":"markdown","source":"***"},{"metadata":{},"cell_type":"markdown","source":"# 2. ภาคปฏิบัติ"},{"metadata":{},"cell_type":"markdown","source":"ในปัจจุบันมี library สำหรับ Mask R-CNN ให้เลือกใช้มากมาย เช่น \n* __[torchvision 0.3](https://pytorch.org/blog/torchvision03/)__ ซึ่ง torchvision เวอร์ชันนี้ ก็มีโมเดล Mask R-CNN มาให้เรียกใช้งานได้\n* __[maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark)__ เป็น library ที่พัฒนาโดยทีม Facebook Research\n* __[mmdetection](https://github.com/open-mmlab/mmdetection)__ เป็น library ที่พัฒนาโดยทีมที่ชนะการแข่งขัน [COCO Detection 2018 challenge](http://cocodataset.org/#detection-leaderboard)\n* __[Mask R-CNN ของบริษัท Matterport](https://github.com/matterport/Mask_RCNN)__\n\nโดยสาม library แรกได้ใช้ PyTorch ในการทำ ส่วน library สุดท้ายจะเป็น Keras กับ TensorFlow ครับ"},{"metadata":{},"cell_type":"markdown","source":"## 2.1 Import and Define\nในขั้นแรกเป็นการ import ตัว library ต่างๆ และกำหนดค่าตัวแปรที่ต้องใช้"},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport gc\nimport sys\nimport json\nimport glob\nimport random\nfrom pathlib import Path\n\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport itertools\nfrom tqdm import tqdm\n\nfrom imgaug import augmenters as iaa\nfrom sklearn.model_selection import train_test_split\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DATA_DIR = Path('/kaggle/input')\nROOT_DIR = Path('/kaggle/working')\n\nNUM_CATS = 46 # ในที่นี้จะทำ classification เฉพาะส่วนที่เป็น category ซึ่งมีจำนวนทั้งสิ้น 46\nIMAGE_SIZE = 512 # รูปภาพที่เป็น input เข้า Mask R-CNN จะถูกทำให้เป็นขนาด 512x512 ซึ่งเท่ากับขนาดของ mask ที่ส่งเป็นคำตอบ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2.2 Download Libraries and Pretrained Weights"},{"metadata":{},"cell_type":"markdown","source":"ในที่นี้ เราได้เลือก Mask R-CNN ของบริษัท Matterport มาใช้ครับ ก็จะทำการดาวน์โหลด library ตัวนี้มา รวมทั้งดาวน์โหลด pretrained weight ที่มีให้ด้วย"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"!git clone https://www.github.com/matterport/Mask_RCNN.git\nos.chdir('Mask_RCNN')\n\n!rm -rf .git\n!rm -rf images assets","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"sys.path.append(ROOT_DIR/'Mask_RCNN')\nfrom mrcnn.config import Config\nfrom mrcnn import utils\nimport mrcnn.model as modellib\nfrom mrcnn import visualize\nfrom mrcnn.model import log","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!wget --quiet https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5\n!ls -lh mask_rcnn_coco.h5\n\nCOCO_WEIGHTS_PATH = 'mask_rcnn_coco.h5'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2.3 Set Config"},{"metadata":{},"cell_type":"markdown","source":"โมเดล Mask R-CNN จะมี hyperparameter ให้ปรับได้มาก ในที่นี้ได้ทำการปรับแค่บางตัว สำหรับ hyperparameter ทั้งหมด ท่านสามารถดูได้[ที่นี่](https://github.com/matterport/Mask_RCNN/blob/master/mrcnn/config.py) ว่ามีอะไรบ้างครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"class FashionConfig(Config):\n    NAME = \"fashion\"\n    NUM_CLASSES = NUM_CATS + 1 # +1 สำหรับคลาส background\n    \n    GPU_COUNT = 1\n    IMAGES_PER_GPU = 4\n    \n    BACKBONE = 'resnet50'\n    \n    IMAGE_MIN_DIM = IMAGE_SIZE\n    IMAGE_MAX_DIM = IMAGE_SIZE    \n    IMAGE_RESIZE_MODE = 'none'\n    \n    RPN_ANCHOR_SCALES = (16, 32, 64, 128, 256)\n    TRAIN_ROIS_PER_IMAGE = 100\n    \n    # เนื่องจาก Kaggle จำกัดเวลาในการรัน kernel ไว้ 9 ชั่วโมง \n    # เราจึงกำหนดค่า STEPS_PER_EPOCH และ VALIDATION_STEPS ให้รันทันในเวลานี้ครับ\n    STEPS_PER_EPOCH = 5500\n    VALIDATION_STEPS = 100\n    \nconfig = FashionConfig()\nconfig.display()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2.4 Prepare Datasets"},{"metadata":{},"cell_type":"markdown","source":"ขั้นตอนต่อไปเป็นการอ่าน dataset โดยเริ่มจากไฟล์ label_descriptions.json ซึ่งบอกข้อมูลของ label โดยเราจะเก็บชื่อของ category ไว้ใช้อ้างอิงและแสดงผล"},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(DATA_DIR/\"label_descriptions.json\") as f:\n    label_descriptions = json.load(f)\n\nlabel_names = [x['name'] for x in label_descriptions['categories']]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ไฟล์ข้อมูลหลักก็คือ train.csv ซึ่งแต่ละแถวคือแต่ละ mask และมีคอลัมน์ต่างๆ ดังนี้\n- __ImageId__ บอกชื่อไฟล์รูปภาพ\n- __EncodedPixels__ บอกว่า mask นี้ ประกอบด้วย pixel ใดบ้าง โดยเก็บข้อมูลในรูปแบบ run-length encoding\n- __Height__ บอกความสูงของรูปภาพ\n- __Width__ บอกความกว้างของรูปภาพ\n- __ClassId__ บอกประเภทเครื่องแต่งกายของ mask นี้\n\nจะขออธิบาย __EncodedPixels__ และ __ClassId__ เพิ่มเติมอีกหน่อยนะครับ\n- __EncodedPixels__ เป็น run-length encoding คือจากภาพสองมิติ จะคลี่ออกมาเป็นมิติเดียว โดยไล่จากบนลงล่างก่อน แล้วถ้า mask ประกอบด้วย pixel ตำแหน่งที่ 10 ถึง 14 (คือจากตำแหน่งที่ 10 มี pixel ที่เป็น mask อยู่ติดกัน 4) และ 23 ถึง 29 (คือจากตำแหน่งที่ 23 มี pixel ที่เป็น mask อยู่ติดกัน 7) ก็จะเก็บค่าเป็น 10 4 23 7\n- __ClassId__ ตัวเลขด้านหน้าสุดคือหมายเลข category จากนั้นจะคั่นด้วยเครื่องหมาย '\\_' แล้วตัวเลขถัดๆ ไป จะเป็นหมายเลข attribute โดยในที่นี้จะทำเฉพาะ category อย่างเดียวครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"segment_df = pd.read_csv(DATA_DIR/\"train.csv\")\nsegment_df['CategoryId'] = segment_df['ClassId'].str.split('_').str[0]\n\nprint(\"Total segments: \", len(segment_df))\nsegment_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ขั้นตอนถัดมาจะเป็นการรวมแถวที่เป็นรูปภาพเดียวกันเข้าด้วยกัน"},{"metadata":{"trusted":true},"cell_type":"code","source":"image_df = segment_df.groupby('ImageId')['EncodedPixels', 'CategoryId'].agg(lambda x: list(x))\nsize_df = segment_df.groupby('ImageId')['Height', 'Width'].mean()\nimage_df = image_df.join(size_df, on='ImageId')\n\nprint(\"Total images: \", len(image_df))\nimage_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"อันนี้เป็นฟังก์ชันสำหรับปรับขนาดภาพ"},{"metadata":{"trusted":true},"cell_type":"code","source":"def resize_image(image_path):\n    img = cv2.imread(image_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_AREA)  \n    return img","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ส่วนสำคัญในการเรียกใช้โมเดล Mask R-CNN ของ Matterport คือการสร้างคลาสที่เป็น dataset ขึ้นมา โดยขยายจากคลาส utils.Dataset และต้องใส่ implementation ในฟังก์ชันต่างๆ ดังนี้\n* __\\_\\_init\\_\\___ จะเก็บข้อมูลของ category และรูปภาพทั้งหมดไว้ โดยใช้ฟังก์ชัน add_class และ add_image ตามลำดับ\n* __image_reference__ สำหรับดึง reference information ที่จะนำไปแสดงผล\n* __load_image__ สำหรับส่งรูปภาพเพื่อนำไปเป็น input ให้กับโมเดล\n* __load_mask__ สำหรับส่ง mask และ label เพื่อนำไปเป็น target ให้กับโมเดล ซึ่งในที่นี้จะต้องแปลงรูปแบบของ mask เดิม ที่เป็น run-length encoding ให้เป็น array ขนาด (512, 512, M) เมื่อ M เป็นจำนวน mask ทั้งหมดที่มีอยู่ในรูปภาพ ส่วน label ก็คือ array ขนาด (M,) ซึ่งเป็น category ของ mask ต่างๆ ในรูปภาพ"},{"metadata":{"trusted":true},"cell_type":"code","source":"class FashionDataset(utils.Dataset):\n\n    def __init__(self, df):\n        super().__init__(self)\n        \n        # Add classes\n        for i, name in enumerate(label_names):\n            self.add_class(\"fashion\", i+1, name)\n        \n        # Add images \n        for i, row in df.iterrows():\n            self.add_image(\"fashion\", \n                           image_id=row.name, \n                           path=str(DATA_DIR/'train'/row.name), \n                           labels=row['CategoryId'],\n                           annotations=row['EncodedPixels'], \n                           height=row['Height'], width=row['Width'])\n\n    def image_reference(self, image_id):\n        info = self.image_info[image_id]\n        return info['path'], [label_names[int(x)] for x in info['labels']]\n    \n    def load_image(self, image_id):\n        return resize_image(self.image_info[image_id]['path'])\n\n    def load_mask(self, image_id):\n        info = self.image_info[image_id]\n                \n        mask = np.zeros((IMAGE_SIZE, IMAGE_SIZE, len(info['annotations'])), dtype=np.uint8)\n        labels = []\n        \n        for m, (annotation, label) in enumerate(zip(info['annotations'], info['labels'])):\n            sub_mask = np.full(info['height']*info['width'], 0, dtype=np.uint8)\n            annotation = [int(x) for x in annotation.split(' ')]\n            \n            for i, start_pixel in enumerate(annotation[::2]):\n                sub_mask[start_pixel: start_pixel+annotation[2*i+1]] = 1\n\n            sub_mask = sub_mask.reshape((info['height'], info['width']), order='F')\n            sub_mask = cv2.resize(sub_mask, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_NEAREST)\n            \n            mask[:, :, m] = sub_mask\n            labels.append(int(label)+1)\n            \n        return mask, np.array(labels)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"เมื่อสร้างคลาส dataset ของข้อมูลชุดนี้ ที่ชื่อว่า FashionDataset เสร็จแล้ว ก็จะสามารถลองทำ visualization ดูได้ ว่ามีรูปภาพและ mask เป็นอย่างไรบ้าง"},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = FashionDataset(image_df)\ndataset.prepare()\n\nfor i in range(6):\n    image_id = random.choice(dataset.image_ids)\n    print(dataset.image_reference(image_id))\n    \n    image = dataset.load_image(image_id)\n    mask, class_ids = dataset.load_mask(image_id)\n    visualize.display_top_masks(image, mask, class_ids, dataset.class_names, limit=4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ก่อนที่จะเทรน ก็จะทำการแบ่งข้อมูลเป็น training set กับ validation set"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df, valid_df = train_test_split(image_df, test_size=0.2, random_state=42)\n\ntrain_dataset = FashionDataset(train_df)\ntrain_dataset.prepare()\n\nvalid_dataset = FashionDataset(valid_df)\nvalid_dataset.prepare()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"เมื่อสร้าง training set และ validation set เสร็จแล้ว ก็จะลองทำ visualization ดูว่า category ต่างๆ มีการกระจายเป็นยังไงบ้างครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_segments = np.concatenate(train_df['CategoryId'].values).astype(int)\nprint(\"Total train images: \", len(train_df))\nprint(\"Total train segments: \", len(train_segments))\n\nplt.figure(figsize=(12, 3))\nvalues, counts = np.unique(train_segments, return_counts=True)\nplt.bar(values, counts)\nplt.xticks(values, label_names, rotation='vertical')\nplt.show()\n\nvalid_segments = np.concatenate(valid_df['CategoryId'].values).astype(int)\nprint(\"Total validation images: \", len(valid_df))\nprint(\"Total validation segments: \", len(valid_segments))\n\nplt.figure(figsize=(12, 3))\nvalues, counts = np.unique(valid_segments, return_counts=True)\nplt.bar(values, counts)\nplt.xticks(values, label_names, rotation='vertical')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2.5 Train"},{"metadata":{},"cell_type":"markdown","source":"ในขั้นแรกของกระบวนการเทรน ก็จะสร้างโมเดล Mask R-CNN จาก config ที่กำหนดไว้ และโหลด pretrained weight มาใส่ครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = modellib.MaskRCNN(mode='training', config=config, model_dir=ROOT_DIR)\n\nmodel.load_weights(COCO_WEIGHTS_PATH, by_name=True, exclude=[\n    'mrcnn_class_logits', 'mrcnn_bbox_fc', 'mrcnn_bbox', 'mrcnn_mask'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"library นี้ รองรับการทำ augmentation จาก [imgaug](https://github.com/aleju/imgaug) ด้วย ในที่นี้ได้เพิ่ม augmentation ที่เป็น horizontal flip เข้าไป"},{"metadata":{"trusted":true},"cell_type":"code","source":"augmentation = iaa.Sequential([\n    iaa.Fliplr(0.5)\n])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ในขั้นแรก จะทำการเทรนเฉพาะส่วนหัวของโมเดลก่อนครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nmodel.train(train_dataset, valid_dataset,\n            learning_rate=2e-3,\n            epochs=1,\n            layers='heads',\n            augmentation=None)\n\nhistory = model.keras_model.history.history","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"แล้วต่อมาจึงค่อยเทรนโมเดลทั้งหมด"},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nmodel.train(train_dataset, valid_dataset,\n            learning_rate=1e-3,\n            epochs=3,\n            layers='all',\n            augmentation=augmentation)\n\nnew_history = model.keras_model.history.history\nfor k in new_history: history[k] = history[k] + new_history[k]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ในที่นี้ได้เทรนไปทั้งสิ้นจำนวน 3 รอบ (epoch) เมื่อเทรนเสร็จแล้วก็จะพล็อตกราฟของ loss โดยรวม และ loss ของหัว class กับหัว mask ออกมาดู พร้อมกับเลือกโมเดลในรอบที่ดีที่สุดเพื่อนำไปใช้กับ test data ต่อไปครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"epochs = range(3)\n\nplt.figure(figsize=(18, 6))\n\nplt.subplot(131)\nplt.plot(epochs, history['loss'], label=\"train loss\")\nplt.plot(epochs, history['val_loss'], label=\"valid loss\")\nplt.legend()\nplt.subplot(132)\nplt.plot(epochs, history['mrcnn_class_loss'], label=\"train class loss\")\nplt.plot(epochs, history['val_mrcnn_class_loss'], label=\"valid class loss\")\nplt.legend()\nplt.subplot(133)\nplt.plot(epochs, history['mrcnn_mask_loss'], label=\"train mask loss\")\nplt.plot(epochs, history['val_mrcnn_mask_loss'], label=\"valid mask loss\")\nplt.legend()\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"best_epoch = np.argmin(history[\"val_loss\"]) + 1\nprint(\"Best epoch: \", best_epoch)\nprint(\"Valid loss: \", history[\"val_loss\"][best_epoch-1])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2.6 Predict"},{"metadata":{},"cell_type":"markdown","source":"ขั้นตอนสุดท้ายก็จะเป็นการนำโมเดลที่เทรนไว้มาใช้ทำนาย test data โดยเริ่มจากการเซ็ตค่า config แล้วโหลดโมเดลที่เทรนไว้ขึ้นมา"},{"metadata":{"trusted":true},"cell_type":"code","source":"class InferenceConfig(FashionConfig):\n    GPU_COUNT = 1\n    IMAGES_PER_GPU = 1\n\ninference_config = InferenceConfig()\n\nglob_list = glob.glob(f'/kaggle/working/fashion*/mask_rcnn_fashion_{best_epoch:04d}.h5')\nmodel_path = glob_list[0] if glob_list else ''\n\nmodel = modellib.MaskRCNN(mode='inference', \n                          config=inference_config,\n                          model_dir=ROOT_DIR)\n\nassert model_path != '', \"Provide path to trained weights\"\nprint(\"Loading weights from \", model_path)\nmodel.load_weights(model_path, by_name=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"แล้วอ่านไฟล์ sample_submission.csv ซึ่งมีชื่อรูปภาพที่จะต้องทำการทดสอบ"},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df = pd.read_csv(DATA_DIR/\"sample_submission.csv\")\nsample_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ฟังก์ชัน to_rle เป็นฟังก์ชันที่แปลง mask จากโมเดลเป็น run-length encoding ซึ่งเป็น format ที่ใช้ในการส่งผลลัพธ์ครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"def to_rle(bits):\n    rle = []\n    pos = 1\n    for bit, group in itertools.groupby(bits):\n        group_list = list(group)\n        if bit:\n            rle.extend([pos, len(group_list)])\n        pos += len(group_list)\n    return rle","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"นอกจากนี้ ระบบตรวจผลลัพธ์ยังไม่อนุญาตให้ pixel นึง อยู่ใน mask หลายอันที่เป็นคลาสเดียวกัน จึงต้องมีฟังก์ชัน trim_masks เพื่อใช้แก้กรณีนี้"},{"metadata":{"trusted":true},"cell_type":"code","source":"def trim_masks(masks, rois, class_ids):\n    class_pos = np.argsort(class_ids)\n    class_rle = to_rle(np.sort(class_ids))\n    \n    pos = 0\n    for i, _ in enumerate(class_rle[::2]):\n        previous_pos = pos\n        pos += class_rle[2*i+1]\n        if pos-previous_pos == 1:\n            continue \n        mask_indices = class_pos[previous_pos:pos]\n        \n        union_mask = np.zeros(masks.shape[:-1], dtype=bool)\n        for m in mask_indices:\n            masks[:, :, m] = np.logical_and(masks[:, :, m], np.logical_not(union_mask))\n            union_mask = np.logical_or(masks[:, :, m], union_mask)\n        for m in mask_indices:\n            mask_pos = np.where(masks[:, :, m]==True)\n            if np.any(mask_pos):\n                y1, x1 = np.min(mask_pos, axis=1)\n                y2, x2 = np.max(mask_pos, axis=1)\n                rois[m, :] = [y1, x1, y2, x2]\n            \n    return masks, rois","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ในส่วนนี้ก็จะเป็นการรันเพื่อทำนายผลลัพธ์ครับ"},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nsub_list = []\nmissing_count = 0\nfor i, row in tqdm(sample_df.iterrows(), total=len(sample_df)):\n    image = resize_image(str(DATA_DIR/'test'/row['ImageId']))\n    result = model.detect([image])[0]\n    if result['masks'].size > 0:\n        masks, _ = trim_masks(result['masks'], result['rois'], result['class_ids'])\n        for m in range(masks.shape[-1]):\n            mask = masks[:, :, m].ravel(order='F')\n            rle = to_rle(mask)\n            label = result['class_ids'][m] - 1\n            sub_list.append([row['ImageId'], ' '.join(list(map(str, rle))), label])\n    else:\n        # The system does not allow missing ids, this is an easy way to fill them\n        sub_list.append([row['ImageId'], '1 1', 23])\n        missing_count += 1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"เมื่อได้ผลลัพธ์ออกมาแล้ว ก็จะนำมาสร้างเป็นไฟล์สำหรับส่งตรวจ"},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df = pd.DataFrame(sub_list, columns=sample_df.columns.values)\nprint(\"Total image results: \", submission_df['ImageId'].nunique())\nprint(\"Missing images: \", missing_count)\nsubmission_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"สุดท้ายนี้ ลองมาดูผลลัพธ์ที่ได้กันซักหน่อยครับ จะเห็นว่าไม่เลวเลยทีเดียว แม้จะยังมีที่ผิดอยู่ก็ตาม"},{"metadata":{"trusted":true},"cell_type":"code","source":"for i in range(9):\n    image_id = sample_df.sample()['ImageId'].values[0]\n    image_path = str(DATA_DIR/'test'/image_id)\n    \n    img = cv2.imread(image_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    result = model.detect([resize_image(image_path)])\n    r = result[0]\n    \n    if r['masks'].size > 0:\n        masks = np.zeros((img.shape[0], img.shape[1], r['masks'].shape[-1]), dtype=np.uint8)\n        for m in range(r['masks'].shape[-1]):\n            masks[:, :, m] = cv2.resize(r['masks'][:, :, m].astype('uint8'), \n                                        (img.shape[1], img.shape[0]), interpolation=cv2.INTER_NEAREST)\n        \n        y_scale = img.shape[0]/IMAGE_SIZE\n        x_scale = img.shape[1]/IMAGE_SIZE\n        rois = (r['rois'] * [y_scale, x_scale, y_scale, x_scale]).astype(int)\n        \n        masks, rois = trim_masks(masks, rois, r['class_ids'])\n    else:\n        masks, rois = r['masks'], r['rois']\n        \n    visualize.display_instances(img, rois, masks, r['class_ids'], \n                                ['bg']+label_names, r['scores'],\n                                title=image_id, figsize=(12, 12))","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"}},"nbformat":4,"nbformat_minor":1}