วิชา: Artificial Intelligence | สัปดาห์ที่: 9–10
เครื่องมือหลัก: scikit-learn, pandas, numpy, matplotlib, seaborn, OpenAI Gym
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828',
'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945',
'lineColor': '#d79921',
'secondaryColor': '#3c3836',
'tertiaryColor': '#1d2021',
'background': '#282828',
'mainBkg': '#282828',
'nodeBorder': '#504945',
'clusterBkg': '#32302f',
'titleColor': '#ebdbb2',
'edgeLabelBackground': '#3c3836',
'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart TB
subgraph ERA1["ยุคที่ 1: รากฐาน (1940–1960)"]
A1["1943 - McCulloch-Pitts Neuron - เซลล์ประสาทเทียม"]
A2["1950 - Alan Turing - Turing Test"]
A3["1957 - Perceptron - (Frank Rosenblatt)"]
end
subgraph ERA2["ยุคที่ 2: AI Winter & Revival (1960–1990)"]
B1["1960s - Neural Net Research - วิจัยโครงข่าย"]
B2["1969 - Minsky & Papert - XOR Problem"]
B3["1986 - Backpropagation - Rumelhart et al."]
end
subgraph ERA3["ยุคที่ 3: ยุคทอง ML (1990–2010)"]
C1["1995 - SVM - Cortes & Vapnik"]
C2["2001 - Random Forest - (Breiman)"]
C3["2006 - Deep Learning - (Hinton et al.)"]
end
subgraph ERA4["ยุคที่ 4: Modern ML (2010–ปัจจุบัน)"]
D1["2012 - AlexNet - ImageNet Winner"]
D2["2017 - Transformer - Attention is All You Need"]
D3["2022– - LLMs: GPT, Claude - Generative AI"]
end
ERA1 --> ERA2 --> ERA3 --> ERA4
style ERA1 fill:#3c3836,stroke:#d79921,color:#ebdbb2
style ERA2 fill:#3c3836,stroke:#458588,color:#ebdbb2
style ERA3 fill:#3c3836,stroke:#b8bb26,color:#ebdbb2
style ERA4 fill:#3c3836,stroke:#fb4934,color:#ebdbb2
Supervised Learning คือกระบวนการที่โมเดลเรียนรู้จากข้อมูลที่มีป้ายกำกับ (labeled data) โดยมีทั้งข้อมูลนำเข้า (input features) และคำตอบที่ถูกต้อง (target/label) เพื่อให้โมเดลสามารถทำนายผลลัพธ์สำหรับข้อมูลใหม่ที่ยังไม่เคยเห็น
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828',
'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945',
'lineColor': '#d79921',
'secondaryColor': '#3c3836',
'background': '#282828',
'mainBkg': '#3c3836',
'nodeBorder': '#504945',
'clusterBkg': '#32302f',
'titleColor': '#ebdbb2',
'edgeLabelBackground': '#3c3836',
'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart TD
A["📊 ข้อมูลฝึก - (Training Data) - X, y"]
B["🧠 อัลกอริทึม ML - (ML Algorithm)"]
C["📦 โมเดล - (Model)"]
D["📋 ข้อมูลใหม่ - (New Data) X_new"]
E["🎯 การทำนาย - (Prediction) ŷ"]
F["📈 การประเมิน - (Evaluation) - Accuracy / MSE"]
A --> B --> C
D --> C --> E
E --> F
style A fill:#3c3836,stroke:#b8bb26,color:#ebdbb2
style B fill:#3c3836,stroke:#d79921,color:#ebdbb2
style C fill:#3c3836,stroke:#458588,color:#ebdbb2
style D fill:#3c3836,stroke:#8ec07c,color:#ebdbb2
style E fill:#3c3836,stroke:#fb4934,color:#ebdbb2
style F fill:#3c3836,stroke:#d3869b,color:#ebdbb2
Classification คือการทำนายว่าข้อมูลนั้นอยู่ในกลุ่ม (class/category) ใด ผลลัพธ์เป็นค่าแบบกลุ่ม (discrete) เช่น สแปม/ไม่สแปม, ป่วย/ไม่ป่วย, ชนิดดอกไม้
Decision Tree คือโมเดลที่ตัดสินใจโดยใช้กฎ if-else แบบลำดับชั้น ทำงานโดยแบ่งข้อมูลออกตาม feature ที่ให้ข้อมูลสูงสุด (highest information gain)
สูตรคำนวณ Information Gain:
โดยที่:
ตัวอย่างการคำนวณ Entropy:
สมมติมีข้อมูลผู้ป่วย 10 คน: ป่วย 7 คน, ไม่ป่วย 3 คน
"""
ตัวอย่างการสร้าง Decision Tree สำหรับจำแนกสายพันธุ์ดอกไม้ Iris
โดยใช้ scikit-learn
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# โหลดข้อมูล Iris (150 ตัวอย่าง, 4 features, 3 คลาส)
iris = load_iris()
X = iris.data # sepal length, sepal width, petal length, petal width
y = iris.target # 0=Setosa, 1=Versicolor, 2=Virginica
# แสดงข้อมูลตัวอย่างแรก
df = pd.DataFrame(X, columns=iris.feature_names)
df['species'] = [iris.target_names[i] for i in y]
print("=== ตัวอย่างข้อมูล 5 แถวแรก ===")
print(df.head())
print(f" - รูปร่างข้อมูล: {X.shape}")
print(f"จำนวนคลาส: {len(np.unique(y))} คลาส")
# แบ่งข้อมูลเป็น Training (80%) และ Testing (20%)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f" - จำนวนข้อมูลฝึก: {X_train.shape[0]} ตัวอย่าง")
print(f"จำนวนข้อมูลทดสอบ: {X_test.shape[0]} ตัวอย่าง")
# สร้างและฝึกโมเดล Decision Tree
model = DecisionTreeClassifier(
max_depth=3, # ความลึกสูงสุดของต้นไม้ (ป้องกัน overfitting)
criterion='entropy', # ใช้ Information Gain
random_state=42
)
model.fit(X_train, y_train)
# ประเมินโมเดล
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f" - === ผลการประเมิน ===")
print(f"ความแม่นยำ (Accuracy): {accuracy:.4f} ({accuracy*100:.2f}%)")
print(" - รายงานละเอียด:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
# แสดงความสำคัญของ Features
print("=== ความสำคัญของ Features ===")
for name, importance in zip(iris.feature_names, model.feature_importances_):
bar = "█" * int(importance * 30)
print(f" {name:<25} {bar} {importance:.4f}")
# ตัวอย่างการทำนายข้อมูลใหม่
new_flower = np.array([[5.1, 3.5, 1.4, 0.2]]) # ข้อมูลดอกไม้ใหม่
prediction = model.predict(new_flower)
probability = model.predict_proba(new_flower)
print(f" - === ทำนายดอกไม้ใหม่ ===")
print(f"Input: sepal_length=5.1, sepal_width=3.5, petal_length=1.4, petal_width=0.2")
print(f"ผลทำนาย: {iris.target_names[prediction[0]]}")
print(f"ความน่าจะเป็น: Setosa={probability[0][0]:.2%}, "
f"Versicolor={probability[0][1]:.2%}, "
f"Virginica={probability[0][2]:.2%}")
ผลลัพธ์ที่คาดหวัง:
=== ตัวอย่างข้อมูล 5 แถวแรก ===
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) species
0 5.1 3.5 1.4 0.2 setosa
...
ความแม่นยำ (Accuracy): 0.9667 (96.67%)
ผลทำนาย: setosa
KNN คืออัลกอริทึมที่จำแนกประเภทโดยดูจาก k ตัวอย่างที่ใกล้ที่สุด (nearest neighbors) ในชุดข้อมูลฝึก แล้วลงคะแนนเสียงข้างมาก (majority vote)
สูตรระยะห่าง Euclidean:
ตัวอย่างการคำนวณ KNN ด้วยมือ:
| จุดข้อมูล | Feature 1 (x₁) | Feature 2 (x₂) | Label | ระยะห่างจาก Query |
|---|---|---|---|---|
| A | 1.0 | 2.0 | 🔴 Red | √((3-1)²+(4-2)²) = 2.83 |
| B | 2.0 | 3.0 | 🔴 Red | √((3-2)²+(4-3)²) = 1.41 |
| C | 4.0 | 5.0 | 🔵 Blue | √((3-4)²+(4-5)²) = 1.41 |
| D | 5.0 | 4.0 | 🔵 Blue | √((3-5)²+(4-4)²) = 2.00 |
| E | 3.5 | 4.5 | 🔵 Blue | √((3-3.5)²+(4-4.5)²) = 0.71 |
Query point = (3.0, 4.0), k=3
3 Nearest: E(0.71), B(1.41), C(1.41)
Vote: Red=1, Blue=2 → ทำนายเป็น Blue ✅
"""
ตัวอย่าง KNN สำหรับตรวจจับโรคเบาหวาน (Diabetes Detection)
ใช้ข้อมูล Pima Indians Diabetes Dataset
"""
import numpy as np
import pandas as pd
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import confusion_matrix, classification_report
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# สร้างข้อมูลจำลองสำหรับตรวจโรคเบาหวาน
np.random.seed(42)
n_samples = 200
# Features: [น้ำตาลในเลือด (glucose), BMI, อายุ (age), ความดันโลหิต (blood_pressure)]
glucose = np.concatenate([np.random.normal(100, 20, 120), np.random.normal(140, 25, 80)])
bmi = np.concatenate([np.random.normal(25, 4, 120), np.random.normal(32, 6, 80)])
age = np.concatenate([np.random.normal(30, 8, 120), np.random.normal(45, 10, 80)])
bp = np.concatenate([np.random.normal(70, 10, 120), np.random.normal(80, 12, 80)])
labels = np.array([0]*120 + [1]*80) # 0=ปกติ, 1=เป็นเบาหวาน
X = np.column_stack([glucose, bmi, age, bp])
y = labels
feature_names = ['น้ำตาลในเลือด', 'BMI', 'อายุ', 'ความดันโลหิต']
# Standardization (สำคัญมากสำหรับ KNN!)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit บน train เท่านั้น
X_test_scaled = scaler.transform(X_test) # transform บน test
# หาค่า k ที่ดีที่สุด
k_values = range(1, 21)
cv_scores = []
for k in k_values:
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
best_k = k_values[np.argmax(cv_scores)]
print(f"=== หา k ที่ดีที่สุด ===")
print(f"k ที่ดีที่สุด: {best_k}, Accuracy: {max(cv_scores):.4f}")
# ฝึกโมเดลด้วย k ที่ดีที่สุด
knn = KNeighborsClassifier(n_neighbors=best_k)
knn.fit(X_train_scaled, y_train)
y_pred = knn.predict(X_test_scaled)
print(f" - === ผลประเมินบน Test Set ===")
print(classification_report(y_test, y_pred,
target_names=['ปกติ', 'เบาหวาน']))
# ทำนายผู้ป่วยใหม่
new_patient = np.array([[150, 30, 50, 85]]) # glucose, BMI, age, bp
new_scaled = scaler.transform(new_patient)
pred = knn.predict(new_scaled)
prob = knn.predict_proba(new_scaled)
print(f" - === ทำนายผู้ป่วยใหม่ ===")
print(f"น้ำตาล=150, BMI=30, อายุ=50, ความดัน=85")
print(f"ผลวินิจฉัย: {'⚠️ เบาหวาน' if pred[0]==1 else '✅ ปกติ'}")
print(f"ความน่าจะเป็น: ปกติ={prob[0][0]:.2%}, เบาหวาน={prob[0][1]:.2%}")
# วาดกราฟ k vs Accuracy
plt.figure(figsize=(8, 4))
plt.plot(k_values, cv_scores, 'o-', color='#d79921', linewidth=2, markersize=6)
plt.axvline(x=best_k, color='#fb4934', linestyle='--', label=f'Best k={best_k}')
plt.xlabel('จำนวน k (Neighbors)', fontsize=12)
plt.ylabel('Cross-Validation Accuracy', fontsize=12)
plt.title('การเลือกค่า k ที่เหมาะสมสำหรับ KNN', fontsize=13)
plt.legend()
plt.tight_layout()
plt.savefig('/tmp/knn_k_selection.png', dpi=100, bbox_inches='tight')
print(" - บันทึกกราฟแล้ว: /tmp/knn_k_selection.png")
Naive Bayes ใช้ทฤษฎีเบย์ (Bayes' Theorem) โดยมีข้อสมมติว่า feature แต่ละตัวเป็นอิสระต่อกัน (conditional independence)
สูตร Bayes' Theorem:
โดยที่:
ตัวอย่างการคำนวณ Naive Bayes กรองสแปม:
| contains "free" | contains "win" | contains "meeting" | Label | |
|---|---|---|---|---|
| 1 | ✅ | ✅ | ❌ | 🔴 Spam |
| 2 | ✅ | ❌ | ❌ | 🔴 Spam |
| 3 | ❌ | ❌ | ✅ | 🟢 Ham |
| 4 | ❌ | ✅ | ✅ | 🟢 Ham |
ทดสอบ: Email ใหม่มี "free"=✅, "win"=✅, "meeting"=❌
Regression คือการทำนายค่าต่อเนื่อง (continuous value) เช่น ราคาบ้าน, อุณหภูมิ, ยอดขาย
สูตร Linear Regression:
Loss Function (Mean Squared Error):
โดยที่:
ตัวอย่างการคำนวณ Simple Linear Regression ด้วยมือ:
ข้อมูลพื้นที่บ้าน (ตร.ม.) และราคา (ล้านบาท):
| บ้าน | พื้นที่ (x) | ราคา (y) |
|---|---|---|
| 1 | 50 | 2.5 |
| 2 | 80 | 4.0 |
| 3 | 100 | 5.5 |
| 4 | 120 | 6.0 |
| 5 | 150 | 7.5 |
x̄ = (50+80+100+120+150)/5 = 100, ȳ = (2.5+4+5.5+6+7.5)/5 = 5.1
β₀ = ȳ - β₁x̄ = 5.1 - 0.0485 × 100 = 0.25
สมการ: ŷ = 0.25 + 0.0485 × x
ทำนาย: บ้านพื้นที่ 130 ตร.ม. → ŷ = 0.25 + 0.0485 × 130 = 6.555 ล้านบาท
"""
Linear และ Polynomial Regression สำหรับทำนายราคาบ้าน
พร้อมการ Visualize ผลลัพธ์
"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
# ===== สร้างข้อมูลจำลอง: ราคาบ้าน =====
np.random.seed(42)
n = 100
area = np.random.uniform(40, 200, n) # พื้นที่ 40-200 ตร.ม.
rooms = np.random.randint(1, 6, n) # จำนวนห้อง 1-5 ห้อง
age = np.random.uniform(0, 30, n) # อายุบ้าน 0-30 ปี
distance = np.random.uniform(1, 20, n) # ระยะจากศูนย์เมือง (กม.)
# ราคาจริง (ล้านบาท) พร้อม noise
price = (
0.05 * area
+ 0.3 * rooms
- 0.05 * age
- 0.1 * distance
+ 1.0
+ np.random.normal(0, 0.3, n)
)
# สร้าง DataFrame
df = pd.DataFrame({
'พื้นที่ (ตร.ม.)': area,
'จำนวนห้อง': rooms,
'อายุบ้าน (ปี)': age,
'ระยะจากเมือง (กม.)': distance,
'ราคา (ล้านบาท)': price
})
print("=== สถิติเบื้องต้น ===")
print(df.describe().round(2))
# เตรียมข้อมูล
X = df[['พื้นที่ (ตร.ม.)', 'จำนวนห้อง', 'อายุบ้าน (ปี)', 'ระยะจากเมือง (กม.)']].values
y = df['ราคา (ล้านบาท)'].values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# ===== Linear Regression =====
lr = LinearRegression()
lr.fit(X_train, y_train)
y_pred_lr = lr.predict(X_test)
mse_lr = mean_squared_error(y_test, y_pred_lr)
r2_lr = r2_score(y_test, y_pred_lr)
print(f" - === Linear Regression ===")
print(f"Intercept (β₀): {lr.intercept_:.4f}")
for name, coef in zip(['พื้นที่', 'ห้อง', 'อายุ', 'ระยะ'], lr.coef_):
print(f" β ({name}): {coef:.4f}")
print(f"MSE: {mse_lr:.4f}")
print(f"RMSE: {np.sqrt(mse_lr):.4f} ล้านบาท")
print(f"R²: {r2_lr:.4f} ({r2_lr*100:.1f}% variance explained)")
# ===== ทำนายบ้านตัวอย่าง =====
sample = np.array([[120, 3, 5, 8]]) # พื้นที่=120, ห้อง=3, อายุ=5ปี, ระยะ=8กม.
pred = lr.predict(sample)
print(f" - === ทำนายราคาบ้านตัวอย่าง ===")
print(f"พื้นที่=120 ตร.ม., 3 ห้อง, อายุ=5ปี, ระยะ=8กม.")
print(f"ราคาที่ทำนาย: {pred[0]:.2f} ล้านบาท")
# ===== Visualize =====
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
fig.patch.set_facecolor('#282828')
for ax in axes:
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
for spine in ax.spines.values():
spine.set_edgecolor('#504945')
# กราฟ 1: Actual vs Predicted
axes[0].scatter(y_test, y_pred_lr, color='#83a598', alpha=0.7, s=50)
lims = [min(y_test.min(), y_pred_lr.min()), max(y_test.max(), y_pred_lr.max())]
axes[0].plot(lims, lims, 'r--', color='#fb4934', linewidth=2, label='Perfect Prediction')
axes[0].set_xlabel('ราคาจริง (ล้านบาท)', color='#ebdbb2', fontsize=11)
axes[0].set_ylabel('ราคาทำนาย (ล้านบาท)', color='#ebdbb2', fontsize=11)
axes[0].set_title(f'Actual vs Predicted - R² = {r2_lr:.3f}', color='#ebdbb2', fontsize=12)
axes[0].legend(labelcolor='#ebdbb2')
# กราฟ 2: Residuals
residuals = y_test - y_pred_lr
axes[1].hist(residuals, bins=20, color='#d79921', edgecolor='#282828', alpha=0.8)
axes[1].axvline(x=0, color='#fb4934', linestyle='--', linewidth=2)
axes[1].set_xlabel('Residual (ล้านบาท)', color='#ebdbb2', fontsize=11)
axes[1].set_ylabel('จำนวน', color='#ebdbb2', fontsize=11)
axes[1].set_title('Distribution of Residuals - (ควรมีรูปแบบ Normal)', color='#ebdbb2', fontsize=12)
plt.tight_layout()
plt.savefig('/tmp/regression_results.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print(" - บันทึกกราฟแล้ว: /tmp/regression_results.png")
Cross-Validation เป็นเทคนิคที่ประเมินโมเดลอย่างน่าเชื่อถือโดยแบ่งข้อมูลออกเป็น k ส่วน (folds) ฝึกโมเดล k-1 ส่วน และทดสอบบนส่วนที่เหลือ ทำซ้ำ k ครั้ง
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart TB
subgraph FOLD1["Fold 1"]
direction TB
T1["Test"]
TR1["Train | Train | Train | Train"]
T1 --- TR1
end
subgraph FOLD2["Fold 2"]
direction TB
TR2a["Train"]
T2["Test"]
TR2b["Train | Train | Train"]
TR2a --- T2 --- TR2b
end
subgraph FOLD3["Fold 3"]
direction TB
TR3a["Train | Train"]
T3["Test"]
TR3b["Train | Train"]
TR3a --- T3 --- TR3b
end
FOLD1 --> S1["Score 1"]
FOLD2 --> S2["Score 2"]
FOLD3 --> S3["Score 3"]
S1 & S2 & S3 --> AVG["📊 Mean ± Std"]
style FOLD1 fill:#3c3836,stroke:#d79921,color:#ebdbb2
style FOLD2 fill:#3c3836,stroke:#b8bb26,color:#ebdbb2
style FOLD3 fill:#3c3836,stroke:#458588,color:#ebdbb2
style AVG fill:#3c3836,stroke:#fb4934,color:#ebdbb2
| เมตริก | สูตร | ความหมาย |
|---|---|---|
| Accuracy | (TP+TN)/(TP+TN+FP+FN) | ความแม่นยำโดยรวม |
| Precision | TP/(TP+FP) | จากที่ทำนายว่า Positive มีกี่ % ที่ถูก |
| Recall | TP/(TP+FN) | จากที่เป็น Positive จริงทำนายถูกกี่ % |
| F1-Score | 2×(P×R)/(P+R) | ค่าเฉลี่ยฮาร์มอนิกของ P และ R |
| AUC-ROC | พื้นที่ใต้กราฟ ROC | ประสิทธิภาพแยกคลาสโดยรวม |
โดยที่: TP=True Positive, TN=True Negative, FP=False Positive, FN=False Negative
"""
การประเมินโมเดล: Cross-Validation และ Confusion Matrix
เปรียบเทียบหลายอัลกอริทึมบนชุดข้อมูลเดียวกัน
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import (confusion_matrix, classification_report,
roc_auc_score, roc_curve)
# โหลดข้อมูลมะเร็งเต้านม (Wisconsin Breast Cancer Dataset)
data = load_breast_cancer()
X, y = data.data, data.target
print(f"ข้อมูล: {X.shape[0]} ตัวอย่าง, {X.shape[1]} features")
print(f"คลาส: {data.target_names}")
print(f"สัดส่วน: Malignant={sum(y==0)}, Benign={sum(y==1)}")
# กำหนดอัลกอริทึมที่จะเปรียบเทียบ
models = {
'Decision Tree': Pipeline([
('clf', DecisionTreeClassifier(max_depth=5, random_state=42))
]),
'KNN': Pipeline([
('scaler', StandardScaler()),
('clf', KNeighborsClassifier(n_neighbors=7))
]),
'Naive Bayes': Pipeline([
('scaler', StandardScaler()),
('clf', GaussianNB())
]),
'Random Forest': Pipeline([
('clf', RandomForestClassifier(n_estimators=100, random_state=42))
])
}
# 5-Fold Cross-Validation
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
results = {}
print(f" - {'='*55}")
print(f"{'อัลกอริทึม':<20} {'Mean Acc':>10} {'Std':>10} {'95% CI':>15}")
print(f"{'='*55}")
for name, model in models.items():
scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
results[name] = scores
ci = 1.96 * scores.std()
print(f"{name:<20} {scores.mean():>10.4f} {scores.std():>10.4f} "
f"[{scores.mean()-ci:.3f}, {scores.mean()+ci:.3f}]")
print(f"{'='*55}")
# วาดกราฟเปรียบเทียบ
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
fig.patch.set_facecolor('#282828')
fig.suptitle('เปรียบเทียบอัลกอริทึม Classification', color='#ebdbb2', fontsize=14)
for ax in axes:
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
# กราฟ 1: Box Plot เปรียบเทียบ
colors = ['#fb4934', '#d79921', '#b8bb26', '#83a598']
bp = axes[0].boxplot(
[results[m] for m in models.keys()],
patch_artist=True, labels=list(models.keys())
)
for patch, color in zip(bp['boxes'], colors):
patch.set_facecolor(color)
patch.set_alpha(0.7)
axes[0].set_ylabel('Accuracy', color='#ebdbb2', fontsize=11)
axes[0].set_title('5-Fold Cross-Validation Accuracy', color='#ebdbb2', fontsize=12)
axes[0].tick_params(axis='x', rotation=15)
# กราฟ 2: Confusion Matrix ของ Random Forest
from sklearn.model_selection import train_test_split
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
best_model = models['Random Forest']
best_model.fit(X_tr, y_tr)
y_pred = best_model.predict(X_te)
cm = confusion_matrix(y_te, y_pred)
sns.heatmap(cm, annot=True, fmt='d', ax=axes[1],
cmap='YlOrRd', linewidths=0.5,
xticklabels=data.target_names,
yticklabels=data.target_names)
axes[1].set_xlabel('ทำนาย', color='#ebdbb2', fontsize=11)
axes[1].set_ylabel('จริง', color='#ebdbb2', fontsize=11)
axes[1].set_title('Confusion Matrix (Random Forest)', color='#ebdbb2', fontsize=12)
axes[1].tick_params(colors='#ebdbb2')
plt.tight_layout()
plt.savefig('/tmp/model_comparison.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print(f" - บันทึกกราฟแล้ว")
Unsupervised Learning คือการเรียนรู้จากข้อมูลที่ ไม่มีป้ายกำกับ (unlabeled data) โมเดลต้องค้นหารูปแบบ (patterns) และโครงสร้าง (structure) ในข้อมูลด้วยตนเอง
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
mindmap
root((Unsupervised - Learning))
Clustering
K-Means
Hierarchical
DBSCAN
Dimensionality - Reduction
PCA
t-SNE
UMAP
Generative - Models
Autoencoders
GMM
VAE
Association - Rules
Apriori
FP-Growth
K-Means เป็นอัลกอริทึมจัดกลุ่มที่แบ่งข้อมูลออกเป็น k กลุ่ม โดยแต่ละกลุ่มมี centroid (จุดศูนย์กลาง) โดยลดผลรวมระยะห่างจากจุดศูนย์กลาง (Within-Cluster Sum of Squares)
สูตร Objective Function:
โดยที่ μₖ = centroid ของ cluster k, Cₖ = เซตของจุดใน cluster k
ขั้นตอน K-Means:
ตัวอย่างการคำนวณ K-Means รอบแรก (k=2):
| จุด | x | y | Centroid เริ่มต้น C1=(1,1), C2=(5,5) |
|---|---|---|---|
| P1 | 1 | 2 | d(C1)=1.0, d(C2)=6.4 → Cluster 1 |
| P2 | 2 | 1 | d(C1)=1.0, d(C2)=5.7 → Cluster 1 |
| P3 | 4 | 5 | d(C1)=5.0, d(C2)=1.4 → Cluster 2 |
| P4 | 5 | 4 | d(C1)=5.7, d(C2)=1.0 → Cluster 2 |
| P5 | 5 | 6 | d(C1)=6.4, d(C2)=1.0 → Cluster 2 |
Centroid ใหม่: C1 = ((1+2)/2, (2+1)/2) = (1.5, 1.5); C2 = ((4+5+5)/3, (5+4+6)/3) = (4.67, 5.0)
"""
K-Means Clustering สำหรับแบ่งกลุ่มลูกค้า (Customer Segmentation)
พร้อมการหาค่า k ที่เหมาะสมด้วย Elbow Method และ Silhouette Score
"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
# สร้างข้อมูลลูกค้า (Customer Segmentation)
np.random.seed(42)
n_customers = 300
# สร้างกลุ่มลูกค้า 4 กลุ่ม
groups = {
'กลุ่มประหยัด': {'annual_income': (20,40), 'spending_score': (10,30), 'n': 75},
'กลุ่มมาตรฐาน': {'annual_income': (50,70), 'spending_score': (40,60), 'n': 90},
'กลุ่มมั่งคั่ง': {'annual_income': (80,120), 'spending_score': (70,90), 'n': 80},
'กลุ่มสุรุ่ยสุร่าย': {'annual_income': (20,45), 'spending_score': (65,90), 'n': 55},
}
data_list = []
for group_name, params in groups.items():
income = np.random.uniform(*params['annual_income'], params['n'])
spending = np.random.uniform(*params['spending_score'], params['n'])
age = np.random.randint(20, 65, params['n'])
data_list.append(pd.DataFrame({
'รายได้ต่อปี (พัน)': income,
'คะแนนการใช้จ่าย': spending,
'อายุ': age,
'true_group': group_name
}))
df = pd.concat(data_list, ignore_index=True)
print("=== ข้อมูลลูกค้า ===")
print(df.describe().round(2))
X = df[['รายได้ต่อปี (พัน)', 'คะแนนการใช้จ่าย', 'อายุ']].values
# Standardize
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# ===== Elbow Method หาค่า k ที่เหมาะสม =====
inertias = []
sil_scores = []
k_range = range(2, 10)
for k in k_range:
km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42)
km.fit(X_scaled)
inertias.append(km.inertia_)
sil_scores.append(silhouette_score(X_scaled, km.labels_))
best_k_sil = k_range[np.argmax(sil_scores)]
print(f" - === ผลการหา k ที่เหมาะสม ===")
print(f"k ที่ดีที่สุด (Silhouette): {best_k_sil}, Score = {max(sil_scores):.4f}")
# ===== K-Means ด้วย k ที่ดีที่สุด =====
kmeans = KMeans(n_clusters=best_k_sil, init='k-means++', n_init=10, random_state=42)
df['Cluster_KMeans'] = kmeans.fit_predict(X_scaled)
# วิเคราะห์แต่ละ cluster
print(f" - === วิเคราะห์ {best_k_sil} Clusters ===")
for c in range(best_k_sil):
subset = df[df['Cluster_KMeans'] == c]
print(f" - Cluster {c} ({len(subset)} ลูกค้า):")
print(f" รายได้เฉลี่ย: {subset['รายได้ต่อปี (พัน)'].mean():.1f} พัน")
print(f" คะแนนใช้จ่ายเฉลี่ย: {subset['คะแนนการใช้จ่าย'].mean():.1f}")
print(f" อายุเฉลี่ย: {subset['อายุ'].mean():.1f} ปี")
# Visualize ด้วย PCA 2D
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X_scaled)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
fig.patch.set_facecolor('#282828')
fig.suptitle('Customer Segmentation ด้วย K-Means', color='#ebdbb2', fontsize=14)
colors = ['#fb4934', '#d79921', '#b8bb26', '#83a598', '#d3869b']
for ax in axes:
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
# Elbow + Silhouette
ax2 = axes[0].twinx()
axes[0].set_facecolor('#3c3836')
axes[0].plot(k_range, inertias, 'o-', color='#d79921', linewidth=2, label='Inertia')
ax2.plot(k_range, sil_scores, 's--', color='#83a598', linewidth=2, label='Silhouette')
axes[0].set_xlabel('จำนวน Clusters (k)', color='#ebdbb2')
axes[0].set_ylabel('Inertia (WCSS)', color='#d79921')
ax2.set_ylabel('Silhouette Score', color='#83a598')
axes[0].set_title('Elbow Method & Silhouette Score', color='#ebdbb2')
axes[0].axvline(x=best_k_sil, color='#fb4934', linestyle=':', linewidth=2)
ax2.set_facecolor('#3c3836')
ax2.tick_params(colors='#ebdbb2')
# Cluster Plot
for c in range(best_k_sil):
mask = df['Cluster_KMeans'] == c
axes[1].scatter(X_2d[mask, 0], X_2d[mask, 1],
color=colors[c], alpha=0.7, s=50, label=f'Cluster {c}')
axes[1].set_xlabel('PCA Component 1', color='#ebdbb2')
axes[1].set_ylabel('PCA Component 2', color='#ebdbb2')
axes[1].set_title('การแบ่งกลุ่มลูกค้า (2D PCA)', color='#ebdbb2')
axes[1].legend(labelcolor='#ebdbb2', facecolor='#3c3836')
plt.tight_layout()
plt.savefig('/tmp/kmeans_clustering.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print(" - บันทึกกราฟแล้ว: /tmp/kmeans_clustering.png")
Hierarchical Clustering สร้าง dendrogram (ต้นไม้ลำดับชั้น) เพื่อแสดงความสัมพันธ์ระหว่างกลุ่ม โดยไม่ต้องกำหนดจำนวน k ล่วงหน้า มีสองแบบ:
Linkage Methods:
| Method | สูตร | ข้อดี | ข้อเสีย |
|---|---|---|---|
| Single | min d(a,b) | ดีกับ elongated clusters | ไวต่อ noise/outliers |
| Complete | max d(a,b) | clusters มีขนาดสม่ำเสมอ | ไม่ดีกับ elongated shapes |
| Average | mean d(a,b) | ประนีประนอม | ช้ากว่า |
| Ward | minimize variance | clusters กลม, ขนาดใกล้เคียง | ไม่ดีกับ non-globular |
DBSCAN จัดกลุ่มตามความหนาแน่น (density) สามารถค้นหากลุ่มที่มีรูปร่างซับซ้อน และตรวจจับ outliers ได้โดยอัตโนมัติ
พารามิเตอร์หลัก:
ประเภทของจุด:
"""
เปรียบเทียบ K-Means, Hierarchical, และ DBSCAN
บนข้อมูลที่มีรูปทรงต่างกัน
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN
from sklearn.datasets import make_moons, make_circles, make_blobs
from sklearn.preprocessing import StandardScaler
# สร้างข้อมูลรูปทรงต่างกัน 3 ชุด
np.random.seed(42)
datasets = [
(make_blobs(n_samples=200, centers=3, random_state=42)[0], "Blob Clusters"),
(make_moons(n_samples=200, noise=0.05, random_state=42)[0], "Moon Shape"),
(make_circles(n_samples=200, noise=0.05, factor=0.5, random_state=42)[0], "Circle Shape"),
]
algorithms = {
'K-Means (k=3)': KMeans(n_clusters=3, random_state=42),
'Hierarchical': AgglomerativeClustering(n_clusters=3, linkage='ward'),
'DBSCAN': DBSCAN(eps=0.3, min_samples=10),
}
fig, axes = plt.subplots(3, 3, figsize=(15, 12))
fig.patch.set_facecolor('#282828')
fig.suptitle('เปรียบเทียบอัลกอริทึม Clustering', color='#ebdbb2', fontsize=14)
colors_cmap = plt.cm.Set1
for row, (X_data, data_name) in enumerate(datasets):
X_scaled = StandardScaler().fit_transform(X_data)
for col, (alg_name, alg) in enumerate(algorithms.items()):
ax = axes[row][col]
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
labels = alg.fit_predict(X_scaled)
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
n_noise = list(labels).count(-1)
# Plot
unique_labels = set(labels)
colors = [colors_cmap(each) for each in np.linspace(0, 1, len(unique_labels))]
for label, color in zip(unique_labels, colors):
mask = labels == label
marker = 'x' if label == -1 else 'o'
color = '#504945' if label == -1 else color
ax.scatter(X_scaled[mask, 0], X_scaled[mask, 1],
c=[color], s=30, marker=marker, alpha=0.8)
title = f"{data_name} - {alg_name} - ({n_clusters} clusters"
if n_noise > 0:
title += f", {n_noise} noise)"
else:
title += ")"
ax.set_title(title, color='#ebdbb2', fontsize=9)
plt.tight_layout()
plt.savefig('/tmp/clustering_comparison.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print("บันทึกกราฟเปรียบเทียบ Clustering แล้ว")
ความหมายและแนวคิด
Principal Component Analysis (PCA) คือเทคนิคลดมิติข้อมูล (Dimensionality Reduction) ที่ค้นหา ทิศทางของความแปรปรวนสูงสุด (Maximum Variance Directions) ในข้อมูลหลายมิติ แล้วฉายข้อมูลลงบนแกนใหม่ที่เรียกว่า Principal Components (PC) ซึ่งเป็น orthogonal (ตั้งฉากกัน)
เป้าหมายหลัก:
ชุดข้อมูลตัวอย่าง
สมมติมีข้อมูลนักศึกษา 6 คน วัด 2 ตัวแปร ได้แก่ คะแนนคณิตศาสตร์ (Math) และคะแนนฟิสิกส์ (Physics) (คะแนนเต็ม 10)
| นักศึกษา | คณิตศาสตร์ (x₁) | ฟิสิกส์ (x₂) |
|---|---|---|
| A | 2 | 2.4 |
| B | 4 | 4.0 |
| C | 5 | 5.5 |
| D | 6 | 5.5 |
| E | 8 | 7.6 |
| F | 9 | 8.5 |
สังเกต: ทั้งสองตัวแปรดูเหมือนสัมพันธ์กัน (นักศึกษาที่เก่งคณิตฯ มักเก่งฟิสิกส์ด้วย)
PCA จะช่วยค้นหาแกนหลักที่ "อธิบายรูปแบบนี้" ได้ดีที่สุด
ขั้นตอนที่ 1: คำนวณค่าเฉลี่ย (Mean) ของแต่ละตัวแปร
ขั้นตอนที่ 2: Centering — หักค่าเฉลี่ยออกจากข้อมูล (Zero Mean)
สูตร: x̃ᵢ = xᵢ − x̄
| นักศึกษา | x₁ (Math) | x₂ (Physics) | x̃₁ = x₁ − 5.667 | x̃₂ = x₂ − 5.583 |
|---|---|---|---|---|
| A | 2 | 2.4 | −3.667 | −3.183 |
| B | 4 | 4.0 | −1.667 | −1.583 |
| C | 5 | 5.5 | −0.667 | −0.083 |
| D | 6 | 5.5 | +0.333 | −0.083 |
| E | 8 | 7.6 | +2.333 | +2.017 |
| F | 9 | 8.5 | +3.333 | +2.917 |
| ผลรวม | — | — | ≈ 0.000 ✅ | ≈ 0.000 ✅ |
หลัง centering ผลรวมของแต่ละคอลัมน์ต้องเป็น 0 เสมอ
ขั้นตอนที่ 3: คำนวณ Covariance Matrix
Covariance Matrix บอกว่าตัวแปรแต่ละคู่มีความสัมพันธ์กันอย่างไร
คำนวณทีละค่า (n=6, หาร n−1=5):
Var(x̃₁) = Cov(x̃₁, x̃₁):
| นักศึกษา | x̃₁ | x̃₁² |
|---|---|---|
| A | −3.667 | 13.447 |
| B | −1.667 | 2.779 |
| C | −0.667 | 0.445 |
| D | +0.333 | 0.111 |
| E | +2.333 | 5.443 |
| F | +3.333 | 11.109 |
| รวม | 33.334 |
Var(x̃₂) = Cov(x̃₂, x̃₂):
| นักศึกษา | x̃₂ | x̃₂² |
|---|---|---|
| A | −3.183 | 10.131 |
| B | −1.583 | 2.506 |
| C | −0.083 | 0.007 |
| D | −0.083 | 0.007 |
| E | +2.017 | 4.068 |
| F | +2.917 | 8.509 |
| รวม | 25.228 |
Cov(x̃₁, x̃₂):
| นักศึกษา | x̃₁ | x̃₂ | x̃₁ × x̃₂ |
|---|---|---|---|
| A | −3.667 | −3.183 | 11.672 |
| B | −1.667 | −1.583 | 2.639 |
| C | −0.667 | −0.083 | 0.055 |
| D | +0.333 | −0.083 | −0.028 |
| E | +2.333 | +2.017 | 4.706 |
| F | +3.333 | +2.917 | 9.723 |
| รวม | 28.767 |
สรุป Covariance Matrix:
Covariance = 5.753 (บวก, สูง) → ยืนยันว่า x₁ และ x₂ สัมพันธ์กันมาก
ขั้นตอนที่ 4: คำนวณ Eigenvalues และ Eigenvectors
Eigenvalues (λ) บอกว่าแกนนั้นๆ อธิบาย variance ได้เท่าไร
Eigenvectors (v) บอกทิศทางของแกนใหม่แต่ละแกน
วิธีหา Eigenvalues: แก้สมการ det(Σ − λI) = 0
ขยายวงเล็บ:
ใช้สูตร Quadratic:
ผลลัพธ์:
ตรวจสอบ: λ₁ + λ₂ = 11.669 + 0.045 = 11.714 ≈ 6.667 + 5.046 = 11.713 ✅
(ผลรวม Eigenvalues ต้องเท่ากับ Trace ของ Covariance Matrix)
คำนวณ Eigenvectors
สำหรับ λ₁ = 11.669 แก้ระบบ (Σ − λ₁I)v = 0:
จากแถวแรก: −5.002 v₁ + 5.753 v₂ = 0 → v₁ = (5.753/5.002) v₂ = 1.150 v₂
ให้ v₂ = 1 → v = [1.150, 1.000]ᵀ
Normalize (หารด้วย ‖v‖):
สำหรับ λ₂ = 0.045 (ทำแบบเดิม):
ตรวจสอบ Orthogonality: e₁ · e₂ = (0.754)(−0.656) + (0.656)(0.754) = −0.495 + 0.495 = 0 ✅
ขั้นตอนที่ 5: คำนวณ Explained Variance Ratio
สรุป: แค่ PC1 เพียงแกนเดียว ก็อธิบาย variance ได้ถึง 99.62% แสดงว่าข้อมูล 2 มิตินี้สามารถ ลดเหลือ 1 มิติ โดยแทบไม่สูญเสียข้อมูล
| Principal Component | Eigenvalue | Explained Variance | Cumulative |
|---|---|---|---|
| PC1 | 11.669 | 99.62% | 99.62% |
| PC2 | 0.045 | 0.38% | 100.00% |
ขั้นตอนที่ 6: ฉายข้อมูลลงบน Principal Components (Projection)
การฉายข้อมูล: คูณ centered data matrix กับ eigenvector
ตารางการคำนวณ PC Scores:
| นักศึกษา | x̃₁ | x̃₂ | PC1 Score (z₁) | PC2 Score (z₂) |
|---|---|---|---|---|
| A | −3.667 | −3.183 | (−3.667)(0.754)+(−3.183)(0.656) = −4.842 | (−3.667)(−0.656)+(−3.183)(0.754) = +0.082 |
| B | −1.667 | −1.583 | (−1.667)(0.754)+(−1.583)(0.656) = −2.294 | (−1.667)(−0.656)+(−1.583)(0.754) = −0.102 |
| C | −0.667 | −0.083 | (−0.667)(0.754)+(−0.083)(0.656) = −0.557 | (−0.667)(−0.656)+(−0.083)(0.754) = +0.375 |
| D | +0.333 | −0.083 | (+0.333)(0.754)+(−0.083)(0.656) = +0.196 | (+0.333)(−0.656)+(−0.083)(0.754) = −0.281 |
| E | +2.333 | +2.017 | (+2.333)(0.754)+(+2.017)(0.656) = +3.080 | (+2.333)(−0.656)+(+2.017)(0.754) = −0.011 |
| F | +3.333 | +2.917 | (+3.333)(0.754)+(+2.917)(0.656) = +4.427 | (+3.333)(−0.656)+(+2.917)(0.754) = +0.012 |
สังเกต: ค่า PC2 Score มีขนาดเล็กมาก (ใกล้ 0 ทุกตัว) → ยืนยันว่า PC2 แทบไม่มีข้อมูลเพิ่มเติม
สรุปภาพรวมทุกขั้นตอน
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828',
'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945',
'lineColor': '#d79921',
'secondaryColor': '#3c3836',
'background': '#282828',
'mainBkg': '#3c3836',
'nodeBorder': '#504945',
'clusterBkg': '#32302f',
'titleColor': '#ebdbb2',
'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart TD
S1["📊 ขั้นที่ 1\nข้อมูลดิบ X\n(6 ตัวอย่าง × 2 features)"]
S2["➖ ขั้นที่ 2\nCentering\nX̃ = X - mean(X)"]
S3["🔢 ขั้นที่ 3\nCovariance Matrix Σ\nΣ = (1/n-1) X̃ᵀX̃"]
S4["🔍 ขั้นที่ 4\nEigen Decomposition\nΣv = λv"]
S5["📈 ขั้นที่ 5\nExplained Variance\nλ₁/(λ₁+λ₂) = 99.62%"]
S6["🎯 ขั้นที่ 6\nProjection\nZ = X̃ × E"]
S7["✅ ผลลัพธ์\nลดจาก 2D → 1D\nคงข้อมูลได้ 99.62%"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
R1["x̄₁=5.667, x̄₂=5.583"]
R2["Σ = [[6.667, 5.753],[5.753, 5.046]]"]
R3["λ₁=11.669, λ₂=0.045\ne₁=[0.754, 0.656]"]
R4["PC1 Scores: −4.842 ถึง +4.427"]
S2 -.->|"ผล"| R1
S3 -.->|"ผล"| R2
S4 -.->|"ผล"| R3
S6 -.->|"ผล"| R4
style S1 fill:#3c3836,stroke:#d79921,color:#ebdbb2
style S2 fill:#3c3836,stroke:#458588,color:#ebdbb2
style S3 fill:#3c3836,stroke:#b8bb26,color:#ebdbb2
style S4 fill:#3c3836,stroke:#d3869b,color:#ebdbb2
style S5 fill:#3c3836,stroke:#83a598,color:#ebdbb2
style S6 fill:#3c3836,stroke:#fe8019,color:#ebdbb2
style S7 fill:#3c3836,stroke:#fb4934,color:#ebdbb2
style R1 fill:#282828,stroke:#504945,color:#a89984
style R2 fill:#282828,stroke:#504945,color:#a89984
style R3 fill:#282828,stroke:#504945,color:#a89984
style R4 fill:#282828,stroke:#504945,color:#a89984
ตัวอย่าง Code Python พร้อม Visualization
"""
PCA แบบ Step-by-Step: คำนวณด้วยมือ (numpy) และเปรียบเทียบกับ scikit-learn
ข้อมูล: คะแนนคณิตศาสตร์และฟิสิกส์ของนักศึกษา 6 คน
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# ===== ข้อมูลตัวอย่าง =====
students = ['A', 'B', 'C', 'D', 'E', 'F']
X = np.array([
[2, 2.4], # A
[4, 4.0], # B
[5, 5.5], # C
[6, 5.5], # D
[8, 7.6], # E
[9, 8.5], # F
], dtype=float)
print("=" * 55)
print(" PCA คำนวณทีละขั้นตอน (Step-by-Step)")
print("=" * 55)
# ===== ขั้นที่ 1: แสดงข้อมูลดิบ =====
print("\n📊 ขั้นที่ 1: ข้อมูลดิบ")
print(f"{'นักศึกษา':>8} {'คณิต (x₁)':>10} {'ฟิสิกส์ (x₂)':>12}")
print("-" * 35)
for name, row in zip(students, X):
print(f"{name:>8} {row[0]:>10.1f} {row[1]:>12.1f}")
# ===== ขั้นที่ 2: Centering =====
mean = X.mean(axis=0)
X_centered = X - mean
print(f"\n➖ ขั้นที่ 2: Centering (หักค่าเฉลี่ย)")
print(f" x̄₁ = {mean[0]:.3f}, x̄₂ = {mean[1]:.3f}")
print(f"\n{'นักศึกษา':>8} {'x̃₁':>10} {'x̃₂':>10}")
print("-" * 33)
for name, row in zip(students, X_centered):
print(f"{name:>8} {row[0]:>+10.3f} {row[1]:>+10.3f}")
print(f"{'ผลรวม':>8} {X_centered[:,0].sum():>+10.3f} {X_centered[:,1].sum():>+10.3f} ← ต้องเป็น ≈ 0")
# ===== ขั้นที่ 3: Covariance Matrix =====
cov_matrix = np.cov(X_centered.T) # ddof=1 (หาร n-1) โดย default
print(f"\n🔢 ขั้นที่ 3: Covariance Matrix")
print(f" Var(x̃₁) = {cov_matrix[0,0]:.4f}")
print(f" Var(x̃₂) = {cov_matrix[1,1]:.4f}")
print(f" Cov(x̃₁, x̃₂) = {cov_matrix[0,1]:.4f}")
print(f"\n Σ = [[{cov_matrix[0,0]:.3f}, {cov_matrix[0,1]:.3f}],")
print(f" [{cov_matrix[1,0]:.3f}, {cov_matrix[1,1]:.3f}]]")
# ===== ขั้นที่ 4: Eigenvalues & Eigenvectors =====
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
# เรียงจากมากไปน้อย
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
print(f"\n🔍 ขั้นที่ 4: Eigenvalues และ Eigenvectors")
print(f" λ₁ = {eigenvalues[0]:.4f} → PC1 (สำคัญที่สุด)")
print(f" λ₂ = {eigenvalues[1]:.4f} → PC2")
print(f"\n Eigenvector PC1: [{eigenvectors[0,0]:.4f}, {eigenvectors[1,0]:.4f}]")
print(f" Eigenvector PC2: [{eigenvectors[0,1]:.4f}, {eigenvectors[1,1]:.4f}]")
dot_product = eigenvectors[:,0].dot(eigenvectors[:,1])
print(f"\n ตรวจสอบ Orthogonality: e₁ · e₂ = {dot_product:.6f} ≈ 0 ✅")
# ===== ขั้นที่ 5: Explained Variance =====
total_var = eigenvalues.sum()
explained = eigenvalues / total_var
print(f"\n📈 ขั้นที่ 5: Explained Variance Ratio")
print(f"\n {'PC':>4} {'Eigenvalue':>12} {'Explained %':>12} {'Cumulative %':>13}")
print(" " + "-" * 45)
cum = 0
for i, (ev, exp) in enumerate(zip(eigenvalues, explained)):
cum += exp
bar = "█" * int(exp * 30)
print(f" PC{i+1} {ev:>12.4f} {exp*100:>10.2f}% {cum*100:>11.2f}% {bar}")
print(f"\n ✅ PC1 อธิบาย {explained[0]*100:.2f}% ของ variance ทั้งหมด")
print(f" → สามารถลดจาก 2D เหลือ 1D ได้โดยแทบไม่สูญเสียข้อมูล!")
# ===== ขั้นที่ 6: Projection =====
Z = X_centered @ eigenvectors # PC Scores
print(f"\n🎯 ขั้นที่ 6: PC Scores (Projection)")
print(f"\n {'นักศึกษา':>8} {'x̃₁':>8} {'x̃₂':>8} {'PC1 Score':>10} {'PC2 Score':>10}")
print(" " + "-" * 52)
for name, xc, zr in zip(students, X_centered, Z):
print(f" {name:>8} {xc[0]:>+8.3f} {xc[1]:>+8.3f} "
f"{zr[0]:>+10.3f} {zr[1]:>+10.3f}")
# ===== เปรียบเทียบกับ scikit-learn =====
print(f"\n🔬 เปรียบเทียบกับ scikit-learn PCA:")
pca_sk = PCA(n_components=2)
Z_sk = pca_sk.fit_transform(X_centered)
print(f" scikit-learn Explained Variance: "
f"{pca_sk.explained_variance_ratio_[0]*100:.2f}%, "
f"{pca_sk.explained_variance_ratio_[1]*100:.2f}%")
print(f" |PC1 Score difference| max: {np.abs(np.abs(Z[:,0]) - np.abs(Z_sk[:,0])).max():.6f} ✅")
print(f" (ผลลัพธ์ตรงกัน — อาจต่างเครื่องหมายได้ เพราะ eigenvectors ไม่ unique)")
# ===== Visualization =====
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
fig.patch.set_facecolor('#282828')
fig.suptitle('PCA Step-by-Step: คะแนนคณิตศาสตร์ vs ฟิสิกส์',
color='#ebdbb2', fontsize=14, fontweight='bold')
for ax in axes:
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
for spine in ax.spines.values():
spine.set_edgecolor('#504945')
student_colors = ['#fb4934','#d79921','#b8bb26','#83a598','#458588','#d3869b']
# ── กราฟ 1: ข้อมูลดิบพร้อม PC Directions ──
ax1 = axes[0]
for i, (name, point) in enumerate(zip(students, X)):
ax1.scatter(*point, color=student_colors[i], s=120, zorder=5)
ax1.annotate(f" {name}", point, color=student_colors[i],
fontsize=12, fontweight='bold')
# วาด PC directions จากจุด mean
scale = 3.0
origin = mean
for j, (ev, color, label) in enumerate(
zip(eigenvectors.T, ['#d79921','#83a598'], ['PC1','PC2'])):
ax1.annotate('',
xy=origin + scale * eigenvalues[j]**0.5 * ev / np.linalg.norm(ev),
xytext=origin,
arrowprops=dict(arrowstyle='->', color=color, lw=2.5))
offset = [0.3, -0.4] if j == 0 else [0.3, 0.3]
ax1.text(*(origin + (scale * eigenvalues[j]**0.5 + 0.4) * ev / np.linalg.norm(ev) + offset),
label, color=color, fontsize=12, fontweight='bold')
ax1.axhline(mean[1], color='#504945', linestyle=':', linewidth=0.8)
ax1.axvline(mean[0], color='#504945', linestyle=':', linewidth=0.8)
ax1.set_xlabel('คณิตศาสตร์ (x₁)', color='#ebdbb2', fontsize=11)
ax1.set_ylabel('ฟิสิกส์ (x₂)', color='#ebdbb2', fontsize=11)
ax1.set_title('ข้อมูลดิบ + ทิศทาง PC', color='#ebdbb2', fontsize=12)
ax1.set_aspect('equal')
# ── กราฟ 2: Scree Plot ──
ax2 = axes[1]
pc_labels = ['PC1', 'PC2']
bars = ax2.bar(pc_labels, explained * 100,
color=['#d79921', '#83a598'], alpha=0.85, width=0.5)
ax2.plot(pc_labels, np.cumsum(explained) * 100,
'o--', color='#fb4934', linewidth=2, markersize=8, label='Cumulative')
for bar, pct in zip(bars, explained * 100):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{pct:.2f}%', ha='center', va='bottom',
color='#ebdbb2', fontsize=12, fontweight='bold')
ax2.axhline(99, color='#504945', linestyle=':', linewidth=1)
ax2.set_ylabel('Explained Variance (%)', color='#ebdbb2', fontsize=11)
ax2.set_title('Scree Plot\n(PC1 อธิบายได้ 99.62%)', color='#ebdbb2', fontsize=12)
ax2.set_ylim(0, 115)
ax2.legend(labelcolor='#ebdbb2', facecolor='#282828')
# ── กราฟ 3: ข้อมูลหลัง Projection บนแกน PC1 ──
ax3 = axes[2]
ax3.axhline(0, color='#504945', linewidth=1)
ax3.axhline(0, color='#d79921', linewidth=3, alpha=0.4, label='PC1 Axis')
for i, (name, score) in enumerate(zip(students, Z)):
ax3.scatter(score[0], score[1],
color=student_colors[i], s=120, zorder=5)
ax3.annotate(f" {name}\n({score[0]:+.2f})", (score[0], score[1]),
color=student_colors[i], fontsize=9,
textcoords='offset points', xytext=(5, 5))
# เส้น projection ลงบน PC1
ax3.plot([score[0], score[0]], [score[1], 0],
color=student_colors[i], linestyle='--', linewidth=0.8, alpha=0.6)
ax3.set_xlabel('PC1 Score (99.62% variance)', color='#ebdbb2', fontsize=11)
ax3.set_ylabel('PC2 Score (0.38% variance)', color='#ebdbb2', fontsize=11)
ax3.set_title('PC Scores: ข้อมูลในพิกัด PCA\n(PC2 แทบไม่มีข้อมูลเพิ่ม)',
color='#ebdbb2', fontsize=12)
ax3.legend(labelcolor='#ebdbb2', facecolor='#282828')
plt.tight_layout()
plt.savefig('/tmp/pca_step_by_step.png', dpi=110, bbox_inches='tight',
facecolor='#282828')
print(f"\nบันทึกกราฟ: /tmp/pca_step_by_step.png")
print("=" * 55)
ผลลัพธ์ที่คาดหวัง:
=======================================================
PCA คำนวณทีละขั้นตอน (Step-by-Step)
=======================================================
📊 ขั้นที่ 1: ข้อมูลดิบ
นักศึกษา คณิต (x₁) ฟิสิกส์ (x₂)
-----------------------------------
A 2.0 2.4
...
🔢 ขั้นที่ 3: Covariance Matrix
Var(x̃₁) = 6.6667
Var(x̃₂) = 5.0457
Cov(x̃₁, x̃₂) = 5.7533
📈 ขั้นที่ 5: Explained Variance Ratio
PC Eigenvalue Explained % Cumulative %
PC1 11.6690 99.62% 99.62% ██████████████████████████████
PC2 0.0447 0.38% 100.00%
✅ PC1 อธิบาย 99.62% ของ variance ทั้งหมด
ข้อควรระวังและข้อพึงรู้
| ประเด็น | รายละเอียด |
|---|---|
| Scaling ก่อน PCA | ถ้า features มี scale ต่างกัน (เช่น น้ำหนักกับส่วนสูง) ต้อง StandardScaler ก่อนเสมอ |
| Interpretability | PC axes ไม่มีความหมายทางกายภาพโดยตรง ตีความยากกว่า features เดิม |
| Linear Method | PCA จับได้เฉพาะ linear structure ถ้าข้อมูลซับซ้อน ใช้ Kernel PCA หรือ t-SNE แทน |
| Information Loss | เมื่อลดมิติ ข้อมูลบางส่วนหายไปเสมอ ต้องตัดสินใจว่า % ที่ยอมรับได้คือเท่าไร |
| Sign of Eigenvectors | เครื่องหมาย (+/−) ของ eigenvector ไม่ unique อาจต่างกันระหว่าง implementations |
| Number of Components | เลือกโดยดู Cumulative Explained Variance ≥ 95% (หรือ 99% สำหรับงานที่ต้องการความแม่นยำสูง) |
เปรียบเทียบ PCA กับ t-SNE
| คุณสมบัติ | PCA | t-SNE |
|---|---|---|
| ประเภท | Linear | Non-linear |
| เป้าหมาย | Maximize global variance | Preserve local structure |
| ความเร็ว | เร็วมาก O(nd²) | ช้า O(n²) |
| Interpretability | สูง (eigenvectors อธิบายได้) | ต่ำ (ไม่ preserve global structure) |
| Transform ข้อมูลใหม่ | ทำได้ทันที | ต้องฝึกใหม่ |
| ใช้เพื่อ | Preprocessing + Visualization | Visualization เท่านั้น |
| Deterministic | ใช่ | ไม่ (random seed ต้องกำหนด) |
Reinforcement Learning (RL) เป็นการเรียนรู้ผ่านการโต้ตอบกับสภาพแวดล้อม (environment) Agent เรียนรู้ว่าควรทำ action ใดในแต่ละ state เพื่อให้ได้ รางวัล (reward) สะสมสูงสุด ในระยะยาว
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart LR
AG["🤖 Agent - (ตัวแทน)"]
ENV["🌍 Environment - (สภาพแวดล้อม)"]
AG -->|"Action (a) - การกระทำ"| ENV
ENV -->|"State (s') - สถานะใหม่"| AG
ENV -->|"Reward (r) - รางวัล"| AG
style AG fill:#3c3836,stroke:#d79921,color:#ebdbb2
style ENV fill:#3c3836,stroke:#83a598,color:#ebdbb2
Q-Learning เป็น model-free RL algorithm ที่เรียนรู้ Q-function (action-value function) ซึ่งบอกว่า "การทำ action a ใน state s จะได้รางวัลสะสมเท่าใด"
Bellman Equation (Q-Learning Update):
โดยที่:
ตัวอย่างการคำนวณ Q-Learning:
สมมติ: α=0.1, γ=0.9, Q(s1,a1)=0.0 เดิม, ได้ r=+10, Q(s2,best)=5.0
"""
Q-Learning บนสภาพแวดล้อม FrozenLake (จำลองด้วย numpy)
เนื่องจาก OpenAI Gym อาจต้องติดตั้งเพิ่ม จึงสร้าง environment เอง
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# ===== FrozenLake 4x4 Grid World =====
# S = Start, F = Frozen (ปลอดภัย), H = Hole (ตกหลุม), G = Goal
GRID = [
'S', 'F', 'F', 'F',
'F', 'H', 'F', 'H',
'F', 'F', 'F', 'H',
'H', 'F', 'F', 'G'
]
N_STATES = 16
N_ACTIONS = 4 # 0=ซ้าย, 1=ลง, 2=ขวา, 3=ขึ้น
def get_next_state(state, action):
"""คำนวณ state ถัดไปและ reward"""
row, col = state // 4, state % 4
# Stochastic: 66% ไปทิศที่เลือก, 17% เลี้ยวซ้าย, 17% เลี้ยวขวา
actual_action = np.random.choice(
[action, (action-1)%4, (action+1)%4],
p=[0.66, 0.17, 0.17]
)
dr = {0: 0, 1: 1, 2: 0, 3: -1}
dc = {0: -1, 1: 0, 2: 1, 3: 0}
new_row = max(0, min(3, row + dr[actual_action]))
new_col = max(0, min(3, col + dc[actual_action]))
new_state = new_row * 4 + new_col
cell = GRID[new_state]
if cell == 'G':
return new_state, 1.0, True # ถึงเป้าหมาย!
elif cell == 'H':
return new_state, -1.0, True # ตกหลุม
else:
return new_state, -0.01, False # ก้าวต่อไป
# ===== Q-Learning Algorithm =====
ALPHA = 0.1 # Learning rate
GAMMA = 0.9 # Discount factor
EPSILON = 1.0 # Exploration rate (epsilon-greedy)
EPSILON_DECAY = 0.995
EPSILON_MIN = 0.01
N_EPISODES = 3000
Q = np.zeros((N_STATES, N_ACTIONS)) # Q-table เริ่มต้นเป็น 0
rewards_per_episode = []
success_rate_history = []
print("=== Q-Learning: FrozenLake 4x4 ===")
print(f"α={ALPHA}, γ={GAMMA}, Episodes={N_EPISODES}")
for episode in range(N_EPISODES):
state = 0 # เริ่มที่ Start (0,0)
total_reward = 0
done = False
while not done:
# Epsilon-Greedy Policy
if np.random.random() < EPSILON:
action = np.random.randint(N_ACTIONS) # Explore
else:
action = np.argmax(Q[state]) # Exploit
# ก้าวไปข้างหน้า
next_state, reward, done = get_next_state(state, action)
# Q-Learning Update (Bellman Equation)
old_q = Q[state, action]
best_next_q = np.max(Q[next_state]) if not done else 0
Q[state, action] = old_q + ALPHA * (
reward + GAMMA * best_next_q - old_q
)
state = next_state
total_reward += reward
# Decay Epsilon
EPSILON = max(EPSILON_MIN, EPSILON * EPSILON_DECAY)
rewards_per_episode.append(total_reward)
# บันทึก success rate ทุก 100 episodes
if (episode + 1) % 100 == 0:
recent_rewards = rewards_per_episode[-100:]
success_rate = sum(1 for r in recent_rewards if r > 0) / 100
success_rate_history.append(success_rate)
if (episode + 1) % 500 == 0:
print(f" Episode {episode+1:4d}: ε={EPSILON:.3f}, "
f"Success Rate={success_rate:.2%}")
# แสดง Optimal Policy
actions_str = ['←', '↓', '→', '↑']
print(f" - === Optimal Policy (Q-Table) ===")
print(" 0 1 2 3")
for row in range(4):
line = f"{row}: "
for col in range(4):
state = row * 4 + col
cell = GRID[state]
if cell in ['H', 'G']:
line += f" {cell} "
else:
best = np.argmax(Q[state])
line += f" {actions_str[best]} "
print(line)
print(f" - === Q-Values ของ Start State (0) ===")
for i, (a, q) in enumerate(zip(['←', '↓', '→', '↑'], Q[0])):
print(f" Action {a}: Q = {q:.4f}")
# Visualize การเรียนรู้
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.patch.set_facecolor('#282828')
for ax in axes:
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
# Rolling average reward
window = 100
rolling_avg = np.convolve(rewards_per_episode,
np.ones(window)/window, mode='valid')
axes[0].plot(rolling_avg, color='#d79921', linewidth=1.5)
axes[0].set_xlabel('Episode', color='#ebdbb2')
axes[0].set_ylabel(f'Avg Reward ({window} eps)', color='#ebdbb2')
axes[0].set_title('Q-Learning: Reward ต่อ Episode', color='#ebdbb2', fontsize=12)
axes[0].axhline(y=0, color='#504945', linestyle='--')
# Success Rate
axes[1].plot(range(100, N_EPISODES+1, 100), success_rate_history,
'o-', color='#b8bb26', linewidth=2, markersize=4)
axes[1].set_xlabel('Episode', color='#ebdbb2')
axes[1].set_ylabel('Success Rate', color='#ebdbb2')
axes[1].set_title('อัตราความสำเร็จ (ทุก 100 Episodes)', color='#ebdbb2', fontsize=12)
axes[1].yaxis.set_major_formatter(plt.FuncFormatter(lambda x,_: f'{x:.0%}'))
plt.tight_layout()
plt.savefig('/tmp/qlearning_results.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print(" - บันทึกกราฟแล้ว: /tmp/qlearning_results.png")
| ฟังก์ชัน | สัญลักษณ์ | ความหมาย |
|---|---|---|
| Policy | π(a|s) | ความน่าจะเป็นที่เลือก action a ใน state s |
| State Value | V(s) | รางวัลสะสมที่คาดหวังจาก state s ตาม policy π |
| Action Value | Q(s,a) | รางวัลสะสมที่คาดหวังเมื่อทำ a ใน s ตาม π |
| Advantage | A(s,a) | Q(s,a) - V(s): ความได้เปรียบของ action นั้น |
Feature Engineering คือกระบวนการสร้าง, แปลง, หรือเลือก features ที่เหมาะสมเพื่อให้โมเดลทำงานได้ดีขึ้น ถือเป็น "ศิลปะ" ของ Machine Learning
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart TD
RAW["📥 Raw Data - ข้อมูลดิบ"]
subgraph FE["Feature Engineering"]
direction TB
FE1["🔧 Feature Creation - สร้าง Feature ใหม่"]
FE2["🔄 Feature Transformation - แปลง Feature"]
FE3["🔍 Feature Selection - เลือก Feature"]
FE1 --> FE2 --> FE3
end
subgraph TRANS["Transformations"]
T1["Scaling - (StandardScaler, MinMax)"]
T2["Encoding - (One-Hot, Label)"]
T3["Binning - (ช่วงอายุ, ราคา)"]
T4["Log Transform - (skewed data)"]
T5["Interaction Terms - (x1 × x2)"]
end
subgraph SEL["Feature Selection Methods"]
S1["Filter Methods - (Correlation, Chi-Square)"]
S2["Wrapper Methods - (RFE, Forward Selection)"]
S3["Embedded Methods - (LASSO, Tree Importance)"]
end
RAW --> FE
FE2 --- TRANS
FE3 --- SEL
style RAW fill:#3c3836,stroke:#d79921,color:#ebdbb2
style FE fill:#282828,stroke:#504945,color:#ebdbb2
style TRANS fill:#282828,stroke:#458588,color:#ebdbb2
style SEL fill:#282828,stroke:#b8bb26,color:#ebdbb2
"""
Feature Engineering & Selection ครบวงจร
ตัวอย่าง: ข้อมูลการอยู่รอด (Titanic-style)
"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
from sklearn.feature_selection import (SelectKBest, f_classif,
RFE, mutual_info_classif)
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
# ===== สร้างข้อมูลจำลอง =====
np.random.seed(42)
n = 500
df = pd.DataFrame({
'age': np.random.randint(1, 80, n),
'gender': np.random.choice(['Male', 'Female'], n),
'pclass': np.random.choice([1, 2, 3], n, p=[0.2, 0.3, 0.5]),
'fare': np.random.exponential(30, n).clip(0, 500),
'siblings': np.random.choice([0,1,2,3], n, p=[0.6,0.25,0.1,0.05]),
'embarked': np.random.choice(['C','S','Q'], n, p=[0.2, 0.7, 0.1]),
'has_cabin': np.random.choice([0, 1], n, p=[0.7, 0.3])
})
# สร้าง target ที่สมเหตุสมผล
df['survived'] = (
(df['gender'] == 'Female').astype(int) * 0.5 +
(df['pclass'] == 1).astype(int) * 0.3 +
(df['age'] < 15).astype(int) * 0.2 +
(df['fare'] > 50).astype(int) * 0.1 +
np.random.normal(0, 0.15, n) > 0.3
).astype(int)
print("=== ข้อมูลดิบ ===")
print(df.head())
print(f" - สัดส่วนรอดชีวิต: {df['survived'].mean():.2%}")
# ===== Feature Engineering =====
df_eng = df.copy()
# 1. Feature Creation: สร้าง features ใหม่
df_eng['family_size'] = df['siblings'] + 1 # รวมตัวเอง
df_eng['is_alone'] = (df_eng['family_size'] == 1).astype(int)
df_eng['age_group'] = pd.cut(df['age'],
bins=[0, 12, 18, 35, 60, 100],
labels=['child','teen','young','adult','senior'])
df_eng['fare_per_class'] = df['fare'] / df['pclass']
df_eng['fare_log'] = np.log1p(df['fare']) # Log transform
# 2. Encoding
df_eng['gender_enc'] = (df['gender'] == 'Female').astype(int)
embarked_dummies = pd.get_dummies(df['embarked'], prefix='emb')
df_eng = pd.concat([df_eng, embarked_dummies], axis=1)
# อายุกลุ่ม → one-hot
age_dummies = pd.get_dummies(df_eng['age_group'], prefix='age')
df_eng = pd.concat([df_eng, age_dummies], axis=1)
# 3. เลือก Features ที่จะใช้ (ตัด columns ดิบออก)
drop_cols = ['gender', 'embarked', 'age_group', 'survived']
feature_cols = [c for c in df_eng.columns if c not in drop_cols]
X = df_eng[feature_cols].fillna(0).values
y = df['survived'].values
print(f" - === หลัง Feature Engineering ===")
print(f"จำนวน features: {len(feature_cols)}")
print(f"Features: {feature_cols}")
# ===== Feature Selection =====
# วิธีที่ 1: SelectKBest (Filter Method)
selector_kb = SelectKBest(score_func=f_classif, k=8)
X_kb = selector_kb.fit_transform(X, y)
kb_scores = selector_kb.scores_
selected_kb = [feature_cols[i] for i in selector_kb.get_support(indices=True)]
print(f" - SelectKBest (k=8): {selected_kb}")
# วิธีที่ 2: Random Forest Feature Importance (Embedded)
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
importances = rf.feature_importances_
sorted_idx = np.argsort(importances)[::-1][:10] # Top 10
print(f" - Top 10 Features (Random Forest Importance):")
for i in sorted_idx[:5]:
bar = "█" * int(importances[i] * 100)
print(f" {feature_cols[i]:<25} {bar} {importances[i]:.4f}")
# เปรียบเทียบผลลัพธ์
rf_all = cross_val_score(rf, X, y, cv=5, scoring='accuracy').mean()
rf_best = cross_val_score(rf, X[:, sorted_idx[:8]], y, cv=5, scoring='accuracy').mean()
print(f" - Accuracy (ทุก features): {rf_all:.4f}")
print(f"Accuracy (top 8 features): {rf_best:.4f}")
# Visualize Feature Importance
fig, ax = plt.subplots(figsize=(10, 6))
fig.patch.set_facecolor('#282828')
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
top_features = [feature_cols[i] for i in sorted_idx[:10]]
top_scores = importances[sorted_idx[:10]]
colors = ['#d79921' if i < 3 else '#83a598' for i in range(10)]
bars = ax.barh(range(10), top_scores[::-1], color=colors[::-1], alpha=0.8)
ax.set_yticks(range(10))
ax.set_yticklabels(top_features[::-1], color='#ebdbb2', fontsize=10)
ax.set_xlabel('Feature Importance', color='#ebdbb2', fontsize=11)
ax.set_title('Top 10 Feature Importance (Random Forest)', color='#ebdbb2', fontsize=12)
for bar, score in zip(bars, top_scores[::-1]):
ax.text(score + 0.002, bar.get_y() + bar.get_height()/2,
f'{score:.4f}', va='center', color='#ebdbb2', fontsize=9)
plt.tight_layout()
plt.savefig('/tmp/feature_importance.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print(" - บันทึกกราฟแล้ว")
Overfitting คือปรากฏการณ์ที่โมเดลเรียนรู้ข้อมูลฝึกได้ดีเกินไป จนไม่สามารถ generalize ไปยังข้อมูลใหม่ได้ Underfitting คือตรงข้าม โมเดลเรียนรู้น้อยเกินไป
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
flowchart LR
UF["🔵 Underfitting - High Bias - Low Variance - - Train Error: สูง - Test Error: สูง"]
GF["🟢 Good Fit - Low Bias - Low Variance - - Train Error: ต่ำ - Test Error: ต่ำ"]
OF["🔴 Overfitting - Low Bias - High Variance - - Train Error: ต่ำมาก - Test Error: สูง"]
UF -->|"เพิ่มความซับซ้อน - (เพิ่ม features, epochs)"| GF
GF -->|"ซับซ้อนเกินไป - (ลด regularization)"| OF
OF -->|"Regularization - (L1, L2, Dropout)"| GF
style UF fill:#3c3836,stroke:#458588,color:#ebdbb2
style GF fill:#3c3836,stroke:#b8bb26,color:#ebdbb2
style OF fill:#3c3836,stroke:#fb4934,color:#ebdbb2
Regularization เป็นเทคนิคเพิ่ม penalty term เข้าไปใน loss function เพื่อป้องกัน overfitting
โดยที่ λ = regularization strength (ยิ่งมาก ยิ่ง penalize coefficients ใหญ่)
ความแตกต่างหลัก L1 vs L2:
| คุณสมบัติ | L1 (LASSO) | L2 (Ridge) |
|---|---|---|
| ผลต่อ coefficients | บางตัวเป็น 0 (Sparse) | หดเล็กลงทุกตัว |
| Feature Selection | ใช่ (อัตโนมัติ) | ไม่ |
| เมื่อ features สัมพันธ์กัน | เลือกหนึ่งตัว | กระจายน้ำหนักเท่าๆ กัน |
| เหมาะกับ | Many irrelevant features | Multicollinearity |
"""
สาธิต Overfitting และการใช้ Regularization
พร้อมเปรียบเทียบ L1, L2, ElasticNet
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, validation_curve
from sklearn.metrics import mean_squared_error
from sklearn.datasets import make_regression
# ===== สร้างข้อมูล: ความสัมพันธ์ไม่เชิงเส้น =====
np.random.seed(42)
n = 80
X_raw = np.sort(np.random.uniform(-3, 3, n))
y_raw = np.sin(X_raw) + 0.5 * np.cos(2*X_raw) + np.random.normal(0, 0.3, n)
X_raw = X_raw.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(
X_raw, y_raw, test_size=0.2, random_state=42
)
# ===== เปรียบเทียบ Polynomial Degree ต่างๆ =====
degrees = [1, 3, 6, 15]
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.patch.set_facecolor('#282828')
fig.suptitle('Underfitting vs Overfitting: Polynomial Regression',
color='#ebdbb2', fontsize=14)
X_plot = np.linspace(-3, 3, 300).reshape(-1, 1)
colors_degree = ['#458588', '#b8bb26', '#d79921', '#fb4934']
for idx, (degree, ax, color) in enumerate(zip(degrees, axes.flat, colors_degree)):
ax.set_facecolor('#3c3836')
ax.tick_params(colors='#ebdbb2')
model = Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('reg', LinearRegression())
])
model.fit(X_train, y_train)
y_plot = model.predict(X_plot)
train_rmse = np.sqrt(mean_squared_error(y_train, model.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
ax.scatter(X_train, y_train, color='#83a598', s=30, alpha=0.7, label='Train')
ax.scatter(X_test, y_test, color='#d3869b', s=30, alpha=0.7, label='Test', marker='^')
ax.plot(X_plot, y_plot, color=color, linewidth=2.5)
status = "✅ Good Fit" if degree == 3 else ("⬇️ Underfit" if degree == 1 else "🔴 Overfit")
ax.set_title(
f'Degree={degree} {status} - '
f'Train RMSE={train_rmse:.3f}, Test RMSE={test_rmse:.3f}',
color='#ebdbb2', fontsize=11
)
ax.legend(labelcolor='#ebdbb2', facecolor='#282828', fontsize=8)
ax.set_ylim(-2.5, 2.5)
plt.tight_layout()
plt.savefig('/tmp/overfitting_demo.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print("บันทึกกราฟ Overfitting Demo แล้ว")
# ===== เปรียบเทียบ L1 vs L2 Regularization =====
# ใช้ข้อมูล Regression ที่มี many features
X_reg, y_reg = make_regression(n_samples=200, n_features=50,
n_informative=10, noise=20, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X_reg, y_reg, test_size=0.2)
alphas = np.logspace(-3, 3, 50)
models_reg = {
'Ridge (L2)': Ridge,
'LASSO (L1)': Lasso,
'ElasticNet (L1+L2)': lambda a: ElasticNet(alpha=a, l1_ratio=0.5),
}
fig2, ax2 = plt.subplots(figsize=(10, 5))
fig2.patch.set_facecolor('#282828')
ax2.set_facecolor('#3c3836')
ax2.tick_params(colors='#ebdbb2')
reg_colors = ['#83a598', '#fb4934', '#d79921']
for (name, ModelClass), color in zip(models_reg.items(), reg_colors):
test_errors = []
for alpha in alphas:
if name == 'ElasticNet (L1+L2)':
m = ModelClass(alpha)
else:
m = ModelClass(alpha=alpha)
m.fit(X_tr, y_tr)
test_errors.append(np.sqrt(mean_squared_error(y_te, m.predict(X_te))))
ax2.semilogx(alphas, test_errors, color=color, linewidth=2, label=name)
ax2.set_xlabel('Alpha (λ) - Regularization Strength', color='#ebdbb2', fontsize=11)
ax2.set_ylabel('Test RMSE', color='#ebdbb2', fontsize=11)
ax2.set_title('L1 vs L2 vs ElasticNet Regularization - (Test Error vs Alpha)',
color='#ebdbb2', fontsize=12)
ax2.legend(labelcolor='#ebdbb2', facecolor='#3c3836')
ax2.axvline(x=1.0, color='#504945', linestyle=':', linewidth=1)
plt.tight_layout()
plt.savefig('/tmp/regularization_comparison.png', dpi=100, bbox_inches='tight',
facecolor='#282828')
print("บันทึกกราฟ Regularization Comparison แล้ว")
| เทคนิค | วิธีการ | เหมาะกับ |
|---|---|---|
| Cross-Validation | ประเมินบนหลาย folds | ทุกโมเดล |
| Early Stopping | หยุดฝึกเมื่อ val loss ไม่ลด | Neural Networks |
| Dropout | สุ่มปิด neurons | Neural Networks |
| Data Augmentation | เพิ่มข้อมูลฝึก | Computer Vision, NLP |
| Ensemble Methods | รวมหลายโมเดล | ทุกโมเดล |
| Pruning | ตัด branches ที่ไม่สำคัญ | Decision Trees |
| Regularization | เพิ่ม penalty | Linear Models |
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#282828', 'primaryTextColor': '#ebdbb2',
'primaryBorderColor': '#504945', 'lineColor': '#d79921',
'background': '#282828', 'mainBkg': '#3c3836',
'nodeBorder': '#504945', 'clusterBkg': '#32302f',
'titleColor': '#ebdbb2', 'fontFamily': 'Sarabun, sans-serif'
}}}%%
mindmap
root((Machine - Learning - Foundations))
Supervised Learning
Classification
Decision Tree
KNN
Naive Bayes
Random Forest
Regression
Linear Regression
Polynomial Regression
Model Evaluation
Cross-Validation
Confusion Matrix
F1, AUC-ROC
Unsupervised Learning
Clustering
K-Means
Hierarchical
DBSCAN
Dim Reduction
PCA
t-SNE
Reinforcement Learning
Q-Learning
Policy Functions
Bellman Equation
Feature Engineering
Scaling & Encoding
Feature Creation
Feature Selection
Regularization
L1 LASSO
L2 Ridge
ElasticNet
Overfitting Prevention
Machine Learning Foundations ครอบคลุมหัวข้อสำคัญ 5 ด้าน:
Supervised Learning — เรียนรู้จากข้อมูลมีป้ายกำกับ ใช้สำหรับ Classification (Decision Tree, KNN, Naive Bayes) และ Regression (Linear, Polynomial) โดยต้องประเมินโมเดลด้วย Cross-Validation และ Confusion Matrix
Unsupervised Learning — ค้นหารูปแบบในข้อมูลไม่มีป้ายกำกับ ผ่าน Clustering (K-Means, DBSCAN, Hierarchical) และ Dimensionality Reduction (PCA, t-SNE) เพื่อทำความเข้าใจโครงสร้างข้อมูล
Reinforcement Learning — เรียนรู้ผ่านการโต้ตอบกับสภาพแวดล้อม โดย Agent สะสม reward สูงสุดด้วย Q-Learning และ Bellman Equation
Feature Engineering — กระบวนการสำคัญที่เปลี่ยนข้อมูลดิบเป็น features ที่มีคุณภาพ ทั้งการสร้าง, แปลง (Scaling, Encoding), และเลือก features (Filter, Wrapper, Embedded methods)
Overfitting & Regularization — ปัญหาสำคัญของ ML ที่แก้ได้ด้วย L1/L2 Regularization, Cross-Validation, Early Stopping และ Ensemble Methods
Mitchell, T. M. (1997). Machine Learning. McGraw-Hill. — ตำราพื้นฐาน ML คลาสสิก
Géron, A. (2022). Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow (3rd ed.). O'Reilly Media. — แนวปฏิบัติจริงด้วย Python
Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. — เนื้อหาเชิงทฤษฎีระดับสูง
Sutton, R. S., & Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.). MIT Press. — RL reference หลัก [Online: http://incompleteideas.net/book/the-book.html]
Pedregosa, F. et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research, 12, 2825–2830. — เอกสาร scikit-learn อย่างเป็นทางการ
scikit-learn Documentation. (2024). https://scikit-learn.org/stable/documentation.html
Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. [Online: https://www.deeplearningbook.org/]
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning (2nd ed.). Springer. [Online: https://hastie.su.domains/ElemStatLearn/]
📌 หมายเหตุสำหรับผู้เรียน: เอกสารนี้มุ่งให้ความเข้าใจทั้งทฤษฎีและการปฏิบัติควบคู่กัน ขอแนะนำให้ลองรัน code ตัวอย่างทุกส่วน และทดลองปรับ hyperparameters เพื่อสังเกตผลที่เปลี่ยนแปลง การเรียนรู้ Machine Learning ต้องอาศัยการทดลองซ้ำๆ จึงจะเกิดความเข้าใจที่ลึกซึ้ง