Perceptron ตอนที่ 2 : ตัวอย่างการประยุกต์ใช้

ตอนที่ 1 ได้กล่าวถึงองค์ประกอบหลักและlearning algorithm ของ perceptron ไปแล้ว ในตอนนี้จะนำเอาความรู้ที่ได้มาเขียน Python code เพื่อใช้แยกกลุ่มข้อมูลจริงดู จะได้เห็นภาพชัดขึ้นว่า perceptron ทำงานอย่างไร

ตัวอย่างที่ 1 เรียนรู้เพื่อแยกเพศจากส่วนสูงแลน้ำหนัก
ตัวอย่างนี้จะทำให้ perceptron ทำการเรียนรู้จากข้อมูลเพื่อให้ได้ \( \vec{W} \) ที่นำมาใช้แยกเพศของตัวอย่างได้เมื่อทราบส่วนสูงและน้ำหนัก

1. download ข้อมูลจาก https://www.kaggle.com/mustafaali96/weight-height  ท่านจำเป็นต้องทำการลงทะเบียนก่อนจึงจะสามารถ download ได้ การลงทะเบียนไม่เสียเงิน  ข้อมูลบันทึกในรูปแบบของ CSV file บรรจุข้อมูลความสูง (inches) และ น้ำหนัก (bounds) ของตัวอย่าง 10,000 คน เป็นชายและหญิงกลุ่มละ 5,000  คน
 
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.utils import shuffle


# prepare data
ds = pd.read_csv("datasets_weight-height.csv")
ds = shuffle(ds)

#males
ds.loc[ds['Gender']=='Male','Gender'] = -1

#females
ds.loc[ds['Gender']=='Female','Gender'] = 1




การทำงานเริ่มจากการอ่านข้อมูล CSV file เข้ามาแล้วทำการแยกออกเป็นสองส่วนของชาย หญิงแยกกันเก็บไว้ใน males_ds และ femals_ds ตามลำดับ

นำข้อมูลมา plot แบบ scatter โดยให้แกนตั้งเป็นส่วนสูง แกนนอนเป็นน้ำหนัก พบว่าข้อมูลมีการแบ่งกลุ่มตามเพศค่อนข้างชัดเจน กลุ่มสีเขียวแทนกลุ่มเพศชายและกลุ่มสีเหลืองแทนเพศหญิง จุดสีแดงแสดงตำแหน่งของค่ากลางของแต่ละกลุ่ม ดูรูปที่ 1

รูปที่ 1

ขั้นตอนต่อไปคือการสร้าง dataset 2 dataset จากข้อมูลต้นฉบับ dataset แรกเอาไว้สอน perceptron จำนวน 80 % ของทั้งหมด ที่เหลือเอามาสร้างเอาไว้ทดสอบ perceptron ว่าสามารถใช้แยกข้อมูลได้จริงหรือไม่ เหตุที่ต้องทำการแยกข้อมูลออกจากกันเพราะเราไม่ต้องการให้ perceptron ได้รู้จักข้อมูลในชุดที่ใช้ทดสอบนั้นเอง

#create training dataset 
size = len(ds)
training_size = round(size * 0.8)

X_train = ds.loc[:training_size,['Heigh','Weight']]
l_train = ds.loc[:training_size,'Gender']

สร้าง perceptron class จุดที่ต้องขยายความคือ
  • input_size คือ dimension หรือจำนวน features ของ input data เช่น input ที่ประกอบด้วยค่าของ ส่วนสูงและน้ำหนัก ก็จะมี input_size เป็น 2 เป็นต้น
  • dimension ของ self.W ต้องมากกว่า input_size อยู่ 1 เพราะใช้แทนตำแหน่งของ bias (ดูเรื่อง linear classifier)

class Perceptron(object):

    def __init__(self, input_size, lr=1, epochs=100):
        self.W = np.zeros(input_size+1)
        self.epochs = epochs
        self.lr = lr
    
    def activation_fn(self, x):
        return 1 if x >= 0 else -1
 
    def predict(self, x):
        z = self.W.T.dot(x)
        a = self.activation_fn(z)
        return a
    
    def fit(self, X, d):
        for epoch in range(self.epochs):
            i = np.random.choice(X.shape[0])
            x = X[i]
            e = 0
            x = np.insert(x, 0, 1)
            y = self.predict(x)
            e = d[i] - y
            self.W = self.W + self.lr * e * x

เริ่มต้นการเรียนรู้

# learning 
# input_size parameter is dimension of input
perceptron = Perceptron(input_size=2,lr=1.0, epochs=80000) 
perceptron.fit(np.array(X_train), np.array(l_train))

# print what we want to know
print(perceptron.W)

[  188.          8804.98328662 -3648.86955358]

ค่า parameter ที่ได้ทำให้เราทราบว่าสมการที่ช่วยแยกเพศคือ
\[ \begin{align*} g &= 188 + 8804.98h - 3648.87w \\\\ \hat{y} &= \begin{cases} 1 & \quad \text{ if g} \geq 0 \\ -1 & \quad \text{ otherwise} \end{cases}\\\\ gender &= \begin{cases} \text{female} & \quad \text{ if }\hat{y} = 1 \\ \text{male} & \quad \text{ if }\hat{y} = -1 \end{cases} \end{align*} \]
เมื่อ h = ความสูง (inches) และ w = น้ำหนัก (bound) ยกตัวอย่างข้อมูล {'gender':'Male','Height': 73.847017,'Weight': 241.893563}
\[ \begin{align*} g &= 188 + 8804.98 \times 73.847017 - 3648.87 \times 241.893563 \\ \hat{y} &= sign(-232228.6574791501) = -1 \\ \therefore gender &= \text{male} \end{align*} \]

นำสมการที่ได้ไป plot ดูจะได้ตามรูปที่ 2 คือสมการเส้นตรงดูแล้วน่าจะเหมาะสมกับการแบ่งกลุ่มข้อมูล นำมาทดสอบกับข้อมูลที่เตรียมไว้แล้วดูผล

X_test = np.array(ds.loc[testing_size:,['Height','Weight']])
l_test = np.array(ds.loc[testing_size:,'Gender'])
corect = 0
for i in range(X_test.shape[0]):
    x = np.insert(X_test[i],0,1) # insert 1 to dimension 0
    pred = perceptron.predict(x)
    if pred == l_test[i] :
        corect+=1

print(corect/test_size)

0.9195

ตัวเลข 0.9195 ตีความได้ว่าตัวแบบสามารถแยกเพศจากข้อมูลความสูงและน้ำหน้กได้ถูกต้องประมาณ 91 % ของตัวอย่างที่นำมาทดสอบจำนวน 2000 ตัวอย่าง

รูปที่ 2


  ตัวอย่างที่ 2  : เรียนรู้เพื่อช่วยวินิจฉัย Breast cancer
ข้อมูลนี้เป็นของ University of Wisconsin Hospitals, Madison โดย Dr. William H. Wolberg  จากเว็บ https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/ ในเว็บจะมีไฟล์หลายไฟล์ เลือกดาวน์โหลด 2 ไฟล์คือ 
หมายเหตุ หลังจาก download file มาแล้ว ผมเปลี่ยนนามสกุลไฟล์จาก .data ไปเป็น .csv เพราะว่าได้นำมาเติม column header เข้าไปเพื่อความสะดวกในการทำเป็น pandas.dataFrame
Variable nameFull NameDomain
CT Clump Thickness1-10
UCSi Uniformity of Cell Size1-10
UCSh Uniformity of Cell Shap1-10
MAd Marginal Adhesion1-10
SECS Single Epithelial Cell Size1-10
BNu Bare Nuclei1-10
BCh Bland Chromatin1-10
NNu Normal Nucleoli1-10
NNu Mitoses 1-10
Variable nameFull NameDomain
CT Clump Thickness1-10
UCSi Uniformity of Cell Size1-10
UCSh Uniformity of Cell Shap1-10
MAd Marginal Adhesion1-10
SECS Single Epithelial Cell Size1-10
BNu Bare Nuclei1-10
BCh Bland Chromatin1-10
NNu Normal Nucleoli1-10
NNu Mitoses 1-10
  
bc_ds = pd.read_csv("breast-cancer-wisconsin.csv")

# ID is not needed
bc_ds = bc_ds.drop(columns=['ID'])

# shuffer dataframe
bc_ds = shuffle(bc_ds)

# show sample data
bc_ds.head()
  
  

จะเห็นว่าข้อมูลมี 9 features ซึ่งไม่สามารถนำไปทำ scatter plot ได้ในคราวเดียวอย่างตัวอย่างแรก (ต้องแยกไป plot ทีละคู่) ใน column  สุดท้าย เป็นข้อมูลระบุ class ของแต่ละรายการ มีเพียงสอง class คือ 4 = malignant case และ 2 = benign case จึงสามารถนำเอา perceptron มาใช้ได้ โดยจะทำการเปลี่ยนตัวเลขให้สอดคล้องกับตัวแบบคือ class 2 เปลี่ยนเป็น  -1 และ class 4 เป็น 1 แล้วทำการแยกส่วนข้อมูลเป็น training dataset และ testing dataset

bc_ds.loc[bc_ds['Classes']==2,'Classes'] = -1
bc_ds.loc[bc_ds['Classes']==4,'Classes'] = 1
cols = ['CT','UCSi','UCSh','MAd','SECS','BNu','BCh','NNu','Mitoses']
train_size = round(len(bc_ds) * 0.8)

X_train = np.array(bc_ds.loc[:train_size,cols])
l_train = np.array(bc_ds.loc[:train_size,'Classes'])
X_test = np.array(bc_ds.loc[train_size:,cols])
l_test = np.array(bc_ds.loc[train_size:,'Classes'])


  
Training กำหนดให้ทำการ train 80000 รอบ
    
# learning 
bc_perceptron = Perceptron(input_size=9,lr=1.0, epochs=80000)
bc_perceptron.fit(X_train, l_train)

# print what we want to know
print(bc_perceptron.W)

[-774.   36.   -4.    8.   46.  -18.   42.   40.   34.   72.]
ค่า parameter ที่ได้ทำให้เราทราบว่าสมการที่ใช้แยกข้อมูลระหว่าง malignant กับ benign คือ
\[ \begin{align*} c &= 36CT-4UCSi+8UCSh+46MAd-18SECS+42BNu +40BCh+34NNu+72NNu - 774 \\\\ \hat{y} &= \begin{cases} 1 & \quad \text{if c} \geq 0 \\ -1 & \quad \text{otherwise} \end{cases} \\\\ class &= \begin{cases} \text{benign} & \quad \text{if }\hat{y} = -1 \\ \text{malignant} & \quad \text{if }\hat{y} = 1 \end{cases} \end{align*} \]
ยกตัวอย่าง ถ้าผู้ป่วยรายหนึ่งมีข้อมูลดังนี้
Variable nameFull NameValue
CT Clump Thickness8
UCSi Uniformity of Cell Size7
UCSh Uniformity of Cell Shap4
MAd Marginal Adhesion4
SECS Single Epithelial Cell Size5
BNu Bare Nuclei3
BCh Bland Chromatin5
NNu Normal Nucleoli10
NNu Mitoses 1
\[ \begin{align*} c &= sign( (36\times 8)- (4 \times 7) + (8 \times 4) + (46 \times 4)- (18 \times 5) + (42 \times 3) + (40\times 5)+ (34 \times 10)+ (72 \times 1) - 774) \\ c &= sign(350) = 1 \\ \therefore class &= \text{malignant} \end{align*} \]
ทดสอบตัวแบบกับ testing dataset
    
# testing
bc_test = np.array(bc_ds.loc[train_size:,cols])
bc_label = np.array(bc_ds.loc[train_size:,'Classes'])
bc_test = np.insert(bc_test,0,1,axis=1)
correct = 0
for i in range(bc_test.shape[0]):
    pre = bc_perceptron.predict(bc_test[i])
    if bc_label[i] == pre :
        correct += 1
print(correct/bc_test.shape[0])   

0.975609756097561
ได้ความแม่นยำประมาณ 97 % ถือว่าดีทีเดียว

สรุป
 ในตอนนี้เป็นการนำเอาความรู้ในเรื่อง perceptron มาใช้ทำ binary classification ของข้อมูลจริงในสองรูปแบบคือแบบที่มี feature เพียงสอง คือ ส่วนสูงกับน้ำหนัก และอีกแบบคือข้อมูลที่มี feature มากกว่า 2 จะเห็นได้ว่า perceptron ทำงานได้ดีในรูปแบบปัญหา binary classification แต่ในรูปแบบปัญหาที่ซับซ้อนกว่านี้ลำพังตัวแบบ perceptron จะไม่สามารถใช้งานได้ จึงต้องมีการกล่าวถึงในตอนต่อไปคือ multiplayer perceptron 

ความคิดเห็น