>> ต่อจาก
บทที่ ๑๐ สำหรับงานทางด้านการจัดการรูปภาพเราจำเป็นต้องอ่านข้อมูลรูปภาพขึ้นมาแล้วแปลงเป็นเทนเซอร์เพื่อนำมาใช้เป็นข้อมูลป้อนให้โครงข่ายประสาทเทียม
pytorch ได้เตรียมคำสั่งสำหรับอ่านและจัดการกับข้อมูลรูปภาพเอาไว้อย่างสะดวกดี อยู่ภายในมอดูล torchvision
นอกจากนี้ยังได้มีข้อมูลตัวอย่างที่นิยมใช้ เช่น MNIST หรือ CIFAR เตรียมไว้ด้วย
มอดูลต่างๆที่ต้องใช้ เพื่อที่จะใช้งานรูปภาพ นอกจากเรียกใช้ torch ตามปกติแล้วยังต้องใช้อีกหลายอย่าง ตามนี้
import torch
from torch.utils.data import DataLoader as Dalo
import torchvision.datasets as ds
import torchvision.transforms as tf
DataLoader นั้นเหมือนกับในบทที่แล้ว ต้องใช้เพื่อสร้างเจเนอเรเตอร์สำหรับมินิแบตช์ดึงข้อมูลออกมาทีละกลุ่ม
torchvision.datasets คือมอดูลที่มีคำสั่งสำหรับโหลดไฟล์รูปภาพที่เตรียมไว้ หรือโหลดข้อมูลตัวอย่างอย่าง MNIST, cifar, ฯลฯ
torchvision.transforms คือมอดูลที่มีคำสั่งต่างๆสำหรับแปลงไฟล์รูปภาพให้เป็นรูปแบบต่างๆที่เหมาะแก่การใช้
ต่อจากนี้จะย่อเหลือเป็น Dalo, ds, tf แบบนี้เพื่อให้เรียกได้สั้นๆ
การอ่านรูปภาพที่เตรียมไว้ ก่อนอื่นหากต้องการใช้ไฟล์รูปภาพที่ตัวเองเตรียมไว้เอง ให้เตรียมไฟล์ภาพโดยแยกข้อมูลแต่ละประเภทไว้คนละโฟลเดอร์
เพื่อเป็นตัวอย่าง ลองโหลดข้อมูลชุดนี้ไปใช้ได้
https://phyblas.hinaboshi.com/triamhai/ruprang-raisi-25x25x1000x5.rar ข้อมูลนี้สร้างขึ้นด้วยฟังก์ชันที่เขียนถึงไปในนี้
https://phyblas.hinaboshi.com/20180811 หากสนใจสามารถลองสร้างขึ้นมาใช้เองได้
หากโหลดไปแล้วแตกไฟล์จะเห็นว่าภายในโฟลเดอร์ประกอบไปด้วย ๕ โฟลเดอร์แยกกัน
ให้ใช้ ds.ImageFolder(root=ที่อยู่ไฟล์,transform=ตัวเลือกแปลงไฟล์)
ที่อยู่ไฟล์ในที่นี้หมายถึงโฟลเดอร์ใหญ่ที่เก็บโฟลเดอร์ย่อยที่บรรจุไฟล์รูปแต่ละกลุ่มไว้
ตัวเลือกแปลงไฟล์คือออบเจ็กต์ในมอดูล torchvision.transforms (tf) ซึ่งกำหนดตัวเลือกว่าจะต้องการอ่านไฟล์ในลักษณะไหน
โดยทั่วไปเพื่อจะใช้งานในโครงข่ายประสาทจะต้องการข้อมูลในรูปเทนเซอร์ ดังนั้นจึงต้องใช้ตัวเลือก tf.ToTensor() เสมอ
การโหลดข้อมูลรูปภาพอาจเขียนได้ดังนี้
folder = 'ruprang-raisi-25x25x1000x5'
data = ds.ImageFolder(root=folder,transform=tf.ToTensor())
minibatch = Dalo(data,batch_size=16,shuffle=1) # สุ่มข้อมูลออกมาทีละ 16 ตัว
for Xb,zb in minibatch: break
print(Xb.shape,zb) # ได้ torch.Size([16, 3, 25, 25]) tensor([2, 3, 0, 3, 4, 1, 4, 4, 1, 4, 4, 1, 2, 2, 0, 1])
ตรง for Xb,zb in minibatch: break นี้มีไว้เพื่อให้ทำการอ่านข้อมูลออกมาเอาชุดแรกมาดูเพื่อเป็นตัวอย่างให้ดูเฉยๆ เพราะปกติ DataLoader จะทำงานเมื่อเจอ for เท่านั้น
ในที่นี้ข้อมูลตัวแรกที่ได้คือ Xb ก็คือข้อมูลรูปภาพเป็นเทนเซอร์ขนาด 16,3,25,25 มา คือ (จำนวนข้อมูล,จำนวนสี,ความสูง,ความกว้าง)
ส่วนตัวหลังคือ zb คือคำตอบ จะเป็นเลข 0,1,2,... ต่างกันตามโฟลเดอร์ของรูป
จำนวนข้อมูลแต่ชะชุดจะเท่ากับจำนวน batch_size ที่ตั้งไว้
สี ในที่นี้ข้อมูลถูกอ่านออกมาเป็น ๓ สี แม้ว่าจะเป็นภาพขาวดำก็ตาม ดังนั้นต้องทำการยุบให้เหลือสีเดียวโดยใช้เมธอด .mean() เพียงแต่ว่าเวลาที่จะป้อนเข้าโครงข่ายประสาทคอนโวลูชันยังจะต้องเหลือมิติไว้ ดังนั้นให้ใส่คีย์เวิร์ด keepdim=True เพื่อรักษามิตินั้นไว้ด้วย
print(Xb.mean(1).shape) # ได้ torch.Size([16, 25, 25])
print(Xb.mean(1,keepdim=True).shape) # ได้ torch.Size([16, 1, 25, 25])
นอกจากนี้ยังมีอีกวิธีหนึ่งคือใช้ตัวเลือกปรับแต่งรูป tf.Grayscale() จะกล่าวถึงตอนหลัง
ลองสั่งวาดภาพตัวอย่างดูได้
import numpy as np
import matplotlib.pyplot as plt
plt.axes([0,0,1,1]).imshow(np.hstack(Xb.mean(1)),cmap='gray')
plt.axis('off')
plt.show()
ส่วน zb ก็คือเลขแสดงประเภทของข้อมูล ในที่นี้มี ๕ โฟลเดอร์ เลขกลุ่มจึงอยู่ในช่วง 0 ถึง 4
การใช้ข้อมูล MNIST MNIST คือชุดข้อมูลตัวเลขที่เขียนด้วยลายมือ
ใน sklearn ก็มีคำสั่งโหลดข้อมูลนี้เช่นกัน รายละเอียดอ่านได้ใน
https://phyblas.hinaboshi.com/20170920 สำหรับใน pytorch ข้อมูล MNIST สามารถดึงมาใช้ได้โดยใช้ df.MNIST()
ตัวเลือกมีดังนี้
root = ที่อยู่ที่จะเก็บไฟล์
train = จะเอาชุดข้อมูลฝึกหรือข้อมูลทดสอบ
transform = ตัวเลือกแปลงไฟล์
download = ถ้ายังไม่มีข้อมูลอยู่จะโหลดใหม่หรือไม่
โดยค่าตั้งต้นแล้ว download อยู่ที่ False ดังนั้นถ้าเราจะใช้คำสั่งนี้ครั้งแรกยังไม่ได้โหลดข้อมูลเตรียมไว้ต้องตั้ง download=True ไว้ด้วย
ไฟล์จะถูกโหลดไปอยู่ที่โฟลเดอร์ที่เรากำหนดใน root
ข้อมูลมี ๒ ชุดคือชุดฝึก (train) กับชุดทดสอบ (test) ถ้าให้ train=1 จะได้ชุดข้อมูลฝึก ถ้าให้ train=0 จะได้ชุดข้อมูลทดสอบ
ลองดึงข้อมูลฝึกออกมาแสดง
folder_mnist = '~/pytorchdata/mnist'
data = ds.MNIST(folder_mnist,transform=tf.ToTensor(),download=1,train=1)
minibatch = Dalo(data,batch_size=16,shuffle=1)
for Xb,zb in minibatch: break
print(Xb.shape,zb) # ได้ torch.Size([16, 1, 28, 28]) tensor([2, 1, 4, 4, 1, 0, 3, 0, 4, 4, 2, 1, 8, 5, 5, 2])
plt.axes([0,0,1,1]).imshow(np.hstack(Xb[:,0]),cmap='gray')
plt.axis('off')
plt.show()
นอกจากนี้ยังมีชุดข้อมูล FashionMNIST ซึ่งเป็นข้อมูลภาพเสื้อผ่านรูปแบบต่างๆ ๑๐ แบบ เทียบกับข้อมูลตัวเลขแล้วยากขึ้นมาหน่อย
ลองโหลดมาใช้ดูได้เช่นกัน
folder_fashionmnist = '~/pytorchdata/fashionmnist'
data = ds.FashionMNIST(folder_fashionmnist,transform=tf.ToTensor(),download=1,train=1)
การใช้ข้อมูล CIFAR-10 CIFAR-10 คือข้อมูลรูปภาพ 10 ชนิด ประกอบไปด้วย เครื่องบิน, รถยนต์, นก, แมว, กวาง, หมา, กบ, ม้า, เรือ, รถบรรทุก
สำหรับใน pytorch ข้อมูล CIFAR-10 สามารถดึงมาใช้ได้โดยใช้ df.CIFAR10() ตัวเลือกต่างๆเหมือน MNIST
นอกจากนี้ยังมี CIFAR-100 ซึ่งคล้ายกันแต่มีข้อมูลถึง 100 กลุ่ม เรียกโดยใช้ df.CIFAR100()
ลองโหลดมาแสดงดู
folder_cifar = '~/pytorchdata/cifar'
data = ds.CIFAR10(folder_cifar,transform=tf.ToTensor(),download=1,train=1)
minibatch = Dalo(data,batch_size=16,shuffle=1)
for Xb,zb in minibatch: break
plt.axes([0,0,1,1]).imshow(np.hstack(Xb.numpy().transpose(0,2,3,1)))
plt.axis('off')
plt.show()
เนื่องจากไฟล์ที่โหลดมาปกติจะเอามิติของสีขึ้นก่อน แต่สำหรับ matplotlib เวลาจะแสดงรูปต้องการอาเรย์ที่มีมิติของสีอยู่ท้ายสุด ดังนั้นจึงต้องสลับแกนด้วย .transpose() ก่อนจึงจะแสดงผลได้
ตัวเลือกปรับแต่งภาพ บางทีภาพที่เตรียมไว้อาจไม่ได้อยู่ในรูปแบบที่ต้องการนำมาใช้ทันที ใน torchvision.transform ได้เตรียมตัวแปลงต่างๆไว้มากมายใช้ได้สะดวก
เวลาที่ต้องการจะใส่ตัวเลือกปรับแต่งมากกว่าหนึ่งตัวให้สร้างออบเจ็กต์ tf.Compose() เก็บตัวเลือกปรับแต่งทั้งหมดไว้แล้วนำตัวนี้มาใช้
ตัวอย่างเช่น หากต้องการแปลงภาพให้เป็นขาวดำ ใช้ tf.Grayscale()
folder_cifar = '~/pytorchdata/cifar'
tran = tf.Compose([
tf.Grayscale(),
tf.ToTensor()])
data = ds.CIFAR10(folder_cifar,transform=tran)
minibatch = Dalo(data,batch_size=16)
for Xb,zb in minibatch: break
plt.axes([0,0,1,1]).imshow(np.hstack(Xb[:,0]),cmap='gray')
plt.axis('off')
plt.show()
tf.Resize ใช้เปลี่ยนขนาดภาพให้เป็นขนาดที่ต้องการ ขนาดรูปกำหนดเป็น (สูง,กว้าง) หรือใช้เลขตัวเดียวจะเป็นจตุรัส
tran = tf.Compose([
tf.Resize((40,30)),
tf.ToTensor()])
data = ds.CIFAR10(folder_cifar,transform=tran,download=1,train=1)
minibatch = Dalo(data,batch_size=16,shuffle=1)
for Xb,zb in minibatch: break
plt.axes([0,0,1,1]).imshow(np.hstack(Xb.numpy().transpose(0,2,3,1)))
plt.axis('off')
plt.show()
tf.Pad ใช้เติมขอบ
tran = tf.Compose([
tf.Pad(10,fill=(100,150,20),padding_mode='constant'),
tf.ToTensor()])
tf.CenterCrop จะตัดรูปเอาส่วนตรงกลางตามขนาดที่กำหนด
tran = tf.Compose([
tf.CenterCrop((20,30)),
tf.ToTensor()])
tf.Normalize ปรับค่าของเทนเซอร์โดยลบค่าเฉลี่ยและหารค่าส่วนเบี่ยงเบนมาตรฐานตามที่กำหนด
input[channel] = (input[channel] - mean[channel]) / std[channel]
เช่น รูปภาพโดยทั่วไปจะมีค่าความเข้มของแต่ละสีอยู่ระหว่าง 0 ถึง 1 แต่ถ้าหากต้องการแปลงให้อยู่ระหว่าง -1 ถึง 1 ก็อาจใส่แบบนี้
tran = tf.Compose([
tf.ToTensor(),
tf.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
tf.Lambda แปลงโดยใช้ฟังก์ชันอะไรบางอย่างที่กำหนดเอง
เช่น ลองสุ่มสร้างคลื่นรบกวนให้ภาพ
tran = tf.Compose([
tf.ToTensor(),
tf.Lambda(lambda x: x*(0.4+0.6*torch.rand(x.shape)))])
ตัวแปลงที่ใช้จะทำตามลำดับที่ใส่ไป ดังนั้นหากตัวแปลงไหนที่ใช้จัดการกับเทนเซอร์ เช่น tf.Normalize ต้องไว้หลัง tf.ToTensor() ถ้าเป็นตัวแปลงสำหรับจัดการรูปภาพก่อนแปลงให้ไว้ก่อน
ตัวเลือกแปลงภาพแบบสุ่ม มีตัวเลือกแปลงกลุ่มหนึ่งที่จะทำการเปลี่ยนแปลงภาพแบบสุ่มไม่แน่นอน การแปลงแบบนี้มีประโยชน์มากในการเรียนรู้ เพราะสามารถสร้างความหลากหลายให้ข้อมูลได้แม้จะใช้ภาพเดิม ในการวนซ้ำแต่ละครั้งจะสุ่มแปลงต่างกันไป
tf.RandomRotation หมุนภาพแบบสุ่ม
tran = tf.Compose([
tf.RandomRotation((-45,45)),
tf.ToTensor(),
])
data = ds.CIFAR10(folder_cifar,transform=tran)
minibatch = Dalo(data,batch_size=16)
rup = []
for i in range(4):
for Xb,zb in minibatch: break
rup.append(np.hstack(Xb.numpy().transpose(0,2,3,1)))
rup = np.vstack(rup)
plt.axes([0,0,1,1]).imshow(rup)
plt.axis('off')
plt.show()
tf.RandomResizedCrop ตัดเอาส่วนของภาพแบบสุ่มมาขยายเป็นขนาดที่กำหนด
tran = tf.Compose([
tf.RandomResizedCrop(size=48,scale=(0.25,1),ratio=(0.5,2)),
tf.ToTensor(),
])
tf.RandomVerticalFlip และ tf.RandomHorizontalFlip พลิกบนล่าง และซ้ายขวา ด้วยความน่าจะเป็นตามที่กำหนด
tran = tf.Compose([
tf.RandomVerticalFlip(0.25),
tf.RandomHorizontalFlip(0.5),
tf.ToTensor(),
])
tf.RandomGrayscale สุ่มเปลี่ยนเป็นขาวดำ
tran = tf.Compose([
tf.RandomGrayscale(0.25),
tf.ToTensor(),
])
tf.ColorJitter ปรับ brightness, contrast, saturation, hue แบบสุ่ม
tran = tf.Compose([
tf.ColorJitter(brightness=1,contrast=1,saturation=1,hue=0.5),
tf.ToTensor(),
])
tf.RandomApply สุ่มทำคำสั่งแปลงบางอย่างด้วยความน่าจะเป็นตามที่กำหนด
tran = tf.Compose([
tf.RandomApply([tf.Resize((24,24)),tf.Pad(4)],p=0.5),
tf.RandomApply([tf.Resize((48,48)),tf.CenterCrop((32,32))],p=0.5),
tf.ToTensor(),
])
ใช้วิธีต่างๆนี้ทำให้สามารถดึงภาพมาใช้และปรับแต่งสร้างความหลากหลายได้สะดวก
ในบทต่อไปจะนำรูปมาใช้ในการเรียนรู้ของโครงข่ายประสาทแบบคอนโวลูชัน