ระบุอารมณ์จากภาพใบหน้า โดยใช้ deep learning
Published: 2024-04-16 12:52:16

หากร้านกาแฟแห่งหนึ่งต้องการเก็บข้อมูลเก็บข้อมูลความพึ่งพอใจลูกค้าในแต่ละวัน ซึ่งตอนแรกก็ได้แจกแบบสอบถามแต่ปรากฏว่าแทบไม่มีใครทำเลย จึงเกิดไอเดียว่าถ้าเราเก็บข้อมูลความพึ่งพอใจลูกค้าโดยใช้ภาพหน้าแคชเชียร์ในการประเมินความพึ่งพอใจขอลูกค้าแทนการกรอกแบบสอบถามแทนล่ะ?

>  ซึ่งในบทความนี้จะพูดถึงรายละเอียดในการออกแบบและการใช้งานของโมเดล โดยประกอบด้วยขั้นตอนการสร้าง Convolutional Neural Network (CNN) สำหรับการระบุอารมณ์ใบหน้า การเตรียมข้อมูลสำหรับการฝึกโมเดล เลือกและปรับพารามิเตอร์ของโมเดล การฝึกโมเดล และการทดสอบประสิทธิภาพของโมเดลที่สร้างขึ้นในสภาพแวดล้อมจริง นอกจากนี้ยังรวมถึงการปรับแต่งโมเดลเพื่อให้มีประสิทธิภาพมากที่สุดในการระบุอารมณ์

**ขั้นตอนที่ 1 ดาวน์โหลดข้อมูล**
โดยในโปรเจค์นี้เราจะใช้ dataset ของ  FER2013 จาก [Challenges in Representation Learning: Facial Expression Recognition Challenge ](https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data)ใน Kaggle

ตัวอย่าง code ของการดาวน์โหลดข้อมูลและการแตกไฟล์

```
import os

# Configure kaggle
os.chdir('/root/')
!mkdir -p .kaggle
os.chdir('/root/.kaggle')
!wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1Y-o0TVcjehM8SZB3Nt8U3xkyeQu-Nse-' -O kaggle.json > /dev/null 2>&1
!ls /root/.kaggle

# Set permissions
!chmod 600 /root/.kaggle/kaggle.json

# Create data folder
os.chdir('/content/')
!rm -rf data
!mkdir data
os.chdir('data')
!pwd

# Download data
!pip install -q kaggle
!kaggle competitions download -c challenges-in-representation-learning-facial-expression-recognition-challenge

# Unzip data
!unzip train.csv.zip train.csv
```
เมื่อดาวน์โหลดข้อมูลและแตกไฟล์ zip เสร็จสมบูรณ์จะแสดงผลดังนี้

![ผลลัพธ์ดาวน์โหลดข้อมูลเสร็จสมบูรณ์](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2bt8wwtdgqcalrj2hfx.png)

**ขั้นตอนที่ 2 นำเข้าข้อมูล**
dataset ที่เราดาวน์โหลดมาจาก Kaggle จะอยู่ในรูปแบบไฟล์ ".csv" ดังนั้นเราจะต้องโหลดไฟล์ "train.csv" และแปลงเป็น numpy array ข้อมูลภาพที่ใช้สำหรับการเทรนโมเดลไว้ใน "x_train" และ "y_train" ตามลำดับ
โดยสามารถดูตัวอย่างได้จาก code นี้

```
import csv
import numpy

train_images = []
train_labels = []

categories_count = {}

with open('train.csv') as train:

    # Read train.csv file
    csv_reader = csv.reader(train)
    next(csv_reader)  # Skip the header

    for row in csv_reader:

        # Append image
        pixels_str = row[1]
        pixels_list = [int(i) for i in pixels_str.split(' ')]
        pixels_list = numpy.array(pixels_list, dtype='uint8')
        image = pixels_list.reshape((48, 48))
        train_images.append(image)

        label_str = row[0]

        # Calculate categories count
        count = 0
        if label_str in categories_count:
            count = categories_count[label_str] + 1
        categories_count[label_str] = count

        # Append label
        label = int(label_str)
        train_labels.append(label)

# Create numpy array of train images and labels
x_train = numpy.array(train_images)
y_train = numpy.array(train_labels)

print('x_train shape: {0}'.format(x_train.shape))
print('y_train shape: {0}'.format(y_train.shape))
```
เมื่อรันโค้ดจะได้ผลลัพธ์ดังนี้
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6bsvl4vqkkf4izu0l26.png)

ภาพที่ dataset มีให้มี 7 หมวดหมู่สีหน้าที่แตกต่างกัน (0=โกรธ, 1=เกลียด, 2=กลัว, 3=มีความสุข, 4=เศร้า, 5=เซอร์ไพรส์, 6=ธรรมดา) และกราฟแสดงการกระจายของหมวดหมู่สีหน้าแสดงดังนี้

โดยสามารถตรวจสอบข้อมูลของ dataset และแสดงกราฟข้อมูลด้วย code นี้

```
import matplotlib.pyplot as plt
%matplotlib inline

categories = ('Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral')
y_pos = numpy.arange(len(categories))

counts = []
for label in range(len(categories)):
    label_str = str(label)
    count = categories_count[label_str]
    counts.append(count)
    plt.text(label - 0.25, count + 15, str(count))

# Draw histogram
plt.bar(y_pos, counts, align='center', alpha=0.5)
plt.xticks(y_pos, categories)
plt.ylabel('Count')
plt.title('FER2013 Dataset Categories')

plt.show()
```
กราฟที่แสดงผล
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvgeimia1rh0ec8pksb4.png)

จากนั้นเรามาแสดงรูปภาพหนึ่งภาพในdatasetกัน โดยในแต่ละภาพมีระดับสีเทาและมีขนาด 48 x 48 พิกเซล

```
image = x_train[0]
label = y_train[0]

print('Label is: ' + str(label))
plt.imshow(image, cmap='gray')
```
ผลลัพธ์
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz2awn3vfgjortghvhfi.png)

**ขั้นตอนที่ 3 ประมวลผลข้อมูล**
ต่อไป เราต้องแบ่งชุดข้อมูลออกเป็นชุดการฝึกและชุดทดสอบ ที่นี่ เราเลือกชุดข้อมูล 20% เป็นชุดทดสอบ และชุดข้อมูลที่เหลือเป็นชุดเทรนโมเดล

```
from sklearn.model_selection import train_test_split

# Split dataset into train set and test set
x_train, x_test, y_train, y_test = train_test_split(x_train, y_train, test_size=0.2)
x_train, x_test = x_train / 255.0, x_test / 255.0

x_train = x_train.reshape(len(x_train), 48, 48, 1)
x_test = x_test.reshape(len(x_test), 48, 48, 1)

print(x_train.shape)
print(x_test.shape)
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot6szbkh9hp4c0w7016x.png)

**ขั้นตอนที่ 4 เทรนโมเดล**
ต่อไป เราต้องเทรนโมเดลด้วยชุดข้อมูลรูปภาพ ที่นี่เราใช้ Tensorflow เป็น Back-end เพื่อเทรนโมเดล ดังนั้นเราจึงจำเป็นต้องนำเข้าแพ็คเกจที่จำเป็นทั้งหมดก่อนกระบวนการเทรนโมเดล
```
%tensorflow_version 2.x
import tensorflow as tf

print('Tensorflow version: {}'.format(tf.__version__))

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, BatchNormalization, MaxPool2D, Dropout, Flatten, Dense

from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

from tensorflow.keras import Model, Input

from tensorflow.keras.preprocessing.image import ImageDataGenerator
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mi9c7uk9cl8a0g9biym2.png)

โมเดลนี้จะอ้างอิงถึง VGGNet architecture ได้ออกแบบโมเดลด้วยการใช้เลเยอร์หลายชั้นซ้อนกัน โมเดลนี้จะมีองค์ประกอบดังต่อไปนี้

- ชั้นคอนโวลูชัน (Convolutional layers): เป็นส่วนประกอบหลักของโครงสร้างของเรา ซึ่งเรียนรู้ลักษณะของภาพโดยการคำนวณผลคูณของน้ำหนักและพื้นที่เล็กบนภาพ คล้ายกับ VGGNet architecture ทุก Convolutional ถูกออกแบบด้วยขนาดเคอร์เนล 3 x 3 และมีตัวกรองหลายตัว
- ฟังก์ชันการกระตุ้น (Activation functions): ทำหน้าที่รับเอาผลลัพธ์จากเลเยอร์ต่างๆ ในเครือข่ายมาประยุกต์ใช้ ในโมเดลนี้เราใช้ฟังก์ชัน ReLU (Rectified Linear Unit) เพื่อเพิ่มความไม่เป็นเส้นตรง (non-linearity) ให้กับเครือข่าย นอกจากนี้ยังใช้ฟังก์ชัน Softmax เพื่อคำนวณความน่าจะเป็นของแต่ละประเภท
- ชั้นพูลลิ่ง (Pooling layers): จะลดขนาดของภาพเพื่อลดข้อมูลทางเชิงพื้นที่และคัดคุณลักษณะ ในโมเดลของเราเราจะใช้ Max Pooling ขนาน 3 x 3 และขนาน 2 x 2
- ชั้นเดนส์ (Dense layers): ถูกจัดเรียงซ้อนกันเป็นชั้น fully-connected ของเครือข่าย ทำหน้าที่รับข้อมูลฟีเจอร์จากชั้น convolutional ชั้นก่อนๆ และดำเนินการตัดสินใจ
- ชั้น Dropout: ถูกใช้เพื่อป้องกันการเรียนรู้มากเกินพอ (over-fitting) ในขณะที่เทรนโมเดล
- ปรับปรุงความสามารถในการแบทช์ (Batch normalization): เทคนิคนี้ช่วยเร่งการเรียนรู้โดยการทำให้เอาต์พุตของชั้น activation ชั้นก่อนหน้าเป็นมาตรฐาน

ภาพโครงสร้างของโมเดล
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5cvzey3ulqopmlll4otb.png)

โมเดลของเรามีเลเยอร์ 5 ชุด ใน 4 ชุดแรกประกอบด้วยเลเยอร์ convolutional 2 ชั้น ตามด้วยเลเยอร์ pooling 1 ชั้น นอกจากนี้เรายังใช้ batch normalization เพื่อเร่งการเรียนรู้และ dropout เพื่อป้องกันการ(over-fitting) จากนั้นเราจะมีเลเยอร์ fully-connected 3 ชั้น ตามด้วยฟังก์ชันการกระตุ้น Softmax ซึ่งทำหน้าที่สร้างความน่าจะเป็นของประเภทของสีหน้า สุดท้ายเราคอมไพล์โมเดลของเราโดยใช้ Adam optimizer กับค่า learning rate tertentu (อัตราการเรียนรู้ที่กำหนด) เนื่องจากเราจัดการกับปัญหาด้านการจำแนกประเภท เราจะใช้ sparse_categorical_crossentropy เป็น loss function

```
cnn_model = Sequential()

# 1st convolution layer
cnn_model.add(Conv2D(64, input_shape=(48, 48, 1), kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(64, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 2nd convolution layer
cnn_model.add(Conv2D(128, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(128, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 3rd convolution layer
cnn_model.add(Conv2D(256, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(256, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 4th convolution layer
cnn_model.add(Conv2D(512, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(512, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# Fully connected layer
cnn_model.add(Flatten())
cnn_model.add(Dense(512, activation='relu'))
cnn_model.add(Dropout(0.3))
cnn_model.add(Dense(256, activation='relu'))
cnn_model.add(Dropout(0.3))
cnn_model.add(Dense(64, activation='relu'))
cnn_model.add(Dropout(0.3))

cnn_model.add(Dense(7, activation='softmax'))

# Compile the model
adam = Adam(learning_rate=0.001)
cnn_model.compile(optimizer=adam,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

# Summary the model
cnn_model.summary()cnn_model = Sequential()

# 1st convolution layer
cnn_model.add(Conv2D(64, input_shape=(48, 48, 1), kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(64, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 2nd convolution layer
cnn_model.add(Conv2D(128, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(128, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 3rd convolution layer
cnn_model.add(Conv2D(256, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(256, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# 4th convolution layer
cnn_model.add(Conv2D(512, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(Conv2D(512, padding='same', kernel_size=(3, 3), activation='relu'))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPool2D(pool_size=(3, 3), strides=(2, 2)))
cnn_model.add(Dropout(0.3))

# Fully connected layer
cnn_model.add(Flatten())
cnn_model.add(Dense(512, activation='relu'))
cnn_model.add(Dropout(0.3))
cnn_model.add(Dense(256, activation='relu'))
cnn_model.add(Dropout(0.3))
cnn_model.add(Dense(64, activation='relu'))
cnn_model.add(Dropout(0.3))

cnn_model.add(Dense(7, activation='softmax'))

# Compile the model
adam = Adam(learning_rate=0.001)
cnn_model.compile(optimizer=adam,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

# Summary the model
cnn_model.summary()
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgrhkp52r01hc4x2xh3t.png)

ถัดมาจะเทรนโมเดล ในกรณีนี้เราใช้วิธี Early Stopping ซึ่งจะหยุดกระบวนการเทรนโมเดลเมื่อไม่มีการปรับปรุงความแม่นยำ 
ตัวอย่าง code ในการเทรนโมเดล
```
# Callbacks
early_stopping = EarlyStopping(monitor='val_accuracy', patience=10)
reduce_learning_rate = ReduceLROnPlateau(monitor='val_loss', factor=0.9, patience=5)

# Train the model
history = cnn_model.fit(x_train,
                        y_train,
                        batch_size=64,
                        epochs=100,
                        validation_data=(x_test, y_test),
                        callbacks=[early_stopping, reduce_learning_rate])
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94pnqh441b1ta6kk9rna.png)
หลังจากกระบวนการเทรนโมเดลเสร็จสิ้น มาแสดงแผนภาพความแม่นยำในการเทรนโมเดลหลังจากกระบวนการเสร็จสิ้น

```
# Show accuracy diagram
plt.title('Model Accuracy')
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label='val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
```
ผลลัพธ์ในการเทรนโมเดล
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gusmo2sya14gu05ixwxe.png)

**ขั้นตอนที่ 5 ปรับปรุงโมเดล**
เราจะพยายามปรับปรุงโมเดลเพื่อเพิ่มความแม่นยำในการตรวจสอบ ในกรณีนี้ได้ใช้เทคนิคต่อไปนี้ในการปรับปรุงโมเดลของเรา:
- สร้างข้อมูลที่ยาก (Generate hard data)
- ขยายข้อมูล (Data augmentation)
```
# Evaluate the model before improvement
_, score_before_improvement = cnn_model.evaluate(x_test, y_test)
print('Score before improvement: {}'.format(score_before_improvement))
```
ก่อนที่จะปรับปรุงโมเดลได้ทดสอบโมเดล จากผลคะแนนก่อนการปรับปรุงพบว่าคะแนนอยู่รอบ 64%
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ofbuaa4ip41f91apv54.png)
ถัดไปจะปรับปรุงโมเดลของเราโดยการสร้างข้อมูลที่ยากต่อการเรียนรู้ (Hard Data) โดยเฉพาะ แทนที่จะฝึกโมเดลซ้ำและซ้ำ จะเลือกรูปภาพผิดพลาดโดยโมเดล และฝึกโมเดลด้วยรูปภาพเหล่านี้เท่านั้น และนำรายการที่ผิดพลาดมาใส่ในอาร์เรย์ของข้อมูลที่ยากต่อการเรียนรู้ เพื่อฝึกโมเดลเพิ่มเติมในภายหลัง

```
# Generate hard data
hard_images = []
hard_labels = []

# Make predictions
predictions = cnn_model.predict(x_test)
for i, v in enumerate(predictions):
    y_predict = numpy.argmax(v)
    y_real = y_train[i]
    if y_predict != y_real:
        # If predict incorrectly, append to array
        image = x_train[i]
        hard_image = image.reshape(1, 48, 48, 1)
        hard_images.append(image)
        hard_labels.append(y_real)

x_hard = numpy.array(hard_images)
y_hard = numpy.array(hard_labels)

print(x_hard.shape)
print(y_hard.shape)
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a27vdlkw8rdl4238nkij.png)
ต่อไปจะนำภาพที่โมเดลเราเคยเรียนรู้ผิดพลาด ไปฝึกโมเดลอีกครั้ง  ภาพเหล่านี้คือ "ข้อมูลยาก" (hard data) ที่จะช่วยให้โมเดลเรียนรู้และปรับปรุงความสามารถในการแยกประเภท (classify) ได้ดีขึ้น

```
# Train the model on hard data
x_hard_train, x_hard_test, y_hard_train, y_hard_test = train_test_split(x_hard, y_hard, test_size=0.2)
history = cnn_model.fit(x_hard_train,
                        y_hard_train,
                        batch_size=64,
                        epochs=100,
                        validation_data=(x_hard_test, y_hard_test),
                        callbacks=[early_stopping, reduce_learning_rate])
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uemaj1hifke5uhedlg3.png)

วิธีนี้มีโอกาสทำให้เกิดปัญหา Overfitting กับข้อมูลที่ผิดพลาดเหล่านี้ได้ ดังนั้นจะนำโมเดลไปฝึกอีกครั้งเพื่อหาจุดสมดุล (balance out)

นอกจากนี้จะทำการเพิ่มประสิทธิภาพข้อมูล (data augmentation) ด้วยการหมุน (rotate), เลื่อน (shift),  ย่อ-ขยาย (zoom) หรือ พลิก (flip) ภาพ เพื่อสร้างความหลากหลายให้กับชุดข้อมูลฝึก (training dataset)  เทคนิคนี้จะช่วยลดปัญหา Overfitting และทำให้โมเดลเรียนรู้คุณสมบัติทั่วไป (generic features) ของภาพได้ดีขึ้น
```
# Perform data augmentation
data_generator = ImageDataGenerator(featurewise_center=False,
                                    featurewise_std_normalization=False,
                                    rotation_range=10,
                                    width_shift_range=0.1,
                                    height_shift_range=0.1,
                                    zoom_range=.1,
                                    horizontal_flip=True)
flow = data_generator.flow(x_train, 
                           y_train, 
                           batch_size=64)

# Train the model again to balance out
history = cnn_model.fit(flow,
                        epochs=100,
                        validation_data=(x_test, y_test),
                        callbacks=[early_stopping, reduce_learning_rate])
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4jud4r87jrx576fbzjy.png)

สุดท้ายเรามาทดสอบประสิทธิภาพของโมเดลอีกครั้งหลังจากปรับปรุง 
```
# Evaluate the model after improvement
_, score_after_improvement = cnn_model.evaluate(x_test, y_test)
print('Score after improvement: {}'.format(score_after_improvement))
```
 ก่อนหน้านี้ ความแม่นยำบนข้อมูลทดสอบ (validation accuracy) อยู่ที่ประมาณ 64%  แต่หลังจากนำข้อมูลยากไปฝึกซ้ำ ความแม่นยำได้เพิ่มขึ้นเป็นประมาณ 68%
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lvw8n34fzmlp8y06jmn.png)

**ขั้นตอนที่ 6 ทดสอบโมเดล**
ต่อไปเราจะทำการทดสอบโมเดล และสร้างตารางความสับสน (confusion matrix)  เนื่องจาก dataset FER2013 ไม่มีภาพที่มีการแสดงอารมณ์ "รังเกียจ (Disgust)" มากพอ  เราอาจจะวิเคราะห์จากตารางความสับสนได้ว่า  โมเดลเราอาจจะจำแนกภาพแสดงอารมณ์ "รังเกียจ" ได้ไม่แม่นยำเท่าไหร่
```
!pip install -U mlxtend > /dev/null 2>&1

from mlxtend.plotting import plot_confusion_matrix
from sklearn.metrics import confusion_matrix

# Make predictions
predictions = cnn_model.predict(x_test)
y_predict = numpy.argmax(predictions, axis=1)

# Create confusion matrix
conf_matrix = confusion_matrix(y_test, y_predict)

# Display confusion matrix
class_names = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral']
fig, ax = plot_confusion_matrix(conf_mat=conf_matrix, class_names=class_names)
plt.title('Confusion Matrix')
plt.show()
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3n2q8so0qw4l05uzgzlx.png)

จากนั้นมาลองใช้ภาพสีหน้าของมนุษย์อื่นๆ ที่พบในเว็บไซต์ทั่วไปกัน
```
# Download into data folder
os.chdir('/content/data')

!wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1ommGLsYSnmX8846iyQigKQfIfvpQzsF0' -O happy.jpg > /dev/null 2>&1
!wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1I1IjDm9Az4pkDqOz3zcQAguEJ1OsWagt' -O sad.jpg > /dev/null 2>&1
!wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1ZLglz_y0QklbYTcwJW1wH0YnAwBn6-1I' -O surprise.jpg > /dev/null 2>&1
```
```
from tensorflow.keras.preprocessing import image

def predict_facial_expression(image_path):
    """Predict facial expression for image"""
    # Open image
    rgb_image = image.load_img(path=image_path,
                            color_mode='grayscale',
                            target_size=(48, 48))
    # Convert to array
    rgb_array = image.img_to_array(rgb_image)
    rgb_array = numpy.expand_dims(rgb_array, axis=0)

    # Show image
    rgb_display = numpy.array(rgb_array, 'float32')
    rgb_display = rgb_display.reshape([48, 48]);
    plt.imshow(rgb_display, cmap='gray')
    plt.show()

    # Make predictions
    rgb_array = rgb_array / 255.0
    prediction = cnn_model.predict(rgb_array)

    # Draw the histogram
    plt.bar(y_pos, prediction[0], align='center', alpha=0.5)
    plt.xticks(y_pos, categories)
    plt.ylabel('Percentage')
    plt.title('Facial Expression Prediction')
    plt.show()
```

**มีความสุข**

`# Happy
predict_facial_expression('happy.jpg')`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hddlmwah307x3y3xtpzq.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xt7ec1y9hbhuubgzu51.png)

**เศร้า**
`# Sad
predict_facial_expression('sad.jpg')`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/he4scss8xevnsl3cgeyx.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8nsoicppxg2no1bl587s.png)
**เซอร์ไพรส์**
`# Surprise
predict_facial_expression('surprise.jpg')`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wk0yt46o86s4q2nlc6ci.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0whqv246p85h1idp2iq7.png)

**ขั้นตอนที่ 7 Export โมเดล**
ท้ายที่สุด ให้เราส่งออกโมเดลและ save โมเดล TensorFlow และ TensorFlow Lite ของเรา
ตัวอย่าง code ในการ export model
```
# Create build folder
os.chdir('/content/')
!rm -rf build
!mkdir build
os.chdir('build')
!pwd
```
```
ตัวอย่าง code การ save โมเดล (TensorFlow)
# Save tensorflow model
cnn_model.save('FER2013.h5')
```
ตัวอย่าง code การ save โมเดล (TensorFlow Lite)
```
# Save tensorflow lite model
converter = tf.lite.TFLiteConverter.from_keras_model(cnn_model)
tflite_model = converter.convert()
open("FER2013.tflite", "wb").write(tflite_model)
```



**สรุป**
การตรวจจับอารมณ์บนใบหน้า (facial emotion recognition) ด้วยเทคนิค deep learning มีความแม่นยำในการตรวจจับและจำแนกอารมณ์จากสีหน้าบุคคลแบบเรียลไทม์ในระดับที่น่าพึ่งพอใจ สามารถนำไปใช้ประโยชน์จริงได้ในอนาคต เช่น การโต้ตอบระหว่างมนุษย์อย่างเรากับคอมพิวเตอร์ ระบบวิเคราะห์ความรู้สึก(sentiment analysis) อย่างไรก็ตามก็ต้องคำนึงถึงประเด็นทางจริยธรรมและระวังความเป็นส่วนตัวของแต่ละบุคคลด้วย