φυβλαςのβλογ
phyblas的博客



[python] วิเคราะห์การถดถอยโดยใช้วิธีการเคอร์เนล
เขียนเมื่อ 2018/07/24 22:28
แก้ไขล่าสุด 2021/09/28 16:42
วิธีการเคอร์เนล (核方法, kernel method) หรือนิยมเรียกกันว่าลูกเล่นเคอร์เนล (核技巧, kernel trick) เป็นวิธีการที่ใช้อย่างกว้างขวางในเทคนิคการเรียนรู้ของเครื่อง

สำหรับบทความนี้จะมาอธิบายหลักการและการใช้วิธีการเคอร์เนลโดยละเอียด



หลักการ

การวิเคราะห์ปัญหาที่ไม่เป็นเชิงเส้นนั้นมีความซับซ้อน โดยทั่วไปหากสามารถเปลี่ยนปัญหาที่พิจารณาให้เป็นเชิงเส้นได้จะสามารถคิดอะไรๆได้ง่ายขึ้น

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

ภาพตัวอย่างคร่าวๆ ด้านบนคือปริภูมิเดิมของข้อมูลที่เราพิจารณาอยู่ตอนแรก ส่วนภาพล่างคือจุดข้อมูลเหล่านั้นภายในปริภูมิใหม่ สีเดียวกันกับด้านบนแสดงถึงจุดเดียวกันถูกแปลงมาเป็นจุดนั้นในด้านล่าง



ในการใช้วิธีการเคอร์เนล ปริภูมิใหม่ที่ว่านี้เรียกว่าเป็นปริภูมิของเคอร์เนล ค่าในปริภูมิเคอร์เนลเป็นฟังก์ชันของจุดต่างๆในปริภูมิเดิม

ปริภูมิของเคอร์เนลที่ว่านี้ ถ้าจะพูดโดยละเอียดจริงๆก็คือเป็นปริภูมิชนิดที่เรียกว่า ปริภูมิฮิลแบร์ทเคอร์เนลเกิดซ้ำ (再生核希尔伯特空间, reproducing kernel Hilbert space)

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

วิธีการเคอร์เนลมีพื้นฐานมาจากการใช้ฟังก์ชันฐาน (基函数, basis function) ซึ่งเขียนถึงไปใน https://phyblas.hinaboshi.com/20180720

เนื้อหาในนี้จะต่อเนื่องจากพื้นฐานในบทความนั้น สมการต่างๆก็ยกมาจากในนั้นโดยอาจจะไม่ได้อธิบายซ้ำโดยละเอียดนัก ดังนั้นควรอ่านบทความนั้นมาก่อน

ปริภูมิเคอร์เนลได้จากการแปลงต่อมาจากปริภูมิของฟังก์ชันฐานลักษณะเฉพาะ ϕ อีกที

โดยนิยามฟังก์ชันใหม่ K เรียกว่าเป็นฟังก์ชันเคอร์เนล หรืออาจเรียกว่าเคอร์เนลเฉยๆ
..(1)

K เกิดจากผลคูณเวกเตอร์ ϕ ในปริภูมิของ ϕ หรือโดยทั่วไปมักเรียกว่าเป็นผลคูณภายใน (内积, inner product) ของปริภูมิ ϕ บางครั้งก็นิยมเขียนแทนด้วยกรอบลักษณะแบบนี้
..(2)

โดยทั่วไปถ้าปริภูมิ ϕ มีจำนวนมิติจำกัด เช่นมีจำนวนมิติ m มิติ ผลคูณภายในจะได้ว่า
..(3)

แต่ในวิธีการเคอร์เนลเรามักจะให้ ϕ มีจำนวนมิติเป็นอนันต์ ซึ่งผลคูณภายในจะเป็นการหาปริพันธ์แบบนี้
..(4)

มีมิติเป็นจำนวนอนันต์นี่เป็นยังไง คิดว่าอาจจะเป็นเรื่องที่จินตนาการได้ยาก ขอยกตัวอย่างฟังก์ชัน เช่นกรณีที่ฐาน ϕ เป็นฟังก์ชันเกาส์
..(5)

ในที่นี้ ξ จะหมายถึงจุดที่ใช้เป็นศูนย์กลาง ซึ่งตำแหน่งนี้อาจเป็นจุดใดๆบนปริภูมิของตัวแปรต้น x ดังนั้นจึงมีจำนวนเป็นอนันต์ จึงเรียกได้ว่าปริภูมิของ ϕ มีจำนวนมิติเป็นอนันต์

แบบนี้ผลคูณภายในจะกลายเป็น
..(6)

จะเห็นว่าเมื่อใช้ฟังก์ชันฐานเป็นฟังก์ชันเกาส์ ก็จะได้เคอร์เนลออกมาเป็นฟังก์ชันเกาส์ในลักษณะเดิม

เขียนเคอร์เนลเกาส์ใหม่ในรูปทั่วไปได้เป็น
..(7)

โดย
..(8)

โดย σ แสดงถึงขนาดความกว้าง ปกติฟังก์ชันโดยพื้นฐานแล้วจะถูกเขียนในรูป σ เพราะเห็นภาพชัดกว่า แต่เพื่อให้เขียนง่ายในที่นี้ใช้รูป γ

โดยทั่วไปเคอร์เนลเกาส์แบบนี้มักถูกเรียกว่า เคอร์เนลฟังก์ชันฐานแนวรัศมี (径向基函数核, Radial basis function kernel) และนิยมเรียกย่อๆว่า RBF

เพียงแต่ว่ากันในรายละเอียดจริงๆแล้ว RBF หมายถึงฟังก์ชันอะไรก็ได้ที่ใช้ฐานเป็นฟังก์ชันที่มีจุดศูนย์กลางและมีค่าเปลี่ยนแปลงตามระยะห่างจากใจกลาง ดังนั้นอาจไม่ใช่ฟังก์ชันเกาส์ เพียงแต่ปกติฟังก์ชันเกาส์ใช้กันทั่วไปมากที่สุด ดังนั้นพอพูดถึง RBF ก็จะหมายถึงฟังก์ชันเกาส์

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

ในปัญหาการวิเคราะห์การถดถอยจะคำนวณเคอร์เนล K ระหว่างค่าใหม่ x ที่ต้องการหาค่ากับ x เก่าที่เป็นข้อมูลที่มี แล้วนำมาคำนวณ
..(9)

ในที่นี้เขียนห้อย B หมายถึงข้อมูลกลุ่มใหม่ที่ต้องการหาค่า ห้อย A หมายถึงกลุ่มเก่าซึ่งรู้ค่า z อยู่แล้วและใช้ในการเรียนรู้

ที่ใช้สัญลักษณ์เป็นเวกเตอร์ในที่นี้หมายถึงว่ามีข้อมูล x หลายตัว

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

ถ้าแจกเป็นเมทริกซ์ให้เห็นภาพชัดจะได้แบบนี้
..(10)

โดย n เป็นจำนวนข้อมูลเก่า n' เป็นจำนวนข้อมูลกลุ่มใหม่

ค่าของแต่ละตัวจะได้เป็น
..(11)

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

ค่า a คำนวณจากเคอร์เนลภายในตัวข้อมูลเก่าบวกกับเรกูลาไรซ์
..(12)

โดย λ คือขนาดของการเรกูลาไรซ์

ส่วนที่มาของค่า a ถ้าจะเข้าใจอาจต้องอธิบายย้อนกลับไปนิด

เดิมทีในการใช้ฟังก์ชันฐานลักษณะเฉพาะในการคำนวณนั้น เราจะอธิบายค่าของฟังก์ชัน z ที่เราต้องการหาในรูปของฟังก์ชันฐาน ϕ(x) คูณกับน้ำหนัก w
..(13)

เมื่อมีจุดข้อมูลอยู่หลายค่า ค่า x และ z ก็มีอยู่หลายตัว อาจแสดงได้ในรูปเมทริกซ์ดังนี้
..(14)

โดยในที่นี้ m เป็นจำนวนฐาน และ n เป็นจำนวนจุดข้อมูลที่ใช้

ค่าน้ำหนัก w นี้เป็นค่าที่ต้องคำนวณหาเอาจากข้อมูลค่า x และ z ที่ป้อนเข้าไปเป็นตัวอย่างข้อมูลสำหรับการเรียนรู้

จากเมทริกซ์ข้างต้นนี้จะได้ระบบสมการทั้งหมด n สมการ และมีค่าที่ต้องการหาคือ w ทั้งหมด m ตัว

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

ดังนั้นเวลาแก้หาค่า w จะเป็นการหาโดยพิจารณาจากการทำให้ค่าความคลาดเคลื่อนต่ำสุด นั่นคือ
..(15)

แล้วก็จะได้ว่า
..(16)

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

ดังนั้นตรงนี้จะกลายมาเป็นปัญหาที่ว่าให้หาค่า w ที่มีขนาดต่ำสุด หรือก็คือผลรวมกำลังสองต่ำที่สุด
..(17)

การแก้หาค่าอาจทำโดยใช้ตัวคูณลากรองจ์ (拉格朗日乘数, Lagrange multipliers) สำหรับวิธีการจะขอละไว้ แต่ผลที่ได้จะออกมาเป็น
..(18)

จะเห็นว่าสมการ (16) กับ (18) อาจดูคล้ายกันแต่ก็ต่างกัน ที่สำคัญคือ ΦΦT จะได้เมทริกซ์ขนาด n×n ในขณะที่ ΦTΦ จะเป็นเมทริกซ์ขนาด m×m

ถ้านำ w ที่ได้จากตรงนี้ไปใช้เพื่อคำนวณหาค่า z ใหม่สำหรับค่า x ใหม่โดยใช้สมการ (14) จะได้ว่า
..(19)

และจะเห็นว่าฟังก์ชันฐานที่ปรากฏในนี้อยู่ในรูปของเวกเตอร์คูณกัน ซึ่งก็คือผลคูณภายใน ผลคูณภายในของฟังก์ชันฐานก็คือเคอร์เนล ดังนิยามในสมการ (1) ดังนั้นจะได้
..(20)

ซึ่งก็กลับไปสู่สมการ (11) แล้วก็จะได้ค่า a ตามสมการ (12)

จะเห็นว่า ϕ ทั้งหมดถูกเขียนในรูปของ K ได้หมด นั่นหมายความว่าเราไม่จำเป็นต้องคำนวณฟังก์ชัน ϕ โดยตรงแต่คำนวณ K ซึ่งเป็นผลคูณภายในของ ϕ

แนวคิดตรงนี้มีประโยชน์มาก เพราะเมื่อมีจำนวนฟังก์ชันฐานเยอะมาก แบบนั้นถ้าเราต้องมาคำนวณ ϕ ออกมาจำนวนขนาดนั้น ปริมาณการคำนวณจะหนักมาก

แต่พอกลายเป็นว่าแค่คำนวณเคอร์เนล K ก็พอแล้ว ไม่ต้องคำนวณ ϕ โดยตรงแล้ว ต่อให้จำนวนฐานมีมากแค่ไหน จำนวนฐานอาจมีมากถึงเป็นอนันต์ ก็ไม่ทำให้ต้องคำนวณหนัก

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

ลองเขียนสรุปลำดับการคำนวณแล้วก็จะออกมาแบบนี้

.*.

แต่ขั้นตอน Φ นั้นถูกละไปไม่ต้องคำนวณโดยตรง จาก x คำนวณ K เลย นี่คือวิธีการของลูกเล่นเคอร์เนล



โปรแกรม

เมื่อเข้าใจหลักการแล้วต่อมาลองเขียนโปรแกรมขึ้นมาดู

การคำนวณก็เป็นไปตามขั้นตอนนี้
1. คำนวณเคอร์เนล (K) ภายในตัวแปรต้นของข้อมูลที่จะเรียนรู้ (x)
2. ใช้ K และตัวแปรตามของข้อมูลที่จะเรียนรู้ (z) คำนวณค่าน้ำหนัก (a)
3. คำนวณเคอร์เนล (K_) ของข้อมูลที่ต้องการหาค่า (x_) และข้อมูลที่เรียนรู้ (x)
3. ใช้ K_ และ a คำนวณตัวแปรตามที่ต้องการหาค่า (z_) ของข้อมูลที่ต้องการหาค่า

ลองเขียนโค้ดได้แบบนี้
import numpy as np
import matplotlib.pyplot as plt

def gauss(x1,x2):
    return np.exp(-gamma*(x1-x2)**2)

np.random.seed(3)
n = 30
x = np.random.uniform(0,1,n) # จุดข้อมูลที่ใช้เรียนรู้
z = np.sin(x*9)*np.exp(-2*x)+np.random.normal(0,0.04,n)

gamma = 10 # γ
l = 0.02 # λ
K = gauss(x[:,None],x)
a = np.linalg.solve(K+np.eye(n)*l,z)
x_ = np.linspace(x.min(),x.max(),401) # จุดที่ต้องการหาค่า
K_ = gauss(x_[:,None],x)
z_ = K_.dot(a)

plt.scatter(x,z,c='y',edgecolor='g')
plt.plot(x_,z_,'m')
plt.show()



ต่อมาลองเขียนให้อยู่ในรูปของคลาส
class ThotthoiKernel:
    def __init__(self,gamma=1,l=0.1):
        self.gamma = gamma
        self.l = l

    def rianru(self,x,z):
        self.x = x
        self.K = self.kernel(x[:,None],x)
        self.a = np.linalg.solve(self.K+np.eye(len(z))*self.l,z)

    def thamnai(self,x_):
        K_ = self.kernel(x_[:,None],self.x)
        return K_.dot(self.a)

    def kernel(self,x1,x2):
        return np.exp(-self.gamma*((x1-x2)**2))

ขอทดสอบการใช้ด้วยการลองเปรียบเทียบผลของพารามิเตอร์ γ และ λ ที่มีผลต่อค่าที่ได้
n = 60
x = np.random.uniform(0,10,n)
z = np.sin(x*2)*np.exp(-0.2*x)*np.random.normal(1,0.1,n)
x_ = np.linspace(x.min(),x.max(),501)
plt.figure(figsize=[6.5,7])
for i,l in enumerate([0.01,0.1,1]):
    for j,g in enumerate([0.1,1,10,100]):
        tt = ThotthoiKernel(gamma=g,l=l)
        tt.rianru(x,z)
        z_ = tt.thamnai(x_)
        plt.subplot2grid((4,3),(j,i),xticks=[],yticks=[])
        plt.plot(x_,z_,'m')
        plt.scatter(x,z,s=10,c='y',edgecolor='g')
        plt.title('$\\lambda$=%.1f,$\\gamma$=%.1f'%(l,g),size=8)
plt.tight_layout()
plt.show()


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



กรณีหลายตัวแปร

ในตัวอย่างที่อธิบายผ่านมาเป็นปัญหาที่มีตัวแปรต้นเพียงตัวเดียว (มิติเดียว) คือ x

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

ให้ข้อมูลตัวแปรต้นแทนด้วย
..(21)

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

การคำนวณ Φ ก็เปลี่ยนเป็นใช้ค่าในแต่ละมิติมาร่วมคำนวณ แล้วเคอร์เนลก็คำนวณจาก Φ เหมือนเดิม
..(22)
..(23)

การคำนวณเคอร์เนลเกาส์จะกลายเป็นแบบนี้
..(24)

ที่เพิ่มเข้ามาคือการคำนวณระยะห่างจะต้องคิดหลายมิติแล้วมาบวกกัน

เขียนโค้ดใหม่ได้ดังนี้
from scipy.spatial.distance import cdist

class ThotthoiKernel:
    def __init__(self,gamma=1,l=0.1):
        self.gamma = gamma
        self.l = l

    def rianru(self,X,z):
        self.X = X
        self.K = self.kernel(X,X)
        self.a = np.linalg.solve(self.K+np.eye(len(z))*self.l,z)

    def thamnai(self,X_):
        K_ = self.kernel(X_,self.X)
        return K_.dot(self.a)

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

หลักๆคือเปลี่ยนจากเดิมที่เป็นมิติเดียวมาเป็นหลายมิติ

นอกจากนี้ในที่นี้เปลี่ยนมาใช้ cdist ในการคำนวณระยะห่างตอนที่คำนวณเคอร์เนล ฟังก์ชันนี้นอกจากจะเขียนง่ายแล้วก็ยังเร็วด้วย

รายละเอียดเกี่ยวกับวิธีการใช้ฟังก์ชันนี้เขียนไว้ใน https://phyblas.hinaboshi.com/20180722

ลองนำมาใช้กับข้อมูลสองมิติดู
from mpl_toolkits.mplot3d import Axes3D

n = 200
X = np.random.random([n,2])
x,y = X.T
z = np.sin(x*9)*np.exp(-2*x)+np.random.normal(0,0.1,n)+5*(y-0.5)**2

gamma = 30
l = 0.01
tt = ThotthoiKernel(gamma=gamma,l=l)
tt.rianru(X,z)
mx,my = np.meshgrid(np.linspace(x.min(),x.max(),201),np.linspace(y.min(),y.max(),201))
mX = np.array([mx.ravel(),my.ravel()]).T
mz = tt.thamnai(mX).reshape(201,-1)
ax = plt.figure(figsize=[7,6]).add_axes([0,0,1,1],projection='3d')
c = np.abs(z-tt.thamnai(X))
sc = ax.scatter(x,y,z,c=c,edgecolor='k',cmap='plasma')
ax.plot_surface(mx,my,mz,alpha=0.2,color='g',edgecolor='k')
plt.colorbar(sc,pad=0.01,aspect=50)
plt.show()


พื้นผิวคือค่า z ที่คำนวณได้ที่ x,y ค่าต่างๆ ส่วนจุดคือข้อมูลจริง

สีของจุดแสดงถึงความแตกต่างระหว่างค่า z จริงกับค่า z บนระนาบ



ทั้งหมดนี้เป็นหลักการพื้นฐานของลูกเล่นเคอร์เนล และการใช้ในการวิเคราะห์การถดถอย

แต่จริงๆแล้วแทนที่จะใช้ในการวิเคราะห์การถดถอย คนจำนวนมากทางสายการเรียนรู้ของเครื่องจะรู้จักลูกเล่นเคอร์เนลในฐานะเทคนิคที่ประยุกต์ใช้กับเทคนิคอื่นๆเช่น เครื่องเวกเตอร์ค้ำยัน (支持向量机, support vector machine, SVM) หรือ การวิเคราะห์องค์ประกอบหลัก (主成分分析, principle component Analysis, PCA) มากกว่า

สำหรับ SVM ได้เขียนถึงไปแล้วใน https://phyblas.hinaboshi.com/20180709

ส่วน PCA เขียนไว้ใน https://phyblas.hinaboshi.com/20180727



อ้างอิง


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

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

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

หมวดหมู่

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

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

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



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

  查看日志

  推荐日志

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