Part 2: Optical Character Recognition with TensorFlow and Keras
มาตอนกับที่ตอนที่ 2 สำหรับการทำโมเดล OCR ด้วย TensorFlow และ Keras จากตอนนี้หน้านี้ได้ทำการจัดระเบียบข้อมูล ให้เหมาะสมกับการทำงานด้วย Neural network แล้ว มาถึงตอนนี้คงถึงเวลาเริ่มสร้างโมเดลกันแล้ว สำหรับใครที่ยังไม่ได้อ่านตอนแรก สามารถกดได้ที่ Link ด้านล่าง เพื่อความต่อเนื่อง
ขอ Recap สั้น ๆ จากตอนก่อนหน้านี้ที่เขียนถึง Data processing ข้อมูล ณ ตอนนี้ถูกแยกออกเป็น 2 Arrays หลัก ๆ คือ Labels และ Images ต่อไปเป็นการเข้าสู่การทำโมเดลแล้ว
Engineering variables
ขอเริ่มที่ Features ก่อน ตอนนี้ข้อมูลถูกเก็บอยู่ใน Array ที่มี Number ต่าง ๆ แทนตัวเลขหรือตัวหนังสือ สิ่งที่นิยมทำคือการเปลี่ยน Number เหล่านั้นให้อยู่ในช่วง 0–1 หรือเรียกว่าการ Normalisation
ทั้งนี้สามารถหารค่าใน Array ทุกตัวด้วย 255.0
หรือใช้ Library ใน TensorFlow ก็ได้เช่นกัน ซึ่งตอนนี้ขอใช้เป็น Library ให้เรียกใช้จาก tensorflow.keras.utils.normalize()
หรือถ้ามีการ Import ไว้ (เหมือนในตัวอย่าง) สามารถเรียกใช้งาน .normalize()
ได้เลย หลังจากนั้นลอง .show()
รูปภาพเดิมเพื่อเทียบความแตกต่าง
ขั้นตอนต่อไปเป็นการเปลี่ยน Shape ของ Array 3 มิติ ให้เป็น Tensor หรือ Array ที่มีขนาด 4 มิติ เพื่อให้เข้า Neural network ที่ออกแบบไว้ได้ จากเดิมมิติของ Array คือ (442450, 28, 28)
ให้ทำการเพิ่มมิติขึ้นมาอีก 1 มิติ ซึ่งสามารถใช้คำสั่ง .reshape()
แล้วเพิ่มมิติ 1 เข้าไปที่ตำแหน่งสุดท้าย เหมือนที่โค้ดตัวอย่างเขียนไว้ หรือใช้ numpy.expand_dims()
เพื่อเป็นการเพิ่มมิติให้ข้อมูลก็ทำได้เช่นกัน ถ้าเขียนโดยใช้ Numpy สามารถเขียนได้เป็น images = np.expand_dims(images, axis = -1)
ซึ่ง Return ค่ามิติออกมาเท่ากับโค้ดตัวอย่าง สามารถศึกษาคำสั่งนี้เพิ่มเติมได้จาก Link นี้
Reshape images shape: (442450, 28, 28, 1)
**สำหรับการออกแบบ Network แบบอื่น ๆ เช่น Fully connected อาจไม่จำเป็นต้องทำขั้นนี้ โดยอาจใช้ Flatten layer เพื่อให้รูปภาพกลายเป็นมิติเดียวสำหรับการ Training ก็ได้เช่นกัน
ต่อมาขอพูดถึง Target variables มีหลายเทคนิคที่สามารถใช้กับ Target ที่เป็น Multiple classes ได้ วิธีการหนึ่งที่นิยมทำคือ One-Hot Encoder ทบทวนสั้น ๆ จากที่เคยเขียนเอาไว้คือ แปลงเลข Multiple classes target จากเดิมที่เป็นเลข 0–36 ให้กลายเป็นเลข 0 หรือ 1 และเปลี่ยนมิติจากเดิมที่เป็นมิติ 1 มิติ (442450,)
ให้เป็น 2 มิติคือ (442450, 36)
**แต่ทั้งนี้ทั้งนั้นขึ้นอยู่กับการออกแบบ Model architecture ว่าต้องการใช้ Loss function แบบไหน เช่น หากเปลี่ยน Target ด้วย One-Hot Encoder ควรใช้ categorical_crossentropy
แต่ถ้า Target ยังเป็น Multiple classes ที่เป็นการ Labels encoding อยู่ (เหมือนในตอนนี้) ก็ควรใช้ Loss function ที่เหมาะสมคือ sparse_categorical_crossentropy
เมื่อไม่ได้ต้องทำอะไรเพิ่มเติมที่ตัวแปร Y ขั้นตอนต่อไปคือการแยก Train และ Test สำหรับการทำโมเดลต่อไป (กำหนด Testing เป็น 20%)
Training images shape: (353960, 28, 28, 1)
Training labels shape: (353960,)
Testing images shape: (88490, 28, 28, 1)
Testing labels shape: (88490,)
Model development
สร้าง Augment ให้กับรูปภาพ คือให้รูปภาพถูก Train ด้วยหลายมุมองศา เพื่อให้เกิดความ Flexible มากขึ้นในการใช้งานจริง ซึ่งค่าต่าง ๆ สามารถปรับเพิ่มหรือลดได้ตามความต้องการ แต่สิ่งที่อยากให้สนใจคือ horizontal_filp = False
ที่ต้องเป็น False
เพราะว่าตัวหนังสือหรือตัวเลขส่วนมาก ไม่สามารถพลิกหรือกลับด้านได้
Optimiser ของโมเดลนี้ใช้เป็น Adam()
ด้วย Learning rate ที่ 0.01
ไม่แนะนำให้ปรับเป็นค่าสูงกว่านี้ เพราะ Loss function จะไม่สามารถหา Global minimum ได้
เนื่องจาก Pre-trained model หลาย ๆ ตัว ถูก Train มาด้วยภาพสี RGB ดังนั้นการใส่ภาพขาวดำหรือ Grayscale ที่มีอยู่ตอนนี้เข้าไป ทำให้มิติของ Input layer ขาดไป 2 มิติ (RGB มี 3 มิติ ส่วน Grayscale มี 1 มิติ) จึงไม่สามารถใช้งาน Pre-trained model ได้ ดังนั้นการ Training model ในครั้งนี้ขอใช้เป็น Convolutional Neural Network (CNN) แทน
การสร้าง CNN architecture สามารถงานโครงสร้างอย่าง LeNet-5 ได้ ซึ่งอาจทำให้โมเดลมีความแม่นยำมากขึ้น แต่สำหรับโมเดลนี้ ขอสร้าง CNN อย่างง่ายขึ้นมาใช้งานก่อน สำหรับ LeNet-5 สามารถอ่านเพิ่มเติมได้จาก Link ด้านล่าง
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_6 (Conv2D) (None, 26, 26, 32) 320 _________________________________________________________________ conv2d_7 (Conv2D) (None, 24, 24, 64) 18496 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 12, 12, 64) 0 _________________________________________________________________ dropout_6 (Dropout) (None, 12, 12, 64) 0 _________________________________________________________________ flatten_3 (Flatten) (None, 9216) 0 _________________________________________________________________ dense_6 (Dense) (None, 128) 1179776 _________________________________________________________________ dense_7 (Dense) (None, 64) 8256 _________________________________________________________________ dropout_7 (Dropout) (None, 64) 0 _________________________________________________________________ dense_8 (Dense) (None, 36) 2340 ================================================================= Total params: 1,209,188
Trainable params: 1,209,188
Non-trainable params: 0 _________________________________________________________________
เมื่อทุกอย่างพร้อมแล้วสามารถ .fit()
โมเดลได้ในขั้นตอนสุดท้าย ซึ่งโมเดลนี้ใช้ batch_size = 512
เพราะทดลองผ่านค่าต่าง ๆ ลงไปแล้ว เช่น 64, 128 หรือ 256 ความแม่นยำที่ได้ออกมาไม่ได้แตกต่างกันมาก โมเดลนี้ทำการ Training ทั้งหมด 10 Epochs
...
Epoch 49/50
692/692 [==============================] - 148s 214ms/step - loss: 0.2201 - accuracy: 0.9373 - val_loss: 0.1534 - val_accuracy: 0.9535
Epoch 50/50
692/692 [==============================] - 148s 214ms/step - loss: 0.2199 - accuracy: 0.9369 - val_loss: 0.1661 - val_accuracy: 0.9428
ทำการแสดงค่า Model accuracy และ Model loss ออกมาเป็นกราฟ ระหว่าง Training set และ Testing set
ขั้นตอนสุดท้ายคือการ Evaluate model เพื่อให้แสดง Model loss และ Model accuracy จาก Testing set ก่อนนำไปใช้งานจริง
2766/2766 [==============================] - 14s 5ms/step - loss: 0.1661 - accuracy: 0.9428
2766/2766 [==============================] - 14s 5ms/step - loss: 0.1661 - accuracy: 0.9428
Model loss: 0.1661, Model accuracy: 0.9428
ทำการ .save()
โมเดลสุดท้ายไว้ สำหรับการนำไปใช้งานในด้านอื่น ๆ เช่นการอ่านเอกสาร ซึ่งคงได้ทำในโอกาสต่อไป
Model prediction
ทดลองใช้งานโมเดลที่สร้างขึ้นมากับรูปภาพ ซึ่งถือได้ว่ามีความซับซ้อนน้อยกว่างานเอกสาร เพิ่มดูความสามารถในการ Predict ผลของโมเดล เนื่องจากคำตอบจากโมเดลจะเป็น Probabilities ทั้งหมด 36 ค่า ตามจำนวน Target ดังนั้นเพื่อการอ่านผลที่ง่านขึ้น ขอสร้างเป็น Labels ที่เก็บคำตอบไว้ทั้งหมด 36 คำตอบ 0-Z
จากที่ได้บอกไปด้านบนว่า คำตอบจาก .predict()
มีค่าความน่าจะเป็นทั้งหมด 36 ค่า โดยที่ค่าความน่าเป็นที่มีค่ามากที่สุด ถือได้ว่าเป็นสิ่งที่โมเดลทำนายออกมา ดังนั้นสามารถใช้ .argmax(axis = 1)
เพื่อให้ Return เป็นค่า Index ค่า Probability ที่มากที่สุดออกมาได้
เขียนเป็น Loop function เพื่อ Random รูปภาพใน Testing set ออกมา 1
รูป ซึ่งหากต้องการมากกว่า 1
รูป สามารถแก้ไข Optional ใน numpy.random.choice()
ให้เป็นค่าตามที่ต้องการได้ จากนั้นสร้างตัวแปรชื่อว่า prediction
ด้วยให้ไปดึงค่าจาก Array pred
ตามตำแหน่งที่โดนสุ่มออกมา ซึ่งใน Array pred
เก็บค่า Index ที่ Probability ที่มากที่สุดไว้อยู่ เมื่อได้คำตอบที่เป็นตัวเลขออกมาแล้ว สามารถใช้เป็น Index ในตัว label_names
เพื่อ Return ออกมาเป็นตัวเลขหรือตัวหนังสือได้ตามต้องการ
ในส่วนของการแสดงผล plt.imshow()
รับค่าที่รูปภาพหรือ Array ที่เป็น 2 มิติ (width, height) แต่เนื่องโมเดลถูกเทรนมาด้วย Tensor ที่มีขนาด 4 มิติ ดังนั้นจึงต้อง .reshape()
ในอยู่ในรูป 2 มิติ ถึงสามารถทำการแสดงผลได้
Conclusion
ทดลองใช้โมเดลที่สร้างเสร็จแล้ว ผลลัพธ์ที่ออกมาถือว่าอยู่ในระดับที่น่าพอใจ แต่ยังไม่แน่ใจว่าพอใช้ในงาน Production เช่นการอ่าน Text หรือลายมือ โมเดลจะยังสามารถทำงานได้ดีอยู่ไหม แต่เรื่องนั้นเอาไว้เป็นเรื่องของอนาคตที่คงได้เห็นกันในเร็ว ๆ นี้