Random Forest

บทความโดย ผศ.ดร.ณัฐโชติ พรหมฤทธิ์ และ อ.ดร.สัจจาภรณ์ ไวจรรยา
ภาควิชาคอมพิวเตอร์
คณะวิทยาศาสตร์
มหาวิทยาลัยศิลปากร

Random Forest คือ Algorithm การเรียนรู้ของเครื่อง (Machine Learning) ที่เกิดจากการรวม Decision Tree หลาย ๆ ต้นเข้าด้วยกัน โดยแต่ละต้นจะถูกสร้างขึ้นจากคุณลักษณะของข้อมูล (Feature) ที่สุ่มมาเพียงบางส่วน

Random Forest เป็นเทคนิคการรวม Model หลาย ๆ Model เพื่อสร้าง Model ที่มีประสิทธิภาพสูงขึ้น (Ensemble Learning) ซึ่งใช้หลักการของ Bagging (Bootstrap Aggregating) เพื่อเพิ่มความแม่นยำและลดปัญหา Overfitting โดยการสุ่มตัวอย่าง (Random Sampling) แบบใส่คืนเพื่อสร้างชุดข้อมูลย่อยหลายชุดจากชุดข้อมูลต้นฉบับ ทำให้แต่ละชุดข้อมูลย่อยอาจมีตัวอย่างซ้ำกันได้ สำหรับนำมาสร้าง Model หลาย ๆ Model โดยแต่ละ Model จะใช้ชุดข้อมูลย่อยที่สุ่มมาในการสร้าง

สมมติว่าเรามีชุดข้อมูลต้นฉบับที่มีตัวเลข 5 ตัว ดังนี้

[1, 2, 3, 4, 5]

การสุ่มตัวอย่างแบบ Bootstrap

  1. เลือกตัวเลขจากชุดข้อมูลต้นฉบับแบบสุ่ม 1 ตัว
  2. นำตัวเลขที่เลือกไว้บันทึกลงในชุดข้อมูลใหม่
  3. คืนตัวเลขที่เลือกลงในชุดข้อมูลต้นฉบับ (ใส่คืน)
  4. ทำซ้ำขั้นตอนที่ 1-3 จนกว่าจะได้จำนวนตัวอย่างเท่ากับชุดข้อมูลต้นฉบับ

ตัวอย่างผลลัพธ์ที่อาจเกิดขึ้นจากการสุ่มแบบ Bootstrap

ชุดข้อมูลย่อยที่ 1 [1, 3, 3, 5, 2]
ชุดข้อมูลย่อยที่ 2 [4, 2, 1, 1, 5]
ชุดข้อมูลย่อยที่ 3 [2, 4, 4, 3, 5]

แต่ละชุดข้อมูลย่อยมีจำนวนตัวอย่างเท่ากับชุดข้อมูลต้นฉบับ (5 ตัว) โดยตัวเลขบางตัวอาจปรากฏซ้ำในชุดข้อมูลย่อยเดียวกัน และบางตัวอาจไม่ถูกเลือกเลยในบางชุดข้อมูลย่อย

ในการทำนายผล (Prediction) แบบการจำแนกประเภท (Classification) จะใช้การโหวตเสียงข้างมาก ส่วนการทำนายแบบ Regression จะใช้ค่าเฉลี่ยของผลลัพธ์จากทุก Model

Random Forest มักให้ผลลัพธ์ที่แม่นยำกว่า Decision Tree เดี่ยว ๆ และช่วยลดปัญหา Overfitting ได้ดีกว่า Decision Tree ต้นเดียว รวมทั้งจากการมีความหลากหลายของ Decision Tree ทำให้มันสามารถจับรูปแบบที่ซับซ้อนได้ดีขึ้น

import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
# โหลดข้อมูล
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.Series(data.target, name='target')
# แบ่งข้อมูลเป็นชุดฝึกสอนและชุดทดสอบ
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2)
# สร้างและฝึกสอน Model Random Forest
rf_classifier = RandomForestClassifier(n_estimators=100, random_state=2)
rf_classifier.fit(X_train, y_train)
# ทำนายผลลัพธ์
y_pred = rf_classifier.predict(X_test)
# ประเมินประสิทธิภาพของ Model
print("Accuracy:", accuracy_score(y_test, y_pred))
print("\nClassification Report:\n", classification_report(y_test, y_pred))

Accuracy: 0.9473684210526315

# สร้าง Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(10,7))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title('Confusion Matrix')
plt.ylabel('Actual Label')
plt.xlabel('Predicted Label')
plt.show()
# แสดงความสำคัญของ Feature
feature_importance = pd.DataFrame({'feature': data.feature_names, 'importance': rf_classifier.feature_importances_})
feature_importance = feature_importance.sort_values('importance', ascending=False).head(10)
plt.figure(figsize=(10,6))
sns.barplot(x='importance', y='feature', data=feature_importance)
plt.title('Top 10 Important Features')
plt.tight_layout()
plt.show()
# เปรียบเทียบประสิทธิภาพกับ Decision Tree
from sklearn.tree import DecisionTreeClassifier

dt_classifier = DecisionTreeClassifier(random_state=2)
dt_classifier.fit(X_train, y_train)
dt_pred = dt_classifier.predict(X_test)

print("\nRandom Forest Accuracy:", accuracy_score(y_test, y_pred))
print("Decision Tree Accuracy:", accuracy_score(y_test, dt_pred))

Random Forest Accuracy: 0.9473684210526315
Decision Tree Accuracy: 0.9122807017543859

# เปรียบเทียบการทำนายของ Random Forest และ Decision Tree
comparison = pd.DataFrame({'Actual': y_test, 'Random Forest': y_pred, 'Decision Tree': dt_pred})
print("\nSample Predictions:")
print(comparison.sample(10))

สรุปกระบวนการสร้าง Random Forest

  1. สุ่มตัวอย่างข้อมูลด้วยวิธี Bootstrap (การสุ่มแบบมีการทดแทน)
  2. สร้าง Decision Tree จากข้อมูลที่สุ่มได้โดยในแต่ละ Node จะสุ่มเลือกคุณลักษณะ (Feature) มาใช้ในการแบ่งข้อมูล
  3. ทำซ้ำขั้นตอนที่ 1 และ 2 จนได้จำนวน Tree ตามที่กำหนด
  4. ในการทำนายใช้การโหวตเสียงส่วนใหญ่ (สำหรับ Classification) หรือค่าเฉลี่ย (สำหรับ Regression) จากผลลัพธ์ของ Tree ทุกต้น

การปรับแต่ง Parameter ที่สำคัญ

  1. n_estimators คือ จำนวน Decision Tree ใน Random Forest
  2. max_depth คือ ความลึกสูงสุดของแต่ละ Tree
  3. min_samples_split คือ จำนวนตัวอย่างขั้นต่ำที่ต้องมีเพื่อแบ่ง Node ภายใน
  4. min_samples_leaf คือ จำนวนตัวอย่างขั้นต่ำที่ต้องมีในแต่ละ Leaf Node
  5. max_features คือ จำนวนคุณลักษณะที่พิจารณาเมื่อหาการแบ่งที่ดีที่สุด

การใช้ชุดข้อมูล Heart Disease จาก UCI Machine Learning Repository เพื่อสร้าง Random Forest สำหรับการทำนายความเสี่ยงของโรคหัวใจ

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_curve, auc
from sklearn.preprocessing import StandardScaler
# โหลดข้อมูล
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
columns = ["age", "sex", "cp", "trestbps", "chol", "fbs", "restecg", "thalach", "exang", "oldpeak", "slope", "ca", "thal", "num"]
data = pd.read_csv(url, names=columns, na_values="?")
data.shape

(303, 14)

data.head()
# จัดการค่าที่หายไป
data = data.dropna()
# แปลง Column เป้าหมายให้เป็น Binary (0 = ไม่เป็นโรคหัวใจ, 1 = เป็นโรคหัวใจ)
data["num"] = (data["num"] > 0).astype(int)
# แยก Feature และ target
X = data.drop("num", axis=1)
y = data["num"]
# แบ่งข้อมูลเป็นชุดฝึกสอนและชุดทดสอบ
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2)
# สร้างและฝึกสอน Model Random Forest
rf_classifier = RandomForestClassifier(n_estimators=100, random_state=2)
rf_classifier.fit(X_train, y_train)
# ทำนายผลลัพธ์
y_pred = rf_classifier.predict(X_test)
# ประเมินประสิทธิภาพของ Model
print("Accuracy:", accuracy_score(y_test, y_pred))
print("\nClassification Report:\n", classification_report(y_test, y_pred))

Accuracy: 0.8333333333333334

# สร้าง Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(10,7))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title('Confusion Matrix')
plt.ylabel('Actual Label')
plt.xlabel('Predicted Label')
plt.show()
# แสดงความสำคัญของ Feature
feature_importance = pd.DataFrame({'feature': X.columns, 'importance': rf_classifier.feature_importances_})
feature_importance = feature_importance.sort_values('importance', ascending=False)
plt.figure(figsize=(10,6))
sns.barplot(x='importance', y='feature', data=feature_importance)
plt.title('Feature Importance')
plt.tight_layout()
plt.show()
# สร้าง ROC Curve
y_pred_proba = rf_classifier.predict_proba(X_test)[:, 1]
fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
roc_auc = auc(fpr, tpr)

plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc="lower right")
plt.show()
# ปรับแต่ง Parameter ด้วย GridSearchCV
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [5, 10, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

grid_search = GridSearchCV(estimator=RandomForestClassifier(random_state=2), 
                           param_grid=param_grid, 
                           cv=5, 
                           n_jobs=-1, 
                           verbose=2)

grid_search.fit(X_train, y_train)

จำนวนชุด Parameter ที่เป็นไปได้ทั้งหมดที่ GridSearchCV จะทดสอบ ซึ่งเกิดจากการรวมกันของค่าต่าง ๆ ใน Parameter param_grid ที่กำหนดไว้ คือ 81 ชุด Parameter

ข้อมูลจะถูกแบ่งออกเป็น 5 ส่วนเท่า ๆ กัน โดยแต่ละครั้งจะใช้ 4 ส่วนเป็นข้อมูลฝึกฝน และ 1 ส่วนเป็นข้อมูลทดสอบ

สมมติว่า X_train มีตัวอย่างทั้งหมด 237 ตัวอย่าง การทำ 5-fold Cross-validation จะเป็นดังนี้

แบ่งข้อมูลออกเป็น 5 ส่วนเท่า ๆ กัน (หรือใกล้เคียงที่สุด)
Fold 1 ตัวอย่างที่ 1-47
Fold 2 ตัวอย่างที่ 48-94
Fold 3 ตัวอย่างที่ 95-141
Fold 4 ตัวอย่างที่ 142-188
Fold 5 ตัวอย่างที่ 189-237

ฝึกและทดสอบ Model 5 รอบ โดยในแต่ละรอบ ใช้ 4 ส่วนเป็นข้อมูลฝึกฝน (Training data) และใช้ 1 ส่วนที่เหลือเป็นข้อมูลทดสอบ (Validation data)

ตัวอย่างการทำ 5 รอบ

รอบที่ 1
ข้อมูลฝึกฝน Fold 2, 3, 4, 5 (190 ตัวอย่าง)
ข้อมูลทดสอบ Fold 1 (47 ตัวอย่าง)

รอบที่ 2
ข้อมูลฝึกฝน Fold 1, 3, 4, 5 (190 ตัวอย่าง)
ข้อมูลทดสอบ Fold 2 (47 ตัวอย่าง)

รอบที่ 3
ข้อมูลฝึกฝน Fold 1, 2, 4, 5 (190 ตัวอย่าง)
ข้อมูลทดสอบ Fold 3 (47 ตัวอย่าง)

รอบที่ 4
ข้อมูลฝึกฝน Fold 1, 2, 3, 5 (190 ตัวอย่าง)
ข้อมูลทดสอบ Fold 4 (47 ตัวอย่าง)

รอบที่ 5
ข้อมูลฝึกฝน Fold 1, 2, 3, 4 (189 ตัวอย่าง)
ข้อมูลทดสอบ Fold 5 (48 ตัวอย่าง)

ในแต่ละรอบจะได้ค่าประสิทธิภาพของ Model (ความแม่นยำ) นำค่าความแม่นยำจากทั้ง 5 รอบ มาเฉลี่ยกัน เพื่อให้ได้ค่าประสิทธิภาพโดยรวมของ Model

ดังนั้นจำนวนครั้งทั้งหมดที่ Model จะถูกฝึกฝนและทดสอบจะเท่ากับ 81 (จำนวนชุด Parameter) * 5 (จำนวน Fold) = 405

print("Best parameters:", grid_search.best_params_)
print("Best cross-validation score:", grid_search.best_score_)

Best parameters: {'max_depth': 10, 'min_samples_leaf': 2, 'min_samples_split': 10, 'n_estimators': 300}
Best cross-validation score: 0.8224290780141844

สร้าง Tree ใหม่จาก X_train และ y_train ขนาด 237 ตัวอย่าง ด้วยชุด Parameter ที่ให้ผลลัพธ์ดีที่สุด (0.8224290780141844)

# ใช้ Model ที่ดีที่สุดจาก GridSearchCV
best_rf_classifier = grid_search.best_estimator_
y_pred_best = best_rf_classifier.predict(X_test)

print("\nBest Model Accuracy:", accuracy_score(y_test, y_pred_best))
print("\nBest Model Classification Report:\n", classification_report(y_test, y_pred_best))

Best Model Accuracy: 0.8333333333333334

หมายเหตุ เมื่อเราได้ Parameter ที่ดีที่สุดแล้ว เราสามารถใช้ข้อมูลทั้งหมดที่มีเพื่อสร้าง Model สุดท้าย ซึ่งรวมถึงทั้งข้อมูล Train และ Test (สำหรับกรณีที่เรามี Dataset ขนาดเล็ก) ซึ่งการแยกข้อมูล Train และ Test มีไว้สำหรับการประเมินประสิทธิภาพ

# สร้าง Model สุดท้ายโดยใช้ข้อมูลทั้งหมด
final_rf_classifier = RandomForestClassifier(**grid_search.best_params_, random_state=2)
final_rf_classifier.fit(X, y)  # ใช้ X และ y ทั้งหมด ไม่แบ่ง Train/Test

บันทึก Model ที่ดีที่สุด แล้วกด Download ไฟล์​ 'best_rf_model.joblib'

import joblib

joblib.dump(best_rf_classifier, 'best_rf_model.joblib')

สร้าง REST API ด้วย Flask web framework

ติดตั้ง Flask

pip install flask

สร้างไฟล์ Python สำหรับ API (app.py)

from flask import Flask, request, jsonify
import pandas as pd
import joblib

app = Flask(__name__)

# โหลด Model ที่ฝึกฝนแล้ว
model = joblib.load('best_rf_model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
    # รับข้อมูลจาก request
    data = request.json
    
    # แปลงข้อมูลเป็น DataFrame
    df = pd.DataFrame(data, index=[0])
    
    # ทำนายผลลัพธ์
    prediction = model.predict(df)
    
    # ส่งผลลัพธ์กลับ
    return jsonify({'prediction': int(prediction[0])})

if __name__ == '__main__':
    app.run(debug=True)

รัน API

python app.py

ทดสอบ API

curl -X POST -H "Content-Type: application/json" -d '{"age": 63, "sex": 1, "cp": 3, "trestbps": 145, "chol": 233, "fbs": 1, "restecg": 0, "thalach": 150, "exang": 0, "oldpeak": 2.3, "slope": 0, "ca": 0, "thal": 1}' http://localhost:5000/predict

เราสามารถนำ API ไปรันบน Google Colab เพื่อทดสอบได้ โดยการใช้ ngrok เพื่อสร้างการเชื่อมต่อชั่วคราว

สร้าง Notebook ใหม่บน Google Colab แล้วติดตั้ง Libary ที่จำเป็น

!pip install flask numpy pandas scikit-learn joblib pyngrok

Upload ไฟล์ 'best_rf_model.joblib' แล้วไปที่ https://dashboard.ngrok.com/get-started/your-authtoken เพื่อสมัครใช้งาน ngrok และขอ Token

สร้าง Flask app

from flask import Flask, request, jsonify
import pandas as pd
import joblib
import numpy as np
from pyngrok import ngrok

app = Flask(__name__)

# โหลด Model ที่ฝึกฝนแล้ว
model = joblib.load('best_rf_model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
    # รับข้อมูลจาก request
    data = request.json
    
    # แปลงข้อมูลเป็น DataFrame
    df = pd.DataFrame(data, index=[0])
    
    # ทำนายผลลัพธ์
    prediction = model.predict(df)
    
    # ส่งผลลัพธ์กลับ
    return jsonify({'prediction': int(prediction[0])})

# ตั้งค่า authtoken สำหรับ ngrok (แทนที่ด้วย authtoken ของคุณ)
ngrok.set_auth_token('775loMThQLej5NR4v0ejH9y25WQD9_3LrV9NMeVFRn5Hvimy3gB')

# เริ่มต้น Flask app และ ngrok
if __name__ == '__main__':
    # เริ่ม ngrok
    public_url = ngrok.connect(5000)
    print(f'Public URL: {public_url}')
    
    # เริ่ม Flask app
    app.run(host='0.0.0.0', port=5000)

สร้าง Notebook ใหม่บน Google Colab อีก Notebook แล้วทดสอบ API

import requests

# URL ที่ได้จาก ngrok
url = 'https://7abd-34-31-114-17.ngrok-free.app/predict'  # แทนที่ด้วย URL ที่ได้จากขั้นตอนก่อนหน้า

# ข้อมูลทดสอบ
data = {
    "age": 63, 
    "sex": 1, 
    "cp": 3, 
    "trestbps": 145, 
    "chol": 233, 
    "fbs": 1, 
    "restecg": 0, 
    "thalach": 150, 
    "exang": 0, 
    "oldpeak": 2.3, 
    "slope": 0, 
    "ca": 0, 
    "thal": 1
}

# ส่ง POST request
response = requests.post(url, json=data)

# แสดงผลลัพธ์
print(response.json())

{'prediction': 0}

การประยุกต์ใช้ Random Forest ในทางการแพทย์

  1. การทำนายความเสี่ยงของโรค เช่นในตัวอย่างที่ใช้ทำนายความเสี่ยงของโรคหัวใจ
  2. การวินิจฉัยโรค ใช้ข้อมูลทางคลินิกและผลการตรวจทางห้องปฏิบัติการเพื่อช่วยในการวินิจฉัยโรค
  3. การพยากรณ์โรค ทำนายความก้าวหน้าของโรคหรือโอกาสในการตอบสนองต่อการรักษา
  4. การค้นหาปัจจัยเสี่ยง ใช้ Feature Importance เพื่อระบุปัจจัยที่มีผลต่อการเกิดโรคมากที่สุด
  5. การแยกประเภทภาพทางการแพทย์ ช่วยในการวิเคราะห์ภาพ X-ray, MRI, หรือ CT scan เพื่อตรวจหาความผิดปกติ