ในบทความที่ผ่านมาได้เขียนถึงการเรียนรู้ของเครื่องไปเยอะพอสมควรแล้ว จะเห็นว่าขั้นตอนการทำงานของโปรแกรมก็คือ
1. สร้างแบบจำลองการเรียนรู้ของเครื่อง (การถดถอยโลจิสติก, โครงข่ายประสาทเทียม, SVM, ฯลฯ)
2. ป้อนข้อมูลให้แบบจำลองไปเรียนรู้
3. นำแบบจำลองที่เรียนรู้เสร็จแล้วมาใช้ทำนายผล
ปกติแล้วขั้นตอนที่นานที่สุดก็คือการเรียนรู้ เพราะต้องป้อนข้อมูลแล้วให้โปรแกรมวนทำซ้ำหลายครั้งเพื่อปรับค่าพารามิเตอร์น้ำหนักและไบแอส
แล้วสุดท้ายตอนที่นำผลที่ได้ไปใช้ทำนายนั้น ก็แค่เป็นการใช้ค่าพารามิเตอร์ที่ได้เป็นผลออกมานั้นเพื่อเป็นฟังก์ชันในการคำนวณเพื่อให้ได้ผล จึงใช้เวลาแป๊บเดียว
แต่ว่าหากเราให้แบบจำลองทำการเรียนรู้เสร็จแล้วก็ปิดโปรแกรมไป พารามิเตอร์ที่อุตส่าห์เรียนรู้ตั้งนานเพื่อได้มานั้นก็จะหายไปพร้อมกับแบบจำลองทันที หากไม่ได้มีการบันทึกเอาไว้
แบบนั้นก็จะเท่ากับว่าเรียนรู้เสร็จแล้วต้องนำมาใช้เพื่อทำนายค่าทันที ไม่เช่นนั้นจะมาใช้อีกทีต้องมาเรียนรู้ใหม่
แต่ในการใช้งานจริงๆโดยทั่วไปแล้วควรจะเป็นในลักษณะที่ว่าเขียนโปรแกรมสำหรับป้อนข้อมูลให้แบบจำลองของเราทำการเรียนรู้ให้เสร็จเรียบร้อย เสร็จแล้วก็ค่อยเขียนอีกโปรแกรมซึ่งเป็นการนำผลจากการเรียนรู้นั้นมาใช้
ถ้าทำแบบนี้พอเรียนรู้เสร็จแล้วเราจะนำผลนั้นมาใช้กี่ครั้งก็ได้ในทันที
การเรียนรู้ของเครื่องนั้นถ้าข้อมูลเยอะจริงๆอาจใช้เวลาเป็นวันเลยทีเดียว อีกทั้งบางทีก็ไม่ใช่เรียนรู้ครั้งเดียวก็พอ อาจต้องเรียนรู้ใหม่เรื่อยๆเมื่อได้ข้อมูลใหม่เข้ามา ดังนั้นจึงจำเป็นต้องสามารถเก็บผลจากการเรียนรู้เอาไว้ใช้ต่อเรื่อยๆได้
ในบทความนี้จะพูดถึงการบันทึกเก็บผลการเรียนรู้เพื่อนำไปใช้ต่อ
วิธีการแบ่งคร่าวๆได้เป็น ๒ แบบ คือ
- บันทึกเก็บตัวออบเจ็กต์แบบจำลองเอาไว้
- บันทึกเก็บแค่พารามิเตอร์ที่เรียนรู้ได้
แต่ละวิธีมีข้อดีต่างกันออกไป
เริ่มจากวิธีแรก การบันทึกเก็บตัวแบบจำลอง อาจทำได้ง่ายโดยใช้มอดูลที่ชื่อ pickle ซึ่งเป็นมอดูลมาตรฐานของไพธอนที่ใช้ในการเก็บบันทึกออบเจ็กต์ไว้เพื่อใช้งานข้ามโปรแกรม
ขอยกตัวอย่างโดยใช้ข้อมูล MNIST (รายละเอียดเรื่อง MNIST อ่านได้ใน
https://phyblas.hinaboshi.com/20170922)
โดยจะเขียนโปรแกรมการถดถอยโลจิสติกโดยใช้คลาส ThotthoiLogistic ที่ได้เขียนไว้ใน
https://phyblas.hinaboshi.com/20171006 ตัวไฟล์ที่เก็บคลาสโหลดจากในนี้ได้
https://gist.github.com/phyblas/e65d8f2dc813b0d9289431a1061428fb ตัวอย่างในบทความนี้ใช้แบบจำลองการถดถอยโลจิสติกเป็นตัวอย่าง แต่ก็สามารถใช้กับแบบจำลองการเรียนรู้ของเครื่องชนิดอื่นๆ (เช่น โครงข่ายประสาทเทียม, SVM, ฯลฯ) ได้เช่นกัน
ตัวโปรแกรมสำหรับการเรียนรู้เขียนได้ดังนี้
from thotthoilogistic import ThotthoiLogistic # ใช้คลาสที่เขียนขึ้นมาเอง
from sklearn import datasets
from sklearn.model_selection import train_test_split
import pickle
mnist = datasets.fetch_mldata('MNIST original') # โหลดข้อมูล MNIST
X,z = mnist.data/255.,mnist.target
X_fuek,X_thotsop,z_fuek,z_thotsop = train_test_split(X,z,test_size=0.2) # แบ่งข้อมูลฝึกกับทดสอบ
tl = ThotthoiLogistic() # สร้างออบเจ็กต์แบบจำลองจากคลาส
tl.rianru(X_fuek,z_fuek,X_thotsop,z_thotsop,ro=5) # ทำการเรียนรู้
# บันทึกออบเจ็กต์ใส่ลงไฟล์
with open('mnistxz.pickle','wb') as f:
pickle.dump(tl,f)
ในที่นี้ไฟล์ mnistxz.pickle เป็นไฟล์ไบนารีซึ่งจะถูกสร้างขึ้นมาเพื่อเก็บข้อมูลต่างๆของออบเจ็กต์นี้ ชื่อสกุลจะเป็นอะไรก็ไม่สำคัญ แต่ที่นิยมใช้คือ .pickle หรือ .pkl
จากนั้นในโปรแกรมที่ต้องการนำผลมาใช้ก็เขียนดังนี้
from sklearn import datasets
import pickl
# อ่านข้อมูลออบเจ็กต์ที่เรียนรู้ไว้แล้ว
with open('mnistxz.pickle','rb') as f:
tl2 = pickle.load(f)
mnist = datasets.fetch_mldata('MNIST original')
X,z = mnist.data/255.,mnist.target
print((tl2.thamnai(X)==z).mean()) # นำมาใช้ทำนายผลและคำนวณความแม่น
# ได้ 0.927857142857
ในที่นี้ tl2 คือออบเจ็กต์ในคลาส ThotthoiLogistic ซึ่งเรียนรู้ไว้เรียบร้อยและถูกโหลดเข้ามาในนี้
และสังเกตได้ว่าไม่จำเป็นต้องทำการ import เรียกใช้คลาส ThotthoiLogistic ไว้ก่อนด้วย เพราะข้อมูลของคลาสก็ถูกเก็บไว้ในไฟล์หมดอยู่แล้ว
จากนั้นก็เรียกใช้เมธอด .thamnai เพื่อทำนายข้อมูลจากตัวแปรต้น X แล้วเทียบเคียงกับผล z เพื่อแปลงเป็นค่าความแม่นยำในการทำนายได้ทันที
พอทำแบบนี้แล้วไฟล์ mnistxz.pickle ก็อาจถือได้ว่าเท่ากับเป็นตัวเก็บฟังก์ชันสำหรับใช้คำนวณทำนายผล คือโหลดมาแล้วใช้คำนวณได้เลย ไม่ต้องสนว่ามันมาจากไหน แบบนี้บางทีเราอาจส่งไฟล์นี้ให้คนอื่นไปใช้งานก็ยังได้
เหมือนสมมุติว่าในโลกอนาคตเราไปซื้อหุ่นยนต์ทำงานบ้านที่ถูกฝึกมาจากโรงงานแล้วมาใช้ เราอาจไม่ต้องรู้ว่ามันถูกฝึกมายังไง รู้แค่ว่ามันทำงานให้เราได้ก็พอ เพียงแต่เราจะไม่รู้ว่าจะฝึกมันต่อยังไงเท่านั้นเอง แต่ถ้ามันฝึกมาดีพร้อมใช้งานอยู่แล้วก็ไม่จำเป็นแล้ว
นอกจากนี้ยังมีอีกวิธีคืออาจใช้คำสั่ง joblib ในมอดูลย่อย externals ของ sklearn ก็ได้
หากใช้แล้วการเขียนจะดูสั้นลง นั่นคือเวลาจะบันทึกก็แค่
from sklearn.externals import joblib
joblib.dump(tl,'mnistxz.pickle')
ส่วนเวลาจะโหลดก็
from sklearn.externals import joblib
tl2 = joblib.load('mnistxz.pickle')
ผลที่ได้จะไม่ต่างจากใช้ pickle คู่กับ open แค่เขียนสั้นลง
ต่อมาดูอีกวิธีก็คือการเก็บแค่พารามิเตอร์ไว้เพื่อนำมาใช้
วิธีนี้มีข้อดีก็คือสามารถนำมาใช้กับแบบจำลองที่ไม่ใช่คลาสเดียวกันได้ ขอแค่มีโครงสร้างที่เหมือนกันหรือเทียบเคียงกันได้ก็พอแล้ว
ยกตัวอย่างเช่นกรณีของการถดถอยโลจิสติก เราอาจต้องการนำผลที่ได้จากตัวอย่างก่อนหน้านี้ไปใช้กับคลาส LogisticRegression ของ sklearn (เกี่ยวกับคลาสนี้อ่านได้ที่
https://phyblas.hinaboshi.com/20171010)
ไม่ว่าคลาสไหนก็ถูกสร้างมาเพื่อทำการถดถอยโลจิสติกเหมือนกัน โครงสร้างแม้จะมีความแตกต่างกันในรายละเอียด แต่สิ่งที่เหมือนกันก็คือมีการใช้พารามิเตอร์่น้ำหนักและไบแอสเพื่อทำนายผลเหมือนกัน
นั่นหมายความว่าขอแค่มีค่าน้ำหนักและไบแอสก็สามารถใช้งานได้แล้ว ไม่จำเป็นต้องการอย่างอื่น
เราอาจลองเขียนโปรแกรมให้เรียนรู้ข้อมูล MNIST ด้วยคลาส ThotthoiLogistic ของเรา เสร็จแล้วก็ทำการบันทึกค่าพารามิเตอร์น้ำหนักเก็บเอาไว้ แล้วโหลดค่านี้ขึ้นมาอีกทีเพื่อใช้กับ LogisticRegression ของ sklearn
วิธีการเขียนอาเรย์ลงไฟล์อ่านได้ใน
https://phyblas.hinaboshi.com/numa39 ในการบันทึกค่าน้ำหนักนั้นอาจใช้คำสั่ง np.savetxt หรือ np.save ก็ได้
เพียงแต่ว่า np.savetxt นั้นเป็นการบันทึกในรูปของไฟล์ข้อความ ซึ่งจะสิ้นเปลืองที่เก็บมากกว่า และโหลดช้า ดังนั้นจึงไม่แนะนำ วิธีที่ดีที่สุดก็คือใช้ np.save ซึ่งเป็นการเก็บในรูปไฟล์ไบนารี
หากใช้วิธีนี้แล้วจะทั้งโหลดเร็วและใช้พื้นที่น้อย ไฟล์จะเล็กกว่าการเก็บทั้งออบเจ็กต์ด้วย เพราะว่าเก็บแค่ข้อมูลพารามิเตอร์ ไม่ต้องเก็บข้อมูลอื่นๆของคลาสหรือตัวออบเจ็กต์
ดังนั้นในที่นี้จะใช้ np.save เพื่อบันทึกเก็บค่าพารามิเตอร์
โค้ดในส่วนของการเรียนรู้
from thotthoilogistic import ThotthoiLogistic # ใช้คลาสที่เขียนขึ้นมาเอง
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
# ตรงส่วนนี้ซ้ำกับด้านบน
mnist = datasets.fetch_mldata('MNIST original')
X,z = mnist.data/255.,mnist.target
X_fuek,X_thotsop,z_fuek,z_thotsop = train_test_split(X,z,test_size=0.2)
tl = ThotthoiLogistic()
tl.rianru(X_fuek,z_fuek,X_thotsop,z_thotsop,ro=5)
np.save('mnistxz.npy',tl.w) # บันทึกค่าพารามิเตอร์
ตัวค่าพารามิเตอร์นี้ก็ถูกเก็บอยู่ในแอตทริบิวต์ .w นั่นเอง หากเราลองมาดู
print(tl.w.shape) # ได้ (785, 10)
พารามิเตอร์นี้เป็นอาเรย์สองมิติที่มีขนาดเป็น (จำนวนตัวแปรต้น+1, จำนวนกลุ่มที่แบ่ง)
ที่ +1 ก็เพราะว่านี่เป็นทั้งค่าน้ำหนักและไบแอสรวมกัน โดยข้อมูลแถวแรกที่จริงคือค่าไบแอส ส่วนแถวที่เหลือเป็นน้ำหนัก
หากจะเอาแค่น้ำหนักจะเป็น tl.w[1:] ส่วนไบแอสคือ tl.w[0]
ไบแอสจะเป็นอาเรย์มิติเดียว มีขนาดเท่ากับจำนวนกลุ่มที่แบ่ง
ค่าตัวเลขทั้งหมด (784+1)×10 = 7850 ตัวนี้ล่ะค่าที่สำคัญที่เราต้องการจริงๆ เพราะต้องเอามาใช้ในการคำนวณเพื่อทำนาย
แต่ว่า sklearn มีการแยกค่าน้ำหนักและไบแอสเป็นคนละตัวแปรกัน ดังนั้นจึงต้องแยกใส่
การนำพารามิเตอร์มาใส่ให้กับ sklearn เพื่อใช้งานทำได้ดังนี้
from sklearn import datasets
from sklearn.linear_model import LogisticRegression as Lori
import numpy as np
mnist = datasets.fetch_mldata('MNIST original')
X,z = mnist.data/255.,mnist.target
w = np.load('mnistxz.npy') # โหลดข้อมูลค่าน้ำหนักและไบแอส
lori = Lori()
lori.coef_ = w[1:].T # ป้อนค่าน้ำหนัก
lori.intercept_ = w[0] # ป้อนค่าไบแอส
lori.classes_ = np.arange(w.shape[1]) # ป้อนค่าจำนวนกลุ่มที่แบ่ง
print((lori.predict(X)==z).mean()) # ใช้ทำนายผล หาความแม่น
#print(lori.score(X,z)) # จะใช้แบบนี้ก็ได้ ค่าเท่ากัน
# ได้ 0.927857142857
ในที่นี้ .coef_ คือค่าน้ำหนัก ส่วน .intercept_ คือไบแอส นอกจากนี้จำนวนกลุ่มที่แบ่ง .classes_ ก็เป็นสิ่งที่ต้องระบุเพิ่มเข้าไปด้วย
เมื่อป้อนค่าทั้ง ๓ นี้เข้าไปแล้วก็จะสามารถใช้เมธอด predict เพื่อทำการทำนายค่าได้ทันทีโดยที่ไม่ต้องมีการใช้เมธอด fit เพื่อทำการเรียนรู้ก่อนเลย ผลการทำนายที่ได้ก็เหมือนกับที่ใช้คลาสที่สร้างขึ้นเองทำนาย
แน่นอนว่าสามารถทำกลับกัน คือเรียนรู้ด้วย sklearn แล้วก็เอาผลไปใช้ในคลาสของเราเองก็ได้
สรุปง่ายๆก็คือ สิ่งที่สำคัญที่สุดก็คือค่าพารามิเตอร์ที่เป็นผลสำเร็จจากการเรียนรู้
แน่นอนว่าการเรียนรู้อะไรสักอย่างนั้นมันเป็นเรื่องยากลำบาก หากเรียนเสร็จแล้วก็ลืมคงเป็นเรื่องน่าเศร้า ดังนั้นจึงต้องทำให้ผลของการเรียนรู้นั้นเหลือไว้
ไม่เช่นนั้นก็เปรียบเหมือนคนที่จำอะไรไม่ได้เลย ต้องมาเปิดตำราใหม่ทุกครั้งเวลาที่จะงานอะไร แบบนั้นย่อมเสียเวลา แต่ถ้าเป็นอะไรที่เรียนรู้แล้ว เข้าไปอยู่ในหัวแล้ว เวลาจะทำงานอะไรก็ทำได้ทันทีเลย
แต่คนเราเวลาเรียนหนังสือก็ไม่ได้ท่องจำหนังสือทั้งหมด แค่เอาข้อมูลและประสบการณ์ที่ได้มามาประมวลผลแล้วทำความเข้าใจปรับระบบความคิด
การถดถอยโลจิสติกก็เช่นกัน โปรแกรมไม่ได้ทำการบันทึกจดจำข้อมูลที่ป้อนเข้าไปเลย แต่ใช้ข้อมูลเพื่อปรับค่าพารามิเตอร์ให้เหมาะที่จะใช้เพื่อทำนายผลให้ถูกต้อง
เช่น ในการเรียนรู้ข้อมูล MNIST ข้อมูลที่ป้อนเข้าไปเพื่อเรียนรู้มีจำนวน 70,000×784 = 54,880,000 ตัว แต่ผลสำเร็จสุดท้ายที่โปรแกรมทำการบันทึกไว้จริงๆคือพารามิเตอร์เพียง 7850 ตัวที่เรียนรู้ได้มาเท่านั้น
ตรงนี้ก็แสดงให้เห็นถึงความที่เรียบง่ายแต่แฝงไปด้วยความสามารถของเทคนิคการเรียนรู้ของเครื่อง
อ้างอิง https://morvanzhou.github.io/tutorials/machine-learning/sklearn/3-5-save