top of page
Portfolio Management Toolkit Notesสารบัญ
Portfolio Management Toolkit Notesพิมพ์หน้านี้

Machine Learning · เริ่มต้นเรียนรู้จากข้อมูล · บทย่อย 1

Machine Learning เรียนรู้อะไรจากข้อมูล

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

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

บทนี้ใช้ข้อมูลสมมติทั้งหมดเพื่อฝึกอ่านตารางและคำนวณ ไม่มีตัวเลขผลตอบแทนของหุ้นหรือกองทุนจริง โค้ดใช้ NumPy และ pandas รันเรียงจากต้นหน้าใน Notebook ใหม่ได้ หากยังไม่คุ้นกับ array และ DataFrame ให้อ่านพื้นฐาน Python สำหรับผลตอบแทนก่อน

หนึ่งแถวคือสิ่งที่เราสังเกตหนึ่งครั้ง

เริ่มด้วยผลตอบแทนรายเดือนของสินทรัพย์สมมติหนึ่งตัว ตั้งแต่มกราคมถึงกันยายน 2021 หนึ่งแถวหมายถึงหนึ่งเดือนและใช้วันสิ้นเดือนเป็นชื่อแถว ตัวเลข 0.02 หมายถึงผลตอบแทน 2% ของเดือนนั้น ไม่ใช่ราคาสินทรัพย์

pd.date_range สร้างวันที่ต่อเนื่อง periods=9 ให้เก้าวันที่ และ freq="ME" เลือกวันสิ้นเดือน pd.Series จับคู่ผลตอบแทนกับวันที่ ส่วน .to_frame() เปลี่ยน Series เป็นตารางหนึ่งคอลัมน์สำหรับแสดงผล

import numpy as np
import pandas as pd

months = pd.date_range("2021-01-31", periods=9, freq="ME")
ml_returns = pd.Series(
    [0.02, -0.01, 0.03, -0.02, 0.01, -0.04, 0.02, 0.01, 0.03],
    index=months, name="return",
)
print(ml_returns.to_frame())
print("Number of monthly observations:", len(ml_returns))

เราได้ observations เก้าครั้ง เช่น มีนาคมให้ผลตอบแทน 3% และเมษายนให้ −2% ถ้าเปลี่ยนเป็นข้อมูลหลายหุ้น หนึ่งแถวอาจหมายถึงหุ้นหนึ่งตัวในเดือนหนึ่ง ต้องระบุหน่วยสังเกตให้ชัดก่อนแบ่งข้อมูล หุ้นหลายตัวในวันเดียวกันอาจรับข่าวเดียวกัน จึงไม่ควรนับว่าเป็นหลักฐานอิสระทั้งหมดเพียงเพราะมีหลายแถว

Feature เป็นข้อมูลเข้า ส่วน Target เป็นคำตอบที่ต้องการประมาณ

Feature คือข้อมูลที่ส่งเข้าโมเดล เช่น ผลตอบแทนล่าสุดหรือความผันผวนย้อนหลัง ส่วน target หรือ label คือคำตอบที่ใช้ฝึกและตรวจโมเดล เช่น ผลตอบแทนเดือนถัดไป สัญลักษณ์ที่ใช้บ่อยคือ XX สำหรับตาราง features และ yy สำหรับ target

สำหรับคำถาม “หลังสิ้นเดือนนี้ เราจะพยากรณ์ผลตอบแทนเดือนหน้าอย่างไร” ลองสร้างสอง features คือผลตอบแทนเดือนล่าสุดกับ SD ของผลตอบแทนสามเดือนล่าสุด หน้าต่างสามเดือนรวมเดือนปัจจุบันที่เพิ่งจบแล้ว จึงใช้เฉพาะอดีตและปัจจุบัน ณ เวลาพยากรณ์ ส่วน target เป็นผลตอบแทนเดือนถัดไป

.rolling(3).std(ddof=0) เลื่อนหน้าต่างยาวสาม observations แล้วหาความผันผวนของตัวเลขในหน้าต่างโดยหาร variance ด้วย 3 นี่เป็นนิยาม feature ที่เราเลือกเพื่อสาธิต ไม่ได้อ้างว่าเป็นตัวประมาณ variance ที่ไม่มีอคติหรือเป็นการพยากรณ์ความเสี่ยงที่ดีที่สุด หน่วยยังเป็น SD ของผลตอบแทนรายเดือน

.shift(-1) นำค่าของแถวถัดไปมาใส่ที่แถวปัจจุบันเพื่อสร้าง target ในตารางย้อนหลัง การเลื่อนแบบนี้ใช้สร้างคำตอบสำหรับการฝึกได้ แต่ห้ามส่งคอลัมน์ที่เห็นอนาคตนี้เข้าไปเป็น feature

feature_table = pd.DataFrame({
    "recent_return": ml_returns,
    "volatility_3m": ml_returns.rolling(3).std(ddof=0),
    "next_return": ml_returns.shift(-1),
})
feature_table["label_known_on"] = feature_table.index + pd.offsets.MonthEnd(1)
labeled_data = feature_table.dropna(
    subset=["recent_return", "volatility_3m", "next_return"]
)
print(labeled_data.round(4))

มกราคมและกุมภาพันธ์ยังไม่มีผลตอบแทนครบสามเดือน จึงมี NaN ใน feature ความผันผวน ส่วนกันยายนไม่มีผลตอบแทนเดือนตุลาคมในตาราง จึงมี NaN ใน target เราลบเฉพาะแถวที่ยังไม่ครบสำหรับการฝึกด้วย .dropna(subset=...) เหลือหกแถวตั้งแต่มีนาคมถึงสิงหาคม

แถวมีนาคมมี recent_return=0.03, volatility_3m ประมาณ 0.016997 หรือ 1.6997% และ next_return=-0.02 วันที่ใน label_known_on คือ 30 เมษายน เพราะหลังสิ้นมีนาคมเรายังไม่รู้ผลตอบแทนเมษายน pd.offsets.MonthEnd(1) ใช้ขยับจากวันสิ้นเดือนหนึ่งไปวันสิ้นเดือนถัดไป

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

ณ วันฝึกโมเดล เรารู้คำตอบของแถวใดแล้ว

สมมติว่าฝึกโมเดลหลังสิ้นวันที่ 30 มิถุนายน 2021 เรารู้ผลตอบแทนถึงเดือนมิถุนายน แต่ target ของแถวมิถุนายนคือผลตอบแทนกรกฎาคม ซึ่งยังไม่เกิด จึงใช้แถวมีนาคม เมษายน และพฤษภาคมเป็นตัวอย่างที่มีคำตอบครบได้ ส่วน features ของมิถุนายนพร้อมสำหรับใช้พยากรณ์กรกฎาคม

เงื่อนไข label_known_on <= cutoff สร้างชุดค่า True/False แล้ว .loc[...] เลือกเฉพาะแถวที่เป็น True เราเก็บ cutoff เป็นวันที่ด้วย pd.Timestamp เพื่อเปรียบเทียบวันโดยตรง

cutoff = pd.Timestamp("2021-06-30")
known_at_cutoff = labeled_data.loc[labeled_data["label_known_on"] <= cutoff]
features_at_cutoff = feature_table.loc[cutoff, ["recent_return", "volatility_3m"]]
print("Rows with known answers:", known_at_cutoff.index.strftime("%Y-%m").tolist())
print("Features available at cutoff:")
print(features_at_cutoff)

ผลรายการแถวคือ ['2021-03', '2021-04', '2021-05'] ส่วน features ที่ใช้พยากรณ์ ณ สิ้นมิถุนายนคือผลตอบแทนล่าสุด −4% และความผันผวนสามเดือนประมาณ 2.0548% ข้อมูลเพียงสามแถวนี้เล็กเกินกว่าจะใช้ประเมินคุณภาพโมเดลจริง เราใช้เพื่อให้ตรวจลำดับเวลาได้ด้วยตา

Data leakage เกิดเมื่อกระบวนการฝึกได้รับข้อมูลที่ยังไม่ควรมีในเวลาที่ต้องตัดสินใจ เช่น นำ target กรกฎาคมมาฝึกก่อนกรกฎาคมจบ หรือคำนวณค่าเฉลี่ยเพื่อปรับสเกล features จากทั้งอดีตและอนาคต บทการทดสอบโมเดลจะตรวจกรณีเหล่านี้ด้วยข้อมูลที่ยาวขึ้น

คำถามต่างกัน ทำให้ชนิดของคำตอบต่างกัน

Supervised learning ใช้คู่ตัวอย่าง (X,y)(X,y) เพื่อเรียนความสัมพันธ์ระหว่างข้อมูลเข้ากับคำตอบ ถ้า yy เป็นตัวเลขต่อเนื่อง เช่น ผลตอบแทนเดือนหน้า เรียกโจทย์ว่า regression ถ้า yy เป็นกลุ่ม เช่น ผลตอบแทนบวกหรือไม่บวก เรียกว่า classification

สองโจทย์นี้ใช้ features ชุดเดียวกันได้ แต่รักษารายละเอียดของคำตอบต่างกัน การเปลี่ยนผลตอบแทนเป็น 0/1 จะทิ้งข้อมูลว่ากำไรหรือขาดทุนมากเท่าไร เช่น −0.1% กับ −20% กลายเป็น label 0 เหมือนกันหากกำหนด “บวก” ว่าต้องมากกว่าศูนย์

เลือกสองคอลัมน์ features ด้วยวงเล็บสองชั้นเพื่อให้ได้ DataFrame แล้ว .to_numpy() แปลงเป็น array ตัว X_features.shape จะแสดงจำนวนแถวและคอลัมน์ ส่วน .astype(int) เปลี่ยน False/True เป็น 0/1

X_features = labeled_data[["recent_return", "volatility_3m"]].to_numpy()
target_return = labeled_data["next_return"].to_numpy()
target_positive = (target_return > 0).astype(int)
print("X shape:", X_features.shape)
print("Regression target:", target_return)
print("Classification target:", target_positive)

ได้ XX ขนาด (6, 2) คือหก observations สอง features ส่วน regression target คือ [-0.02, 0.01, -0.04, 0.02, 0.01, 0.03] และ classification target คือ [0, 1, 0, 1, 1, 1] แต่ละตำแหน่งของ yy ต้องตรงกับแถวเดียวกันของ XX เสมอ

ชื่อ logistic regression อาจทำให้สับสน เพราะในงาน ML โดยทั่วไปโมเดลนี้ประมาณความน่าจะเป็นของ class แล้วใช้จำแนกประเภท ไม่ได้หมายความว่าคำตอบสุดท้ายต้องเป็นผลตอบแทนต่อเนื่อง รายละเอียดอยู่ในบทเริ่มพยากรณ์

จากค่าพยากรณ์สู่ความผิดพลาดที่วัดได้

สมมติให้มีกฎพยากรณ์ผลตอบแทนหนึ่งเดือน

y^=0.002+0.25x1,\hat y=0.002+0.25x_1,

โดย x1x_1 คือผลตอบแทนเดือนล่าสุดและ y^\hat y คือค่าพยากรณ์เดือนถัดไป หมวกบน yy ใช้แยกค่าที่พยากรณ์ออกจากคำตอบที่เกิดจริง ค่าคงที่ 0.002 คือ 0.2 จุดเปอร์เซ็นต์ และสัมประสิทธิ์ 0.25 หมายความว่าเมื่อ feature เพิ่มหนึ่งจุดเปอร์เซ็นต์ ค่าพยากรณ์เพิ่ม 0.25 จุดเปอร์เซ็นต์ตามกฎนี้

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

Mean squared error หรือ MSE หาค่าเฉลี่ยของความคลาดเคลื่อนยกกำลังสอง:

MSE⁡=1n∑i=1n(y^i−yi)2,RMSE⁡=MSE⁡.\operatorname{MSE}=\frac1n\sum_{i=1}^{n}(\hat y_i-y_i)^2, \qquad \operatorname{RMSE}=\sqrt{\operatorname{MSE}}.

การยกกำลังสองทำให้ข้อผิดพลาดบวกและลบไม่หักล้างกัน และให้โทษมากขึ้นกับความผิดพลาดขนาดใหญ่ RMSE กลับมาอยู่ในหน่วยเดียวกับ yy ส่วน MSE มีหน่วยยกกำลังสอง X_features[:, 0] เลือกทุกแถวของคอลัมน์แรก โดย Python เริ่มนับคอลัมน์จากศูนย์

fixed_predictions = 0.002 + 0.25 * X_features[:, 0]
fixed_errors = fixed_predictions - target_return
mse_fixed = np.mean(fixed_errors ** 2)
rmse_fixed = np.sqrt(mse_fixed)
print("Predicted monthly returns (%):", np.round(100 * fixed_predictions, 2))
print(f"MSE in squared decimal returns: {mse_fixed:.8f}")
print(f"RMSE in monthly return units: {rmse_fixed:.4%}")

ค่าพยากรณ์คือ [0.95, -0.30, 0.45, -0.80, 0.70, 0.45]% ต่อเดือน MSE เท่ากับประมาณ 0.00074379 และ RMSE 2.7273 จุดเปอร์เซ็นต์ต่อเดือน แถวแรกพยากรณ์ +0.95% แต่เกิด −2% จึงผิด 0.0095−(−0.02)=0.02950.0095-(-0.02)=0.0295 หรือ 2.95 จุดเปอร์เซ็นต์ พจน์ของแถวนี้ในผลรวม squared errors คือ 0.02952=0.000870250.0295^2=0.00087025

การเห็น RMSE ตัวเดียวไม่บอกว่าคุ้มใช้หรือไม่ ต้องเทียบกับวิธีพื้นฐาน เช่น พยากรณ์ด้วยค่าเฉลี่ยจากข้อมูลฝึก และวัดบนข้อมูลที่ไม่ได้ใช้เลือกโมเดล หน่วยที่ต่างกันก็ทำให้ MSE ต่างกัน หากเปลี่ยนผลตอบแทนจากทศนิยมเป็นตัวเลขเปอร์เซ็นต์ MSE จะโต 1002=10,000100^2=10{,}000 เท่า โดยคุณภาพคำตอบเดิมไม่ได้เปลี่ยน

ความน่าจะเป็นกับคำตอบว่าใช่หรือไม่ใช่

ในโจทย์ผลตอบแทนบวก โมเดลอาจให้ความน่าจะเป็น p=P(y=1∣X)p=P(y=1\mid X) เช่น 0.65 แล้วเราจึงเลือกเกณฑ์หรือ threshold เพื่อเปลี่ยนเป็นคำตอบ 0/1 ถ้าใช้เกณฑ์ p≥0.5p\geq0.5 ให้ทายเป็น 1 ค่าทั้ง 0.51 และ 0.99 จะได้ class เดียวกัน แม้ระดับความมั่นใจต่างกัน

ตัวเลขความน่าจะเป็นในตัวอย่างนี้กำหนดขึ้นเพื่อฝึกอ่านผล ยังไม่ได้มาจากโมเดลที่ fit แล้ว == เปรียบเทียบว่าคำตอบตรงกันหรือไม่ และ np.mean ของ array True/False ให้สัดส่วนที่ตอบถูกหรือ accuracy

supplied_probabilities = np.array([0.35, 0.65, 0.60, 0.70, 0.40, 0.80])
positive_predictions = (supplied_probabilities >= 0.5).astype(int)
classification_accuracy = np.mean(positive_predictions == target_positive)
print("Predicted classes:", positive_predictions)
print("Observed classes:", target_positive)
print(f"Accuracy: {classification_accuracy:.2%}")

ได้คำตอบ [0, 1, 1, 1, 0, 1] ถูกสี่จากหกแถว หรือ 66.67% พลาดแถวที่ผลตอบแทนจริง −4% และแถวที่จริง +1% โดย accuracy นับการพลาดทั้งสองครั้งเป็นหนึ่งเท่ากัน หากความเสียหายของสองกรณีต่างกันต้องนำมาพิจารณาตอนออกแบบเกณฑ์ตัดสินใจด้วย

โมเดลที่ให้ความน่าจะเป็นถูก calibration ควรมีสัดส่วนเหตุการณ์ที่เกิดจริงสอดคล้องกับค่าที่รายงานเมื่อมีตัวอย่างมากพอ เช่น กลุ่มที่พยากรณ์ใกล้ 0.7 ควรเกิดเหตุการณ์ใกล้ 70% การเรียกตัวเลขว่า probability หรือมี accuracy สูง ยังไม่พิสูจน์คุณสมบัตินี้ บทถัดไปจะใช้ log loss เพื่อตรวจคุณภาพคำตอบแบบความน่าจะเป็นด้วย

ทายทิศทางได้ ไม่ได้กำหนดว่าควรลงทุนเท่าไร

สมมติว่าเรารู้จริงในโลกตัวอย่างว่าสินทรัพย์มีโอกาส 60% ที่จะได้ +2% และโอกาส 40% ที่จะเสีย −4% ความน่าจะเป็นของผลตอบแทนบวกเกินครึ่ง แต่ผลตอบแทนคาดหวังคือ

E[r]=0.60(0.02)+0.40(−0.04)=−0.004=−0.4%.E[r]=0.60(0.02)+0.40(-0.04)=-0.004=-0.4\%.

การตัดสินใจลงทุนต้องใช้ทั้งขนาดกำไร ขนาดขาดทุน ความน่าจะเป็น ต้นทุน และความเสี่ยงที่ยอมรับได้ ถ้าคิดต้นทุนเพิ่มเติม 0.1% ของเงินลงทุน ผลตอบแทนคาดหวังหลังต้นทุนในตัวอย่างจะเป็น −0.5%

outcome_returns = np.array([0.02, -0.04])
outcome_probabilities = np.array([0.60, 0.40])
expected_gross_return = outcome_probabilities @ outcome_returns
assumed_cost = 0.001
expected_net_return = expected_gross_return - assumed_cost
print(f"Probability of a positive return: {outcome_probabilities[0]:.0%}")
print(f"Expected gross return: {expected_gross_return:.2%}")
print(f"Expected net return: {expected_net_return:.2%}")

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

Reinforcement learning ขยายไปสู่โจทย์ที่มีสถานะ การกระทำ และผลตอบแทนของการกระทำต่อเนื่อง เช่น เลือกน้ำหนักแล้วสังเกตมูลค่าพอร์ตกับต้นทุนรอบถัดไป การกำหนด reward ทำให้ระบบเรียนว่าจะปรับการกระทำอย่างไรในสภาพแวดล้อมที่ใช้ฝึก แต่คุณภาพยังขึ้นกับสภาพแวดล้อม สมมติฐาน และการทดสอบ ระบบที่เรียนได้ดีในแบบจำลองตลาดอาจทำงานต่างออกไปในตลาดจริง คอร์สช่วงต้นแนะนำแนวคิดนี้โดยยังไม่ได้สอนตัวแก้ reinforcement learning เราจะใช้การแยก “ค่าพยากรณ์” ออกจาก “กฎตัดสินใจ” เป็นฐานก่อน

เมื่อไม่มี Label เราค้นหารูปแบบอะไรได้บ้าง

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

จัดกลุ่มจากความใกล้กัน

สมมติ A–D มี annual volatility เท่ากับ 10%, 12%, 30% และ 32% ถ้ากำหนดจุดศูนย์กลางสองกลุ่มไว้ที่ 11% กับ 31% เราสามารถจัดแต่ละตัวเข้ากลุ่มที่ใกล้ที่สุด นี่คือขั้นตอน assignment ในการจัดกลุ่ม โดยยังไม่ใช่การรันอัลกอริทึม K-means ครบทุกขั้น

[:, None] เปลี่ยน array สี่ค่าเป็นสี่แถวหนึ่งคอลัมน์ ส่วน [None, :] ทำให้ศูนย์กลางเป็นหนึ่งแถวสองคอลัมน์ เมื่อลบกัน NumPy จึงสร้างระยะทางครบสี่สินทรัพย์คูณสองกลุ่ม argmin(axis=1) คืนตำแหน่งของระยะทางต่ำสุดในแต่ละแถว

cluster_vols = np.array([0.10, 0.12, 0.30, 0.32])
cluster_centers = np.array([0.11, 0.31])
cluster_distances = np.abs(cluster_vols[:, None] - cluster_centers[None, :])
cluster_assignments = cluster_distances.argmin(axis=1)
print("Distances in annual-volatility units:")
print(np.round(cluster_distances, 2))
print("Group labels:", cluster_assignments)

ได้กลุ่ม [0, 0, 1, 1] หมายถึง A/B อยู่ด้วยกันและ C/D อยู่ด้วยกัน เลข 0 กับ 1 เป็นชื่อกลุ่ม จะสลับชื่อก็ยังเป็นการแบ่งกลุ่มเดียวกัน ผลนี้บอกเพียงว่า volatility ใกล้กันตาม feature ที่ใช้ ไม่ได้บอกว่า correlation สูง ผลตอบแทนเท่ากัน หรือควรแบ่งเงินในกลุ่มเท่ากัน หากเพิ่มหลาย features ต้องพิจารณาสเกลก่อนวัดระยะทาง รายละเอียดจะต่อในหัวข้อ asset clustering

ลดจำนวนมิติด้วยการผสม Features

Principal component analysis หรือ PCA สร้างแกนใหม่จากส่วนผสมเชิงเส้นของ features เพื่อเก็บความแปรปรวนของข้อมูลไว้มากที่สุดตามจำนวนแกนที่เลือก ค่าที่ได้เป็น feature ใหม่ เช่น z=(x1+x2)/2z=(x_1+x_2)/\sqrt2 จึงอาจใช้ข้อมูลจากทั้งสองคอลัมน์พร้อมกัน แม้ลดจากสองคอลัมน์เหลือคอลัมน์เดียว

ดูจุดสมมติสี่จุด [-2,-1], [-1,-2], [1,2], [2,1] ซึ่งเป็น features ไม่มีหน่วยที่จัดสเกลไว้แล้วและมีค่าเฉลี่ยแต่ละคอลัมน์เป็นศูนย์ จุดส่วนใหญ่เรียงไปตามแนวจากซ้ายล่างไปขวาบน เราจึงลองฉายลงแกนที่มีทิศทาง [1,1]/2[1,1]/\sqrt2 ตัวหารทำให้ความยาวของ vector ทิศทางเท่ากับหนึ่ง

pca_points = np.array([[-2.0, -1.0], [-1.0, -2.0], [1.0, 2.0], [2.0, 1.0]])
pca_direction = np.array([1.0, 1.0]) / np.sqrt(2)
pca_scores = pca_points @ pca_direction
pca_reconstruction = pca_scores[:, None] * pca_direction
pca_explained_fraction = np.sum(pca_reconstruction ** 2) / np.sum(pca_points ** 2)
print("One-dimensional scores:", np.round(pca_scores, 4))
print("Reconstructed points:", pca_reconstruction)
print(f"Fraction of centered variation retained: {pca_explained_fraction:.1%}")

คะแนนหนึ่งมิติประมาณ [-2.1213, -2.1213, 2.1213, 2.1213] เมื่อนำกลับมาเป็นสองมิติได้ [-1.5,-1.5] สองจุดและ [1.5,1.5] สองจุด เราสูญเสียความแตกต่างภายในแต่ละคู่ แต่เก็บผลรวมกำลังสองรอบค่าเฉลี่ยได้ 18/20=9018/20=90% ในตัวอย่างนี้แกนที่เลือกตรงกับ principal component แรกจริง ส่วน PCA ของข้อมูลทั่วไปต้องคำนวณทิศทางจากข้อมูลฝึก

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

ความสัมพันธ์ในรูปเครือข่าย

อีกวิธีคือให้สินทรัพย์เป็นจุดหรือ node แล้วเชื่อมคู่ที่มีความสัมพันธ์ตามเกณฑ์ เช่น correlation เกิน 0.6 ตาราง 0/1 ที่บอกว่าคู่ใดเชื่อมกันเรียกว่า adjacency matrix เราใช้ตัวอย่างสามสินทรัพย์ที่กำหนด correlation ขึ้นเอง และไม่นับการเชื่อมสินทรัพย์กับตัวเอง

corr_map = np.array([[1.0, 0.8, 0.1], [0.8, 1.0, 0.2], [0.1, 0.2, 1.0]])
network_adjacency = (corr_map > 0.6).astype(int)
np.fill_diagonal(network_adjacency, 0)
network_degrees = network_adjacency.sum(axis=1)
print("Adjacency matrix:")
print(network_adjacency)
print("Number of connections:", network_degrees)

ผลมีเส้นเชื่อมเพียง A–B จึงได้จำนวนเพื่อนบ้าน [1,1,0] เกณฑ์ 0.6 เป็นค่าที่เลือกเพื่อสาธิต เมื่อเปลี่ยนเกณฑ์เครือข่ายก็เปลี่ยน และตำแหน่งที่โปรแกรมวาดจุดไว้กลางภาพไม่ใช่หลักฐานว่าสินทรัพย์นั้นมีอิทธิพลเชิงเหตุผลต่อระบบ การศึกษาความสัมพันธ์แบบมีเงื่อนไขจะใช้แนวคิด partial correlation เพิ่มเติมในบทเครือข่าย

เลือกข้อมูลให้ตรงกับงานที่จะใช้

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

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

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

แบบฝึกหัดพร้อมเฉลย

1. บอกหน่วยสังเกตให้ครบ

ถ้าตารางมี 100 หุ้นและ 12 เดือน แถวหนึ่งแทนหุ้นหนึ่งตัวในเดือนหนึ่ง จะมี observations กี่แถว และถือว่าทั้งหมดเป็นอิสระต่อกันได้ทันทีหรือไม่

เฉลย: ถ้าครบทุกคู่จะมี 100×12=1,200100\times12=1{,}200 แถว แต่หุ้นในเดือนเดียวกันอาจรับปัจจัยตลาดร่วมกัน และหุ้นตัวเดิมอาจมีความสัมพันธ์ข้ามเวลา จำนวนแถวจึงต่างจากจำนวนหลักฐานอิสระ

2. ผลตอบแทนเดือนหน้าควรอยู่ใน X หรือ y

ต้องการพยากรณ์ผลตอบแทนกรกฎาคมหลังสิ้นมิถุนายน คอลัมน์ next_return ของแถวมิถุนายนใช้เป็น feature ได้หรือไม่

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

3. ผลตอบแทนศูนย์อยู่ Class ใด

ใช้กฎ (target_return > 0).astype(int) ถ้า target เท่ากับศูนย์จะได้ class ใด และกรณี target หายควรตีความเหมือนกันหรือไม่

เฉลย: ผลตอบแทนศูนย์เป็น class 0 เพราะไม่ได้มากกว่าศูนย์ ส่วนข้อมูลหายยังไม่รู้คำตอบ จึงต้องจัดการ missing target ก่อนเปรียบเทียบ ในตัวอย่างเราเลือกแถวที่ target ครบแล้วจึงสร้าง class เพื่อไม่ให้ NaN ถูกตีความเป็นคำตอบที่ไม่บวก

4. ตรวจ Error ของแถวแรก

กฎให้ y^=0.002+0.25(0.03)=0.0095\hat y=0.002+0.25(0.03)=0.0095 ขณะที่ y=−0.02y=-0.02 จงหา error และ squared error

เฉลย: Error ตามนิยามค่าพยากรณ์ลบค่าจริงคือ 0.0295 หรือ 2.95 จุดเปอร์เซ็นต์ และ squared error เท่ากับ 0.00087025 หน่วยทศนิยมผลตอบแทนยกกำลังสอง Error บวกในที่นี้หมายถึงพยากรณ์สูงกว่าที่เกิดจริง

5. โอกาสกำไรสูงกว่าครึ่งเพียงพอหรือไม่

สินทรัพย์มีโอกาส 70% ที่ได้ +1% และ 30% ที่เสีย −5% ถ้าไม่คิดต้นทุน ผลตอบแทนคาดหวังเป็นเท่าไร

เฉลย: 0.70(0.01)+0.30(−0.05)=−0.0080.70(0.01)+0.30(-0.05)=-0.008 หรือ −0.8% ต่อช่วง โอกาสเกิดกำไรกับผลตอบแทนคาดหวังจึงต้องคำนวณแยกกัน

6. สลับชื่อกลุ่มทำให้ผลเปลี่ยนหรือไม่

ผลจัดกลุ่ม [0,0,1,1] เปลี่ยนเป็น [1,1,0,0] โดยสมาชิกไม่เปลี่ยน ถือว่าเป็นการแบ่งกลุ่มใหม่หรือไม่

เฉลย: เป็นการแบ่งเดียวกัน เพราะ A/B ยังอยู่ด้วยกันและ C/D ยังอยู่ด้วยกัน เลขกลุ่มเป็นป้ายชื่อ ไม่ได้หมายถึงอันดับผลตอบแทนหรือระดับคุณภาพ

7. PCA เก็บ Variation 90% แปลว่าทายถูก 90% หรือไม่

เฉลย: ไม่ใช่ ตัวเลข 90% มาจาก 18/2018/20 ซึ่งเปรียบเทียบผลรวมกำลังสองของข้อมูลที่สร้างกลับได้กับข้อมูลเดิมที่ลบค่าเฉลี่ยแล้ว ตัวอย่างไม่มี target จึงยังไม่ได้วัดความแม่นยำในการพยากรณ์ใด ๆ

8. Correlation Network ยืนยันเหตุและผลได้หรือไม่

ในตัวอย่าง A เชื่อมกับ B เพราะ correlation 0.8 เราสรุปได้หรือไม่ว่าข่าวของ A ทำให้ B เปลี่ยนราคา

เฉลย: สรุปไม่ได้จาก correlation อย่างเดียว ทั้งสองอาจรับข่าวตลาดร่วมกันหรือมีปัจจัยอื่นที่ไม่ได้ใส่ในข้อมูล เส้นเชื่อมบอกเพียงว่าผ่านเกณฑ์ความสัมพันธ์ที่เรากำหนด ส่วนข้อสรุปเชิงเหตุและผลต้องใช้ข้อมูลและการออกแบบการศึกษาที่รองรับคำถามนั้น

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

แหล่งที่มาและขอบเขต

อ่าน Transcript ภาษาอังกฤษเต็มเมื่อ 3 ตุลาคม 2026 ของ Welcome, Introduction to machine-learning, Financial applications, Supervised learning, Unsupervised learning และ Challenges ahead ในคอร์ส Python and Machine Learning for Asset Management ใช้เป็นแนวหัวข้อและบริบท ไม่ได้นำตัวอย่างบุคคล ข้อมูลตลาด หรือข้อกล่าวอ้างประสิทธิภาพในอดีตมาใช้เป็นผลทดลองของบทนี้

คำอธิบาย PCA ตรวจประกอบกับ scikit-learn 1.6: PCA และข้อควรระวังการเตรียมข้อมูลกับ Common pitfalls: Data leakage สมการ ตาราง และโค้ดในหน้านี้สร้างขึ้นใหม่เพื่อให้ตรวจเลขและลำดับเวลาได้โดยไม่ใช้ไฟล์ส่วนตัวของคอร์ส

ค้นหาในหนังสือ

ปิด
คำค้นหา

© 2023 by QuantCorner, Thailand.

bottom of page