การจำแนกสายพันธุ์มะม่วง โดยใช้ Visual Geometry Group 16 (VGG16) ใน Python
Tags: ai, vgg16, python, deeplearning
Published: 2024-04-16 09:09:07

ถ้าเราต้องการจำแนกประเภทของรูปภาพหรือประเภทของสิ่งของ เช่น ยี่ห้อรถยนต์ สายพันธุ์มะม่วง หรือสายพันธุ์สัตว์ หนึ่งในวิธีที่ง่ายและเหมาะสมที่ใช้สำหรับการทำนายข้อมูลแบบนี้คือ การใช้ VGG16 เป็นอีกหนึ่งวิธีที่เราสามารถเลือกใช้ได้ ซึ่ง Visual Geometry Group 16  หรือ VGG16 เป็นโมเดลเครือข่ายประสาทเทียม โดยจุดเด่นของ VGG16 คือ โครงสร้างของโมเดลที่เรียบง่าย ประกอบไปด้วย Convolutional 3x3 ซ้อนกัน 16 ชั้น โดยใช้ stride 1 และ padding แบบ same 

โดย **Convolutional 3x3** หมายถึง การใช้ตัวกรอง (Filter) ขนาด 3x3 ทำการแปลงข้อมูลภาพ (Input) ให้เป็นข้อมูลภาพ (Output) 
**การซ้อนกัน 16 ชั้น** หมายถึง การใช้ Convolutional 3x3 ซ้ำกัน 16 ครั้ง
**Stride 1** หมายถึง การเลื่อนตัวกรอง (Filter) ทีละ 1 พิกเซล
**Padding แบบ Same**หมายถึง การเติมขอบของข้อมูลภาพ (Input) ด้วยค่า 0 ก่อนการ Convolutional

บทความนี้ เราจะมาดู Visual Geometry Group 16 หรือ VGG16 ใน Python กัน เราจะใช้ Google Colab ในการรันโค้ด โดย dataset ที่เราใช้เป็นตัวอย่างคือ Mango Variety and Grading Dataset ซึ่งเป็นข้อมูลสายพันธุ์มะม่วงได้มาจาก Mango Varieties Classification and Grading

**ขั้นตอนที่ 1 นำเข้า Libraries ที่ต้องใช้ทั้งหมดลงใน Google Colab** 
ตัวอย่างของ Libraries 

```
import numpy as np
import cv2
import os
from tqdm import tqdm
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPool2D
from keras.applications.vgg16 import VGG16
import tensorflow as tf
from os import listdir
from os.path import isfile, join
```

**ขั้นตอนที่ 2 โหลดข้อมูลชุดรูปภาพลงใน Google Colab**
โดยขั้นตอนนี้เราจะทำการอัพโหลด dataset เข้า Google Drive และทำการเชื่อม Google Colab เข้ากับโฟลเดอร์ที่เก็บ dataset ใน Google Drive  

```
from google.colab import drive
drive.mount('/content/drive')
```
ตัวอย่างผลที่ได้จาก Code 

```
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
```
เราก็จะสามารถเชื่อม Google Colab กับ Google Drive ได้ หลังจากนั้นเราก็ทำการ ลิ้งค์ไปยังโฟลเดอร์Project ของเราด้วยคำสั่ง

```
FOLDERNAME = 'VGG16/MangoVGG16'
%cd /content/drive/My\ Drive/VGG16
```

**ขั้นตอนที่ 3 กำหนดขนาดของรูปภาพ**
โดยในขั้นตอนนี้เราจะต้องทำการกำหนดขนาดของรูปภาพให้รูปภาพมีความกว้าง 128 พิกเซล และกำหนดคลาสของมะม่วงแต่ละชนิดโดยในที่นี้เราจะทำการกำหนดไว้ 3 คลาส เพราะต้องจำแนกสายพันธุ์มะม่วง จำนวน 3 สายพันธุ์ 

```
width = 128
num_classes = 3
```
**ขั้นตอนที่ 4 ทำการอ่านไฟล์รูปภาพในแต่ละโฟลเดอร์**
โดยในขั้นตอนนี้เราจะทำการอ่านไฟล์รูปภาพที่ใช้ในการเทรนโมเดล และรูปภาพในการเทสโมเดล

```
trainpath = 'train/'
testpath = 'test/'

trainImg = [trainpath+f for f in listdir(trainpath) if not f.startswith('.')]
testImg = [testpath + f for f in listdir(testpath) if not f.startswith('.')]
```
**ขั้นตอนที่ 5 สร้างฟังก์ชันในการอ่านไฟล์รูปภาพ**
ในขั้นตอนนี้จะทำการอ่านไฟล์รูปภาพและสีแต่ละพิกเซลออกมา 

•โดยเราจะสร้างฟังก์ชันที่ชื่อว่า **img2data** โดยเราจะสร้างตัวแปร **rawImgs**  เพื่อเก็บตัวแปรของแต่ละพิกเซลเอาไว้
•จะสร้าง labels เอาไว้เพื่อให้อ่าน path ว่าเป็นรูปของมะม่วงสายพันธุ์อะไร

ถ้าหากว่าอ่านออกมาแล้วเป็นมะม่วงสายพันธุ์ Fajri จะกำหนดให้ labels เป็น [1,0,0] มะม่วงสายพันธุ์ Dasheri กำหนดให้ labels เป็น [0,1,0] มะม่วงสายพันธุ์ Chaunsa กำหนดให้ labels เป็น [0,0,1] และจะใช้ฟังก์ชัน OpenCV ในการอ่านไฟล์รูปภาพออกมา และทำการปรับขนาดที่เราทำการกำหนดไว้ และให้ส่งค่ากลับไปด้วยสั่ง return ไปที่ rawImgs และ labels 

```
def img2data(path):
  rawImgs = []
  labels = []
  c = 0
  for imagePath in path:
    for item in tqdm(os.listdir(imagePath,)):
      file = os.path.join(imagePath, item)
      c += 1
      l = imagePath.split('/')[1]

      # Ensure file exists before loading
      if not os.path.exists(file):
        print(f"Error: File not found: {file}")
        continue  # Skip this image

      try:
        img = cv2.imread(file, cv2.COLOR_BGR2RGB)
        if img is None:
          print(f"Error: Failed to read image: {file}")
          continue  # Skip this image

        # Check for valid dimensions
        if img.shape[0] == 0 or img.shape[1] == 0:
          print(f"Error: Image has invalid dimensions: {file}")
          continue  # Skip this image

        img = cv2.resize(img, (width, width))
        rawImgs.append(img)

        if l == 'Fajri':
          labels.append([1, 0, 0])
        elif l == 'Dasheri':
          labels.append([0, 1, 0])
        elif l == 'Chaunsa':
          labels.append([0, 0, 1])
      except Exception as e:
        print(f"Error processing image: {file} - {e}")

  return rawImgs, labels
```
**ขั้นตอนที่ 6 ทำการอ่านไฟล์รูปภาพออกมา**
ขั้นตอนนี้เราจะทำตัวแปรมารับค่าของ **trainImg** และ **testImg** ขึ้นมา โดยจะเป็นค่าของ RGB สำหรับรูปภาพในการเทสและเทรนของโมเดลแต่ละโมเดล

```
x_train , y_train = img2data(trainImg)
x_test, y_test = img2data(testImg)
```
ผลลัพธ์ที่ได้จาก Code 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8johxh62q4yen93irm6a.png)

**ขั้นตอนที่ 7 ทำแปรข้อมูลรูปภาพให้เป็น Array**
ขั้นตอนนี้ต้องแปรรูปภาพเป็น Array เพื่อให้โมเดล Deep Leaning ทำงานโดยใช้หลักการคณิตศาสตร์และอัลกอริทึมที่ทำงานบนข้อมูลตัวเลข ซึ่งแต่ละพิกเซล จะมีค่าสีเป็น RGB ระหว่าง 0 ถึง 255 และเป็นการเพิ่มประสิทธิภาพการประมวลผล และการสร้างความสม่ำเสมอของข้อมูล

```
x_train = np.array(x_train)
y_train = np.array(y_train)
x_test = np.array(x_test)
y_test = np.array(y_test)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /=255
x_test /=255
```
**ขั้นตอนที่ 8 ดูผลลัพธ์ทั้งหมดของรูปภาพ**
ขั้นตอนนี้เราจะทำการดูผลลัพธ์ของรูปภาพที่แปลงเป็น Array 

```
x_train.shape,y_train.shape,x_test.shape,y_test.shape
```
ผลลัพธ์ที่ได้จาก Code 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtuz33n3njevdhq9k3y1.png)

560 คือรูปภาพทั้งหมดที่ใช้ในการ train และมีอัตราส่วนของรูปภาพอยู่ที่ 128 แถว และ 128 คอลัม และมิติของภาพ คือ 3 มิติ คือค่า RGB และผลเฉลยของภาพ มี 560 และมี 3 ค่า คือ มะม่วงสายพันธุ์ Fajri ที่กำหนดให้ labels เป็น [1,0,0] สายพันธุ์ Dasheri  ที่กำหนดให้ labels เป็น [0,1,0] มะม่วงสายพันธุ์ Chaunsa ที่กำหนดให้ labels เป็น [0,0,1] 

**ขั้นตอนที่ 9 สร้างโมเดล Convolutional Neural Network (CNN) สำหรับการจัดประเภทภาพ (Image Classification) โดยใช้ Transfer Learning จาก Pre-Trained Model VGG16**
จะทำการสร้างโมเดล CNN แบบสองชั้นขึ้นมา โดยใช้ VGG16 แล้วต่อด้วยโมเดล CNN ที่สร้างขึ้นใหม่ เพื่อปรับแต่งการเรียนรู้ของโมเดลให้มีประสิทธิภาพมากขึ้น (Fine – Tune) สำหรับการจัดประเภทภาพโดยเฉพาะ 

```
base_model = VGG16(input_shape=(128, 128, 3), include_top=False, weights='imagenet')
base_model.trainable = False  # ฟรีซเลเยอร์โมเดล VGG16
num_classes = 3

width = 128
model = Sequential([
    keras.layers.Conv2D(32,(3,3), activation='relu', input_shape = (width,width, 3)),
    keras.layers.MaxPooling2D(pool_size=(2,2)),
    keras.layers.Conv2D(64,(3,3), activation='relu'),
    keras.layers.MaxPooling2D(pool_size=(2,2)),
    keras.layers.Dense(128),
    keras.layers.Flatten(),
    keras.layers.Dense(num_classes, activation='softmax')
])
```
**ขั้นตอนที่ 10 ดูรูปร่างของข้อมูลที่ได้นำมาทดสอบ**
ทำการแสดงผลลัพธ์เพื่อวิเคราะห์โครงสร้างและประสิทธิภาพของโมเดล Deep Learning 

```
model.summary()
```
ผลลัพธ์ที่ได้จาก Code

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1c48aqtipenpjp1rj997.png)

โมเดลนี้มีทั้งหมด 6 ชั้น ประกอบด้วย Convolution layer 2 ชั้น , MaxPooling layer 2 ชั้น, Flatten layer 1 ชั้นและ Dense layer 1  ชั้น โดยมพารามิเตอร์ทั้งหมด 373,315 ตัว โมเดลมีพารามิเตอร์ที่ trainable 352,641 ตัว และโมเดลที่ไม่มีพารามิเตอร์ 0 ตัว

**ขั้นตอนที่ 11 Compile Model Deep Learning เตรียมโมเดลให้พร้อมสำหรับการ Train**
ทำการกำหนด epochs เอาไว้ที่ 20 ครั้ง และ batch size อยู่ที่ 32 รอบ คือจำนวนในการเทรน 1 ครั้ง จะมีจำนวนอยู่ที่ 32 รอบ

```
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=0.0001),loss='categorical_crossentropy',metrics=['accuracy'])
batch_size = 32
epochs = 20
```
**ขั้นตอนที่ 12 การ Train Model**
ทำการ Train Model จำนวน 20 รอบ ตามจำนวน epochs

```
history = model.fit(x_train, y_train ,batch_size=batch_size, epochs=epochs ,validation_data=(x_test, y_test))
```
ผลลัพธ์ที่ได้จาก Code 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gptu81jfmg1f0h425fjv.png)

**ขั้นตอนที่ 13 การพล๊อตกราฟ**
ขั้นตอนนี้เราจะทำการพล๊อตกราฟขึ้นมา เพื่อดูค่าความแม่นยำของโมเดลที่เราได้นำมา
ทดลอง และเราได้ใช้ไลบรารี่ Matplotlib ขึ้นมา เพื่อสามารถพล๊อตกราฟได้

```
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
# "Loss"
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
avg_acc = np.mean(acc) * 100
avg_val_acc = np.mean(val_acc) * 100


print("Average training accuracy:", avg_acc, "%")
print("Average validation accuracy:", avg_val_acc, "%")

```
ผลลัพธ์ที่ได้จาก Code

จากกราฟที่ได้มาสรุปได้ว่า ในแนวแกน X มีจำนวน epoch 20 epoch และแนวแกน Y คือความแม่นยำของโมเดล โดยที่จะเริ่มต้นที่ร้อยละ 0.4 และสูงสุดที่ร้อยละ 0.97 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5td13uabs50basxnhqs.png)

จากกราฟที่ได้มาสรุปได้ว่า ในแนวแกน X มีจำนวน epoch 20 epoch และแนวแกน Y คือค่าความคลาดเคลื่อนของโมเดล โดยความคลาดเคลื่อนจะเริ่มต้นที่ร้อยละ 1.0 และต่ำสุดที่ร้อยละ 0.07 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyma6k2dldplphlsrgr5.png)

ในการทดสอบนี้ทั้งหมด ค่าความแม่นยำของการเทรนโมเดลเฉลี่ยอยู่ที่ **87.17 %**  และความแม่นยำของการทดสอบโมเดลเฉลี่ยอยู่ที่ **87.80 %**

**ขั้นตอนที่ 14 ทำนายผลของสายพันธุ์มะม่วง**
ทำการทำนายสายพันธุ์มะม่วง โดยใช้ Model Deep Learning ที่เรา Train เอาไว้ในการทดสอบ

```
testpath = 'test/'
testImg = [testpath+f for f in listdir(testpath) if listdir(join(testpath, f))]
rimg = []
for imagePath in (testImg):
    for item in (os.listdir(imagePath)):
        file = os.path.join(imagePath, item)
        if item.split('.')[0] != "":

          img = cv2.imread(file , cv2.COLOR_BGR2RGB)
          ori = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
          img = cv2.resize(img ,(width,width))
          rimg = np.array(img)
          rimg = rimg.astype('float32')
          rimg /= 255
          rimg = np.reshape(rimg ,(1,128,128,3))
          predict = model.predict(rimg)
          label = ['Fajri','Dasheri','Chaunsa']
          result = label[np.argmax(predict)]
          print(predict * 100)
          print('real:'+str(item))
          print('predict:'+str(result))
          plt.imshow(ori)
          plt.show()

```
ผลลัพธ์ที่ได้จาก Code 
จากผลลัพธ์ในการทดสอบความแม่นยำในการตรวจสายพันธุ์มะม่วง ภาพแรกพบว่ามีความเป็นได้ว่าเป็นมะม่วงสายพันธุ์ Dasheri อยู่ที่ **99 %** ซึ่งเป็นคำตอบที่ถูกต้อง

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8ewmh17aemmz97xm2z4.png)


จากผลลัพธ์ในการทดสอบความแม่นยำในการตรวจสายพันธุ์มะม่วง ภาพที่สองพบว่ามีความเป็นได้ว่าเป็นมะม่วงสายพันธุ์ Chaunsa อยู่ที่ **99 %** ซึ่งเป็นคำตอบที่ถูกต้อง

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cj8xdq328td3xltp7o0u.png)

จากผลลัพธ์ในการทดสอบความแม่นยำในการตรวจสายพันธุ์มะม่วง ภาพที่สามพบว่ามีความเป็นได้ว่าเป็นมะม่วงสายพันธุ์ Fajri อยู่ที่ **94 %** ซึ่งเป็นคำตอบที่ถูกต้อง

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvveda8m8tpzth8mddin.png)

**สรุป**
จากการทดลองเราจะเห็นได้ว่า Visual Geometry Group 16 (VGG16) ใน Python ที่ใช้ในการจำแนกสายพันธุ์มะม่วง มีความแม่นยำอยู่ที่ **91 %** ถึงแม้ว่าจะมีความแม่นยำที่สูงแต่ก็ยังมีโอกาสที่จะผิดพลาดได้ และโมเดลนี้สามารถนำไปใช้ในการตรวจสอบเพิ่มเติมเกี่ยวกับมะม่วงได้ เช่น การตรวจสอบคุณภาพของมะม่วง วิเคราะห์ความสุก-ดิบของมะม่วงได้


Referenceshttps
https://www.kaggle.com/datasets/riyaelizashaju/skin-disease-image-dataset-balanced?fbclid=IwAR3wbTp8l5yo_5fx6HAX8Vd2-9cca3khAc8EiBGFObaALfdVid29IuB_rYE
https://keras.io/api/applications/vgg/
https://www.tensorflow.org/tutorials/images/cnn?hl=th
https://opencv.org/