φυβλαςのβλογ
บล็อกของ phyblas



[python] การวิเคราะห์องค์ประกอบหลักแบบเคอร์เนล
เขียนเมื่อ 2018/07/30 18:43
แก้ไขล่าสุด 2022/07/19 05:22
ในตอนที่แล้วได้เขียนถึงการวิเคราะห์องค์ประกอบหลัก (主成分分析, principle component Analysis, PCA) ไปแล้ว https://phyblas.hinaboshi.com/20180727

สำหรับตอนนี้จะอธิบายการนำวิธีการเคอรเนลมาใช้ร่วมกับการวิเคราะห์องค์ประกอบหลัก เรียกว่าการวิเคราะห์องค์ประกอบหลักแบบเคอร์เนล (核主成分分析, kernel principal component analysis, kernel PCA)

ในการวิเคราะห์องค์ประกอบหลักแบบดั้งเดิมนั้นเป็นแค่การหมุนแกน การเปลี่ยนแปลงทั้งหมดเป็นแบบเชิงเส้น ดังนั้นไม่ว่าจะแปลงยังไง ข้อมูลก็จะยังคงมีหน้าตาเดิม ไม่เปลี่ยนแปลงไปนัก

ดังนั้นวิธีนี้จึงใช้ไม่ได้ผลกับข้อมูลที่ไม่สามารถแบ่งเป็นเชิงเส้นได้

ตัวอย่างเช่นข้อมูลรูปจันทร์เสี้ยวซึ่งเคยยกตัวอย่างไปใน https://phyblas.hinaboshi.com/20171202
import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
X,z = datasets.make_moons(500,noise=0.05,random_state=0)
plt.axes(aspect=1)
plt.scatter(X[:,0],X[:,1],c=z,edgecolor='k',cmap='winter')
plt.show()


ข้อมูลแบบนี้ไม่ว่าจะหมุนแกนยังไงก็ไม่มีทางแยกข้อมูลให้อยู่ในแกนเดียวได้

แต่หากนำวิธีการเคอร์เนลมาประยุกต์ใช้กับการวิเคราะห์องค์ประกอบหลักก็จะทำให้สามารถแปลงข้อมูลแบบไม่ใช่เชิงเส้นได้

เรื่องแนวคิดพื้นฐานของวิธีการเคอร์เนลเขียนเอาไว้ใน https://phyblas.hinaboshi.com/20180724

ดังนั้นในที่นี้จะไม่พูดถึงรายละเอียดมากแต่จะหยิบมาใช้

ในการวิเคราะห์องค์ประกอบหลักแบบดั้งเดิมนั้นเราจะคำนวณความแปรปรวนร่วมเกี่ยวของจุดข้อมูลต่างๆโดยพิจารณาแต่ละมิติ คือ
..(1)

โดย i และ j เป็นดัชนีมิติของข้อมูล ส่วน k คือดัชนีลำดับข้อมูล โดยมีข้อมูลอยู่ n ตัว

หรือเขียนในรูปผลคูณภายในได้เป็น
..(2)

โดยในที่นี้เวกเตอร์ของ x หมายถึงค่าในมิตินึงของข้อมูลทั้งหมด
..(3)

แต่ในการวิเคราะห์องค์ประกอบหลักแบบเคอร์เนลจะเปลี่ยนแนวทางในการพิจารณาใหม่ คือจะคำนวณความแปรปรวนร่วมเกี่ยวของฟังก์ชันฐานต่างๆโดยพิจารณาแต่ละจุดข้อมูล
..(4)

ในที่นี้ i และ j เป็นดัชนีลำดับข้อมูล ส่วน k คือดัชนีลำดับของฟังก์ชันฐาน และเวกเตอร์ x ในที่นี้คือค่าของข้อมูลตัวนึงในทุกมิติ
..(5)

เมื่ออาศัยความรู้เรื่องวิธีการเคอร์เนลแล้ว จะรู้ว่าผลคูณภายในของฟังก์ชันฐานสามารถเขียนแทนในรูปของเคอร์เนลได้
..(6)

ดังนั้นการคำนวณความแปรปรวนร่วมเกี่ยวก็คือการคำนวณเคอร์เนลนั่นเอง ในที่นี้จึงใช้เคอร์เนลในทำนองเดียวกับที่เคยพิจารณาความแปรปรวนร่วมเกี่ยว

ส่วนเวลาคำนวณเพื่อเปลี่ยนพิกัดนั้น หากเป็นการวิเคราะห์องค์ประกอบแบบดั้งเดิมจะคำนวณโดยเอาค่าเดิมมาคูณกับเวกเตอร์ลักษณะเฉพาะ
..(7)

แต่ว่าเมื่อใช้เคอร์เนล จะเปลี่ยนเป็นคำนวณจากเคอร์เนลแทน แบบนี้
..(8)

โดย
..(9)
..(10)

ในที่นี้ m คือจำนวนมิติของข้อมูลหลังแปลงแล้ว ซึ่งจริงๆแล้ว m ควรจะมีจำนวนเท่ากับจำนวนข้อมูล นั่นคือ m=n เพียงแต่ถ้าเป็นแบบนั้นเท่ากับเป็นการเพิ่มมิติ โดยทั่วไปจะเลือกแค่ตัวที่มีค่าลักษณะเฉพาะสูงสุดอันดับต้นๆ

ส่วน x เป็นเมทริกซ์ของข้อมูลทั้งหมดในทุกมิติ
..(11)

ให้เมทริกซ์ x' เป็นเมทริกซ์ของข้อมูลชุดใหม่ และ ξ(x') เป็นเมทริกซ์ของข้อมูลใหม่หลังแปลง
..(12)

ค่าหลังแปลงจะคำนวณได้จาก
..(13)

โดย K เป็นเมทริกซ์ของเคอร์เนลที่คำนวนระหว่างข้อมูลใหม่กับข้อมูลที่ใช้เรียนรู้
..(14)

ค่าหลังแปลงแล้วจะคำนวณได้เป็น
..(15)

ทีนี้ เช่นเดียวกับตอนที่ทำการวิเคราะห์องค์ประกอบแบบดั้งเดิม เราสามารถตั้งสมการปัญหาเวกเตอร์ลักษณะเฉพาะได้
..(16)

โดย K(x,x) คือเคอร์เนลที่คำนวณภายในชุดข้อมูลที่ใช้ในการเรียนรู้

ส่วน Λ คือเมทริกซ์ของค่าลักษณะเฉพาะ
..(17)

และ V* คือ เมทริกซ์ของเวกเตอร์ลักษณะเฉพาะ

ในที่นี้ทำการคำนวณหาเวกเตอร์และค่าลักษณะเฉพาะของ K(x,x) ก็จะได้ Λ และ V* ออกมา การคำนวณหาค่าตรงนี้ใช้ np.linalg.eigh ได้เช่นเดียวกัน

จากนั้นเมทริกซ์ V จากสมการ (13) ก็คำนวณได้จาก
..(18)

แล้วจึงได้ว่า
..(19)

สำหรับ ξ ของ x ที่เป็นข้อมูลที่ใช้เรียนรู้นั้นจะได้ว่า
..(20)

นั่นคือตัวเวกเตอร์ลักษณะเฉพาะที่หาค่าได้จะเป็นค่าหลังแปลงของข้อมูลที่ใช้เรียนรู้ไปโดยทันที ดังนั้นถ้าแค่จะคำนวณเพื่อหาในพิกัดใหม่ของข้อมูลที่ใช้เรียนรู้ละก็ แค่หาเวกเตอร์ลักษณะเฉพาะได้ก็จบเลย แต่ถ้าต้องการหาชุดข้อมูลใหม่ก็ต้องคำนวณเคอร์เนลใหม่แล้วก็นำไปคูณตามสมการ (19)



ส่วนสำคัญที่ต้องเสริมอีกคือ K ในที่นี้ไม่ใช่เคอร์เนลที่คำนวณจากฟังก์ชันเคอร์เนลธรรมดา แต่ต้องมีการปรับเข้าศูนย์กลาง (中心化, centering)

ให้ K* เป็นค่าที่ได้จากการคำนวณโดยเคอร์เนลฟังก์ชันธรรมดา ยังไม่ได้ปรับเข้าศูนย์กลาง จะคำนวณค่า K ที่ได้หลังจากปรับเข้าศูนย์กลางได้ดังนี้
..(21)

เมทริกซ์ของเคอร์เนลที่ปรับเข้าศูนย์กลางแล้วสามารถคำนวณได้เป็น
..(22)

โดย 1n คือเมทริกซ์ที่ค่าของทุกตัวเป็น 1/n

ในไพธอนอาจเขียนโค้ดได้ในลักษณะนี้
n = K.shape[0]
_1n = np.ones((n,n))/n
_1nK = _1n.dot(K)
K = K - _1nK - K.dot(_1n) + _1nK.dot(_1n)

อย่างไรก็ตาม สามารถคำนวณในลักษณะเดียวกันนี้ได้โดยใช้ KernelCenterer ของ sklearn ซึ่งจะได้ผลเหมือนกันแต่เร็วขึ้นมาก
from sklearn.preprocessing import KernelCenterer
kc = KernelCenterer()
K = kc.fit_transform(K)


โดยรวมแล้ว ข้อแตกต่างที่สำคัญระหว่างการวิเคราะห์องค์ประกอบหลักแบบใช้เคอร์เนลกับไม่ใช้ก็คือ สิ่งที่พิจารณาในแบบเคอร์เนลจะไม่ใช่ความแปรปรวนระหว่างแต่ละมิติ แต่เป็นความแปรปรวนระหว่างฟังก์ชันฐาน และเวกเตอร์ลักษณะเฉพาะและค่าลักษณะเฉพาะจะมีจำนวนเท่ากับจำนวนข้อมูล แทนที่จะเป็นจำนวนมิติ



โปรแกรม

ลองนำวิธีการมาใช้กับข้อมูลรูปพระจันทร์เสี้ยวที่ยกเป็นตัวอย่างมาข้างต้น
from sklearn.preprocessing import KernelCenterer
from scipy.spatial.distance import cdist

def gauss(X1,X2):
    return np.exp(-gamma*cdist(X1,X2,'sqeuclidean'))

X,z = datasets.make_moons(500,noise=0.05,random_state=0)
gamma = 20
K = gauss(X,X)
kc = KernelCenterer()
K = kc.fit_transform(K) # ปรับเข้าศูนย์กลาง
kha_eig,vec_eig = np.linalg.eigh(K)
Xi = vec_eig[:,::-1][:,:2]
a = kha_eig[::-1][:2]
V = Xi/a
plt.scatter(Xi[:,0],Xi[:,1],c=z,edgecolor='k',cmap='winter')
plt.show()


ผลที่ได้จะเห็นว่าข้อมูลทั้งสองกลุ่มถูกแยกตามแกนนอนได้ ซึ่งทำให้สามารถลดปัญหาเหลือมิติเดียวได้

เมื่อต้องการแปลงพิกัดของข้อมูลชุดอื่นที่ไม่ได้ถูกรวมอยู่ในชุดที่ใช้ตอนแรกก็คำนวณเคอร์เนลแล้วคูณกับ V

เพื่อให้แน่ใจว่าตำแหน่งถูกต้อง ลองให้ข้อมูลใหม่มาจากข้อมูลเก่าส่วนหนึ่ง คำนวณตำแหน่งจุดใหม่วาดซ้อนลงไป
X2,z2 = X[:20],z[:20]
# คำนวณเคอร์เนลแล้วปรับเข้าศูนย์กลางแล้วจึงคูณกับ V
Xi2 = kc.transform(gauss(X2,X)).dot(V)
plt.scatter(Xi[:,0],Xi[:,1],c=z,edgecolor='k',cmap='winter')
plt.scatter(Xi2[:,0],Xi2[:,1],150,c='r',marker='*',edgecolor='k')
plt.show()


ตำแหน่งที่ได้ตรงกันดี แสดงว่าการคำนวณเป็นไปตามที่ควรจะเป็น



เขียนเป็นคลาส

ต่อมาลองเรียบเรียงเขียนใหม่ในรูปแบบคลาส
class WikhroOngprakopLakKernel:
    def __init__(self,gamma,m=None):
        self.gamma = gamma
        self.m = m
        self.kc = KernelCenterer()

    def rianru(self,X):
        self.X = X
        if(self.m): m = self.m
        else: m = X.shape[0]
        K = self.kernel(X,X)
        K = self.kc.fit_transform(K)
        kha_eig,vec_eig = np.linalg.eigh(K)
        Xi = vec_eig[:,::-1][:,:m]
        self.a = kha_eig[::-1][:m]
        self.V = Xi/self.a
        return Xi

    def plaeng(self,X):
        return self.kc(self.kernel(X,self.X)).dot(self.V)

    def kernel(self,X1,X2):
        return np.exp(-self.gamma*cdist(X1,X2,'sqeuclidean'))

เนื่องจากเวกเตอร์ลักษณะเฉพาะที่คำนวณได้ก็คือค่าในพิกัดใหม่ของข้อมูลที่ใช้เรียนรู้อยู่แล้ว ดังนั้นจึงให้คืนค่านั้นกลับไปในขั้นตอนการเรียนรู้เลย

ลองทดสอบกับข้อมูลไวน์ (https://phyblas.hinaboshi.com/20171207)
gamma = 0.07
m = 2
w = datasets.load_wine()
X,z = w.data,w.target
X = (X-X.mean(0))/X.std(0) # แปลงข้อมูลให้เป็นมาตรฐาน
wol = WikhroOngprakopLakKernel(gamma=gamma,m=m)
Xi = wol.rianru(X)
plt.axes(aspect=1)
plt.scatter(Xi[:,0],Xi[:,1],c=z,cmap='jet',edgecolor='k')
plt.show()
plt.figure()


หากเทียบกับตอนใช้การวิเคราะห์องค์ประกอบแบบเชิงเส้นธรรมดา ผลที่ได้จะดูออกมาดีกว่า

เพียงแต่ว่าการเลือกค่า γ ให้เหมาะสมเป็นเรื่องที่สำคัญ เพราะมีผลต่อผลลัพธ์ที่จะได้มาก



เปรียบเทียบผลของค่า γ

ลองใช้ข้อมูลกลุ่มรูปไข่ดาวเป็นตัวอย่างเพื่อเปรียบเทียบผลของค่า γ ที่ต่างกัน (รายละเอียดเกี่ยวกับชุดข้อมูลนี้ https://phyblas.hinaboshi.com/20180716)
n = 200
X,z = datasets.make_circles(n,factor=0.2,noise=0.1)
plt.axes(aspect=1)
plt.scatter(X[:,0],X[:,1],c=z,alpha=0.4,cmap='coolwarm',edgecolor='k')

plt.figure(figsize=[5,9])
for i,gamma in enumerate(np.logspace(-1,4,6)):
    plt.subplot(321+i,aspect=1,title='$\\gamma=%.1f$'%gamma)
    wol = WikhroOngprakopLakKernel(gamma=gamma,m=2)
    Xi = wol.rianru(X)
    plt.scatter(Xi[:,0],Xi[:,1],c=z,alpha=0.4,cmap='coolwarm',edgecolor='k')
plt.tight_layout()
plt.show()



จะเห็นว่าผลที่ได้ต่างกันออกไปมาก จึงควรเลือกค่า γ ให้เหมาะสม



อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> numpy
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> matplotlib
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> sklearn

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文