अध्याय ६ · खण्ड II · 30 मिनेट
निर्माण, मूल्याङ्कन, र दोहोर्याउने
Build, फ्रेमिङ documentबाट काम गर्ने मोडेलसम्म। आकार परिचित लाग्नेछ किनभने Courses 02 र 03 ले तपाईंलाई चुपचाप तयार पारिरहेको छ। यो सबै सँगै मिल्ने ठाउँ हो।
तपाईंसँग फ्रेमिङ document छ। अर्को चरण build हो — खाली नोटबुकबाट काम गर्ने, ट्युन गरिएको, cross-validated मोडेलसम्म जुनले तपाईंले तोकेको सफलता मापदण्ड पूरा गर्छ। यो खण्डले चलिरहेको उदाहरणका रूपमा NEPSE दिशा पूर्वानुमानकर्तासँग पूरै कार्यप्रवाह हिँडाउँछ। आकार तपाईंले टुक्रामा देख्नुभएको केही हो; यो पहिलो पटक यो सबै जोडिन्छ। यो खण्डको अन्त्यसम्ममा तपाईंसँग वास्तविक, अन्त-देखि-अन्त ML परियोजना छ।
हरेक परियोजनाको आठ-चरणको आकार
अबदेखि हरेक ML परियोजना उही आठ चरण पछ्याउँछ। क्रम आत्मसात् गर्नुहोस्; विवरण फरक हुन्छन्, आकार हुँदैन।
- फ्रेम — पछिल्लो खण्डको deliverable।
- प्राप्त — कच्चा डाटा ल्याउनुहोस् र लोड गर्नुहोस्।
- अन्वेषण — EDA (Course 02 अध्याय 4)।
- सफा — छुटेको/डुप्लिकेट/खराब डाटा सम्हाल्नुहोस् (Course 02 अध्याय 3)।
- फिचर इन्जिनियर — मोडेलले प्रयोग गर्ने स्तम्भ बनाउनुहोस् (Course 02 अध्याय 5)।
- बाँड्नुहोस्, fit, मूल्याङ्कन — तपाईंको पहिलो इमानदार प्रदर्शन सङ्ख्या (Course 03 अध्याय 1)।
- दोहोर्याउनुहोस् — अन्य मोडेल कोसिस गर्नुहोस्, हाइपरपारामिटर ट्युन गर्नुहोस्, फिचर काट्नुहोस् (Course 03 अध्याय 5)।
- Cross-validate र अन्तिम गर्नुहोस् — भरपर्दो सङ्ख्या पाउनुहोस् (Course 03 अध्याय 5 खण्ड 3)।
अध्याय 6 को अन्तिम खण्डले नवौं चरण ओगट्छ — नतिजा इमानदारीसाथ प्रस्तुत गर्ने — तर build आफैका लागि, आठ चरण।
चरण 2 — प्राप्त
NEPSE का लागि: सार्वजनिक स्रोतमध्ये एकबाट दैनिक सूचक मानको CSV डाउनलोड गर्नुहोस् (NEPSE Alpha, Sharesansar — scraping अनुमति प्रमाणित गर्नुहोस्; Course 02 अध्याय 2 खण्ड 3 हेर्नुहोस्)। data/raw/nepse_daily.csv का रूपमा बचाउनुहोस्।
import pandas as pd
df = pd.read_csv("data/raw/nepse_daily.csv", parse_dates=["date"])
df = df.sort_values("date").reset_index(drop=True)
print(df.shape, df["date"].min(), df["date"].max())
पाँच वर्षको दैनिक डाटा लगभग 1,250 व्यापार पङ्क्ति हो। मध्यम तर काम गर्न मिल्ने।
चरण 3 — अन्वेषण
Course 02 अध्याय 4 खण्ड 1 को 30-मिनेटे बानी लागू गर्नुहोस्।
print(df.dtypes)
print(df.describe())
df["close"].plot(figsize=(12, 4))
# दैनिक रिटर्नको वितरण
df["return"] = df["close"].pct_change()
df["return"].hist(bins=80)
तपाईंले क्लासिक वित्तीय-डाटा ढाँचा देख्नुहुनेछ: लगभग सामान्य रिटर्न फ्याट-भन्दा-सामान्य पुच्छरसँग, कहिलेकाहीं volatility regimes, र सुस्त drift का लामो अवधि।
तीन अनुमान-र-जाँच प्रश्न:
- Q: सूचक कति अंश दिन माथि जान्छ? सम्भवतः 52% को नजिक — बजार सुस्त रूपमा माथि सर्छ। जाँच्नुहोस्।
- Q: उच्च volatility का cluster छन्?
return.abs().rolling(20).mean()प्लट गर्नुहोस् र हेर्नुहोस्। लगभग निश्चित रूपमा हो। - Q: उच्चतम-रिटर्न दिनहरू clustered छन्? 50 सबैभन्दा ठूला gain दिनहरू 50 सबैभन्दा ठूला loss दिनहरू नजिक छन् कि जाँच्नुहोस्। लगभग निश्चित रूपमा हो — volatility cluster हुन्छ।
यो 15 मिनेटले तपाईंको फिचर इन्जिनियरिङलाई आकार दिन्छ। तपाईंले डाटामा volatility regimes छन् सिक्नुहुन्छ, जसले rolling-volatility फिचरले मद्दत गर्ने सुझाव दिन्छ।
चरण 4 — सफा
दैनिक सूचक डाटाका लागि, सफा गर्ने हल्का हुन्छ:
NaNclose prices भएका पङ्क्ति हटाउनुहोस् (data feed त्रुटि)।- कुनै डुप्लिकेट मिति नभएको प्रमाणित गर्नुहोस्।
- मिति स्तम्भ राम्रोसँग क्रमबद्ध छ भन्ने सुनिश्चित गर्नुहोस्।
df = df.dropna(subset=["close"]).reset_index(drop=True)
assert df["date"].is_monotonic_increasing
assert not df["date"].duplicated().any()
तीन लाइन। केही परियोजनाका लागि सफा गर्ने सय लाइन हुनेछ; दैनिक NEPSE का लागि, तीन लाइन। प्रति परियोजना समायोजन गर्नुहोस्।
चरण 5 — फिचर इन्जिनियर
यो जहाँ डोमेन ज्ञानले तपाईंलाई सबैभन्दा बढी किन्दछ। भोलिको दिशा पूर्वानुमान गर्न:
import numpy as np
df["return_1d"] = df["close"].pct_change()
df["return_3d"] = df["close"].pct_change(3)
df["return_5d"] = df["close"].pct_change(5)
df["return_20d"] = df["close"].pct_change(20)
df["return_1d_lag1"] = df["return_1d"].shift(1)
df["return_1d_lag2"] = df["return_1d"].shift(2)
df["volatility_5d"] = df["return_1d"].rolling(5).std()
df["volatility_20d"] = df["return_1d"].rolling(20).std()
df["above_ma_20"] = (df["close"] > df["close"].rolling(20).mean()).astype(int)
df["above_ma_50"] = (df["close"] > df["close"].rolling(50).mean()).astype(int)
df["day_of_week"] = df["date"].dt.dayofweek
df["month"] = df["date"].dt.month
# लक्ष्य: के सूचक भोलि माथि गयो?
df["target_up_tomorrow"] = (df["close"].shift(-1) > df["close"]).astype(int)
# rolling फिचर र future-shifted लक्ष्यबाट NaN भएका पङ्क्ति हटाउनुहोस्
df = df.dropna().reset_index(drop=True)
एक दर्जन फिचर — विभिन्न horizon मा हालैका रिटर्न, rolling volatility, moving averages को सापेक्ष स्थिति, calendar फिचर। वित्तमा डोमेन विशेषज्ञले यिनलाई मानक छोटो-horizon technical फिचरका रूपमा चिन्नेछन्।
चरण 6 — पहिलो fit र मूल्याङ्कन
TimeSeriesSplit सँग पूर्ण pipeline (किनभने यो समय शृङ्खला हो):
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score, TimeSeriesSplit
from sklearn.metrics import f1_score, classification_report, confusion_matrix
features = [
"return_1d", "return_3d", "return_5d", "return_20d",
"return_1d_lag1", "return_1d_lag2",
"volatility_5d", "volatility_20d",
"above_ma_20", "above_ma_50",
"day_of_week", "month",
]
X = df[features]
y = df["target_up_tomorrow"]
# कालानुक्रमिक train/test
cutoff = int(len(df) * 0.8)
X_train, X_test = X.iloc[:cutoff], X.iloc[cutoff:]
y_train, y_test = y.iloc[:cutoff], y.iloc[cutoff:]
model = GradientBoostingClassifier(random_state=42)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))
print(confusion_matrix(y_test, predictions))
print(f"Baseline (always up): {y_test.mean():.3f}")
नमुना आउटपुट:
precision recall f1-score support
0 0.58 0.51 0.54 115
1 0.62 0.68 0.65 135
accuracy 0.60 250
macro avg 0.60 0.59 0.60 250
weighted avg 0.60 0.60 0.60 250
[[ 59 56]
[ 43 92]]
Baseline (always up): 0.540
मोडेल आधारभन्दा 6 प्रतिशत बिन्दु माथि छ। NEPSE दिशा पूर्वानुमानका लागि, त्यो उचित पहिलो प्रहार हो — बजारहरू गाह्रो छन्। सिक्का-उछाल भन्दा राम्रो, जादू होइन।
चरण 7 — दोहोर्याउनुहोस्
अध्याय 5 प्रयोग गरेर कोसिस गर्न स्पष्ट दुई-तीन दोहोरिने काम:
# धेरै मोडेल कोसिस गर्नुहोस्
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
models = {
"Logistic": LogisticRegression(max_iter=1000),
"Forest": RandomForestClassifier(n_estimators=200, random_state=42),
"GradBoost": GradientBoostingClassifier(random_state=42),
}
for name, m in models.items():
m.fit(X_train, y_train)
pred = m.predict(X_test)
print(f"{name:12s} F1={f1_score(y_test, pred):.3f}")
अनि RandomizedSearchCV सँग विजेतालाई ट्युन गर्नुहोस्:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_dist = {
"n_estimators": randint(50, 300),
"max_depth": randint(2, 8),
"learning_rate": uniform(0.01, 0.3),
"subsample": uniform(0.6, 0.4),
}
tscv = TimeSeriesSplit(n_splits=5)
search = RandomizedSearchCV(
GradientBoostingClassifier(random_state=42),
param_distributions=param_dist,
n_iter=30,
cv=tscv,
scoring="f1",
n_jobs=-1,
random_state=42,
)
search.fit(X_train, y_train)
print(f"Best params: {search.best_params_}")
print(f"CV F1: {search.best_score_:.3f}")
print(f"Test F1: {f1_score(y_test, search.predict(X_test)):.3f}")
तपाईं सामान्यतया 1-3 प्रतिशत बिन्दुको सुधार देख्नुहुनेछ। जादू होइन, तर अर्थपूर्ण — र अनुशासनयुक्त खोजबाट कमाइएको।
चरण 8 — Finalist लाई cross-validate
RandomizedSearchCV भित्रको CV ले तपाईंलाई पहिले नै भरपर्दो CV स्कोर दियो। राखिएको परीक्षण F1 ले तपाईंलाई साँच्चै इमानदार मूल्याङ्कन दिन्छ। Sanity-check:
# खोजबाट CV स्कोर
cv_score = search.best_score_
# परीक्षण सेट स्कोर (साँच्चै राखिएको)
test_score = f1_score(y_test, search.predict(X_test))
# यदि तिनी ~0.03 भन्दा बढी असहमत छन् भने, छानबिन गर्नुहोस्
gap = abs(cv_score - test_score)
print(f"CV F1: {cv_score:.3f} Test F1: {test_score:.3f} Gap: {gap:.3f}")
~0.05 भन्दा माथिको खाडलले सामान्यतया तालिम र परीक्षणबीच वितरण सर्ने (समय-शृङ्खला डाटासँग सम्भव — बजार बदलिन्छ), वा सानो/अप्रतिनिधि परीक्षण सेट, वा लुकेको लीकेज सुझाव दिन्छ।
छोटो परावर्तन — के भर्खर भयो
एक कदम पछि हट्नुहोस्। एक खण्डमा, तपाईं data/raw/nepse_daily.csv बाट ट्युन गरिएको gradient-boosting वर्गीकरणकर्तासँग आधारभन्दा माथि परीक्षण-सेट F1 स्कोरसहित इमानदार CV मूल्याङ्कनमा पुग्नुभयो। पूरा कुरा तपाईंको टाइप गर्ने गतिमा निर्भर दुई देखि चार घण्टाको काम हो।
त्यो कार्यरत ML परियोजनाको आकार हो। उही आकार — फ्रेम, प्राप्त, अन्वेषण, सफा, इन्जिनियर, fit, दोहोर्याउने, प्रमाणित गर्ने — तपाईंले आफ्नो करियरको बाँकी अवधिभर बनाउने हरेक परियोजनामा लागू हुन्छ, डोमेन जे भए पनि। विशिष्टता बदलिन्छ। ढाँचा रहन्छ।
आफ्नो बुझाइ जाँच्नुहोस्
Quick check
—NEPSE उदाहरणमा, लक्ष्य `close.shift(-1) > close` छ। एक टिम सदस्यले `close.shift(-1)` लाई फिचरका रूपमा थप्न सुझाव दिन्छन्, यसले मोडेललाई मद्दत गर्नेछ भन्ने तर्क गर्दै। के गलत छ?
Quick check
—NEPSE दिशा पूर्वानुमानकर्ता ट्युन गरेपछि, cross-validated F1 0.62 र राखिएको परीक्षण F1 0.49 छ। सम्भावित कारण र प्रतिक्रिया के हो?
अब के आउँछ
मोडेल build र मूल्याङ्कन भयो। अन्तिम चरण — स्किप गर्न सजिलो र सबैभन्दा उच्च-लिभरेज — नतिजाहरू इमानदारीसाथ प्रस्तुत गर्ने हो। सङ्ख्या, चार्ट, सीमाबद्धता, र तपाईं के अर्को build गर्नुहुन्थ्यो। त्यो अध्याय 6 को अर्को र अन्तिम खण्ड हो।